master
c 238 lines 8.46 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "jsonwrap.h"
4 #include "jsonwrap-internal.h"
5
6 struct rrdlabels_formatting_v2 {
7 DICTIONARY *keys;
8 QUERY_INSTANCE *qi;
9 bool v2;
10 };
11
12 struct rrdlabels_keys_dict_entry {
13 const char *name;
14 DICTIONARY *values;
15 STORAGE_POINT query_points;
16 QUERY_METRICS_COUNTS metrics;
17 };
18
19 struct rrdlabels_key_value_dict_entry {
20 const char *key;
21 const char *value;
22 STORAGE_POINT query_points;
23 QUERY_METRICS_COUNTS metrics;
24 };
25
26 static int rrdlabels_formatting_v2(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
27 struct rrdlabels_formatting_v2 *t = data;
28
29 struct rrdlabels_keys_dict_entry *d = dictionary_set(t->keys, name, NULL, sizeof(*d));
30 if(!d->values) {
31 d->name = name;
32 d->values = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
33 }
34
35 char n[RRD_ID_LENGTH_MAX * 2 + 2];
36 snprintfz(n, RRD_ID_LENGTH_MAX * 2, "%s:%s", name, value);
37
38 struct rrdlabels_key_value_dict_entry *z = dictionary_set(d->values, n, NULL, sizeof(*z));
39 if(!z->key) {
40 z->key = name;
41 z->value = value;
42 }
43
44 if(t->v2) {
45 QUERY_INSTANCE *qi = t->qi;
46
47 z->metrics.selected += qi->metrics.selected;
48 z->metrics.excluded += qi->metrics.excluded;
49 z->metrics.queried += qi->metrics.queried;
50 z->metrics.failed += qi->metrics.failed;
51
52 d->metrics.selected += qi->metrics.selected;
53 d->metrics.excluded += qi->metrics.excluded;
54 d->metrics.queried += qi->metrics.queried;
55 d->metrics.failed += qi->metrics.failed;
56
57 storage_point_merge_to(z->query_points, qi->query_points);
58 storage_point_merge_to(d->query_points, qi->query_points);
59 }
60
61 return 1;
62 }
63
64 // Sort label values by sum value (highest first)
65 int label_values_sorted_sum_compar(const DICTIONARY_ITEM **item1, const DICTIONARY_ITEM **item2) {
66 struct rrdlabels_key_value_dict_entry *z1 = dictionary_acquired_item_value(*item1);
67 struct rrdlabels_key_value_dict_entry *z2 = dictionary_acquired_item_value(*item2);
68
69 // Sort by sum (highest first)
70 if (z1->query_points.sum > z2->query_points.sum) return -1;
71 if (z1->query_points.sum < z2->query_points.sum) return 1;
72
73 // If equal sum, sort alphabetically
74 return strcmp(dictionary_acquired_item_name(*item1), dictionary_acquired_item_name(*item2));
75 }
76
77 /**
78 * Output a label value to the JSON buffer
79 */
80 static inline void output_label_value(BUFFER *wb, QUERY_TARGET *qt, const char *id, const char *name,
81 QUERY_METRICS_COUNTS *metrics, STORAGE_POINT *points) {
82 buffer_json_add_array_item_object(wb);
83 buffer_json_member_add_string(wb, "id", id);
84
85 if (name)
86 buffer_json_member_add_string(wb, JSKEY(name), name);
87
88 // Only include detailed statistics if MINIMAL_STATS option is not set
89 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
90 query_target_metric_counts(wb, metrics);
91 }
92
93 query_target_points_statistics(wb, qt, points);
94 buffer_json_object_close(wb);
95 }
96
97 // Callback for label values walkthrough
98 struct label_values_walkthrough_data {
99 BUFFER *wb;
100 struct summary_total_counts *totals;
101 QUERY_TARGET *qt;
102 size_t cardinality_limit;
103 size_t count;
104
105 // Aggregated "remaining" value data
106 struct {
107 QUERY_METRICS_COUNTS metrics;
108 STORAGE_POINT points;
109 size_t count;
110 } remaining;
111 };
112
113 static int label_values_walkthrough_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
114 struct label_values_walkthrough_data *lwt = data;
115 struct summary_total_counts *totals = lwt->totals;
116 QUERY_TARGET *qt = lwt->qt;
117 struct rrdlabels_key_value_dict_entry *z = value;
118
119 lwt->count++;
120
121 // Check if we need to apply cardinality limiting
122 if (lwt->cardinality_limit > 0 && lwt->count > lwt->cardinality_limit - 1) {
123 // This value exceeds our limit - aggregate it
124
125 // Increment our remaining count
126 lwt->remaining.count++;
127
128 // Aggregate metrics counts
129 aggregate_metrics_counts(&lwt->remaining.metrics, &z->metrics);
130
131 // Aggregate points
132 storage_point_merge_to(lwt->remaining.points, z->query_points);
133
134 // Still add this to summary totals
135 aggregate_into_summary_totals(totals, &z->metrics);
136
137 return 1; // Continue processing next value
138 }
139
140 // Output this value normally using our helper function
141 output_label_value(lwt->wb, qt, z->value, NULL, &z->metrics, &z->query_points);
142
143 // Add to summary totals
144 aggregate_into_summary_totals(totals, &z->metrics);
145
146 return 1;
147 }
148
149 void query_target_summary_labels_v12(BUFFER *wb, QUERY_TARGET *qt, const char *key, bool v2, struct summary_total_counts *key_totals, struct summary_total_counts *value_totals) {
150 buffer_json_member_add_array(wb, key);
151 struct rrdlabels_formatting_v2 t = {
152 .keys = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE),
153 .v2 = v2,
154 };
155 for (long c = 0; c < (long) qt->instances.used; c++) {
156 QUERY_INSTANCE *qi = query_instance(qt, c);
157 RRDINSTANCE_ACQUIRED *ria = qi->ria;
158 t.qi = qi;
159 rrdlabels_walkthrough_read(rrdinstance_acquired_labels(ria), rrdlabels_formatting_v2, &t);
160 }
161
162 size_t cardinality_limit = qt->request.cardinality_limit;
163
164 struct rrdlabels_keys_dict_entry *d;
165 dfe_start_read(t.keys, d) {
166 if(v2) {
167 buffer_json_add_array_item_object(wb);
168 buffer_json_member_add_string(wb, "id", d_dfe.name);
169
170 // Only include detailed statistics if MINIMAL_STATS option is not set
171 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
172 query_target_metric_counts(wb, &d->metrics);
173 }
174
175 query_target_points_statistics(wb, qt, &d->query_points);
176 aggregate_into_summary_totals(key_totals, &d->metrics);
177 buffer_json_member_add_array(wb, JSKEY(label_values));
178
179 // Apply cardinality limiting to label values
180 size_t values_count = dictionary_entries(d->values);
181
182 // Setup walkthrough data regardless of whether we'll apply cardinality limiting
183 struct label_values_walkthrough_data vt = {
184 .wb = wb,
185 .totals = value_totals,
186 .qt = qt,
187 .cardinality_limit = (cardinality_limit > 0 && values_count > cardinality_limit) ? cardinality_limit : 0,
188 .count = 0,
189 .remaining = {
190 .metrics = {0},
191 .points = STORAGE_POINT_UNSET,
192 .count = 0
193 }
194 };
195
196 // Choose appropriate comparison function based on whether we need cardinality limiting
197 dict_item_comparator_t comparator = (vt.cardinality_limit > 0) ?
198 label_values_sorted_sum_compar :
199 NULL; // No specific ordering for normal case
200
201 if (comparator)
202 dictionary_sorted_walkthrough_rw(d->values, DICTIONARY_LOCK_READ,
203 label_values_walkthrough_cb, &vt, comparator);
204 else
205 dictionary_walkthrough_rw(d->values, DICTIONARY_LOCK_READ,
206 label_values_walkthrough_cb, &vt);
207
208 // Add the aggregated "remaining" value if there are any
209 if (vt.remaining.count > 0) {
210 char remaining_label[50];
211 snprintfz(remaining_label, sizeof(remaining_label), "remaining %zu values", vt.remaining.count);
212
213 // Use our helper function for consistency
214 output_label_value(wb, qt, "__remaining_values__", remaining_label,
215 &vt.remaining.metrics, &vt.remaining.points);
216 }
217
218 buffer_json_array_close(wb); // vl
219 buffer_json_object_close(wb); // label key
220
221 } else {
222 // v1 format - no cardinality limiting here
223 struct rrdlabels_key_value_dict_entry *z;
224 dfe_start_read(d->values, z){
225 buffer_json_add_array_item_array(wb);
226 buffer_json_add_array_item_string(wb, z->key);
227 buffer_json_add_array_item_string(wb, z->value);
228 buffer_json_array_close(wb);
229 }
230 dfe_done(z);
231 }
232
233 dictionary_destroy(d->values);
234 }
235 dfe_done(d);
236 dictionary_destroy(t.keys);
237 buffer_json_array_close(wb);
238 }