Browse code

Merge pull request #2435 from kamailio/vhernando/xhttp_prom_v2

xhttp_prom: add support for histogram metrics.

vhernando authored on 10/08/2020 21:15:55 • GitHub committed on 10/08/2020 21:15:55
Showing 7 changed files
... ...
@@ -23,7 +23,7 @@
23 23
 	</para>
24 24
 	<para>
25 25
 	  The module generates metrics based on &kamailio; statistics, and also the user
26
-	  can create his own metrics (currently counters and gauges).
26
+	  can create his own metrics (currently counters, gauges and histograms).
27 27
 	</para>
28 28
 	<para>
29 29
 	  The xHTTP_PROM module uses the xHTTP module to handle HTTP requests.
... ...
@@ -112,6 +112,10 @@ modparam("xhttp_prom", "xhttp_prom_buf_size", 1024)
112 112
 		Specifies a timeout in minutes. A metric not used during this timeout is
113 113
 		automatically deleted. Listing metrics does not count as using them.
114 114
 	  </para>
115
+	  <para>
116
+		<emphasis>If set to 0 timeout is disabled.</emphasis>
117
+ 		Negative values are not allowed.
118
+	  </para>
115 119
 	  <para>
116 120
 		<emphasis>
117 121
 		  Default value is 60 minutes.
... ...
@@ -151,6 +155,14 @@ modparam("xhttp_prom", "xhttp_prom_timeout", 600)
151 155
 		  Default value is "", meaning do not display any &kamailio; statistics.
152 156
 		</emphasis>
153 157
 	  </para>
158
+	  <para>
159
+		<emphasis>
160
+		  IMPORTANT: &kamailio; internal statistics are parsed to convert - into _, so they
161
+		  accomplish with Prometheus guidelines for metric names.
162
+		  <ulink url="https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels">https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels</ulink>
163
+		</emphasis>
164
+		User generated statistics and label names are not parsed.
165
+	  </para>
154 166
 	  <example>
155 167
 		<title>Set <varname>xhttp_prom_stats</varname> parameter</title>
156 168
 		<programlisting format="linespecific">
... ...
@@ -166,6 +178,32 @@ modparam("xhttp_prom", "xhttp_prom_stats", "200_replies")
166 178
 
167 179
 # Do not display internal &kamailio; statistics. This is the default option.
168 180
 modparam("xhttp_prom", "xhttp_prom_stats", "")
181
+...
182
+		</programlisting>
183
+	  </example>
184
+	</section>
185
+	<section id="xhttp_prom.p.xhttp_prom_beginning">
186
+	  <title><varname>xhttp_prom_beginning</varname> (str)</title>
187
+	  <para>
188
+		Specifies beginning of string the metrics are build with.
189
+	  </para>
190
+	  <para>
191
+		<emphasis>It defaults to "kamailio_"</emphasis>, so if not specified every
192
+		metric will start with "kamailio_".
193
+	  </para>
194
+	  <para>
195
+		Void string "" is also allowed, meaning no prefix string for every metric
196
+		name.
197
+	  </para>
198
+	  <example>
199
+		<title>Set <varname>xhttp_prom_beginning</varname> parameter</title>
200
+		<programlisting format="linespecific">
201
+...
202
+# All metrics will start with "my_metric_".
203
+modparam("xhttp_prom", "xhttp_prom_beginning", "my_metric_")
204
+
205
+# No string at the beginning.
206
+modparam("xhttp_prom", "xhttp_prom_beginning", "");
169 207
 ...
170 208
 		</programlisting>
171 209
 	  </example>
... ...
@@ -283,6 +321,86 @@ modparam("xhttp_prom", "prom_gauge", "name=gg_second;");
283 321
 # Create gg_third gauge with two labels method and handler:
284 322
 modparam("xhttp_prom", "prom_gauge", "name=gg_third; label=method:handler;");
285 323
 
324
+...
325
+		</programlisting>
326
+	  </example>
327
+	</section>
328
+	<section id="xhttp_prom.p.prom_histogram">
329
+	  <title><varname>prom_histogram</varname> (str)</title>
330
+	  <para>
331
+		Create a histogram metric.
332
+	  </para>
333
+	  <para>
334
+		This function declares a histogram but the actual histogram is only created
335
+		when observing it.
336
+	  </para>
337
+	  <para>
338
+		It takes a list of attribute=value separated by semicolon, the attributes can
339
+		be name, label and buckets.
340
+	  </para>
341
+	  <itemizedlist>
342
+		<listitem>
343
+		  <para>
344
+			<emphasis>name</emphasis> - name of the histogram. This attribute is mandatory.
345
+			It is used to generate the metric name. Each name is unique, no metric shall
346
+			repeat a name.
347
+		  </para>
348
+		</listitem>
349
+		<listitem>
350
+		  <para>
351
+			<emphasis>label</emphasis> - names of labels in the histogram. Optional.
352
+			Only one label parameter at most allowed in histograms.
353
+			Each label name is separated by <emphasis>:</emphasis> without spaces.
354
+			At most only three label names allowed in each label parameter.
355
+			<example><title><varname>prom_histogram</varname> label example</title>
356
+			<programlisting format="linespecific">
357
+# Create two labels called method and handler
358
+label = method:handler
359
+This would generate  {method="whatever", handler="whatever2"} when building
360
+the metric.
361
+			</programlisting>
362
+			</example>
363
+		  </para>
364
+		</listitem>
365
+		<listitem>
366
+		  <para>
367
+			<emphasis>buckets</emphasis> - specifies upper bounds for buckets in the
368
+			histogram. This attribute is optional.
369
+		  </para>
370
+		  <para>
371
+			Bucket values are separated by ":". Each value has to be a number.
372
+		  </para>
373
+		  <para>
374
+			"+Inf" upper bucket is always automatically included.
375
+		  </para>
376
+		  <para>At least one bucket value is needed (other than +Inf).</para>
377
+		  <para>Every bucket value has to increase in the list.</para>
378
+		  <para>
379
+			If no buckets specified, default bucket list is set to these values:
380
+			<para>[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]</para>
381
+		  </para>
382
+		</listitem>
383
+	  </itemizedlist>
384
+	  <example>
385
+		<title>Set <varname>prom_histogram</varname> parameter</title>
386
+		<programlisting format="linespecific">
387
+...
388
+		  
389
+# Create my_histo histogram with no labels and default buckets.
390
+modparam("xhttp_prom", "prom_histogram", "name=my_histo;");
391
+
392
+# Create second_histo histogram with one label and default buckets.
393
+# modparam("xhttp_prom", "prom_histogram", "name=second_histo; label=my_lbl");
394
+
395
+# Create a histogram with no labels and buckets 3.1, 5, 6.5
396
+modparam("xhttp_prom", "prom_histogram", "name=histo_third; buckets=3.1:5:6.5");
397
+
398
+# Create a histogram with a label and buckets 3.1, 5, 6.5
399
+modparam("xhttp_prom", "prom_histogram", "name=histo_fourth; label=lbl; buckets=3.1:5:6.5");
400
+
401
+These lines declare the histogram but the actual metric will be created when
402
+using it by prom_histogram_observe function.
403
+
286 404
 ...
287 405
 		</programlisting>
288 406
 	  </example>
... ...
@@ -454,6 +572,60 @@ modparam("xhttp_prom", "prom_gauge", "name=cnt02; label=method:IP;");
454 572
 prom_gauge_set("gg02", "2.8", "push", "192.168.0.1");
455 573
 # When listed the metric it will show a line like this:
456 574
 kamailio_gg02 {method="push", IP="192.168.0.1"} 2.8 1234567890
