Crude stat segment lock recovery
[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 #include <sys/socket.h>
31 #include <vpp-api/client/stat_client.h>
32 #include <vlib/vlib.h>
33 #include <vpp/stats/stats.h>
34 #include <ctype.h>
35
36 /* https://github.com/prometheus/prometheus/wiki/Default-port-allocations */
37 #define SERVER_PORT 9477
38
39 static char *
40 prom_string (char *s)
41 {
42   char *p = s;
43   while (*p)
44     {
45       if (!isalnum (*p))
46         *p = '_';
47       p++;
48     }
49   return s;
50 }
51
52 static void
53 dump_metrics (FILE * stream, stat_segment_cached_pointer_t * cp)
54 {
55   stat_segment_data_t *res;
56   int i, j, k;
57
58   res = stat_segment_collect (cp);
59   for (i = 0; i < vec_len (res); i++)
60     {
61       switch (res[i].type)
62         {
63         case STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE:
64           fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
65           for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
66             for (j = 0; j < vec_len (res[i].simple_counter_vec[k]); j++)
67               fformat (stream, "%s{thread=\"%d\",interface=\"%d\"} %lld\n",
68                        prom_string (res[i].name), k, j,
69                        res[i].simple_counter_vec[k][j]);
70           break;
71
72         case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED:
73           fformat (stream, "# TYPE %s_packets counter\n",
74                    prom_string (res[i].name));
75           fformat (stream, "# TYPE %s_bytes counter\n",
76                    prom_string (res[i].name));
77           for (k = 0; k < vec_len (res[i].simple_counter_vec); k++)
78             for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++)
79               {
80                 fformat (stream,
81                          "%s_packets{thread=\"%d\",interface=\"%d\"} %lld\n",
82                          prom_string (res[i].name), k, j,
83                          res[i].combined_counter_vec[k][j].packets);
84                 fformat (stream,
85                          "%s_bytes{thread=\"%d\",interface=\"%d\"} %lld\n",
86                          prom_string (res[i].name), k, j,
87                          res[i].combined_counter_vec[k][j].bytes);
88               }
89           break;
90         case STAT_DIR_TYPE_ERROR_INDEX:
91           fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
92           fformat (stream, "%s{thread=\"0\"} %lld\n",
93                    prom_string (res[i].name), res[i].error_value);
94           break;
95
96         case STAT_DIR_TYPE_SCALAR_POINTER:
97           fformat (stream, "# TYPE %s counter\n", prom_string (res[i].name));
98           fformat (stream, "%s %.2f\n", prom_string (res[i].name),
99                    res[i].scalar_value);
100           break;
101
102         default:
103           fformat (stderr, "Unknown value %d\n", res[i].type);
104           ;
105         }
106     }
107   stat_segment_data_free (res);
108
109 }
110
111
112 #define ROOTPAGE  "<html><head><title>Metrics exporter</title></head><body><ul><li><a href=\"/metrics\">metrics</a></li></ul></body></html>"
113 #define NOT_FOUND_ERROR "<html><head><title>Document not found</title></head><body><h1>404 - Document not found</h1></body></html>"
114
115 static void
116 http_handler (FILE * stream, stat_segment_cached_pointer_t * cp)
117 {
118   char status[80] = { 0 };
119   if (fgets (status, sizeof (status) - 1, stream) == 0)
120     {
121       fprintf (stderr, "fgets error: %s %s\n", status, strerror (errno));
122       return;
123     }
124   char *saveptr;
125   char *method = strtok_r (status, " \t\r\n", &saveptr);
126   if (method == 0 || strncmp (method, "GET", 4) != 0)
127     {
128       fputs ("HTTP/1.0 405 Method Not Allowed\r\n", stream);
129       return;
130     }
131   char *request_uri = strtok_r (NULL, " \t", &saveptr);
132   char *protocol = strtok_r (NULL, " \t\r\n", &saveptr);
133   if (protocol == 0 || strncmp (protocol, "HTTP/1.", 7) != 0)
134     {
135       fputs ("HTTP/1.0 400 Bad Request\r\n", stream);
136       return;
137     }
138   /* Read the other headers */
139   for (;;)
140     {
141       char header[1024];
142       if (fgets (header, sizeof (header) - 1, stream) == 0)
143         {
144           fprintf (stderr, "fgets error: %s\n", strerror (errno));
145           return;
146         }
147       if (header[0] == '\n' || header[1] == '\n')
148         {
149           break;
150         }
151     }
152   if (strcmp (request_uri, "/") == 0)
153     {
154       fprintf (stream, "HTTP/1.0 200 OK\r\nContent-Length: %lu\r\n\r\n",
155                (unsigned long) strlen (ROOTPAGE));
156       fputs (ROOTPAGE, stream);
157       return;
158     }
159   if (strcmp (request_uri, "/metrics") != 0)
160     {
161       fprintf (stream,
162                "HTTP/1.0 404 Not Found\r\nContent-Length: %lu\r\n\r\n",
163                (unsigned long) strlen (NOT_FOUND_ERROR));
164       fputs (NOT_FOUND_ERROR, stream);
165       return;
166     }
167   fputs ("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", stream);
168   dump_metrics (stream, cp);
169 }
170
171 static int
172 start_listen (u16 port)
173 {
174   struct sockaddr_in6 serveraddr;
175   int addrlen = sizeof (serveraddr);
176   int enable = 1;
177
178   int listenfd = socket (AF_INET6, SOCK_STREAM, 0);
179   if (listenfd == -1)
180     {
181       perror ("Failed opening socket");
182     }
183
184   int rv =
185     setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof (int));
186   if (rv < 0)
187     {
188       perror ("Failed setsockopt");
189     }
190
191   memset (&serveraddr, 0, sizeof (serveraddr));
192   serveraddr.sin6_family = AF_INET6;
193   serveraddr.sin6_port = htons (port);
194   serveraddr.sin6_addr = in6addr_any;
195
196   if (bind (listenfd, (struct sockaddr *) &serveraddr, addrlen) < 0)
197     {
198       fprintf (stderr, "bind() error %s\n", strerror (errno));
199       return -1;
200     }
201   if (listen (listenfd, 1000000) != 0)
202     {
203       fprintf (stderr, "listen() error for %s\n", strerror (errno));
204       return -1;
205     }
206   return listenfd;
207 }
208
209 /* Socket epoll, linux-specific */
210 union my_sockaddr
211 {
212   struct sockaddr_storage storage;
213   struct sockaddr addr;
214   struct sockaddr_in sin_addr;
215   struct sockaddr_in6 sin6_addr;
216 };
217
218
219
220 int
221 main (int argc, char **argv)
222 {
223   unformat_input_t _argv, *a = &_argv;
224   u8 *stat_segment_name, *pattern = 0, **patterns = 0;
225   int rv;
226   void *heap_base;
227
228   heap_base = clib_mem_vm_map ((void *) 0x10000000ULL, 128 << 20);
229   clib_mem_init (heap_base, 128 << 20);
230
231   unformat_init_command_line (a, argv);
232
233   stat_segment_name = (u8 *) STAT_SEGMENT_SOCKET_FILE;
234
235   while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT)
236     {
237       if (unformat (a, "socket-name %s", &stat_segment_name))
238         ;
239       else if (unformat (a, "%s", &pattern))
240         {
241           vec_add1 (patterns, pattern);
242         }
243       else
244         {
245           fformat (stderr,
246                    "%s: usage [socket-name <name>] <patterns> ...\n",
247                    argv[0]);
248           exit (1);
249         }
250     }
251
252   rv = stat_segment_connect ((char *) stat_segment_name);
253   if (rv)
254     {
255       fformat (stderr, "Couldn't connect to vpp, does %s exist?\n",
256                stat_segment_name);
257       exit (1);
258     }
259
260   u8 **dir;
261   stat_segment_cached_pointer_t *cp;
262
263   dir = stat_segment_ls (patterns);
264   cp = stat_segment_register (dir);
265   if (!cp)
266     {
267       fformat (stderr,
268                "Couldn't register required counters with stat segment\n");
269       exit (1);
270     }
271
272   int fd = start_listen (SERVER_PORT);
273   if (fd < 0)
274     {
275       exit (1);
276     }
277   for (;;)
278     {
279       int conn_sock = accept (fd, NULL, NULL);
280       if (conn_sock < 0)
281         {
282           fprintf (stderr, "Accept failed: %s", strerror (errno));
283           continue;
284         }
285       else
286         {
287           struct sockaddr_in6 clientaddr;
288           char address[INET6_ADDRSTRLEN];
289           socklen_t addrlen;
290           getpeername (conn_sock, (struct sockaddr *) &clientaddr, &addrlen);
291           if (inet_ntop
292               (AF_INET6, &clientaddr.sin6_addr, address, sizeof (address)))
293             {
294               printf ("Client address is [%s]:%d\n", address,
295                       ntohs (clientaddr.sin6_port));
296             }
297         }
298
299       FILE *stream = fdopen (conn_sock, "r+");
300       if (stream == NULL)
301         {
302           fprintf (stderr, "fdopen error: %s\n", strerror (errno));
303           close (conn_sock);
304           continue;
305         }
306       /* Single reader at the moment */
307       http_handler (stream, cp);
308       fclose (stream);
309     }
310
311   stat_segment_disconnect ();
312
313   exit (0);
314 }
315
316 /*
317  * fd.io coding-style-patch-verification: ON
318  *
319  * Local Variables:
320  * eval: (c-set-style "gnu")
321  * End:
322  */