api: API trace improvements
[vpp.git] / src / vlibmemory / vlib_api_cli.c
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2018 Cisco and/or its affiliates.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *------------------------------------------------------------------
16  */
17
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22
23 #include <vlibapi/api.h>
24 #include <vlibmemory/api.h>
25
26 static clib_error_t *
27 vl_api_show_histogram_command (vlib_main_t * vm,
28                                unformat_input_t * input,
29                                vlib_cli_command_t * cli_cmd)
30 {
31   u64 total_counts = 0;
32   int i;
33
34   for (i = 0; i < SLEEP_N_BUCKETS; i++)
35     {
36       total_counts += vector_rate_histogram[i];
37     }
38
39   if (total_counts == 0)
40     {
41       vlib_cli_output (vm, "No control-plane activity.");
42       return 0;
43     }
44
45 #define _(n)                                                    \
46     do {                                                        \
47         f64 percent;                                            \
48         percent = ((f64) vector_rate_histogram[SLEEP_##n##_US]) \
49             / (f64) total_counts;                               \
50         percent *= 100.0;                                       \
51         vlib_cli_output (vm, "Sleep %3d us: %llu, %.2f%%",n,    \
52                          vector_rate_histogram[SLEEP_##n##_US], \
53                          percent);                              \
54     } while (0);
55   foreach_histogram_bucket;
56 #undef _
57
58   return 0;
59 }
60
61 /*?
62  * Display the binary api sleep-time histogram
63 ?*/
64 /* *INDENT-OFF* */
65 VLIB_CLI_COMMAND (cli_show_api_histogram_command, static) =
66 {
67   .path = "show api histogram",
68   .short_help = "show api histogram",
69   .function = vl_api_show_histogram_command,
70 };
71 /* *INDENT-ON* */
72
73 static clib_error_t *
74 vl_api_clear_histogram_command (vlib_main_t * vm,
75                                 unformat_input_t * input,
76                                 vlib_cli_command_t * cli_cmd)
77 {
78   int i;
79
80   for (i = 0; i < SLEEP_N_BUCKETS; i++)
81     vector_rate_histogram[i] = 0;
82   return 0;
83 }
84
85 /*?
86  * Clear the binary api sleep-time histogram
87 ?*/
88 /* *INDENT-OFF* */
89 VLIB_CLI_COMMAND (cli_clear_api_histogram_command, static) =
90 {
91   .path = "clear api histogram",
92   .short_help = "clear api histogram",
93   .function = vl_api_clear_histogram_command,
94 };
95 /* *INDENT-ON* */
96
97 static clib_error_t *
98 vl_api_client_command (vlib_main_t * vm,
99                        unformat_input_t * input, vlib_cli_command_t * cli_cmd)
100 {
101   vl_api_registration_t **regpp, *regp;
102   svm_queue_t *q;
103   char *health;
104   api_main_t *am = vlibapi_get_main ();
105   u32 *confused_indices = 0;
106
107   if (!pool_elts (am->vl_clients))
108     goto socket_clients;
109   vlib_cli_output (vm, "Shared memory clients");
110   vlib_cli_output (vm, "%20s %8s %14s %18s %s",
111                    "Name", "PID", "Queue Length", "Queue VA", "Health");
112
113   /* *INDENT-OFF* */
114   pool_foreach (regpp, am->vl_clients)
115    {
116     regp = *regpp;
117
118     if (regp)
119       {
120         if (regp->unanswered_pings > 0)
121           health = "questionable";
122         else
123           health = "OK";
124
125         q = regp->vl_input_queue;
126
127         vlib_cli_output (vm, "%20s %8d %14d 0x%016llx %s\n",
128                          regp->name, q->consumer_pid, q->cursize,
129                          q, health);
130       }
131     else
132       {
133         clib_warning ("NULL client registration index %d",
134                       regpp - am->vl_clients);
135         vec_add1 (confused_indices, regpp - am->vl_clients);
136       }
137   }
138   /* *INDENT-ON* */
139
140   /* This should "never happen," but if it does, fix it... */
141   if (PREDICT_FALSE (vec_len (confused_indices) > 0))
142     {
143       int i;
144       for (i = 0; i < vec_len (confused_indices); i++)
145         {
146           pool_put_index (am->vl_clients, confused_indices[i]);
147         }
148     }
149   vec_free (confused_indices);
150
151   if (am->missing_clients)
152     vlib_cli_output (vm, "%u messages with missing clients",
153                      am->missing_clients);
154 socket_clients:
155   vl_sock_api_dump_clients (vm, am);
156
157   return 0;
158 }
159
160 static clib_error_t *
161 vl_api_status_command (vlib_main_t * vm,
162                        unformat_input_t * input, vlib_cli_command_t * cli_cmd)
163 {
164   api_main_t *am = vlibapi_get_main ();
165
166   /* check if rx_trace and tx_trace are not null pointers */
167   if (am->rx_trace == 0)
168     {
169       vlib_cli_output (vm, "RX Trace disabled\n");
170     }
171   else
172     {
173       if (am->rx_trace->enabled == 0)
174         vlib_cli_output (vm, "RX Trace disabled\n");
175       else
176         vlib_cli_output (vm, "RX Trace enabled\n");
177     }
178
179   if (am->tx_trace == 0)
180     {
181       vlib_cli_output (vm, "TX Trace disabled\n");
182     }
183   else
184     {
185       if (am->tx_trace->enabled == 0)
186         vlib_cli_output (vm, "TX Trace disabled\n");
187       else
188         vlib_cli_output (vm, "TX Trace enabled\n");
189     }
190
191   return 0;
192 }
193
194 /* *INDENT-OFF* */
195 VLIB_CLI_COMMAND (cli_show_api_command, static) =
196 {
197   .path = "show api",
198   .short_help = "Show API information",
199 };
200 /* *INDENT-ON* */
201
202 /*?
203  * Display current api client connections
204 ?*/
205 /* *INDENT-OFF* */
206 VLIB_CLI_COMMAND (cli_show_api_clients_command, static) =
207 {
208   .path = "show api clients",
209   .short_help = "Client information",
210   .function = vl_api_client_command,
211 };
212 /* *INDENT-ON* */
213
214 /*?
215  * Display the current api message tracing status
216 ?*/
217 /* *INDENT-OFF* */
218 VLIB_CLI_COMMAND (cli_show_api_status_command, static) =
219 {
220   .path = "show api trace-status",
221   .short_help = "Display API trace status",
222   .function = vl_api_status_command,
223 };
224 /* *INDENT-ON* */
225
226 static clib_error_t *
227 vl_api_message_table_command (vlib_main_t * vm,
228                               unformat_input_t * input,
229                               vlib_cli_command_t * cli_cmd)
230 {
231   api_main_t *am = vlibapi_get_main ();
232   int i;
233   int verbose = 0;
234
235   if (unformat (input, "verbose"))
236     verbose = 1;
237
238
239   if (verbose == 0)
240     vlib_cli_output (vm, "%-4s %s", "ID", "Name");
241   else
242     vlib_cli_output (vm, "%-4s %-40s %6s %7s", "ID", "Name", "Bounce",
243                      "MP-safe");
244
245   for (i = 1; i < vec_len (am->msg_names); i++)
246     {
247       if (verbose == 0)
248         {
249           vlib_cli_output (vm, "%-4d %s", i,
250                            am->msg_names[i] ? am->msg_names[i] :
251                            "  [no handler]");
252         }
253       else
254         {
255           vlib_cli_output (vm, "%-4d %-40s %6d %7d", i,
256                            am->msg_names[i] ? am->msg_names[i] :
257                            "  [no handler]", am->message_bounce[i],
258                            am->is_mp_safe[i]);
259         }
260     }
261
262   return 0;
263 }
264
265 /*?
266  * Display the current api message decode tables
267 ?*/
268 /* *INDENT-OFF* */
269 VLIB_CLI_COMMAND (cli_show_api_message_table_command, static) =
270 {
271   .path = "show api message-table",
272   .short_help = "Message Table",
273   .function = vl_api_message_table_command,
274 };
275 /* *INDENT-ON* */
276
277 static int
278 range_compare (vl_api_msg_range_t * a0, vl_api_msg_range_t * a1)
279 {
280   int len0, len1, clen;
281
282   len0 = vec_len (a0->name);
283   len1 = vec_len (a1->name);
284   clen = len0 < len1 ? len0 : len1;
285   return (strncmp ((char *) a0->name, (char *) a1->name, clen));
286 }
287
288 static u8 *
289 format_api_msg_range (u8 * s, va_list * args)
290 {
291   vl_api_msg_range_t *rp = va_arg (*args, vl_api_msg_range_t *);
292
293   if (rp == 0)
294     s = format (s, "%-50s%9s%9s", "Name", "First-ID", "Last-ID");
295   else
296     s = format (s, "%-50s%9d%9d", rp->name, rp->first_msg_id,
297                 rp->last_msg_id);
298
299   return s;
300 }
301
302 static clib_error_t *
303 vl_api_show_plugin_command (vlib_main_t * vm,
304                             unformat_input_t * input,
305                             vlib_cli_command_t * cli_cmd)
306 {
307   api_main_t *am = vlibapi_get_main ();
308   vl_api_msg_range_t *rp = 0;
309   int i;
310
311   if (vec_len (am->msg_ranges) == 0)
312     {
313       vlib_cli_output (vm, "No plugin API message ranges configured...");
314       return 0;
315     }
316
317   rp = vec_dup (am->msg_ranges);
318
319   vec_sort_with_function (rp, range_compare);
320
321   vlib_cli_output (vm, "Plugin API message ID ranges...\n");
322   vlib_cli_output (vm, "%U", format_api_msg_range, 0 /* header */ );
323
324   for (i = 0; i < vec_len (rp); i++)
325     vlib_cli_output (vm, "%U", format_api_msg_range, rp + i);
326
327   vec_free (rp);
328
329   return 0;
330 }
331
332 /*?
333  * Display the plugin binary API message range table
334 ?*/
335 /* *INDENT-OFF* */
336 VLIB_CLI_COMMAND (cli_show_api_plugin_command, static) =
337 {
338   .path = "show api plugin",
339   .short_help = "show api plugin",
340   .function = vl_api_show_plugin_command,
341 };
342 /* *INDENT-ON* */
343
344 typedef enum
345 {
346   DUMP,
347   DUMP_JSON,
348   REPLAY,
349   INITIALIZERS,
350 } vl_api_replay_t;
351
352 u8 *
353 format_vl_msg_api_trace_status (u8 * s, va_list * args)
354 {
355   api_main_t *am = va_arg (*args, api_main_t *);
356   vl_api_trace_which_t which = va_arg (*args, vl_api_trace_which_t);
357   vl_api_trace_t *tp;
358   char *trace_name;
359
360   switch (which)
361     {
362     case VL_API_TRACE_TX:
363       tp = am->tx_trace;
364       trace_name = "TX trace";
365       break;
366
367     case VL_API_TRACE_RX:
368       tp = am->rx_trace;
369       trace_name = "RX trace";
370       break;
371
372     default:
373       abort ();
374     }
375
376   if (tp == 0)
377     {
378       s = format (s, "%s: not yet configured.\n", trace_name);
379       return s;
380     }
381
382   s = format (s, "%s: used %d of %d items, %s enabled, %s wrapped\n",
383               trace_name, vec_len (tp->traces), tp->nitems,
384               tp->enabled ? "is" : "is not", tp->wrapped ? "has" : "has not");
385   return s;
386 }
387
388 static void
389 vl_msg_api_process_file (vlib_main_t * vm, u8 * filename,
390                          u32 first_index, u32 last_index,
391                          vl_api_replay_t which)
392 {
393   vl_api_trace_file_header_t *hp;
394   int i, fd;
395   u16 *msgid_vec = 0;
396   struct stat statb;
397   size_t file_size;
398   u8 *msg;
399   api_main_t *am = vlibapi_get_main ();
400   u8 *tmpbuf = 0;
401   u32 nitems, nitems_msgtbl;
402
403   fd = open ((char *) filename, O_RDONLY);
404
405   if (fd < 0)
406     {
407       vlib_cli_output (vm, "Couldn't open %s\n", filename);
408       return;
409     }
410
411   if (fstat (fd, &statb) < 0)
412     {
413       vlib_cli_output (vm, "Couldn't stat %s\n", filename);
414       close (fd);
415       return;
416     }
417
418   if (!(statb.st_mode & S_IFREG) || (statb.st_size < sizeof (*hp)))
419     {
420       vlib_cli_output (vm, "File not plausible: %s\n", filename);
421       close (fd);
422       return;
423     }
424
425   file_size = statb.st_size;
426   file_size = (file_size + 4095) & ~(4095);
427
428   hp = mmap (0, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
429
430   if (hp == (vl_api_trace_file_header_t *) MAP_FAILED)
431     {
432       vlib_cli_output (vm, "mmap failed: %s\n", filename);
433       close (fd);
434       return;
435     }
436   close (fd);
437
438   CLIB_MEM_UNPOISON (hp, file_size);
439
440   nitems = ntohl (hp->nitems);
441
442   if (last_index == (u32) ~ 0)
443     {
444       last_index = nitems - 1;
445     }
446
447   if (first_index >= nitems || last_index >= nitems)
448     {
449       vlib_cli_output (vm, "Range (%d, %d) outside file range (0, %d)\n",
450                        first_index, last_index, nitems - 1);
451       munmap (hp, file_size);
452       return;
453     }
454   if (hp->wrapped)
455     vlib_cli_output (vm,
456                      "Note: wrapped/incomplete trace, results may vary\n");
457
458   size_t file_size_left = file_size;
459
460 #define assert_size(size_left, s)                                             \
461   do                                                                          \
462     {                                                                         \
463       if ((s) >= size_left)                                                   \
464         {                                                                     \
465           vlib_cli_output (vm, "corrupted file");                             \
466           munmap (hp, file_size);                                             \
467           vec_free (msgid_vec);                                               \
468           return;                                                             \
469         }                                                                     \
470       size_left -= s;                                                         \
471     }                                                                         \
472   while (0);
473
474   assert_size (file_size_left, sizeof (hp[0]));
475   msg = (u8 *) (hp + 1);
476
477   serialize_main_t _sm, *sm = &_sm;
478   u32 msgtbl_size = ntohl (hp->msgtbl_size);
479   u8 *name_and_crc;
480
481   assert_size (file_size_left, msgtbl_size);
482
483   unserialize_open_data (sm, msg, msgtbl_size);
484   unserialize_integer (sm, &nitems_msgtbl, sizeof (u32));
485
486   for (i = 0; i < nitems_msgtbl; i++)
487     {
488       u16 msg_index = unserialize_likely_small_unsigned_integer (sm);
489       unserialize_cstring (sm, (char **) &name_and_crc);
490       u16 msg_index2 = vl_msg_api_get_msg_index (name_and_crc);
491       vec_validate (msgid_vec, msg_index);
492       msgid_vec[msg_index] = msg_index2;
493     }
494
495   msg += msgtbl_size;
496
497   for (i = 0; i < first_index; i++)
498     {
499       trace_cfg_t *cfgp;
500       int size;
501       u16 msg_id;
502
503       assert_size (file_size_left, sizeof (u32));
504       size = clib_host_to_net_u32 (*(u32 *) msg);
505       msg += sizeof (u32);
506
507       assert_size (file_size_left, size);
508       msg_id = ntohs (*((u16 *) msg));
509       if (msg_id < vec_len (msgid_vec))
510         msg_id = msgid_vec[msg_id];
511       cfgp = am->api_trace_cfg + msg_id;
512       if (!cfgp)
513         {
514           vlib_cli_output (vm, "Ugh: msg id %d no trace config\n", msg_id);
515           munmap (hp, file_size);
516           vec_free (msgid_vec);
517           return;
518         }
519       msg += size;
520     }
521
522   if (which == REPLAY)
523     am->replay_in_progress = 1;
524
525   for (; i <= last_index; i++)
526     {
527       trace_cfg_t *cfgp;
528       u16 msg_id;
529       int size;
530
531       if (which == DUMP)
532         vlib_cli_output (vm, "---------- trace %d -----------\n", i);
533
534       size = clib_host_to_net_u32 (*(u32 *) msg);
535       msg += sizeof (u32);
536
537       msg_id = ntohs (*((u16 *) msg));
538       if (msg_id < vec_len (msgid_vec))
539         {
540           msg_id = msgid_vec[msg_id];
541         }
542
543       cfgp = am->api_trace_cfg + msg_id;
544       if (!cfgp)
545         {
546           vlib_cli_output (vm, "Ugh: msg id %d no trace config\n", msg_id);
547           munmap (hp, file_size);
548           vec_free (tmpbuf);
549           am->replay_in_progress = 0;
550           return;
551         }
552
553       /* Copy the buffer (from the read-only mmap'ed file) */
554       vec_validate (tmpbuf, size - 1 + sizeof (uword));
555       clib_memcpy (tmpbuf + sizeof (uword), msg, size);
556       clib_memset (tmpbuf, 0xf, sizeof (uword));
557
558       /*
559        * Endian swap if needed. All msg data is supposed to be in
560        * network byte order.
561        */
562       if (((which == DUMP || which == DUMP_JSON) &&
563            clib_arch_is_little_endian))
564         {
565           void (*endian_fp) (void *);
566           if (msg_id >= vec_len (am->msg_endian_handlers)
567               || (am->msg_endian_handlers[msg_id] == 0))
568             {
569               vlib_cli_output (vm, "Ugh: msg id %d no endian swap\n", msg_id);
570               munmap (hp, file_size);
571               vec_free (tmpbuf);
572               am->replay_in_progress = 0;
573               return;
574             }
575           endian_fp = am->msg_endian_handlers[msg_id];
576           (*endian_fp) (tmpbuf + sizeof (uword));
577         }
578
579       /* msg_id always in network byte order */
580       if (clib_arch_is_little_endian)
581         {
582           u16 *msg_idp = (u16 *) (tmpbuf + sizeof (uword));
583           *msg_idp = msg_id;
584         }
585
586       switch (which)
587         {
588         case DUMP_JSON:
589           if (msg_id < vec_len (am->msg_print_json_handlers) &&
590               am->msg_print_json_handlers[msg_id])
591             {
592               u8 *(*print_fp) (void *, void *);
593
594               print_fp = (void *) am->msg_print_json_handlers[msg_id];
595               (*print_fp) (tmpbuf + sizeof (uword), vm);
596             }
597           else
598             {
599               vlib_cli_output (vm, "Skipping msg id %d: no JSON print fcn\n",
600                                msg_id);
601               break;
602             }
603           break;
604
605         case DUMP:
606           if (msg_id < vec_len (am->msg_print_handlers) &&
607               am->msg_print_handlers[msg_id])
608             {
609               u8 *(*print_fp) (void *, void *);
610
611               print_fp = (void *) am->msg_print_handlers[msg_id];
612               (*print_fp) (tmpbuf + sizeof (uword), vm);
613             }
614           else
615             {
616               vlib_cli_output (vm, "Skipping msg id %d: no print fcn\n",
617                                msg_id);
618               break;
619             }
620           break;
621
622         case INITIALIZERS:
623           if (msg_id < vec_len (am->msg_print_handlers) &&
624               am->msg_print_handlers[msg_id])
625             {
626               u8 *s;
627               int j;
628               u8 *(*print_fp) (void *, void *);
629
630               print_fp = (void *) am->msg_print_handlers[msg_id];
631
632               vlib_cli_output (vm, "/*");
633
634               (*print_fp) (tmpbuf + sizeof (uword), vm);
635               vlib_cli_output (vm, "*/\n");
636
637               s = format (0, "static u8 * vl_api_%s_%d[%d] = {",
638                           am->msg_names[msg_id], i,
639                           am->api_trace_cfg[msg_id].size);
640
641               for (j = 0; j < am->api_trace_cfg[msg_id].size; j++)
642                 {
643                   if ((j & 7) == 0)
644                     s = format (s, "\n    ");
645                   s = format (s, "0x%02x,", tmpbuf[sizeof (uword) + j]);
646                 }
647               s = format (s, "\n};\n%c", 0);
648               vlib_cli_output (vm, (char *) s);
649               vec_free (s);
650             }
651           break;
652
653         case REPLAY:
654           if (msg_id < vec_len (am->msg_print_handlers) &&
655               am->msg_print_handlers[msg_id] && cfgp->replay_enable)
656             {
657               void (*handler) (void *, vlib_main_t *);
658
659               handler = (void *) am->msg_handlers[msg_id];
660
661               if (!am->is_mp_safe[msg_id])
662                 vl_msg_api_barrier_sync ();
663               (*handler) (tmpbuf + sizeof (uword), vm);
664               if (!am->is_mp_safe[msg_id])
665                 vl_msg_api_barrier_release ();
666             }
667           else
668             {
669               if (cfgp->replay_enable)
670                 vlib_cli_output (vm, "Skipping msg id %d: no handler\n",
671                                  msg_id);
672               break;
673             }
674           break;
675         }
676
677       _vec_len (tmpbuf) = 0;
678       msg += size;
679     }
680
681   munmap (hp, file_size);
682   vec_free (tmpbuf);
683   vec_free (msgid_vec);
684   am->replay_in_progress = 0;
685 }
686
687 static int
688 file_exists (u8 *fname)
689 {
690   FILE *fp = 0;
691   fp = fopen ((char *) fname, "r");
692   if (fp)
693     {
694       fclose (fp);
695       return 1;
696     }
697   return 0;
698 }
699
700 typedef struct
701 {
702   vlib_main_t *vm;
703   u8 is_json;
704 } vl_msg_print_args;
705
706 static int
707 vl_msg_print_trace (u8 *msg, void *ctx)
708 {
709   vl_msg_print_args *a = ctx;
710   api_main_t *am = vlibapi_get_main ();
711   u16 msg_id = ntohs (*((u16 *) msg));
712   void (*print_fp) (void *, void *);
713   void (**handlers) (void *, void *);
714   u8 is_json = a->is_json;
715   u8 *tmpbuf = 0;
716
717   if (clib_arch_is_little_endian)
718     {
719       u32 msg_length = vec_len (msg);
720       vec_validate (tmpbuf, msg_length - 1);
721       clib_memcpy_fast (tmpbuf, msg, msg_length);
722       msg = tmpbuf;
723
724       void (*endian_fp) (void *);
725       endian_fp = am->msg_endian_handlers[msg_id];
726       (*endian_fp) (tmpbuf);
727     }
728
729   if (is_json)
730     handlers = am->msg_print_json_handlers;
731   else
732     handlers = am->msg_print_handlers;
733
734   if (msg_id < vec_len (handlers) && handlers[msg_id])
735     {
736       print_fp = (void *) handlers[msg_id];
737       (*print_fp) (msg, a->vm);
738     }
739   else
740     {
741       vlib_cli_output (a->vm, "Skipping msg id %d: no print fcn\n", msg_id);
742     }
743
744   vec_free (tmpbuf);
745   return 0;
746 }
747
748 static int
749 vl_msg_api_dump_trace (vlib_main_t *vm, vl_api_trace_which_t which, u8 is_json)
750 {
751   api_main_t *am = vlibapi_get_main ();
752   vl_api_trace_t *tp;
753
754   switch (which)
755     {
756     case VL_API_TRACE_TX:
757       tp = am->tx_trace;
758       break;
759     case VL_API_TRACE_RX:
760       tp = am->rx_trace;
761       break;
762     default:
763       return -1;
764     }
765
766   if (tp == 0 || tp->nitems == 0 || vec_len (tp->traces) == 0)
767     return -1;
768
769   vl_msg_print_args args;
770   clib_memset (&args, 0, sizeof (args));
771   args.is_json = is_json;
772   args.vm = vm;
773   vl_msg_traverse_trace (tp, vl_msg_print_trace, &args);
774
775   return 0;
776 }
777
778 static char *
779 vl_msg_read_file (FILE *f)
780 {
781   const size_t bufsize = 1024;
782   char *buf[bufsize], *v = 0;
783   size_t n;
784
785   while ((n = fread (buf, 1, bufsize, f)))
786     vec_add (v, buf, n);
787
788   return v;
789 }
790
791 static u16
792 vl_msg_find_id_by_name_and_crc (vlib_main_t *vm, api_main_t *am, char *name)
793 {
794   uword *p;
795   p = hash_get_mem (am->msg_index_by_name_and_crc, name);
796   if (!p)
797     return (u16) ~0;
798
799   return p[0];
800 }
801
802 static u16
803 vl_msg_find_id_by_name (vlib_main_t *vm, api_main_t *am, char *name)
804 {
805   uword *p;
806
807   if (!am->msg_id_by_name)
808     {
809       vlib_cli_output (vm, "message id table not yet initialized!\n");
810       return (u16) ~0;
811     }
812
813   p = hash_get_mem (am->msg_id_by_name, name);
814   if (!p)
815     return (u16) ~0;
816
817   return p[0];
818 }
819
820 static int
821 vl_msg_exec_json_command (vlib_main_t *vm, cJSON *o)
822 {
823   api_main_t *am = vlibapi_get_main ();
824   u16 msg_id;
825   void *(*fromjson) (cJSON *, int *);
826   int len = 0, rv = -1;
827   trace_cfg_t *cfgp;
828   u8 *msg = 0;
829
830   cJSON *msg_id_obj = cJSON_GetObjectItem (o, "_msgname");
831   if (!msg_id_obj)
832     {
833       vlib_cli_output (vm, "Missing '_msgname' element!\n");
834       return rv;
835     }
836   char *name = cJSON_GetStringValue (msg_id_obj);
837
838   cJSON *crc_obj = cJSON_GetObjectItem (o, "_crc");
839   if (!msg_id_obj)
840     {
841       vlib_cli_output (vm, "Missing '_crc' element!\n");
842       return rv;
843     }
844   char *crc = cJSON_GetStringValue (crc_obj);
845   u8 proc_warning = 0;
846
847   u8 *name_crc = format (0, "%s_%s%c", name, crc, 0);
848   msg_id = vl_msg_find_id_by_name_and_crc (vm, am, (char *) name_crc);
849   if (msg_id == (u16) ~0)
850     {
851       msg_id = vl_msg_find_id_by_name (vm, am, name);
852       if (msg_id == (u16) ~0)
853         {
854           vlib_cli_output (vm, "unknown msg id %d!\n", msg_id);
855           vec_free (name_crc);
856           return rv;
857         }
858       proc_warning = 1;
859     }
860   vec_free (name_crc);
861
862   cfgp = am->api_trace_cfg + msg_id;
863   if (!cfgp)
864     {
865       vlib_cli_output (vm, "msg id %d no trace config\n", msg_id);
866       return rv;
867     }
868
869   if (cfgp->replay_enable)
870     {
871
872       if (proc_warning)
873         vlib_cli_output (vm, "warning: msg %d has different signature\n");
874
875       fromjson = am->msg_fromjson_handlers[msg_id];
876       if (!fromjson)
877         {
878           vlib_cli_output (vm, "missing fromjson convert function! id %d\n",
879                            msg_id);
880           return rv;
881         }
882
883       msg = (u8 *) fromjson (o, &len);
884       if (!msg)
885         {
886           vlib_cli_output (vm, "failed to convert JSON (msg id %d)!\n",
887                            msg_id);
888           return rv;
889         }
890
891       if (clib_arch_is_little_endian)
892         {
893           void (*endian_fp) (void *);
894           endian_fp = am->msg_endian_handlers[msg_id];
895           (*endian_fp) (msg);
896         }
897
898       void (*handler) (void *, vlib_main_t *);
899       handler = (void *) am->msg_handlers[msg_id];
900       if (!handler)
901         {
902           vlib_cli_output (vm, "no handler for msg id %d!\n", msg_id);
903           goto end;
904         }
905
906       if (!am->is_mp_safe[msg_id])
907         vl_msg_api_barrier_sync ();
908       (*handler) (msg, vm);
909       if (!am->is_mp_safe[msg_id])
910         vl_msg_api_barrier_release ();
911     }
912
913   rv = 0;
914 end:
915   if (msg)
916     cJSON_free (msg);
917   return rv;
918 }
919
920 static void
921 vl_msg_replay_json (vlib_main_t *vm, u8 *filename)
922 {
923   api_main_t *am = vlibapi_get_main ();
924   cJSON *o = 0;
925   int rv = 0;
926   FILE *f = fopen ((char *) filename, "r");
927
928   if (!f)
929     {
930       vlib_cli_output (vm, "failed to open %s!\n", filename);
931       return;
932     }
933
934   char *buf = vl_msg_read_file (f);
935   fclose (f);
936
937   o = cJSON_Parse (buf);
938   vec_free (buf);
939   if (!o)
940     {
941       vlib_cli_output (vm, "%s: Failed parsing JSON input: %s\n", filename,
942                        cJSON_GetErrorPtr ());
943       return;
944     }
945
946   if (cJSON_IsArray (o))
947     {
948       am->replay_in_progress = 1;
949       size_t size = cJSON_GetArraySize (o);
950       for (int i = 0; i < size; i++)
951         {
952           rv = vl_msg_exec_json_command (vm, cJSON_GetArrayItem (o, i));
953           if (rv < 0)
954             {
955               am->replay_in_progress = 0;
956               break;
957             }
958         }
959     }
960   else
961     {
962       rv = vl_msg_exec_json_command (vm, o);
963     }
964
965   if (rv < 0)
966     vlib_cli_output (vm, "error during replaying API trace");
967
968   cJSON_Delete (o);
969 }
970
971 static void
972 vl_msg_dump_file_json (vlib_main_t *vm, u8 *filename)
973 {
974   FILE *f = fopen ((char *) filename, "r");
975   char *buf;
976
977   if (!f)
978     {
979       vlib_cli_output (vm, "failed to open %s!\n", filename);
980       return;
981     }
982
983   buf = vl_msg_read_file (f);
984   fclose (f);
985
986   if (!buf)
987     {
988       vlib_cli_output (vm, "no content in %s!\n", filename);
989       return;
990     }
991
992   vlib_cli_output (vm, buf);
993   vec_free (buf);
994 }
995
996 /** api_trace_command_fn - control the binary API trace / replay feature
997
998     Note: this command MUST be marked thread-safe. Replay with
999     multiple worker threads depends in many cases on worker thread
1000     graph replica maintenance. If we (implicitly) assert a worker
1001     thread barrier at the debug CLI level, all graph replica changes
1002     are deferred until the replay operation completes. If an interface
1003     is deleted, the wheels fall off.
1004  */
1005
1006 static clib_error_t *
1007 api_trace_command_fn (vlib_main_t * vm,
1008                       unformat_input_t * input, vlib_cli_command_t * cmd)
1009 {
1010   unformat_input_t _line_input, *line_input = &_line_input;
1011   u32 nitems = 256 << 10;
1012   api_main_t *am = vlibapi_get_main ();
1013   vl_api_trace_which_t which = VL_API_TRACE_RX;
1014   u8 *filename = 0;
1015   u8 *chroot_filename = 0;
1016   u32 first = 0;
1017   u32 last = (u32) ~ 0;
1018   FILE *fp;
1019   int rv;
1020
1021   /* Get a line of input. */
1022   if (!unformat_user (input, unformat_line_input, line_input))
1023     return 0;
1024
1025   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
1026     {
1027       if (unformat (line_input, "on") || unformat (line_input, "enable"))
1028         {
1029           if (unformat (line_input, "nitems %d", &nitems))
1030             ;
1031           vlib_worker_thread_barrier_sync (vm);
1032           vl_msg_api_trace_configure (am, which, nitems);
1033           vl_msg_api_trace_onoff (am, which, 1 /* on */ );
1034           vlib_worker_thread_barrier_release (vm);
1035         }
1036       else if (unformat (line_input, "off"))
1037         {
1038           vlib_worker_thread_barrier_sync (vm);
1039           vl_msg_api_trace_onoff (am, which, 0);
1040           vlib_worker_thread_barrier_release (vm);
1041         }
1042       else if (unformat (line_input, "save-json %s", &filename))
1043         {
1044           if (strstr ((char *) filename, "..") ||
1045               index ((char *) filename, '/'))
1046             {
1047               vlib_cli_output (vm, "illegal characters in filename '%s'",
1048                                filename);
1049               goto out;
1050             }
1051
1052           chroot_filename = format (0, "/tmp/%s%c", filename, 0);
1053
1054           vec_free (filename);
1055
1056           if (file_exists (chroot_filename))
1057             {
1058               vlib_cli_output (vm, "file exists: %s\n", chroot_filename);
1059               goto out;
1060             }
1061
1062           fp = fopen ((char *) chroot_filename, "w");
1063           if (fp == NULL)
1064             {
1065               vlib_cli_output (vm, "Couldn't create %s\n", chroot_filename);
1066               goto out;
1067             }
1068           vlib_worker_thread_barrier_sync (vm);
1069           rv = vl_msg_api_trace_save (am, which, fp, 1);
1070           if (rv == -1)
1071             vlib_cli_output (vm, "API Trace data not present\n");
1072           else if (rv < 0)
1073             vlib_cli_output (vm, "failed to save api trace\n");
1074           else
1075             vlib_cli_output (vm, "API trace saved to %s\n", chroot_filename);
1076           vlib_worker_thread_barrier_release (vm);
1077           fclose (fp);
1078         }
1079       else if (unformat (line_input, "save %s", &filename))
1080         {
1081           if (strstr ((char *) filename, "..")
1082               || index ((char *) filename, '/'))
1083             {
1084               vlib_cli_output (vm, "illegal characters in filename '%s'",
1085                                filename);
1086               goto out;
1087             }
1088
1089           chroot_filename = format (0, "/tmp/%s%c", filename, 0);
1090
1091           vec_free (filename);
1092
1093           if (file_exists (chroot_filename))
1094             {
1095               vlib_cli_output (vm, "file exists: %s\n", chroot_filename);
1096               goto out;
1097             }
1098
1099           fp = fopen ((char *) chroot_filename, "w");
1100           if (fp == NULL)
1101             {
1102               vlib_cli_output (vm, "Couldn't create %s\n", chroot_filename);
1103               goto out;
1104             }
1105           vlib_worker_thread_barrier_sync (vm);
1106           rv = vl_msg_api_trace_save (am, which, fp, 0);
1107           vlib_worker_thread_barrier_release (vm);
1108           fclose (fp);
1109           if (rv == -1)
1110             vlib_cli_output (vm, "API Trace data not present\n");
1111           else if (rv == -2)
1112             vlib_cli_output (vm, "File for writing is closed\n");
1113           else if (rv == -10)
1114             vlib_cli_output (vm, "Error while writing header to file\n");
1115           else if (rv == -11)
1116             vlib_cli_output (vm, "Error while writing trace to file\n");
1117           else if (rv == -12)
1118             vlib_cli_output (vm,
1119                              "Error while writing end of buffer trace to file\n");
1120           else if (rv == -13)
1121             vlib_cli_output (vm,
1122                              "Error while writing start of buffer trace to file\n");
1123           else if (rv < 0)
1124             vlib_cli_output (vm, "Unknown error while saving: %d", rv);
1125           else
1126             vlib_cli_output (vm, "API trace saved to %s\n", chroot_filename);
1127           goto out;
1128         }
1129       else if (unformat (line_input, "tojson %s", &filename))
1130         {
1131           vl_msg_api_process_file (vm, filename, first, last, DUMP_JSON);
1132         }
1133       else if (unformat (line_input, "dump-file-json %s", &filename))
1134         {
1135           vl_msg_dump_file_json (vm, filename);
1136         }
1137       else if (unformat (line_input, "dump-file %s", &filename))
1138         {
1139           vl_msg_api_process_file (vm, filename, first, last, DUMP);
1140         }
1141       else if (unformat (line_input, "dump-json"))
1142         {
1143           vl_msg_api_dump_trace (vm, which, 1);
1144         }
1145       else if (unformat (line_input, "dump"))
1146         {
1147           vl_msg_api_dump_trace (vm, which, 0);
1148         }
1149       else if (unformat (line_input, "replay-json %s", &filename))
1150         {
1151           vl_msg_replay_json (vm, filename);
1152         }
1153       else if (unformat (line_input, "replay %s", &filename))
1154         {
1155           vl_msg_api_process_file (vm, filename, first, last, REPLAY);
1156         }
1157       else if (unformat (line_input, "initializers %s", &filename))
1158         {
1159           vl_msg_api_process_file (vm, filename, first, last, INITIALIZERS);
1160         }
1161       else if (unformat (line_input, "tx"))
1162         {
1163           which = VL_API_TRACE_TX;
1164         }
1165       else if (unformat (line_input, "first %d", &first))
1166         {
1167           ;
1168         }
1169       else if (unformat (line_input, "last %d", &last))
1170         {
1171           ;
1172         }
1173       else if (unformat (line_input, "status"))
1174         {
1175           vlib_cli_output (vm, "%U", format_vl_msg_api_trace_status,
1176                            am, which);
1177         }
1178       else if (unformat (line_input, "free"))
1179         {
1180           vlib_worker_thread_barrier_sync (vm);
1181           vl_msg_api_trace_onoff (am, which, 0);
1182           vl_msg_api_trace_free (am, which);
1183           vlib_worker_thread_barrier_release (vm);
1184         }
1185       else if (unformat (line_input, "post-mortem-on"))
1186         vl_msg_api_post_mortem_dump_enable_disable (1 /* enable */ );
1187       else if (unformat (line_input, "post-mortem-off"))
1188         vl_msg_api_post_mortem_dump_enable_disable (0 /* enable */ );
1189       else
1190         return clib_error_return (0, "unknown input `%U'",
1191                                   format_unformat_error, input);
1192     }
1193 out:
1194   vec_free (filename);
1195   vec_free (chroot_filename);
1196   unformat_free (line_input);
1197   return 0;
1198 }
1199
1200 /*?
1201  * Display, replay, or save a binary API trace
1202 ?*/
1203
1204 /* *INDENT-OFF* */
1205 VLIB_CLI_COMMAND (api_trace_command, static) = {
1206   .path = "api trace",
1207   .short_help = "api trace [tx][on|off][first <n>][last <n>][status][free]"
1208                 "[post-mortem-on][dump|dump-file|dump-json|save|tojson|save-"
1209                 "json|replay <file>|replay-json <file>][nitems <n>]"
1210                 "[initializers <file>]",
1211   .function = api_trace_command_fn,
1212   .is_mp_safe = 1,
1213 };
1214 /* *INDENT-ON* */
1215
1216 static clib_error_t *
1217 api_trace_config_fn (vlib_main_t * vm, unformat_input_t * input)
1218 {
1219   u32 nitems = 256 << 10;
1220   vl_api_trace_which_t which = VL_API_TRACE_RX;
1221   api_main_t *am = vlibapi_get_main ();
1222
1223   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1224     {
1225       if (unformat (input, "on") || unformat (input, "enable"))
1226         {
1227           if (unformat (input, "nitems %d", &nitems))
1228             ;
1229           vl_msg_api_trace_configure (am, which, nitems);
1230           vl_msg_api_trace_onoff (am, which, 1 /* on */ );
1231           vl_msg_api_post_mortem_dump_enable_disable (1 /* enable */ );
1232         }
1233       else if (unformat (input, "save-api-table %s",
1234                          &am->save_msg_table_filename))
1235         ;
1236       else
1237         return clib_error_return (0, "unknown input `%U'",
1238                                   format_unformat_error, input);
1239     }
1240   return 0;
1241 }
1242
1243 /*?
1244  * This module has three configuration parameters:
1245  * "on" or "enable" - enables binary api tracing
1246  * "nitems <nnn>" - sets the size of the circular buffer to <nnn>
1247  * "save-api-table <filename>" - dumps the API message table to /tmp/<filename>
1248 ?*/
1249 VLIB_CONFIG_FUNCTION (api_trace_config_fn, "api-trace");
1250
1251 static clib_error_t *
1252 api_queue_config_fn (vlib_main_t * vm, unformat_input_t * input)
1253 {
1254   api_main_t *am = vlibapi_get_main ();
1255   u32 nitems;
1256
1257   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1258     {
1259       if (unformat (input, "length %d", &nitems) ||
1260           (unformat (input, "len %d", &nitems)))
1261         {
1262           if (nitems >= 1024)
1263             am->vlib_input_queue_length = nitems;
1264           else
1265             clib_warning ("vlib input queue length %d too small, ignored",
1266                           nitems);
1267         }
1268       else
1269         return clib_error_return (0, "unknown input `%U'",
1270                                   format_unformat_error, input);
1271     }
1272   return 0;
1273 }
1274
1275 VLIB_CONFIG_FUNCTION (api_queue_config_fn, "api-queue");
1276
1277 static u8 *
1278 extract_name (u8 * s)
1279 {
1280   u8 *rv;
1281
1282   rv = vec_dup (s);
1283
1284   while (vec_len (rv) && rv[vec_len (rv)] != '_')
1285     _vec_len (rv)--;
1286
1287   rv[vec_len (rv)] = 0;
1288
1289   return rv;
1290 }
1291
1292 static u8 *
1293 extract_crc (u8 * s)
1294 {
1295   int i;
1296   u8 *rv;
1297
1298   rv = vec_dup (s);
1299
1300   for (i = vec_len (rv) - 1; i >= 0; i--)
1301     {
1302       if (rv[i] == '_')
1303         {
1304           vec_delete (rv, i + 1, 0);
1305           break;
1306         }
1307     }
1308   return rv;
1309 }
1310
1311 typedef struct
1312 {
1313   u8 *name_and_crc;
1314   u8 *name;
1315   u8 *crc;
1316   u32 msg_index;
1317   int which;
1318 } msg_table_unserialize_t;
1319
1320 static int
1321 table_id_cmp (void *a1, void *a2)
1322 {
1323   msg_table_unserialize_t *n1 = a1;
1324   msg_table_unserialize_t *n2 = a2;
1325
1326   return (n1->msg_index - n2->msg_index);
1327 }
1328
1329 static int
1330 table_name_and_crc_cmp (void *a1, void *a2)
1331 {
1332   msg_table_unserialize_t *n1 = a1;
1333   msg_table_unserialize_t *n2 = a2;
1334
1335   return strcmp ((char *) n1->name_and_crc, (char *) n2->name_and_crc);
1336 }
1337
1338 static clib_error_t *
1339 dump_api_table_file_command_fn (vlib_main_t * vm,
1340                                 unformat_input_t * input,
1341                                 vlib_cli_command_t * cmd)
1342 {
1343   u8 *filename = 0;
1344   api_main_t *am = vlibapi_get_main ();
1345   serialize_main_t _sm, *sm = &_sm;
1346   clib_error_t *error;
1347   u32 nmsgs;
1348   u32 msg_index;
1349   u8 *name_and_crc;
1350   int compare_current = 0;
1351   int numeric_sort = 0;
1352   msg_table_unserialize_t *table = 0, *item;
1353   u32 i;
1354   u32 ndifferences = 0;
1355
1356   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1357     {
1358       if (unformat (input, "file %s", &filename))
1359         ;
1360       else if (unformat (input, "compare-current")
1361                || unformat (input, "compare"))
1362         compare_current = 1;
1363       else if (unformat (input, "numeric"))
1364         numeric_sort = 1;
1365       else
1366         return clib_error_return (0, "unknown input `%U'",
1367                                   format_unformat_error, input);
1368     }
1369
1370   if (numeric_sort && compare_current)
1371     return clib_error_return
1372       (0, "Comparison and numeric sorting are incompatible");
1373
1374   if (filename == 0)
1375     return clib_error_return (0, "File not specified");
1376
1377   /* Load the serialized message table from the table dump */
1378
1379   error = unserialize_open_clib_file (sm, (char *) filename);
1380
1381   if (error)
1382     return error;
1383
1384   unserialize_integer (sm, &nmsgs, sizeof (u32));
1385
1386   for (i = 0; i < nmsgs; i++)
1387     {
1388       msg_index = unserialize_likely_small_unsigned_integer (sm);
1389       unserialize_cstring (sm, (char **) &name_and_crc);
1390       vec_add2 (table, item, 1);
1391       item->msg_index = msg_index;
1392       item->name_and_crc = name_and_crc;
1393       item->name = extract_name (name_and_crc);
1394       item->crc = extract_crc (name_and_crc);
1395       item->which = 0;          /* file */
1396     }
1397   unserialize_close (sm);
1398
1399   /* Compare with the current image? */
1400   if (compare_current)
1401     {
1402       /* Append the current message table */
1403       u8 *tblv = vl_api_serialize_message_table (am, 0);
1404
1405       serialize_open_vector (sm, tblv);
1406       unserialize_integer (sm, &nmsgs, sizeof (u32));
1407
1408       for (i = 0; i < nmsgs; i++)
1409         {
1410           msg_index = unserialize_likely_small_unsigned_integer (sm);
1411           unserialize_cstring (sm, (char **) &name_and_crc);
1412
1413           vec_add2 (table, item, 1);
1414           item->msg_index = msg_index;
1415           item->name_and_crc = name_and_crc;
1416           item->name = extract_name (name_and_crc);
1417           item->crc = extract_crc (name_and_crc);
1418           item->which = 1;      /* current_image */
1419         }
1420       vec_free (tblv);
1421     }
1422
1423   /* Sort the table. */
1424   if (numeric_sort)
1425     vec_sort_with_function (table, table_id_cmp);
1426   else
1427     vec_sort_with_function (table, table_name_and_crc_cmp);
1428
1429   if (compare_current)
1430     {
1431       u8 *dashes = 0;
1432       ndifferences = 0;
1433
1434       /*
1435        * In this case, the recovered table will have two entries per
1436        * API message. So, if entries i and i+1 match, the message definitions
1437        * are identical. Otherwise, the crc is different, or a message is
1438        * present in only one of the tables.
1439        */
1440       vlib_cli_output (vm, "%-60s | %s", "Message Name", "Result");
1441       vec_validate_init_empty (dashes, 60, '-');
1442       vec_terminate_c_string (dashes);
1443       vlib_cli_output (vm, "%60s-|-%s", dashes, "-----------------");
1444       vec_free (dashes);
1445       for (i = 0; i < vec_len (table);)
1446         {
1447           /* Last message lonely? */
1448           if (i == vec_len (table) - 1)
1449             {
1450               ndifferences++;
1451               goto last_unique;
1452             }
1453
1454           /* Identical pair? */
1455           if (!strncmp
1456               ((char *) table[i].name_and_crc,
1457                (char *) table[i + 1].name_and_crc,
1458                vec_len (table[i].name_and_crc)))
1459             {
1460               i += 2;
1461               continue;
1462             }
1463
1464           ndifferences++;
1465
1466           /* Only in one of two tables? */
1467           if (i + 1 == vec_len (table)
1468               || strcmp ((char *) table[i].name, (char *) table[i + 1].name))
1469             {
1470             last_unique:
1471               vlib_cli_output (vm, "%-60s | only in %s",
1472                                table[i].name, table[i].which ?
1473                                "image" : "file");
1474               i++;
1475               continue;
1476             }
1477           /* In both tables, but with different signatures */
1478           vlib_cli_output (vm, "%-60s | definition changed", table[i].name);
1479           i += 2;
1480         }
1481       if (ndifferences == 0)
1482         vlib_cli_output (vm, "No api message signature differences found.");
1483       else
1484         vlib_cli_output (vm, "\nFound %u api message signature differences",
1485                          ndifferences);
1486       goto cleanup;
1487     }
1488
1489   /* Dump the table, sorted as shown above */
1490   vlib_cli_output (vm, "%=60s %=8s %=10s", "Message name", "MsgID", "CRC");
1491
1492   for (i = 0; i < vec_len (table); i++)
1493     {
1494       item = table + i;
1495       vlib_cli_output (vm, "%-60s %8u %10s", item->name,
1496                        item->msg_index, item->crc);
1497     }
1498
1499 cleanup:
1500   for (i = 0; i < vec_len (table); i++)
1501     {
1502       vec_free (table[i].name_and_crc);
1503       vec_free (table[i].name);
1504       vec_free (table[i].crc);
1505     }
1506
1507   vec_free (table);
1508
1509   return 0;
1510 }
1511
1512 /*?
1513  * Displays a serialized API message decode table, sorted by message name
1514  *
1515  * @cliexpar
1516  * @cliexstart{show api dump file <filename>}
1517  *                                                Message name    MsgID        CRC
1518  * accept_session                                                    407   8e2a127e
1519  * accept_session_reply                                              408   67d8c22a
1520  * add_node_next                                                     549   e4202993
1521  * add_node_next_reply                                               550   e89d6eed
1522  * etc.
1523  * @cliexend
1524 ?*/
1525
1526 /*?
1527  * Compares a serialized API message decode table with the current image
1528  *
1529  * @cliexpar
1530  * @cliexstart{show api dump file <filename> compare}
1531  * ip_add_del_route                                             definition changed
1532  * ip_table_add_del                                             definition changed
1533  * l2_macs_event                                                only in image
1534  * vnet_ip4_fib_counters                                        only in file
1535  * vnet_ip4_nbr_counters                                        only in file
1536  * @cliexend
1537 ?*/
1538
1539 /*?
1540  * Display a serialized API message decode table, compare a saved
1541  * decode table with the current image, to establish API differences.
1542  *
1543 ?*/
1544 /* *INDENT-OFF* */
1545 VLIB_CLI_COMMAND (dump_api_table_file, static) =
1546 {
1547   .path = "show api dump",
1548   .short_help = "show api dump file <filename> [numeric | compare-current]",
1549   .function = dump_api_table_file_command_fn,
1550 };
1551
1552 /* *INDENT-ON* */
1553 /*
1554  * fd.io coding-style-patch-verification: ON
1555  *
1556  * Local Variables:
1557  * eval: (c-set-style "gnu")
1558  * End:
1559  */