575
+...
576
+		</programlisting>
577
+	  </example>
578
+	</section>
579
+	<section id="xhttp_prom.f.prom_histogram_observe">
580
+	  <title>
581
+		<function moreinfo="none">prom_histogram_observe(name, number, l0, l1, l2)</function>
582
+	  </title>
583
+	  <para>
584
+		Get a histogram identified by its name and labels and observe a value in it.
585
+		If histogram does not exist it creates the histogram and accumulate the value in its
586
+		buckets, counter and sum.
587
+	  </para>
588
+	  <para>
589
+		Name is mandatory, number is mandatory.
590
+		Number is a string that will be parsed as a float.
591
+		l0, l1, l2 are values of labels and are optional.
592
+	  </para>
593
+	  <para>
594
+		name value and number of labels have to match a previous histogram definition with prom_histogram.
595
+	  </para>
596
+	  <para>
597
+		This function accepts pseudovariables on its parameters.
598
+	  </para>
599
+	  <para>
600
+		Available via KEMI framework as <emphasis>histogram_observe_l0</emphasis>,
601
+		<emphasis>histogram_observe_l1</emphasis>,
602
+		<emphasis>histogram_observe_l2</emphasis> and
603
+		<emphasis>histogram_observe_l3</emphasis>.
604
+	  </para>
605
+	  <example>
606
+		<title><function>prom_histogram_observe</function> usage</title>
607
+		<programlisting format="linespecific">
608
+...
609
+# Definition of hist01 histogram with no labels and default buckets.
610
+modparam("xhttp_prom", "prom_histogram", "name=hist01;");
611
+...
612
+# Observe -12.5 value in hist01 histogram (with no labels). If histogram does not exist it gets created:
613
+prom_histogram_observe("hist01", "-12.5");
614
+...
615
+
616
+# Definition of hist02 histogram with two labels method and IP and buckets [2.3, 5.8, +Inf]:
617
+modparam("xhttp_prom", "prom_histogram", "name=hist02; label=method:IP; buckets=2.3:5.8");
618
+...
619
+# Observe 2.8 value in hist02 histogram with labels method and IP.
620
+# It creates the histogram if it does not exist.
621
+prom_histogram_observe("hist02", "2.8", "push", "192.168.0.1");
622
+# When listed the metric it will show lines like this:
623
+hist02_bucket{method="push", IP="192.168.0.1", le="2.300000"} 0 1592574659768
624
+hist02_bucket{method="push", IP="192.168.0.1", le="5.800000"} 1 1592574659768
625
+hist02_bucket{method="push", IP="192.168.0.1", le="+Inf"} 1 1592574659768
626
+hist02_sum{method="push", IP="192.168.0.1"} 2.800000 1592574659768
627
+hist02_count{method="push", IP="192.168.0.1"} 1 1592574659768
628
+
457 629
 ...
458 630
 		</programlisting>
459 631
 	  </example>
