1 /*
2 * Copyright (C) 2012 VoIP Embedded, Inc.
3 *
4 * Copyright (C) 2019 Vicente Hernando (Sonoc)
5 *
6 * This file is part of Kamailio, a free SIP server.
7 *
8 * Kamailio is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version
12 *
13 * Kamailio is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 *
22 */
23
24 /**
25 * Functionality of prometheus module.
26 */
27
28 #include <string.h>
29 #include <time.h>
30 #include <inttypes.h>
31 #include <stdarg.h>
32
33 #include "../../core/counters.h"
34 #include "../../core/ut.h"
35
36 #include "prom.h"
37 #include "prom_metric.h"
38
39 /**
40 * Delete current buffer data.
41 */
prom_body_delete(prom_ctx_t * ctx)42 void prom_body_delete(prom_ctx_t *ctx)
43 {
44 ctx->reply.body.len = 0;
45 }
46
47 /**
48 * Write some data in prom_body buffer.
49 *
50 * /return number of bytes written.
51 * /return -1 on error.
52 */
prom_body_printf(prom_ctx_t * ctx,char * fmt,...)53 int prom_body_printf(prom_ctx_t *ctx, char *fmt, ...)
54 {
55 struct xhttp_prom_reply *reply = &ctx->reply;
56
57 va_list ap;
58
59 va_start(ap, fmt);
60
61 LM_DBG("Body current length: %d\n", reply->body.len);
62
63 char *p = reply->buf.s + reply->body.len;
64 int remaining_len = reply->buf.len - reply->body.len;
65 LM_DBG("Remaining length: %d\n", remaining_len);
66
67 /* int vsnprintf(char *str, size_t size, const char *format, va_list ap); */
68 int len = vsnprintf(p, remaining_len, fmt, ap);
69 if (len < 0) {
70 LM_ERR("Error printing body buffer\n");
71 goto error;
72 } else if (len >= remaining_len) {
73 LM_ERR("Error body buffer overflow: %d (%d)\n", len, remaining_len);
74 goto error;
75 } else {
76 /* Buffer printed OK. */
77 reply->body.len += len;
78 LM_DBG("Body new length: %d\n", reply->body.len);
79 }
80
81 va_end(ap);
82 return len;
83
84 error:
85 va_end(ap);
86 return -1;
87 }
88
89 /**
90 * Get current timestamp in milliseconds.
91 *
92 * /param ts pointer to timestamp integer.
93 * /return 0 on success.
94 */
get_timestamp(uint64_t * ts)95 int get_timestamp(uint64_t *ts)
96 {
97 assert(ts);
98
99 struct timeval current_time;
100 if (gettimeofday(¤t_time, NULL) < 0) {
101 LM_ERR("failed to get current time!\n");
102 return -1;
103 }
104
105 *ts = (uint64_t)current_time.tv_sec*1000 +
106 (uint64_t)current_time.tv_usec/1000;
107
108 return 0;
109 }
110
111 /**
112 * Generate a string suitable for a Prometheus metric.
113 *
114 * /return 0 on success.
115 */
metric_generate(prom_ctx_t * ctx,str * group,str * name,counter_handle_t * h)116 static int metric_generate(prom_ctx_t *ctx, str *group, str *name, counter_handle_t *h)
117 {
118 long counter_val = counter_get_val(*h);
119
120 /* Calculate timestamp. */
121 uint64_t ts;
122 if (get_timestamp(&ts)) {
123 LM_ERR("Error getting current timestamp\n");
124 return -1;
125 }
126 LM_DBG("Timestamp: %" PRIu64 "\n", ts);
127
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",
132 group->len, group->s, name->len, name->s,
133 counter_val, (uint64_t)ts);
134
135 if (prom_body_printf(ctx, "kamailio_%.*s_%.*s %lu %" PRIu64 "\n",
136 group->len, group->s, name->len, name->s,
137 counter_val, (uint64_t)ts) == -1) {
138 LM_ERR("Fail to print\n");
139 return -1;
140 }
141
142 return 0;
143 }
144
145 /**
146 * Statistic getter callback.
147 */
prom_get_grp_vars_cbk(void * p,str * g,str * n,counter_handle_t h)148 static void prom_get_grp_vars_cbk(void* p, str* g, str* n, counter_handle_t h)
149 {
150 metric_generate(p, g, n, &h);
151 }
152
153 /**
154 * Group statistic getter callback.
155 */
prom_get_all_grps_cbk(void * p,str * g)156 static void prom_get_all_grps_cbk(void* p, str* g)
157 {
158 counter_iterate_grp_vars(g->s, prom_get_grp_vars_cbk, p);
159 }
160
161 #define STATS_MAX_LEN 1024
162
163 /**
164 * Get statistics (based on stats_get_all)
165 *
166 * /return 0 on success
167 */
prom_stats_get(prom_ctx_t * ctx,str * stat)168 int prom_stats_get(prom_ctx_t *ctx, str *stat)
169 {
170 if (stat == NULL) {
171 LM_ERR("No stats set\n");
172 return -1;
173 }
174
175 prom_body_delete(ctx);
176
177 LM_DBG("User defined statistics\n");
178 if (prom_metric_list_print(ctx)) {
179 LM_ERR("Fail to print user defined metrics\n");
180 return -1;
181 }
182
183 LM_DBG("Statistics for: %.*s\n", stat->len, stat->s);
184
185 int len = stat->len;
186
187 stat_var *s_stat;
188
189 if (len == 0) {
190 LM_DBG("Do not show Kamailio statistics\n");
191
192 }
193 else if (len==3 && strncmp("all", stat->s, 3)==0) {
194 LM_DBG("Showing all statistics\n");
195 if (prom_body_printf(
196 ctx, "\n# Kamailio whole internal statistics\n") == -1) {
197 LM_ERR("Fail to print\n");
198 return -1;
199 }
200
201 counter_iterate_grp_names(prom_get_all_grps_cbk, ctx);
202 }
203 else if (stat->s[len-1]==':') {
204 LM_DBG("Showing statistics for group: %.*s\n", stat->len, stat->s);
205
206 if (len == 1) {
207 LM_ERR("Void group for statistics: %.*s\n", stat->len, stat->s);
208 return -1;
209
210 } else {
211 if (prom_body_printf(
212 ctx, "\n# Kamailio statistics for group: %.*s\n",
213 stat->len, stat->s) == -1) {
214 LM_ERR("Fail to print\n");
215 return -1;
216 }
217
218 /* Temporary stat_tmp string. */
219 char stat_tmp[STATS_MAX_LEN];
220 memcpy(stat_tmp, stat->s, len);
221 stat_tmp[len-1] = '\0';
222 counter_iterate_grp_vars(stat_tmp, prom_get_grp_vars_cbk, ctx);
223 stat_tmp[len-1] = ':';
224 }
225 }
226 else {
227 LM_DBG("Showing statistic for: %.*s\n", stat->len, stat->s);
228
229 s_stat = get_stat(stat);
230 if (s_stat) {
231 str group_str, name_str;
232 group_str.s = get_stat_module(s_stat);
233 if (group_str.s) {
234 group_str.len = strlen(group_str.s);
235 } else {
236 group_str.len = 0;
237 }
238
239 name_str.s = get_stat_name(s_stat);
240 if (name_str.s) {
241 name_str.len = strlen(name_str.s);
242 } else {
243 name_str.len = 0;
244 }
245
246 LM_DBG("%s:%s = %lu\n",
247 ZSW(get_stat_module(s_stat)), ZSW(get_stat_name(s_stat)),
248 get_stat_val(s_stat));
249
250 if (group_str.len && name_str.len && s_stat) {
251 if (prom_body_printf(
252 ctx, "\n# Kamailio statistics for: %.*s\n",
253 stat->len, stat->s) == -1) {
254 LM_ERR("Fail to print\n");
255 return -1;
256 }
257
258 counter_handle_t stat_handle;
259 stat_handle.id = (unsigned short)(unsigned long)s_stat;
260 if (metric_generate(ctx, &group_str, &name_str, &stat_handle)) {
261 LM_ERR("Failed to generate metric: %.*s - %.*s\n",
262 group_str.len, group_str.s,
263 name_str.len, name_str.s);
264 return -1;
265 }
266 } else {
267 LM_ERR("Not enough length for group (%d) or name (%d)\n",
268 group_str.len, name_str.len);
269 return -1;
270 }
271 } /* if s_stat */
272 else {
273 LM_ERR("stat not found: %.*s\n", stat->len, stat->s);
274 return -1;
275 }
276 } /* if len == 0 */
277
278 return 0;
279 } /* prom_stats_get */
280
281