master
c 471 lines 23.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "function-metrics-cardinality.h"
4 #include "database/contexts/rrdcontext-internal.h"
5
6 struct counts {
7 size_t nodes;
8 size_t instances;
9 size_t metrics;
10
11 size_t online_nodes;
12 size_t online_instances;
13 size_t online_metrics;
14
15 size_t offline_nodes;
16 size_t offline_instances;
17 size_t offline_metrics;
18 };
19
20 int function_metrics_cardinality(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
21 buffer_flush(wb);
22 wb->content_type = CT_APPLICATION_JSON;
23 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
24
25 buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(localhost));
26 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
27 buffer_json_member_add_string(wb, "type", "table");
28 buffer_json_member_add_time_t(wb, "update_every", 10);
29 buffer_json_member_add_boolean(wb, "has_history", false);
30 buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_METRICS_CARDINALITY_HELP);
31
32 // Add parameter definition similar to network-viewer
33 buffer_json_member_add_array(wb, "accepted_params");
34 {
35 buffer_json_add_array_item_string(wb, "group");
36 }
37 buffer_json_array_close(wb);
38
39 buffer_json_member_add_array(wb, "required_params");
40 {
41 buffer_json_add_array_item_object(wb);
42 {
43 buffer_json_member_add_string(wb, "id", "group");
44 buffer_json_member_add_string(wb, "name", "Grouping");
45 buffer_json_member_add_string(wb, "help", "Select how to group the metrics");
46 buffer_json_member_add_boolean(wb, "unique_view", true);
47 buffer_json_member_add_string(wb, "type", "select");
48 buffer_json_member_add_array(wb, "options");
49 {
50 buffer_json_add_array_item_object(wb);
51 {
52 buffer_json_member_add_string(wb, "id", "by-context");
53 buffer_json_member_add_string(wb, "name", "Group by Context");
54 }
55 buffer_json_object_close(wb);
56 buffer_json_add_array_item_object(wb);
57 {
58 buffer_json_member_add_string(wb, "id", "by-node");
59 buffer_json_member_add_string(wb, "name", "Group by Node");
60 }
61 buffer_json_object_close(wb);
62 }
63 buffer_json_array_close(wb);
64 }
65 buffer_json_object_close(wb);
66 }
67 buffer_json_array_close(wb);
68
69 // Parse function parameters
70 bool by_node = false;
71 {
72 char *function_copy = strdupz(function);
73 char *words[1024];
74 size_t num_words = quoted_strings_splitter_whitespace(function_copy, words, 1024);
75 for (size_t i = 1; i < num_words; i++) {
76 char *param = get_word(words, num_words, i);
77 if (strcmp(param, "group:by-node") == 0) {
78 by_node = true;
79 } else if (strcmp(param, "group:by-context") == 0) {
80 by_node = false;
81 } else if (strcmp(param, "info") == 0) {
82 buffer_json_finalize(wb);
83 freez(function_copy);
84 return HTTP_RESP_OK;
85 }
86 }
87 freez(function_copy);
88 }
89
90 DICTIONARY *contexts_dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
91
92 buffer_json_member_add_array(wb, "data");
93
94 struct counts all = { 0 };
95
96 // Collect stats for each context across all nodes
97 RRDHOST *host;
98 dfe_start_read(rrdhost_root_index, host) {
99 if(!host->rrdctx.contexts) continue;
100
101 bool host_online = rrdhost_is_online(host);
102
103 RRDCONTEXT *rc;
104 dfe_start_read(host->rrdctx.contexts, rc) {
105 const char *aggregation_key = by_node ? string2str(host->hostname) :string2str(rc->id);
106
107 struct counts cnt = { 0 };
108
109 cnt.nodes++;
110
111 if (host_online)
112 cnt.online_nodes++;
113 else
114 cnt.offline_nodes++;
115
116 RRDINSTANCE *ri;
117 dfe_start_read(rc->rrdinstances, ri) {
118 cnt.instances++;
119
120 if (rrd_flag_check(ri, RRD_FLAG_COLLECTED)) {
121 cnt.online_instances++;
122
123 RRDMETRIC *rm;
124 dfe_start_read(ri->rrdmetrics, rm) {
125 cnt.metrics++;
126
127 if (rrd_flag_check(rm, RRD_FLAG_COLLECTED))
128 cnt.online_metrics++;
129 else
130 cnt.offline_metrics++;
131 }
132 dfe_done(rm);
133 }
134 else {
135 cnt.offline_instances++;
136 size_t metrics = dictionary_entries(ri->rrdmetrics);
137 cnt.metrics += metrics;
138 cnt.offline_metrics += metrics;
139 }
140 }
141 dfe_done(ri);
142
143 struct counts *cc = dictionary_get(contexts_dict, aggregation_key);
144 if (cc) {
145 cc->nodes += cnt.nodes;
146 cc->online_nodes += cnt.online_nodes;
147 cc->offline_nodes += cnt.offline_nodes;
148
149 cc->instances += cnt.instances;
150 cc->online_instances += cnt.online_instances;
151 cc->offline_instances += cnt.offline_instances;
152
153 cc->metrics += cnt.metrics;
154 cc->online_metrics += cnt.online_metrics;
155 cc->offline_metrics += cnt.offline_metrics;
156 }
157 else
158 dictionary_set(contexts_dict, aggregation_key, &cnt, sizeof(cnt));
159
160 // keep track of the total
161 all.nodes += cnt.nodes;
162 all.online_nodes += cnt.online_nodes;
163 all.offline_nodes += cnt.offline_nodes;
164
165 all.instances += cnt.instances;
166 all.online_instances += cnt.online_instances;
167 all.offline_instances += cnt.offline_instances;
168
169 all.metrics += cnt.metrics;
170 all.online_metrics += cnt.online_metrics;
171 all.offline_metrics += cnt.offline_metrics;
172 }
173 dfe_done(rc);
174 }
175 dfe_done(host);
176
177 struct counts max = { 0 };
178
179 // Output collected stats
180 struct counts *cnt;
181 dfe_start_read(contexts_dict, cnt) {
182 buffer_json_add_array_item_array(wb);
183 {
184 buffer_json_add_array_item_string(wb, cnt_dfe.name);
185
186 if(!by_node) {
187 buffer_json_add_array_item_uint64(wb, cnt->nodes);
188 buffer_json_add_array_item_uint64(wb, cnt->online_nodes);
189 buffer_json_add_array_item_uint64(wb, cnt->offline_nodes);
190 }
191
192 buffer_json_add_array_item_uint64(wb, cnt->instances);
193 buffer_json_add_array_item_uint64(wb, cnt->online_instances);
194 buffer_json_add_array_item_uint64(wb, cnt->offline_instances);
195
196 buffer_json_add_array_item_uint64(wb, cnt->metrics);
197 buffer_json_add_array_item_uint64(wb, cnt->online_metrics);
198 buffer_json_add_array_item_uint64(wb, cnt->offline_metrics);
199
200 double instances_ephemerality = (cnt->instances > 0) ? ((double)cnt->offline_instances * 100.0) / (double)cnt->instances : 0.0;
201 buffer_json_add_array_item_double(wb, instances_ephemerality);
202
203 double metrics_ephemerality = (cnt->metrics > 0) ? ((double)cnt->offline_metrics * 100.0) / (double)cnt->metrics : 0.0;
204 buffer_json_add_array_item_double(wb, metrics_ephemerality);
205
206 double percentage_of_all_instances = (all.instances) ? ((double)cnt->instances * 100.0) / (double)all.instances : 0.0;
207 buffer_json_add_array_item_double(wb, percentage_of_all_instances);
208
209 double percentage_of_online_instances = (all.online_instances) ? ((double)cnt->online_instances * 100.0) / (double)all.online_instances : 0.0;
210 buffer_json_add_array_item_double(wb, percentage_of_online_instances);
211
212 double percentage_of_offline_instances = (all.offline_instances) ? ((double)cnt->offline_instances * 100.0) / (double)all.offline_instances : 0.0;
213 buffer_json_add_array_item_double(wb, percentage_of_offline_instances);
214
215 double percentage_of_all_metrics = (all.metrics) ? ((double)cnt->metrics * 100.0) / (double)all.metrics : 0.0;
216 buffer_json_add_array_item_double(wb, percentage_of_all_metrics);
217
218 double percentage_of_online_metrics = (all.online_metrics) ? ((double)cnt->online_metrics * 100.0) / (double)all.online_metrics : 0.0;
219 buffer_json_add_array_item_double(wb, percentage_of_online_metrics);
220
221 double percentage_of_offline_metrics = (all.offline_metrics) ? ((double)cnt->offline_metrics * 100.0) / (double)all.offline_metrics : 0.0;
222 buffer_json_add_array_item_double(wb, percentage_of_offline_metrics);
223 }
224 buffer_json_array_close(wb);
225
226 if(cnt->nodes > max.nodes)
227 max.nodes = cnt->nodes;
228 if(cnt->instances > max.instances)
229 max.instances = cnt->instances;
230 if(cnt->metrics > max.metrics)
231 max.metrics = cnt->metrics;
232 if(cnt->online_nodes > max.online_nodes)
233 max.online_nodes = cnt->online_nodes;
234 if(cnt->online_instances > max.online_instances)
235 max.online_instances = cnt->online_instances;
236 if(cnt->online_metrics > max.online_metrics)
237 max.online_metrics = cnt->online_metrics;
238 if(cnt->offline_nodes > max.offline_nodes)
239 max.offline_nodes = cnt->offline_nodes;
240 if(cnt->offline_instances > max.offline_instances)
241 max.offline_instances = cnt->offline_instances;
242 if(cnt->offline_metrics > max.offline_metrics)
243 max.offline_metrics = cnt->offline_metrics;
244 }
245 dfe_done(cnt);
246
247 buffer_json_array_close(wb); // data
248
249 buffer_json_member_add_object(wb, "columns");
250 {
251 size_t field_id = 0;
252
253 if(by_node) {
254 buffer_rrdf_table_add_field(wb, field_id++, "Hostname", "Hostname",
255 RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
256 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
257 RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
258 RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE,
259 NULL);
260 }
261 else {
262 buffer_rrdf_table_add_field(wb, field_id++, "Context", "Context Name",
263 RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
264 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
265 RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
266 RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE,
267 NULL);
268 }
269
270 if(!by_node) {
271 buffer_rrdf_table_add_field(wb, field_id++, "All Nodes", "Number of Nodes",
272 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
273 0, "nodes", (double)max.nodes, RRDF_FIELD_SORT_DESCENDING, NULL,
274 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
275 RRDF_FIELD_OPTS_NONE,
276 NULL);
277
278 buffer_rrdf_table_add_field(wb, field_id++, "Curr. Nodes", "Number of Online Nodes",
279 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
280 0, "nodes", (double)max.online_nodes, RRDF_FIELD_SORT_DESCENDING, NULL,
281 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
282 RRDF_FIELD_OPTS_VISIBLE,
283 NULL);
284
285 buffer_rrdf_table_add_field(wb, field_id++, "Old Nodes", "Number of Offline Nodes",
286 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
287 0, "nodes", (double)max.offline_nodes, RRDF_FIELD_SORT_DESCENDING, NULL,
288 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
289 RRDF_FIELD_OPTS_VISIBLE,
290 NULL);
291 }
292
293 buffer_rrdf_table_add_field(wb, field_id++, "All Instances", "Total Number of Instances",
294 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
295 0, "instances", (double)max.instances, RRDF_FIELD_SORT_DESCENDING, NULL,
296 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
297 RRDF_FIELD_OPTS_NONE,
298 NULL);
299
300 buffer_rrdf_table_add_field(wb, field_id++, "Curr. Instances", "Total Number of Currently Collected Instances",
301 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
302 0, "instances", (double)max.online_instances, RRDF_FIELD_SORT_DESCENDING, NULL,
303 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
304 RRDF_FIELD_OPTS_VISIBLE,
305 NULL);
306
307 buffer_rrdf_table_add_field(wb, field_id++, "Old Instances", "Total Number of Archived Instances",
308 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
309 0, "instances", (double)max.offline_instances, RRDF_FIELD_SORT_DESCENDING, NULL,
310 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
311 RRDF_FIELD_OPTS_VISIBLE,
312 NULL);
313
314 buffer_rrdf_table_add_field(wb, field_id++, "All Dimensions", "Total Number of Time-Series",
315 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
316 0, "metrics", (double)max.metrics, RRDF_FIELD_SORT_DESCENDING, NULL,
317 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
318 RRDF_FIELD_OPTS_VISIBLE,
319 NULL);
320
321 buffer_rrdf_table_add_field(wb, field_id++, "Curr. Dimensions", "Total Number of Currently Collected Time-Series",
322 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
323 0, "metrics", (double)max.online_metrics, RRDF_FIELD_SORT_DESCENDING, NULL,
324 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
325 RRDF_FIELD_OPTS_NONE,
326 NULL);
327
328 buffer_rrdf_table_add_field(wb, field_id++, "Old Dimensions", "Total Number of Archived Time-Series",
329 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
330 0, "metrics", (double)max.offline_metrics, RRDF_FIELD_SORT_DESCENDING, NULL,
331 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
332 RRDF_FIELD_OPTS_NONE,
333 NULL);
334
335 buffer_rrdf_table_add_field(wb, field_id++, "Ephemeral Instances", "Percentage of Archived Instances vs All Instances of the row",
336 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
337 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
338 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
339 RRDF_FIELD_OPTS_VISIBLE,
340 NULL);
341
342 buffer_rrdf_table_add_field(wb, field_id++, "Ephemeral Dimensions", "Percentage of Archived Time-Series vs All Time-Series of the row",
343 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
344 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
345 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
346 RRDF_FIELD_OPTS_NONE,
347 NULL);
348
349 buffer_rrdf_table_add_field(wb, field_id++, "All Instances %", "Percentage of All Instances of row vs the sum of All Instances across all rows",
350 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
351 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
352 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
353 RRDF_FIELD_OPTS_VISIBLE,
354 NULL);
355
356 buffer_rrdf_table_add_field(wb, field_id++, "Curr. Instances %", "Percentage of Currently Collected Instances of row vs the sum of Currently Collected Instances across all rows",
357 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
358 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
359 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
360 RRDF_FIELD_OPTS_NONE,
361 NULL);
362
363 buffer_rrdf_table_add_field(wb, field_id++, "Old Instances %", "Percentage of Old Instances of row vs the sum of Old Instances across all rows",
364 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
365 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
366 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
367 RRDF_FIELD_OPTS_VISIBLE,
368 NULL);
369
370 buffer_rrdf_table_add_field(wb, field_id++, "All Dimensions %", "Percentage of All Time-Series of row vs the sum of All Time-Series across all rows",
371 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
372 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
373 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
374 RRDF_FIELD_OPTS_NONE,
375 NULL);
376
377 buffer_rrdf_table_add_field(wb, field_id++, "Curr. Dimensions %", "Percentage of Currently Collected Time-Series of row vs the sum of Currently Collected Time-Series across all rows",
378 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
379 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
380 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
381 RRDF_FIELD_OPTS_NONE,
382 NULL);
383
384 buffer_rrdf_table_add_field(wb, field_id++, "Old Dimensions %", "Percentage of Archived Time-Series of row vs the sum of Archived Time-Series across all rows",
385 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
386 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
387 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
388 RRDF_FIELD_OPTS_NONE,
389 NULL);
390 }
391 buffer_json_object_close(wb); // columns
392
393 buffer_json_member_add_string(wb, "default_sort_column", "Old Instances");
394
395 buffer_json_member_add_object(wb, "charts");
396 {
397 buffer_json_member_add_object(wb, "Instances Ephemerality");
398 {
399 buffer_json_member_add_array(wb, "columns");
400 buffer_json_add_array_item_string(wb, "Curr. Instances");
401 buffer_json_add_array_item_string(wb, "Old Instances");
402 buffer_json_array_close(wb);
403
404 buffer_json_member_add_string(wb, "name", "Instances Ephemerality");
405 buffer_json_member_add_string(wb, "type", "doughnut");
406 buffer_json_member_add_string(wb, "groupBy", "all");
407 buffer_json_member_add_string(wb, "aggregation", "sum");
408 }
409 buffer_json_object_close(wb);
410
411 buffer_json_member_add_object(wb, "Dimensions Ephemerality");
412 {
413 buffer_json_member_add_array(wb, "columns");
414 buffer_json_add_array_item_string(wb, "Curr. Dimensions");
415 buffer_json_add_array_item_string(wb, "Old Dimensions");
416 buffer_json_array_close(wb);
417
418 buffer_json_member_add_string(wb, "name", "Dimensions Ephemerality");
419 buffer_json_member_add_string(wb, "type", "doughnut");
420 buffer_json_member_add_string(wb, "groupBy", "all");
421 buffer_json_member_add_string(wb, "aggregation", "sum");
422 }
423 buffer_json_object_close(wb);
424
425 buffer_json_member_add_object(wb, "Instances Total");
426 {
427 buffer_json_member_add_array(wb, "columns");
428 buffer_json_add_array_item_string(wb, "All Instances");
429 buffer_json_array_close(wb);
430
431 buffer_json_member_add_string(wb, "name", "Instances Total");
432 buffer_json_member_add_string(wb, "type", "value");
433 buffer_json_member_add_string(wb, "groupBy", "all");
434 buffer_json_member_add_string(wb, "aggregation", "sum");
435 }
436 buffer_json_object_close(wb);
437
438 buffer_json_member_add_object(wb, "Dimensions Total");
439 {
440 buffer_json_member_add_array(wb, "columns");
441 buffer_json_add_array_item_string(wb, "All Dimensions");
442 buffer_json_array_close(wb);
443
444 buffer_json_member_add_string(wb, "name", "Dimensions Total");
445 buffer_json_member_add_string(wb, "type", "value");
446 buffer_json_member_add_string(wb, "groupBy", "all");
447 buffer_json_member_add_string(wb, "aggregation", "sum");
448 }
449 buffer_json_object_close(wb);
450 }
451 buffer_json_object_close(wb); // charts
452
453 buffer_json_member_add_array(wb, "default_charts");
454 {
455 buffer_json_add_array_item_array(wb);
456 buffer_json_add_array_item_string(wb, "Instances Ephemerality");
457 buffer_json_array_close(wb);
458
459 buffer_json_add_array_item_array(wb);
460 buffer_json_add_array_item_string(wb, "Dimensions Ephemerality");
461 buffer_json_array_close(wb);
462 }
463 buffer_json_array_close(wb); // default_charts
464
465 buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
466 buffer_json_finalize(wb);
467
468 dictionary_destroy(contexts_dict);
469
470 return HTTP_RESP_OK;
471 }