... ...
@@ -705,6 +877,31 @@ event_route[xhttp:request] {
705 877
 		  ...
706 878
 		</programlisting>
707 879
 	  </example>
880
+    </section>
881
+	<section  id="xhttp_prom.rpc.histogram_observe">
882
+	  <title><function moreinfo="none">xhttp_prom.histogram_observe</function></title>
883
+	  <para>
884
+		Observe a number in a histogram. Select the histogram by its name and labels.
885
+	  </para>
886
+      <para>
887
+        Name: <emphasis>xhttp_prom.histogram_observe</emphasis>
888
+      </para>
889
+      <para>Parameters:</para>
890
+	  <itemizedlist>
891
+		<listitem><para><emphasis>name</emphasis>: name of the histogram (mandatory)</para></listitem>
892
+		<listitem><para><emphasis>number</emphasis>: float value to observe in the histogram (mandatory)</para></listitem>
893
+		<listitem><para><emphasis>l0</emphasis>: value of the first label (optional)</para></listitem>
894
+		<listitem><para><emphasis>l1</emphasis>: value of second label (optional)</para></listitem>
895
+		<listitem><para><emphasis>l2</emphasis>: value of the third label (optional)</para></listitem>
896
+	  </itemizedlist>
897
+	  <example>
898
+		<title><function>xhttp_prom.histogram_observe</function> usage</title>
899
+		<programlisting format="linespecific">
900
+		  ...
901
+		  &kamcmd; xhttp_prom.histogram_observe "hist01" -- -5.2
902
+		  ...
903
+		</programlisting>
904
+	  </example>
708 905
     </section>
709 906
 	<section  id="xhttp_prom.rpc.metric_list_print">
710 907
 	  <title><function moreinfo="none">xhttp_prom.metric_list_print</function></title>
... ...
@@ -715,6 +912,10 @@ event_route[xhttp:request] {
715 912
         Name: <emphasis>xhttp_prom.metric_list_print</emphasis>
716 913
       </para>
717 914
       <para>Parameters:<emphasis>none</emphasis></para>
915
+	  <para>
916
+		<emphasis>NOTE:</emphasis>: If you list a lot of metrics you may need to increase
917
+		buffer size of your RPC transport layer.
918
+	  </para>
718 919
 	  <example>
719 920
 		<title><function>xhttp_prom.metric_list_print</function> usage</title>
720 921
 		<programlisting format="linespecific">
... ...
@@ -22,7 +22,10 @@
22 22
  */
23 23
 
24 24
 /**
25
- * Functionality of prometheus module.
25
+ * @file
26
+ * @brief xHTTP_PROM :: Functionality of xhttp_prom module.
27
+ * @ingroup xhttp_prom
28
+ * - Module: @ref xhttp_prom
26 29
  */
27 30
 
28 31
 #include <string.h>
... ...
@@ -35,9 +38,10 @@
35 38
 
36 39
 #include "prom.h"
37 40
 #include "prom_metric.h"
41
+#include "xhttp_prom.h"
38 42
 
39 43
 /**
40
- * Delete current buffer data.
44
+ * @brief Delete current buffer data.
41 45
  */
42 46
 void prom_body_delete(prom_ctx_t *ctx)
43 47
 {
... ...
@@ -45,10 +49,10 @@ void prom_body_delete(prom_ctx_t *ctx)
45 49
 }
46 50
 
47 51
 /**
48
- * Write some data in prom_body buffer.
52
+ * @brief Write some data in prom_body buffer.
49 53
  *
50
- * /return number of bytes written.
51
- * /return -1 on error.
54
+ * @return number of bytes written.
55
+ * @return -1 on error.
52 56
  */
53 57
 int prom_body_printf(prom_ctx_t *ctx, char *fmt, ...)
54 58
 {
... ...
@@ -87,10 +91,63 @@ error:
87 91
 }
88 92
 
89 93
 /**
90
- * Get current timestamp in milliseconds.
94
+ * @brief Write a metric name in prom_body buffer.
91 95
  *
92
- * /param ts pointer to timestamp integer.
93
- * /return 0 on success.
96
+ * Based on prom_body_printf function.
97
+ *
98
+ * @return number of bytes written.
99
+ * @return -1 on error.
100
+ */
101
+int prom_body_name_printf(prom_ctx_t *ctx, char *fmt, ...)
102
+{
103
+	struct xhttp_prom_reply *reply = &ctx->reply;
104
+	
105
+	va_list ap;
106
+	
107
+	va_start(ap, fmt);
108
+
109
+	LM_DBG("Body current length: %d\n", reply->body.len);
110
+
111
+	char *p = reply->buf.s + reply->body.len;
112
+	int remaining_len = reply->buf.len - reply->body.len;
113
+	LM_DBG("Remaining length: %d\n", remaining_len);
114
+
115
+	/* int vsnprintf(char *str, size_t size, const char *format, va_list ap); */
116
+	int len = vsnprintf(p, remaining_len, fmt, ap);
117
+	if (len < 0) {
118
+		LM_ERR("Error printing body buffer\n");
119
+		goto error;
120
+	} else if (len >= remaining_len) {
121
+		LM_ERR("Error body buffer overflow: %d (%d)\n", len, remaining_len);
122
+		goto error;
123
+	} else {
124
+		/* Buffer printed OK. */
125
+
126
+		/* Change - into _ to accomplish with Prometheus guidelines for metric names */
127
+		int i;
128
+		for (i=0; i<len; i++) {
129
+			if (p[i] == '-') {
130
+				p[i] = '_';
131
+			}
132
+		}
133
+		
134
+		reply->body.len += len;
135
+		LM_DBG("Body new length: %d\n", reply->body.len);
136
+	}
137
+
138
+	va_end(ap);
139
+	return len;
140
+
141
+error:
142
+	va_end(ap);
143
+	return -1;
144
+}
145
+
146
+/**
147
+ * @brief Get current timestamp in milliseconds.
148
+ *
149
+ * @param ts pointer to timestamp integer.
150
+ * @return 0 on success.
94 151
  */
95 152
 int get_timestamp(uint64_t *ts)
96 153
 {
... ...
@@ -109,9 +166,9 @@ int get_timestamp(uint64_t *ts)
109 166
 }
110 167
 
111 168
 /**
112
- * Generate a string suitable for a Prometheus metric.
169
+ * @brief Generate a string suitable for a Prometheus metric.
113 170
  *
114
- * /return 0 on success.
171
+ * @return 0 on success.
115 172
  */
116 173
 static int metric_generate(prom_ctx_t *ctx, str *group, str *name, counter_handle_t *h)
117 174
 {
... ...
@@ -123,17 +180,20 @@ static int metric_generate(prom_ctx_t *ctx, str *group, str *name, counter_handl
123 180
 		LM_ERR("Error getting current timestamp\n");
124 181
 		return -1;
125 182
 	}
126
-	LM_DBG("Timestamp: %" PRIu64 "\n", ts);
127 183
 
128
-	
129
-	/* LM_DBG("%.*s:%.*s = %lu\n",
130
-	   group->len, group->s, name->len, name->s, counter_val); */
131
-	LM_DBG("kamailio_%.*s_%.*s %lu %" PRIu64 "\n",
184
+	LM_DBG("metric -> group: %.*s  name: %.*s  value: %lu  TS: %" PRIu64 "\n",
132 185
 		   group->len, group->s, name->len, name->s,
133 186
 		   counter_val, (uint64_t)ts);
134 187
 
135
-	if (prom_body_printf(ctx, "kamailio_%.*s_%.*s %lu %" PRIu64 "\n",
136
-						 group->len, group->s, name->len, name->s,
188
+	/* Print metric name. */
189
+	if (prom_body_name_printf(ctx, "%.*s%.*s_%.*s",
190
+						 xhttp_prom_beginning.len, xhttp_prom_beginning.s,
191
+						 group->len, group->s, name->len, name->s) == -1) {
192
+		LM_ERR("Fail to print\n");
193
+		return -1;
194
+	}
195
+						 
196
+	if (prom_body_printf(ctx, " %lu %" PRIu64 "\n",
137 197
 						 counter_val, (uint64_t)ts) == -1) {
138 198
 		LM_ERR("Fail to print\n");
139 199
 		return -1;
... ...
@@ -143,7 +203,7 @@ static int metric_generate(prom_ctx_t *ctx, str *group, str *name, counter_handl
143 203
 }
144 204
 
145 205
 /**
146
- * Statistic getter callback.
206
+ * @brief Statistic getter callback.
147 207
  */
148 208
 static void prom_get_grp_vars_cbk(void* p, str* g, str* n, counter_handle_t h)
149 209
 {
... ...
@@ -151,7 +211,7 @@ static void prom_get_grp_vars_cbk(void* p, str* g, str* n, counter_handle_t h)
151 211
 }
152 212
 
153 213
 /**
154
- * Group statistic getter callback.
214
+ * @brief Group statistic getter callback.
155 215
  */
156 216
 static void prom_get_all_grps_cbk(void* p, str* g)
157 217
 {
... ...
@@ -161,9 +221,9 @@ static void prom_get_all_grps_cbk(void* p, str* g)
161 221
 #define STATS_MAX_LEN 1024
162 222
 
163 223
 /**
164
- * Get statistics (based on stats_get_all)
224
+ * @brief Get statistics (based on stats_get_all)
165 225
  *
166
- * /return 0 on success
226
+ * @return 0 on success
167 227
  */
168 228
 int prom_stats_get(prom_ctx_t *ctx, str *stat)
169 229
 {
... ...
@@ -22,7 +22,10 @@
22 22
  */
23 23
 
24 24
 /**
25
- * Header for functionality of prometheus module.
25
+ * @file
26
+ * @brief xHTTP_PROM :: Header for functionality of xhttp_prom module.
27
+ * @ingroup xhttp_prom
28
+ * - Module: @ref xhttp_prom
26 29
  */
27 30
 
28 31
 #ifndef _PROM_H_
... ...
@@ -31,25 +34,25 @@
31 34
 #include "xhttp_prom.h"
32 35
 
33 36
 /**
34
- * Get current timestamp in milliseconds.
37
+ * @brief Get current timestamp in milliseconds.
35 38
  *
36
- * /param ts pointer to timestamp integer.
37
- * /return 0 on success.
39
+ * @param ts pointer to timestamp integer.
40
+ * @return 0 on success.
38 41
  */
39 42
 int get_timestamp(uint64_t *ts);
40 43
 
41 44
 /**
42
- * Write some data in prom_body buffer.
45
+ * @brief Write some data in prom_body buffer.
43 46
  *
44
- * /return number of bytes written.
45
- * /return -1 on error.
47
+ * @return number of bytes written.
48
+ * @return -1 on error.
46 49
  */
47 50
 int prom_body_printf(prom_ctx_t *ctx, char *fmt, ...);
48 51
 
49 52
 /**
50
- * Get statistics (based on stats_get_all)
53
+ * @brief Get statistics (based on stats_get_all)
51 54
  *
52
- * /return 0 on success
55
+ * @return 0 on success
53 56
  */
54 57
 int prom_stats_get(prom_ctx_t *ctx, str *stat);
55 58
 
... ...
@@ -25,26 +25,38 @@
25 25
 #include <stdint.h>
26 26
 #include <inttypes.h>
27 27
 #include <string.h>
28
+#include <stdlib.h>
28 29
 
29 30
 #include "../../core/mem/shm_mem.h"
30 31
 #include "../../core/locking.h"
31 32
 #include "../../core/ut.h"
32 33
 #include "../../core/parser/parse_param.h"
33 34
 
35
+#include "xhttp_prom.h"
34 36
 #include "prom_metric.h"
35 37
 #include "prom.h"
36 38
 
37
-/* TODO: Every internal function locks and unlocks the metric system. */
39
+/**
40
+ * @file
41
+ * @brief xHTTP_PROM :: User defined metrics for Prometheus.
42
+ * @ingroup xhttp_prom
43
+ * - Module: @ref xhttp_prom
44
+ */
38 45
 
46
+/* Every internal function locks and unlocks the metric system. */
47
+
48
+/**
49
+ * @brief enumeration of metric types.
50
+ */
39 51
 typedef enum metric_type {
40 52
 	M_UNSET = 0,
41 53
 	M_COUNTER = 1,
42
-	M_GAUGE = 2
43
-	/* TODO: Add more types. */
54
+	M_GAUGE = 2,
55
+	M_HISTOGRAM = 3
44 56
 } metric_type_t;
45 57
 
46 58
 /**
47
- * Struct to store a string (node of a list)
59
+ * @brief Struct to store a string (node of a list)
48 60
  */
49 61
 typedef struct prom_lb_node_s {
50 62
 	str n;
... ...
@@ -52,56 +64,129 @@ typedef struct prom_lb_node_s {
52 64
 } prom_lb_node_t;
53 65
 
54 66
 /**
55
- * Struct to store a list of strings (labels)
67
+ * @brief Struct to store a list of strings (labels)
56 68
  */
57 69
 typedef struct prom_lb_s {
58
-	int n_elem; /* Number of strings. */
70
+	int n_elem; /**< Number of strings. */
59 71
 	struct prom_lb_node_s *lb;
60
-	/* TODO: Hashes? */
61 72
 } prom_lb_t;
62 73
 
63 74
 /**
64
- * Struct to store a value of a label.
75
+ * @brief Struct to store histogram sum, counters, etc.
76
+ */
77
+typedef struct prom_hist_value_s {
78
+	uint64_t count; /**< Total counter equal Inf */
79
+	double sum; /**< Total sum */
80
+	uint64_t *buckets_count; /**< Array of counters for buckets. */
81
+} prom_hist_value_t;
82
+
83
+typedef struct prom_metric_s prom_metric_t;
84
+
85
+/**
86
+ * @brief Struct to store a value of a label.
65 87
  */
66 88
 typedef struct prom_lvalue_s {
67
-	prom_lb_t lval;
68
-	uint64_t ts; /* timespan. Last time metric was modified. */
89
+	prom_lb_t lval; /**< values for labels in current metric. */
90
+	uint64_t ts; /**< timespan. Last time metric was modified. */
69 91
 	union {
70
-		uint64_t cval;
71
-		double gval;
92
+		uint64_t cval; /**< Counter value. */
93
+		double gval; /**< Gauge value. */
94
+		prom_hist_value_t *hval; /**< Pointer to histogram data. */
72 95
 	} m;
96
+	struct prom_metric_s *metric; /**< Metric associated to current lvalue. */
73 97
 	struct prom_lvalue_s *next;
74 98
 } prom_lvalue_t;
75 99
 
76 100
 /**
77
- * Struct to store a metric.
101
+ * @brief Struct to store number of buckets and their upper values.
78 102
  */
79
-typedef struct prom_metric_s {
80
-	metric_type_t type;
81
-	str name;
82
-	struct prom_lb_s *lb_name; /* Names of labels. */
103
+typedef struct prom_buckets_upper_s {
104
+	int count;                  /**< Number of buckets. */
105
+	double *upper_bounds;       /**< Upper bounds for buckets. */
106
+} prom_buckets_upper_t;
107
+
108
+/**
109
+ * @brief Struct to store a metric.
110
+ */
111
+struct prom_metric_s {
112
+	metric_type_t type; /**< Metric type. */
113
+	str name; /**< Name of the metric. */
114
+	struct prom_lb_s *lb_name; /**< Names of labels. */
115
+	struct prom_buckets_upper_s *buckets_upper; /**< Upper bounds for buckets. */
83 116
 	struct prom_lvalue_s *lval_list;
84 117
 	struct prom_metric_s *next;
85
-} prom_metric_t;
118
+};
86 119
 
87 120
 /**
88
- * Data related to Prometheus metrics.
121
+ * @brief Data related to Prometheus metrics.
89 122
  */
90 123
 static prom_metric_t *prom_metric_list = NULL;
91
-static gen_lock_t *prom_lock = NULL; /* Lock to protect Prometheus metrics. */
92
-static uint64_t lvalue_timeout = 120000; /* Timeout in milliseconds for old lvalue struct. */
124
+static gen_lock_t *prom_lock = NULL; /**< Lock to protect Prometheus metrics. */
125
+static uint64_t lvalue_timeout; /**< Timeout in milliseconds for old lvalue struct. */
93 126
 
94 127
 static void prom_counter_free(prom_metric_t *m_cnt);
95 128
 static void prom_gauge_free(prom_metric_t *m_gg);
129
+static void prom_histogram_free(prom_metric_t *m_hist);
96 130
 static void prom_metric_free(prom_metric_t *metric);
97 131
 static void prom_lb_free(prom_lb_t *prom_lb, int shared_mem);
98 132
 static void prom_lb_node_free(prom_lb_node_t *lb_node, int shared_mem);
99 133
 static int prom_lb_node_add(prom_lb_t *m_lb, char *s, int len, int shared_mem);
100 134
 static void prom_lvalue_free(prom_lvalue_t *plv);
101 135
 static void prom_lvalue_list_free(prom_lvalue_t *plv);
136
+static void prom_histogram_value_free(prom_hist_value_t *phv);
137
+
138
+/**
139
+ * @brief Parse a string and convert to double.
140
+ *
141
+ * @param s_number pointer to number string.
142
+ * @param pnumber double passed as reference.
143
+ *
144
+ * @return 0 on success.
145
+ * On error value pointed by pnumber is undefined.
146
+ */
147
+int double_parse_str(str *s_number, double *pnumber)
148
+{
149
+	char *s = NULL;
150
+	
151
+	if (!s_number || !s_number->s || s_number->len == 0) {
152
+		LM_ERR("Bad s_number to convert to double\n");
153
+		goto error;
154
+	}
155
+
156
+	if (!pnumber) {
157
+		LM_ERR("No double passed by reference\n");
158
+		goto error;
159
+	}
160
+
161
+	/* We generate a zero terminated string. */
162
+
163
+	/* We set last character to zero to get a zero terminated string. */
164
+	int len = s_number->len;
165
+	s = pkg_malloc(len + 1);
166
+	if (!s) {
167
+		PKG_MEM_ERROR;
168
+		goto error;
169
+	}
170
+	memcpy(s, s_number->s, len);
171
+	s[len] = '\0'; /* Zero terminated string. */
172
+
173
+	/* atof function does not check for errors. */
174
+	double num = atof(s);
175
+	LM_DBG("double number (%.*s) -> %f\n", len, s, num);
176
+
177
+	*pnumber = num;
178
+	pkg_free(s);
179
+	return 0;
180
+
181
+error:
182
+	if (s) {
183
+		pkg_free(s);
184
+	}
185
+	return -1;
186
+}
102 187
 
103 188
 /**
104
- * Free list of Prometheus metrics.
189
+ * @brief Free list of Prometheus metrics.
105 190
  */
106 191
 static void prom_metric_list_free()
107 192
 {
... ...
@@ -118,12 +203,12 @@ static void prom_metric_list_free()
118 203
 }
119 204
 
120 205
 /**
121
- * Initialize user defined metrics.
206
+ * @brief Initialize user defined metrics.
122 207
  */
123
-int prom_metric_init(int timeout_minutes)
208
+int prom_metric_init()
124 209
 {
125 210
 	/* Initialize timeout. minutes to milliseconds. */
126
-	if (timeout_minutes < 1) {
211
+	if (timeout_minutes < 0) {
127 212
 		LM_ERR("Invalid timeout: %d\n", timeout_minutes);
128 213
 		return -1;
129 214
 	}
... ...
@@ -149,7 +234,7 @@ int prom_metric_init(int timeout_minutes)
149 234
 }
150 235
 
151 236
 /**
152
- * Close user defined metrics.
237
+ * @brief Close user defined metrics.
153 238
  */
154 239
 void prom_metric_close()
155 240
 {
... ...
@@ -169,7 +254,7 @@ void prom_metric_close()
169 254
 }
170 255
 
171 256
 /**
172
- * Free a metric.
257
+ * @brief Free a metric.
173 258
  */
174 259
 static void prom_metric_free(prom_metric_t *metric)
175 260
 {
... ...
@@ -179,6 +264,8 @@ static void prom_metric_free(prom_metric_t *metric)
179 264
 		prom_counter_free(metric);
180 265
 	} else if (metric->type == M_GAUGE) {
181 266
 		prom_gauge_free(metric);
267
+	} else if (metric->type == M_HISTOGRAM) {
268
+		prom_histogram_free(metric);
182 269
 	} else {
183 270
 		LM_ERR("Unknown metric: %d\n", metric->type);
184 271
 		return;
... ...
@@ -186,7 +273,7 @@ static void prom_metric_free(prom_metric_t *metric)
186 273
 }
187 274
 
188 275
 /**
189
- * Free a counter.
276
+ * @brief Free a counter.
190 277
  */
191 278
 static void prom_counter_free(prom_metric_t *m_cnt)
192 279
 {
... ...
@@ -206,10 +293,10 @@ static void prom_counter_free(prom_metric_t *m_cnt)
206 293
 }
207 294
 
208 295
 /**
209
- * Get a metric based on its name.
296
+ * @brief Get a metric based on its name.
210 297
  *
211
- * /return pointer to metric on success.
212
- * /return NULL on error.
298
+ * @return pointer to metric on success.
299
+ * @return NULL on error.
213 300
  */
214 301
 static prom_metric_t* prom_metric_get(str *s_name)
215 302
 {
... ...
@@ -227,9 +314,9 @@ static prom_metric_t* prom_metric_get(str *s_name)
227 314
 }
228 315
 
229 316
 /**
230
- * Compare prom_lb_t structure using some strings.
317
+ * @brief Compare prom_lb_t structure using some strings.
231 318
  *
232
- * /return 0 if prom_lb_t matches the strings.
319
+ * @return 0 if prom_lb_t matches the strings.
233 320
  */
234 321
 static int prom_lb_compare(prom_lb_t *plb, str *l1, str *l2, str *l3)
235 322
 {
... ...
@@ -292,9 +379,9 @@ static int prom_lb_compare(prom_lb_t *plb, str *l1, str *l2, str *l3)
292 379
 }
293 380
 
294 381
 /**
295
- * Compare two lval structures.
382
+ * @brief Compare two lval structures.
296 383
  *
297
- * /return 0 if they are the same.
384
+ * @return 0 if they are the same.
298 385
  */
299 386
 static int prom_lvalue_compare(prom_lvalue_t *p, str *l1, str *l2, str *l3)
300 387
 {
... ...
@@ -312,7 +399,8 @@ static int prom_lvalue_compare(prom_lvalue_t *p, str *l1, str *l2, str *l3)
312 399
 }
313 400
 
314 401
 /**
315
- * Free an lvalue structure.
402
+ * @brief Free a lvalue structure.
403
+ *
316 404
  * Only defined for shared memory.
317 405
  */
318 406
 static void prom_lvalue_free(prom_lvalue_t *plv)
... ...
@@ -321,6 +409,11 @@ static void prom_lvalue_free(prom_lvalue_t *plv)
321 409
 		return;
322 410
 	}
323 411
 
412
+	/* Free hval member of structure. */
413
+	if (plv->metric->type == M_HISTOGRAM && plv->m.hval) {
414
+		prom_histogram_value_free(plv->m.hval);
415
+	}
416
+	
324 417
 	/* Free list of strings. */
325 418
 	prom_lb_node_t *lb_node = plv->lval.lb;
326 419
 	while (lb_node) {
... ...
@@ -333,7 +426,8 @@ static void prom_lvalue_free(prom_lvalue_t *plv)
333 426
 }
334 427
 
335 428
 /**
336
- * Free a list of lvalue structures.
429
+ * @brief Free a list of lvalue structures.
430
+ *
337 431
  * Only defined for shared memory.
338 432
  */
339 433
 static void prom_lvalue_list_free(prom_lvalue_t *plv)
... ...
@@ -346,10 +440,12 @@ static void prom_lvalue_list_free(prom_lvalue_t *plv)
346 440
 }
347 441
 
348 442
 /**
349
- * Fill lvalue data in prom_lvalue_t structure based on three strings.
443
+ * @brief Fill lvalue data in prom_lvalue_t structure based on three strings.
444
+ *
350 445
  * Only defined for shared memory.
446
+ * It does not fill histogram counters and sum.
351 447
  *
352
- * /return 0 on success.
448
+ * @return 0 on success.
353 449
  */
354 450
 static int prom_lvalue_lb_create(prom_lvalue_t *lv, str *l1, str *l2, str *l3)
355 451
 {
... ...
@@ -398,11 +494,13 @@ static int prom_lvalue_lb_create(prom_lvalue_t *lv, str *l1, str *l2, str *l3)
398 494
 }
399 495
 
400 496
 /**
401
- * Create and insert a lvalue structure into a metric.
497
+ * @brief Create and insert a lvalue structure into a metric.
498
+ *
402 499
  * It only works in shared memory.
500
+ * It does not fill internal histogram counters and sum, only name and labels.
403 501
  *
404
- * /return pointer to newly created structure on success.
405
- * /return NULL on error.
502
+ * @return pointer to newly created structure on success.
503
+ * @return NULL on error.
406 504
  */
407 505
 static prom_lvalue_t* prom_metric_lvalue_create(prom_metric_t *p_m, str *l1, str *l2, str *l3)
408 506
 {
... ...
@@ -419,6 +517,9 @@ static prom_lvalue_t* prom_metric_lvalue_create(prom_metric_t *p_m, str *l1, str
419 517
 	}
420 518
 	memset(plv, 0, sizeof(*plv));
421 519
 
520
+	/* Set link to metric */
521
+	plv->metric = p_m;
522
+	
422 523
 	if (prom_lvalue_lb_create(plv, l1, l2, l3)) {
423 524
 		LM_ERR("Cannot create list of strings\n");
424 525
 		goto error;
... ...
@@ -441,11 +542,12 @@ error:
441 542
 }
442 543
 
443 544
 /**
444
- * Find a lvalue based on its labels.
545
+ * @brief Find a lvalue based on its labels.
546
+ *
445 547
  * If it does not exist it creates a new one and inserts it into the metric.
446 548
  *
447
- * /return pointer to lvalue on success.
448
- * /return NULL on error.
549
+ * @return pointer to lvalue on success.
550
+ * @return NULL on error.
449 551
  */
450 552
 static prom_lvalue_t* prom_lvalue_get_create(prom_metric_t *p_m, str *l1, str *l2, str *l3)
451 553
 {
... ...
@@ -507,7 +609,8 @@ static prom_lvalue_t* prom_lvalue_get_create(prom_metric_t *p_m, str *l1, str *l
507 609
 }
508 610
 
509 611
 /**
510
- * Delete old lvalue structures in a metric.
612
+ * @brief Delete old lvalue structures in a metric.
613
+ *
511 614
  * Only for shared memory.
512 615
  */
513 616
 static void prom_metric_timeout_delete(prom_metric_t *p_m)
... ...
@@ -543,7 +646,7 @@ static void prom_metric_timeout_delete(prom_metric_t *p_m)
543 646
 }
544 647
 
545 648
 /**
546
- * Delete old lvalue structures in list of metrics.
649
+ * @brief Delete old lvalue structures in list of metrics.
547 650
  */
548 651
 static void	prom_metric_list_timeout_delete()
549 652
 {
... ...
@@ -556,11 +659,12 @@ static void	prom_metric_list_timeout_delete()
556 659
 }
557 660
 
558 661
 /**
559
- * Get a lvalue based on its metric name and labels.
662
+ * @brief Get a lvalue based on its metric name and labels.
663
+ *
560 664
  * If metric name exists but no lvalue matches it creates a new lvalue.
561 665
  *
562
- * /return NULL if no lvalue was found or created.
563
- * /return pointer to lvalue on success.
666
+ * @return NULL if no lvalue was found or created.
667
+ * @return pointer to lvalue on success.
564 668
  */
565 669
 static prom_lvalue_t* prom_metric_lvalue_get(str *s_name, metric_type_t m_type,
566 670
 											 str *l1, str *l2, str *l3)
... ...
@@ -571,8 +675,10 @@ static prom_lvalue_t* prom_metric_lvalue_get(str *s_name, metric_type_t m_type,
571 675
 	}
572 676
 
573 677
 	/* Delete old lvalue structures. */
574
-	prom_metric_list_timeout_delete();
575
-	
678
+	if (lvalue_timeout > 0) {
679
+		prom_metric_list_timeout_delete();
680
+	}
681
+
576 682
     prom_metric_t *p_m = prom_metric_get(s_name);
577 683
 	if (p_m == NULL) {
578 684
 		LM_ERR("No metric found for name: %.*s\n", s_name->len, s_name->s);
... ...
@@ -605,7 +711,7 @@ static prom_lvalue_t* prom_metric_lvalue_get(str *s_name, metric_type_t m_type,
605 711
 }
606 712
 
607 713
 /**
608
- * Free a node in a list of strings.
714
+ * @brief Free a node in a list of strings.
609 715
  */
610 716
 static void prom_lb_node_free(prom_lb_node_t *lb_node, int shared_mem)
611 717
 {
... ...
@@ -633,9 +739,9 @@ static void prom_lb_node_free(prom_lb_node_t *lb_node, int shared_mem)
633 739
 }
634 740
 
635 741
 /**
636
- * Free a list of str (for labels).
742
+ * @brief Free a list of str (for labels).
637 743
  *
638
- * /param shared_mem 0 means pkg memory otherwise shared one.
744
+ * @param shared_mem 0 means pkg memory otherwise shared one.
639 745
  */
640 746
 static void prom_lb_free(prom_lb_t *prom_lb, int shared_mem)
641 747
 {
... ...
@@ -659,17 +765,17 @@ static void prom_lb_free(prom_lb_t *prom_lb, int shared_mem)
659 765
 	}
660 766
 }
661 767
 
662
-#define LABEL_SEP ':'  /* Field separator for labels. */
768
+#define LABEL_SEP ':'  /**< Field separator for labels. */
663 769
 
664 770
 /**
665
- * Add a string to list of strings.
771
+ * @brief Add a string to list of strings.
666 772
  *
667
- * /param m_lb pointer to list of strings.
668
- * /param s whole string.
669
- * /param pos_start position of first character to add.
670
- * /param pos_end position after last character to add.
773
+ * @param m_lb pointer to list of strings.
774
+ * @param s whole string.
775
+ * @param pos_start position of first character to add.
776
+ * @param pos_end position after last character to add.
671 777
  *
672
- * /return 0 on success.
778
+ * @return 0 on success.
673 779
  */
674 780
 static int prom_lb_node_add(prom_lb_t *m_lb, char *s, int len, int shared_mem)
675 781
 {
... ...
@@ -746,12 +852,12 @@ error:
746 852
 }
747 853
 
748 854
 /**
749
- * Create a list of str (for labels)
855
+ * @brief Create a list of str (for labels)
750 856
  *
751
- * /param shared_mem 0 means pkg memory otherwise shared one.
857
+ * @param shared_mem 0 means pkg memory otherwise shared one.
752 858
  *
753
- * /return pointer to prom_lb_t struct on success.
754
- * /return NULL on error.
859
+ * @return pointer to prom_lb_t struct on success.
860
+ * @return NULL on error.
755 861
  */
756 862
 static prom_lb_t* prom_lb_create(str *lb_str, int shared_mem)
757 863
 {
... ...
@@ -814,9 +920,9 @@ error:
814 920
 }
815 921
 
816 922
 /**
817
- * Create a label and add it to a metric.
923
+ * @brief Create a label and add it to a metric.
818 924
  *
819
- * /return 0 on success.
925
+ * @return 0 on success.
820 926
  */
821 927
 static int prom_label_create(prom_metric_t *mt, str *lb_str)
822 928
 {
... ...
@@ -851,7 +957,7 @@ static int prom_label_create(prom_metric_t *mt, str *lb_str)
851 957
 }
852 958
 
853 959
 /**
854
- * Create a counter and add it to list.
960
+ * @brief Create a counter and add it to list.
855 961
  */
856 962
 int prom_counter_create(char *spec)
857 963
 {
... ...
@@ -929,7 +1035,7 @@ error:
929 1035
 }
930 1036
 
931 1037
 /**
932
- * Free a gauge.
1038
+ * @brief Free a gauge.
933 1039
  */
934 1040
 static void prom_gauge_free(prom_metric_t *m_gg)
935 1041
 {
... ...
@@ -949,7 +1055,7 @@ static void prom_gauge_free(prom_metric_t *m_gg)
949 1055
 }
950 1056
 
951 1057
 /**
952
- * Create a gauge and add it to list.
1058
+ * @brief Create a gauge and add it to list.
953 1059
  */
954 1060
 int prom_gauge_create(char *spec)
955 1061
 {
... ...
@@ -1027,7 +1133,7 @@ error:
1027 1133
 }
1028 1134
 
1029 1135
 /**
1030
- * Add some positive amount to a counter.
1136
+ * @brief Add some positive amount to a counter.
1031 1137
  */
1032 1138
 int prom_counter_inc(str *s_name, int number, str *l1, str *l2, str *l3)
1033 1139
 {
... ...
@@ -1050,7 +1156,7 @@ int prom_counter_inc(str *s_name, int number, str *l1, str *l2, str *l3)
1050 1156
 }
1051 1157
 
1052 1158
 /**
1053
- * Reset a counter.
1159
+ * @brief Reset a counter.
1054 1160
  */
1055 1161
 int prom_counter_reset(str *s_name, str *l1, str *l2, str *l3)
1056 1162
 {
... ...
@@ -1073,7 +1179,7 @@ int prom_counter_reset(str *s_name, str *l1, str *l2, str *l3)
1073 1179
 }
1074 1180
 
1075 1181
 /**
1076
- * Set a value in a gauge.
1182
+ * @brief Set a value in a gauge.
1077 1183
  */
1078 1184
 int prom_gauge_set(str *s_name, double number, str *l1, str *l2, str *l3)
1079 1185
 {
... ...
@@ -1096,7 +1202,7 @@ int prom_gauge_set(str *s_name, double number, str *l1, str *l2, str *l3)
1096 1202
 }
1097 1203
 
1098 1204
 /**
1099
- * Reset value in a gauge.
1205
+ * @brief Reset value in a gauge.
1100 1206
  */
1101 1207
 int prom_gauge_reset(str *s_name, str *l1, str *l2, str *l3)
1102 1208
 {
... ...
@@ -1118,10 +1224,420 @@ int prom_gauge_reset(str *s_name, str *l1, str *l2, str *l3)
1118 1224
 	return 0;
1119 1225
 }
1120 1226
 
1227
+#define BUCKET_SEP ':'  /**< Field separator for buckets. */
1228
+
1229
+/**
1230
+ * @brief Create upper bounds for histogram buckets.
1231
+ *
1232
+ * @return 0 on success.
1233
+ */
1234
+int prom_buckets_create(prom_metric_t *m_hist, str *bucket_str)
1235
+{
1236
+	assert(m_hist);
1237
+
1238
+	char *s;
1239
+	int cnt;
1240
+	prom_buckets_upper_t *b_upper = NULL;
1241
+
1242
+	b_upper = (prom_buckets_upper_t*)shm_malloc(sizeof(prom_buckets_upper_t));
1243
+	if (b_upper == NULL) {
1244
+		SHM_MEM_ERROR;
1245
+		goto error;
1246
+	}
1247
+	memset(b_upper, 0, sizeof(*b_upper));
1248
+
1249
+	/* Parse bucket_str */
1250
+
1251
+	if (bucket_str == NULL) {
1252
+		LM_DBG("Setting default configuration for histogram buckets\n");
1253
+
1254
+		/* Default bucket configuration. */
1255
+		/* [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] */
1256
+
1257
+		b_upper->count = 11; /* We do not count Inf bucket. */
1258
+		b_upper->upper_bounds = (double*)shm_malloc(sizeof(double) * b_upper->count);
1259
+		if (b_upper->upper_bounds == NULL) {
1260
+			SHM_MEM_ERROR;
1261
+			goto error;
1262
+		}
1263
+		b_upper->upper_bounds[0] = 0.005;
1264
+		b_upper->upper_bounds[1] = 0.01;
1265
+		b_upper->upper_bounds[2] = 0.025;
1266
+		b_upper->upper_bounds[3] = 0.05;
1267
+		b_upper->upper_bounds[4] = 0.1;
1268
+		b_upper->upper_bounds[5] = 0.25;
1269
+		b_upper->upper_bounds[6] = 0.5;
1270
+		b_upper->upper_bounds[7] = 1.0;
1271
+		b_upper->upper_bounds[8] = 2.5;
1272
+		b_upper->upper_bounds[9] = 5.0;
1273
+		b_upper->upper_bounds[10] = 10.0;
1274
+		
1275
+	} else {
1276
+		/* bucket_str exits. */
1277
+
1278
+		if (bucket_str->len == 0 || bucket_str->s == NULL) {
1279
+			LM_ERR("Void bucket string\n");
1280
+			goto error;
1281
+		}
1282
+
1283
+		/* Count number of buckets. */
1284
+		cnt = 1; /* At least one bucket. */
1285
+		int i = 0;
1286
+		s = bucket_str->s;
1287
+		while (i < bucket_str->len) {
1288
+			if (s[i] == BUCKET_SEP) {
1289
+				cnt++;
1290
+			}
1291
+			i++;
1292
+		}
1293
+		LM_DBG("Preliminarily found %d buckets\n", cnt);
1294
+
1295
+		b_upper->count = cnt; /* We do not count Inf bucket. */
1296
+		b_upper->upper_bounds = (double*)shm_malloc(sizeof(double) * b_upper->count);
1297
+		if (b_upper->upper_bounds == NULL) {
1298
+			SHM_MEM_ERROR;
1299
+			goto error;
1300
+		}
1301
+
1302
+		/* Parse bucket_str and fill b_upper->upper_bounds */
1303
+		int len = bucket_str->len;
1304
+		s = bucket_str->s;
1305
+		int pos_end = 0, pos_start = 0;
1306
+		cnt = 0;
1307
+		while (pos_end < len) {
1308
+			if (s[pos_end] == BUCKET_SEP) {
1309
+				str st;
1310
+				st.len = pos_end - pos_start;
1311
+				st.s = s + pos_start;
1312
+				if (double_parse_str(&st, b_upper->upper_bounds + cnt)) {
1313
+					LM_ERR("Cannot add double to bucket (%d): %.*s\n",
1314
+						   cnt, bucket_str->len, bucket_str->s);
1315
+					goto error;
1316
+				}
1317
+				pos_start = pos_end + 1;
1318
+				cnt++;
1319
+			}
1320
+
1321
+			pos_end++;
1322
+		}
1323
+		/* Add last string if it does exist. */
1324
+		if (pos_end > pos_start) {
1325
+			str st;
1326
+			st.len = pos_end - pos_start;
1327
+			st.s = s + pos_start;
1328
+			if (double_parse_str(&st, b_upper->upper_bounds + cnt)) {
1329
+				LM_ERR("Cannot add double to bucket (%d): %.*s\n",
1330
+					   cnt, bucket_str->len, bucket_str->s);
1331
+				goto error;
1332
+			}
1333
+			cnt++;
1334
+		}
1335
+		if (cnt < 1) {
1336
+			LM_ERR("At least one bucket needed\n");
1337
+			goto error;
1338
+		}
1339
+		if (cnt != b_upper->count) {
1340
+			LM_ERR("Wrong number of parsed buckets. Expected %d found %d\n",
1341
+				   b_upper->count, cnt);
1342
+			goto error;
1343
+		}
1344
+
1345
+		/* Check buckets increase. */
1346
+		for (cnt = 1; cnt < b_upper->count; cnt++) {
1347
+			if (b_upper->upper_bounds[cnt] < b_upper->upper_bounds[cnt - 1]) {
1348
+				LM_ERR("Buckets must increase\n");
1349
+				goto error;
1350
+			}
1351
+		}
1352
+		
1353
+	} /* if (bucket_str == NULL) */
1354
+	
1355
+	/* Everything OK. */
1356
+	m_hist->buckets_upper = b_upper;
1357
+	return 0;
1358
+	
1359
+error:
1360
+
1361
+	if (b_upper) {
1362
+		if (b_upper->upper_bounds) {
1363
+			shm_free(b_upper->upper_bounds);
1364
+		}
1365
+		shm_free(b_upper);
1366
+	}
1367
+
1368
+	return -1;
1369
+}
1370
+
1371
+/**
1372
+ * @brief Free a histogram.
1373
+ */
1374
+static void prom_histogram_free(prom_metric_t *m_hist)
1375
+{
1376
+	assert(m_hist);
1377
+
1378
+	assert(m_hist->type == M_HISTOGRAM);
1379
+
1380
+	if (m_hist->name.s) {
1381
+		shm_free(m_hist->name.s);
1382
+	}
1383
+
1384
+	/* Free buckets_upper. */
1385
+	if (m_hist->buckets_upper) {
1386
+		if (m_hist->buckets_upper->upper_bounds) {
1387
+			shm_free(m_hist->buckets_upper->upper_bounds);
1388
+		}
1389
+		shm_free(m_hist->buckets_upper);
1390
+	}
1391
+	
1392
+	prom_lb_free(m_hist->lb_name, 1);
1393
+
1394
+	prom_lvalue_list_free(m_hist->lval_list);
1395
+	
1396
+	shm_free(m_hist);
1397
+}
1398
+
1399
+/**
1400
+ * @brief Create a histogram and add it to list.
1401
+ *
1402
+ * @return 0 on success.
1403
+ */
1404
+int prom_histogram_create(char *spec)
1405
+{
1406
+	param_t *pit=NULL;
1407
+	param_hooks_t phooks;
1408
+	prom_metric_t *m_hist = NULL;
1409
+	str s;
1410
+
1411
+	s.s = spec;
1412
+	s.len = strlen(spec);
1413
+	if(s.s[s.len-1]==';')
1414
+		s.len--;
1415
+	if (parse_params(&s, CLASS_ANY, &phooks, &pit)<0)
1416
+	{
1417
+		LM_ERR("failed parsing params value\n");
1418
+		goto error;
1419
+	}
1420
+	m_hist = (prom_metric_t*)shm_malloc(sizeof(prom_metric_t));
1421
+	if (m_hist == NULL) {
1422
+		SHM_MEM_ERROR;
1423
+		goto error;
1424
+	}
1425
+	memset(m_hist, 0, sizeof(*m_hist));
1426
+	m_hist->type = M_HISTOGRAM;
1427
+
1428
+	param_t *p = NULL;
1429
+	for (p = pit; p; p = p->next) {
1430
+		if (p->name.len == 5 && strncmp(p->name.s, "label", 5) == 0) {
1431
+			/* Fill histogram label. */
1432
+			if (prom_label_create(m_hist, &p->body)) {
1433
+				LM_ERR("Error creating label: %.*s\n", p->body.len, p->body.s);
1434
+				goto error;
1435
+			}
1436
+			LM_DBG("label = %.*s\n", p->body.len, p->body.s);
1437
+
1438
+		} else if (p->name.len == 4 && strncmp(p->name.s, "name", 4) == 0) {
1439
+			/* Fill histogram name. */
1440
+			if (shm_str_dup(&m_hist->name, &p->body)) {
1441
+				LM_ERR("Error creating histogram name: %.*s\n", p->body.len, p->body.s);
1442
+				goto error;
1443
+			}
1444
+			LM_DBG("name = %.*s\n", m_hist->name.len, m_hist->name.s);
1445
+
1446
+		} else if (p->name.len == 7 && strncmp(p->name.s, "buckets", 7) == 0) {
1447
+			/* Fill histogram buckets. */
1448
+			if (prom_buckets_create(m_hist, &p->body)) {
1449
+				LM_ERR("Error creating buckets: %.*s\n", p->body.len, p->body.s);
1450
+				goto error;
1451
+			}
1452
+			LM_DBG("buckets = %.*s\n", p->body.len, p->body.s);
1453
+			
1454
+		} else {
1455
+			LM_ERR("Unknown field: %.*s (%.*s)\n", p->name.len, p->name.s,
1456
+				   p->body.len, p->body.s);
1457
+			goto error;
1458
+		}
1459
+	} /* for p = pit */
1460
+
1461
+	if (m_hist->name.s == NULL || m_hist->name.len == 0) {
1462
+		LM_ERR("No histogram name\n");
1463
+		goto error;
1464
+	}
1465
+
1466
+	/* Set default buckets. */
1467
+	if (m_hist->buckets_upper == NULL) {
1468
+		LM_DBG("Setting default buckets\n");
1469
+		if (prom_buckets_create(m_hist, NULL)) {
1470
+			LM_ERR("Failed to create default buckets\n");
1471
+			goto error;
1472
+		}
1473
+	}
1474
+	
1475
+	/* Place histogram at the end of list. */
1476
+	prom_metric_t **l = &prom_metric_list;
1477
+	while (*l != NULL) {
1478
+		l = &((*l)->next);
1479
+	}
1480
+	*l = m_hist;
1481
+	m_hist->next = NULL;
1482
+
1483
+	/* For debugging purpose show upper bounds for buckets. */
1484
+	int i;
1485
+	for (i = 0; i < m_hist->buckets_upper->count; i++) {
1486
+		LM_DBG("Bucket (%d) -> %f\n", i, m_hist->buckets_upper->upper_bounds[i]);
1487
+	}
1488
+	
1489
+	/* Everything went fine. */
1490
+	return 0;
1491
+
1492
+error:
1493
+	if (pit != NULL) {
1494
+		free_params(pit);
1495
+	}
1496
+	if (m_hist != NULL) {
1497
+		prom_histogram_free(m_hist);
1498
+	}
1499
+	return -1;
1500
+}
1501
+
1502
+/**
1503
+ * @brief Free a prom_hist_value_t structure.
1504
+ */
1505
+static void prom_histogram_value_free(prom_hist_value_t *phv)
1506
+{
1507
+	if (!phv) {
1508
+		return;
1509
+	}
1510
+
1511
+	if (phv->buckets_count) {
1512
+		shm_free(phv->buckets_count);
1513
+	}
1514
+
1515
+	shm_free(phv);
1516
+}
1517
+
1121 1518
 /**
1122
- * Print labels.
1519
+ * @brief Create prom_hist_value_t structure if needed
1123 1520
  *
1124
- * /return 0 on success.
1521
+ * @return 0 on success.
1522
+ */
1523
+static int prom_histogram_value_create(prom_lvalue_t *hlv)
1524
+{
1525
+	assert(hlv);
1526
+
1527
+	if (hlv->m.hval) {
1528
+		/* Return if prom_hist_value_t structure already exists. */
1529
+		return 0;
1530
+	}
1531
+
1532
+	prom_hist_value_t *phv = NULL;
1533
+	phv = (prom_hist_value_t*)shm_malloc(sizeof(*phv));
1534
+	if (phv == NULL) {
1535
+		SHM_MEM_ERROR;
1536
+		goto error;
1537
+	}
1538
+	memset(phv, 0, sizeof(*phv));
1539
+
1540
+	int count = hlv->metric->buckets_upper->count;
1541
+	LM_DBG("Setting array for %d buckets\n", count);
1542
+	phv->buckets_count = (uint64_t*)shm_malloc(sizeof(uint64_t) * count);
1543
+	if (!phv->buckets_count) {
1544
+		SHM_MEM_ERROR;
1545
+		goto error;
1546
+	}
1547
+	memset(phv->buckets_count, 0, sizeof(uint64_t) * count);
1548
+	
1549
+	hlv->m.hval = phv;
1550
+	return 0;
1551
+	
1552
+error:
1553
+	if (phv) {
1554
+		prom_histogram_value_free(phv);
1555
+	}
1556
+
1557
+	return -1;
1558
+}
1559
+
1560
+/**
1561
+ * @brief Observe a value in lvalue for histogram.
1562
+ *
1563
+ * @return 0 on success.
1564
+ */
1565
+static int prom_histogram_lvalue_observe(prom_lvalue_t *hlv, double number)
1566
+{
1567
+	assert(hlv);
1568
+
1569
+	/* Create prom_hist_value_t structure if needed. */
1570
+	if (prom_histogram_value_create(hlv)) {
1571
+		LM_ERR("Failed to create histogram_value\n");
1572
+		goto error;
1573
+	}
1574
+
1575
+	int i;
1576
+	int cnt = hlv->metric->buckets_upper->count;
1577
+	double *buck_up = hlv->metric->buckets_upper->upper_bounds;
1578
+	for (i = cnt - 1; i >= 0; i--) {
1579
+		if (number <= buck_up[i]) {
1580
+			hlv->m.hval->buckets_count[i]++;
1581
+		} else {
1582
+			break;
1583
+		}
1584
+	}
1585
+
1586
+	hlv->m.hval->count++;
1587
+	hlv->m.hval->sum = hlv->m.hval->sum + number;
1588
+
1589
+	LM_DBG("bucket_count: %" PRIu64 "\n", hlv->m.hval->count);
1590
+	LM_DBG("bucket_sum: %f\n", hlv->m.hval->sum);
1591
+
1592
+	for (i = 0; i < cnt; i++) {
1593
+		LM_DBG("bucket (%d) [%f] -> %" PRIu64 "\n",
1594
+			   i, buck_up[i], hlv->m.hval->buckets_count[i]);
1595
+	}
1596
+	
1597
+	/* Everything fine. */
1598
+	return 0;
1599
+
1600
+error:
1601
+
1602
+	return -1;
1603
+}
1604
+
1605
+/**
1606
+ * @brief Observe a value in a histogram.
1607
+ *
1608
+ * @param number value to observe.
1609
+ */
1610
+int prom_histogram_observe(str *s_name, double number, str *l1, str *l2, str *l3)
1611
+{
1612
+	lock_get(prom_lock);
1613
+
1614
+	/* Find a lvalue based on its metric name and labels. */
1615
+	prom_lvalue_t *p = NULL;
1616
+	p = prom_metric_lvalue_get(s_name, M_HISTOGRAM, l1, l2, l3);