master
c 150 lines 6.28 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "jsonwrap.h"
4 #include "jsonwrap-internal.h"
5
6 // Helper structure for sorting and limiting items based on their contribution
7 typedef struct {
8 size_t index; // Original index in the array
9 NETDATA_DOUBLE contribution; // Sorting metric (usually volume contribution)
10 const char *name; // Name for display
11 } CARDINALITY_ITEM;
12
13 // Comparison function for sorting items by contribution
14 static int cardinality_item_compare(const void *a, const void *b) {
15 const CARDINALITY_ITEM *item_a = (const CARDINALITY_ITEM *)a;
16 const CARDINALITY_ITEM *item_b = (const CARDINALITY_ITEM *)b;
17
18 // Sort by contribution (highest first)
19 if (item_a->contribution > item_b->contribution) return -1;
20 if (item_a->contribution < item_b->contribution) return 1;
21
22 // If equal contribution, sort alphabetically by name
23 if (item_a->name && item_b->name)
24 return strcmp(item_a->name, item_b->name);
25
26 return 0;
27 }
28
29 void query_target_summary_nodes_v2(BUFFER *wb, QUERY_TARGET *qt, const char *key, struct summary_total_counts *totals) {
30 buffer_json_member_add_array(wb, key);
31 size_t count = qt->nodes.used;
32 size_t cardinality_limit = qt->request.cardinality_limit;
33
34 bool show_node_status = !(qt->request.options & RRDR_OPTION_MINIMAL_STATS);
35
36 // Check if we need to apply cardinality limiting
37 if (cardinality_limit > 0 && count > cardinality_limit) {
38 // We'll need to sort and limit the nodes
39 CARDINALITY_ITEM *items = mallocz(sizeof(CARDINALITY_ITEM) * count);
40
41 // Collect contribution data for each node
42 for (size_t c = 0; c < count; c++) {
43 QUERY_NODE *qn = query_node(qt, c);
44 items[c].index = c;
45 items[c].name = rrdhost_hostname(qn->rrdhost);
46
47 // Use query points as the metric for contribution
48 if (qt->query_points.sum > 0)
49 items[c].contribution = qn->query_points.sum * 100.0 / qt->query_points.sum;
50 else
51 items[c].contribution = 0.0;
52 }
53
54 // Sort by contribution
55 qsort(items, count, sizeof(CARDINALITY_ITEM), cardinality_item_compare);
56
57 // First add the top (limit-1) nodes
58 size_t nodes_to_show = cardinality_limit - 1;
59 NETDATA_DOUBLE remaining_contribution = 0.0;
60 size_t remaining_count = 0;
61 QUERY_METRICS_COUNTS aggregated_metrics = {0};
62 QUERY_INSTANCES_COUNTS aggregated_instances = {0};
63 QUERY_ALERTS_COUNTS aggregated_alerts = {0};
64 STORAGE_POINT aggregated_points = STORAGE_POINT_UNSET;
65
66 for (size_t i = 0; i < count; i++) {
67 if (i < nodes_to_show) {
68 // Output this node normally
69 QUERY_NODE *qn = query_node(qt, items[i].index);
70 RRDHOST *host = qn->rrdhost;
71 buffer_json_add_array_item_object(wb);
72 buffer_json_node_add_v2(wb, host, qn->slot, qn->duration_ut, show_node_status);
73
74 // Only include detailed statistics if MINIMAL_STATS option is not set
75 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
76 query_target_instance_counts(wb, &qn->instances);
77 query_target_metric_counts(wb, &qn->metrics);
78 query_target_alerts_counts(wb, &qn->alerts, NULL, false);
79 }
80
81 query_target_points_statistics(wb, qt, &qn->query_points);
82 buffer_json_object_close(wb);
83
84 aggregate_into_summary_totals(totals, &qn->metrics);
85 } else {
86 // Aggregate the remaining nodes
87 QUERY_NODE *qn = query_node(qt, items[i].index);
88 remaining_contribution += items[i].contribution;
89 remaining_count++;
90
91 // Aggregate metrics, instances, and alerts counts
92 aggregate_metrics_counts(&aggregated_metrics, &qn->metrics);
93 aggregate_instances_counts(&aggregated_instances, &qn->instances);
94 aggregate_alerts_counts(&aggregated_alerts, &qn->alerts);
95
96 // Aggregate points
97 storage_point_merge_to(aggregated_points, qn->query_points);
98
99 aggregate_into_summary_totals(totals, &qn->metrics);
100 }
101 }
102
103 // Add the aggregated "remaining" node if there are any
104 if (remaining_count > 0) {
105 buffer_json_add_array_item_object(wb);
106
107 // Add basic info for the aggregated node
108 char remaining_label[50];
109 snprintfz(remaining_label, sizeof(remaining_label), "remaining %zu nodes", remaining_count);
110
111 buffer_json_member_add_string(wb, "id", "__remaining_nodes__");
112 buffer_json_member_add_string(wb, JSKEY(hostname), remaining_label);
113 buffer_json_member_add_double(wb, JSKEY(contribution), remaining_contribution);
114
115 // Only include detailed statistics if MINIMAL_STATS option is not set
116 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
117 query_target_instance_counts(wb, &aggregated_instances);
118 query_target_metric_counts(wb, &aggregated_metrics);
119 query_target_alerts_counts(wb, &aggregated_alerts, NULL, false);
120 }
121
122 query_target_points_statistics(wb, qt, &aggregated_points);
123 buffer_json_object_close(wb);
124 }
125
126 freez(items);
127 } else {
128 // No limiting needed, output all nodes
129 for (size_t c = 0; c < count; c++) {
130 QUERY_NODE *qn = query_node(qt, c);
131 RRDHOST *host = qn->rrdhost;
132 buffer_json_add_array_item_object(wb);
133 buffer_json_node_add_v2(wb, host, qn->slot, qn->duration_ut, show_node_status);
134
135 // Only include detailed statistics if MINIMAL_STATS option is not set
136 if (!(qt->window.options & RRDR_OPTION_MINIMAL_STATS)) {
137 query_target_instance_counts(wb, &qn->instances);
138 query_target_metric_counts(wb, &qn->metrics);
139 query_target_alerts_counts(wb, &qn->alerts, NULL, false);
140 }
141
142 query_target_points_statistics(wb, qt, &qn->query_points);
143 buffer_json_object_close(wb);
144
145 aggregate_into_summary_totals(totals, &qn->metrics);
146 }
147 }
148
149 buffer_json_array_close(wb);
150 }