master
c 2,931 lines 116 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "database/rrd.h"
4
5 #define STATSD_CHART_PREFIX "statsd"
6
7 #define PLUGIN_STATSD_NAME "statsd.plugin"
8
9 #define STATSD_LISTEN_PORT 8125
10 #define STATSD_LISTEN_BACKLOG 4096
11
12 #define WORKER_JOB_TYPE_TCP_CONNECTED 0
13 #define WORKER_JOB_TYPE_TCP_DISCONNECTED 1
14 #define WORKER_JOB_TYPE_RCV_DATA 2
15 #define WORKER_JOB_TYPE_SND_DATA 3
16
17 #if WORKER_UTILIZATION_MAX_JOB_TYPES < 4
18 #error Please increase WORKER_UTILIZATION_MAX_JOB_TYPES to at least 4
19 #endif
20
21 // --------------------------------------------------------------------------------------
22
23 // DO NOT ENABLE MULTITHREADING - IT IS NOT WELL TESTED
24 // #define STATSD_MULTITHREADED 1
25
26 #define STATSD_DICTIONARY_OPTIONS (DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_ADD_IN_FRONT)
27 #define STATSD_DECIMAL_DETAIL 1000 // floating point values get multiplied by this, with the same divisor
28
29 // --------------------------------------------------------------------------------------------------------------------
30 // data specific to each metric type
31
32 typedef struct statsd_metric_gauge {
33 NETDATA_DOUBLE value;
34 } STATSD_METRIC_GAUGE;
35
36 typedef struct statsd_metric_counter { // counter and meter
37 collected_number value;
38 } STATSD_METRIC_COUNTER;
39
40 typedef struct statsd_histogram_extensions {
41 netdata_mutex_t mutex;
42
43 // average is stored in metric->last
44 collected_number last_min;
45 collected_number last_max;
46 collected_number last_percentile;
47 collected_number last_median;
48 collected_number last_stddev;
49 collected_number last_sum;
50
51 int zeroed;
52
53 RRDDIM *rd_min;
54 RRDDIM *rd_max;
55 RRDDIM *rd_percentile;
56 RRDDIM *rd_median;
57 RRDDIM *rd_stddev;
58 //RRDDIM *rd_sum;
59
60 uint32_t size;
61 uint32_t used;
62 NETDATA_DOUBLE *values; // dynamic array of values collected
63 } STATSD_METRIC_HISTOGRAM_EXTENSIONS;
64
65 typedef struct statsd_metric_histogram { // histogram and timer
66 STATSD_METRIC_HISTOGRAM_EXTENSIONS *ext;
67 } STATSD_METRIC_HISTOGRAM;
68
69 typedef struct statsd_metric_set {
70 DICTIONARY *dict;
71 } STATSD_METRIC_SET;
72
73 typedef struct statsd_metric_dictionary_item {
74 uint32_t count;
75 RRDDIM *rd;
76 } STATSD_METRIC_DICTIONARY_ITEM;
77
78 typedef struct statsd_metric_dictionary {
79 DICTIONARY *dict;
80 } STATSD_METRIC_DICTIONARY;
81
82
83 // --------------------------------------------------------------------------------------------------------------------
84 // this is a metric - for all types of metrics
85
86 typedef enum __attribute__((packed)) statsd_metric_options {
87 STATSD_METRIC_OPTION_NONE = 0x00000000, // no options set
88 STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED = 0x00000001, // do not update the chart dimension, when this metric is not collected
89 STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED = 0x00000002, // render a private chart for this metric
90 STATSD_METRIC_OPTION_PRIVATE_CHART_CHECKED = 0x00000004, // the metric has been checked if it should get private chart or not
91 STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT = 0x00000008, // show the count of events for this private chart
92 STATSD_METRIC_OPTION_CHECKED_IN_APPS = 0x00000010, // set when this metric has been checked against apps
93 STATSD_METRIC_OPTION_USED_IN_APPS = 0x00000020, // set when this metric is used in apps
94 STATSD_METRIC_OPTION_CHECKED = 0x00000040, // set when the charting thread checks this metric for use in charts (its usefulness)
95 STATSD_METRIC_OPTION_USEFUL = 0x00000080, // set when the charting thread finds the metric useful (i.e. used in a chart)
96 STATSD_METRIC_OPTION_COLLECTION_FULL_LOGGED = 0x00000100, // set when the collection is full for this metric
97 STATSD_METRIC_OPTION_UPDATED_CHART_METADATA = 0x00000200, // set when the private chart metadata have been updated via tags
98 STATSD_METRIC_OPTION_OBSOLETE = 0x00004000, // set when the metric is obsoleted
99 } STATS_METRIC_OPTIONS;
100
101 typedef enum __attribute__((packed)) statsd_metric_type {
102 STATSD_METRIC_TYPE_GAUGE,
103 STATSD_METRIC_TYPE_COUNTER,
104 STATSD_METRIC_TYPE_METER,
105 STATSD_METRIC_TYPE_TIMER,
106 STATSD_METRIC_TYPE_HISTOGRAM,
107 STATSD_METRIC_TYPE_SET,
108 STATSD_METRIC_TYPE_DICTIONARY
109 } STATSD_METRIC_TYPE;
110
111
112 typedef struct statsd_metric {
113 const char *name; // the name of the metric - linked to dictionary name
114 uint32_t hash; // hash of the name
115
116 STATSD_METRIC_TYPE type;
117
118 // metadata about data collection
119 collected_number events; // the number of times this metric has been collected (never resets)
120 uint32_t count; // the number of times this metric has been collected since the last flush
121 time_t last_collected; // timestamp of the last incoming value
122
123 // the actual collected data
124 union {
125 STATSD_METRIC_GAUGE gauge;
126 STATSD_METRIC_COUNTER counter;
127 STATSD_METRIC_HISTOGRAM histogram;
128 STATSD_METRIC_SET set;
129 STATSD_METRIC_DICTIONARY dictionary;
130 };
131
132 char *units;
133 char *dimname;
134 char *family;
135
136 // chart related members
137 STATS_METRIC_OPTIONS options; // STATSD_METRIC_OPTION_* (bitfield)
138 char reset; // set to 1 by the charting thread to instruct the collector thread(s) to reset this metric
139 collected_number last; // the last value sent to netdata
140 RRDSET *st; // the private chart of this metric
141 RRDDIM *rd_value; // the dimension of this metric value
142 RRDDIM *rd_count; // the dimension for the number of events received
143
144 // linking, used for walking through all metrics
145 struct statsd_metric *next_useful;
146 } STATSD_METRIC;
147
148
149 // --------------------------------------------------------------------------------------------------------------------
150 // each type of metric has its own index
151
152 typedef struct statsd_index {
153 char *name; // the name of the index of metrics
154 uint32_t events; // the number of events processed for this index
155 uint32_t metrics; // the number of metrics in this index
156 uint32_t useful; // the number of useful metrics in this index
157
158 STATS_METRIC_OPTIONS default_options; // default options for all metrics in this index
159 STATSD_METRIC_TYPE type; // the type of index
160 DICTIONARY *dict;
161
162 STATSD_METRIC *first_useful; // the linked list of useful metrics (new metrics are added in front)
163 } STATSD_INDEX;
164
165 // --------------------------------------------------------------------------------------------------------------------
166 // synthetic charts
167
168 typedef enum __attribute__((packed)) statsd_app_chart_dimension_value_type {
169 STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS,
170 STATSD_APP_CHART_DIM_VALUE_TYPE_LAST,
171 STATSD_APP_CHART_DIM_VALUE_TYPE_AVERAGE,
172 STATSD_APP_CHART_DIM_VALUE_TYPE_SUM,
173 STATSD_APP_CHART_DIM_VALUE_TYPE_MIN,
174 STATSD_APP_CHART_DIM_VALUE_TYPE_MAX,
175 STATSD_APP_CHART_DIM_VALUE_TYPE_PERCENTILE,
176 STATSD_APP_CHART_DIM_VALUE_TYPE_MEDIAN,
177 STATSD_APP_CHART_DIM_VALUE_TYPE_STDDEV
178 } STATSD_APP_CHART_DIM_VALUE_TYPE;
179
180 typedef struct statsd_app_chart_dimension {
181 const char *name; // the name of this dimension
182 const char *metric; // the source metric name of this dimension
183 uint32_t metric_hash; // hash for fast string comparisons
184
185 int32_t multiplier; // the multiplier of the dimension
186 int32_t divisor; // the divisor of the dimension
187 RRDDIM_FLAGS flags; // the RRDDIM flags for this dimension
188 RRDDIM_OPTIONS options; // the RRDDIM options for this dimension
189 RRD_ALGORITHM algorithm; // the algorithm of this dimension
190
191 STATSD_APP_CHART_DIM_VALUE_TYPE value_type; // which value to use of the source metric
192
193 SIMPLE_PATTERN *metric_pattern; // set when the 'metric' is a simple pattern
194
195 RRDDIM *rd; // a pointer to the RRDDIM that has been created for this dimension
196 collected_number *value_ptr; // a pointer to the source metric value
197
198 struct statsd_app_chart_dimension *next; // the next dimension for this chart
199 } STATSD_APP_CHART_DIM;
200
201 typedef struct statsd_app_chart {
202 const char *id;
203 const char *name;
204 const char *title;
205 const char *family;
206 const char *context;
207 const char *units;
208 const char *module;
209 int32_t priority;
210 RRDSET_TYPE chart_type;
211 STATSD_APP_CHART_DIM *dimensions;
212 uint32_t dimensions_count;
213 uint32_t dimensions_linked_count;
214
215 RRDSET *st;
216 struct statsd_app_chart *next;
217 } STATSD_APP_CHART;
218
219 typedef struct statsd_app {
220 const char *name;
221 SIMPLE_PATTERN *metrics;
222 STATS_METRIC_OPTIONS default_options;
223 RRD_DB_MODE rrd_memory_mode;
224 int32_t rrd_history_entries;
225 DICTIONARY *dict;
226
227 const char *source;
228 STATSD_APP_CHART *charts;
229 struct statsd_app *next;
230 } STATSD_APP;
231
232 // --------------------------------------------------------------------------------------------------------------------
233 // global statsd data
234
235 struct collection_thread_status {
236 SPINLOCK spinlock;
237 bool initializing;
238 uint32_t max_sockets;
239
240 ND_THREAD *thread;
241 };
242
243 static struct statsd {
244 STATSD_INDEX gauges;
245 STATSD_INDEX counters;
246 STATSD_INDEX timers;
247 STATSD_INDEX histograms;
248 STATSD_INDEX meters;
249 STATSD_INDEX sets;
250 STATSD_INDEX dictionaries;
251
252 size_t unknown_types;
253 size_t socket_errors;
254 size_t tcp_socket_connects;
255 size_t tcp_socket_disconnects;
256 size_t tcp_socket_connected;
257 size_t tcp_socket_reads;
258 size_t tcp_packets_received;
259 size_t tcp_bytes_read;
260 size_t udp_socket_reads;
261 size_t udp_packets_received;
262 size_t udp_bytes_read;
263
264 time_t update_every;
265 bool enabled;
266 bool private_charts_hidden;
267 SIMPLE_PATTERN *charts_for;
268
269 uint32_t tcp_idle_timeout;
270 collected_number decimal_detail;
271 uint32_t private_charts;
272 uint32_t max_private_charts_hard;
273 uint32_t set_obsolete_after;
274
275 STATSD_APP *apps;
276 uint32_t recvmmsg_size;
277 uint32_t histogram_increase_step;
278 uint32_t dictionary_max_unique;
279 double histogram_percentile;
280 char *histogram_percentile_str;
281
282 int threads;
283 struct collection_thread_status *collection_threads_status;
284
285 LISTEN_SOCKETS sockets;
286 } statsd = {
287 .enabled = 1,
288 .max_private_charts_hard = 1000,
289 .private_charts_hidden = false,
290 .recvmmsg_size = 10,
291 .decimal_detail = STATSD_DECIMAL_DETAIL,
292
293 .gauges = {
294 .name = "gauge",
295 .events = 0,
296 .metrics = 0,
297 .dict = NULL,
298 .type = STATSD_METRIC_TYPE_GAUGE,
299 .default_options = STATSD_METRIC_OPTION_NONE
300 },
301 .counters = {
302 .name = "counter",
303 .events = 0,
304 .metrics = 0,
305 .dict = NULL,
306 .type = STATSD_METRIC_TYPE_COUNTER,
307 .default_options = STATSD_METRIC_OPTION_NONE
308 },
309 .timers = {
310 .name = "timer",
311 .events = 0,
312 .metrics = 0,
313 .dict = NULL,
314 .type = STATSD_METRIC_TYPE_TIMER,
315 .default_options = STATSD_METRIC_OPTION_NONE
316 },
317 .histograms = {
318 .name = "histogram",
319 .events = 0,
320 .metrics = 0,
321 .dict = NULL,
322 .type = STATSD_METRIC_TYPE_HISTOGRAM,
323 .default_options = STATSD_METRIC_OPTION_NONE
324 },
325 .meters = {
326 .name = "meter",
327 .events = 0,
328 .metrics = 0,
329 .dict = NULL,
330 .type = STATSD_METRIC_TYPE_METER,
331 .default_options = STATSD_METRIC_OPTION_NONE
332 },
333 .sets = {
334 .name = "set",
335 .events = 0,
336 .metrics = 0,
337 .dict = NULL,
338 .type = STATSD_METRIC_TYPE_SET,
339 .default_options = STATSD_METRIC_OPTION_NONE
340 },
341 .dictionaries = {
342 .name = "dictionary",
343 .events = 0,
344 .metrics = 0,
345 .dict = NULL,
346 .type = STATSD_METRIC_TYPE_DICTIONARY,
347 .default_options = STATSD_METRIC_OPTION_NONE
348 },
349
350 .tcp_idle_timeout = 600,
351
352 .apps = NULL,
353 .histogram_percentile = 95.0,
354 .histogram_increase_step = 10,
355 .dictionary_max_unique = 200,
356 .threads = 0,
357 .collection_threads_status = NULL,
358 .sockets = {
359 .config = &netdata_config,
360 .config_section = CONFIG_SECTION_STATSD,
361 .default_bind_to = "udp:localhost tcp:localhost",
362 .default_port = STATSD_LISTEN_PORT,
363 .backlog = STATSD_LISTEN_BACKLOG
364 },
365 };
366
367
368 // --------------------------------------------------------------------------------------------------------------------
369 // statsd index management - add/find metrics
370
371 static void dictionary_metric_insert_callback(const DICTIONARY_ITEM *item, void *value, void *data) {
372 STATSD_INDEX *index = (STATSD_INDEX *)data;
373 STATSD_METRIC *m = (STATSD_METRIC *)value;
374 const char *name = dictionary_acquired_item_name(item);
375
376 netdata_log_debug(D_STATSD, "Creating new %s metric '%s'", index->name, name);
377
378 m->name = name;
379 m->hash = simple_hash(name);
380 m->type = index->type;
381 m->options = index->default_options;
382
383 if (m->type == STATSD_METRIC_TYPE_HISTOGRAM || m->type == STATSD_METRIC_TYPE_TIMER) {
384 m->histogram.ext = callocz(1,sizeof(STATSD_METRIC_HISTOGRAM_EXTENSIONS));
385 netdata_mutex_init(&m->histogram.ext->mutex);
386 }
387
388 __atomic_fetch_add(&index->metrics, 1, __ATOMIC_RELAXED);
389 }
390
391 static void dictionary_metric_delete_callback(const DICTIONARY_ITEM *item, void *value, void *data) {
392 (void)data; // STATSD_INDEX *index = (STATSD_INDEX *)data;
393 (void)item;
394 STATSD_METRIC *m = (STATSD_METRIC *)value;
395
396 if(m->type == STATSD_METRIC_TYPE_HISTOGRAM || m->type == STATSD_METRIC_TYPE_TIMER) {
397 freez(m->histogram.ext);
398 m->histogram.ext = NULL;
399 }
400
401 freez(m->units);
402 freez(m->family);
403 freez(m->dimname);
404 }
405
406 static inline STATSD_METRIC *statsd_find_or_add_metric(STATSD_INDEX *index, const char *name) {
407 netdata_log_debug(D_STATSD, "searching for metric '%s' under '%s'", name, index->name);
408
409 #ifdef STATSD_MULTITHREADED
410 // avoid the write lock of dictionary_set() for existing metrics
411 STATSD_METRIC *m = dictionary_get(index->dict, name);
412 if(!m) m = dictionary_set(index->dict, name, NULL, sizeof(STATSD_METRIC));
413 #else
414 // no locks here, go faster
415 // this will call the dictionary_metric_insert_callback() if an item
416 // is inserted, otherwise it will return the existing one.
417 // We used the flag DICT_OPTION_DONT_OVERWRITE_VALUE to support this.
418 STATSD_METRIC *m = dictionary_set(index->dict, name, NULL, sizeof(STATSD_METRIC));
419 #endif
420
421 index->events++;
422 return m;
423 }
424
425
426 // --------------------------------------------------------------------------------------------------------------------
427 // statsd parsing numbers
428
429 static inline NETDATA_DOUBLE statsd_parse_float(const char *v, NETDATA_DOUBLE def) {
430 NETDATA_DOUBLE value;
431
432 if(likely(v && *v)) {
433 char *e = NULL;
434 value = str2ndd(v, &e);
435 if(unlikely(e && *e))
436 collector_error("STATSD: excess data '%s' after value '%s'", e, v);
437 }
438 else
439 value = def;
440
441 return value;
442 }
443
444 static inline NETDATA_DOUBLE statsd_parse_sampling_rate(const char *v) {
445 NETDATA_DOUBLE sampling_rate = statsd_parse_float(v, 1.0);
446 if(unlikely(isless(sampling_rate, 0.001))) sampling_rate = 0.001;
447 if(unlikely(isgreater(sampling_rate, 1.0))) sampling_rate = 1.0;
448 return sampling_rate;
449 }
450
451 static inline long long statsd_parse_int(const char *v, long long def) {
452 long long value;
453
454 if(likely(v && *v)) {
455 char *e = NULL;
456 value = str2ll(v, &e);
457 if(unlikely(e && *e))
458 collector_error("STATSD: excess data '%s' after value '%s'", e, v);
459 }
460 else
461 value = def;
462
463 return value;
464 }
465
466
467 // --------------------------------------------------------------------------------------------------------------------
468 // statsd processors per metric type
469
470 static inline void statsd_reset_metric(STATSD_METRIC *m) {
471 m->reset = 0;
472 m->count = 0;
473 }
474
475 static inline int value_is_zinit(const char *value) {
476 return (value && *value == 'z' && *++value == 'i' && *++value == 'n' && *++value == 'i' && *++value == 't' && *++value == '\0');
477 }
478
479 #define is_metric_checked(m) ((m)->options & STATSD_METRIC_OPTION_CHECKED)
480 #define is_metric_useful_for_collection(m) (!is_metric_checked(m) || ((m)->options & STATSD_METRIC_OPTION_USEFUL))
481
482 static inline void metric_update_counters_and_obsoletion(STATSD_METRIC *m) {
483 m->events++;
484 m->count++;
485 m->last_collected = now_realtime_sec();
486 m->options &= ~STATSD_METRIC_OPTION_OBSOLETE;
487 }
488
489 static inline void statsd_process_gauge(STATSD_METRIC *m, const char *value, const char *sampling) {
490 if(!is_metric_useful_for_collection(m)) return;
491
492 if(unlikely(!value || !*value)) {
493 collector_error("STATSD: metric '%s' of type gauge, with empty value is ignored.", m->name);
494 return;
495 }
496
497 if(unlikely(m->reset)) {
498 // no need to reset anything specific for gauges
499 statsd_reset_metric(m);
500 }
501
502 if(unlikely(value_is_zinit(value))) {
503 // magic loading of metric, without affecting anything
504 }
505 else {
506 if (unlikely(*value == '+' || *value == '-'))
507 m->gauge.value += statsd_parse_float(value, 1.0) / statsd_parse_sampling_rate(sampling);
508 else
509 m->gauge.value = statsd_parse_float(value, 1.0);
510
511 metric_update_counters_and_obsoletion(m);
512 }
513 }
514
515 static inline void statsd_process_counter_or_meter(STATSD_METRIC *m, const char *value, const char *sampling) {
516 if(!is_metric_useful_for_collection(m)) return;
517
518 // we accept empty values for counters
519
520 if(unlikely(m->reset)) statsd_reset_metric(m);
521
522 if(unlikely(value_is_zinit(value))) {
523 // magic loading of metric, without affecting anything
524 }
525 else {
526 m->counter.value += llrintndd((NETDATA_DOUBLE) statsd_parse_int(value, 1) / statsd_parse_sampling_rate(sampling));
527
528 metric_update_counters_and_obsoletion(m);
529 }
530 }
531
532 #define statsd_process_counter(m, value, sampling) statsd_process_counter_or_meter(m, value, sampling)
533 #define statsd_process_meter(m, value, sampling) statsd_process_counter_or_meter(m, value, sampling)
534
535 static inline void statsd_process_histogram_or_timer(STATSD_METRIC *m, const char *value, const char *sampling, const char *type) {
536 if(!is_metric_useful_for_collection(m)) return;
537
538 if(unlikely(!value || !*value)) {
539 collector_error("STATSD: metric of type %s, with empty value is ignored.", type);
540 return;
541 }
542
543 if(unlikely(value_is_zinit(value))) {
544 // magic loading of metric, without affecting anything
545
546 netdata_mutex_lock(&m->histogram.ext->mutex);
547 if(unlikely(m->reset)) {
548 m->histogram.ext->used = 0;
549 statsd_reset_metric(m);
550 }
551 netdata_mutex_unlock(&m->histogram.ext->mutex);
552 }
553 else {
554 NETDATA_DOUBLE v = statsd_parse_float(value, 1.0);
555 NETDATA_DOUBLE sampling_rate = statsd_parse_sampling_rate(sampling);
556 if(unlikely(isless(sampling_rate, 0.01))) sampling_rate = 0.01;
557 if(unlikely(isgreater(sampling_rate, 1.0))) sampling_rate = 1.0;
558
559 long long samples = llrintndd(1.0 / sampling_rate);
560 netdata_mutex_lock(&m->histogram.ext->mutex);
561
562 if(unlikely(m->reset)) {
563 m->histogram.ext->used = 0;
564 statsd_reset_metric(m);
565 }
566
567 while(samples-- > 0) {
568 if(unlikely(m->histogram.ext->used == m->histogram.ext->size)) {
569 m->histogram.ext->size += statsd.histogram_increase_step;
570 m->histogram.ext->values = reallocz(m->histogram.ext->values, sizeof(NETDATA_DOUBLE) * m->histogram.ext->size);
571 }
572
573 m->histogram.ext->values[m->histogram.ext->used++] = v;
574 }
575
576 netdata_mutex_unlock(&m->histogram.ext->mutex);
577 metric_update_counters_and_obsoletion(m);
578 }
579 }
580
581 #define statsd_process_timer(m, value, sampling) statsd_process_histogram_or_timer(m, value, sampling, "timer")
582 #define statsd_process_histogram(m, value, sampling) statsd_process_histogram_or_timer(m, value, sampling, "histogram")
583
584 static inline void statsd_process_set(STATSD_METRIC *m, const char *value) {
585 if(!is_metric_useful_for_collection(m)) return;
586
587 if(unlikely(!value || !*value)) {
588 netdata_log_error("STATSD: metric of type set, with empty value is ignored.");
589 return;
590 }
591
592 if(unlikely(m->reset)) {
593 if(likely(m->set.dict)) {
594 dictionary_destroy(m->set.dict);
595 m->set.dict = NULL;
596 }
597 statsd_reset_metric(m);
598 }
599
600 if (unlikely(!m->set.dict))
601 m->set.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS, &dictionary_stats_category_collectors, 0);
602
603 if(unlikely(value_is_zinit(value))) {
604 // magic loading of metric, without affecting anything
605 }
606 else {
607 #ifdef STATSD_MULTITHREADED
608 // avoid the write lock to check if something is already there
609 if(!dictionary_get(m->set.dict, value))
610 dictionary_set(m->set.dict, value, NULL, 0);
611 #else
612 dictionary_set(m->set.dict, value, NULL, 0);
613 #endif
614 metric_update_counters_and_obsoletion(m);
615 }
616 }
617
618 static inline void statsd_process_dictionary(STATSD_METRIC *m, const char *value) {
619 if(!is_metric_useful_for_collection(m)) return;
620
621 if(unlikely(!value || !*value)) {
622 netdata_log_error("STATSD: metric of type set, with empty value is ignored.");
623 return;
624 }
625
626 if(unlikely(m->reset))
627 statsd_reset_metric(m);
628
629 if (unlikely(!m->dictionary.dict))
630 m->dictionary.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC_DICTIONARY_ITEM));
631
632 if(unlikely(value_is_zinit(value))) {
633 // magic loading of metric, without affecting anything
634 }
635 else {
636 STATSD_METRIC_DICTIONARY_ITEM *t = (STATSD_METRIC_DICTIONARY_ITEM *)dictionary_get(m->dictionary.dict, value);
637
638 if (unlikely(!t)) {
639 if(!t && dictionary_entries(m->dictionary.dict) >= statsd.dictionary_max_unique)
640 value = "other";
641
642 t = (STATSD_METRIC_DICTIONARY_ITEM *)dictionary_set(m->dictionary.dict, value, NULL, sizeof(STATSD_METRIC_DICTIONARY_ITEM));
643 }
644
645 t->count++;
646 metric_update_counters_and_obsoletion(m);
647 }
648 }
649
650
651 // --------------------------------------------------------------------------------------------------------------------
652 // statsd parsing
653
654 static inline const char *statsd_parse_skip_up_to(const char *s, char d1, char d2, char d3) {
655 char c;
656
657 for(c = *s; c && c != d1 && c != d2 && c != d3 && c != '\r' && c != '\n'; c = *++s) ;
658
659 return s;
660 }
661
662 const char *statsd_parse_skip_spaces(const char *s) {
663 char c;
664
665 for(c = *s; c && ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ); c = *++s) ;
666
667 return s;
668 }
669
670 static inline const char *statsd_parse_field_trim(const char *start, char *end) {
671 if(unlikely(!start || !*start)) {
672 start = end;
673 return start;
674 }
675
676 while(start <= end && (*start == ' ' || *start == '\t'))
677 start++;
678
679 *end = '\0';
680 end--;
681 while(end >= start && (*end == ' ' || *end == '\t'))
682 *end-- = '\0';
683
684 return start;
685 }
686
687 static void statsd_process_metric(const char *name, const char *value, const char *type, const char *sampling, const char *tags) {
688 netdata_log_debug(D_STATSD, "STATSD: raw metric '%s', value '%s', type '%s', sampling '%s', tags '%s'", name?name:"(null)", value?value:"(null)", type?type:"(null)", sampling?sampling:"(null)", tags?tags:"(null)");
689
690 if(unlikely(!name || !*name)) return;
691 if(unlikely(!type || !*type)) type = "m";
692
693 STATSD_METRIC *m = NULL;
694
695 char t0 = type[0], t1 = type[1];
696 if(unlikely(t0 == 'g' && t1 == '\0')) {
697 statsd_process_gauge(
698 m = statsd_find_or_add_metric(&statsd.gauges, name),
699 value, sampling);
700 }
701 else if(unlikely((t0 == 'c' || t0 == 'C') && t1 == '\0')) {
702 // etsy/statsd uses 'c'
703 // brubeck uses 'C'
704 statsd_process_counter(
705 m = statsd_find_or_add_metric(&statsd.counters, name),
706 value, sampling);
707 }
708 else if(unlikely(t0 == 'm' && t1 == '\0')) {
709 statsd_process_meter(
710 m = statsd_find_or_add_metric(&statsd.meters, name),
711 value, sampling);
712 }
713 else if(unlikely(t0 == 'h' && t1 == '\0')) {
714 statsd_process_histogram(
715 m = statsd_find_or_add_metric(&statsd.histograms, name),
716 value, sampling);
717 }
718 else if(unlikely(t0 == 's' && t1 == '\0')) {
719 statsd_process_set(
720 m = statsd_find_or_add_metric(&statsd.sets, name),
721 value);
722 }
723 else if(unlikely(t0 == 'd' && t1 == '\0')) {
724 statsd_process_dictionary(
725 m = statsd_find_or_add_metric(&statsd.dictionaries, name),
726 value);
727 }
728 else if(unlikely(t0 == 'm' && t1 == 's' && type[2] == '\0')) {
729 statsd_process_timer(
730 m = statsd_find_or_add_metric(&statsd.timers, name),
731 value, sampling);
732 }
733 else {
734 statsd.unknown_types++;
735 netdata_log_error("STATSD: metric '%s' with value '%s' is sent with unknown metric type '%s'", name, value?value:"", type);
736 }
737
738 if(m && tags && *tags) {
739 const char *s = tags;
740 while(*s) {
741 const char *tagkey = NULL, *tagvalue = NULL;
742 char *tagkey_end = NULL, *tagvalue_end = NULL;
743
744 s = tagkey_end = (char *)statsd_parse_skip_up_to(tagkey = s, ':', '=', ',');
745 if(tagkey == tagkey_end) {
746 if (*s) {
747 s++;
748 s = statsd_parse_skip_spaces(s);
749 }
750 continue;
751 }
752
753 if(likely(*s == ':' || *s == '='))
754 s = tagvalue_end = (char *) statsd_parse_skip_up_to(tagvalue = ++s, ',', '\0', '\0');
755
756 if(*s == ',') s++;
757
758 statsd_parse_field_trim(tagkey, tagkey_end);
759 statsd_parse_field_trim(tagvalue, tagvalue_end);
760
761 if(tagkey && *tagkey && tagvalue && *tagvalue) {
762 if (strcmp(tagkey, "units") == 0 && (!m->units || strcmp(m->units, tagvalue) != 0)) {
763 m->units = strdupz(tagvalue);
764 m->options |= STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
765 }
766
767 if (strcmp(tagkey, "name") == 0 && (!m->dimname || strcmp(m->dimname, tagvalue) != 0)) {
768 m->dimname = strdupz(tagvalue);
769 m->options |= STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
770 }
771
772 if (strcmp(tagkey, "family") == 0 && (!m->family || strcmp(m->family, tagvalue) != 0)) {
773 m->family = strdupz(tagvalue);
774 m->options |= STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
775 }
776 }
777 }
778 }
779 }
780
781 static inline size_t statsd_process(char *buffer, size_t size, int require_newlines) {
782 buffer[size] = '\0';
783 netdata_log_debug(D_STATSD, "RECEIVED: %zu bytes: '%s'", size, buffer);
784
785 const char *s = buffer;
786 while(*s) {
787 const char *name = NULL, *value = NULL, *type = NULL, *sampling = NULL, *tags = NULL;
788 char *name_end = NULL, *value_end = NULL, *type_end = NULL, *sampling_end = NULL, *tags_end = NULL;
789
790 s = name_end = (char *)statsd_parse_skip_up_to(name = s, ':', '=', '|');
791 if(name == name_end) {
792 if (*s) {
793 s++;
794 s = statsd_parse_skip_spaces(s);
795 }
796 continue;
797 }
798
799 if(likely(*s == ':' || *s == '='))
800 s = value_end = (char *) statsd_parse_skip_up_to(value = ++s, '|', '@', '#');
801
802 if(likely(*s == '|'))
803 s = type_end = (char *) statsd_parse_skip_up_to(type = ++s, '|', '@', '#');
804
805 while(*s == '|' || *s == '@' || *s == '#') {
806 // parse all the fields that may be appended
807
808 if ((*s == '|' && s[1] == '@') || *s == '@') {
809 s = sampling_end = (char *)statsd_parse_skip_up_to(sampling = ++s, '|', '@', '#');
810 if (*sampling == '@') sampling++;
811 }
812 else if ((*s == '|' && s[1] == '#') || *s == '#') {
813 s = tags_end = (char *)statsd_parse_skip_up_to(tags = ++s, '|', '@', '#');
814 if (*tags == '#') tags++;
815 }
816 else {
817 // unknown field, skip it
818 s = (char *)statsd_parse_skip_up_to(++s, '|', '@', '#');
819 }
820 }
821
822 // skip everything until the end of the line
823 while(*s && *s != '\n') s++;
824
825 if(unlikely(require_newlines && *s != '\n' && s > buffer)) {
826 // move the remaining data to the beginning
827 size -= (name - buffer);
828 memmove(buffer, name, size);
829 return size;
830 }
831 else
832 s = statsd_parse_skip_spaces(s);
833
834 statsd_process_metric(
835 statsd_parse_field_trim(name, name_end)
836 , statsd_parse_field_trim(value, value_end)
837 , statsd_parse_field_trim(type, type_end)
838 , statsd_parse_field_trim(sampling, sampling_end)
839 , statsd_parse_field_trim(tags, tags_end)
840 );
841 }
842
843 return 0;
844 }
845
846
847 // --------------------------------------------------------------------------------------------------------------------
848 // statsd pollfd interface
849
850 #define STATSD_TCP_BUFFER_SIZE 65536 // minimize tcp reads
851 #define STATSD_UDP_BUFFER_SIZE 65536 // 65535 max IPv4 packet + 1 for null terminator
852
853 typedef enum {
854 STATSD_SOCKET_DATA_TYPE_TCP,
855 STATSD_SOCKET_DATA_TYPE_UDP
856 } STATSD_SOCKET_DATA_TYPE;
857
858 struct statsd_tcp {
859 STATSD_SOCKET_DATA_TYPE type;
860 size_t size;
861 size_t len;
862 char buffer[];
863 };
864
865 struct statsd_udp {
866 struct collection_thread_status *status;
867 STATSD_SOCKET_DATA_TYPE type;
868
869 #ifdef HAVE_RECVMMSG
870 size_t size;
871 struct iovec *iovecs;
872 struct mmsghdr *msgs;
873 #else
874 int *running;
875 char buffer[STATSD_UDP_BUFFER_SIZE];
876 #endif
877 };
878
879 // new TCP client connected
880 static void *statsd_add_callback(POLLINFO *pi, nd_poll_event_t *events, void *data) {
881 (void)pi;
882 (void)data;
883
884 worker_is_busy(WORKER_JOB_TYPE_TCP_CONNECTED);
885 *events = ND_POLL_READ;
886
887 struct statsd_tcp *t = (struct statsd_tcp *)callocz(sizeof(struct statsd_tcp) + STATSD_TCP_BUFFER_SIZE, 1);
888 t->type = STATSD_SOCKET_DATA_TYPE_TCP;
889 t->size = STATSD_TCP_BUFFER_SIZE - 1;
890 statsd.tcp_socket_connects++;
891 statsd.tcp_socket_connected++;
892
893 worker_is_idle();
894 return t;
895 }
896
897 // TCP client disconnected
898 static void statsd_del_callback(POLLINFO *pi) {
899 worker_is_busy(WORKER_JOB_TYPE_TCP_DISCONNECTED);
900
901 struct statsd_tcp *t = pi->data;
902
903 if(likely(t)) {
904 if(t->type == STATSD_SOCKET_DATA_TYPE_TCP) {
905 if(t->len != 0) {
906 statsd.socket_errors++;
907 netdata_log_error("STATSD: client is probably sending unterminated metrics. Closed socket left with '%s'. Trying to process it.", t->buffer);
908 statsd_process(t->buffer, t->len, 0);
909 }
910 statsd.tcp_socket_disconnects++;
911 statsd.tcp_socket_connected--;
912 }
913 else
914 netdata_log_error("STATSD: internal error: received socket data type is %d, but expected %d", (int)t->type, (int)STATSD_SOCKET_DATA_TYPE_TCP);
915
916 freez(t);
917 }
918
919 worker_is_idle();
920 }
921
922 // Receive data
923 static int statsd_rcv_callback(POLLINFO *pi, nd_poll_event_t *events) {
924 int retval = -1;
925 worker_is_busy(WORKER_JOB_TYPE_RCV_DATA);
926
927 *events = ND_POLL_READ;
928
929 int fd = pi->fd;
930
931 switch(pi->socktype) {
932 case SOCK_STREAM: {
933 struct statsd_tcp *d = (struct statsd_tcp *)pi->data;
934 if(unlikely(!d)) {
935 netdata_log_error("STATSD: internal error: expected TCP data pointer is NULL");
936 statsd.socket_errors++;
937 retval = -1;
938 goto cleanup;
939 }
940
941 #ifdef NETDATA_INTERNAL_CHECKS
942 if(unlikely(d->type != STATSD_SOCKET_DATA_TYPE_TCP)) {
943 netdata_log_error("STATSD: internal error: socket data type should be %d, but it is %d", (int)STATSD_SOCKET_DATA_TYPE_TCP, (int)d->type);
944 statsd.socket_errors++;
945 retval = -1;
946 goto cleanup;
947 }
948 #endif
949
950 int ret = 0;
951 ssize_t rc;
952 do {
953 rc = recv(fd, &d->buffer[d->len], d->size - d->len, MSG_DONTWAIT);
954 if (rc < 0) {
955 // read failed
956 if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) {
957 netdata_log_error("STATSD: recv() on TCP socket %d failed.", fd);
958 statsd.socket_errors++;
959 ret = -1;
960 }
961 }
962 else if (!rc) {
963 // connection closed
964 netdata_log_debug(D_STATSD, "STATSD: client disconnected.");
965 ret = -1;
966 }
967 else {
968 // data received
969 d->len += rc;
970 statsd.tcp_socket_reads++;
971 statsd.tcp_bytes_read += rc;
972
973 pulse_statsd_received_bytes(rc);
974 }
975
976 if(likely(d->len > 0)) {
977 statsd.tcp_packets_received++;
978 d->len = statsd_process(d->buffer, d->len, 1);
979 }
980
981 if(unlikely(ret == -1)) {
982 retval = -1;
983 goto cleanup;
984 }
985
986 } while (rc != -1);
987 break;
988 }
989
990 case SOCK_DGRAM: {
991 struct statsd_udp *d = (struct statsd_udp *)pi->data;
992 if(unlikely(!d)) {
993 netdata_log_error("STATSD: internal error: expected UDP data pointer is NULL");
994 statsd.socket_errors++;
995 retval = -1;
996 goto cleanup;
997 }
998
999 #ifdef NETDATA_INTERNAL_CHECKS
1000 if(unlikely(d->type != STATSD_SOCKET_DATA_TYPE_UDP)) {
1001 netdata_log_error("STATSD: internal error: socket data should be %d, but it is %d", (int)d->type, (int)STATSD_SOCKET_DATA_TYPE_UDP);
1002 statsd.socket_errors++;
1003 retval = -1;
1004 goto cleanup;
1005 }
1006 #endif
1007
1008 #ifdef HAVE_RECVMMSG
1009 ssize_t rc;
1010 do {
1011 rc = recvmmsg(fd, d->msgs, (unsigned int)d->size, MSG_DONTWAIT, NULL);
1012 if (rc < 0) {
1013 // read failed
1014 if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) {
1015 netdata_log_error("STATSD: recvmmsg() on UDP socket %d failed.", fd);
1016 statsd.socket_errors++;
1017 retval = -1;
1018 goto cleanup;
1019 }
1020 } else if (rc) {
1021 // data received
1022 statsd.udp_socket_reads++;
1023 statsd.udp_packets_received += rc;
1024
1025 size_t i, total_size = 0;
1026 for (i = 0; i < (size_t)rc; ++i) {
1027 size_t len = (size_t)d->msgs[i].msg_len;
1028 statsd.udp_bytes_read += len;
1029 total_size += len;
1030 statsd_process(d->msgs[i].msg_hdr.msg_iov->iov_base, len, 0);
1031 }
1032
1033 pulse_statsd_received_bytes(total_size);
1034 }
1035 } while (rc != -1);
1036
1037 #else // !HAVE_RECVMMSG
1038 ssize_t rc;
1039 do {
1040 rc = recv(fd, d->buffer, STATSD_UDP_BUFFER_SIZE - 1, MSG_DONTWAIT);
1041 if (rc < 0) {
1042 // read failed
1043 if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) {
1044 netdata_log_error("STATSD: recv() on UDP socket %d failed.", fd);
1045 statsd.socket_errors++;
1046 retval = -1;
1047 goto cleanup;
1048 }
1049 } else if (rc) {
1050 // data received
1051 statsd.udp_socket_reads++;
1052 statsd.udp_packets_received++;
1053 statsd.udp_bytes_read += rc;
1054 statsd_process(d->buffer, (size_t) rc, 0);
1055
1056 pulse_statsd_received_bytes(rc);
1057 }
1058 } while (rc != -1);
1059 #endif
1060
1061 break;
1062 }
1063
1064 default: {
1065 netdata_log_error("STATSD: internal error: unknown socktype %d on socket %d", pi->socktype, fd);
1066 statsd.socket_errors++;
1067 retval = -1;
1068 goto cleanup;
1069 }
1070 }
1071
1072 retval = 0;
1073 cleanup:
1074 worker_is_idle();
1075 return retval;
1076 }
1077
1078 static int statsd_snd_callback(POLLINFO *pi __maybe_unused, nd_poll_event_t *events __maybe_unused) {
1079 worker_is_busy(WORKER_JOB_TYPE_SND_DATA);
1080 netdata_log_error("STATSD: snd_callback() called, but we never requested to send data to statsd clients.");
1081 worker_is_idle();
1082
1083 return -1;
1084 }
1085
1086 // --------------------------------------------------------------------------------------------------------------------
1087 // statsd child thread to collect metrics from network
1088
1089 void statsd_collector_thread_cleanup(void *pptr) {
1090 struct statsd_udp *d = CLEANUP_FUNCTION_GET_PTR(pptr);
1091 if(!d) return;
1092
1093 #ifdef HAVE_RECVMMSG
1094 size_t i;
1095 for (i = 0; i < d->size; i++)
1096 freez(d->iovecs[i].iov_base);
1097
1098 freez(d->iovecs);
1099 freez(d->msgs);
1100 #endif
1101
1102 freez(d);
1103 worker_unregister();
1104 }
1105
1106 static bool statsd_should_stop(void) {
1107 return !service_running(SERVICE_COLLECTORS);
1108 }
1109
1110 void statsd_collector_thread(void *ptr) {
1111 struct collection_thread_status *status = ptr;
1112 spinlock_lock(&status->spinlock);
1113 status->initializing = false;
1114 spinlock_unlock(&status->spinlock);
1115
1116 worker_register("STATSD");
1117 worker_register_job_name(WORKER_JOB_TYPE_TCP_CONNECTED, "tcp connect");
1118 worker_register_job_name(WORKER_JOB_TYPE_TCP_DISCONNECTED, "tcp disconnect");
1119 worker_register_job_name(WORKER_JOB_TYPE_RCV_DATA, "receive");
1120 worker_register_job_name(WORKER_JOB_TYPE_SND_DATA, "send");
1121
1122 collector_info("STATSD collector thread started with taskid %d", gettid_cached());
1123
1124 struct statsd_udp *d = callocz(sizeof(struct statsd_udp), 1);
1125 d->status = status;
1126
1127 CLEANUP_FUNCTION_REGISTER(statsd_collector_thread_cleanup) cleanup_ptr = d;
1128
1129 #ifdef HAVE_RECVMMSG
1130 d->type = STATSD_SOCKET_DATA_TYPE_UDP;
1131 d->size = statsd.recvmmsg_size;
1132 d->iovecs = callocz(sizeof(struct iovec), d->size);
1133 d->msgs = callocz(sizeof(struct mmsghdr), d->size);
1134
1135 size_t i;
1136 for (i = 0; i < d->size; i++) {
1137 d->iovecs[i].iov_base = mallocz(STATSD_UDP_BUFFER_SIZE);
1138 d->iovecs[i].iov_len = STATSD_UDP_BUFFER_SIZE - 1;
1139 d->msgs[i].msg_hdr.msg_iov = &d->iovecs[i];
1140 d->msgs[i].msg_hdr.msg_iovlen = 1;
1141 }
1142 #endif
1143
1144 poll_events(&statsd.sockets
1145 , statsd_add_callback
1146 , statsd_del_callback
1147 , statsd_rcv_callback
1148 , statsd_snd_callback
1149 , NULL
1150 , statsd_should_stop
1151 , NULL // No access control pattern
1152 , 0 // No dns lookups for access control pattern
1153 , (void *)d
1154 , 0 // tcp request timeout, 0 = disabled
1155 , statsd.tcp_idle_timeout // tcp idle timeout, 0 = disabled
1156 , statsd.update_every * 1000
1157 , ptr // timer_data
1158 , status->max_sockets
1159 );
1160 }
1161
1162
1163 // --------------------------------------------------------------------------------------------------------------------
1164 // statsd applications configuration files parsing
1165
1166 #define STATSD_CONF_LINE_MAX 8192
1167
1168 static STATSD_APP_CHART_DIM_VALUE_TYPE string2valuetype(const char *type, size_t line, const char *filename) {
1169 if(!type || !*type) type = "last";
1170
1171 if(!strcmp(type, "events")) return STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS;
1172 else if(!strcmp(type, "last")) return STATSD_APP_CHART_DIM_VALUE_TYPE_LAST;
1173 else if(!strcmp(type, "min")) return STATSD_APP_CHART_DIM_VALUE_TYPE_MIN;
1174 else if(!strcmp(type, "max")) return STATSD_APP_CHART_DIM_VALUE_TYPE_MAX;
1175 else if(!strcmp(type, "sum")) return STATSD_APP_CHART_DIM_VALUE_TYPE_SUM;
1176 else if(!strcmp(type, "average")) return STATSD_APP_CHART_DIM_VALUE_TYPE_AVERAGE;
1177 else if(!strcmp(type, "median")) return STATSD_APP_CHART_DIM_VALUE_TYPE_MEDIAN;
1178 else if(!strcmp(type, "stddev")) return STATSD_APP_CHART_DIM_VALUE_TYPE_STDDEV;
1179 else if(!strcmp(type, "percentile")) return STATSD_APP_CHART_DIM_VALUE_TYPE_PERCENTILE;
1180
1181 netdata_log_error("STATSD: invalid type '%s' at line %zu of file '%s'. Using 'last'.", type, line, filename);
1182 return STATSD_APP_CHART_DIM_VALUE_TYPE_LAST;
1183 }
1184
1185 static const char *valuetype2string(STATSD_APP_CHART_DIM_VALUE_TYPE type) {
1186 switch(type) {
1187 case STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS: return "events";
1188 case STATSD_APP_CHART_DIM_VALUE_TYPE_LAST: return "last";
1189 case STATSD_APP_CHART_DIM_VALUE_TYPE_MIN: return "min";
1190 case STATSD_APP_CHART_DIM_VALUE_TYPE_MAX: return "max";
1191 case STATSD_APP_CHART_DIM_VALUE_TYPE_SUM: return "sum";
1192 case STATSD_APP_CHART_DIM_VALUE_TYPE_AVERAGE: return "average";
1193 case STATSD_APP_CHART_DIM_VALUE_TYPE_MEDIAN: return "median";
1194 case STATSD_APP_CHART_DIM_VALUE_TYPE_STDDEV: return "stddev";
1195 case STATSD_APP_CHART_DIM_VALUE_TYPE_PERCENTILE: return "percentile";
1196 }
1197
1198 return "unknown";
1199 }
1200
1201 static STATSD_APP_CHART_DIM *add_dimension_to_app_chart(
1202 STATSD_APP *app __maybe_unused
1203 , STATSD_APP_CHART *chart
1204 , const char *metric_name
1205 , const char *dim_name
1206 , collected_number multiplier
1207 , collected_number divisor
1208 , RRDDIM_FLAGS flags
1209 , RRDDIM_OPTIONS options
1210 , STATSD_APP_CHART_DIM_VALUE_TYPE value_type
1211 ) {
1212 STATSD_APP_CHART_DIM *dim = callocz(sizeof(STATSD_APP_CHART_DIM), 1);
1213
1214 dim->metric = strdupz(metric_name);
1215 dim->metric_hash = simple_hash(dim->metric);
1216
1217 dim->name = strdupz((dim_name)?dim_name:"");
1218 dim->multiplier = multiplier;
1219 dim->divisor = divisor;
1220 dim->value_type = value_type;
1221 dim->flags = flags;
1222 dim->options = options;
1223
1224 if(!dim->multiplier)
1225 dim->multiplier = 1;
1226
1227 if(!dim->divisor)
1228 dim->divisor = 1;
1229
1230 // append it to the list of dimension
1231 STATSD_APP_CHART_DIM *tdim;
1232 for(tdim = chart->dimensions; tdim && tdim->next ; tdim = tdim->next) ;
1233 if(!tdim) {
1234 dim->next = chart->dimensions;
1235 chart->dimensions = dim;
1236 }
1237 else {
1238 dim->next = tdim->next;
1239 tdim->next = dim;
1240 }
1241 chart->dimensions_count++;
1242
1243 netdata_log_debug(D_STATSD, "Added dimension '%s' to chart '%s' of app '%s', for metric '%s', with type %u, multiplier %d, divisor %d",
1244 dim->name, chart->id, app->name, dim->metric, dim->value_type, dim->multiplier, dim->divisor);
1245
1246 return dim;
1247 }
1248
1249 static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHART *chart, DICTIONARY *dict) {
1250 netdata_log_debug(D_STATSD, "STATSD configuration reading file '%s'", filename);
1251
1252 char *buffer = mallocz(STATSD_CONF_LINE_MAX + 1);
1253
1254 FILE *fp = fopen(filename, "r");
1255 if(!fp) {
1256 netdata_log_error("STATSD: cannot open file '%s'.", filename);
1257 freez(buffer);
1258 return -1;
1259 }
1260
1261 size_t line = 0;
1262 char *s;
1263 while(fgets(buffer, STATSD_CONF_LINE_MAX, fp) != NULL) {
1264 buffer[STATSD_CONF_LINE_MAX] = '\0';
1265 line++;
1266
1267 s = trim(buffer);
1268 if (!s || *s == '#') {
1269 netdata_log_debug(D_STATSD, "STATSD: ignoring line %zu of file '%s', it is empty.", line, filename);
1270 continue;
1271 }
1272
1273 netdata_log_debug(D_STATSD, "STATSD: processing line %zu of file '%s': %s", line, filename, buffer);
1274
1275 if(*s == 'i' && strncmp(s, "include", 7) == 0) {
1276 s = trim(&s[7]);
1277 if(s && *s) {
1278 char *tmp;
1279 if(*s == '/')
1280 tmp = strdupz(s);
1281 else {
1282 // the file to be included is relative to current file
1283 // find the directory name from the file we already read
1284 char *filename2 = strdupz(filename); // copy filename, since dirname() will change it
1285 char *dir = dirname(filename2); // find the directory part of the filename
1286 tmp = filename_from_path_entry_strdupz(dir, s); // compose the new filename to read;
1287 freez(filename2); // free the filename we copied
1288 }
1289 statsd_readfile(tmp, app, chart, dict);
1290 freez(tmp);
1291 }
1292 else
1293 netdata_log_error("STATSD: ignoring line %zu of file '%s', include filename is empty", line, filename);
1294
1295 continue;
1296 }
1297
1298 int len = (int) strlen(s);
1299 if (*s == '[' && s[len - 1] == ']') {
1300 // new section
1301 s[len - 1] = '\0';
1302 s++;
1303
1304 if (!strcmp(s, "app")) {
1305 // a new app
1306 app = callocz(sizeof(STATSD_APP), 1);
1307 app->name = strdupz("unnamed");
1308 app->rrd_memory_mode = localhost->rrd_memory_mode;
1309 app->rrd_history_entries = localhost->rrd_history_entries;
1310
1311 app->next = statsd.apps;
1312 statsd.apps = app;
1313 chart = NULL;
1314 dict = NULL;
1315
1316 {
1317 char lineandfile[FILENAME_MAX + 1];
1318 snprintfz(lineandfile, FILENAME_MAX, "%zu@%s", line, filename);
1319 app->source = strdupz(lineandfile);
1320 }
1321 }
1322 else if(app) {
1323 if(!strcmp(s, "dictionary")) {
1324 if(!app->dict)
1325 app->dict = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED, &dictionary_stats_category_collectors, 0);
1326
1327 dict = app->dict;
1328 }
1329 else {
1330 dict = NULL;
1331
1332 // a new chart
1333 chart = callocz(sizeof(STATSD_APP_CHART), 1);
1334 netdata_fix_chart_id(s);
1335 chart->id = strdupz(s);
1336 chart->name = strdupz(s);
1337 chart->title = strdupz("Statsd chart");
1338 chart->context = strdupz(s);
1339 chart->family = strdupz("overview");
1340 chart->units = strdupz("value");
1341 chart->priority = NETDATA_CHART_PRIO_STATSD_PRIVATE;
1342 chart->chart_type = RRDSET_TYPE_LINE;
1343
1344 chart->next = app->charts;
1345 app->charts = chart;
1346
1347 if (!strncmp(
1348 filename,
1349 netdata_configured_stock_config_dir,
1350 strlen(netdata_configured_stock_config_dir))) {
1351 char tmpfilename[FILENAME_MAX + 1];
1352 strncpyz(tmpfilename, filename, FILENAME_MAX);
1353 chart->module = strdupz(basename(tmpfilename));
1354 } else {
1355 chart->module = strdupz("synthetic_chart");
1356 }
1357 }
1358 }
1359 else
1360 netdata_log_error("STATSD: ignoring line %zu ('%s') of file '%s', [app] is not defined.", line, s, filename);
1361
1362 continue;
1363 }
1364
1365 if(!app) {
1366 netdata_log_error("STATSD: ignoring line %zu ('%s') of file '%s', it is outside all sections.", line, s, filename);
1367 continue;
1368 }
1369
1370 char *name = s;
1371 char *value = strchr(s, '=');
1372 if(!value) {
1373 netdata_log_error("STATSD: ignoring line %zu ('%s') of file '%s', there is no = in it.", line, s, filename);
1374 continue;
1375 }
1376 *value = '\0';
1377 value++;
1378
1379 name = trim(name);
1380 value = trim(value);
1381
1382 if(!name || *name == '#') {
1383 netdata_log_error("STATSD: ignoring line %zu of file '%s', name is empty.", line, filename);
1384 continue;
1385 }
1386 if(!value) {
1387 netdata_log_debug(D_CONFIG, "STATSD: ignoring line %zu of file '%s', value is empty.", line, filename);
1388 continue;
1389 }
1390
1391 if(unlikely(dict)) {
1392 // parse [dictionary] members
1393
1394 dictionary_set(dict, name, value, strlen(value) + 1);
1395 }
1396 else if(!chart) {
1397 // parse [app] members
1398
1399 if(!strcmp(name, "name")) {
1400 freez((void *)app->name);
1401 netdata_fix_chart_name(value);
1402 app->name = strdupz(value);
1403 }
1404 else if (!strcmp(name, "metrics")) {
1405 simple_pattern_free(app->metrics);
1406 app->metrics = simple_pattern_create(value, NULL, SIMPLE_PATTERN_EXACT, true);
1407 }
1408 else if (!strcmp(name, "private charts")) {
1409 if (!strcmp(value, "yes") || !strcmp(value, "on"))
1410 app->default_options |= STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
1411 else
1412 app->default_options &= ~STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
1413 }
1414 else if (!strcmp(name, "gaps when not collected")) {
1415 if (!strcmp(value, "yes") || !strcmp(value, "on"))
1416 app->default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
1417 }
1418 else if (!strcmp(name, "memory mode")) {
1419 // this is not supported anymore
1420 // with the implementation of storage engines, all charts have the same storage engine always
1421 // app->rrd_memory_mode = rrd_memory_mode_id(value);
1422 ;
1423 }
1424 else if (!strcmp(name, "history")) {
1425 app->rrd_history_entries = atol(value);
1426 if (app->rrd_history_entries < 5)
1427 app->rrd_history_entries = 5;
1428 }
1429 else {
1430 netdata_log_error("STATSD: ignoring line %zu ('%s') of file '%s'. Unknown keyword for the [app] section.", line, name, filename);
1431 continue;
1432 }
1433 }
1434 else {
1435 // parse [chart] members
1436
1437 if(!strcmp(name, "name")) {
1438 freez((void *)chart->name);
1439 netdata_fix_chart_id(value);
1440 chart->name = strdupz(value);
1441 }
1442 else if(!strcmp(name, "title")) {
1443 freez((void *)chart->title);
1444 chart->title = strdupz(value);
1445 }
1446 else if (!strcmp(name, "family")) {
1447 freez((void *)chart->family);
1448 chart->family = strdupz(value);
1449 }
1450 else if (!strcmp(name, "context")) {
1451 freez((void *)chart->context);
1452 netdata_fix_chart_id(value);
1453 chart->context = strdupz(value);
1454 }
1455 else if (!strcmp(name, "units")) {
1456 freez((void *)chart->units);
1457 chart->units = strdupz(value);
1458 }
1459 else if (!strcmp(name, "priority")) {
1460 chart->priority = atol(value);
1461 }
1462 else if (!strcmp(name, "type")) {
1463 chart->chart_type = rrdset_type_id(value);
1464 }
1465 else if (!strcmp(name, "dimension")) {
1466 // metric [name [type [multiplier [divisor]]]]
1467 char *words[10] = { NULL };
1468 size_t num_words = quoted_strings_splitter_pluginsd(value, words, 10);
1469
1470 int pattern = 0;
1471 size_t i = 0;
1472 char *metric_name = get_word(words, num_words, i++);
1473
1474 if(strcmp(metric_name, "pattern") == 0) {
1475 metric_name = get_word(words, num_words, i++);
1476 pattern = 1;
1477 }
1478
1479 char *dim_name = get_word(words, num_words, i++);
1480 char *type = get_word(words, num_words, i++);
1481 char *multiplier = get_word(words, num_words, i++);
1482 char *divisor = get_word(words, num_words, i++);
1483 char *opts = get_word(words, num_words, i++);
1484
1485 RRDDIM_FLAGS flags = RRDDIM_FLAG_NONE;
1486 RRDDIM_OPTIONS options = RRDDIM_OPTION_NONE;
1487 if(opts && *opts) {
1488 if(strstr(opts, "hidden") != NULL) options |= RRDDIM_OPTION_HIDDEN;
1489 if(strstr(opts, "noreset") != NULL) options |= RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS;
1490 if(strstr(opts, "nooverflow") != NULL) options |= RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS;
1491 }
1492
1493 if(!pattern) {
1494 if(app->dict) {
1495 if(dim_name && *dim_name) {
1496 char *n = dictionary_get(app->dict, dim_name);
1497 if(n) dim_name = n;
1498 }
1499 else {
1500 dim_name = dictionary_get(app->dict, metric_name);
1501 }
1502 }
1503
1504 if(!dim_name || !*dim_name)
1505 dim_name = metric_name;
1506 }
1507
1508 STATSD_APP_CHART_DIM *dim = add_dimension_to_app_chart(
1509 app
1510 , chart
1511 , metric_name
1512 , dim_name
1513 , (multiplier && *multiplier)?str2l(multiplier):1
1514 , (divisor && *divisor)?str2l(divisor):1
1515 , flags
1516 ,
1517 options, string2valuetype(type, line, filename)
1518 );
1519
1520 if(pattern)
1521 dim->metric_pattern = simple_pattern_create(dim->metric, NULL, SIMPLE_PATTERN_EXACT, true);
1522 }
1523 else {
1524 netdata_log_error("STATSD: ignoring line %zu ('%s') of file '%s'. Unknown keyword for the [%s] section.", line, name, filename, chart->id);
1525 continue;
1526 }
1527 }
1528 }
1529
1530 freez(buffer);
1531 fclose(fp);
1532 return 0;
1533 }
1534
1535 static int statsd_file_callback(const char *filename, void *data __maybe_unused, bool stock_config __maybe_unused) {
1536 return statsd_readfile(filename, NULL, NULL, NULL);
1537 }
1538
1539 static inline void statsd_readdir(const char *user_path, const char *stock_path, const char *subpath) {
1540 recursive_config_double_dir_load(user_path, stock_path, subpath, statsd_file_callback, NULL, 0);
1541 }
1542
1543 // --------------------------------------------------------------------------------------------------------------------
1544 // send metrics to netdata - in private charts - called from the main thread
1545
1546 // extract chart type and chart id from metric name
1547 static inline void statsd_get_metric_type_and_id(STATSD_METRIC *m, char *type, char *id, char *context, const char *metrictype, size_t len) {
1548
1549 // The full chart type.id looks like this:
1550 // ${STATSD_CHART_PREFIX} + "_" + ${METRIC_NAME} + "_" + ${METRIC_TYPE}
1551 //
1552 // where:
1553 // STATSD_CHART_PREFIX = "statsd" as defined above
1554 // METRIC_NAME = whatever the user gave to statsd
1555 // METRIC_TYPE = "gauge", "counter", "meter", "timer", "histogram", "set", "dictionary"
1556
1557 // for chart type, we want:
1558 // ${STATSD_CHART_PREFIX} + "_" + the first word of ${METRIC_NAME}
1559
1560 // find the first word of ${METRIC_NAME}
1561 char *firstword = mallocz(len + 1);
1562 char *s = "";
1563 strncpyz(firstword, m->name, len);
1564 for (s = firstword; *s ; s++) {
1565 if (unlikely(*s == '.' || *s == '_')) {
1566 *s = '\0';
1567 s++;
1568 break;
1569 }
1570 }
1571 // firstword has the first word of ${METRIC_NAME}
1572 // s has the remaining, if any
1573
1574 // create the chart type:
1575 snprintfz(type, len, STATSD_CHART_PREFIX "_%s", firstword);
1576
1577 // for chart id, we want:
1578 // the remaining of the words of ${METRIC_NAME} + "_" + ${METRIC_TYPE}
1579 // or the ${METRIC_NAME} has no remaining words, the ${METRIC_TYPE} alone
1580 if(*s)
1581 snprintfz(id, len, "%s_%s", s, metrictype);
1582 else
1583 snprintfz(id, len, "%s", metrictype);
1584
1585 freez(firstword);
1586
1587 // for the context, we want the full of both the above, separated with a dot (type.id):
1588 snprintfz(context, RRD_ID_LENGTH_MAX, "%s.%s", type, id);
1589
1590 // make sure they don't have illegal characters
1591 netdata_fix_chart_id(type);
1592 netdata_fix_chart_id(id);
1593 netdata_fix_chart_id(context);
1594 }
1595
1596 static inline RRDSET *statsd_private_rrdset_create(
1597 STATSD_METRIC *m __maybe_unused
1598 , const char *type
1599 , const char *id
1600 , const char *name
1601 , const char *family
1602 , const char *context
1603 , const char *title
1604 , const char *units
1605 , long priority
1606 , int update_every
1607 , RRDSET_TYPE chart_type
1608 ) {
1609 if(!m->st)
1610 statsd.private_charts++;
1611
1612 RRDSET *st = rrdset_create_custom(
1613 localhost // host
1614 , type // type
1615 , id // id
1616 , name // name
1617 , family // family
1618 , context // context
1619 , title // title
1620 , units // units
1621 , PLUGIN_STATSD_NAME // plugin
1622 , "private_chart" // module
1623 , priority // priority
1624 , update_every // update every
1625 , chart_type // chart type
1626 , default_rrd_memory_mode // memory mode
1627 , default_rrd_history_entries // history
1628 );
1629 rrdset_flag_set(st, RRDSET_FLAG_STORE_FIRST);
1630
1631 if(statsd.private_charts_hidden)
1632 rrdset_flag_set(st, RRDSET_FLAG_HIDDEN);
1633
1634 // rrdset_flag_set(st, RRDSET_FLAG_DEBUG);
1635 return st;
1636 }
1637
1638 static inline void statsd_private_chart_gauge(STATSD_METRIC *m) {
1639 netdata_log_debug(D_STATSD, "updating private chart for gauge metric '%s'", m->name);
1640
1641 if(m->options & STATSD_METRIC_OPTION_OBSOLETE)
1642 return;
1643
1644 if(unlikely(!m->st || m->options & STATSD_METRIC_OPTION_UPDATED_CHART_METADATA)) {
1645 m->options &= ~STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
1646
1647 char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1648 statsd_get_metric_type_and_id(m, type, id, context, "gauge", RRD_ID_LENGTH_MAX);
1649
1650 char title[RRD_ID_LENGTH_MAX + 1];
1651 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for gauge %s", m->name);
1652
1653 m->st = statsd_private_rrdset_create(
1654 m
1655 , type
1656 , id
1657 , NULL // name
1658 , m->family?m->family:"gauges" // family (submenu)
1659 , context // context
1660 , title // title
1661 , m->units?m->units:"value" // units
1662 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1663 , statsd.update_every
1664 , RRDSET_TYPE_LINE
1665 );
1666
1667 m->rd_value = rrddim_add(m->st, "gauge", m->dimname?m->dimname:NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1668
1669 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1670 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1671 }
1672
1673 rrddim_set_by_pointer(m->st, m->rd_value, m->last);
1674
1675 if(m->rd_count)
1676 rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1677
1678 rrdset_done(m->st);
1679 }
1680
1681 static inline void statsd_private_chart_counter_or_meter(STATSD_METRIC *m, const char *dim, const char *family) {
1682 netdata_log_debug(D_STATSD, "updating private chart for %s metric '%s'", dim, m->name);
1683
1684 if(m->options & STATSD_METRIC_OPTION_OBSOLETE)
1685 return;
1686
1687 if(unlikely(!m->st || m->options & STATSD_METRIC_OPTION_UPDATED_CHART_METADATA)) {
1688 m->options &= ~STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
1689
1690 char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1691 statsd_get_metric_type_and_id(m, type, id, context, dim, RRD_ID_LENGTH_MAX);
1692
1693 char title[RRD_ID_LENGTH_MAX + 1];
1694 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for %s %s", dim, m->name);
1695
1696 m->st = statsd_private_rrdset_create(
1697 m
1698 , type
1699 , id
1700 , NULL // name
1701 , m->family?m->family:family // family (submenu)
1702 , context // context
1703 , title // title
1704 , m->units?m->units:"events/s" // units
1705 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1706 , statsd.update_every
1707 , RRDSET_TYPE_AREA
1708 );
1709
1710 m->rd_value = rrddim_add(m->st, dim, m->dimname?m->dimname:NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1711
1712 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1713 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1714 }
1715
1716 rrddim_set_by_pointer(m->st, m->rd_value, m->last);
1717
1718 if(m->rd_count)
1719 rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1720
1721 rrdset_done(m->st);
1722 }
1723
1724 static inline void statsd_private_chart_set(STATSD_METRIC *m) {
1725 netdata_log_debug(D_STATSD, "updating private chart for set metric '%s'", m->name);
1726
1727 if(m->options & STATSD_METRIC_OPTION_OBSOLETE)
1728 return;
1729
1730 if(unlikely(!m->st || m->options & STATSD_METRIC_OPTION_UPDATED_CHART_METADATA)) {
1731 m->options &= ~STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
1732
1733 char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1734 statsd_get_metric_type_and_id(m, type, id, context, "set", RRD_ID_LENGTH_MAX);
1735
1736 char title[RRD_ID_LENGTH_MAX + 1];
1737 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for set %s", m->name);
1738
1739 m->st = statsd_private_rrdset_create(
1740 m
1741 , type
1742 , id
1743 , NULL // name
1744 , m->family?m->family:"sets" // family (submenu)
1745 , context // context
1746 , title // title
1747 , m->units?m->units:"entries" // units
1748 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1749 , statsd.update_every
1750 , RRDSET_TYPE_LINE
1751 );
1752
1753 m->rd_value = rrddim_add(m->st, "set", m->dimname?m->dimname:"unique", 1, 1, RRD_ALGORITHM_ABSOLUTE);
1754
1755 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1756 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1757 }
1758
1759 rrddim_set_by_pointer(m->st, m->rd_value, m->last);
1760
1761 if(m->rd_count)
1762 rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1763
1764 rrdset_done(m->st);
1765 }
1766
1767 static inline void statsd_private_chart_dictionary(STATSD_METRIC *m) {
1768 netdata_log_debug(D_STATSD, "updating private chart for dictionary metric '%s'", m->name);
1769
1770 if(m->options & STATSD_METRIC_OPTION_OBSOLETE)
1771 return;
1772
1773 if(unlikely(!m->st || m->options & STATSD_METRIC_OPTION_UPDATED_CHART_METADATA)) {
1774 m->options &= ~STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
1775
1776 char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1777 statsd_get_metric_type_and_id(m, type, id, context, "dictionary", RRD_ID_LENGTH_MAX);
1778
1779 char title[RRD_ID_LENGTH_MAX + 1];
1780 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for dictionary %s", m->name);
1781
1782 m->st = statsd_private_rrdset_create(
1783 m
1784 , type
1785 , id
1786 , NULL // name
1787 , m->family?m->family:"dictionaries" // family (submenu)
1788 , context // context
1789 , title // title
1790 , m->units?m->units:"events/s" // units
1791 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1792 , statsd.update_every
1793 , RRDSET_TYPE_STACKED
1794 );
1795
1796 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1797 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1798 }
1799
1800 STATSD_METRIC_DICTIONARY_ITEM *t;
1801 dfe_start_read(m->dictionary.dict, t) {
1802 if (!t->rd) t->rd = rrddim_add(m->st, t_dfe.name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1803 rrddim_set_by_pointer(m->st, t->rd, (collected_number)t->count);
1804 }
1805 dfe_done(t);
1806
1807 if(m->rd_count)
1808 rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1809
1810 rrdset_done(m->st);
1811 }
1812
1813 static inline void statsd_private_chart_timer_or_histogram(STATSD_METRIC *m, const char *dim, const char *family, const char *units) {
1814 netdata_log_debug(D_STATSD, "updating private chart for %s metric '%s'", dim, m->name);
1815
1816 if(m->options & STATSD_METRIC_OPTION_OBSOLETE)
1817 return;
1818
1819 if(unlikely(!m->st || m->options & STATSD_METRIC_OPTION_UPDATED_CHART_METADATA)) {
1820 m->options &= ~STATSD_METRIC_OPTION_UPDATED_CHART_METADATA;
1821
1822 char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1823 statsd_get_metric_type_and_id(m, type, id, context, dim, RRD_ID_LENGTH_MAX);
1824
1825 char title[RRD_ID_LENGTH_MAX + 1];
1826 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for %s %s", dim, m->name);
1827
1828 m->st = statsd_private_rrdset_create(
1829 m
1830 , type
1831 , id
1832 , NULL // name
1833 , m->family?m->family:family // family (submenu)
1834 , context // context
1835 , title // title
1836 , m->units?m->units:units // units
1837 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1838 , statsd.update_every
1839 , RRDSET_TYPE_AREA
1840 );
1841
1842 m->histogram.ext->rd_min = rrddim_add(m->st, "min", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1843 m->histogram.ext->rd_max = rrddim_add(m->st, "max", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1844 m->rd_value = rrddim_add(m->st, "average", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1845 m->histogram.ext->rd_percentile = rrddim_add(m->st, statsd.histogram_percentile_str, NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1846 m->histogram.ext->rd_median = rrddim_add(m->st, "median", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1847 m->histogram.ext->rd_stddev = rrddim_add(m->st, "stddev", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1848 //m->histogram.ext->rd_sum = rrddim_add(m->st, "sum", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1849
1850 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1851 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1852 }
1853
1854 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_min, m->histogram.ext->last_min);
1855 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_max, m->histogram.ext->last_max);
1856 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_percentile, m->histogram.ext->last_percentile);
1857 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_median, m->histogram.ext->last_median);
1858 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_stddev, m->histogram.ext->last_stddev);
1859 //rrddim_set_by_pointer(m->st, m->histogram.ext->rd_sum, m->histogram.ext->last_sum);
1860 rrddim_set_by_pointer(m->st, m->rd_value, m->last);
1861
1862 if(m->rd_count)
1863 rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1864
1865 rrdset_done(m->st);
1866 }
1867
1868 // --------------------------------------------------------------------------------------------------------------------
1869 // statsd flush metrics
1870
1871 static inline void metric_check_obsoletion(STATSD_METRIC *m) {
1872 if(!(m->options & STATSD_METRIC_OPTION_OBSOLETE) &&
1873 statsd.set_obsolete_after &&
1874 (m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED) &&
1875 m->last_collected + (time_t)statsd.set_obsolete_after < now_realtime_sec()) {
1876
1877 if(m->st) {
1878 rrdset_is_obsolete___safe_from_collector_thread(m->st);
1879 m->st = NULL;
1880 }
1881
1882 m->options |= STATSD_METRIC_OPTION_OBSOLETE;
1883 }
1884 }
1885
1886 static inline void statsd_flush_gauge(STATSD_METRIC *m) {
1887 netdata_log_debug(D_STATSD, "flushing gauge metric '%s'", m->name);
1888
1889 int updated = 0;
1890 if(unlikely(!m->reset && m->count)) {
1891 m->last = (collected_number) (m->gauge.value * statsd.decimal_detail);
1892
1893 m->reset = 1;
1894 updated = 1;
1895 }
1896
1897 if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
1898 statsd_private_chart_gauge(m);
1899
1900 metric_check_obsoletion(m);
1901 }
1902
1903 static inline void statsd_flush_counter_or_meter(STATSD_METRIC *m, const char *dim, const char *family) {
1904 netdata_log_debug(D_STATSD, "flushing %s metric '%s'", dim, m->name);
1905
1906 int updated = 0;
1907 if(unlikely(!m->reset && m->count)) {
1908 m->last = m->counter.value;
1909
1910 m->reset = 1;
1911 updated = 1;
1912 }
1913
1914 if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
1915 statsd_private_chart_counter_or_meter(m, dim, family);
1916
1917 metric_check_obsoletion(m);
1918 }
1919
1920 static inline void statsd_flush_counter(STATSD_METRIC *m) {
1921 statsd_flush_counter_or_meter(m, "counter", "counters");
1922 }
1923
1924 static inline void statsd_flush_meter(STATSD_METRIC *m) {
1925 statsd_flush_counter_or_meter(m, "meter", "meters");
1926 }
1927
1928 static inline void statsd_flush_set(STATSD_METRIC *m) {
1929 netdata_log_debug(D_STATSD, "flushing set metric '%s'", m->name);
1930
1931 int updated = 0;
1932 if(unlikely(!m->reset && m->count)) {
1933 m->last = (collected_number)dictionary_entries(m->set.dict);
1934
1935 m->reset = 1;
1936 updated = 1;
1937 }
1938 else {
1939 m->last = 0;
1940 }
1941
1942 if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
1943 statsd_private_chart_set(m);
1944
1945 metric_check_obsoletion(m);
1946 }
1947
1948 static inline void statsd_flush_dictionary(STATSD_METRIC *m) {
1949 netdata_log_debug(D_STATSD, "flushing dictionary metric '%s'", m->name);
1950
1951 int updated = 0;
1952 if(unlikely(!m->reset && m->count)) {
1953 m->last = (collected_number)dictionary_entries(m->dictionary.dict);
1954
1955 m->reset = 1;
1956 updated = 1;
1957 }
1958 else {
1959 m->last = 0;
1960 }
1961
1962 if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
1963 statsd_private_chart_dictionary(m);
1964
1965 if(dictionary_entries(m->dictionary.dict) >= statsd.dictionary_max_unique) {
1966 if(!(m->options & STATSD_METRIC_OPTION_COLLECTION_FULL_LOGGED)) {
1967 m->options |= STATSD_METRIC_OPTION_COLLECTION_FULL_LOGGED;
1968 collector_info(
1969 "STATSD dictionary '%s' reach max of %zu items - try increasing 'dictionaries max unique dimensions' in netdata.conf",
1970 m->name,
1971 dictionary_entries(m->dictionary.dict));
1972 }
1973 }
1974
1975 metric_check_obsoletion(m);
1976 }
1977
1978 static inline void statsd_flush_timer_or_histogram(STATSD_METRIC *m, const char *dim, const char *family, const char *units) {
1979 netdata_log_debug(D_STATSD, "flushing %s metric '%s'", dim, m->name);
1980
1981 int updated = 0;
1982 if(unlikely(!m->reset && m->count)) {
1983 netdata_mutex_lock(&m->histogram.ext->mutex);
1984
1985 if(likely(m->histogram.ext->used > 0)) {
1986 size_t len = m->histogram.ext->used;
1987 NETDATA_DOUBLE *series = m->histogram.ext->values;
1988 sort_series(series, len);
1989
1990 m->histogram.ext->last_min = (collected_number)roundndd(series[0] * statsd.decimal_detail);
1991 m->histogram.ext->last_max = (collected_number)roundndd(series[len - 1] * statsd.decimal_detail);
1992 m->last = (collected_number)roundndd(average(series, len) * statsd.decimal_detail);
1993 m->histogram.ext->last_stddev = (collected_number)roundndd(standard_deviation(series, len) * statsd.decimal_detail);
1994 m->histogram.ext->last_sum = (collected_number)roundndd(sum(series, len) * statsd.decimal_detail);
1995 m->histogram.ext->last_median = (collected_number)roundndd(median_on_sorted_series(series, len) * statsd.decimal_detail);
1996 m->histogram.ext->last_percentile = (collected_number)roundndd(percentile_on_sorted_series(series, len, statsd.histogram_percentile / 100) * statsd.decimal_detail);
1997
1998 m->histogram.ext->zeroed = 0;
1999 m->reset = 1;
2000 updated = 1;
2001 }
2002
2003 netdata_mutex_unlock(&m->histogram.ext->mutex);
2004
2005 if(updated)
2006 netdata_log_debug(D_STATSD, "STATSD %s metric %s: min " COLLECTED_NUMBER_FORMAT ", max " COLLECTED_NUMBER_FORMAT ", last " COLLECTED_NUMBER_FORMAT ", pcent " COLLECTED_NUMBER_FORMAT ", median " COLLECTED_NUMBER_FORMAT ", stddev " COLLECTED_NUMBER_FORMAT ", sum " COLLECTED_NUMBER_FORMAT,
2007 dim, m->name, m->histogram.ext->last_min, m->histogram.ext->last_max, m->last, m->histogram.ext->last_percentile, m->histogram.ext->last_median, m->histogram.ext->last_stddev, m->histogram.ext->last_sum);
2008 }
2009 else if(unlikely(!m->histogram.ext->zeroed)) {
2010 // reset the metrics
2011 // if we collected anything, they will be updated below
2012 // this ensures that we report zeros if nothing is collected
2013
2014 m->histogram.ext->last_min = 0;
2015 m->histogram.ext->last_max = 0;
2016 m->last = 0;
2017 m->histogram.ext->last_median = 0;
2018 m->histogram.ext->last_stddev = 0;
2019 m->histogram.ext->last_sum = 0;
2020 m->histogram.ext->last_percentile = 0;
2021
2022 m->histogram.ext->zeroed = 1;
2023 }
2024
2025 if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
2026 statsd_private_chart_timer_or_histogram(m, dim, family, units);
2027
2028 metric_check_obsoletion(m);
2029 }
2030
2031 static inline void statsd_flush_timer(STATSD_METRIC *m) {
2032 statsd_flush_timer_or_histogram(m, "timer", "timers", "milliseconds");
2033 }
2034
2035 static inline void statsd_flush_histogram(STATSD_METRIC *m) {
2036 statsd_flush_timer_or_histogram(m, "histogram", "histograms", "value");
2037 }
2038
2039 static inline RRD_ALGORITHM statsd_algorithm_for_metric(STATSD_METRIC *m) {
2040 switch(m->type) {
2041 default:
2042 case STATSD_METRIC_TYPE_GAUGE:
2043 case STATSD_METRIC_TYPE_SET:
2044 case STATSD_METRIC_TYPE_TIMER:
2045 case STATSD_METRIC_TYPE_HISTOGRAM:
2046 return RRD_ALGORITHM_ABSOLUTE;
2047
2048 case STATSD_METRIC_TYPE_METER:
2049 case STATSD_METRIC_TYPE_COUNTER:
2050 case STATSD_METRIC_TYPE_DICTIONARY:
2051 return RRD_ALGORITHM_INCREMENTAL;
2052 }
2053 }
2054
2055 static inline void link_metric_to_app_dimension(STATSD_APP *app, STATSD_METRIC *m, STATSD_APP_CHART *chart, STATSD_APP_CHART_DIM *dim) {
2056 if(dim->value_type == STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS) {
2057 dim->value_ptr = &m->events;
2058 dim->algorithm = RRD_ALGORITHM_INCREMENTAL;
2059 }
2060 else if(m->type == STATSD_METRIC_TYPE_HISTOGRAM || m->type == STATSD_METRIC_TYPE_TIMER) {
2061 dim->algorithm = RRD_ALGORITHM_ABSOLUTE;
2062 dim->divisor *= statsd.decimal_detail;
2063
2064 switch(dim->value_type) {
2065 case STATSD_APP_CHART_DIM_VALUE_TYPE_EVENTS:
2066 // will never match - added to avoid warning
2067 break;
2068
2069 case STATSD_APP_CHART_DIM_VALUE_TYPE_LAST:
2070 case STATSD_APP_CHART_DIM_VALUE_TYPE_AVERAGE:
2071 dim->value_ptr = &m->last;
2072 break;
2073
2074 case STATSD_APP_CHART_DIM_VALUE_TYPE_SUM:
2075 dim->value_ptr = &m->histogram.ext->last_sum;
2076 break;
2077
2078 case STATSD_APP_CHART_DIM_VALUE_TYPE_MIN:
2079 dim->value_ptr = &m->histogram.ext->last_min;
2080 break;
2081
2082 case STATSD_APP_CHART_DIM_VALUE_TYPE_MAX:
2083 dim->value_ptr = &m->histogram.ext->last_max;
2084 break;
2085
2086 case STATSD_APP_CHART_DIM_VALUE_TYPE_MEDIAN:
2087 dim->value_ptr = &m->histogram.ext->last_median;
2088 break;
2089
2090 case STATSD_APP_CHART_DIM_VALUE_TYPE_PERCENTILE:
2091 dim->value_ptr = &m->histogram.ext->last_percentile;
2092 break;
2093
2094 case STATSD_APP_CHART_DIM_VALUE_TYPE_STDDEV:
2095 dim->value_ptr = &m->histogram.ext->last_stddev;
2096 break;
2097 }
2098 }
2099 else {
2100 if (dim->value_type != STATSD_APP_CHART_DIM_VALUE_TYPE_LAST)
2101 netdata_log_error("STATSD: unsupported value type for dimension '%s' of chart '%s' of app '%s' on metric '%s'", dim->name, chart->id, app->name, m->name);
2102
2103 dim->value_ptr = &m->last;
2104 dim->algorithm = statsd_algorithm_for_metric(m);
2105
2106 if(m->type == STATSD_METRIC_TYPE_GAUGE)
2107 dim->divisor *= statsd.decimal_detail;
2108 }
2109
2110 if(unlikely(chart->st && dim->rd)) {
2111 rrddim_set_algorithm(chart->st, dim->rd, dim->algorithm);
2112 rrddim_set_multiplier(chart->st, dim->rd, dim->multiplier);
2113 rrddim_set_divisor(chart->st, dim->rd, dim->divisor);
2114 }
2115
2116 chart->dimensions_linked_count++;
2117 m->options |= STATSD_METRIC_OPTION_USED_IN_APPS;
2118 netdata_log_debug(D_STATSD, "metric '%s' of type %u linked with app '%s', chart '%s', dimension '%s', algorithm '%s'", m->name, m->type, app->name, chart->id, dim->name, rrd_algorithm_name(dim->algorithm));
2119 }
2120
2121 static inline void check_if_metric_is_for_app(STATSD_INDEX *index, STATSD_METRIC *m) {
2122 (void)index;
2123
2124 STATSD_APP *app;
2125 for(app = statsd.apps; app ;app = app->next) {
2126 if(unlikely(simple_pattern_matches(app->metrics, m->name))) {
2127 netdata_log_debug(D_STATSD, "metric '%s' matches app '%s'", m->name, app->name);
2128
2129 // the metric should get the options from the app
2130
2131 if(app->default_options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED)
2132 m->options |= STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
2133 else
2134 m->options &= ~STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
2135
2136 if(app->default_options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED)
2137 m->options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2138 else
2139 m->options &= ~STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2140
2141 m->options |= STATSD_METRIC_OPTION_PRIVATE_CHART_CHECKED;
2142
2143 // check if there is a chart in this app, willing to get this metric
2144 STATSD_APP_CHART *chart;
2145 for(chart = app->charts; chart; chart = chart->next) {
2146
2147 STATSD_APP_CHART_DIM *dim;
2148 for(dim = chart->dimensions; dim ; dim = dim->next) {
2149 if(unlikely(dim->metric_pattern)) {
2150 size_t dim_name_len = strlen(dim->name);
2151 size_t wildcarded_len = dim_name_len + strlen(m->name) + 1;
2152 char *wildcarded = mallocz(wildcarded_len);
2153
2154 strcpy(wildcarded, dim->name);
2155 char *ws = &wildcarded[dim_name_len];
2156
2157 if(simple_pattern_matches_extract(dim->metric_pattern, m->name, ws, wildcarded_len - dim_name_len) == SP_MATCHED_POSITIVE) {
2158
2159 char *final_name = NULL;
2160
2161 if(app->dict) {
2162 if(likely(*wildcarded)) {
2163 // use the name of the wildcarded string
2164 final_name = dictionary_get(app->dict, wildcarded);
2165 }
2166
2167 if(unlikely(!final_name)) {
2168 // use the name of the metric
2169 final_name = dictionary_get(app->dict, m->name);
2170 }
2171 }
2172
2173 if(unlikely(!final_name))
2174 final_name = wildcarded;
2175
2176 add_dimension_to_app_chart(
2177 app
2178 , chart
2179 , m->name
2180 , final_name
2181 , dim->multiplier
2182 , dim->divisor
2183 , dim->flags
2184 , dim->options
2185 , dim->value_type
2186 );
2187
2188 // the new dimension is appended to the list
2189 // so, it will be matched and linked later too
2190 }
2191
2192 freez(wildcarded);
2193 }
2194 else if(!dim->value_ptr && dim->metric_hash == m->hash && !strcmp(dim->metric, m->name)) {
2195 // we have a match - this metric should be linked to this dimension
2196 link_metric_to_app_dimension(app, m, chart, dim);
2197 }
2198 }
2199
2200 }
2201 }
2202 }
2203 }
2204
2205 static inline RRDDIM *statsd_add_dim_to_app_chart(STATSD_APP *app, STATSD_APP_CHART *chart, STATSD_APP_CHART_DIM *dim) {
2206 (void)app;
2207
2208 // allow the same statsd metric to be added multiple times to the same chart
2209
2210 STATSD_APP_CHART_DIM *tdim;
2211 size_t count_same_metric = 0, count_same_metric_value_type = 0;
2212 size_t pos_same_metric_value_type = 0;
2213
2214 for (tdim = chart->dimensions; tdim && tdim->next; tdim = tdim->next) {
2215 if (dim->metric_hash == tdim->metric_hash && !strcmp(dim->metric, tdim->metric)) {
2216 count_same_metric++;
2217
2218 if(dim->value_type == tdim->value_type) {
2219 count_same_metric_value_type++;
2220 if (tdim == dim)
2221 pos_same_metric_value_type = count_same_metric_value_type;
2222 }
2223 }
2224 }
2225
2226 if(count_same_metric > 1) {
2227 // the same metric is found multiple times
2228
2229 size_t len = strlen(dim->metric) + 100;
2230 char *metric = mallocz(len + 1);
2231
2232 if(count_same_metric_value_type > 1) {
2233 // the same metric, with the same value type, is added multiple times
2234 snprintfz(metric, len, "%s_%s%zu", dim->metric, valuetype2string(dim->value_type), pos_same_metric_value_type);
2235 }
2236 else {
2237 // the same metric, with different value type is added
2238 snprintfz(metric, len, "%s_%s", dim->metric, valuetype2string(dim->value_type));
2239 }
2240
2241 dim->rd = rrddim_add(chart->st, metric, dim->name, dim->multiplier, dim->divisor, dim->algorithm);
2242 freez(metric);
2243 if(dim->flags != RRDDIM_FLAG_NONE) dim->rd->flags |= dim->flags;
2244 if(dim->options != RRDDIM_OPTION_NONE) dim->rd->collector.options |= dim->options;
2245 return dim->rd;
2246 }
2247
2248 dim->rd = rrddim_add(chart->st, dim->metric, dim->name, dim->multiplier, dim->divisor, dim->algorithm);
2249 if(dim->flags != RRDDIM_FLAG_NONE) dim->rd->flags |= dim->flags;
2250 if(dim->options != RRDDIM_OPTION_NONE) dim->rd->collector.options |= dim->options;
2251 return dim->rd;
2252 }
2253
2254 static inline void statsd_update_app_chart(STATSD_APP *app, STATSD_APP_CHART *chart) {
2255 netdata_log_debug(D_STATSD, "updating chart '%s' for app '%s'", chart->id, app->name);
2256
2257 if(!chart->st) {
2258 chart->st = rrdset_create_custom(
2259 localhost // host
2260 , app->name // type
2261 , chart->id // id
2262 , chart->name // name
2263 , chart->family // family
2264 , chart->context // context
2265 , chart->title // title
2266 , chart->units // units
2267 , PLUGIN_STATSD_NAME // plugin
2268 , chart->module // module
2269 , chart->priority // priority
2270 , statsd.update_every // update every
2271 , chart->chart_type // chart type
2272 , app->rrd_memory_mode // memory mode
2273 , app->rrd_history_entries // history
2274 );
2275
2276 rrdset_flag_set(chart->st, RRDSET_FLAG_STORE_FIRST);
2277 // rrdset_flag_set(chart->st, RRDSET_FLAG_DEBUG);
2278 }
2279
2280 STATSD_APP_CHART_DIM *dim;
2281 for(dim = chart->dimensions; dim ;dim = dim->next) {
2282 if(likely(!dim->metric_pattern)) {
2283 if (unlikely(!dim->rd))
2284 statsd_add_dim_to_app_chart(app, chart, dim);
2285
2286 if (unlikely(dim->value_ptr)) {
2287 netdata_log_debug(D_STATSD, "updating dimension '%s' (%s) of chart '%s' (%s) for app '%s' with value " COLLECTED_NUMBER_FORMAT, dim->name, rrddim_id(dim->rd), chart->id, rrdset_id(chart->st), app->name, *dim->value_ptr);
2288 rrddim_set_by_pointer(chart->st, dim->rd, *dim->value_ptr);
2289 }
2290 }
2291 }
2292
2293 rrdset_done(chart->st);
2294 netdata_log_debug(D_STATSD, "completed update of chart '%s' for app '%s'", chart->id, app->name);
2295 }
2296
2297 static inline void statsd_update_all_app_charts(void) {
2298 // netdata_log_debug(D_STATSD, "updating app charts");
2299
2300 STATSD_APP *app;
2301 for(app = statsd.apps; app ;app = app->next) {
2302 // netdata_log_debug(D_STATSD, "updating charts for app '%s'", app->name);
2303
2304 STATSD_APP_CHART *chart;
2305 for(chart = app->charts; chart ;chart = chart->next) {
2306 if(unlikely(chart->dimensions_linked_count)) {
2307 statsd_update_app_chart(app, chart);
2308 }
2309 }
2310 }
2311
2312 // netdata_log_debug(D_STATSD, "completed update of app charts");
2313 }
2314
2315 const char *statsd_metric_type_string(STATSD_METRIC_TYPE type) {
2316 switch(type) {
2317 case STATSD_METRIC_TYPE_COUNTER: return "counter";
2318 case STATSD_METRIC_TYPE_GAUGE: return "gauge";
2319 case STATSD_METRIC_TYPE_HISTOGRAM: return "histogram";
2320 case STATSD_METRIC_TYPE_METER: return "meter";
2321 case STATSD_METRIC_TYPE_SET: return "set";
2322 case STATSD_METRIC_TYPE_DICTIONARY: return "dictionary";
2323 case STATSD_METRIC_TYPE_TIMER: return "timer";
2324 default: return "unknown";
2325 }
2326 }
2327
2328 static inline void statsd_flush_index_metrics(STATSD_INDEX *index, void (*flush_metric)(STATSD_METRIC *)) {
2329 STATSD_METRIC *m;
2330
2331 // find the useful metrics (incremental = each time we are called, we check the new metrics only)
2332 dfe_start_read(index->dict, m) {
2333 // since we add new metrics at the beginning
2334 // check for useful charts, until the point we last checked
2335 if(unlikely(is_metric_checked(m))) break;
2336
2337 if(unlikely(!(m->options & STATSD_METRIC_OPTION_CHECKED_IN_APPS))) {
2338 nd_log(NDLS_ACCESS, NDLP_DEBUG, "NEW STATSD METRIC '%s': '%s'", statsd_metric_type_string(m->type), m->name);
2339 check_if_metric_is_for_app(index, m);
2340 m->options |= STATSD_METRIC_OPTION_CHECKED_IN_APPS;
2341 }
2342
2343 if(unlikely(!(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_CHECKED))) {
2344 if(unlikely(statsd.private_charts >= statsd.max_private_charts_hard)) {
2345 netdata_log_debug(D_STATSD, "STATSD: metric '%s' will not be charted, because the hard limit of the maximum number "
2346 "of charts has been reached.", m->name);
2347
2348 collector_info("STATSD: metric '%s' will not be charted, because the hard limit of the maximum number "
2349 "of charts (%u) has been reached. Increase the number of charts by editing netdata.conf, "
2350 "[statsd] section.", m->name, statsd.max_private_charts_hard);
2351
2352 m->options &= ~STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
2353 }
2354 else {
2355 if (simple_pattern_matches(statsd.charts_for, m->name)) {
2356 netdata_log_debug(D_STATSD, "STATSD: metric '%s' will be charted.", m->name);
2357 m->options |= STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
2358 } else {
2359 netdata_log_debug(D_STATSD, "STATSD: metric '%s' will not be charted.", m->name);
2360 m->options &= ~STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED;
2361 }
2362 }
2363
2364 m->options |= STATSD_METRIC_OPTION_PRIVATE_CHART_CHECKED;
2365 }
2366
2367 // mark it as checked
2368 m->options |= STATSD_METRIC_OPTION_CHECKED;
2369
2370 // check if it is used in charts
2371 if((m->options & (STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED|STATSD_METRIC_OPTION_USED_IN_APPS)) && !(m->options & STATSD_METRIC_OPTION_USEFUL)) {
2372 m->options |= STATSD_METRIC_OPTION_USEFUL;
2373 index->useful++;
2374 m->next_useful = index->first_useful;
2375 index->first_useful = m;
2376 }
2377 }
2378 dfe_done(m);
2379
2380 // flush all the unuseful metrics
2381 STATSD_METRIC *m_prev;
2382 for(m_prev = m = index->first_useful; m ; m = m->next_useful) {
2383 flush_metric(m);
2384 if (m->options & STATSD_METRIC_OPTION_OBSOLETE) {
2385 if (m == index->first_useful)
2386 index->first_useful = m->next_useful;
2387 else
2388 m_prev->next_useful = m->next_useful;
2389 dictionary_del(index->dict, m->name);
2390 index->useful--;
2391 index->metrics--;
2392 statsd.private_charts--;
2393 } else
2394 m_prev = m;
2395 }
2396 }
2397
2398
2399 // --------------------------------------------------------------------------------------
2400 // statsd main thread
2401
2402 static int statsd_listen_sockets_setup(void) {
2403 return listen_sockets_setup(&statsd.sockets);
2404 }
2405
2406 static void statsd_main_cleanup(void *pptr) {
2407 struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr);
2408 if(!static_thread) return;
2409
2410 static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
2411
2412 if (statsd.collection_threads_status) {
2413 int i;
2414 for (i = 0; i < statsd.threads; i++) {
2415 bool initializing;
2416 do {
2417 spinlock_lock(&statsd.collection_threads_status[i].spinlock);
2418 initializing = statsd.collection_threads_status[i].initializing;
2419 spinlock_unlock(&statsd.collection_threads_status[i].spinlock);
2420 if (unlikely(initializing))
2421 sleep_usec(1000);
2422 } while(initializing);
2423
2424 (void) nd_thread_join(statsd.collection_threads_status[i].thread);
2425 }
2426 freez(statsd.collection_threads_status);
2427 }
2428
2429 collector_info("STATSD: closing sockets...");
2430 listen_sockets_close(&statsd.sockets);
2431
2432 // destroy the dictionaries
2433 dictionary_destroy(statsd.gauges.dict);
2434 dictionary_destroy(statsd.meters.dict);
2435 dictionary_destroy(statsd.counters.dict);
2436 dictionary_destroy(statsd.histograms.dict);
2437 dictionary_destroy(statsd.dictionaries.dict);
2438 dictionary_destroy(statsd.sets.dict);
2439 dictionary_destroy(statsd.timers.dict);
2440
2441 // Clean up app dictionaries
2442 STATSD_APP *app = statsd.apps;
2443 while(app) {
2444 if(app->dict) {
2445 dictionary_destroy(app->dict);
2446 app->dict = NULL;
2447 }
2448 app = app->next;
2449 }
2450
2451 collector_info("STATSD: cleanup completed.");
2452 static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
2453
2454 worker_unregister();
2455 }
2456
2457 #define WORKER_STATSD_FLUSH_GAUGES 0
2458 #define WORKER_STATSD_FLUSH_COUNTERS 1
2459 #define WORKER_STATSD_FLUSH_METERS 2
2460 #define WORKER_STATSD_FLUSH_TIMERS 3
2461 #define WORKER_STATSD_FLUSH_HISTOGRAMS 4
2462 #define WORKER_STATSD_FLUSH_SETS 5
2463 #define WORKER_STATSD_FLUSH_DICTIONARIES 6
2464 #define WORKER_STATSD_FLUSH_STATS 7
2465
2466 #if WORKER_UTILIZATION_MAX_JOB_TYPES < 8
2467 #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 8
2468 #endif
2469
2470 void *statsd_main(void *ptr) {
2471 CLEANUP_FUNCTION_REGISTER(statsd_main_cleanup) cleanup_ptr = ptr;
2472
2473 worker_register("STATSDFLUSH");
2474 worker_register_job_name(WORKER_STATSD_FLUSH_GAUGES, "gauges");
2475 worker_register_job_name(WORKER_STATSD_FLUSH_COUNTERS, "counters");
2476 worker_register_job_name(WORKER_STATSD_FLUSH_METERS, "meters");
2477 worker_register_job_name(WORKER_STATSD_FLUSH_TIMERS, "timers");
2478 worker_register_job_name(WORKER_STATSD_FLUSH_HISTOGRAMS, "histograms");
2479 worker_register_job_name(WORKER_STATSD_FLUSH_SETS, "sets");
2480 worker_register_job_name(WORKER_STATSD_FLUSH_DICTIONARIES, "dictionaries");
2481 worker_register_job_name(WORKER_STATSD_FLUSH_STATS, "statistics");
2482
2483 statsd.gauges.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2484 statsd.meters.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2485 statsd.counters.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2486 statsd.histograms.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2487 statsd.dictionaries.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2488 statsd.sets.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2489 statsd.timers.dict = dictionary_create_advanced(STATSD_DICTIONARY_OPTIONS | DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(STATSD_METRIC));
2490
2491 dictionary_register_insert_callback(statsd.gauges.dict, dictionary_metric_insert_callback, &statsd.gauges);
2492 dictionary_register_insert_callback(statsd.meters.dict, dictionary_metric_insert_callback, &statsd.meters);
2493 dictionary_register_insert_callback(statsd.counters.dict, dictionary_metric_insert_callback, &statsd.counters);
2494 dictionary_register_insert_callback(statsd.histograms.dict, dictionary_metric_insert_callback, &statsd.histograms);
2495 dictionary_register_insert_callback(statsd.dictionaries.dict, dictionary_metric_insert_callback, &statsd.dictionaries);
2496 dictionary_register_insert_callback(statsd.sets.dict, dictionary_metric_insert_callback, &statsd.sets);
2497 dictionary_register_insert_callback(statsd.timers.dict, dictionary_metric_insert_callback, &statsd.timers);
2498
2499 dictionary_register_delete_callback(statsd.gauges.dict, dictionary_metric_delete_callback, &statsd.gauges);
2500 dictionary_register_delete_callback(statsd.meters.dict, dictionary_metric_delete_callback, &statsd.meters);
2501 dictionary_register_delete_callback(statsd.counters.dict, dictionary_metric_delete_callback, &statsd.counters);
2502 dictionary_register_delete_callback(statsd.histograms.dict, dictionary_metric_delete_callback, &statsd.histograms);
2503 dictionary_register_delete_callback(statsd.dictionaries.dict, dictionary_metric_delete_callback, &statsd.dictionaries);
2504 dictionary_register_delete_callback(statsd.sets.dict, dictionary_metric_delete_callback, &statsd.sets);
2505 dictionary_register_delete_callback(statsd.timers.dict, dictionary_metric_delete_callback, &statsd.timers);
2506
2507 // ----------------------------------------------------------------------------------------------------------------
2508 // statsd configuration
2509
2510 statsd.enabled = inicfg_get_boolean(&netdata_config, CONFIG_SECTION_PLUGINS, "statsd", statsd.enabled);
2511
2512 statsd.update_every = nd_profile.update_every;
2513 statsd.update_every = (int)inicfg_get_duration_seconds(&netdata_config, CONFIG_SECTION_STATSD, "update every (flushInterval)", statsd.update_every);
2514 if(statsd.update_every < nd_profile.update_every) {
2515 collector_error("STATSD: minimum flush interval %d given, but the minimum is the update every of netdata. Using %d",
2516 (int)statsd.update_every, (int)nd_profile.update_every);
2517 statsd.update_every = nd_profile.update_every;
2518 inicfg_set_duration_seconds(&netdata_config, CONFIG_SECTION_STATSD, "update every (flushInterval)", statsd.update_every);
2519 }
2520
2521 #ifdef HAVE_RECVMMSG
2522 statsd.recvmmsg_size = (size_t)inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "udp messages to process at once", (long long)statsd.recvmmsg_size);
2523 #endif
2524
2525 statsd.charts_for = simple_pattern_create(
2526 inicfg_get(&netdata_config, CONFIG_SECTION_STATSD, "create private charts for metrics matching", "*"), NULL,
2527 SIMPLE_PATTERN_EXACT, true);
2528
2529 statsd.max_private_charts_hard =
2530 (size_t)inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "max private charts hard limit", (long long)statsd.max_private_charts_hard);
2531
2532 statsd.set_obsolete_after =
2533 (size_t)inicfg_get_duration_seconds(&netdata_config, CONFIG_SECTION_STATSD, "set charts as obsolete after", (long long)statsd.set_obsolete_after);
2534
2535 statsd.decimal_detail =
2536 (collected_number)inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "decimal detail", (long long int)statsd.decimal_detail);
2537
2538 statsd.tcp_idle_timeout =
2539 (size_t) inicfg_get_duration_seconds(&netdata_config, CONFIG_SECTION_STATSD, "disconnect idle tcp clients after", (long long int)statsd.tcp_idle_timeout);
2540
2541 statsd.private_charts_hidden =
2542 (unsigned int)inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "private charts hidden", statsd.private_charts_hidden);
2543
2544 statsd.histogram_percentile =
2545 (double)inicfg_get_double(&netdata_config,
2546 CONFIG_SECTION_STATSD, "histograms and timers percentile (percentThreshold)", statsd.histogram_percentile);
2547
2548 if(isless(statsd.histogram_percentile, 0) || isgreater(statsd.histogram_percentile, 100)) {
2549 collector_error("STATSD: invalid histograms and timers percentile %0.5f given", statsd.histogram_percentile);
2550 statsd.histogram_percentile = 95.0;
2551 }
2552 {
2553 char buffer[314 + 1];
2554 snprintfz(buffer, sizeof(buffer) - 1, "%0.1f%%", statsd.histogram_percentile);
2555 statsd.histogram_percentile_str = strdupz(buffer);
2556 }
2557
2558 statsd.dictionary_max_unique =
2559 inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "dictionaries max unique dimensions", statsd.dictionary_max_unique);
2560
2561 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "add dimension for number of events received", 0)) {
2562 statsd.gauges.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2563 statsd.counters.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2564 statsd.meters.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2565 statsd.sets.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2566 statsd.histograms.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2567 statsd.timers.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2568 statsd.dictionaries.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2569 }
2570
2571 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on gauges (deleteGauges)", 0))
2572 statsd.gauges.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2573
2574 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on counters (deleteCounters)", 0))
2575 statsd.counters.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2576
2577 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on meters (deleteMeters)", 0))
2578 statsd.meters.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2579
2580 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on sets (deleteSets)", 0))
2581 statsd.sets.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2582
2583 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on histograms (deleteHistograms)", 0))
2584 statsd.histograms.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2585
2586 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on timers (deleteTimers)", 0))
2587 statsd.timers.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2588
2589 if(inicfg_get_boolean(&netdata_config, CONFIG_SECTION_STATSD, "gaps on dictionaries (deleteDictionaries)", 0))
2590 statsd.dictionaries.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2591
2592 size_t max_sockets = (size_t)inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "statsd server max TCP sockets", (long long int)(rlimit_nofile.rlim_cur / 4));
2593
2594 #ifdef STATSD_MULTITHREADED
2595 statsd.threads = (int)inicfg_get_number(&netdata_config, CONFIG_SECTION_STATSD, "threads", processors);
2596 if(statsd.threads < 1) {
2597 collector_error("STATSD: Invalid number of threads %d, using %d", statsd.threads, processors);
2598 statsd.threads = processors;
2599 inicfg_set_number(&netdata_config, CONFIG_SECTION_STATSD, "collector threads", statsd.threads);
2600 }
2601 #else
2602 statsd.threads = 1;
2603 #endif
2604
2605 // read custom application definitions
2606 statsd_readdir(netdata_configured_user_config_dir, netdata_configured_stock_config_dir, "statsd.d");
2607
2608 // ----------------------------------------------------------------------------------------------------------------
2609 // statsd setup
2610
2611 if(!statsd.enabled) goto cleanup;
2612
2613 statsd_listen_sockets_setup();
2614 if(!statsd.sockets.opened) {
2615 collector_error("STATSD: No statsd sockets to listen to. statsd will be disabled.");
2616 goto cleanup;
2617 }
2618
2619 statsd.collection_threads_status = callocz((size_t)statsd.threads, sizeof(struct collection_thread_status));
2620
2621 int i;
2622 for(i = 0; i < statsd.threads ;i++) {
2623 statsd.collection_threads_status[i].max_sockets = max_sockets / statsd.threads;
2624 char tag[NETDATA_THREAD_TAG_MAX + 1];
2625 snprintfz(tag, NETDATA_THREAD_TAG_MAX, "STATSD_IN[%d]", i + 1);
2626 spinlock_init(&statsd.collection_threads_status[i].spinlock);
2627 statsd.collection_threads_status[i].initializing = true;
2628 statsd.collection_threads_status[i].thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_DEFAULT,
2629 statsd_collector_thread, &statsd.collection_threads_status[i]);
2630 }
2631
2632 // ----------------------------------------------------------------------------------------------------------------
2633 // statsd monitoring charts
2634
2635 RRDSET *st_metrics = NULL;
2636 RRDDIM *rd_metrics_gauge = NULL;
2637 RRDDIM *rd_metrics_counter = NULL;
2638 RRDDIM *rd_metrics_timer = NULL;
2639 RRDDIM *rd_metrics_meter = NULL;
2640 RRDDIM *rd_metrics_histogram = NULL;
2641 RRDDIM *rd_metrics_set = NULL;
2642 RRDDIM *rd_metrics_dictionary = NULL;
2643 RRDSET *st_useful_metrics = NULL;
2644 RRDDIM *rd_useful_metrics_gauge = NULL;
2645 RRDDIM *rd_useful_metrics_counter = NULL;
2646 RRDDIM *rd_useful_metrics_timer = NULL;
2647 RRDDIM *rd_useful_metrics_meter = NULL;
2648 RRDDIM *rd_useful_metrics_histogram = NULL;
2649 RRDDIM *rd_useful_metrics_set = NULL;
2650 RRDDIM *rd_useful_metrics_dictionary = NULL;
2651 RRDSET *st_events = NULL;
2652 RRDDIM *rd_events_gauge = NULL;
2653 RRDDIM *rd_events_counter = NULL;
2654 RRDDIM *rd_events_timer = NULL;
2655 RRDDIM *rd_events_meter = NULL;
2656 RRDDIM *rd_events_histogram = NULL;
2657 RRDDIM *rd_events_set = NULL;
2658 RRDDIM *rd_events_dictionary = NULL;
2659 RRDDIM *rd_events_unknown = NULL;
2660 RRDDIM *rd_events_errors = NULL;
2661 RRDSET *st_reads = NULL;
2662 RRDDIM *rd_reads_tcp = NULL;
2663 RRDDIM *rd_reads_udp = NULL;
2664 RRDSET *st_bytes = NULL;
2665 RRDDIM *rd_bytes_tcp = NULL;
2666 RRDDIM *rd_bytes_udp = NULL;
2667 RRDSET *st_packets = NULL;
2668 RRDDIM *rd_packets_tcp = NULL;
2669 RRDDIM *rd_packets_udp = NULL;
2670 RRDSET *st_tcp_connects = NULL;
2671 RRDDIM *rd_tcp_connects = NULL;
2672 RRDDIM *rd_tcp_disconnects = NULL;
2673 RRDSET *st_tcp_connected = NULL;
2674 RRDDIM *rd_tcp_connected = NULL;
2675 RRDSET *st_pcharts = NULL;
2676 RRDDIM *rd_pcharts = NULL;
2677
2678 if(pulse_enabled) {
2679 st_metrics = rrdset_create_localhost(
2680 "netdata",
2681 "statsd_metrics",
2682 NULL,
2683 "statsd",
2684 NULL,
2685 "Metrics in the netdata statsd database",
2686 "metrics",
2687 PLUGIN_STATSD_NAME,
2688 "stats",
2689 132010,
2690 statsd.update_every,
2691 RRDSET_TYPE_STACKED);
2692 rd_metrics_gauge = rrddim_add(st_metrics, "gauges", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2693 rd_metrics_counter = rrddim_add(st_metrics, "counters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2694 rd_metrics_timer = rrddim_add(st_metrics, "timers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2695 rd_metrics_meter = rrddim_add(st_metrics, "meters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2696 rd_metrics_histogram = rrddim_add(st_metrics, "histograms", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2697 rd_metrics_set = rrddim_add(st_metrics, "sets", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2698 rd_metrics_dictionary = rrddim_add(st_metrics, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2699
2700 st_useful_metrics = rrdset_create_localhost(
2701 "netdata",
2702 "statsd_useful_metrics",
2703 NULL,
2704 "statsd",
2705 NULL,
2706 "Useful metrics in the netdata statsd database",
2707 "metrics",
2708 PLUGIN_STATSD_NAME,
2709 "stats",
2710 132010,
2711 statsd.update_every,
2712 RRDSET_TYPE_STACKED);
2713 rd_useful_metrics_gauge = rrddim_add(st_useful_metrics, "gauges", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2714 rd_useful_metrics_counter = rrddim_add(st_useful_metrics, "counters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2715 rd_useful_metrics_timer = rrddim_add(st_useful_metrics, "timers", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2716 rd_useful_metrics_meter = rrddim_add(st_useful_metrics, "meters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2717 rd_useful_metrics_histogram = rrddim_add(st_useful_metrics, "histograms", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2718 rd_useful_metrics_set = rrddim_add(st_useful_metrics, "sets", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2719 rd_useful_metrics_dictionary = rrddim_add(st_useful_metrics, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2720
2721 st_events = rrdset_create_localhost(
2722 "netdata",
2723 "statsd_events",
2724 NULL,
2725 "statsd",
2726 NULL,
2727 "Events processed by the netdata statsd server",
2728 "events/s",
2729 PLUGIN_STATSD_NAME,
2730 "stats",
2731 132011,
2732 statsd.update_every,
2733 RRDSET_TYPE_STACKED);
2734 rd_events_gauge = rrddim_add(st_events, "gauges", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2735 rd_events_counter = rrddim_add(st_events, "counters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2736 rd_events_timer = rrddim_add(st_events, "timers", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2737 rd_events_meter = rrddim_add(st_events, "meters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2738 rd_events_histogram = rrddim_add(st_events, "histograms", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2739 rd_events_set = rrddim_add(st_events, "sets", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2740 rd_events_dictionary = rrddim_add(st_events, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2741 rd_events_unknown = rrddim_add(st_events, "unknown", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2742 rd_events_errors = rrddim_add(st_events, "errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2743
2744 st_reads = rrdset_create_localhost(
2745 "netdata",
2746 "statsd_reads",
2747 NULL,
2748 "statsd",
2749 NULL,
2750 "Read operations made by the netdata statsd server",
2751 "reads/s",
2752 PLUGIN_STATSD_NAME,
2753 "stats",
2754 132012,
2755 statsd.update_every,
2756 RRDSET_TYPE_STACKED);
2757 rd_reads_tcp = rrddim_add(st_reads, "tcp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2758 rd_reads_udp = rrddim_add(st_reads, "udp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2759
2760 st_bytes = rrdset_create_localhost(
2761 "netdata",
2762 "statsd_bytes",
2763 NULL,
2764 "statsd",
2765 NULL,
2766 "Bytes read by the netdata statsd server",
2767 "kilobits/s",
2768 PLUGIN_STATSD_NAME,
2769 "stats",
2770 132013,
2771 statsd.update_every,
2772 RRDSET_TYPE_STACKED);
2773 rd_bytes_tcp = rrddim_add(st_bytes, "tcp", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
2774 rd_bytes_udp = rrddim_add(st_bytes, "udp", NULL, 8, BITS_IN_A_KILOBIT, RRD_ALGORITHM_INCREMENTAL);
2775
2776 st_packets = rrdset_create_localhost(
2777 "netdata",
2778 "statsd_packets",
2779 NULL,
2780 "statsd",
2781 NULL,
2782 "Network packets processed by the netdata statsd server",
2783 "packets/s",
2784 PLUGIN_STATSD_NAME,
2785 "stats",
2786 132014,
2787 statsd.update_every,
2788 RRDSET_TYPE_STACKED);
2789 rd_packets_tcp = rrddim_add(st_packets, "tcp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2790 rd_packets_udp = rrddim_add(st_packets, "udp", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2791
2792 st_tcp_connects = rrdset_create_localhost(
2793 "netdata",
2794 "tcp_connects",
2795 NULL,
2796 "statsd",
2797 "netdata.statsd_tcp_connects",
2798 "statsd server TCP connects and disconnects",
2799 "events",
2800 PLUGIN_STATSD_NAME,
2801 "stats",
2802 132015,
2803 statsd.update_every,
2804 RRDSET_TYPE_LINE);
2805 rd_tcp_connects = rrddim_add(st_tcp_connects, "connects", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2806 rd_tcp_disconnects = rrddim_add(st_tcp_connects, "disconnects", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
2807
2808 st_tcp_connected = rrdset_create_localhost(
2809 "netdata",
2810 "tcp_connected",
2811 NULL,
2812 "statsd",
2813 "netdata.statsd_tcp_connected",
2814 "statsd server TCP connected sockets",
2815 "sockets",
2816 PLUGIN_STATSD_NAME,
2817 "stats",
2818 132016,
2819 statsd.update_every,
2820 RRDSET_TYPE_LINE);
2821 rd_tcp_connected = rrddim_add(st_tcp_connected, "connected", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2822
2823 st_pcharts = rrdset_create_localhost(
2824 "netdata",
2825 "private_charts",
2826 NULL,
2827 "statsd",
2828 "netdata.statsd_private_charts",
2829 "Private metric charts created by the netdata statsd server",
2830 "charts",
2831 PLUGIN_STATSD_NAME,
2832 "stats",
2833 132020,
2834 statsd.update_every,
2835 RRDSET_TYPE_AREA);
2836 rd_pcharts = rrddim_add(st_pcharts, "charts", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2837 }
2838
2839 // ----------------------------------------------------------------------------------------------------------------
2840 // statsd thread to turn metrics into charts
2841
2842 heartbeat_t hb;
2843 heartbeat_init(&hb, statsd.update_every * USEC_PER_SEC);
2844 while(service_running(SERVICE_COLLECTORS)) {
2845 worker_is_idle();
2846 heartbeat_next(&hb);
2847
2848 worker_is_busy(WORKER_STATSD_FLUSH_GAUGES);
2849 statsd_flush_index_metrics(&statsd.gauges, statsd_flush_gauge);
2850
2851 worker_is_busy(WORKER_STATSD_FLUSH_COUNTERS);
2852 statsd_flush_index_metrics(&statsd.counters, statsd_flush_counter);
2853
2854 worker_is_busy(WORKER_STATSD_FLUSH_METERS);
2855 statsd_flush_index_metrics(&statsd.meters, statsd_flush_meter);
2856
2857 worker_is_busy(WORKER_STATSD_FLUSH_TIMERS);
2858 statsd_flush_index_metrics(&statsd.timers, statsd_flush_timer);
2859
2860 worker_is_busy(WORKER_STATSD_FLUSH_HISTOGRAMS);
2861 statsd_flush_index_metrics(&statsd.histograms, statsd_flush_histogram);
2862
2863 worker_is_busy(WORKER_STATSD_FLUSH_SETS);
2864 statsd_flush_index_metrics(&statsd.sets, statsd_flush_set);
2865
2866 worker_is_busy(WORKER_STATSD_FLUSH_DICTIONARIES);
2867 statsd_flush_index_metrics(&statsd.dictionaries,statsd_flush_dictionary);
2868
2869 worker_is_busy(WORKER_STATSD_FLUSH_STATS);
2870 statsd_update_all_app_charts();
2871
2872 if(unlikely(!service_running(SERVICE_COLLECTORS)))
2873 break;
2874
2875 if(pulse_enabled) {
2876 rrddim_set_by_pointer(st_metrics, rd_metrics_gauge, (collected_number)statsd.gauges.metrics);
2877 rrddim_set_by_pointer(st_metrics, rd_metrics_counter, (collected_number)statsd.counters.metrics);
2878 rrddim_set_by_pointer(st_metrics, rd_metrics_timer, (collected_number)statsd.timers.metrics);
2879 rrddim_set_by_pointer(st_metrics, rd_metrics_meter, (collected_number)statsd.meters.metrics);
2880 rrddim_set_by_pointer(st_metrics, rd_metrics_histogram, (collected_number)statsd.histograms.metrics);
2881 rrddim_set_by_pointer(st_metrics, rd_metrics_set, (collected_number)statsd.sets.metrics);
2882 rrddim_set_by_pointer(st_metrics, rd_metrics_dictionary, (collected_number)statsd.dictionaries.metrics);
2883 rrdset_done(st_metrics);
2884
2885 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_gauge, (collected_number)statsd.gauges.useful);
2886 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_counter, (collected_number)statsd.counters.useful);
2887 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_timer, (collected_number)statsd.timers.useful);
2888 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_meter, (collected_number)statsd.meters.useful);
2889 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_histogram, (collected_number)statsd.histograms.useful);
2890 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_set, (collected_number)statsd.sets.useful);
2891 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_dictionary, (collected_number)statsd.dictionaries.useful);
2892 rrdset_done(st_useful_metrics);
2893
2894 rrddim_set_by_pointer(st_events, rd_events_gauge, (collected_number)statsd.gauges.events);
2895 rrddim_set_by_pointer(st_events, rd_events_counter, (collected_number)statsd.counters.events);
2896 rrddim_set_by_pointer(st_events, rd_events_timer, (collected_number)statsd.timers.events);
2897 rrddim_set_by_pointer(st_events, rd_events_meter, (collected_number)statsd.meters.events);
2898 rrddim_set_by_pointer(st_events, rd_events_histogram, (collected_number)statsd.histograms.events);
2899 rrddim_set_by_pointer(st_events, rd_events_set, (collected_number)statsd.sets.events);
2900 rrddim_set_by_pointer(st_events, rd_events_dictionary, (collected_number)statsd.dictionaries.events);
2901 rrddim_set_by_pointer(st_events, rd_events_unknown, (collected_number)statsd.unknown_types);
2902 rrddim_set_by_pointer(st_events, rd_events_errors, (collected_number)statsd.socket_errors);
2903 rrdset_done(st_events);
2904
2905 rrddim_set_by_pointer(st_reads, rd_reads_tcp, (collected_number)statsd.tcp_socket_reads);
2906 rrddim_set_by_pointer(st_reads, rd_reads_udp, (collected_number)statsd.udp_socket_reads);
2907 rrdset_done(st_reads);
2908
2909 rrddim_set_by_pointer(st_bytes, rd_bytes_tcp, (collected_number)statsd.tcp_bytes_read);
2910 rrddim_set_by_pointer(st_bytes, rd_bytes_udp, (collected_number)statsd.udp_bytes_read);
2911 rrdset_done(st_bytes);
2912
2913 rrddim_set_by_pointer(st_packets, rd_packets_tcp, (collected_number)statsd.tcp_packets_received);
2914 rrddim_set_by_pointer(st_packets, rd_packets_udp, (collected_number)statsd.udp_packets_received);
2915 rrdset_done(st_packets);
2916
2917 rrddim_set_by_pointer(st_tcp_connects, rd_tcp_connects, (collected_number)statsd.tcp_socket_connects);
2918 rrddim_set_by_pointer(st_tcp_connects, rd_tcp_disconnects, (collected_number)statsd.tcp_socket_disconnects);
2919 rrdset_done(st_tcp_connects);
2920
2921 rrddim_set_by_pointer(st_tcp_connected, rd_tcp_connected, (collected_number)statsd.tcp_socket_connected);
2922 rrdset_done(st_tcp_connected);
2923
2924 rrddim_set_by_pointer(st_pcharts, rd_pcharts, (collected_number)statsd.private_charts);
2925 rrdset_done(st_pcharts);
2926 }
2927 }
2928
2929 cleanup: ; // added semi-colon to prevent older gcc error: label at end of compound statement
2930 return NULL;
2931 }