master
c 242 lines 8.69 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "jsonwrap.h"
4 #include "jsonwrap-internal.h"
5
6 struct dimensions_sorted_walkthrough_data {
7 BUFFER *wb;
8 struct summary_total_counts *totals;
9 QUERY_TARGET *qt;
10 size_t cardinality_limit;
11 size_t count;
12 QUERY_METRICS_COUNTS aggregated_metrics;
13 STORAGE_POINT aggregated_points;
14 NETDATA_DOUBLE remaining_contribution;
15 size_t remaining_count;
16 };
17
18 struct dimensions_sorted_entry {
19 const char *id;
20 const char *name;
21 STORAGE_POINT query_points;
22 QUERY_METRICS_COUNTS metrics;
23 uint32_t priority;
24 };
25
26 static int dimensions_sorted_walktrhough_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
27 struct dimensions_sorted_walkthrough_data *sdwd = data;
28 BUFFER *wb = sdwd->wb;
29 struct summary_total_counts *totals = sdwd->totals;
30 QUERY_TARGET *qt = sdwd->qt;
31 struct dimensions_sorted_entry *z = value;
32
33 sdwd->count++;
34
35 // Check if we need to apply cardinality limiting
36 if (sdwd->cardinality_limit > 0 && sdwd->count > sdwd->cardinality_limit - 1) {
37 // This dimension exceeds our limit - aggregate it
38
39 // Increment our remaining count
40 sdwd->remaining_count++;
41
42 // Calculate contribution for this dimension
43 if (qt->query_points.sum > 0) {
44 NETDATA_DOUBLE contribution = z->query_points.sum * 100.0 / qt->query_points.sum;
45 sdwd->remaining_contribution += contribution;
46 }
47
48 // Aggregate metrics counts
49 aggregate_metrics_counts(&sdwd->aggregated_metrics, &z->metrics);
50
51 // Aggregate points
52 storage_point_merge_to(sdwd->aggregated_points, z->query_points);
53
54 // Still add this to summary totals
55 aggregate_into_summary_totals(totals, &z->metrics);
56
57 return 1; // Continue processing next dimension
58 }
59
60 // Output this dimension normally
61 buffer_json_add_array_item_object(wb);
62 buffer_json_member_add_string(wb, "id", z->id);
63 if (z->id != z->name && z->name)
64 buffer_json_member_add_string(wb, JSKEY(name), z->name);
65
66 // Only include detailed statistics if MINIMAL_STATS option is not set
67 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
68 query_target_metric_counts(wb, &z->metrics);
69 }
70
71 query_target_points_statistics(wb, qt, &z->query_points);
72 buffer_json_member_add_uint64(wb, JSKEY(priority), z->priority);
73 buffer_json_object_close(wb);
74
75 aggregate_into_summary_totals(totals, &z->metrics);
76
77 return 1;
78 }
79
80 // Standard sort by priority
81 static int dimensions_sorted_priority_compar(const DICTIONARY_ITEM **item1, const DICTIONARY_ITEM **item2) {
82 struct dimensions_sorted_entry *z1 = dictionary_acquired_item_value(*item1);
83 struct dimensions_sorted_entry *z2 = dictionary_acquired_item_value(*item2);
84
85 if(z1->priority == z2->priority)
86 return strcmp(dictionary_acquired_item_name(*item1), dictionary_acquired_item_name(*item2));
87 else if(z1->priority < z2->priority)
88 return -1;
89 else
90 return 1;
91 }
92
93 // Sort by sum value (highest first), then priority
94 static int dimensions_sorted_sum_compar(const DICTIONARY_ITEM **item1, const DICTIONARY_ITEM **item2) {
95 struct dimensions_sorted_entry *z1 = dictionary_acquired_item_value(*item1);
96 struct dimensions_sorted_entry *z2 = dictionary_acquired_item_value(*item2);
97
98 // Sort by sum (highest first)
99 if (z1->query_points.sum > z2->query_points.sum) return -1;
100 if (z1->query_points.sum < z2->query_points.sum) return 1;
101
102 // If equal sum, use priority as a secondary sort
103 if (z1->priority != z2->priority) {
104 if (z1->priority < z2->priority) return -1;
105 else return 1;
106 }
107
108 // If still equal, sort alphabetically
109 return strcmp(dictionary_acquired_item_name(*item1), dictionary_acquired_item_name(*item2));
110 }
111
112 void query_target_summary_dimensions_v12(BUFFER *wb, QUERY_TARGET *qt, const char *key, bool v2, struct summary_total_counts *totals) {
113 char buf[RRD_ID_LENGTH_MAX * 2 + 2];
114
115 buffer_json_member_add_array(wb, key);
116 DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
117 struct dimensions_sorted_entry *z;
118 size_t q = 0;
119 for (long c = 0; c < (long) qt->dimensions.used; c++) {
120 QUERY_DIMENSION * qd = query_dimension(qt, c);
121 RRDMETRIC_ACQUIRED *rma = qd->rma;
122
123 QUERY_METRIC *qm = NULL;
124 for( ; q < qt->query.used ;q++) {
125 QUERY_METRIC *tqm = query_metric(qt, q);
126 QUERY_DIMENSION *tqd = query_dimension(qt, tqm->link.query_dimension_id);
127 if(tqd->rma != rma) break;
128 qm = tqm;
129 }
130
131 const char *k, *id, *name;
132
133 if(v2) {
134 k = rrdmetric_acquired_name(rma);
135 if(unlikely(!k || !*k))
136 k = rrdmetric_acquired_id(rma);
137 if(unlikely(!k || !*k)) {
138 internal_error(true, "QUERY: dimension at index %ld has empty id and name; skipping it", c);
139 continue;
140 }
141 id = k;
142 name = k;
143 }
144 else {
145 snprintfz(buf, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s",
146 rrdmetric_acquired_id(rma),
147 rrdmetric_acquired_name(rma));
148 k = buf;
149 id = rrdmetric_acquired_id(rma);
150 name = rrdmetric_acquired_name(rma);
151 }
152
153 z = dictionary_set(dict, k, NULL, sizeof(*z));
154 if(!z->id) {
155 z->id = id;
156 z->name = name;
157 z->priority = qd->priority;
158 }
159 else {
160 if(qd->priority < z->priority)
161 z->priority = qd->priority;
162 }
163
164 if(qm) {
165 z->metrics.selected += (qm->status & RRDR_DIMENSION_SELECTED) ? 1 : 0;
166 z->metrics.failed += (qm->status & RRDR_DIMENSION_FAILED) ? 1 : 0;
167
168 if(qm->status & RRDR_DIMENSION_QUERIED) {
169 z->metrics.queried++;
170 storage_point_merge_to(z->query_points, qm->query_points);
171 }
172 }
173 else
174 z->metrics.excluded++;
175 }
176
177 if(v2) {
178 size_t cardinality_limit = qt->request.cardinality_limit;
179 size_t dict_entries = dictionary_entries(dict);
180
181 // Use the enhanced walkthrough with cardinality limiting
182 struct dimensions_sorted_walkthrough_data t = {
183 .wb = wb,
184 .totals = totals,
185 .qt = qt,
186 .cardinality_limit = cardinality_limit,
187 .count = 0,
188 .remaining_count = 0,
189 .remaining_contribution = 0.0,
190 .aggregated_metrics = {0},
191 .aggregated_points = STORAGE_POINT_UNSET
192 };
193
194 // Execute the sorted walkthrough which will either output dimensions directly
195 // or aggregate them if they exceed the cardinality limit
196 // Choose the appropriate comparison function based on cardinality limiting
197 dict_item_comparator_t comparator = (cardinality_limit > 0 && dict_entries > cardinality_limit) ?
198 dimensions_sorted_sum_compar :
199 dimensions_sorted_priority_compar;
200
201 if (comparator)
202 dictionary_sorted_walkthrough_rw(dict, DICTIONARY_LOCK_READ,
203 dimensions_sorted_walktrhough_cb, &t, comparator);
204 else
205 dictionary_walkthrough_rw(dict, DICTIONARY_LOCK_READ,
206 dimensions_sorted_walktrhough_cb, &t);
207
208
209 // Add the aggregated "remaining" dimension if there are any
210 if (t.remaining_count > 0) {
211 buffer_json_add_array_item_object(wb);
212
213 // Add basic info for the aggregated dimension
214 char remaining_label[50];
215 snprintfz(remaining_label, sizeof(remaining_label), "remaining %zu dimensions", t.remaining_count);
216
217 buffer_json_member_add_string(wb, "id", "__remaining_dimensions__");
218 buffer_json_member_add_string(wb, JSKEY(name), remaining_label);
219 buffer_json_member_add_double(wb, JSKEY(contribution), t.remaining_contribution);
220
221 // Only include detailed statistics if MINIMAL_STATS option is not set
222 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
223 query_target_metric_counts(wb, &t.aggregated_metrics);
224 }
225
226 query_target_points_statistics(wb, qt, &t.aggregated_points);
227 buffer_json_object_close(wb);
228 }
229 }
230 else {
231 // v1
232 dfe_start_read(dict, z) {
233 buffer_json_add_array_item_array(wb);
234 buffer_json_add_array_item_string(wb, z->id);
235 buffer_json_add_array_item_string(wb, z->name);
236 buffer_json_array_close(wb);
237 }
238 dfe_done(z);
239 }
240 dictionary_destroy(dict);
241 buffer_json_array_close(wb);
242 }