| 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 | } |