master
c 273 lines 9.32 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "rrdcontext.h"
4 #include "rrdcontext-internal.h"
5 #include "rrdcontext-context-registry.h"
6 #include "web/mcp/mcp.h"
7
8 // The registry - using a raw JudyL array
9 // Key: STRING pointer
10 // Value: reference count (size_t)
11 static Pvoid_t context_registry_judyl = NULL;
12
13 // Spinlock to protect access to the registry
14 static SPINLOCK context_registry_spinlock = SPINLOCK_INITIALIZER;
15
16 // Clean up the context registry
17 void rrdcontext_context_registry_destroy(void) {
18 spinlock_lock(&context_registry_spinlock);
19
20 Word_t index = 0;
21 Pvoid_t *PValue;
22
23 // Free the strings we've held references to
24 PValue = JudyLFirst(context_registry_judyl, &index, PJE0);
25 while (PValue) {
26 // Each string has been duplicated when added, so free it
27 string_freez((STRING *)index);
28 PValue = JudyLNext(context_registry_judyl, &index, PJE0);
29 }
30
31 // Free the entire Judy array
32 JudyLFreeArray(&context_registry_judyl, PJE0);
33
34 spinlock_unlock(&context_registry_spinlock);
35 }
36
37 // Add a context to the registry or increment its reference count
38 bool rrdcontext_context_registry_add(STRING *context) {
39 if (unlikely(!context))
40 return false;
41
42 bool is_new = false;
43
44 spinlock_lock(&context_registry_spinlock);
45
46 // Get or insert a slot for this context
47 Pvoid_t *PValue = JudyLIns(&context_registry_judyl, (Word_t)context, PJE0);
48
49 if (unlikely(PValue == PJERR)) {
50 // Memory allocation error
51 internal_error(true, "RRDCONTEXT: JudyL memory allocation failed in rrdcontext_context_registry_add()");
52 spinlock_unlock(&context_registry_spinlock);
53 return false;
54 }
55
56 size_t count = (size_t)(Word_t)*PValue;
57
58 if (count == 0) {
59 // This is a new context - duplicate the string to increase its reference count
60 string_dup(context);
61 is_new = true;
62 }
63
64 // Increment the reference count
65 *PValue = (void *)(Word_t)(count + 1);
66
67 spinlock_unlock(&context_registry_spinlock);
68
69 return is_new;
70 }
71
72 // Remove a context from the registry or decrement its reference count
73 bool rrdcontext_context_registry_remove(STRING *context) {
74 if (unlikely(!context))
75 return false;
76
77 bool is_last = false;
78
79 spinlock_lock(&context_registry_spinlock);
80
81 // Try to get the value for this context
82 Pvoid_t *PValue = JudyLGet(context_registry_judyl, (Word_t)context, PJE0);
83
84 if (PValue) {
85 size_t count = (size_t)(Word_t)*PValue;
86
87 if (count > 1) {
88 // More than one reference, just decrement
89 *PValue = (void *)(Word_t)(count - 1);
90 }
91 else {
92 // Last reference - remove it and free the string
93 int ret;
94 ret = JudyLDel(&context_registry_judyl, (Word_t)context, PJE0);
95 if (ret == 1) {
96 string_freez(context);
97 is_last = true;
98 }
99 }
100 }
101
102 spinlock_unlock(&context_registry_spinlock);
103
104 return is_last;
105 }
106
107 // Get the current number of unique contexts
108 size_t rrdcontext_context_registry_unique_count(void) {
109 Word_t count = 0;
110
111 spinlock_lock(&context_registry_spinlock);
112
113 // Count entries manually
114 Word_t index = 0;
115 Pvoid_t *PValue = JudyLFirst(context_registry_judyl, &index, PJE0);
116
117 while (PValue) {
118 count++;
119 PValue = JudyLNext(context_registry_judyl, &index, PJE0);
120 }
121
122 spinlock_unlock(&context_registry_spinlock);
123
124 return (size_t)count;
125 }
126
127 void rrdcontext_context_registry_json_mcp_array(BUFFER *wb, SIMPLE_PATTERN *pattern) {
128 spinlock_lock(&context_registry_spinlock);
129
130 buffer_json_member_add_object(wb, "info");
131 {
132 buffer_json_member_add_string(wb, "instructions",
133 "The following is the list of contexts.\n"
134 "You can get additional information for any context by calling,\n"
135 "the tool " MCP_TOOL_GET_METRICS_DETAILS " with params:\n"
136 "`metrics=context1|context2` to get more information about context1 and context2.\n");
137 }
138 buffer_json_object_close(wb); // info
139
140 buffer_json_member_add_array(wb, "header");
141 buffer_json_add_array_item_string(wb, "context");
142 buffer_json_add_array_item_string(wb, "number_of_nodes_having_it");
143 buffer_json_array_close(wb);
144
145 buffer_json_member_add_array(wb, "contexts");
146
147 Word_t index = 0;
148 bool first = true;
149 Pvoid_t *PValue;
150 while ((PValue = JudyLFirstThenNext(context_registry_judyl, &index, &first))) {
151 if (!index || !*PValue) continue;
152
153 const char *context_name = string2str((STRING *)index);
154
155 // Skip if we have a pattern and it doesn't match
156 if (pattern && !simple_pattern_matches(pattern, context_name))
157 continue;
158
159 buffer_json_add_array_item_array(wb);
160 buffer_json_add_array_item_string(wb, context_name);
161 buffer_json_add_array_item_uint64(wb, *(size_t *)PValue);
162 buffer_json_array_close(wb);
163 }
164
165 buffer_json_array_close(wb);
166
167 spinlock_unlock(&context_registry_spinlock);
168 }
169
170 // Implementation to extract and output unique context categories
171 void rrdcontext_context_registry_json_mcp_categories_array(BUFFER *wb, SIMPLE_PATTERN *pattern) {
172 spinlock_lock(&context_registry_spinlock);
173
174 // JudyL array to store unique category STRINGs as keys and counts as values
175 Pvoid_t categories_judyl = NULL;
176
177 // First pass: count occurrences of each category
178 size_t contexts_count = 0, contexts_size = 0;
179 Word_t index = 0;
180 bool first = true;
181 Pvoid_t *PValue;
182 while ((PValue = JudyLFirstThenNext(context_registry_judyl, &index, &first))) {
183 if (!index || !*PValue) continue;
184
185 const char *context_name = string2str((STRING *)index);
186 contexts_size += string_strlen((STRING *)index) + 10;
187 contexts_count++;
188
189 // Find the last dot in the context name
190 const char *first_dot = strchr(context_name, '.');
191
192 // Create a STRING for the category (everything up to the last dot)
193 STRING *category_str;
194 if (first_dot) {
195 // Create a STRING with the part before the last dot
196 category_str = string_strndupz(context_name, first_dot - context_name);
197 } else {
198 // No dots, use the entire context as the category
199 category_str = string_strdupz(context_name);
200 }
201
202 if (!category_str) continue;
203
204 // Get or insert a slot for this category
205 Pvoid_t *CategoryValue = JudyLIns(&categories_judyl, (Word_t)category_str, PJE0);
206
207 if (CategoryValue) {
208 // Check if this is a new entry
209 size_t count = (size_t)(Word_t)*CategoryValue;
210 if (count > 0) {
211 // Already exists, free our reference (JudyL already has one)
212 string_freez(category_str);
213 }
214 // Increment the count
215 *CategoryValue = (void *)(Word_t)(count + 1);
216 } else {
217 // Failed to insert, free the STRING
218 string_freez(category_str);
219 }
220 }
221
222 // Second pass: output the unique categories and their counts
223
224 // Header information
225 buffer_json_member_add_object(wb, "info");
226 {
227 buffer_json_member_add_uint64(wb, "original_contexts_count", contexts_count);
228 buffer_json_member_add_uint64(wb, "original_contexts_size", contexts_size);
229 buffer_json_member_add_string(wb, "instructions",
230 "The following list groups metric contexts by prefix.\n"
231 "In case the original list of contexts is too big to be processed at once,\n"
232 "use the `q` parameter to fetch the contexts in smaller batches.\n"
233 "Example: call the " MCP_TOOL_LIST_METRICS " with params:\n"
234 "`q=system.*|net.*` to get all system.* and net.* contexts\n");
235 }
236 buffer_json_object_close(wb); // info
237
238 buffer_json_member_add_array(wb, "header");
239 buffer_json_add_array_item_string(wb, "category");
240 buffer_json_add_array_item_string(wb, "number_of_contexts");
241 buffer_json_array_close(wb);
242
243 buffer_json_member_add_array(wb, "categories");
244
245 index = 0;
246 first = true;
247 while ((PValue = JudyLFirstThenNext(categories_judyl, &index, &first))) {
248 if (!index) continue;
249
250 STRING *category_str = (STRING *)index;
251 const char *category = string2str(category_str);
252
253 // Apply pattern filtering here, on the category itself
254 if (!pattern || simple_pattern_matches(pattern, category)) {
255 size_t count = (size_t)(Word_t)*PValue;
256
257 buffer_json_add_array_item_array(wb);
258 buffer_json_add_array_item_string(wb, category);
259 buffer_json_add_array_item_uint64(wb, count);
260 buffer_json_array_close(wb);
261 }
262
263 // Free the STRING object as we go
264 string_freez(category_str);
265 }
266
267 buffer_json_array_close(wb);
268
269 // Free the JudyL array (values were already freed in the loop above)
270 JudyLFreeArray(&categories_judyl, PJE0);
271
272 spinlock_unlock(&context_registry_spinlock);
273 }