classify: add bpf support to pcap classifier
[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 #include <vnet/classify/vnet_classify.h>
43
44 u8 *vnet_trace_placeholder;
45
46 /* Helper function for nodes which only trace buffer data. */
47 void
48 vlib_trace_frame_buffers_only (vlib_main_t * vm,
49                                vlib_node_runtime_t * node,
50                                u32 * buffers,
51                                uword n_buffers,
52                                uword next_buffer_stride,
53                                uword n_buffer_data_bytes_in_trace)
54 {
55   u32 n_left, *from;
56
57   n_left = n_buffers;
58   from = buffers;
59
60   while (n_left >= 4)
61     {
62       u32 bi0, bi1;
63       vlib_buffer_t *b0, *b1;
64       u8 *t0, *t1;
65
66       /* Prefetch next iteration. */
67       vlib_prefetch_buffer_with_index (vm, from[2], LOAD);
68       vlib_prefetch_buffer_with_index (vm, from[3], LOAD);
69
70       bi0 = from[0];
71       bi1 = from[1];
72
73       b0 = vlib_get_buffer (vm, bi0);
74       b1 = vlib_get_buffer (vm, bi1);
75
76       if (b0->flags & VLIB_BUFFER_IS_TRACED)
77         {
78           t0 = vlib_add_trace (vm, node, b0, n_buffer_data_bytes_in_trace);
79           clib_memcpy_fast (t0, b0->data + b0->current_data,
80                             n_buffer_data_bytes_in_trace);
81         }
82       if (b1->flags & VLIB_BUFFER_IS_TRACED)
83         {
84           t1 = vlib_add_trace (vm, node, b1, n_buffer_data_bytes_in_trace);
85           clib_memcpy_fast (t1, b1->data + b1->current_data,
86                             n_buffer_data_bytes_in_trace);
87         }
88       from += 2;
89       n_left -= 2;
90     }
91
92   while (n_left >= 1)
93     {
94       u32 bi0;
95       vlib_buffer_t *b0;
96       u8 *t0;
97
98       bi0 = from[0];
99
100       b0 = vlib_get_buffer (vm, bi0);
101
102       if (b0->flags & VLIB_BUFFER_IS_TRACED)
103         {
104           t0 = vlib_add_trace (vm, node, b0, n_buffer_data_bytes_in_trace);
105           clib_memcpy_fast (t0, b0->data + b0->current_data,
106                             n_buffer_data_bytes_in_trace);
107         }
108       from += 1;
109       n_left -= 1;
110     }
111 }
112
113 /* Free up all trace buffer memory. */
114 void
115 clear_trace_buffer (void)
116 {
117   int i;
118   vlib_trace_main_t *tm;
119
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 }
138
139 u8 *
140 format_vlib_trace (u8 * s, va_list * va)
141 {
142   vlib_main_t *vm = va_arg (*va, vlib_main_t *);
143   vlib_trace_header_t *h = va_arg (*va, vlib_trace_header_t *);
144   vlib_trace_header_t *e = vec_end (h);
145   vlib_node_t *node, *prev_node;
146   clib_time_t *ct = &vm->clib_time;
147   f64 t;
148
149   prev_node = 0;
150   while (h < e)
151     {
152       node = vlib_get_node (vm, h->node_index);
153
154       if (node != prev_node)
155         {
156           t =
157             (h->time - vm->cpu_time_main_loop_start) * ct->seconds_per_clock;
158           s =
159             format (s, "\n%U: %v", format_time_interval, "h:m:s:u", t,
160                     node->name);
161         }
162       prev_node = node;
163
164       if (node->format_trace)
165         s = format (s, "\n  %U", node->format_trace, vm, node, h->data);
166       else
167         s = format (s, "\n  %U", node->format_buffer, h->data);
168
169       h = vlib_trace_header_next (h);
170     }
171
172   return s;
173 }
174
175 /* Root of all trace cli commands. */
176 /* *INDENT-OFF* */
177 VLIB_CLI_COMMAND (trace_cli_command,static) = {
178   .path = "trace",
179   .short_help = "Packet tracer commands",
180 };
181 /* *INDENT-ON* */
182
183 int
184 trace_time_cmp (void *a1, void *a2)
185 {
186   vlib_trace_header_t **t1 = a1;
187   vlib_trace_header_t **t2 = a2;
188   i64 dt = t1[0]->time - t2[0]->time;
189   return dt < 0 ? -1 : (dt > 0 ? +1 : 0);
190 }
191
192 /*
193  * Return 1 if this packet passes the trace filter, or 0 otherwise
194  */
195 u32
196 filter_accept (vlib_trace_main_t * tm, vlib_trace_header_t * h)
197 {
198   vlib_trace_header_t *e = vec_end (h);
199
200   if (tm->filter_flag == 0)
201     return 1;
202
203   /*
204    * When capturing a post-mortem dispatch trace,
205    * toss all existing traces once per dispatch cycle.
206    * So we can trace 4 billion pkts without running out of
207    * memory...
208    */
209   if (tm->filter_flag == FILTER_FLAG_POST_MORTEM)
210     return 0;
211
212   if (tm->filter_flag == FILTER_FLAG_INCLUDE)
213     {
214       while (h < e)
215         {
216           if (h->node_index == tm->filter_node_index)
217             return 1;
218           h = vlib_trace_header_next (h);
219         }
220       return 0;
221     }
222   else                          /* FILTER_FLAG_EXCLUDE */
223     {
224       while (h < e)
225         {
226           if (h->node_index == tm->filter_node_index)
227             return 0;
228           h = vlib_trace_header_next (h);
229         }
230       return 1;
231     }
232
233   return 0;
234 }
235
236 /*
237  * Remove traces from the trace buffer pool that don't pass the filter
238  */
239 void
240 trace_apply_filter (vlib_main_t * vm)
241 {
242   vlib_trace_main_t *tm = &vm->trace_main;
243   vlib_trace_header_t **h;
244   vlib_trace_header_t ***traces_to_remove = 0;
245   u32 index;
246   u32 trace_index;
247   u32 n_accepted;
248
249   u32 accept;
250
251   if (tm->filter_flag == FILTER_FLAG_NONE)
252     return;
253
254   /*
255    * Ideally we would retain the first N traces that pass the filter instead
256    * of any N traces.
257    */
258   n_accepted = 0;
259   /* *INDENT-OFF* */
260   pool_foreach (h, tm->trace_buffer_pool)
261     {
262       accept = filter_accept(tm, h[0]);
263
264       if ((n_accepted == tm->filter_count) || !accept)
265           vec_add1 (traces_to_remove, h);
266       else
267           n_accepted++;
268   }
269   /* *INDENT-ON* */
270
271   /* remove all traces that we don't want to keep */
272   for (index = 0; index < vec_len (traces_to_remove); index++)
273     {
274       trace_index = traces_to_remove[index] - tm->trace_buffer_pool;
275       vec_set_len (tm->trace_buffer_pool[trace_index], 0);
276       pool_put_index (tm->trace_buffer_pool, trace_index);
277     }
278
279   vec_free (traces_to_remove);
280 }
281
282 static clib_error_t *
283 cli_show_trace_buffer (vlib_main_t * vm,
284                        unformat_input_t * input, vlib_cli_command_t * cmd)
285 {
286   vlib_trace_main_t *tm;
287   vlib_trace_header_t **h, **traces;
288   u32 i, index = 0;
289   char *fmt;
290   u8 *s = 0;
291   u32 max;
292
293   /*
294    * By default display only this many traces. To display more, explicitly
295    * specify a max. This prevents unexpectedly huge outputs.
296    */
297   max = 50;
298   while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT)
299     {
300       if (unformat (input, "max %d", &max))
301         ;
302       else
303         return clib_error_create ("expected 'max COUNT', got `%U'",
304                                   format_unformat_error, input);
305     }
306
307
308   /* Get active traces from pool. */
309
310   foreach_vlib_main ()
311     {
312       fmt = "------------------- Start of thread %d %s -------------------\n";
313       s = format (s, fmt, index, vlib_worker_threads[index].name);
314
315       tm = &this_vlib_main->trace_main;
316
317       trace_apply_filter (this_vlib_main);
318
319       traces = 0;
320       pool_foreach (h, tm->trace_buffer_pool)
321         {
322           vec_add1 (traces, h[0]);
323         }
324
325       if (vec_len (traces) == 0)
326         {
327           s = format (s, "No packets in trace buffer\n");
328           goto done;
329         }
330
331       /* Sort them by increasing time. */
332       vec_sort_with_function (traces, trace_time_cmp);
333
334       for (i = 0; i < vec_len (traces); i++)
335         {
336           if (i == max)
337             {
338               char *warn = "Limiting display to %d packets."
339                            " To display more specify max.";
340               vlib_cli_output (vm, warn, max);
341               s = format (s, warn, max);
342               goto done;
343             }
344
345           s = format (s, "Packet %d\n%U\n\n", i + 1, format_vlib_trace, vm,
346                       traces[i]);
347         }
348
349     done:
350       vec_free (traces);
351
352       index++;
353     }
354
355   vlib_cli_output (vm, "%v", s);
356   vec_free (s);
357   return 0;
358 }
359
360 /* *INDENT-OFF* */
361 VLIB_CLI_COMMAND (show_trace_cli,static) = {
362   .path = "show trace",
363   .short_help = "Show trace buffer [max COUNT]",
364   .function = cli_show_trace_buffer,
365 };
366 /* *INDENT-ON* */
367
368 int vlib_enable_disable_pkt_trace_filter (int enable) __attribute__ ((weak));
369
370 int
371 vlib_enable_disable_pkt_trace_filter (int enable)
372 {
373   return 0;
374 }
375
376 void
377 vlib_trace_stop_and_clear (void)
378 {
379   vlib_enable_disable_pkt_trace_filter (0);     /* disble tracing */
380   clear_trace_buffer ();
381 }
382
383
384 void
385 trace_update_capture_options (u32 add, u32 node_index, u32 filter, u8 verbose)
386 {
387   vlib_trace_main_t *tm;
388   vlib_trace_node_t *tn;
389
390   if (add == ~0)
391     add = 50;
392
393   foreach_vlib_main ()
394     {
395       tm = &this_vlib_main->trace_main;
396       tm->verbose = verbose;
397       vec_validate (tm->nodes, node_index);
398       tn = tm->nodes + node_index;
399
400       /*
401        * Adding 0 makes no real sense, and there wa no other way
402        * to explicilty zero-out the limits and count, so make
403        * an "add 0" request really be "set to 0".
404        */
405       if (add == 0)
406           tn->limit = tn->count = 0;
407       else
408           tn->limit += add;
409     }
410
411   foreach_vlib_main ()
412     {
413       tm = &this_vlib_main->trace_main;
414       tm->trace_enable = 1;
415     }
416
417   vlib_enable_disable_pkt_trace_filter (! !filter);
418 }
419
420 static clib_error_t *
421 cli_add_trace_buffer (vlib_main_t * vm,
422                       unformat_input_t * input, vlib_cli_command_t * cmd)
423 {
424   unformat_input_t _line_input, *line_input = &_line_input;
425   vlib_node_t *node;
426   u32 node_index, add;
427   u8 verbose = 0;
428   int filter = 0;
429   clib_error_t *error = 0;
430
431   if (!unformat_user (input, unformat_line_input, line_input))
432     return 0;
433
434   if (vnet_trace_placeholder == 0)
435     vec_validate_aligned (vnet_trace_placeholder, 2048,
436                           CLIB_CACHE_LINE_BYTES);
437
438   while (unformat_check_input (line_input) != (uword) UNFORMAT_END_OF_INPUT)
439     {
440       if (unformat (line_input, "%U %d",
441                     unformat_vlib_node, vm, &node_index, &add))
442         ;
443       else if (unformat (line_input, "verbose"))
444         verbose = 1;
445       else if (unformat (line_input, "filter"))
446         filter = 1;
447       else
448         {
449           error = clib_error_create ("expected NODE COUNT, got `%U'",
450                                      format_unformat_error, line_input);
451           goto done;
452         }
453     }
454
455   node = vlib_get_node (vm, node_index);
456
457   if ((node->flags & VLIB_NODE_FLAG_TRACE_SUPPORTED) == 0)
458     {
459       error = clib_error_create ("node '%U' doesn't support per-node "
460                                  "tracing. There may be another way to "
461                                  "initiate trace on this node.",
462                                  format_vlib_node_name, vm, node_index);
463       goto done;
464     }
465
466   u32 filter_table = classify_get_trace_chain ();
467   if (filter && filter_table == ~0)
468     {
469       error = clib_error_create ("No packet trace filter configured...");
470       goto done;
471     }
472
473   trace_update_capture_options (add, node_index, filter, verbose);
474
475 done:
476   unformat_free (line_input);
477
478   return error;
479 }
480
481 /* *INDENT-OFF* */
482 VLIB_CLI_COMMAND (add_trace_cli,static) = {
483   .path = "trace add",
484   .short_help = "trace add <input-graph-node> <add'l-pkts-for-node-> [filter] [verbose]",
485   .function = cli_add_trace_buffer,
486 };
487 /* *INDENT-ON* */
488
489 /*
490  * Configure a filter for packet traces.
491  *
492  * This supplements the packet trace feature so that only packets matching
493  * the filter are included in the trace. Currently the only filter is to
494  * keep packets that include a certain node in the trace or exclude a certain
495  * node in the trace.
496  *
497  * The count of traced packets in the "trace add" command is still used to
498  * create a certain number of traces. The "trace filter" command specifies
499  * how many of those packets should be retained in the trace.
500  *
501  * For example, 1Mpps of traffic is arriving and one of those packets is being
502  * dropped. To capture the trace for only that dropped packet, you can do:
503  *     trace filter include error-drop 1
504  *     trace add dpdk-input 1000000
505  *     <wait one second>
506  *     show trace
507  *
508  * Note that the filter could be implemented by capturing all traces and just
509  * reducing traces displayed by the "show trace" function. But that would
510  * require a lot of memory for storing the traces, making that infeasible.
511  *
512  * To remove traces from the trace pool that do not include a certain node
513  * requires that the trace be "complete" before applying the filter. To
514  * accomplish this, the trace pool is filtered upon each iteraction of the
515  * main vlib loop. Doing so keeps the number of allocated traces down to a
516  * reasonably low number. This requires that tracing for a buffer is not
517  * performed after the vlib main loop interation completes. i.e. you can't
518  * save away a buffer temporarily then inject it back into the graph and
519  * expect that the trace_index is still valid (such as a traffic manager might
520  * do). A new trace buffer should be allocated for those types of packets.
521  *
522  * The filter can be extended to support multiple nodes and other match
523  * criteria (e.g. input sw_if_index, mac address) but for now just checks if
524  * a specified node is in the trace or not in the trace.
525  */
526
527 void
528 trace_filter_set (u32 node_index, u32 flag, u32 count)
529 {
530   foreach_vlib_main ()
531     {
532       vlib_trace_main_t *tm;
533
534       tm = &this_vlib_main->trace_main;
535       tm->filter_node_index = node_index;
536       tm->filter_flag = flag;
537       tm->filter_count = count;
538
539       /*
540        * Clear the trace limits to stop any in-progress tracing
541        * Prevents runaway trace allocations when the filter changes
542        * (or is removed)
543        */
544       vec_free (tm->nodes);
545     }
546 }
547
548
549 static clib_error_t *
550 cli_filter_trace (vlib_main_t * vm,
551                   unformat_input_t * input, vlib_cli_command_t * cmd)
552 {
553   u32 filter_node_index;
554   u32 filter_flag;
555   u32 filter_count;
556
557   if (unformat (input, "include %U %d",
558                 unformat_vlib_node, vm, &filter_node_index, &filter_count))
559     {
560       filter_flag = FILTER_FLAG_INCLUDE;
561     }
562   else if (unformat (input, "exclude %U %d",
563                      unformat_vlib_node, vm, &filter_node_index,
564                      &filter_count))
565     {
566       filter_flag = FILTER_FLAG_EXCLUDE;
567     }
568   else if (unformat (input, "none"))
569     {
570       filter_flag = FILTER_FLAG_NONE;
571       filter_node_index = 0;
572       filter_count = 0;
573     }
574   else
575     return
576       clib_error_create
577       ("expected 'include NODE COUNT' or 'exclude NODE COUNT' or 'none', got `%U'",
578        format_unformat_error, input);
579
580   trace_filter_set (filter_node_index, filter_flag, filter_count);
581
582   return 0;
583 }
584
585 /* *INDENT-OFF* */
586 VLIB_CLI_COMMAND (filter_trace_cli,static) = {
587   .path = "trace filter",
588   .short_help = "trace filter none | [include|exclude] NODE COUNT",
589   .function = cli_filter_trace,
590 };
591 /* *INDENT-ON* */
592
593 static clib_error_t *
594 cli_clear_trace_buffer (vlib_main_t * vm,
595                         unformat_input_t * input, vlib_cli_command_t * cmd)
596 {
597   vlib_trace_stop_and_clear ();
598   return 0;
599 }
600
601 /* *INDENT-OFF* */
602 VLIB_CLI_COMMAND (clear_trace_cli,static) = {
603   .path = "clear trace",
604   .short_help = "Clear trace buffer and free memory",
605   .function = cli_clear_trace_buffer,
606 };
607 /* *INDENT-ON* */
608
609 /* Placeholder function to get us linked in. */
610 void
611 vlib_trace_cli_reference (void)
612 {
613 }
614
615 void *
616 vlib_add_trace (vlib_main_t * vm,
617                 vlib_node_runtime_t * r, vlib_buffer_t * b, u32 n_data_bytes)
618 {
619   return vlib_add_trace_inline (vm, r, b, n_data_bytes);
620 }
621
622 vlib_is_packet_traced_fn_t *
623 vlib_is_packet_traced_function_from_name (const char *name)
624 {
625   vlib_trace_filter_function_registration_t *reg =
626     vlib_trace_filter_main.trace_filter_registration;
627   while (reg)
628     {
629       if (clib_strcmp (reg->name, name) == 0)
630         break;
631       reg = reg->next;
632     }
633   if (!reg)
634     return 0;
635   return reg->function;
636 }
637
638 vlib_is_packet_traced_fn_t *
639 vlib_is_packet_traced_default_function ()
640 {
641   vlib_trace_filter_function_registration_t *reg =
642     vlib_trace_filter_main.trace_filter_registration;
643   vlib_trace_filter_function_registration_t *tmp_reg = reg;
644   while (reg)
645     {
646       if (reg->priority > tmp_reg->priority)
647         tmp_reg = reg;
648       reg = reg->next;
649     }
650   return tmp_reg->function;
651 }
652
653 static clib_error_t *
654 vlib_trace_filter_function_init (vlib_main_t *vm)
655 {
656   vlib_is_packet_traced_fn_t *default_fn =
657     vlib_is_packet_traced_default_function ();
658   foreach_vlib_main ()
659     {
660       vlib_trace_main_t *tm = &this_vlib_main->trace_main;
661       tm->current_trace_filter_function = default_fn;
662     }
663   return 0;
664 }
665
666 vlib_trace_filter_main_t vlib_trace_filter_main;
667
668 VLIB_INIT_FUNCTION (vlib_trace_filter_function_init);
669
670 static clib_error_t *
671 show_trace_filter_function (vlib_main_t *vm, unformat_input_t *input,
672                             vlib_cli_command_t *cmd)
673 {
674   vlib_trace_filter_main_t *tfm = &vlib_trace_filter_main;
675   vlib_trace_main_t *tm = &vm->trace_main;
676   vlib_is_packet_traced_fn_t *current_trace_filter_fn =
677     tm->current_trace_filter_function;
678   vlib_trace_filter_function_registration_t *reg =
679     tfm->trace_filter_registration;
680
681   while (reg)
682     {
683       vlib_cli_output (vm, "%sname:%s description: %s priority: %u",
684                        reg->function == current_trace_filter_fn ? "(*) " : "",
685                        reg->name, reg->description, reg->priority);
686       reg = reg->next;
687     }
688   return 0;
689 }
690
691 VLIB_CLI_COMMAND (show_trace_filter_function_cli, static) = {
692   .path = "show trace filter function",
693   .short_help = "show trace filter function",
694   .function = show_trace_filter_function,
695 };
696
697 uword
698 unformat_vlib_trace_filter_function (unformat_input_t *input, va_list *args)
699 {
700   vlib_is_packet_traced_fn_t **res =
701     va_arg (*args, vlib_is_packet_traced_fn_t **);
702   vlib_trace_filter_main_t *tfm = &vlib_trace_filter_main;
703
704   vlib_trace_filter_function_registration_t *reg =
705     tfm->trace_filter_registration;
706   while (reg)
707     {
708       if (unformat (input, reg->name))
709         {
710           *res = reg->function;
711           return 1;
712         }
713       reg = reg->next;
714     }
715   return 0;
716 }
717
718 void
719 vlib_set_trace_filter_function (vlib_is_packet_traced_fn_t *x)
720 {
721   foreach_vlib_main ()
722     {
723       this_vlib_main->trace_main.current_trace_filter_function = x;
724     }
725 }
726
727 static clib_error_t *
728 set_trace_filter_function (vlib_main_t *vm, unformat_input_t *input,
729                            vlib_cli_command_t *cmd)
730 {
731   unformat_input_t _line_input, *line_input = &_line_input;
732   vlib_is_packet_traced_fn_t *res = 0;
733   clib_error_t *error = 0;
734
735   if (!unformat_user (input, unformat_line_input, line_input))
736     return 0;
737
738   while (unformat_check_input (line_input) != (uword) UNFORMAT_END_OF_INPUT)
739     {
740       if (unformat (line_input, "%U", unformat_vlib_trace_filter_function,
741                     &res))
742         ;
743       else
744         {
745           error = clib_error_create (
746             "expected valid trace filter function, got `%U'",
747             format_unformat_error, line_input);
748           goto done;
749         }
750     }
751   vlib_set_trace_filter_function (res);
752
753 done:
754   unformat_free (line_input);
755
756   return error;
757 }
758
759 VLIB_CLI_COMMAND (set_trace_filter_function_cli, static) = {
760   .path = "set trace filter function",
761   .short_help = "set trace filter function <func_name>",
762   .function = set_trace_filter_function,
763 };
764 /*
765  * fd.io coding-style-patch-verification: ON
766  *
767  * Local Variables:
768  * eval: (c-set-style "gnu")
769  * End:
770  */