Skip to content

Commit eafb11a

Browse files
authored
Add a developer ui page for runtime feature flags (#10982)
1 parent 215a87c commit eafb11a

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

ydb/core/base/feature_flags_service.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ namespace NKikimr {
1616
"expect EvEnd < EventSpaceEnd(TKikimrEvents::ES_FEATURE_FLAGS)");
1717

1818
struct TEvSubscribe : public TEventLocal<TEvSubscribe, EvSubscribe> {
19+
TString Description;
20+
1921
TEvSubscribe() = default;
22+
23+
explicit TEvSubscribe(const TString& description)
24+
: Description(description)
25+
{}
2026
};
2127

2228
struct TEvUnsubscribe : public TEventLocal<TEvUnsubscribe, EvUnsubscribe> {

ydb/core/cms/console/feature_flags_configurator.cpp

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
#include <ydb/core/base/appdata_fwd.h>
55
#include <ydb/core/base/feature_flags.h>
66
#include <ydb/core/base/feature_flags_service.h>
7+
#include <ydb/core/mon/mon.h>
8+
#include <ydb/core/protos/feature_flags.pb.h>
79
#include <ydb/library/actors/core/actor_bootstrapped.h>
10+
#include <ydb/library/actors/core/mon.h>
11+
#include <google/protobuf/descriptor.pb.h>
812

913
namespace NKikimr::NConsole {
1014

@@ -25,6 +29,13 @@ namespace NKikimr::NConsole {
2529
ui32 item = (ui32)NKikimrConsole::TConfigItem::FeatureFlagsItem;
2630
Send(MakeConfigsDispatcherID(SelfId().NodeId()),
2731
new TEvConfigsDispatcher::TEvSetConfigSubscriptionRequest(item));
32+
33+
if (auto* mon = AppData()->Mon) {
34+
auto* actorsMonPage = mon->RegisterIndexPage("actors", "Actors");
35+
mon->RegisterActorPage(
36+
actorsMonPage, "feature_flags", "Feature Flags",
37+
false, TActivationContext::ActorSystem(), SelfId());
38+
}
2839
}
2940

3041
void Handle(TEvConsole::TEvConfigNotificationRequest::TPtr& ev) {
@@ -42,12 +53,14 @@ namespace NKikimr::NConsole {
4253
for (auto& pr : Subscribers) {
4354
// Since feature flags are relaxed this event also establishes
4455
// release-acquire ordering on repeat accesses.
45-
Send(pr.first, new TEvFeatureFlags::TEvChanged, 0, pr.second);
56+
Send(pr.first, new TEvFeatureFlags::TEvChanged, 0, pr.second.Cookie);
4657
}
4758
}
4859

4960
void Handle(TEvFeatureFlags::TEvSubscribe::TPtr& ev) {
50-
Subscribers[ev->Sender] = ev->Cookie;
61+
auto& subscriber = Subscribers[ev->Sender];
62+
subscriber.Cookie = ev->Cookie;
63+
subscriber.Description = std::move(ev->Get()->Description);
5164

5265
// Since feature flags are relaxed client may have observed outdated
5366
// values just before the subscription, which may have actually
@@ -62,19 +75,127 @@ namespace NKikimr::NConsole {
6275
Subscribers.erase(ev->Sender);
6376
}
6477

78+
void Handle(NMon::TEvHttpInfo::TPtr& ev) {
79+
auto cgi = ev->Get()->Request.GetParams();
80+
bool showAll = false;
81+
if (const auto& text = cgi.Get("all")) {
82+
int value;
83+
if (TryFromString(text, value)) {
84+
showAll = value;
85+
}
86+
}
87+
88+
NKikimrConfig::TFeatureFlags flags = AppData()->FeatureFlags;
89+
const auto* d = NKikimrConfig::TFeatureFlags::descriptor();
90+
const auto* r = flags.GetReflection();
91+
92+
TStringStream html;
93+
HTML(html) {
94+
TAG(TH4) {
95+
if (showAll) {
96+
html << "<a href=\"feature_flags?all=0\">";
97+
}
98+
html << "Modified";
99+
if (showAll) {
100+
html << "</a>";
101+
}
102+
html << " | ";
103+
if (!showAll) {
104+
html << "<a href=\"feature_flags?all=1\">";
105+
}
106+
html << "All";
107+
if (!showAll) {
108+
html << "</a>";
109+
}
110+
}
111+
112+
TABLE_SORTABLE_CLASS("table") {
113+
TABLEHEAD() {
114+
TABLER() {
115+
TABLEH() { html << "Flag"; }
116+
TABLEH() { html << "Value"; }
117+
}
118+
}
119+
TABLEBODY() {
120+
for (int fieldIndex = 0; fieldIndex < d->field_count(); ++fieldIndex) {
121+
const auto* protoField = d->field(fieldIndex);
122+
if (protoField->type() != google::protobuf::FieldDescriptor::TYPE_BOOL) {
123+
continue;
124+
}
125+
bool hasValue = r->HasField(flags, protoField);
126+
if (!hasValue && !showAll) {
127+
continue;
128+
}
129+
bool value = r->GetBool(flags, protoField);
130+
TABLER() {
131+
TABLED() {
132+
if (hasValue) {
133+
html << "<b>";
134+
}
135+
html << protoField->name();
136+
if (hasValue) {
137+
html << "</b>";
138+
}
139+
}
140+
TABLED() {
141+
if (hasValue) {
142+
html << "<b>";
143+
}
144+
html << (value ? "true" : "false");
145+
if (hasValue) {
146+
html << "</b>";
147+
}
148+
}
149+
}
150+
}
151+
}
152+
}
153+
154+
TAG(TH4) {
155+
html << "Subscribers";
156+
}
157+
158+
TABLE_SORTABLE_CLASS("table") {
159+
TABLEHEAD() {
160+
TABLER() {
161+
TABLEH() { html << "ActorId"; }
162+
TABLEH() { html << "Description"; }
163+
}
164+
}
165+
TABLEBODY() {
166+
for (const auto& pr : Subscribers) {
167+
TABLER() {
168+
TABLED() { html << pr.first; }
169+
TABLED() { html << pr.second.Description; }
170+
}
171+
}
172+
}
173+
}
174+
}
175+
176+
Send(ev->Sender, new NMon::TEvHttpInfoRes(html.Str()));
177+
}
178+
65179
private:
66180
STFUNC(StateWork) {
67181
switch (ev->GetTypeRewrite()) {
68182
hFunc(TEvConsole::TEvConfigNotificationRequest, Handle);
69183
IgnoreFunc(TEvConfigsDispatcher::TEvSetConfigSubscriptionResponse);
70184
hFunc(TEvFeatureFlags::TEvSubscribe, Handle);
71185
hFunc(TEvFeatureFlags::TEvUnsubscribe, Handle);
186+
hFunc(NMon::TEvHttpInfo, Handle);
72187
}
73188
}
74189

190+
private:
191+
struct TSubscriber {
192+
ui64 Cookie;
193+
TString Description;
194+
};
195+
75196
private:
76197
// ActorId -> Cookie
77-
THashMap<TActorId, ui64> Subscribers;
198+
THashMap<TActorId, TSubscriber> Subscribers;
78199
// Tracks when we updated feature flags at least once
79200
bool Updated = false;
80201
};

0 commit comments

Comments
 (0)