udp: fix csum computation when offload disabled
[vpp.git] / src / vpp / app / vpp_prometheus_export.c
1 /*
2  *------------------------------------------------------------------
3  * vpp_get_stats.c
4  *
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:
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
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  *------------------------------------------------------------------
18  */
19
20 #include <arpa/inet.h>
21 #include <sys/epoll.h>
22 #include <stdbool.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <netdb.h>
30 #ifdef __FreeBSD__
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>
38 #include <ctype.h>
39
40 /* https://github.com/prometheus/prometheus/wiki/Default-port-allocations */
41 #define SERVER_PORT 9482
42
43 #define MAX_TOKENS 10
44
45 static char *
46 prom_string (char *s)
47 {
48   char *p = s;
49   while (*p)
50     {
51       if (!isalnum (*p))
52         *p = '_';
53       p++;
54     }
55   return s;
56 }
57
58 static void
59 print_metric_v1 (FILE *stream, stat_segment_data_t *res)
60 {
61   int j, k;
62
63   switch (res->type)
64     {
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]);
72       break;
73
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++)
79           {
80             fformat (stream,
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);
87           }
88       break;
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),
92                res->scalar_value);
93       break;
94
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]);
101       break;
102
103     case STAT_DIR_TYPE_EMPTY:
104       break;
105
106     default:
107       fformat (stderr, "Unknown value %d\n", res->type);
108       ;
109     }
110 }
111
112 static void
113 sanitize (char *str, int len)
114 {
115   for (int i = 0; i < len; i++)
116     {
117       if (!isalnum (str[i]))
118         str[i] = '_';
119     }
120 }
121
122 static int
123 tokenize (const char *name, char **tokens, int *lengths, int max_tokens)
124 {
125   char *p = (char *) name;
126   char *savep = p;
127
128   int i = 0;
129   while (*p && i < max_tokens - 1)
130     {
131       if (*p == '/')
132         {
133           tokens[i] = (char *) savep;
134           lengths[i] = (int) (p - savep);
135           i++;
136           p++;
137           savep = p;
138         }
139       else
140         {
141           p++;
142         }
143     }
144   tokens[i] = (char *) savep;
145   lengths[i] = (int) (p - savep);
146
147   i++;
148   return i;
149 }
150
151 static void
152 print_metric_v2 (FILE *stream, stat_segment_data_t *res)
153 {
154   int num_tokens = 0;
155   char *tokens[MAX_TOKENS];
156   int lengths[MAX_TOKENS];
157   int j, k;
158
159   num_tokens = tokenize (res->name, tokens, lengths, MAX_TOKENS);
160   switch (res->type)
161     {
162     case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
163       if (res->simple_counter_vec == 0)
164         return;
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++)
167           {
168             if ((num_tokens == 4) &&
169                 (!strncmp (tokens[1], "nodes", lengths[1]) ||
170                  !strncmp (tokens[1], "interfaces", lengths[1])))
171               {
172                 sanitize (tokens[1], lengths[1]);
173                 sanitize (tokens[3], lengths[3]);
174                 fformat (
175                   stream,
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]);
180               }
181             else if ((num_tokens == 3) &&
182                      !strncmp (tokens[1], "sys", lengths[1]))
183               {
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]);
188               }
189             else if (!strncmp (tokens[1], "mem", lengths[1]))
190               {
191                 if (num_tokens == 3)
192                   {
193                     fformat (
194                       stream,
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]);
198                   }
199                 else if (num_tokens == 4)
200                   {
201                     fformat (stream,
202                              "%.*s_%.*s{heap=\"%.*s\",index=\"%d\",thread=\"%"
203                              "d\"} %lu\n",
204                              lengths[1], tokens[1], lengths[3], tokens[3],
205                              lengths[2], tokens[2], j, k,
206                              res->simple_counter_vec[k][j]);
207                   }
208                 else
209                   {
210                     print_metric_v1 (stream, res);
211                   }
212               }
213             else if (!strncmp (tokens[1], "err", lengths[1]))
214               {
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
218                 fformat (stream,
219                          "%.*s{node=\"%.*s\",error=\"%s\",index=\"%d\",thread="
220                          "\"%d\"} %lu\n",
221                          lengths[1], tokens[1], lengths[2], tokens[2],
222                          tokens[3], j, k, res->simple_counter_vec[k][j]);
223               }
224             else
225               {
226                 print_metric_v1 (stream, res);
227               }
228           }
229       break;
230
231     case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
232       if (res->combined_counter_vec == 0)
233         return;
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++)
236           {
237             if ((num_tokens == 4) &&
238                 !strncmp (tokens[1], "interfaces", lengths[1]))
239               {
240                 sanitize (tokens[1], lengths[1]);
241                 sanitize (tokens[3], lengths[3]);
242                 fformat (stream,
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);
248                 fformat (stream,
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);
254               }
255             else
256               {
257                 print_metric_v1 (stream, res);
258               }
259           }
260       break;
261
262     case STAT_DIR_TYPE_SCALAR_INDEX:
263       if ((num_tokens == 4) &&
264           !strncmp (tokens[1], "buffer-pools", lengths[1]))
265         {
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],
270                    res->scalar_value);
271         }
272       else if ((num_tokens == 3) && !strncmp (tokens[1], "sys", lengths[1]))
273         {
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]))
279             {
280               struct timeval tv;
281               gettimeofday (&tv, NULL);
282               fformat (stream, "sys_uptime %.2f\n",
283                        tv.tv_sec - res->scalar_value);
284             }
285         }
286       else
287         {
288           print_metric_v1 (stream, res);
289         }
290       break;
291
292     default:;
293       fformat (stderr, "Unhandled type %d name %s\n", res->type, res->name);
294     }
295 }
296
297 static void
298 dump_metrics (FILE *stream, u8 **patterns, u8 v2)
299 {
300   stat_segment_data_t *res;
301   int i;
302   static u32 *stats = 0;
303
304 retry:
305   res = stat_segment_dump (stats);
306   if (res == 0)
307     { /* Memory layout has changed */
308       if (stats)
309         vec_free (stats);
310       stats = stat_segment_ls (patterns);
311       goto retry;
312     }
313
314   for (i = 0; i < vec_len (res); i++)
315     {
316       if (v2)
317         print_metric_v2 (stream, &res[i]);
318       else
319         print_metric_v1 (stream, &res[i]);
320     }
321   stat_segment_data_free (res);
322 }
323
324
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>"
327
328 static void
329 http_handler (FILE *stream, u8 **patterns, u8 v2)
330 {
331   char status[80] = { 0 };
332   if (fgets (status, sizeof (status) - 1, stream) == 0)
333     {
334       fprintf (stderr, "fgets error: %s %s\n", status, strerror (errno));
335       return;
336     }
337   char *saveptr;
338   char *method = strtok_r (status, " \t\r\n", &saveptr);
339   if (method == 0 || strncmp (method, "GET", 4) != 0)
340     {
341       fputs ("HTTP/1.0 405 Method Not Allowed\r\n", stream);
342       return;
343     }
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)
347     {
348       fputs ("HTTP/1.0 400 Bad Request\r\n", stream);
349       return;
350     }
351   /* Read the other headers */
352   for (;;)
353     {
354       char header[1024];
355       if (fgets (header, sizeof (header) - 1, stream) == 0)
356         {
357           fprintf (stderr, "fgets error: %s\n", strerror (errno));
358           return;
359         }
360       if (header[0] == '\n' || header[1] == '\n')
361         {
362           break;
363         }
364     }
365   if (strcmp (request_uri, "/") == 0)
366     {
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);
370       return;
371     }
372   if (strcmp (request_uri, "/metrics") != 0)
373     {
374       fprintf (stream,
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);
378       return;
379     }
380   fputs ("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", stream);
381   dump_metrics (stream, patterns, v2);
382 }
383
384 static int
385 start_listen (u16 port)
386 {
387   struct sockaddr_in6 serveraddr;
388   int addrlen = sizeof (serveraddr);
389   int enable = 1;
390
391   int listenfd = socket (AF_INET6, SOCK_STREAM, 0);
392   if (listenfd == -1)
393     {
394       perror ("Failed opening socket");
395       return -1;
396     }
397
398   int rv =
399     setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof (int));
400   if (rv < 0)
401     {
402       perror ("Failed setsockopt");
403       close (listenfd);
404       return -1;
405     }
406
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;
411
412   if (bind (listenfd, (struct sockaddr *) &serveraddr, addrlen) < 0)
413     {
414       fprintf (stderr, "bind() error %s\n", strerror (errno));
415       close (listenfd);
416       return -1;
417     }
418   if (listen (listenfd, 1000000) != 0)
419     {
420       fprintf (stderr, "listen() error for %s\n", strerror (errno));
421       close (listenfd);
422       return -1;
423     }
424   return listenfd;
425 }
426
427 /* Socket epoll, linux-specific */
428 union my_sockaddr
429 {
430   struct sockaddr_storage storage;
431   struct sockaddr addr;
432   struct sockaddr_in sin_addr;
433   struct sockaddr_in6 sin6_addr;
434 };
435
436
437
438 int
439 main (int argc, char **argv)
440 {
441   unformat_input_t _argv, *a = &_argv;
442   u8 *stat_segment_name, *pattern = 0, **patterns = 0;
443   u16 port = SERVER_PORT;
444   char *usage =
445     "%s: usage [socket-name <name>] [port <0 - 65535>] [v2] <patterns> ...\n";
446   int rv;
447   u8 v2 = 0;
448
449   /* Allocating 256MB heap */
450   clib_mem_init (0, 256 << 20);
451
452   unformat_init_command_line (a, argv);
453
454   stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
455
456   while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
457     {
458       if (unformat (a, "socket-name %s", &stat_segment_name))
459         ;
460       if (unformat (a, "v2"))
461         v2 = 1;
462       else if (unformat (a, "port %d", &port))
463         ;
464       else if (unformat (a, "%s", &pattern))
465         {
466           vec_add1 (patterns, pattern);
467         }
468       else
469         {
470           fformat (stderr, usage, argv[0]);
471           exit (1);
472         }
473     }
474
475   if (vec_len (patterns) == 0)
476     {
477       fformat (stderr, usage, argv[0]);
478       exit (1);
479     }
480
481   rv = stat_segment_connect ((char *) stat_segment_name);
482   if (rv)
483     {
484       fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
485                stat_segment_name);
486       exit (1);
487     }
488
489   int fd = start_listen (port);
490   if (fd < 0)
491     {
492       exit (1);
493     }
494   for (;;)
495     {
496       int conn_sock = accept (fd, NULL, NULL);
497       if (conn_sock < 0)
498         {
499           fprintf (stderr, "Accept failed: %s", strerror (errno));
500           continue;
501         }
502       else
503         {
504           struct sockaddr_in6 clientaddr = { 0 };
505           char address[INET6_ADDRSTRLEN];
506           socklen_t addrlen;
507           getpeername (conn_sock, (struct sockaddr *) &clientaddr, &addrlen);
508           if (inet_ntop
509               (AF_INET6, &clientaddr.sin6_addr, address, sizeof (address)))
510             {
511               fprintf (stderr, "Client address is [%s]:%d\n", address,
512                        ntohs (clientaddr.sin6_port));
513             }
514         }
515
516       FILE *stream = fdopen (conn_sock, "r+");
517       if (stream == NULL)
518         {
519           fprintf (stderr, "fdopen error: %s\n", strerror (errno));
520           close (conn_sock);
521           continue;
522         }
523       /* Single reader at the moment */
524       http_handler (stream, patterns, v2);
525       fclose (stream);
526     }
527
528   stat_segment_disconnect ();
529   close (fd);
530
531   exit (0);
532 }
533
534 /*
535  * fd.io coding-style-patch-verification: ON
536  *
537  * Local Variables:
538  * eval: (c-set-style "gnu")
539  * End:
540  */