@cryptotaxi247 / netdata-1 / commits / 38b38b809

add dictionary support to statsd (#12980)

* add dictionary support to statsd * faster statsd sets and dictionaries; disabled events dimensions by default * properly detect tags, even without a sampling rate * statsd assumes a pipe between fields * missing param * allow names without values and support more unknown fields * more parser fixes * support multiple tags; remove the sum from the dimensions of histograms and timers, but keep it for synthetic charts * Parse statsd tags and support changing units of private charts * remove debug code * added support for naming dimensions too * updated docs * added support for family tags * updated docs

Costa Tsaousis committed May 25, 2022 at 01:24 UTC 38b38b80923f297246f83bbeac9d6c9e2772daff
6 files changed +467 -160
collectors/statsd.plugin/README.md
+133 -52
@@ -11,17 +11,17 @@ If you want to learn more about the StatsD protocol, we have written a [blog pos
11
12 Netdata is a fully featured statsd server. It can collect statsd formatted metrics, visualize them on its dashboards and store them in it's database for long-term retention.
13
14 -Netdata statsd is inside Netdata (an internal plugin, running inside the Netdata daemon), it is configured via `netdata.conf` and by-default listens on standard statsd port 8125. Netdata supports both tcp and udp packets at the same time.
14 +Netdata statsd is inside Netdata (an internal plugin, running inside the Netdata daemon), it is configured via `netdata.conf` and by-default listens on standard statsd port 8125. Netdata supports both TCP and UDP packets at the same time.
15
16 Since statsd is embedded in Netdata, it means you now have a statsd server embedded on all your servers.
17
18 -Netdata statsd is fast. It can collect more than **1.200.000 metrics per second** on modern hardware, more than **200Mbps of sustained statsd traffic**, using 1 CPU core. The implementation uses two threads: one thread collects metrics, another one updates the charts from the collected data.
18 +Netdata statsd is fast. It can collect several millions of metrics per second on modern hardware, using just 1 CPU core. The implementation uses two threads: one thread collects metrics, another thread updates the charts from the collected data.
19
20 -## Available StatsD collectors
20 +## Available StatsD synthetic application charts
21
22 -Netdata ships with collectors implemented using the StatsD collector. They are configuration files (as you will read below), but they function as a collector, in the sense that configuration file organize the metrics of a data source into pre-defined charts.
22 +Netdata ships with a few synthetic chart definitions to automatically present application metrics into a more uniform way. These synthetic charts are configuration files (you can create your own) that re-arrange statsd metrics into a more meaningful way.
23
24 -On these charts, we can have alarms as with any metric and chart.
24 +On synthetic charts, we can have alarms as with any metric and chart.
25
26 - [K6 load testing tool](https://k6.io)
27 - **Description:** k6 is a developer-centric, free and open-source load testing tool built for making performance testing a productive and enjoyable experience.
@@ -34,34 +34,36 @@ On these charts, we can have alarms as with any metric and chart.
34
35 ## Metrics supported by Netdata
36
37 -Netdata fully supports the StatsD protocol. All StatsD client libraries can be used with Netdata too.
37 +Netdata fully supports the StatsD protocol and also extends it to support more advanced Netdata specific use cases. All StatsD client libraries can be used with Netdata too.
38
39 -- **Gauges**
39 +- **Gauges**
40
41 The application sends `name:value|g`, where `value` is any **decimal/fractional** number, StatsD reports the latest value collected and the number of times it was updated (events).
42
43 The application may increment or decrement a previous value, by setting the first character of the value to `+` or `-` (so, the only way to set a gauge to an absolute negative value, is to first set it to zero).
44
45 [Sampling rate](#sampling-rates) is supported.
46 + [Tags](#tags) are supported for changing chart units, family and dimension name.
47
48 When a gauge is not collected and the setting is not to show gaps on the charts (the default), the last value will be shown, until a data collection event changes it.
49
49 -- **Counters** and **Meters**
50 +- **Counters** and **Meters**
51
52 The application sends `name:value|c`, `name:value|C` or `name:value|m`, where `value` is a positive or negative **integer** number of events occurred, StatsD reports the **rate** and the number of times it was updated (events).
53
53 - `:value` can be omitted and StatsD will assume it is `1`. `|c`, `|C` and `|m` can be omitted an StatsD will assume it is `|m`. So, the application may send just `name` and StatsD will parse it as `name:1|m`.
54 + `:value` can be omitted and StatsD will assume it is `1`. `|c`, `|C` and `|m` can be omitted and StatsD will assume it is `|m`. So, the application may send just `name` and StatsD will parse it as `name:1|m`.
55
56 - Counters use `|c` (etsy/StatsD compatible) or `|C` (brubeck compatible)
57 - Meters use `|m`
58
59 [Sampling rate](#sampling-rates) is supported.
60 + [Tags](#tags) are supported for changing chart units, family and dimension name.
61
60 - When a counter or meter is not collected, Netdata **defaults** to showing a zero value, until a data collection event changes the value.
62 + When a counter or meter is not collected, StatsD **defaults** to showing a zero value, until a data collection event changes the value.
63
62 -- **Timers** and **Histograms**
64 +- **Timers** and **Histograms**
65
64 - The application sends `name:value|ms` or `name:value|h`, where `value` is any **decimal/fractional** number, StatsD reports **min**, **max**, **average**, **sum**, **95th percentile**, **median** and **standard deviation** and the total number of times it was updated (events).
66 + The application sends `name:value|ms` or `name:value|h`, where `value` is any **decimal/fractional** number, StatsD reports **min**, **max**, **average**, **95th percentile**, **median** and **standard deviation** and the total number of times it was updated (events). Internally it also calculates the **sum**, which is available for synthetic charts.
67
68 - Timers use `|ms`
69 - Histograms use `|h`
@@ -69,38 +71,74 @@ Netdata fully supports the StatsD protocol. All StatsD client libraries can be u
71 The only difference between the two, is the `units` of the charts, as timers report *milliseconds*.
72
73 [Sampling rate](#sampling-rates) is supported.
74 + [Tags](#tags) are supported for changing chart units and family.
75
73 - When a counter or meter is not collected, Netdata **defaults** to showing a zero value, until a data collection event changes the value.
76 + When a counter or meter is not collected, StatsD **defaults** to showing a zero value, until a data collection event changes the value.
77
75 -- **Sets**
78 +- **Sets**
79
80 The application sends `name:value|s`, where `value` is anything (**number or text**, leading and trailing spaces are removed), StatsD reports the number of unique values sent and the number of times it was updated (events).
81
79 - Sampling rate is **not** supported for Sets. `value` is always considered text.
82 + Sampling rate is **not** supported for Sets. `value` is always considered text (so `01` and `1` are considered different).
83
81 - When a counter or meter is not collected, Netdata **defaults** to showing a zero value, until a data collection event changes the value.
84 + [Tags](#tags) are supported for changing chart units and family.
85 +
86 + When a set is not collected, Netdata **defaults** to showing a zero value, until a data collection event changes the value.
87 +
88 +- **Dictionaries**
89 +
90 + The application sends `name:value|d`, where `value` is anything (**number or text**, leading and trailing spaces are removed), StatsD reports the number of events sent for each `value` and the total times `name` was updated (events).
91 +
92 + Sampling rate is **not** supported for Dictionaries. `value` is always considered text (so `01` and `1` are considered different).
93 +
94 + [Tags](#tags) are supported for changing chart units and family.
95 +
96 + When a set is not collected, Netdata **defaults** to showing a zero value, until a data collection event changes the value.
97
98 #### Sampling Rates
99
100 The application may append `|@sampling_rate`, where `sampling_rate` is a number from `0.0` to `1.0` in order for StatD to extrapolate the value and predict the total for the entire period. If the application reports to StatsD a value for 1/10th of the time, it can append `|@0.1` to the metrics it sends to statsd.
101
102 +#### Tags
103 +
104 +The application may append `|#tag1:value1,tag2:value2,tag3:value3` etc, where `tagX` and `valueX` are strings. `:valueX` can be omitted.
105 +
106 +Currently, Netdata uses only 2 tags:
107 +
108 + * `units=string` which sets the units of the chart that is automatically generated
109 + * `family=string` which sets the family of the chart that is automatically generated (the family is the submenu of the dashboard)
110 + * `name=string` which sets the name of the dimension of the chart that is automatically generated (only for counters, meters, gauges)
111 +
112 +Other tags are parsed, but currently are ignored.
113 +
114 +Charts are not updated to change units or dimension names once they are created. So, either send the tags on every event, or use the special `zinit` value to initiaze the charts at the beginning. `zinit` is a special value that can be used on any chart, to have netdata initialize the charts, without actually setting any values to them. So, instead of sending `my.metric:VALUE|c|#units=bytes,name=size` every time, the application can send at the beginning `my.metric:zinit|c|#units=bytes,name=size` and then `my.metric:VALUE|c`.
115 +
116 #### Overlapping metrics
117
89 -Netdata's StatsD server maintains different indexes for each of the types supported. This means the same metric `name` may exist under different types concurrently.
118 +Netdata's StatsD server maintains different indexes for each of the metric types supported. This means the same metric `name` may exist under different types concurrently.
119 +
120 +#### How to name your metrics
121 +
122 +A good practice is to name your metrics like `application.operation.metric`, where:
123 +
124 +- `application` is the application name - Netdata will automatically create a dashboard section based on the first keyword of the metrics, so you can have all your applications in different sections.
125 +- `operation` is the operation your application is executing, like `dbquery`, `request`, `response`, etc.
126 +- `metric` is anything you want to name your metric as. Netdata will automatically append the metric type (meter, counter, gauge, set, dictionary, timer, histogram) to the generated chart.
127 +
128 +Using [Tags](#tags) you can also change the submenus of the dashboard, the units of the charts and for meters, counters and gauges, the name of dimension. So, you can have a usable default view without using [Synthetic StatsD charts](#synthetic-statsd-charts)
129
130 #### Multiple metrics per packet
131
93 -Netdata accepts multiple metrics per packet if each is terminated with `\n`.
132 +Netdata accepts multiple metrics per packet if each is terminated with a newline (`\n`) at the end.
133
134 #### TCP packets
135
136 Netdata listens for both TCP and UDP packets. For TCP, is it important to always append `\n` on each metric, as Netdata will use the newline character to detect if a metric is split into multiple TCP packets.
137
99 -On disconnect, Netdata will process the entire buffer, even if it is not terminated with a `\n`.
138
139 #### UDP packets
140
103 -When sending multiple packets over UDP, it is important not to exceed the network MTU, which is usually 1500 bytes.
141 +When sending multiple metrics over a single UDP message, it is important not to exceed the network MTU, which is usually 1500 bytes.
142
143 Netdata will accept UDP packets up to 9000 bytes, but the underlying network will not exceed MTU.
144
@@ -122,7 +160,7 @@ You can find the configuration at `/etc/netdata/netdata.conf`:
160 # private charts memory mode = save
161 # private charts history = 3996
162 # histograms and timers percentile (percentThreshold) = 95.00000
125 - # add dimension for number of events received = yes
163 + # add dimension for number of events received = no
164 # gaps on gauges (deleteGauges) = no
165 # gaps on counters (deleteCounters) = no
166 # gaps on meters (deleteMeters) = no
@@ -150,7 +188,7 @@ You can find the configuration at `/etc/netdata/netdata.conf`:
188
189 - `update every (flushInterval) = 1` seconds, controls the frequency StatsD will push the collected metrics to Netdata charts.
190
153 -- `decimal detail = 1000` controls the number of fractional digits in gauges and histograms. Netdata collects metrics using signed 64 bit integers and their fractional detail is controlled using multipliers and divisors. This setting is used to multiply all collected values to convert them to integers and is also set as the divisors, so that the final data will be a floating point number with this fractional detail (1000 = X.0 - X.999, 10000 = X.0 - X.9999, etc).
191 +- `decimal detail = 1000` controls the number of fractional digits in gauges and histograms. Netdata collects metrics using signed 64-bit integers and their fractional detail is controlled using multipliers and divisors. This setting is used to multiply all collected values to convert them to integers and is also set as the divisors, so that the final data will be a floating point number with this fractional detail (1000 = X.0 - X.999, 10000 = X.0 - X.9999, etc).
192
193 The rest of the settings are discussed below.
194
@@ -180,10 +218,9 @@ The default behavior is to use the same settings as the rest of the Netdata Agen
218
219 ### Optimize private metric charts visualization and storage
220
183 -
221 If you have thousands of metrics, each with its own private chart, you may notice that your web browser becomes slow when you view the Netdata dashboard (this is a web browser issue we need to address at the Netdata UI). So, Netdata has a protection to stop creating charts when `max private charts allowed = 200` (soft limit) is reached.
222
186 -The metrics above this soft limit are still processed by Netdata and will be available to be sent to backend time-series databases, up to `max private charts hard limit = 1000`. So, between 200 and 1000 charts, Netdata will still generate charts, but they will automatically be created with `memory mode = none` (Netdata will not maintain a database for them). These metrics will be sent to backend time series databases, if the backend configuration is set to `as collected`.
223 +The metrics above this soft limit are still processed by Netdata, can be used in synthetic charts and will be available to be sent to backend time-series databases, up to `max private charts hard limit = 1000`. So, between 200 and 1000 charts, Netdata will still generate charts, but they will automatically be created with `memory mode = none` (Netdata will not maintain a database for them). These metrics will be sent to backend time series databases, if the backend configuration is set to `as collected`.
224
225 Metrics above the hard limit are still collected, but they can only be used in synthetic charts (once a metric is added to chart, it will be sent to backend servers too).
226
@@ -240,9 +277,6 @@ This is identical to `counter`.
277 - Format: `name:FLOAT|ms`
278 - StatsD maintains a list of all the values supplied and provides statistics on them.
279
243 -![image](https://cloud.githubusercontent.com/assets/2662304/26131620/acbea6a4-3aa3-11e7-8bdd-4a8996847767.png)
244 -
245 -The same chart with the `sum` unselected:
280 ![image](https://cloud.githubusercontent.com/assets/2662304/26131629/bc34f2d2-3aa3-11e7-8a07-f2fc94ba4352.png)
281
282 ### Synthetic StatsD charts
@@ -369,7 +403,7 @@ Synthetic chart:
403
404 ![screenshot from 2017-08-03 23-29-14](https://user-images.githubusercontent.com/2662304/28942317-958a2c68-78a3-11e7-853f-32850141dd36.png)
405
372 -#### Renaming StatsD metrics
406 +#### Renaming StatsD synthetic charts' metrics
407
408 You can define a dictionary to rename metrics sent by StatsD clients. This enables you to send response `"200"` and Netdata visualize it as `succesful connection`
409
@@ -438,7 +472,7 @@ You can rename the dimensions with this:
472
473 Note that we added a `NAME` to the dimension line with `get.`. This is prefixed to the wildcarded part of the metric name, to compose the key for looking up the dictionary. So `500` became `get.500` which was looked up to the dictionary to find value `500 cannot connect to db`. This way we can have different dimension names, for each of the API methods (i.e. `get.500 = 500 cannot connect to db` while `post.500 = 500 cannot write to disk`).
474
441 -To add all API methods to a chart, you can do this:
475 +To add all 200s across all API methods to a chart, you can do this:
476
477 ```
478 [ok_by_method]
@@ -539,44 +573,79 @@ You can also use StatsD with:
573
574 ### Shell
575
542 -Getting the proper support for a programming language is not always easy, but the Unix shell is available on most Unix systems. You can use shell and `nc` to instrument your systems and send metric data to Netdata's StatsD implementation. Here's how:
576 +Getting the proper support for a programming language is not always easy, but the Unix shell is available on most Unix systems. You can use shell and `nc` to instrument your systems and send metric data to Netdata's StatsD implementation.
577 +
578 +Using the method you can send metrics from any script. You can generate events like: backup.started, backup.ended, backup.time, or even tail logs and convert them to metrics.
579 +
580 +> **IMPORTANT**:
581 +>
582 +> To send StatsD messages you need from the `netcat` package, the `nc` command.
583 +> There are multiple versions of this package. Please try to experiment with the `nc` command you have available on your right system, to find the right parameters.
584 +>
585 +> In the examples below, we assume the `openbsd-netcat` is installed.
586 +
587 +If you plan to send short StatsD events at sporadic occasions, use UDP. The messages should not be too long (remember, most networks support up to 1500 bytes MTU, which is also the limit for StatsD messages over UDP). The good thing is that using UDP will not block your script, even if the StatsD server is not there (UDP messages are "fire-and-forget").
588 +
589
544 -The command you need to run is:
590 +For UDP use this:
591
592 ```sh
547 -echo "NAME:VALUE|TYPE" | nc -u --send-only localhost 8125
593 +echo "APPLICATION.METRIC:VALUE|TYPE" | nc -u -w 0 localhost 8125
594 ```
595
550 -Where:
596 +`-u` turns on UDP, `-w 0` tells `nc` not to wait for a response from StatsD (idle time to close the connection).
597
552 -- `NAME` is the metric name
553 -- `VALUE` is the value for that metric (**gauges** `|g`, **timers** `|ms` and **histograms** `|h` accept decimal/fractional numbers, **counters** `|c` and **meters** `|m` accept integers, **sets** `|s` accept anything)
554 -- `TYPE` is one of `g`, `ms`, `h`, `c`, `m`, `s` to select the metric type.
598 +where:
599
556 -So, to set `metric1` as gauge to value `10`, use:
600 +- `APPLICATION` is any name for your application
601 +- `METRIC` is the name for the specific metric
602 +- `VALUE` is the value for that metric (**meters**, **counters**, **gauges**, **timers** and **histograms** accept integer/decimal/fractional numbers, **sets** and **dictionaries** accept strings)
603 +- `TYPE` is one of `m`, `c`, `g`, `ms`, `h`, `s`, `d` to define the metric type.
604 +
605 +For tailing a log and converting it to metrics, do something like this:
606
607 ```sh
559 -echo "metric1:10|g" | nc -u --send-only localhost 8125
608 +tail -f some.log | awk 'awk commands to parse the log and format statsd metrics' | nc -N -w 120 localhost 8125
609 ```
610
562 -To increment `metric2` by `10`, as a counter, use:
611 +`-N` tells `nc` to close the socket once it receives EOF on its input. `-w 120` tells `nc` to stop if the connection is idle for 120 seconds. The timeout is needed to stop the `nc` command if you restart Netdata while `nc` is connected to it. Without it, `nc` will sit idle forever.
612 +
613 +When you embed the above commands to a script, you may notice that all the metrics are sent to StatsD with a delay. They are buffered in the pipes `|`. You can turn them to real-time by prepending each command with `stdbuf -i0 -oL -eL command to be run`, like this:
614
615 ```sh
565 -echo "metric2:10|c" | nc -u --send-only localhost 8125
616 +stdbuf -i0 -oL -eL tail -f some.log |\
617 + stdbuf -i0 -oL -eL awk 'awk commands to parse the log and format statsd metrics' |\
618 + stdbuf -i0 -oL -eL nc -N -w 120 localhost 8125
619 +```
620 +
621 +If you use `mawk` you also need to run awk with `-W interactive`.
622 +
623 +Examples:
624 +
625 +To set `myapp.used_memory` as gauge to value `123456`, use:
626 +
627 +```sh
628 +echo "myapp.used_memory:123456|g|#units:bytes" | nc -u -w 0 localhost 8125
629 +```
630 +
631 +To increment `myapp.files_sent` by `10`, as a counter, use:
632 +
633 +```sh
634 +echo "myapp.files_sent:10|c|#units:files" | nc -u -w 0 localhost 8125
635 ```
636
637 You can send multiple metrics like this:
638
639 ```sh
640 # send multiple metrics via UDP
572 -printf "metric1:10|g\nmetric2:10|c\n" | nc -u --send-only localhost 8125
641 +printf "myapp.used_memory:123456|g|#units:bytes\nmyapp.files_sent:10|c|#units:files\n" | nc -u -w 0 localhost 8125
642 ```
643
644 Remember, for UDP communication each packet should not exceed the MTU. So, if you plan to push too many metrics at once, prefer TCP communication:
645
646 ```sh
647 # send multiple metrics via TCP
579 -printf "metric1:10|g\nmetric2:10|c\n" | nc --send-only localhost 8125
648 +cat /tmp/statsd.metrics.txt | nc -N -w 120 localhost 8125
649 ```
650
651 You can also use this little function to take care of all the details:
@@ -584,22 +653,29 @@ You can also use this little function to take care of all the details:
653 ```sh
654 #!/usr/bin/env bash
655
656 +# we assume nc is from the openbsd-netcat package
657 +
658 STATSD_HOST="localhost"
659 STATSD_PORT="8125"
660 statsd() {
590 - local udp="-u" all="${*}"
661 + local options="-u -w 0" all="${*}"
662 +
663 + # replace all spaces with newlines
664 + all="${all// /\\n}"
665
666 # if the string length of all parameters given is above 1000, use TCP
593 - [ "${#all}" -gt 1000 ] && udp=
667 + [ "${#all}" -gt 1000 ] && options="-N -w 0"
668
595 - while [ ! -z "${1}" ]
596 - do
597 - printf "${1}\n"
598 - shift
599 - done | nc ${udp} --send-only ${STATSD_HOST} ${STATSD_PORT} || return 1
669 + # send the metrics to statsd
670 + printf "${all}\n" | nc ${options} ${STATSD_HOST} ${STATSD_PORT} || return 1
671
672 return 0
673 }
674 +
675 +if [ ! -z "${*}" ]
676 +then
677 + statsd "${@}"
678 +fi
679 ```
680
681 You can use it like this:
@@ -609,10 +685,15 @@ You can use it like this:
685 source statsd.sh
686
687 # then, at any point:
612 -StatsD "metric1:10|g" "metric2:10|c" ...
688 +statsd "myapp.used_memory:123456|g|#units:bytes" "myapp.files_sent:10|c|#units:files" ...
689 ```
614 -The function is smart enough to call `nc` just once and pass all the metrics to it. It will also automatically switch to TCP if the metrics to send are above 1000 bytes.
690
616 -If you have gotten thus far, make sure to check out our [community forums](https://community.netdata.cloud) to share your experience using Netdata with StatsD.
691 +or even at a terminal prompt, like this:
692
693 +```sh
694 +./statsd.sh "myapp.used_memory:123456|g|#units:bytes" "myapp.files_sent:10|c|#units:files" ...
695 +```
696
697 +The function is smart enough to call `nc` just once and pass all the metrics to it. It will also automatically switch to TCP if the metrics to send are above 1000 bytes.
698 +
699 +If you have gotten thus far, make sure to check out our [community forums](https://community.netdata.cloud) to share your experience using Netdata with StatsD.
collectors/statsd.plugin/statsd.c
+326 -102
@@ -18,6 +18,10 @@
18 #error Please increase WORKER_UTILIZATION_MAX_JOB_TYPES to at least 4
19 #endif
20
21 +#define STATSD_MAX_UNITS_LENGTH 20
22 +#define STATSD_MAX_DIMNAME_LENGTH 20
23 +#define STATSD_MAX_FAMILY_LENGTH 20
24 +
25 // --------------------------------------------------------------------------------------
26
27 // #define STATSD_MULTITHREADED 1
@@ -32,7 +36,7 @@
36 #define STATSD_FIRST_PTR_MUTEX_INIT .first_mutex = NETDATA_MUTEX_INITIALIZER
37 #define STATSD_FIRST_PTR_MUTEX_LOCK(index) netdata_mutex_lock(&((index)->first_mutex))
38 #define STATSD_FIRST_PTR_MUTEX_UNLOCK(index) netdata_mutex_unlock(&((index)->first_mutex))
35 -#define STATSD_DICTIONARY_OPTIONS DICTIONARY_FLAG_DEFAULT
39 +#define STATSD_DICTIONARY_OPTIONS DICTIONARY_FLAG_NONE
40 #else
41 #define STATSD_AVL_TREE avl_tree_type
42 #define STATSD_AVL_INSERT avl_insert
@@ -76,7 +80,7 @@ typedef struct statsd_histogram_extensions {
80 RRDDIM *rd_percentile;
81 RRDDIM *rd_median;
82 RRDDIM *rd_stddev;
79 - RRDDIM *rd_sum;
83 + //RRDDIM *rd_sum;
84
85 size_t size;
86 size_t used;
@@ -92,6 +96,23 @@ typedef struct statsd_metric_set {
96 size_t unique;
97 } STATSD_METRIC_SET;
98
99 +#define STATSD_METRIC_DICTIONARY_FLAGS_DICTFULL_LOGGED 0x000001
100 +
101 +typedef struct statsd_metric_dictionary_item {
102 + char *name;
103 + size_t count;
104 + RRDDIM *rd;
105 + struct statsd_metric_dictionary_item *next;
106 +} STATSD_METRIC_DICTIONARY_ITEM;
107 +
108 +typedef struct statsd_metric_dictionary {
109 + STATSD_METRIC_DICTIONARY_ITEM *other;
110 + DICTIONARY *dict;
111 + size_t unique;
112 + uint32_t flags;
113 + STATSD_METRIC_DICTIONARY_ITEM *base;
114 +} STATSD_METRIC_DICTIONARY;
115 +
116
117 // --------------------------------------------------------------------------------------------------------------------
118 // this is a metric - for all types of metrics
@@ -114,7 +135,8 @@ typedef enum statsd_metric_type {
135 STATSD_METRIC_TYPE_METER,
136 STATSD_METRIC_TYPE_TIMER,
137 STATSD_METRIC_TYPE_HISTOGRAM,
117 - STATSD_METRIC_TYPE_SET
138 + STATSD_METRIC_TYPE_SET,
139 + STATSD_METRIC_TYPE_DICTIONARY
140 } STATSD_METRIC_TYPE;
141
142
@@ -136,8 +158,13 @@ typedef struct statsd_metric {
158 STATSD_METRIC_COUNTER counter;
159 STATSD_METRIC_HISTOGRAM histogram;
160 STATSD_METRIC_SET set;
161 + STATSD_METRIC_DICTIONARY dictionary;
162 };
163
164 + char units[STATSD_MAX_UNITS_LENGTH+1];
165 + char dimname[STATSD_MAX_DIMNAME_LENGTH+1];
166 + char family[STATSD_MAX_FAMILY_LENGTH+1];
167 +
168 // chart related members
169 STATS_METRIC_OPTIONS options; // STATSD_METRIC_OPTION_* (bitfield)
170 char reset; // set to 1 by the charting thread to instruct the collector thread(s) to reset this metric
@@ -255,6 +282,8 @@ static struct statsd {
282 STATSD_INDEX histograms;
283 STATSD_INDEX meters;
284 STATSD_INDEX sets;
285 + STATSD_INDEX dictionaries;
286 +
287 size_t unknown_types;
288 size_t socket_errors;
289 size_t tcp_socket_connects;
@@ -285,6 +314,7 @@ static struct statsd {
314 size_t histogram_increase_step;
315 double histogram_percentile;
316 char *histogram_percentile_str;
317 + size_t dictionary_max_unique;
318
319 int threads;
320 struct collection_thread_status *collection_threads_status;
@@ -352,12 +382,22 @@ static struct statsd {
382 .first = NULL,
383 STATSD_FIRST_PTR_MUTEX_INIT
384 },
385 + .dictionaries = {
386 + .name = "dictionary",
387 + .events = 0,
388 + .metrics = 0,
389 + .index = STATSD_AVL_INDEX_INIT,
390 + .default_options = STATSD_METRIC_OPTION_NONE,
391 + .first = NULL,
392 + STATSD_FIRST_PTR_MUTEX_INIT
393 + },
394
395 .tcp_idle_timeout = 600,
396
397 .apps = NULL,
398 .histogram_percentile = 95.0,
399 .histogram_increase_step = 10,
400 + .dictionary_max_unique = 200,
401 .threads = 0,
402 .collection_threads_status = NULL,
403 .sockets = {
@@ -591,7 +631,7 @@ static inline void statsd_process_set(STATSD_METRIC *m, const char *value) {
631 }
632
633 if (unlikely(!m->set.dict)) {
594 - m->set.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS | DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE);
634 + m->set.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS | DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
635 m->set.unique = 0;
636 }
637
@@ -599,9 +639,16 @@ static inline void statsd_process_set(STATSD_METRIC *m, const char *value) {
639 // magic loading of metric, without affecting anything
640 }
641 else {
602 - void *t = dictionary_get(m->set.dict, value);
603 - if (unlikely(!t)) {
604 - dictionary_set(m->set.dict, value, "", 1);
642 + char c = 'N'; // new
643 + char *cptr = (char *)dictionary_set(m->set.dict, value, &c, sizeof(char));
644 +
645 + // since we pass DICTIONARY_FLAG_DONT_OVERWRITE_VALUE
646 + // the dictionary will return an existing value, if the key is already there
647 + // and based on the returned value, we can know if it is New or Old.
648 +
649 + if(*cptr == 'N') {
650 + // it is a new item
651 + *cptr = 'O'; // mark it as old
652 m->set.unique++;
653 }
654
@@ -610,89 +657,179 @@ static inline void statsd_process_set(STATSD_METRIC *m, const char *value) {
657 }
658 }
659
660 +static inline void statsd_process_dictionary(STATSD_METRIC *m, const char *value) {
661 + if(!is_metric_useful_for_collection(m)) return;
662 +
663 + if(unlikely(!value || !*value)) {
664 + error("STATSD: metric of type set, with empty value is ignored.");
665 + return;
666 + }
667 +
668 + if(unlikely(m->reset))
669 + statsd_reset_metric(m);
670 +
671 + if (unlikely(!m->dictionary.dict)) {
672 + m->dictionary.dict = dictionary_create(STATSD_DICTIONARY_OPTIONS | DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
673 + m->dictionary.unique = 0;
674 + m->dictionary.base = NULL;
675 + }
676 +
677 + if(unlikely(value_is_zinit(value))) {
678 + // magic loading of metric, without affecting anything
679 + }
680 + else {
681 + STATSD_METRIC_DICTIONARY_ITEM *t = (STATSD_METRIC_DICTIONARY_ITEM *)dictionary_get(m->dictionary.dict, value);
682 +
683 + if (unlikely(!t)) {
684 + if(!t && m->dictionary.unique >= statsd.dictionary_max_unique)
685 + value = "other";
686 +
687 + STATSD_METRIC_DICTIONARY_ITEM tmp = {
688 + .name = NULL,
689 + .count = 0,
690 + .rd = NULL,
691 + .next = NULL
692 + };
693 + char *name_ptr = NULL;
694 + t = (STATSD_METRIC_DICTIONARY_ITEM *)dictionary_set_with_name_ptr(m->dictionary.dict, value, &tmp, sizeof(STATSD_METRIC_DICTIONARY_ITEM), &name_ptr);
695 + if(!t->name) {
696 + // we just added this
697 + t->name = name_ptr;
698 + t->next = m->dictionary.base;
699 + m->dictionary.base = t;
700 + m->dictionary.unique++;
701 + }
702 + }
703 +
704 + t->count++;
705 + m->events++;
706 + m->count++;
707 + }
708 +}
709 +
710
711 // --------------------------------------------------------------------------------------------------------------------
712 // statsd parsing
713
617 -static void statsd_process_metric(const char *name, const char *value, const char *type, const char *sampling, const char *tags) {
618 - (void)tags;
714 +static inline const char *statsd_parse_skip_up_to(const char *s, char d1, char d2, char d3) {
715 + char c;
716 +
717 + for(c = *s; c && c != d1 && c != d2 && c != d3 && c != '\r' && c != '\n'; c = *++s) ;
718 +
719 + return s;
720 +}
721 +
722 +const char *statsd_parse_skip_spaces(const char *s) {
723 + char c;
724 +
725 + for(c = *s; c && ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ); c = *++s) ;
726 +
727 + return s;
728 +}
729
730 +static inline const char *statsd_parse_field_trim(const char *start, char *end) {
731 + if(unlikely(!start || !*start)) {
732 + start = end;
733 + return start;
734 + }
735 +
736 + while(start <= end && (*start == ' ' || *start == '\t'))
737 + start++;
738 +
739 + *end = '\0';
740 + end--;
741 + while(end >= start && (*end == ' ' || *end == '\t'))
742 + *end-- = '\0';
743 +
744 + return start;
745 +}
746 +
747 +static void statsd_process_metric(const char *name, const char *value, const char *type, const char *sampling, const char *tags) {
748 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)");
749
750 if(unlikely(!name || !*name)) return;
751 if(unlikely(!type || !*type)) type = "m";
752
625 - char t0 = type[0], t1 = type[1];
753 + STATSD_METRIC *m = NULL;
754
755 + char t0 = type[0], t1 = type[1];
756 if(unlikely(t0 == 'g' && t1 == '\0')) {
757 statsd_process_gauge(
629 - statsd_find_or_add_metric(&statsd.gauges, name, STATSD_METRIC_TYPE_GAUGE),
630 - value, sampling);
758 + m = statsd_find_or_add_metric(&statsd.gauges, name, STATSD_METRIC_TYPE_GAUGE),
759 + value, sampling);
760 }
761 else if(unlikely((t0 == 'c' || t0 == 'C') && t1 == '\0')) {
762 // etsy/statsd uses 'c'
763 // brubeck uses 'C'
764 statsd_process_counter(
636 - statsd_find_or_add_metric(&statsd.counters, name, STATSD_METRIC_TYPE_COUNTER),
637 - value, sampling);
765 + m = statsd_find_or_add_metric(&statsd.counters, name, STATSD_METRIC_TYPE_COUNTER),
766 + value, sampling);
767 }
768 else if(unlikely(t0 == 'm' && t1 == '\0')) {
769 statsd_process_meter(
641 - statsd_find_or_add_metric(&statsd.meters, name, STATSD_METRIC_TYPE_METER),
642 - value, sampling);
770 + m = statsd_find_or_add_metric(&statsd.meters, name, STATSD_METRIC_TYPE_METER),
771 + value, sampling);
772 }
773 else if(unlikely(t0 == 'h' && t1 == '\0')) {
774 statsd_process_histogram(
646 - statsd_find_or_add_metric(&statsd.histograms, name, STATSD_METRIC_TYPE_HISTOGRAM),
647 - value, sampling);
775 + m = statsd_find_or_add_metric(&statsd.histograms, name, STATSD_METRIC_TYPE_HISTOGRAM),
776 + value, sampling);
777 }
778 else if(unlikely(t0 == 's' && t1 == '\0')) {
779 statsd_process_set(
651 - statsd_find_or_add_metric(&statsd.sets, name, STATSD_METRIC_TYPE_SET),
652 - value);
780 + m = statsd_find_or_add_metric(&statsd.sets, name, STATSD_METRIC_TYPE_SET),
781 + value);
782 + }
783 + else if(unlikely(t0 == 'd' && t1 == '\0')) {
784 + statsd_process_dictionary(
785 + m = statsd_find_or_add_metric(&statsd.dictionaries, name, STATSD_METRIC_TYPE_DICTIONARY),
786 + value);
787 }
788 else if(unlikely(t0 == 'm' && t1 == 's' && type[2] == '\0')) {
789 statsd_process_timer(
656 - statsd_find_or_add_metric(&statsd.timers, name, STATSD_METRIC_TYPE_TIMER),
657 - value, sampling);
790 + m = statsd_find_or_add_metric(&statsd.timers, name, STATSD_METRIC_TYPE_TIMER),
791 + value, sampling);
792 }
793 else {
794 statsd.unknown_types++;
795 error("STATSD: metric '%s' with value '%s' is sent with unknown metric type '%s'", name, value?value:"", type);
796 }
663 -}
797
665 -static inline const char *statsd_parse_skip_up_to(const char *s, char d1, char d2) {
666 - char c;
798 + if(m && tags && *tags) {
799 + const char *s = tags;
800 + while(*s) {
801 + const char *tagkey = NULL, *tagvalue = NULL;
802 + char *tagkey_end = NULL, *tagvalue_end = NULL;
803
668 - for(c = *s; c && c != d1 && c != d2 && c != '\r' && c != '\n'; c = *++s) ;
669 -
670 - return s;
671 -}
804 + s = tagkey_end = (char *)statsd_parse_skip_up_to(tagkey = s, ':', '=', ',');
805 + if(tagkey == tagkey_end) {
806 + if (*s) {
807 + s++;
808 + s = statsd_parse_skip_spaces(s);
809 + }
810 + continue;
811 + }
812
673 -const char *statsd_parse_skip_spaces(const char *s) {
674 - char c;
813 + if(likely(*s == ':' || *s == '='))
814 + s = tagvalue_end = (char *) statsd_parse_skip_up_to(tagvalue = ++s, ',', '\0', '\0');
815
676 - for(c = *s; c && ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ); c = *++s) ;
816 + if(*s == ',') s++;
817
678 - return s;
679 -}
818 + statsd_parse_field_trim(tagkey, tagkey_end);
819 + statsd_parse_field_trim(tagvalue, tagvalue_end);
820
681 -static inline const char *statsd_parse_field_trim(const char *start, char *end) {
682 - if(unlikely(!start)) {
683 - start = end;
684 - return start;
685 - }
821 + if(tagkey && tagkey && tagvalue && *tagvalue) {
822 + if (!m->units[0] && strcmp(tagkey, "units") == 0)
823 + strncpyz(m->units, tagvalue, STATSD_MAX_UNITS_LENGTH);
824
687 - while(start <= end && (*start == ' ' || *start == '\t'))
688 - start++;
825 + if (!m->dimname[0] && strcmp(tagkey, "name") == 0)
826 + strncpyz(m->dimname, tagvalue, STATSD_MAX_DIMNAME_LENGTH);
827
690 - *end = '\0';
691 - end--;
692 - while(end >= start && (*end == ' ' || *end == '\t'))
693 - *end-- = '\0';
694 -
695 - return start;
828 + if (!m->family[0] && strcmp(tagkey, "family") == 0)
829 + strncpyz(m->family, tagvalue, STATSD_MAX_FAMILY_LENGTH);
830 + }
831 + }
832 + }
833 }
834
835 static inline size_t statsd_process(char *buffer, size_t size, int require_newlines) {
@@ -704,7 +841,7 @@ static inline size_t statsd_process(char *buffer, size_t size, int require_newli
841 const char *name = NULL, *value = NULL, *type = NULL, *sampling = NULL, *tags = NULL;
842 char *name_end = NULL, *value_end = NULL, *type_end = NULL, *sampling_end = NULL, *tags_end = NULL;
843
707 - s = name_end = (char *)statsd_parse_skip_up_to(name = s, ':', '|');
844 + s = name_end = (char *)statsd_parse_skip_up_to(name = s, ':', '=', '|');
845 if(name == name_end) {
846 if (*s) {
847 s++;
@@ -713,20 +850,27 @@ static inline size_t statsd_process(char *buffer, size_t size, int require_newli
850 continue;
851 }
852
716 - if(likely(*s == ':'))
717 - s = value_end = (char *) statsd_parse_skip_up_to(value = ++s, '|', '|');
853 + if(likely(*s == ':' || *s == '='))
854 + s = value_end = (char *) statsd_parse_skip_up_to(value = ++s, '|', '@', '#');
855
856 if(likely(*s == '|'))
720 - s = type_end = (char *) statsd_parse_skip_up_to(type = ++s, '|', '@');
857 + s = type_end = (char *) statsd_parse_skip_up_to(type = ++s, '|', '@', '#');
858
722 - if(likely(*s == '|' || *s == '@')) {
723 - s = sampling_end = (char *) statsd_parse_skip_up_to(sampling = ++s, '|', '#');
724 - if(*sampling == '@') sampling++;
725 - }
859 + while(*s == '|' || *s == '@' || *s == '#') {
860 + // parse all the fields that may be appended
861
727 - if(likely(*s == '|' || *s == '#')) {
728 - s = tags_end = (char *) statsd_parse_skip_up_to(tags = ++s, '|', '|');
729 - if(*tags == '#') tags++;
862 + if ((*s == '|' && s[1] == '@') || *s == '@') {
863 + s = sampling_end = (char *)statsd_parse_skip_up_to(sampling = ++s, '|', '@', '#');
864 + if (*sampling == '@') sampling++;
865 + }
866 + else if ((*s == '|' && s[1] == '#') || *s == '#') {
867 + s = tags_end = (char *)statsd_parse_skip_up_to(tags = ++s, '|', '@', '#');
868 + if (*tags == '#') tags++;
869 + }
870 + else {
871 + // unknown field, skip it
872 + s = (char *)statsd_parse_skip_up_to(++s, '|', '@', '#');
873 + }
874 }
875
876 // skip everything until the end of the line
@@ -1444,23 +1588,27 @@ static inline void statsd_readdir(const char *user_path, const char *stock_path,
1588 // send metrics to netdata - in private charts - called from the main thread
1589
1590 // extract chart type and chart id from metric name
1447 -static inline void statsd_get_metric_type_and_id(STATSD_METRIC *m, char *type, char *id, const char *defid, size_t len) {
1591 +static inline void statsd_get_metric_type_and_id(STATSD_METRIC *m, char *type, char *id, char *context, const char *metrictype, size_t len) {
1592 char *s;
1593
1450 - snprintfz(type, len, "%s_%s_%s", STATSD_CHART_PREFIX, defid, m->name);
1451 - for(s = type; *s ;s++)
1452 - if(unlikely(*s == '.')) break;
1594 + snprintfz(type, len, "%s_%s", STATSD_CHART_PREFIX, m->name);
1595 + if(sizeof(STATSD_CHART_PREFIX) + 2 < len)
1596 + for(s = &type[sizeof(STATSD_CHART_PREFIX) + 2]; *s ;s++)
1597 + if(unlikely(*s == '.' || *s == '_')) break;
1598
1454 - if(*s == '.') {
1599 + if(*s == '.' || *s == '_') {
1600 *s++ = '\0';
1456 - strncpyz(id, s, len);
1601 + snprintfz(id, len, "%s_%s", s, metrictype);
1602 }
1603 else {
1459 - strncpyz(id, defid, len);
1604 + snprintfz(id, len, "%s", metrictype);
1605 }
1606
1607 + snprintfz(context, RRD_ID_LENGTH_MAX, "statsd_%s.%s", metrictype, m->name);
1608 +
1609 netdata_fix_chart_id(type);
1610 netdata_fix_chart_id(id);
1611 + netdata_fix_chart_id(context);
1612 }
1613
1614 static inline RRDSET *statsd_private_rrdset_create(
@@ -1517,11 +1665,8 @@ static inline void statsd_private_chart_gauge(STATSD_METRIC *m) {
1665 debug(D_STATSD, "updating private chart for gauge metric '%s'", m->name);
1666
1667 if(unlikely(!m->st)) {
1520 - char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1];
1521 - statsd_get_metric_type_and_id(m, type, id, "gauge", RRD_ID_LENGTH_MAX);
1522 -
1523 - char context[RRD_ID_LENGTH_MAX + 1];
1524 - snprintfz(context, RRD_ID_LENGTH_MAX, "statsd_gauge.%s", m->name);
1668 + char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1669 + statsd_get_metric_type_and_id(m, type, id, context, "gauge", RRD_ID_LENGTH_MAX);
1670
1671 char title[RRD_ID_LENGTH_MAX + 1];
1672 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for gauge %s", m->name);
@@ -1531,16 +1676,16 @@ static inline void statsd_private_chart_gauge(STATSD_METRIC *m) {
1676 , type
1677 , id
1678 , NULL // name
1534 - , "gauges" // family (submenu)
1679 + , m->family[0]?m->family:"gauges" // family (submenu)
1680 , context // context
1681 , title // title
1537 - , "value" // units
1682 + , m->units[0]?m->units:"value" // units
1683 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1684 , statsd.update_every
1685 , RRDSET_TYPE_LINE
1686 );
1687
1543 - m->rd_value = rrddim_add(m->st, "gauge", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1688 + m->rd_value = rrddim_add(m->st, "gauge", m->dimname[0]?m->dimname:NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1689
1690 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1691 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
@@ -1559,11 +1704,8 @@ static inline void statsd_private_chart_counter_or_meter(STATSD_METRIC *m, const
1704 debug(D_STATSD, "updating private chart for %s metric '%s'", dim, m->name);
1705
1706 if(unlikely(!m->st)) {
1562 - char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1];
1563 - statsd_get_metric_type_and_id(m, type, id, dim, RRD_ID_LENGTH_MAX);
1564 -
1565 - char context[RRD_ID_LENGTH_MAX + 1];
1566 - snprintfz(context, RRD_ID_LENGTH_MAX, "statsd_%s.%s", dim, m->name);
1707 + char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1708 + statsd_get_metric_type_and_id(m, type, id, context, dim, RRD_ID_LENGTH_MAX);
1709
1710 char title[RRD_ID_LENGTH_MAX + 1];
1711 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for %s %s", dim, m->name);
@@ -1573,16 +1715,16 @@ static inline void statsd_private_chart_counter_or_meter(STATSD_METRIC *m, const
1715 , type
1716 , id
1717 , NULL // name
1576 - , family // family (submenu)
1718 + , m->family[0]?m->family:family // family (submenu)
1719 , context // context
1720 , title // title
1579 - , "events/s" // units
1721 + , m->units[0]?m->units:"events/s" // units
1722 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1723 , statsd.update_every
1724 , RRDSET_TYPE_AREA
1725 );
1726
1585 - m->rd_value = rrddim_add(m->st, dim, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1727 + m->rd_value = rrddim_add(m->st, dim, m->dimname[0]?m->dimname:NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1728
1729 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1730 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
@@ -1601,11 +1743,8 @@ static inline void statsd_private_chart_set(STATSD_METRIC *m) {
1743 debug(D_STATSD, "updating private chart for set metric '%s'", m->name);
1744
1745 if(unlikely(!m->st)) {
1604 - char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1];
1605 - statsd_get_metric_type_and_id(m, type, id, "set", RRD_ID_LENGTH_MAX);
1606 -
1607 - char context[RRD_ID_LENGTH_MAX + 1];
1608 - snprintfz(context, RRD_ID_LENGTH_MAX, "statsd_set.%s", m->name);
1746 + char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1747 + statsd_get_metric_type_and_id(m, type, id, context, "set", RRD_ID_LENGTH_MAX);
1748
1749 char title[RRD_ID_LENGTH_MAX + 1];
1750 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for set %s", m->name);
@@ -1615,16 +1754,16 @@ static inline void statsd_private_chart_set(STATSD_METRIC *m) {
1754 , type
1755 , id
1756 , NULL // name
1618 - , "sets" // family (submenu)
1757 + , m->family[0]?m->family:"sets" // family (submenu)
1758 , context // context
1759 , title // title
1621 - , "entries" // units
1760 + , m->units[0]?m->units:"entries" // units
1761 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1762 , statsd.update_every
1763 , RRDSET_TYPE_LINE
1764 );
1765
1627 - m->rd_value = rrddim_add(m->st, "set", "set size", 1, 1, RRD_ALGORITHM_ABSOLUTE);
1766 + m->rd_value = rrddim_add(m->st, "set", m->dimname[0]?m->dimname:"unique", 1, 1, RRD_ALGORITHM_ABSOLUTE);
1767
1768 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1769 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
@@ -1639,15 +1778,53 @@ static inline void statsd_private_chart_set(STATSD_METRIC *m) {
1778 rrdset_done(m->st);
1779 }
1780
1781 +static inline void statsd_private_chart_dictionary(STATSD_METRIC *m) {
1782 + debug(D_STATSD, "updating private chart for dictionary metric '%s'", m->name);
1783 +
1784 + if(unlikely(!m->st)) {
1785 + char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1786 + statsd_get_metric_type_and_id(m, type, id, context, "dictionary", RRD_ID_LENGTH_MAX);
1787 +
1788 + char title[RRD_ID_LENGTH_MAX + 1];
1789 + snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for dictionary %s", m->name);
1790 +
1791 + m->st = statsd_private_rrdset_create(
1792 + m
1793 + , type
1794 + , id
1795 + , NULL // name
1796 + , m->family[0]?m->family:"dictionaries" // family (submenu)
1797 + , context // context
1798 + , title // title
1799 + , m->units[0]?m->units:"events/s" // units
1800 + , NETDATA_CHART_PRIO_STATSD_PRIVATE
1801 + , statsd.update_every
1802 + , RRDSET_TYPE_STACKED
1803 + );
1804 +
1805 + if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1806 + m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1807 + }
1808 + else rrdset_next(m->st);
1809 +
1810 + STATSD_METRIC_DICTIONARY_ITEM *t;
1811 + for(t = m->dictionary.base; t ;t = t->next) {
1812 + if(!t->rd) t->rd = rrddim_add(m->st, t->name, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
1813 + rrddim_set_by_pointer(m->st, t->rd, (collected_number)t->count);
1814 + }
1815 +
1816 + if(m->rd_count)
1817 + rrddim_set_by_pointer(m->st, m->rd_count, m->events);
1818 +
1819 + rrdset_done(m->st);
1820 +}
1821 +
1822 static inline void statsd_private_chart_timer_or_histogram(STATSD_METRIC *m, const char *dim, const char *family, const char *units) {
1823 debug(D_STATSD, "updating private chart for %s metric '%s'", dim, m->name);
1824
1825 if(unlikely(!m->st)) {
1646 - char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1];
1647 - statsd_get_metric_type_and_id(m, type, id, dim, RRD_ID_LENGTH_MAX);
1648 -
1649 - char context[RRD_ID_LENGTH_MAX + 1];
1650 - snprintfz(context, RRD_ID_LENGTH_MAX, "statsd_%s.%s", dim, m->name);
1826 + char type[RRD_ID_LENGTH_MAX + 1], id[RRD_ID_LENGTH_MAX + 1], context[RRD_ID_LENGTH_MAX + 1];
1827 + statsd_get_metric_type_and_id(m, type, id, context, dim, RRD_ID_LENGTH_MAX);
1828
1829 char title[RRD_ID_LENGTH_MAX + 1];
1830 snprintfz(title, RRD_ID_LENGTH_MAX, "statsd private chart for %s %s", dim, m->name);
@@ -1657,10 +1834,10 @@ static inline void statsd_private_chart_timer_or_histogram(STATSD_METRIC *m, con
1834 , type
1835 , id
1836 , NULL // name
1660 - , family // family (submenu)
1837 + , m->family[0]?m->family:family // family (submenu)
1838 , context // context
1839 , title // title
1663 - , units // units
1840 + , m->units[0]?m->units:units // units
1841 , NETDATA_CHART_PRIO_STATSD_PRIVATE
1842 , statsd.update_every
1843 , RRDSET_TYPE_AREA
@@ -1672,7 +1849,7 @@ static inline void statsd_private_chart_timer_or_histogram(STATSD_METRIC *m, con
1849 m->histogram.ext->rd_percentile = rrddim_add(m->st, statsd.histogram_percentile_str, NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1850 m->histogram.ext->rd_median = rrddim_add(m->st, "median", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1851 m->histogram.ext->rd_stddev = rrddim_add(m->st, "stddev", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1675 - m->histogram.ext->rd_sum = rrddim_add(m->st, "sum", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1852 + //m->histogram.ext->rd_sum = rrddim_add(m->st, "sum", NULL, 1, statsd.decimal_detail, RRD_ALGORITHM_ABSOLUTE);
1853
1854 if(m->options & STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT)
1855 m->rd_count = rrddim_add(m->st, "events", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
@@ -1684,7 +1861,7 @@ static inline void statsd_private_chart_timer_or_histogram(STATSD_METRIC *m, con
1861 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_percentile, m->histogram.ext->last_percentile);
1862 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_median, m->histogram.ext->last_median);
1863 rrddim_set_by_pointer(m->st, m->histogram.ext->rd_stddev, m->histogram.ext->last_stddev);
1687 - rrddim_set_by_pointer(m->st, m->histogram.ext->rd_sum, m->histogram.ext->last_sum);
1864 + //rrddim_set_by_pointer(m->st, m->histogram.ext->rd_sum, m->histogram.ext->last_sum);
1865 rrddim_set_by_pointer(m->st, m->rd_value, m->last);
1866
1867 if(m->rd_count)
@@ -1752,6 +1929,34 @@ static inline void statsd_flush_set(STATSD_METRIC *m) {
1929 statsd_private_chart_set(m);
1930 }
1931
1932 +static inline void statsd_flush_dictionary(STATSD_METRIC *m) {
1933 + debug(D_STATSD, "flushing dictionary metric '%s'", m->name);
1934 +
1935 + int updated = 0;
1936 + if(unlikely(!m->reset && m->count)) {
1937 + m->last = (collected_number)m->dictionary.unique;
1938 +
1939 + m->reset = 1;
1940 + updated = 1;
1941 + }
1942 + else {
1943 + m->last = 0;
1944 + }
1945 +
1946 + if(unlikely(m->options & STATSD_METRIC_OPTION_PRIVATE_CHART_ENABLED && (updated || !(m->options & STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED))))
1947 + statsd_private_chart_dictionary(m);
1948 +
1949 + if(m->dictionary.unique >= statsd.dictionary_max_unique) {
1950 + if(!(m->dictionary.flags & STATSD_METRIC_DICTIONARY_FLAGS_DICTFULL_LOGGED)) {
1951 + m->dictionary.flags |= STATSD_METRIC_DICTIONARY_FLAGS_DICTFULL_LOGGED;
1952 + info(
1953 + "STATSD dictionary '%s' reach max of %zu items - try increasing 'dictionaries max unique dimensions' in netdata.conf",
1954 + m->name,
1955 + m->dictionary.unique);
1956 + }
1957 + }
1958 +}
1959 +
1960 static inline void statsd_flush_timer_or_histogram(STATSD_METRIC *m, const char *dim, const char *family, const char *units) {
1961 debug(D_STATSD, "flushing %s metric '%s'", dim, m->name);
1962
@@ -1824,6 +2029,7 @@ static inline RRD_ALGORITHM statsd_algorithm_for_metric(STATSD_METRIC *m) {
2029
2030 case STATSD_METRIC_TYPE_METER:
2031 case STATSD_METRIC_TYPE_COUNTER:
2032 + case STATSD_METRIC_TYPE_DICTIONARY:
2033 return RRD_ALGORITHM_INCREMENTAL;
2034 }
2035 }
@@ -2090,6 +2296,7 @@ const char *statsd_metric_type_string(STATSD_METRIC_TYPE type) {
2296 case STATSD_METRIC_TYPE_HISTOGRAM: return "histogram";
2297 case STATSD_METRIC_TYPE_METER: return "meter";
2298 case STATSD_METRIC_TYPE_SET: return "set";
2299 + case STATSD_METRIC_TYPE_DICTIONARY: return "dictionary";
2300 case STATSD_METRIC_TYPE_TIMER: return "timer";
2301 default: return "unknown";
2302 }
@@ -2188,10 +2395,11 @@ static void statsd_main_cleanup(void *data) {
2395 #define WORKER_STATSD_FLUSH_TIMERS 3
2396 #define WORKER_STATSD_FLUSH_HISTOGRAMS 4
2397 #define WORKER_STATSD_FLUSH_SETS 5
2191 -#define WORKER_STATSD_FLUSH_STATS 6
2398 +#define WORKER_STATSD_FLUSH_DICTIONARIES 6
2399 +#define WORKER_STATSD_FLUSH_STATS 7
2400
2193 -#if WORKER_UTILIZATION_MAX_JOB_TYPES < 7
2194 -#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 6
2401 +#if WORKER_UTILIZATION_MAX_JOB_TYPES < 8
2402 +#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 8
2403 #endif
2404
2405 void *statsd_main(void *ptr) {
@@ -2202,6 +2410,7 @@ void *statsd_main(void *ptr) {
2410 worker_register_job_name(WORKER_STATSD_FLUSH_TIMERS, "timers");
2411 worker_register_job_name(WORKER_STATSD_FLUSH_HISTOGRAMS, "histograms");
2412 worker_register_job_name(WORKER_STATSD_FLUSH_SETS, "sets");
2413 + worker_register_job_name(WORKER_STATSD_FLUSH_DICTIONARIES, "dictionaries");
2414 worker_register_job_name(WORKER_STATSD_FLUSH_STATS, "statistics");
2415
2416 netdata_thread_cleanup_push(statsd_main_cleanup, ptr);
@@ -2242,13 +2451,16 @@ void *statsd_main(void *ptr) {
2451 statsd.histogram_percentile_str = strdupz(buffer);
2452 }
2453
2245 - if(config_get_boolean(CONFIG_SECTION_STATSD, "add dimension for number of events received", 1)) {
2454 + statsd.dictionary_max_unique = config_get_number(CONFIG_SECTION_STATSD, "dictionaries max unique dimensions", statsd.dictionary_max_unique);
2455 +
2456 + if(config_get_boolean(CONFIG_SECTION_STATSD, "add dimension for number of events received", 0)) {
2457 statsd.gauges.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2458 statsd.counters.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2459 statsd.meters.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2460 statsd.sets.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2461 statsd.histograms.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2462 statsd.timers.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2463 + statsd.dictionaries.default_options |= STATSD_METRIC_OPTION_CHART_DIMENSION_COUNT;
2464 }
2465
2466 if(config_get_boolean(CONFIG_SECTION_STATSD, "gaps on gauges (deleteGauges)", 0))
@@ -2269,6 +2481,9 @@ void *statsd_main(void *ptr) {
2481 if(config_get_boolean(CONFIG_SECTION_STATSD, "gaps on timers (deleteTimers)", 0))
2482 statsd.timers.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2483
2484 + if(config_get_boolean(CONFIG_SECTION_STATSD, "gaps on dictionaries (deleteDictionaries)", 0))
2485 + statsd.dictionaries.default_options |= STATSD_METRIC_OPTION_SHOW_GAPS_WHEN_NOT_COLLECTED;
2486 +
2487 size_t max_sockets = (size_t)config_get_number(CONFIG_SECTION_STATSD, "statsd server max TCP sockets", (long long int)(rlimit_nofile.rlim_cur / 4));
2488
2489 #ifdef STATSD_MULTITHREADED
@@ -2329,6 +2544,7 @@ void *statsd_main(void *ptr) {
2544 RRDDIM *rd_metrics_meter = rrddim_add(st_metrics, "meters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2545 RRDDIM *rd_metrics_histogram = rrddim_add(st_metrics, "histograms", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2546 RRDDIM *rd_metrics_set = rrddim_add(st_metrics, "sets", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2547 + RRDDIM *rd_metrics_dictionary= rrddim_add(st_metrics, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2548
2549 RRDSET *st_useful_metrics = rrdset_create_localhost(
2550 "netdata"
@@ -2350,6 +2566,7 @@ void *statsd_main(void *ptr) {
2566 RRDDIM *rd_useful_metrics_meter = rrddim_add(st_useful_metrics, "meters", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2567 RRDDIM *rd_useful_metrics_histogram = rrddim_add(st_useful_metrics, "histograms", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2568 RRDDIM *rd_useful_metrics_set = rrddim_add(st_useful_metrics, "sets", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2569 + RRDDIM *rd_useful_metrics_dictionary= rrddim_add(st_useful_metrics, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
2570
2571 RRDSET *st_events = rrdset_create_localhost(
2572 "netdata"
@@ -2371,6 +2588,7 @@ void *statsd_main(void *ptr) {
2588 RRDDIM *rd_events_meter = rrddim_add(st_events, "meters", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2589 RRDDIM *rd_events_histogram = rrddim_add(st_events, "histograms", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2590 RRDDIM *rd_events_set = rrddim_add(st_events, "sets", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2591 + RRDDIM *rd_events_dictionary= rrddim_add(st_events, "dictionaries", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2592 RRDDIM *rd_events_unknown = rrddim_add(st_events, "unknown", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2593 RRDDIM *rd_events_errors = rrddim_add(st_events, "errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
2594
@@ -2502,6 +2720,9 @@ void *statsd_main(void *ptr) {
2720 worker_is_busy(WORKER_STATSD_FLUSH_SETS);
2721 statsd_flush_index_metrics(&statsd.sets, statsd_flush_set);
2722
2723 + worker_is_busy(WORKER_STATSD_FLUSH_DICTIONARIES);
2724 + statsd_flush_index_metrics(&statsd.dictionaries,statsd_flush_dictionary);
2725 +
2726 worker_is_busy(WORKER_STATSD_FLUSH_STATS);
2727 statsd_update_all_app_charts();
2728
@@ -2526,6 +2747,7 @@ void *statsd_main(void *ptr) {
2747 rrddim_set_by_pointer(st_metrics, rd_metrics_meter, (collected_number)statsd.meters.metrics);
2748 rrddim_set_by_pointer(st_metrics, rd_metrics_histogram, (collected_number)statsd.histograms.metrics);
2749 rrddim_set_by_pointer(st_metrics, rd_metrics_set, (collected_number)statsd.sets.metrics);
2750 + rrddim_set_by_pointer(st_metrics, rd_metrics_dictionary, (collected_number)statsd.dictionaries.metrics);
2751 rrdset_done(st_metrics);
2752
2753 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_gauge, (collected_number)statsd.gauges.useful);
@@ -2534,6 +2756,7 @@ void *statsd_main(void *ptr) {
2756 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_meter, (collected_number)statsd.meters.useful);
2757 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_histogram, (collected_number)statsd.histograms.useful);
2758 rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_set, (collected_number)statsd.sets.useful);
2759 + rrddim_set_by_pointer(st_useful_metrics, rd_useful_metrics_dictionary, (collected_number)statsd.dictionaries.useful);
2760 rrdset_done(st_useful_metrics);
2761
2762 rrddim_set_by_pointer(st_events, rd_events_gauge, (collected_number)statsd.gauges.events);
@@ -2542,6 +2765,7 @@ void *statsd_main(void *ptr) {
2765 rrddim_set_by_pointer(st_events, rd_events_meter, (collected_number)statsd.meters.events);
2766 rrddim_set_by_pointer(st_events, rd_events_histogram, (collected_number)statsd.histograms.events);
2767 rrddim_set_by_pointer(st_events, rd_events_set, (collected_number)statsd.sets.events);
2768 + rrddim_set_by_pointer(st_events, rd_events_dictionary, (collected_number)statsd.dictionaries.events);
2769 rrddim_set_by_pointer(st_events, rd_events_unknown, (collected_number)statsd.unknown_types);
2770 rrddim_set_by_pointer(st_events, rd_events_errors, (collected_number)statsd.socket_errors);
2771 rrdset_done(st_events);
libnetdata/dictionary/dictionary.c
+3 -2
@@ -170,7 +170,7 @@ void dictionary_destroy(DICTIONARY *dict) {
170
171 // ----------------------------------------------------------------------------
172
173 -void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
173 +void *dictionary_set_with_name_ptr(DICTIONARY *dict, const char *name, void *value, size_t value_len, char **name_ptr) {
174 debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
175
176 uint32_t hash = simple_hash(name);
@@ -185,7 +185,7 @@ void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t val
185 if(unlikely(!nv))
186 fatal("Cannot create name_value.");
187 }
188 - else {
188 + else if(!(dict->flags & DICTIONARY_FLAG_DONT_OVERWRITE_VALUE)) {
189 debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name);
190
191 if(dict->flags & DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE) {
@@ -210,6 +210,7 @@ void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t val
210
211 dictionary_unlock(dict);
212
213 + if(name_ptr) *name_ptr = nv->name;
214 return nv->value;
215 }
216
libnetdata/dictionary/dictionary.h
+4 -2
@@ -31,15 +31,17 @@ typedef struct dictionary {
31 netdata_rwlock_t *rwlock;
32 } DICTIONARY;
33
34 -#define DICTIONARY_FLAG_DEFAULT 0x00000000
34 +#define DICTIONARY_FLAG_NONE 0x00000000
35 #define DICTIONARY_FLAG_SINGLE_THREADED 0x00000001
36 #define DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE 0x00000002
37 #define DICTIONARY_FLAG_NAME_LINK_DONT_CLONE 0x00000004
38 #define DICTIONARY_FLAG_WITH_STATISTICS 0x00000008
39 +#define DICTIONARY_FLAG_DONT_OVERWRITE_VALUE 0x00000010
40
41 extern DICTIONARY *dictionary_create(uint8_t flags);
42 extern void dictionary_destroy(DICTIONARY *dict);
42 -extern void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) NEVERNULL;
43 +extern void *dictionary_set_with_name_ptr(DICTIONARY *dict, const char *name, void *value, size_t value_len, char **name_ptr) NEVERNULL;
44 +#define dictionary_set(dict, name, value, value_len) dictionary_set_with_name_ptr(dict, name, value, value_len, NULL)
45 extern void *dictionary_get(DICTIONARY *dict, const char *name);
46 extern int dictionary_del(DICTIONARY *dict, const char *name);
47
libnetdata/libnetdata.c
+1 -1
@@ -577,7 +577,7 @@ unsigned char netdata_map_chart_ids[256] = {
577 [89] = 'y', // Y
578 [90] = 'z', // Z
579 [91] = '_', // [
580 - [92] = '/', // backslash
580 + [92] = '_', // backslash
581 [93] = '_', // ]
582 [94] = '_', // ^
583 [95] = '_', // _
web/gui/main.js
-1
@@ -1370,7 +1370,6 @@ function enrichChartData(chart) {
1370 case 'net':
1371 case 'disk':
1372 case 'powersupply':
1373 - case 'statsd':
1373 chart.menu = tmp;
1374 break;
1375