[gnome-shell] Dump a complete report from a performance run, as JSON



commit bc57574094c5c3676fee95282439f4ea9c840f03
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Wed May 12 17:24:52 2010 -0400

    Dump a complete report from a performance run, as JSON
    
    When SHELL_PERF_OUTPUT is set, instead of just dumping out the metrics, dump
    a more complete report with:
    
     - Event descriptions
     - Metric descriptions and value
     - Event log
    
    Helper functions shell_perf_log_dump_events() and shell_perf_log_dump_log()
    are added to ShellPerfLog to support this. The gnome-shell wrapper is adapted
    to deal with the changed report format.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=618189

 js/ui/scripting.js   |   39 ++++++++--
 src/gnome-shell.in   |   13 ++--
 src/shell-perf-log.c |  208 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/shell-perf-log.h |   14 +++-
 4 files changed, 256 insertions(+), 18 deletions(-)
---
diff --git a/js/ui/scripting.js b/js/ui/scripting.js
index 5270b1e..12bbded 100644
--- a/js/ui/scripting.js
+++ b/js/ui/scripting.js
@@ -1,6 +1,7 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
 const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
 const Mainloop = imports.mainloop;
 
 const Meta = imports.gi.Meta;
@@ -137,16 +138,38 @@ function _collect(scriptModule, outputFile) {
         scriptModule.finish();
 
     if (outputFile) {
-        let result = {};
-        for (let metric in scriptModule.METRICS) {
-            result[metric] = {
-                value: scriptModule.METRICS[metric],
-                description: scriptModule.METRIC_DESCRIPTIONS[metric]
-            };
+        let f = Gio.file_new_for_path(outputFile);
+        let raw = f.replace(null, false,
+                            Gio.FileCreateFlags.NONE,
+                            null);
+        let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
+        Shell.write_string_to_stream (out, "{\n");
+
+        Shell.write_string_to_stream(out, '"events":\n');
+        Shell.PerfLog.get_default().dump_events(out);
+
+        Shell.write_string_to_stream(out, ',\n"metrics":\n[ ');
+        let first = true;
+        for (let name in scriptModule.METRICS) {
+            let value = scriptModule.METRICS[name];
+            let description = scriptModule.METRIC_DESCRIPTIONS[name];
+
+            if (!first)
+                Shell.write_string_to_stream(out, ',\n  ');
+            first = false;
+
+            Shell.write_string_to_stream(out,
+                                         '{ "name": ' + JSON.stringify(name) + ',\n' +
+                                         '    "description": ' + JSON.stringify(description) + ',\n' +
+                                         '    "value": ' + JSON.stringify(value) + ' }');
         }
+        Shell.write_string_to_stream(out, ' ]');
 
-        let contents = JSON.stringify(result);
-        GLib.file_set_contents(outputFile, contents, contents.length);
+        Shell.write_string_to_stream (out, ',\n"log":\n');
+        Shell.PerfLog.get_default().dump_log(out);
+
+        Shell.write_string_to_stream (out, '\n}\n');
+        out.close(null);
     } else {
         let metrics = [];
         for (let metric in scriptModule.METRICS)
diff --git a/src/gnome-shell.in b/src/gnome-shell.in
index b8bb4bd..78b2cdb 100644
--- a/src/gnome-shell.in
+++ b/src/gnome-shell.in
@@ -314,16 +314,17 @@ def run_performance_test():
         if options.perf_warmup and i == 0:
             continue
 
-        for metric, details in output.iteritems():
-            if not metric in metric_summaries:
+        for metric in output['metrics']:
+            name = metric['name']
+            if not name in metric_summaries:
                 summary = {}
-                summary['description'] = details['description']
+                summary['description'] = metric['description']
                 summary['values'] = []
-                metric_summaries[metric] = summary
+                metric_summaries[name] = summary
             else:
-                summary = metric_summaries[metric]
+                summary = metric_summaries[name]
 
-            summary['values'].append(details['value'])
+            summary['values'].append(metric['value'])
 
     for metric in sorted(metric_summaries.keys()):
         summary = metric_summaries[metric]
diff --git a/src/shell-perf-log.c b/src/shell-perf-log.c
index 5c59682..262e47e 100644
--- a/src/shell-perf-log.c
+++ b/src/shell-perf-log.c
@@ -255,6 +255,13 @@ define_event (ShellPerfLog *perf_log,
       return NULL;
     }
 
+  /* We could do stricter validation, but this will break our JSON dumps */
+  if (strchr (name, '"') != NULL)
+    {
+      g_warning ("Event names can't include '\"'");
+      return NULL;
+    }
+
   if (g_hash_table_lookup (perf_log->events_by_name, name) != NULL)
     {
       g_warning ("Duplicate event event for '%s'\n", name);
@@ -680,13 +687,15 @@ shell_perf_log_collect_statistics (ShellPerfLog *perf_log)
  * shell_perf_log_replay:
  * @perf_log: a #ShellPerfLog
  * @replay_function: function to call for each event in the log
+ * @user_data: data to pass to @replay_function
  *
  * Replays the log by calling the given function for each event
  * in the log.
  */
 void
 shell_perf_log_replay (ShellPerfLog            *perf_log,
-                       ShellPerfReplayFunction  replay_function)
+                       ShellPerfReplayFunction  replay_function,
+                       gpointer                 user_data)
 {
   gint64 event_time = perf_log->start_time;
   GList *iter;
@@ -754,8 +763,203 @@ shell_perf_log_replay (ShellPerfLog            *perf_log,
               pos += strlen ((char *)(block->buffer + pos)) + 1;
             }
 
-          replay_function (event_time, event->name, event->signature, &arg);
+          replay_function (event_time, event->name, event->signature, &arg, user_data);
           g_value_unset (&arg);
         }
     }
 }
+
+static char *
+escape_quotes (const char *input)
+{
+  GString *result;
+  const char *p;
+
+  if (strchr (input, '"') == NULL)
+    return (char *)input;
+
+  result = g_string_new (NULL);
+  for (p = input; *p; p++)
+    {
+      if (*p == '"')
+        g_string_append (result, "\\\"");
+      else
+        g_string_append_c (result, *p);
+    }
+
+  return g_string_free (result, FALSE);
+}
+
+static gboolean
+write_string (GOutputStream *out,
+              const char    *str,
+              GError       **error)
+{
+  return g_output_stream_write_all (out, str, strlen (str),
+                                    NULL, NULL,
+                                    error);
+}
+
+/**
+ * shell_perf_log_dump_events:
+ * @perf_log: a #ShellPerfLog
+ * @out: output stream into which to write the event definitions
+ * @error: location to store #GError, or %NULL
+ *
+ * Dump the definition of currently defined events and statistics, formatted
+ * as JSON, to the specified output stream. The JSON output is an array,
+ * with each element being a dictionary of the form:
+ *
+ * { name: <name of event>,
+ *   description: <descrition of string,
+ *   statistic: true } (only for statistics)
+ *
+ * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
+ */
+gboolean
+shell_perf_log_dump_events (ShellPerfLog   *perf_log,
+                            GOutputStream  *out,
+                            GError        **error)
+{
+  GString *output;
+  int i;
+
+  output = g_string_new (NULL);
+  g_string_append (output, "[ ");
+
+  for (i = 0; i < perf_log->events->len; i++)
+    {
+      ShellPerfEvent *event = g_ptr_array_index (perf_log->events, i);
+      char *escaped_description = escape_quotes (event->description);
+      gboolean is_statistic = g_hash_table_lookup (perf_log->statistics_by_name, event->name) != NULL;
+
+      if (i != 0)
+        g_string_append (output, ",\n  ");
+
+      g_string_append_printf (output,
+                                "{ \"name\": \"%s\",\n"
+                              "    \"description\": \"%s\"",
+                              event->name, escaped_description);
+      if (is_statistic)
+        g_string_append (output, ",\n    \"statistic\": true");
+
+      g_string_append (output, " }");
+
+      if (escaped_description != event->description)
+        g_free (escaped_description);
+    }
+
+  g_string_append (output, " ]");
+
+  return write_string (out, g_string_free (output, FALSE), error);
+}
+
+typedef struct {
+  GOutputStream *out;
+  GError *error;
+  gboolean first;
+} ReplayToJsonClosure;
+
+static void
+replay_to_json (gint64      time,
+                const char *name,
+                const char *signature,
+                GValue     *arg,
+                gpointer    user_data)
+{
+  ReplayToJsonClosure *closure = user_data;
+  char *event_str;
+
+  if (closure->error != NULL)
+    return;
+
+  if (!closure->first)
+    {
+      if (!write_string (closure->out, ",\n  ", &closure->error))
+        return;
+    }
+
+  closure->first = FALSE;
+
+  if (strcmp (signature, "") == 0)
+    {
+      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\"]", time, name);
+    }
+  else if (strcmp (signature, "i") == 0)
+    {
+      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %i]",
+                                   time,
+                                   name,
+                                   g_value_get_int (arg));
+    }
+  else if (strcmp (signature, "x") == 0)
+    {
+      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %"G_GINT64_FORMAT "]",
+                                   time,
+                                   name,
+                                   g_value_get_int64 (arg));
+    }
+  else if (strcmp (signature, "s") == 0)
+    {
+      const char *arg_str = g_value_get_string (arg);
+      char *escaped = escape_quotes (arg_str);
+
+      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", \"%s\"]",
+                                   time,
+                                   name,
+                                   g_value_get_string (arg));
+
+      if (escaped != arg_str)
+        g_free (escaped);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  if (!write_string (closure->out, event_str, &closure->error))
+      return;
+}
+
+/**
+ * shell_perf_log_dump_log:
+ * @perf_log: a #ShellPerfLog
+ * @out: output stream into which to write the event log
+ * @error: location to store #GError, or %NULL
+ *
+ * Writes the performance event log, formatted as JSON, to the specified
+ * output stream. For performance reasons, the output stream passed
+ * in should generally be a buffered (or memory) output stream, since
+ * it will be written to in small pieces. The JSON output is an array
+ * with the elements of the array also being arrays, of the form
+ * '[' <time>, <event name> [, <event_arg>... ] ']'.
+ *
+ * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
+ */
+gboolean
+shell_perf_log_dump_log (ShellPerfLog   *perf_log,
+                         GOutputStream  *out,
+                         GError        **error)
+{
+  ReplayToJsonClosure closure;
+
+  closure.out = out;
+  closure.error = NULL;
+  closure.first = TRUE;
+
+  if (!write_string (out, "[ ", &closure.error))
+    return FALSE;
+
+  shell_perf_log_replay (perf_log, replay_to_json, &closure);
+
+  if (closure.error != NULL)
+    {
+      g_propagate_error (error, closure.error);
+      return FALSE;
+    }
+
+  if (!write_string (out, " ]", &closure.error))
+    return FALSE;
+
+  return TRUE;
+}
diff --git a/src/shell-perf-log.h b/src/shell-perf-log.h
index b566fad..a71f373 100644
--- a/src/shell-perf-log.h
+++ b/src/shell-perf-log.h
@@ -3,6 +3,7 @@
 #define __SHELL_PERF_LOG_H__
 
 #include <glib-object.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
@@ -64,10 +65,19 @@ void shell_perf_log_collect_statistics (ShellPerfLog *perf_log);
 typedef void (*ShellPerfReplayFunction) (gint64      time,
 					 const char *name,
 					 const char *signature,
-					 GValue     *arg);
+					 GValue     *arg,
+                                         gpointer    user_data);
 
 void shell_perf_log_replay (ShellPerfLog            *perf_log,
-			    ShellPerfReplayFunction  replay_function);
+			    ShellPerfReplayFunction  replay_function,
+                            gpointer                 user_data);
+
+gboolean shell_perf_log_dump_events (ShellPerfLog   *perf_log,
+                                     GOutputStream  *out,
+                                     GError        **error);
+gboolean shell_perf_log_dump_log    (ShellPerfLog   *perf_log,
+                                     GOutputStream  *out,
+                                     GError        **error);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]