vlib: add "save memory-trace" debug CLI

Save memory traces of the currently traced heap in JSON format to file
which can be used as machine-readable data for memory leak diagnose.

Type: improvement

Change-Id: I277f5be5838510e907c4dd7a8a4e9a883cb67bc3
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/src/vlib/cli.c b/src/vlib/cli.c
index 98d57c6..4198b4b 100644
--- a/src/vlib/cli.c
+++ b/src/vlib/cli.c
@@ -43,6 +43,7 @@
 #include <vppinfra/callback.h>
 #include <vppinfra/cpu.h>
 #include <vppinfra/elog.h>
+#include <vppinfra/cJSON.h>
 #include <unistd.h>
 #include <ctype.h>
 
@@ -1115,6 +1116,111 @@
 };
 
 static clib_error_t *
+save_memory_trace (vlib_main_t *vm, unformat_input_t *input,
+		   vlib_cli_command_t *cmd)
+{
+  char *file, *chroot_file;
+  uword was_enabled;
+  mheap_trace_t *t, *mem_traces = 0;
+  u8 *tmp;
+  cJSON *traces, *trace, *traceback, *symbol;
+  int i;
+  FILE *fp;
+  char *json_str = 0;
+
+  cJSON_Hooks cjson_hooks = {
+    .malloc_fn = clib_mem_alloc,
+    .free_fn = clib_mem_free,
+    .realloc_fn = clib_mem_realloc,
+  };
+  cJSON_InitHooks (&cjson_hooks);
+
+  if (!unformat (input, "%s", &file))
+    {
+      vlib_cli_output (vm, "expected file name, got `%U'",
+		       format_unformat_error, input);
+      return 0;
+    }
+
+  /* It's fairly hard to get "../oopsie" through unformat; just in case */
+  if (strstr (file, "..") || strchr (file, '/'))
+    {
+      vlib_cli_output (vm, "illegal characters in filename '%s'", file);
+      return 0;
+    }
+  chroot_file = (char *) format (0, "/tmp/%s%c", file, 0);
+  vec_free (file);
+  fp = fopen ((char *) chroot_file, "w");
+  if (fp == NULL)
+    {
+      vlib_cli_output (vm, "couldn't open output file %s '%s'", chroot_file);
+      vec_free (chroot_file);
+      return 0;
+    }
+
+  was_enabled = clib_mem_trace_enable_disable (0);
+  vlib_cli_output (vm, "Saving trace to '%s'", chroot_file);
+  mem_traces = clib_mem_trace_dup (current_traced_heap);
+  traces = cJSON_CreateArray ();
+  vec_foreach (t, mem_traces)
+    {
+      /* Skip over free elements. */
+      if (t->n_allocations == 0)
+	continue;
+
+      trace = cJSON_CreateObject ();
+      cJSON_AddNumberToObject (trace, "count", t->n_allocations);
+      cJSON_AddNumberToObject (trace, "bytes", t->n_bytes);
+      tmp = format (0, "%p%c", t->offset, 0);
+      cJSON_AddStringToObject (trace, "sample", (char *) tmp);
+      vec_free (tmp);
+      traceback = cJSON_AddArrayToObject (trace, "traceback");
+      for (i = 0; i < ARRAY_LEN (t->callers) && t->callers[i]; i++)
+	{
+#if defined(CLIB_UNIX) && !defined(__APPLE__)
+	  tmp = format (0, "%U%c\n", format_clib_elf_symbol_with_address,
+			t->callers[i], 0);
+	  symbol = cJSON_CreateString ((char *) tmp);
+	  cJSON_AddItemToArray (traceback, symbol);
+	  vec_free (tmp);
+#else
+	  tmp = format (0, "%p%c\n", t->callers[i], 0);
+	  symbol = cJSON_CreateString ((char *) tmp);
+	  cJSON_AddItemToArray (traceback, symbol);
+	  vec_free (tmp);
+#endif
+	}
+
+      cJSON_AddItemToArray (traces, trace);
+    }
+  json_str = cJSON_PrintUnformatted (traces);
+  cJSON_Delete (traces);
+  fputs (json_str, fp);
+  fclose (fp);
+  clib_mem_free (json_str);
+
+  vec_free (mem_traces);
+  clib_mem_trace_enable_disable (was_enabled);
+
+  vec_free (chroot_file);
+
+  return 0;
+}
+
+/*?
+ * Save memory traces of the currently traced heap in JSON format to file.
+ * Only filename can be specified, path is fixed (/tmp/<filename>).
+ *
+ * @cliexpar
+ * @cliexcmd{save memory-trace mem_trace.json}
+?*/
+VLIB_CLI_COMMAND (save_memory_trace_command, static) = {
+  .path = "save memory-trace",
+  .short_help = "save memory-trace <filename>",
+  .function = save_memory_trace,
+};
+
+static clib_error_t *
 restart_cmd_fn (vlib_main_t * vm, unformat_input_t * input,
 		vlib_cli_command_t * cmd)
 {