master
c 350 lines 12.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "rrd.h"
4 #include "rrdfunctions-internals.h"
5
6 #define MAX_FUNCTION_LENGTH (PLUGINSD_LINE_MAX - 512) // we need some space for the rest of the line
7
8 // ----------------------------------------------------------------------------
9
10 // we keep a dictionary per RRDSET with these functions
11 // the dictionary is created on demand (only when a function is added to an RRDSET)
12
13 // ----------------------------------------------------------------------------
14
15 static void rrd_functions_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func, void *rrdhost) {
16 RRDHOST *host = rrdhost;
17 struct rrd_host_function *rdcf = func;
18
19 rrd_collector_started();
20 rdcf->collector = rrd_collector_acquire_current_thread();
21 rdcf->rrdhost_state_id = object_state_id(&host->state_id);
22
23 if(!rdcf->priority)
24 rdcf->priority = RRDFUNCTIONS_PRIORITY_DEFAULT;
25
26 // internal_error(true, "FUNCTIONS: adding function '%s' on host '%s', collection tid %d, %s",
27 // dictionary_acquired_item_name(item), rrdhost_hostname(host),
28 // rdcf->collector->tid, rdcf->collector->running ? "running" : "NOT running");
29 }
30
31 static void rrd_functions_cleanup(struct rrd_host_function *rdcf) {
32 rrd_collector_release(rdcf->collector);
33 string_freez(rdcf->help);
34 string_freez(rdcf->tags);
35 }
36
37 static void rrd_functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
38 void *rrdhost __maybe_unused) {
39 struct rrd_host_function *rdcf = func;
40 rrd_functions_cleanup(rdcf);
41 }
42
43 static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
44 void *new_func, void *rrdhost) {
45 RRDHOST *host = rrdhost; (void)host;
46 struct rrd_host_function *rdcf = func;
47 struct rrd_host_function *new_rdcf = new_func;
48
49 rrd_collector_started();
50
51 bool changed = false;
52
53 if(rdcf->collector != thread_rrd_collector) {
54 nd_log(NDLS_DAEMON, NDLP_DEBUG,
55 "FUNCTIONS: function '%s' of host '%s' changed collector from %d to %d",
56 dictionary_acquired_item_name(item), rrdhost_hostname(host),
57 rrd_collector_tid(rdcf->collector), rrd_collector_tid(thread_rrd_collector));
58
59 new_rdcf->collector = rdcf->collector;
60 rdcf->collector = rrd_collector_acquire_current_thread();
61 changed = true;
62 }
63
64 if(rdcf->rrdhost_state_id != object_state_id(&host->state_id)) {
65 nd_log(NDLS_DAEMON, NDLP_DEBUG,
66 "FUNCTIONS: function '%s' of host '%s' changed state id from %u to %u",
67 dictionary_acquired_item_name(item), rrdhost_hostname(host),
68 rdcf->rrdhost_state_id,
69 object_state_id(&host->state_id));
70
71 rdcf->rrdhost_state_id = object_state_id(&host->state_id);
72 changed = true;
73 }
74
75 if(rdcf->execute_cb != new_rdcf->execute_cb) {
76 nd_log(NDLS_DAEMON, NDLP_DEBUG,
77 "FUNCTIONS: function '%s' of host '%s' changed execute callback",
78 dictionary_acquired_item_name(item), rrdhost_hostname(host));
79
80 SWAP(rdcf->execute_cb, new_rdcf->execute_cb);
81 changed = true;
82 }
83
84 if(rdcf->help != new_rdcf->help) {
85 nd_log(NDLS_DAEMON, NDLP_DEBUG,
86 "FUNCTIONS: function '%s' of host '%s' changed help text",
87 dictionary_acquired_item_name(item), rrdhost_hostname(host));
88
89 SWAP(rdcf->help, new_rdcf->help);
90 changed = true;
91 }
92
93 if(rdcf->tags != new_rdcf->tags) {
94 nd_log(NDLS_DAEMON, NDLP_DEBUG,
95 "FUNCTIONS: function '%s' of host '%s' changed tags",
96 dictionary_acquired_item_name(item), rrdhost_hostname(host));
97
98 SWAP(rdcf->tags, new_rdcf->tags);
99 changed = true;
100 }
101
102 if(rdcf->timeout != new_rdcf->timeout) {
103 nd_log(NDLS_DAEMON, NDLP_DEBUG,
104 "FUNCTIONS: function '%s' of host '%s' changed timeout (from %d to %d)",
105 dictionary_acquired_item_name(item), rrdhost_hostname(host),
106 rdcf->timeout, new_rdcf->timeout);
107
108 SWAP(rdcf->timeout, new_rdcf->timeout);
109 changed = true;
110 }
111
112 if(rdcf->version != new_rdcf->version) {
113 nd_log(NDLS_DAEMON, NDLP_DEBUG,
114 "FUNCTIONS: function '%s' of host '%s' changed version (from %"PRIu32", to %"PRIu32")",
115 dictionary_acquired_item_name(item), rrdhost_hostname(host),
116 rdcf->version, new_rdcf->version);
117
118 SWAP(rdcf->version, new_rdcf->version);
119 changed = true;
120 }
121
122 if(rdcf->priority != new_rdcf->priority) {
123 nd_log(NDLS_DAEMON, NDLP_DEBUG,
124 "FUNCTIONS: function '%s' of host '%s' changed priority",
125 dictionary_acquired_item_name(item), rrdhost_hostname(host));
126
127 SWAP(rdcf->priority, new_rdcf->priority);
128 changed = true;
129 }
130
131 if(rdcf->access != new_rdcf->access) {
132 nd_log(NDLS_DAEMON, NDLP_DEBUG,
133 "FUNCTIONS: function '%s' of host '%s' changed access level",
134 dictionary_acquired_item_name(item), rrdhost_hostname(host));
135
136 SWAP(rdcf->access, new_rdcf->access);
137 changed = true;
138 }
139
140 if(rdcf->sync != new_rdcf->sync) {
141 nd_log(NDLS_DAEMON, NDLP_DEBUG,
142 "FUNCTIONS: function '%s' of host '%s' changed sync/async mode",
143 dictionary_acquired_item_name(item), rrdhost_hostname(host));
144
145 SWAP(rdcf->sync, new_rdcf->sync);
146 changed = true;
147 }
148
149 if(rdcf->execute_cb_data != new_rdcf->execute_cb_data) {
150 nd_log(NDLS_DAEMON, NDLP_DEBUG,
151 "FUNCTIONS: function '%s' of host '%s' changed execute callback data",
152 dictionary_acquired_item_name(item), rrdhost_hostname(host));
153
154 SWAP(rdcf->execute_cb_data, new_rdcf->execute_cb_data);
155 changed = true;
156 }
157
158 // internal_error(true, "FUNCTIONS: adding function '%s' on host '%s', collection tid %d, %s",
159 // dictionary_acquired_item_name(item), rrdhost_hostname(host),
160 // rdcf->collector->tid, rdcf->collector->running ? "running" : "NOT running");
161
162 rrd_functions_cleanup(new_rdcf);
163
164 return changed;
165 }
166
167 void rrd_functions_host_init(RRDHOST *host) {
168 if(host->functions) return;
169
170 host->functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
171 &dictionary_stats_category_functions, sizeof(struct rrd_host_function));
172
173 dictionary_register_insert_callback(host->functions, rrd_functions_insert_callback, host);
174 dictionary_register_delete_callback(host->functions, rrd_functions_delete_callback, host);
175 dictionary_register_conflict_callback(host->functions, rrd_functions_conflict_callback, host);
176 }
177
178 void rrd_functions_host_destroy(RRDHOST *host) {
179 dictionary_destroy(host->functions);
180 host->functions = NULL;
181 }
182
183 // ----------------------------------------------------------------------------
184
185 static inline bool is_function_restricted(const char *name, const char *tags) {
186 return (name && name[0] == '_' && name[1] == '_') || (tags && strstr(tags, RRDFUNCTIONS_TAG_HIDDEN) != NULL);
187 }
188
189 static inline bool is_function_dyncfg(const char *name) {
190 if(!name || !*name)
191 return false;
192
193 if(strncmp(name, PLUGINSD_FUNCTION_CONFIG, sizeof(PLUGINSD_FUNCTION_CONFIG) - 1) != 0)
194 return false;
195
196 char c = name[sizeof(PLUGINSD_FUNCTION_CONFIG) - 1];
197 if(c == 0 || isspace(c))
198 return true;
199
200 return false;
201 }
202
203 static inline RRD_FUNCTION_OPTIONS get_function_options(RRDSET *st, const char *name, const char *tags) {
204 if(is_function_dyncfg(name))
205 return RRD_FUNCTION_DYNCFG;
206
207 RRD_FUNCTION_OPTIONS options = st ? RRD_FUNCTION_LOCAL : RRD_FUNCTION_GLOBAL;
208
209 return options | (is_function_restricted(name, tags) ? RRD_FUNCTION_RESTRICTED : 0);
210 }
211
212 void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority, uint32_t version,
213 const char *help, const char *tags,
214 HTTP_ACCESS access, bool sync,
215 rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
216
217 // RRDSET *st may be NULL in this function
218 // to create a GLOBAL function
219
220 if(!tags || !*tags) {
221 if(strcmp(name, "systemd-journal") == 0)
222 tags = "logs";
223 else
224 tags = "top";
225 }
226
227 if(st && !st->functions_view)
228 st->functions_view = dictionary_create_view(host->functions);
229
230 char key[strlen(name) + 1];
231 rrd_functions_sanitize(key, name, sizeof(key));
232
233 struct rrd_host_function tmp = {
234 .collector = NULL,
235 .sync = sync,
236 .timeout = timeout,
237 .version = version,
238 .priority = priority,
239 .options = get_function_options(st, name, tags),
240 .access = access,
241 .execute_cb = execute_cb,
242 .execute_cb_data = execute_cb_data,
243 .help = string_strdupz(help),
244 .tags = string_strdupz(tags),
245 };
246 const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(host->functions, key, &tmp, sizeof(tmp));
247
248 if(st)
249 dictionary_view_set(st->functions_view, key, item);
250 else
251 rrdhost_flag_set(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED);
252
253 dictionary_acquired_item_release(host->functions, item);
254 }
255
256 void rrd_function_del(RRDHOST *host, RRDSET *st, const char *name) {
257 char key[strlen(name) + 1];
258 rrd_functions_sanitize(key, name, sizeof(key));
259 dictionary_del(host->functions, key);
260
261 if(st)
262 dictionary_del(st->functions_view, key);
263 else
264 rrdhost_flag_set(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED);
265
266 dictionary_garbage_collect(host->functions);
267 }
268
269 int rrd_functions_find_by_name(RRDHOST *host, BUFFER *wb, const char *name, size_t key_length, const DICTIONARY_ITEM **item) {
270 char buffer[MAX_FUNCTION_LENGTH + 1];
271 strncpyz(buffer, name, sizeof(buffer) - 1);
272 char *s = NULL;
273
274 OBJECT_STATE_ID state_id = object_state_id(&host->state_id);
275
276 bool found = false;
277 *item = NULL;
278 if(host->functions) {
279 while (buffer[0]) {
280 if((*item = dictionary_get_and_acquire_item(host->functions, buffer))) {
281 found = true;
282
283 struct rrd_host_function *rdcf = dictionary_acquired_item_value(*item);
284 if(rrd_collector_running(rdcf->collector) && rdcf->rrdhost_state_id == state_id) {
285 break;
286 }
287 else {
288
289 nd_log(NDLS_DAEMON, NDLP_DEBUG,
290 "Function '%s' is not available. "
291 "host '%s', collector = { tid: %d, running: %s }, host tid { rcv: %d, snd: %d }, host state { id: %u, expected %u }, hops: %d",
292 name,
293 rrdhost_hostname(host),
294 rrd_collector_tid(rdcf->collector),
295 rrd_collector_running(rdcf->collector) ? "yes" : "no",
296 host->stream.rcv.status.tid, host->stream.snd.status.tid,
297 state_id, rdcf->rrdhost_state_id,
298 rrdhost_ingestion_hops(host)
299 );
300
301 dictionary_acquired_item_release(host->functions, *item);
302 *item = NULL;
303 }
304 }
305
306 // if s == NULL, set it to the end of the buffer;
307 // this should happen only the first time
308 if (unlikely(!s))
309 s = &buffer[key_length - 1];
310
311 // skip a word from the end
312 while (s >= buffer && !isspace((uint8_t)*s)) *s-- = '\0';
313
314 // skip all spaces
315 while (s >= buffer && isspace((uint8_t)*s)) *s-- = '\0';
316 }
317 }
318
319 buffer_flush(wb);
320
321 if(!(*item)) {
322 if(found)
323 return rrd_call_function_error(wb,
324 "The plugin that registered this feature, is not currently running.",
325 HTTP_RESP_SERVICE_UNAVAILABLE);
326 else
327 return rrd_call_function_error(wb,
328 "This feature is not available on this host at this time.",
329 HTTP_RESP_NOT_FOUND);
330 }
331
332 return HTTP_RESP_OK;
333 }
334
335 bool rrd_function_available(RRDHOST *host, const char *function) {
336 if(!host || !host->functions)
337 return false;
338
339 bool ret = false;
340 const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(host->functions, function);
341 if(item) {
342 struct rrd_host_function *rdcf = dictionary_acquired_item_value(item);
343 if(rrd_collector_running(rdcf->collector) && rdcf->rrdhost_state_id == object_state_id(&host->state_id))
344 ret = true;
345
346 dictionary_acquired_item_release(host->functions, item);
347 }
348
349 return ret;
350 }