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(&current_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