2 *------------------------------------------------------------------
5 * Copyright (c) 2018 Cisco and/or its affiliates.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at:
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *------------------------------------------------------------------
20 #include <arpa/inet.h>
21 #include <sys/epoll.h>
31 #include <sys/types.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #endif /* __FreeBSD__ */
35 #include <sys/socket.h>
36 #include <vpp-api/client/stat_client.h>
37 #include <vlib/vlib.h>
40 /* https://github.com/prometheus/prometheus/wiki/Default-port-allocations */
41 #define SERVER_PORT 9482
59 print_metric_v1 (FILE *stream, stat_segment_data_t *res)
65 case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
66 fformat (stream, "# TYPE %s counter\n", prom_string (res->name));
67 for (k = 0; k < vec_len (res->simple_counter_vec); k++)
68 for (j = 0; j < vec_len (res->simple_counter_vec[k]); j++)
69 fformat (stream, "%s{thread=\"%d\",interface=\"%d\"} %lld\n",
70 prom_string (res->name), k, j,
71 res->simple_counter_vec[k][j]);
74 case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
75 fformat (stream, "# TYPE %s_packets counter\n", prom_string (res->name));
76 fformat (stream, "# TYPE %s_bytes counter\n", prom_string (res->name));
77 for (k = 0; k < vec_len (res->simple_counter_vec); k++)
78 for (j = 0; j < vec_len (res->combined_counter_vec[k]); j++)
81 "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
82 prom_string (res->name), k, j,
83 res->combined_counter_vec[k][j].packets);
84 fformat (stream, "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n",
85 prom_string (res->name), k, j,
86 res->combined_counter_vec[k][j].bytes);
89 case STAT_DIR_TYPE_SCALAR_INDEX:
90 fformat (stream, "# TYPE %s counter\n", prom_string (res->name));
91 fformat (stream, "%s %.2f\n", prom_string (res->name),
95 case STAT_DIR_TYPE_NAME_VECTOR:
96 fformat (stream, "# TYPE %s_info gauge\n", prom_string (res->name));
97 for (k = 0; k < vec_len (res->name_vector); k++)
98 if (res->name_vector[k])
99 fformat (stream, "%s_info{index=\"%d\",name=\"%s\"} 1\n",
100 prom_string (res->name), k, res->name_vector[k]);
103 case STAT_DIR_TYPE_EMPTY:
107 fformat (stderr, "Unknown value %d\n", res->type);
113 sanitize (char *str, int len)
115 for (int i = 0; i < len; i++)
117 if (!isalnum (str[i]))
123 tokenize (const char *name, char **tokens, int *lengths, int max_tokens)
125 char *p = (char *) name;
129 while (*p && i < max_tokens - 1)
133 tokens[i] = (char *) savep;
134 lengths[i] = (int) (p - savep);
144 tokens[i] = (char *) savep;
145 lengths[i] = (int) (p - savep);
152 print_metric_v2 (FILE *stream, stat_segment_data_t *res)
155 char *tokens[MAX_TOKENS];
156 int lengths[MAX_TOKENS];
159 num_tokens = tokenize (res->name, tokens, lengths, MAX_TOKENS);
162 case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
163 if (res->simple_counter_vec == 0)
165 for (k = 0; k < vec_len (res->simple_counter_vec); k++)
166 for (j = 0; j < vec_len (res->simple_counter_vec[k]); j++)
168 if ((num_tokens == 4) &&
169 (!strncmp (tokens[1], "nodes", lengths[1]) ||
170 !strncmp (tokens[1], "interfaces", lengths[1])))
172 sanitize (tokens[1], lengths[1]);
173 sanitize (tokens[3], lengths[3]);
176 "%.*s_%.*s{%.*s=\"%.*s\",index=\"%d\",thread=\"%d\"} %lu\n",
177 lengths[1], tokens[1], lengths[3], tokens[3], lengths[1] - 1,
178 tokens[1], lengths[2], tokens[2], j, k,
179 res->simple_counter_vec[k][j]);
181 else if ((num_tokens == 3) &&
182 !strncmp (tokens[1], "sys", lengths[1]))
184 sanitize (tokens[1], lengths[1]);
185 fformat (stream, "%.*s_%.*s{index=\"%d\",thread=\"%d\"} %lu\n",
186 lengths[1], tokens[1], lengths[2], tokens[2], j, k,
187 res->simple_counter_vec[k][j]);
189 else if (!strncmp (tokens[1], "mem", lengths[1]))
195 "%.*s{heap=\"%.*s\",index=\"%d\",thread=\"%d\"} %lu\n",
196 lengths[1], tokens[1], lengths[2], tokens[2], j, k,
197 res->simple_counter_vec[k][j]);
199 else if (num_tokens == 4)
202 "%.*s_%.*s{heap=\"%.*s\",index=\"%d\",thread=\"%"
204 lengths[1], tokens[1], lengths[3], tokens[3],
205 lengths[2], tokens[2], j, k,
206 res->simple_counter_vec[k][j]);
210 print_metric_v1 (stream, res);
213 else if (!strncmp (tokens[1], "err", lengths[1]))
215 // NOTE: the error is in token3, but it may contain '/'.
216 // Considering this is the last token, it is safe to print
217 // token3 until the end of res->name
219 "%.*s{node=\"%.*s\",error=\"%s\",index=\"%d\",thread="
221 lengths[1], tokens[1], lengths[2], tokens[2],
222 tokens[3], j, k, res->simple_counter_vec[k][j]);
226 print_metric_v1 (stream, res);
231 case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
232 if (res->combined_counter_vec == 0)
234 for (k = 0; k < vec_len (res->combined_counter_vec); k++)
235 for (j = 0; j < vec_len (res->combined_counter_vec[k]); j++)
237 if ((num_tokens == 4) &&
238 !strncmp (tokens[1], "interfaces", lengths[1]))
240 sanitize (tokens[1], lengths[1]);
241 sanitize (tokens[3], lengths[3]);
243 "%.*s_%.*s_packets{interface=\"%.*s\",index=\"%d\","
244 "thread=\"%d\"} %lu\n",
245 lengths[1], tokens[1], lengths[3], tokens[3],
246 lengths[2], tokens[2], j, k,
247 res->combined_counter_vec[k][j].packets);
249 "%.*s_%.*s_bytes{interface=\"%.*s\",index=\"%d\","
250 "thread=\"%d\"} %lu\n",
251 lengths[1], tokens[1], lengths[3], tokens[3],
252 lengths[2], tokens[2], j, k,
253 res->combined_counter_vec[k][j].bytes);
257 print_metric_v1 (stream, res);
262 case STAT_DIR_TYPE_SCALAR_INDEX:
263 if ((num_tokens == 4) &&
264 !strncmp (tokens[1], "buffer-pools", lengths[1]))
266 sanitize (tokens[1], lengths[1]);
267 sanitize (tokens[3], lengths[3]);
268 fformat (stream, "%.*s_%.*s{pool=\"%.*s\"} %.2f\n", lengths[1],
269 tokens[1], lengths[3], tokens[3], lengths[2], tokens[2],
272 else if ((num_tokens == 3) && !strncmp (tokens[1], "sys", lengths[1]))
274 sanitize (tokens[1], lengths[1]);
275 sanitize (tokens[2], lengths[2]);
276 fformat (stream, "%.*s_%.*s %.2f\n", lengths[1], tokens[1],
277 lengths[2], tokens[2], res->scalar_value);
278 if (!strncmp (tokens[2], "boottime", lengths[2]))
281 gettimeofday (&tv, NULL);
282 fformat (stream, "sys_uptime %.2f\n",
283 tv.tv_sec - res->scalar_value);
288 print_metric_v1 (stream, res);
293 fformat (stderr, "Unhandled type %d name %s\n", res->type, res->name);
298 dump_metrics (FILE *stream, u8 **patterns, u8 v2)
300 stat_segment_data_t *res;
302 static u32 *stats = 0;
305 res = stat_segment_dump (stats);
307 { /* Memory layout has changed */
310 stats = stat_segment_ls (patterns);
314 for (i = 0; i < vec_len (res); i++)
317 print_metric_v2 (stream, &res[i]);
319 print_metric_v1 (stream, &res[i]);
321 stat_segment_data_free (res);
325 #define ROOTPAGE "<html><head><title>Metrics exporter</title></head><body><ul><li><a href=\"/metrics\">metrics</a></li></ul></body></html>"
326 #define NOT_FOUND_ERROR "<html><head><title>Document not found</title></head><body><h1>404 - Document not found</h1></body></html>"
329 http_handler (FILE *stream, u8 **patterns, u8 v2)
331 char status[80] = { 0 };
332 if (fgets (status, sizeof (status) - 1, stream) == 0)
334 fprintf (stderr, "fgets error: %s %s\n", status, strerror (errno));
338 char *method = strtok_r (status, " \t\r\n", &saveptr);
339 if (method == 0 || strncmp (method, "GET", 4) != 0)
341 fputs ("HTTP/1.0 405 Method Not Allowed\r\n", stream);
344 char *request_uri = strtok_r (NULL, " \t", &saveptr);
345 char *protocol = strtok_r (NULL, " \t\r\n", &saveptr);
346 if (protocol == 0 || strncmp (protocol, "HTTP/1.", 7) != 0)
348 fputs ("HTTP/1.0 400 Bad Request\r\n", stream);
351 /* Read the other headers */
355 if (fgets (header, sizeof (header) - 1, stream) == 0)
357 fprintf (stderr, "fgets error: %s\n", strerror (errno));
360 if (header[0] == '\n' || header[1] == '\n')
365 if (strcmp (request_uri, "/") == 0)
367 fprintf (stream, "HTTP/1.0 200 OK\r\nContent-Length: %lu\r\n\r\n",
368 (unsigned long) strlen (ROOTPAGE));
369 fputs (ROOTPAGE, stream);
372 if (strcmp (request_uri, "/metrics") != 0)
375 "HTTP/1.0 404 Not Found\r\nContent-Length: %lu\r\n\r\n",
376 (unsigned long) strlen (NOT_FOUND_ERROR));
377 fputs (NOT_FOUND_ERROR, stream);
380 fputs ("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", stream);
381 dump_metrics (stream, patterns, v2);
385 start_listen (u16 port)
387 struct sockaddr_in6 serveraddr;
388 int addrlen = sizeof (serveraddr);
391 int listenfd = socket (AF_INET6, SOCK_STREAM, 0);
394 perror ("Failed opening socket");
399 setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof (int));
402 perror ("Failed setsockopt");
407 clib_memset (&serveraddr, 0, sizeof (serveraddr));
408 serveraddr.sin6_family = AF_INET6;
409 serveraddr.sin6_port = htons (port);
410 serveraddr.sin6_addr = in6addr_any;
412 if (bind (listenfd, (struct sockaddr *) &serveraddr, addrlen) < 0)
414 fprintf (stderr, "bind() error %s\n", strerror (errno));
418 if (listen (listenfd, 1000000) != 0)
420 fprintf (stderr, "listen() error for %s\n", strerror (errno));
427 /* Socket epoll, linux-specific */
430 struct sockaddr_storage storage;
431 struct sockaddr addr;
432 struct sockaddr_in sin_addr;
433 struct sockaddr_in6 sin6_addr;
439 main (int argc, char **argv)
441 unformat_input_t _argv, *a = &_argv;
442 u8 *stat_segment_name, *pattern = 0, **patterns = 0;
443 u16 port = SERVER_PORT;
445 "%s: usage [socket-name <name>] [port <0 - 65535>] [v2] <patterns> ...\n";
449 /* Allocating 256MB heap */
450 clib_mem_init (0, 256 << 20);
452 unformat_init_command_line (a, argv);
454 stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
456 while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
458 if (unformat (a, "socket-name %s", &stat_segment_name))
460 if (unformat (a, "v2"))
462 else if (unformat (a, "port %d", &port))
464 else if (unformat (a, "%s", &pattern))
466 vec_add1 (patterns, pattern);
470 fformat (stderr, usage, argv[0]);
475 if (vec_len (patterns) == 0)
477 fformat (stderr, usage, argv[0]);
481 rv = stat_segment_connect ((char *) stat_segment_name);
484 fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
489 int fd = start_listen (port);
496 int conn_sock = accept (fd, NULL, NULL);
499 fprintf (stderr, "Accept failed: %s", strerror (errno));
504 struct sockaddr_in6 clientaddr = { 0 };
505 char address[INET6_ADDRSTRLEN];
507 getpeername (conn_sock, (struct sockaddr *) &clientaddr, &addrlen);
509 (AF_INET6, &clientaddr.sin6_addr, address, sizeof (address)))
511 fprintf (stderr, "Client address is [%s]:%d\n", address,
512 ntohs (clientaddr.sin6_port));
516 FILE *stream = fdopen (conn_sock, "r+");
519 fprintf (stderr, "fdopen error: %s\n", strerror (errno));
523 /* Single reader at the moment */
524 http_handler (stream, patterns, v2);
528 stat_segment_disconnect ();
535 * fd.io coding-style-patch-verification: ON
538 * eval: (c-set-style "gnu")