feature: Add packet trace API
[vpp.git] / src / vlib / trace.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 /*
16  * trace.c: VLIB trace buffer.
17  *
18  * Copyright (c) 2008 Eliot Dresselhaus
19  *
20  * Permission is hereby granted, free of charge, to any person obtaining
21  * a copy of this software and associated documentation files (the
22  * "Software"), to deal in the Software without restriction, including
23  * without limitation the rights to use, copy, modify, merge, publish,
24  * distribute, sublicense, and/or sell copies of the Software, and to
25  * permit persons to whom the Software is furnished to do so, subject to
26  * the following conditions:
27  *
28  * The above copyright notice and this permission notice shall be
29  * included in all copies or substantial portions of the Software.
30  *
31  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32  *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33  *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34  *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35  *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36  *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37  *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38  */
39
40 #include <vlib/vlib.h>
41 #include <vlib/threads.h>
42
43 u8 *vnet_trace_placeholder;
44
45 /* Helper function for nodes which only trace buffer data. */
46 void
47 vlib_trace_frame_buffers_only (vlib_main_t * vm,
48                                vlib_node_runtime_t * node,
49                                u32 * buffers,
50                                uword n_buffers,
51                                uword next_buffer_stride,
52                                uword n_buffer_data_bytes_in_trace)
53 {
54   u32 n_left, *from;
55
56   n_left = n_buffers;
57   from = buffers;
58
59   while (n_left >= 4)
60     {
61       u32 bi0, bi1;
62       vlib_buffer_t *b0, *b1;
63       u8 *t0, *t1;
64
65       /* Prefetch next iteration. */
66       vlib_prefetch_buffer_with_index (vm, from[2], LOAD);
67       vlib_prefetch_buffer_with_index (vm, from[3], LOAD);
68
69       bi0 = from[0];
70       bi1 = from[1];
71
72       b0 = vlib_get_buffer (vm, bi0);
73       b1 = vlib_get_buffer (vm, bi1);
74
75       if (b0->flags & VLIB_BUFFER_IS_TRACED)
76         {
77           t0 = vlib_add_trace (vm, node, b0, n_buffer_data_bytes_in_trace);
78           clib_memcpy_fast (t0, b0->data + b0->current_data,
79                             n_buffer_data_bytes_in_trace);
80         }
81       if (b1->flags & VLIB_BUFFER_IS_TRACED)
82         {
83           t1 = vlib_add_trace (vm, node, b1, n_buffer_data_bytes_in_trace);
84           clib_memcpy_fast (t1, b1->data + b1->current_data,
85                             n_buffer_data_bytes_in_trace);
86         }
87       from += 2;
88       n_left -= 2;
89     }
90
91   while (n_left >= 1)
92     {
93       u32 bi0;
94       vlib_buffer_t *b0;
95       u8 *t0;
96
97       bi0 = from[0];
98
99       b0 = vlib_get_buffer (vm, bi0);
100
101       if (b0->flags & VLIB_BUFFER_IS_TRACED)
102         {
103           t0 = vlib_add_trace (vm, node, b0, n_buffer_data_bytes_in_trace);
104           clib_memcpy_fast (t0, b0->data + b0->current_data,
105                             n_buffer_data_bytes_in_trace);
106         }
107       from += 1;
108       n_left -= 1;
109     }
110 }
111
112 /* Free up all trace buffer memory. */
113 always_inline void
114 clear_trace_buffer (void)
115 {
116   int i;
117   vlib_trace_main_t *tm;
118
119   /* *INDENT-OFF* */
120   foreach_vlib_main (
121   ({
122     tm = &this_vlib_main->trace_main;
123
124     tm->trace_enable = 0;
125     vec_free (tm->nodes);
126   }));
127
128   foreach_vlib_main (
129   ({
130     tm = &this_vlib_main->trace_main;
131
132     for (i = 0; i < vec_len (tm->trace_buffer_pool); i++)
133       if (! pool_is_free_index (tm->trace_buffer_pool, i))
134         vec_free (tm->trace_buffer_pool[i]);
135     pool_free (tm->trace_buffer_pool);
136   }));
137   /* *INDENT-ON* */
138 }
139
140 u8 *
141 format_vlib_trace (u8 * s, va_list * va)
142 {
143   vlib_main_t *vm = va_arg (*va, vlib_main_t *);
144   vlib_trace_header_t *h = va_arg (*va, vlib_trace_header_t *);
145   vlib_trace_header_t *e = vec_end (h);
146   vlib_node_t *node, *prev_node;
147   clib_time_t *ct = &vm->clib_time;
148   f64 t;
149
150   prev_node = 0;
151   while (h < e)
152     {
153       node = vlib_get_node (vm, h->node_index);
154
155       if (node != prev_node)
156         {
157           t =
158             (h->time - vm->cpu_time_main_loop_start) * ct->seconds_per_clock;
159           s =
160             format (s, "\n%U: %v", format_time_interval, "h:m:s:u", t,
161                     node->name);
162         }
163       prev_node = node;
164
165       if (node->format_trace)
166         s = format (s, "\n  %U", node->format_trace, vm, node, h->data);
167       else
168         s = format (s, "\n  %U", node->format_buffer, h->data);
169
170       h = vlib_trace_header_next (h);
171     }
172
173   return s;
174 }
175
176 /* Root of all trace cli commands. */
177 /* *INDENT-OFF* */
178 VLIB_CLI_COMMAND (trace_cli_command,static) = {
179   .path = "trace",
180   .short_help = "Packet tracer commands",
181 };
182 /* *INDENT-ON* */
183
184 int
185 trace_time_cmp (void *a1, void *a2)
186 {
187   vlib_trace_header_t **t1 = a1;
188   vlib_trace_header_t **t2 = a2;
189   i64 dt = t1[0]->time - t2[0]->time;
190   return dt < 0 ? -1 : (dt > 0 ? +1 : 0);
191 }
192
193 /*
194  * Return 1 if this packet passes the trace filter, or 0 otherwise
195  */
196 u32
197 filter_accept (vlib_trace_main_t * tm, vlib_trace_header_t * h)
198 {
199   vlib_trace_header_t *e = vec_end (h);
200
201   if (tm->filter_flag == 0)
202     return 1;
203
204   if (tm->filter_flag == FILTER_FLAG_INCLUDE)
205     {
206       while (h < e)
207         {
208           if (h->node_index == tm->filter_node_index)
209             return 1;
210           h = vlib_trace_header_next (h);
211         }
212       return 0;
213     }
214   else                          /* FILTER_FLAG_EXCLUDE */
215     {
216       while (h < e)
217         {
218           if (h->node_index == tm->filter_node_index)
219             return 0;
220           h = vlib_trace_header_next (h);
221         }
222       return 1;
223     }
224
225   return 0;
226 }
227
228 /*
229  * Remove traces from the trace buffer pool that don't pass the filter
230  */
231 void
232 trace_apply_filter (vlib_main_t * vm)
233 {
234   vlib_trace_main_t *tm = &vm->trace_main;
235   vlib_trace_header_t **h;
236   vlib_trace_header_t ***traces_to_remove = 0;
237   u32 index;
238   u32 trace_index;
239   u32 n_accepted;
240
241   u32 accept;
242
243   if (tm->filter_flag == FILTER_FLAG_NONE)
244     return;
245
246   /*
247    * Ideally we would retain the first N traces that pass the filter instead
248    * of any N traces.
249    */
250   n_accepted = 0;
251   /* *INDENT-OFF* */
252   pool_foreach (h, tm->trace_buffer_pool,
253    ({
254       accept = filter_accept(tm, h[0]);
255
256       if ((n_accepted == tm->filter_count) || !accept)
257           vec_add1 (traces_to_remove, h);
258       else
259           n_accepted++;
260   }));
261   /* *INDENT-ON* */
262
263   /* remove all traces that we don't want to keep */
264   for (index = 0; index < vec_len (traces_to_remove); index++)
265     {
266       trace_index = traces_to_remove[index] - tm->trace_buffer_pool;
267       _vec_len (tm->trace_buffer_pool[trace_index]) = 0;
268       pool_put_index (tm->trace_buffer_pool, trace_index);
269     }
270
271   vec_free (traces_to_remove);
272 }
273
274 static clib_error_t *
275 cli_show_trace_buffer (vlib_main_t * vm,
276                        unformat_input_t * input, vlib_cli_command_t * cmd)
277 {
278   vlib_trace_main_t *tm;
279   vlib_trace_header_t **h, **traces;
280   u32 i, index = 0;
281   char *fmt;
282   u8 *s = 0;
283   u32 max;
284
285   /*
286    * By default display only this many traces. To display more, explicitly
287    * specify a max. This prevents unexpectedly huge outputs.
288    */
289   max = 50;
290   while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT)
291     {
292       if (unformat (input, "max %d", &max))
293         ;
294       else
295         return clib_error_create ("expected 'max COUNT', got `%U'",
296                                   format_unformat_error, input);
297     }
298
299
300   /* Get active traces from pool. */
301
302   /* *INDENT-OFF* */
303   foreach_vlib_main (
304   ({
305     fmt = "------------------- Start of thread %d %s -------------------\n";
306     s = format (s, fmt, index, vlib_worker_threads[index].name);
307
308     tm = &this_vlib_main->trace_main;
309
310     trace_apply_filter(this_vlib_main);
311
312     traces = 0;
313     pool_foreach (h, tm->trace_buffer_pool,
314     ({
315       vec_add1 (traces, h[0]);
316     }));
317
318     if (vec_len (traces) == 0)
319       {
320         s = format (s, "No packets in trace buffer\n");
321         goto done;
322       }
323
324     /* Sort them by increasing time. */
325     vec_sort_with_function (traces, trace_time_cmp);
326
327     for (i = 0; i < vec_len (traces); i++)
328       {
329         if (i == max)
330           {
331             vlib_cli_output (vm, "Limiting display to %d packets."
332                                  " To display more specify max.", max);
333             goto done;
334           }
335
336         s = format (s, "Packet %d\n%U\n\n", i + 1,
337                          format_vlib_trace, vm, traces[i]);
338       }
339
340   done:
341     vec_free (traces);
342
343     index++;
344   }));
345   /* *INDENT-ON* */
346
347   vlib_cli_output (vm, "%v", s);
348   vec_free (s);
349   return 0;
350 }
351
352 /* *INDENT-OFF* */
353 VLIB_CLI_COMMAND (show_trace_cli,static) = {
354   .path = "show trace",
355   .short_help = "Show trace buffer [max COUNT]",
356   .function = cli_show_trace_buffer,
357 };
358 /* *INDENT-ON* */
359
360 int vlib_enable_disable_pkt_trace_filter (int enable) __attribute__ ((weak));
361
362 int
363 vlib_enable_disable_pkt_trace_filter (int enable)
364 {
365   return 0;
366 }
367
368 void
369 vlib_trace_stop_and_clear (void)
370 {
371   vlib_enable_disable_pkt_trace_filter (0);     /* disble tracing */
372   clear_trace_buffer ();
373 }
374
375
376 void
377 trace_update_capture_options (u32 add, u32 node_index, u32 filter, u8 verbose)
378 {
379   vlib_trace_main_t *tm;
380   vlib_trace_node_t *tn;
381
382   if (add == ~0)
383     add = 50;
384
385   /* *INDENT-OFF* */
386   foreach_vlib_main ((
387     {
388       tm = &this_vlib_main->trace_main;
389       tm->verbose = verbose;
390       vec_validate (tm->nodes, node_index);
391       tn = tm->nodes + node_index;
392
393       /*
394        * Adding 0 makes no real sense, and there wa no other way
395        * to explicilty zero-out the limits and count, so make
396        * an "add 0" request really be "set to 0".
397        */
398       if (add == 0)
399           tn->limit = tn->count = 0;
400       else
401           tn->limit += add;
402     }));
403
404   foreach_vlib_main ((
405     {
406       tm = &this_vlib_main->trace_main;
407       tm->trace_enable = 1;
408     }));
409   /* *INDENT-ON* */
410 }
411
412 static clib_error_t *
413 cli_add_trace_buffer (vlib_main_t * vm,
414                       unformat_input_t * input, vlib_cli_command_t * cmd)
415 {
416   unformat_input_t _line_input, *line_input = &_line_input;
417   vlib_node_t *node;
418   u32 node_index, add;
419   u8 verbose = 0;
420   int filter = 0;
421   clib_error_t *error = 0;
422
423   if (!unformat_user (input, unformat_line_input, line_input))
424     return 0;
425
426   if (vnet_trace_placeholder == 0)
427     vec_validate_aligned (vnet_trace_placeholder, 2048,
428                           CLIB_CACHE_LINE_BYTES);
429
430   while (unformat_check_input (line_input) != (uword) UNFORMAT_END_OF_INPUT)
431     {
432       if (unformat (line_input, "%U %d",
433                     unformat_vlib_node, vm, &node_index, &add))
434         ;
435       else if (unformat (line_input, "verbose"))
436         verbose = 1;
437       else if (unformat (line_input, "filter"))
438         filter = 1;
439       else
440         {
441           error = clib_error_create ("expected NODE COUNT, got `%U'",
442                                      format_unformat_error, line_input);
443           goto done;
444         }
445     }
446
447   node = vlib_get_node (vm, node_index);
448
449   if ((node->flags & VLIB_NODE_FLAG_TRACE_SUPPORTED) == 0)
450     {
451       error = clib_error_create ("node '%U' doesn't support per-node "
452                                  "tracing. There may be another way to "
453                                  "initiate trace on this node.",
454                                  format_vlib_node_name, vm, node_index);
455       goto done;
456     }
457
458   if (filter)
459     {
460       if (vlib_enable_disable_pkt_trace_filter (1 /* enable */ ))
461         {
462           error = clib_error_create ("No packet trace filter configured...");
463           goto done;
464         }
465     }
466
467   trace_update_capture_options (add, node_index, filter, verbose);
468
469 done:
470   unformat_free (line_input);
471
472   return error;
473 }
474
475 /* *INDENT-OFF* */
476 VLIB_CLI_COMMAND (add_trace_cli,static) = {
477   .path = "trace add",
478   .short_help = "trace add <input-graph-node> <add'l-pkts-for-node-> [filter] [verbose]",
479   .function = cli_add_trace_buffer,
480 };
481 /* *INDENT-ON* */
482
483 /*
484  * Configure a filter for packet traces.
485  *
486  * This supplements the packet trace feature so that only packets matching
487  * the filter are included in the trace. Currently the only filter is to
488  * keep packets that include a certain node in the trace or exclude a certain
489  * node in the trace.
490  *
491  * The count of traced packets in the "trace add" command is still used to
492  * create a certain number of traces. The "trace filter" command specifies
493  * how many of those packets should be retained in the trace.
494  *
495  * For example, 1Mpps of traffic is arriving and one of those packets is being
496  * dropped. To capture the trace for only that dropped packet, you can do:
497  *     trace filter include error-drop 1
498  *     trace add dpdk-input 1000000
499  *     <wait one second>
500  *     show trace
501  *
502  * Note that the filter could be implemented by capturing all traces and just
503  * reducing traces displayed by the "show trace" function. But that would
504  * require a lot of memory for storing the traces, making that infeasible.
505  *
506  * To remove traces from the trace pool that do not include a certain node
507  * requires that the trace be "complete" before applying the filter. To
508  * accomplish this, the trace pool is filtered upon each iteraction of the
509  * main vlib loop. Doing so keeps the number of allocated traces down to a
510  * reasonably low number. This requires that tracing for a buffer is not
511  * performed after the vlib main loop interation completes. i.e. you can't
512  * save away a buffer temporarily then inject it back into the graph and
513  * expect that the trace_index is still valid (such as a traffic manager might
514  * do). A new trace buffer should be allocated for those types of packets.
515  *
516  * The filter can be extended to support multiple nodes and other match
517  * criteria (e.g. input sw_if_index, mac address) but for now just checks if
518  * a specified node is in the trace or not in the trace.
519  */
520
521 void
522 trace_filter_set (u32 node_index, u32 flag, u32 count)
523 {
524   /* *INDENT-OFF* */
525   foreach_vlib_main (
526   ({
527     vlib_trace_main_t *tm;
528
529     tm = &this_vlib_main->trace_main;
530     tm->filter_node_index = node_index;
531     tm->filter_flag = flag;
532     tm->filter_count = count;
533
534     /*
535      * Clear the trace limits to stop any in-progress tracing
536      * Prevents runaway trace allocations when the filter changes
537      * (or is removed)
538      */
539     vec_free (tm->nodes);
540   }));
541   /* *INDENT-ON* */
542 }
543
544
545 static clib_error_t *
546 cli_filter_trace (vlib_main_t * vm,
547                   unformat_input_t * input, vlib_cli_command_t * cmd)
548 {
549   u32 filter_node_index;
550   u32 filter_flag;
551   u32 filter_count;
552
553   if (unformat (input, "include %U %d",
554                 unformat_vlib_node, vm, &filter_node_index, &filter_count))
555     {
556       filter_flag = FILTER_FLAG_INCLUDE;
557     }
558   else if (unformat (input, "exclude %U %d",
559                      unformat_vlib_node, vm, &filter_node_index,
560                      &filter_count))
561     {
562       filter_flag = FILTER_FLAG_EXCLUDE;
563     }
564   else if (unformat (input, "none"))
565     {
566       filter_flag = FILTER_FLAG_NONE;
567       filter_node_index = 0;
568       filter_count = 0;
569     }
570   else
571     return
572       clib_error_create
573       ("expected 'include NODE COUNT' or 'exclude NODE COUNT' or 'none', got `%U'",
574        format_unformat_error, input);
575
576   trace_filter_set (filter_node_index, filter_flag, filter_count);
577
578   return 0;
579 }
580
581 /* *INDENT-OFF* */
582 VLIB_CLI_COMMAND (filter_trace_cli,static) = {
583   .path = "trace filter",
584   .short_help = "trace filter none | [include|exclude] NODE COUNT",
585   .function = cli_filter_trace,
586 };
587 /* *INDENT-ON* */
588
589 static clib_error_t *
590 cli_clear_trace_buffer (vlib_main_t * vm,
591                         unformat_input_t * input, vlib_cli_command_t * cmd)
592 {
593   vlib_trace_stop_and_clear ();
594   return 0;
595 }
596
597 /* *INDENT-OFF* */
598 VLIB_CLI_COMMAND (clear_trace_cli,static) = {
599   .path = "clear trace",
600   .short_help = "Clear trace buffer and free memory",
601   .function = cli_clear_trace_buffer,
602 };
603 /* *INDENT-ON* */
604
605 /* Placeholder function to get us linked in. */
606 void
607 vlib_trace_cli_reference (void)
608 {
609 }
610
611 int
612 vnet_is_packet_traced (vlib_buffer_t * b,
613                        u32 classify_table_index, int func)
614 __attribute__ ((weak));
615
616 int
617 vnet_is_packet_traced (vlib_buffer_t * b, u32 classify_table_index, int func)
618 {
619   clib_warning ("BUG: STUB called");
620   return 1;
621 }
622
623 void *
624 vlib_add_trace (vlib_main_t * vm,
625                 vlib_node_runtime_t * r, vlib_buffer_t * b, u32 n_data_bytes)
626 {
627   return vlib_add_trace_inline (vm, r, b, n_data_bytes);
628 }
629
630
631
632 /*
633  * fd.io coding-style-patch-verification: ON
634  *
635  * Local Variables:
636  * eval: (c-set-style "gnu")
637  * End:
638  */