Blame src/shell-perf-log.c

Packit d345d1
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
Packit d345d1
Packit d345d1
#include "config.h"
Packit d345d1
Packit d345d1
#include <string.h>
Packit d345d1
Packit d345d1
#include "shell-perf-log.h"
Packit d345d1
Packit d345d1
typedef struct _ShellPerfEvent ShellPerfEvent;
Packit d345d1
typedef struct _ShellPerfStatistic ShellPerfStatistic;
Packit d345d1
typedef struct _ShellPerfStatisticsClosure ShellPerfStatisticsClosure;
Packit d345d1
typedef union  _ShellPerfStatisticValue ShellPerfStatisticValue;
Packit d345d1
typedef struct _ShellPerfBlock ShellPerfBlock;
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * SECTION:shell-perf-log
Packit d345d1
 * @short_description: Event recorder for performance measurement
Packit d345d1
 *
Packit d345d1
 * ShellPerfLog provides a way for different parts of the code to
Packit d345d1
 * record information for subsequent analysis and interactive
Packit d345d1
 * exploration. Events exist of a timestamp, an event ID, and
Packit d345d1
 * arguments to the event.
Packit d345d1
 *
Packit d345d1
 * Emphasis is placed on storing recorded events in a compact
Packit d345d1
 * fashion so log recording disturbs the execution of the program
Packit d345d1
 * as little as possible, however events should not be recorded
Packit d345d1
 * at too fine a granularity - an event that is recorded once
Packit d345d1
 * per frame or once per user action is appropriate, an event that
Packit d345d1
 * occurs many times per frame is not.
Packit d345d1
 *
Packit d345d1
 * Arguments are identified by a D-Bus style signature; at the moment
Packit d345d1
 * only a limited number of event signatures are supported to
Packit d345d1
 * simplify the code.
Packit d345d1
 */
Packit d345d1
struct _ShellPerfLog
Packit d345d1
{
Packit d345d1
  GObject parent;
Packit d345d1
Packit d345d1
  GPtrArray *events;
Packit d345d1
  GHashTable *events_by_name;
Packit d345d1
  GPtrArray *statistics;
Packit d345d1
  GHashTable *statistics_by_name;
Packit d345d1
Packit d345d1
  GPtrArray *statistics_closures;
Packit d345d1
Packit d345d1
  GQueue *blocks;
Packit d345d1
Packit d345d1
  gint64 start_time;
Packit d345d1
  gint64 last_time;
Packit d345d1
Packit d345d1
  guint statistics_timeout_id;
Packit d345d1
Packit d345d1
  guint enabled : 1;
Packit d345d1
};
Packit d345d1
Packit d345d1
struct _ShellPerfEvent
Packit d345d1
{
Packit d345d1
  guint16 id;
Packit d345d1
  char *name;
Packit d345d1
  char *description;
Packit d345d1
  char *signature;
Packit d345d1
};
Packit d345d1
Packit d345d1
union _ShellPerfStatisticValue
Packit d345d1
{
Packit d345d1
  int i;
Packit d345d1
  gint64 x;
Packit d345d1
};
Packit d345d1
Packit d345d1
struct _ShellPerfStatistic
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event;
Packit d345d1
Packit d345d1
  ShellPerfStatisticValue current_value;
Packit d345d1
  ShellPerfStatisticValue last_value;
Packit d345d1
Packit d345d1
  guint initialized : 1;
Packit d345d1
  guint recorded : 1;
Packit d345d1
};
Packit d345d1
Packit d345d1
struct _ShellPerfStatisticsClosure
Packit d345d1
{
Packit d345d1
  ShellPerfStatisticsCallback callback;
Packit d345d1
  gpointer user_data;
Packit d345d1
  GDestroyNotify notify;
Packit d345d1
};
Packit d345d1
Packit d345d1
/* The events in the log are stored in a linked list of fixed size
Packit d345d1
 * blocks.
Packit d345d1
 *
Packit d345d1
 * Note that the power-of-two nature of BLOCK_SIZE here is superficial
Packit d345d1
 * since the allocated block has the 'bytes' field and malloc
Packit d345d1
 * overhead. The current value is well below the size that will
Packit d345d1
 * typically be independently mmapped by the malloc implementation so
Packit d345d1
 * it doesn't matter. If we switched to mmapping blocks manually
Packit d345d1
 * (perhaps to avoid polluting malloc statistics), we'd want to use a
Packit d345d1
 * different value of BLOCK_SIZE.
Packit d345d1
 */
Packit d345d1
#define BLOCK_SIZE 8192
Packit d345d1
Packit d345d1
struct _ShellPerfBlock
Packit d345d1
{
Packit d345d1
  guint32 bytes;
Packit d345d1
  guchar buffer[BLOCK_SIZE];
Packit d345d1
};
Packit d345d1
Packit d345d1
/* Number of milliseconds between periodic statistics collection when
Packit d345d1
 * events are enabled. Statistics collection can also be explicitly
Packit d345d1
 * triggered.
Packit d345d1
 */
Packit d345d1
#define STATISTIC_COLLECTION_INTERVAL_MS 5000
Packit d345d1
Packit d345d1
/* Builtin events */
Packit d345d1
enum {
Packit d345d1
  EVENT_SET_TIME,
Packit d345d1
  EVENT_STATISTICS_COLLECTED
Packit d345d1
};
Packit d345d1
Packit d345d1
G_DEFINE_TYPE(ShellPerfLog, shell_perf_log, G_TYPE_OBJECT);
Packit d345d1
Packit d345d1
static gint64
Packit d345d1
get_time (void)
Packit d345d1
{
Packit d345d1
  return g_get_monotonic_time ();
Packit d345d1
}
Packit d345d1
Packit d345d1
static void
Packit d345d1
shell_perf_log_init (ShellPerfLog *perf_log)
Packit d345d1
{
Packit d345d1
  perf_log->events = g_ptr_array_new ();
Packit d345d1
  perf_log->events_by_name = g_hash_table_new (g_str_hash, g_str_equal);
Packit d345d1
  perf_log->statistics = g_ptr_array_new ();
Packit d345d1
  perf_log->statistics_by_name = g_hash_table_new (g_str_hash, g_str_equal);
Packit d345d1
  perf_log->statistics_closures = g_ptr_array_new ();
Packit d345d1
  perf_log->blocks = g_queue_new ();
Packit d345d1
Packit d345d1
  /* This event is used when timestamp deltas are greater than
Packit d345d1
   * fits in a gint32. 0xffffffff microseconds is about 70 minutes, so this
Packit d345d1
   * is not going to happen in normal usage. It might happen if performance
Packit d345d1
   * logging is enabled some time after starting the shell */
Packit d345d1
  shell_perf_log_define_event (perf_log, "perf.setTime", "", "x");
Packit d345d1
  g_assert (perf_log->events->len == EVENT_SET_TIME + 1);
Packit d345d1
Packit d345d1
  /* The purpose of this event is to allow us to optimize out storing
Packit d345d1
   * statistics that haven't changed. We want to mark every time we
Packit d345d1
   * collect statistics even if we don't record any individual
Packit d345d1
   * statistics so that we can distinguish sudden changes from gradual changes.
Packit d345d1
   *
Packit d345d1
   * The argument is the number of microseconds that statistics collection
Packit d345d1
   * took; we record that since statistics collection could start taking
Packit d345d1
   * significant time if we do things like grub around in /proc/
Packit d345d1
   */
Packit d345d1
  shell_perf_log_define_event (perf_log, "perf.statisticsCollected",
Packit d345d1
                               "Finished collecting statistics",
Packit d345d1
                               "x");
Packit d345d1
  g_assert (perf_log->events->len == EVENT_STATISTICS_COLLECTED + 1);
Packit d345d1
Packit d345d1
  perf_log->start_time = perf_log->last_time = get_time();
Packit d345d1
}
Packit d345d1
Packit d345d1
static void
Packit d345d1
shell_perf_log_class_init (ShellPerfLogClass *class)
Packit d345d1
{
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_get_default:
Packit d345d1
 *
Packit d345d1
 * Gets the global singleton performance log. This is initially disabled
Packit d345d1
 * and must be explicitly enabled with shell_perf_log_set_enabled().
Packit d345d1
 *
Packit d345d1
 * Return value: (transfer none): the global singleton performance log
Packit d345d1
 */
Packit d345d1
ShellPerfLog *
Packit d345d1
shell_perf_log_get_default (void)
Packit d345d1
{
Packit d345d1
  static ShellPerfLog *perf_log;
Packit d345d1
Packit d345d1
  if (perf_log == NULL)
Packit d345d1
    perf_log = g_object_new (SHELL_TYPE_PERF_LOG, NULL);
Packit d345d1
Packit d345d1
  return perf_log;
Packit d345d1
}
Packit d345d1
Packit d345d1
static gboolean
Packit d345d1
statistics_timeout (gpointer data)
Packit d345d1
{
Packit d345d1
  ShellPerfLog *perf_log = data;
Packit d345d1
Packit d345d1
  shell_perf_log_collect_statistics (perf_log);
Packit d345d1
Packit d345d1
  return TRUE;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_set_enabled:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @enabled: whether to record events
Packit d345d1
 *
Packit d345d1
 * Sets whether events are currently being recorded.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_set_enabled (ShellPerfLog *perf_log,
Packit d345d1
                            gboolean      enabled)
Packit d345d1
{
Packit d345d1
  enabled = enabled != FALSE;
Packit d345d1
Packit d345d1
  if (enabled != perf_log->enabled)
Packit d345d1
    {
Packit d345d1
      perf_log->enabled = enabled;
Packit d345d1
Packit d345d1
      if (enabled)
Packit d345d1
        {
Packit d345d1
          perf_log->statistics_timeout_id = g_timeout_add (STATISTIC_COLLECTION_INTERVAL_MS,
Packit d345d1
                                                           statistics_timeout,
Packit d345d1
                                                           perf_log);
Packit d345d1
          g_source_set_name_by_id (perf_log->statistics_timeout_id, "[gnome-shell] statistics_timeout");
Packit d345d1
        }
Packit d345d1
      else
Packit d345d1
        {
Packit d345d1
          g_source_remove (perf_log->statistics_timeout_id);
Packit d345d1
          perf_log->statistics_timeout_id = 0;
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
}
Packit d345d1
Packit d345d1
static ShellPerfEvent *
Packit d345d1
define_event (ShellPerfLog *perf_log,
Packit d345d1
              const char   *name,
Packit d345d1
              const char   *description,
Packit d345d1
              const char   *signature)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event;
Packit d345d1
Packit d345d1
  if (strcmp (signature, "") != 0 &&
Packit d345d1
      strcmp (signature, "s") != 0 &&
Packit d345d1
      strcmp (signature, "i") != 0 &&
Packit d345d1
      strcmp (signature, "x") != 0)
Packit d345d1
    {
Packit d345d1
      g_warning ("Only supported event signatures are '', 's', 'i', and 'x'\n");
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (perf_log->events->len == 65536)
Packit d345d1
    {
Packit d345d1
      g_warning ("Maximum number of events defined\n");
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  /* We could do stricter validation, but this will break our JSON dumps */
Packit d345d1
  if (strchr (name, '"') != NULL)
Packit d345d1
    {
Packit d345d1
      g_warning ("Event names can't include '\"'");
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (g_hash_table_lookup (perf_log->events_by_name, name) != NULL)
Packit d345d1
    {
Packit d345d1
      g_warning ("Duplicate event event for '%s'\n", name);
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  event = g_slice_new (ShellPerfEvent);
Packit d345d1
Packit d345d1
  event->id = perf_log->events->len;
Packit d345d1
  event->name = g_strdup (name);
Packit d345d1
  event->signature = g_strdup (signature);
Packit d345d1
  event->description = g_strdup (description);
Packit d345d1
Packit d345d1
  g_ptr_array_add (perf_log->events, event);
Packit d345d1
  g_hash_table_insert (perf_log->events_by_name, event->name, event);
Packit d345d1
Packit d345d1
  return event;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_define_event:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the event. This should of the form
Packit d345d1
 *   '<namespace>.<specific eventf'>, for example
Packit d345d1
 *   'clutter.stagePaintDone'.
Packit d345d1
 * @description: human readable description of the event.
Packit d345d1
 * @signature: signature defining the arguments that event takes.
Packit d345d1
 *   This is a string of type characters, using the same characters
Packit d345d1
 *   as D-Bus or GVariant. Only a very limited number of signatures
Packit d345d1
 *   are supported: , '', 's', 'i', and 'x'. This mean respectively:
Packit d345d1
 *   no arguments, one string, one 32-bit integer, and one 64-bit
Packit d345d1
 *   integer.
Packit d345d1
 *
Packit d345d1
 * Defines a performance event for later recording.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_define_event (ShellPerfLog *perf_log,
Packit d345d1
                             const char   *name,
Packit d345d1
                             const char   *description,
Packit d345d1
                             const char   *signature)
Packit d345d1
{
Packit d345d1
  define_event (perf_log, name, description, signature);
Packit d345d1
}
Packit d345d1
Packit d345d1
static ShellPerfEvent *
Packit d345d1
lookup_event (ShellPerfLog *perf_log,
Packit d345d1
              const char   *name,
Packit d345d1
              const char   *signature)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event = g_hash_table_lookup (perf_log->events_by_name, name);
Packit d345d1
Packit d345d1
  if (G_UNLIKELY (event == NULL))
Packit d345d1
    {
Packit d345d1
      g_warning ("Discarding unknown event '%s'\n", name);
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (G_UNLIKELY (strcmp (event->signature, signature) != 0))
Packit d345d1
    {
Packit d345d1
      g_warning ("Event '%s'; defined with signature '%s', used with '%s'\n",
Packit d345d1
                 name, event->signature, signature);
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  return event;
Packit d345d1
}
Packit d345d1
Packit d345d1
static void
Packit d345d1
record_event (ShellPerfLog   *perf_log,
Packit d345d1
              gint64          event_time,
Packit d345d1
              ShellPerfEvent *event,
Packit d345d1
              const guchar   *bytes,
Packit d345d1
              size_t          bytes_len)
Packit d345d1
{
Packit d345d1
  ShellPerfBlock *block;
Packit d345d1
  size_t total_bytes;
Packit d345d1
  guint32 time_delta;
Packit d345d1
  guint32 pos;
Packit d345d1
Packit d345d1
  if (!perf_log->enabled)
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  total_bytes = sizeof (gint32) + sizeof (gint16) + bytes_len;
Packit d345d1
  if (G_UNLIKELY (bytes_len > BLOCK_SIZE || total_bytes > BLOCK_SIZE))
Packit d345d1
    {
Packit d345d1
      g_warning ("Discarding oversize event '%s'\n", event->name);
Packit d345d1
      return;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (event_time > perf_log->last_time + G_GINT64_CONSTANT(0xffffffff))
Packit d345d1
    {
Packit d345d1
      perf_log->last_time = event_time;
Packit d345d1
      record_event (perf_log, event_time,
Packit d345d1
                    lookup_event (perf_log, "perf.setTime", "x"),
Packit d345d1
                    (const guchar *)&event_time, sizeof(gint64));
Packit d345d1
      time_delta = 0;
Packit d345d1
    }
Packit d345d1
  else if (event_time < perf_log->last_time)
Packit d345d1
    time_delta = 0;
Packit d345d1
  else
Packit d345d1
    time_delta = (guint32)(event_time - perf_log->last_time);
Packit d345d1
Packit d345d1
  perf_log->last_time = event_time;
Packit d345d1
Packit d345d1
  if (perf_log->blocks->tail == NULL ||
Packit d345d1
      total_bytes + ((ShellPerfBlock *)perf_log->blocks->tail->data)->bytes > BLOCK_SIZE)
Packit d345d1
    {
Packit d345d1
      block = g_new (ShellPerfBlock, 1);
Packit d345d1
      block->bytes = 0;
Packit d345d1
      g_queue_push_tail (perf_log->blocks, block);
Packit d345d1
    }
Packit d345d1
  else
Packit d345d1
    {
Packit d345d1
      block = (ShellPerfBlock *)perf_log->blocks->tail->data;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  pos = block->bytes;
Packit d345d1
Packit d345d1
  memcpy (block->buffer + pos, &time_delta, sizeof (guint32));
Packit d345d1
  pos += sizeof (guint32);
Packit d345d1
  memcpy (block->buffer + pos, &event->id, sizeof (guint16));
Packit d345d1
  pos += sizeof (guint16);
Packit d345d1
  memcpy (block->buffer + pos, bytes, bytes_len);
Packit d345d1
  pos += bytes_len;
Packit d345d1
Packit d345d1
  block->bytes = pos;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_event:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the event
Packit d345d1
 *
Packit d345d1
 * Records a performance event with no arguments.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_event (ShellPerfLog *perf_log,
Packit d345d1
                      const char   *name)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event = lookup_event (perf_log, name, "");
Packit d345d1
  if (G_UNLIKELY (event == NULL))
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  record_event (perf_log, get_time(), event, NULL, 0);
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_event_i:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the event
Packit d345d1
 * @arg: the argument
Packit d345d1
 *
Packit d345d1
 * Records a performance event with one 32-bit integer argument.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_event_i (ShellPerfLog *perf_log,
Packit d345d1
                        const char   *name,
Packit d345d1
                        gint32        arg)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event = lookup_event (perf_log, name, "i");
Packit d345d1
  if (G_UNLIKELY (event == NULL))
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  record_event (perf_log, get_time(), event,
Packit d345d1
                (const guchar *)&arg, sizeof (arg));
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_event_x:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the event
Packit d345d1
 * @arg: the argument
Packit d345d1
 *
Packit d345d1
 * Records a performance event with one 64-bit integer argument.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_event_x (ShellPerfLog *perf_log,
Packit d345d1
                        const char   *name,
Packit d345d1
                        gint64        arg)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event = lookup_event (perf_log, name, "x");
Packit d345d1
  if (G_UNLIKELY (event == NULL))
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  record_event (perf_log, get_time(), event,
Packit d345d1
                (const guchar *)&arg, sizeof (arg));
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_event_s:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the event
Packit d345d1
 * @arg: the argument
Packit d345d1
 *
Packit d345d1
 * Records a performance event with one string argument.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_event_s (ShellPerfLog *perf_log,
Packit d345d1
                         const char   *name,
Packit d345d1
                         const char   *arg)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event = lookup_event (perf_log, name, "s");
Packit d345d1
  if (G_UNLIKELY (event == NULL))
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  record_event (perf_log, get_time(), event,
Packit d345d1
                (const guchar *)arg, strlen (arg) + 1);
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_define_statistic:
Packit d345d1
 * @name: name of the statistic and of the corresponding event.
Packit d345d1
 *  This should follow the same guidelines as for shell_perf_log_define_event()
Packit d345d1
 * @description: human readable description of the statistic.
Packit d345d1
 * @signature: The type of the data stored for statistic. Must
Packit d345d1
 *  currently be 'i' or 'x'.
Packit d345d1
 *
Packit d345d1
 * Defines a statistic. A statistic is a numeric value that is stored
Packit d345d1
 * by the performance log and recorded periodically or when
Packit d345d1
 * shell_perf_log_collect_statistics() is called explicitly.
Packit d345d1
 *
Packit d345d1
 * Code that defines a statistic should update it by calling
Packit d345d1
 * the update function for the particular data type of the statistic,
Packit d345d1
 * such as shell_perf_log_update_statistic_i(). This can be done
Packit d345d1
 * at any time, but would normally done inside a function registered
Packit d345d1
 * with shell_perf_log_add_statistics_callback(). These functions
Packit d345d1
 * are called immediately before statistics are recorded.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_define_statistic (ShellPerfLog *perf_log,
Packit d345d1
                                 const char   *name,
Packit d345d1
                                 const char   *description,
Packit d345d1
                                 const char   *signature)
Packit d345d1
{
Packit d345d1
  ShellPerfEvent *event;
Packit d345d1
  ShellPerfStatistic *statistic;
Packit d345d1
Packit d345d1
  if (strcmp (signature, "i") != 0 &&
Packit d345d1
      strcmp (signature, "x") != 0)
Packit d345d1
    {
Packit d345d1
      g_warning ("Only supported statistic signatures are 'i' and 'x'\n");
Packit d345d1
      return;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  event = define_event (perf_log, name, description, signature);
Packit d345d1
  if (event == NULL)
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  statistic = g_slice_new (ShellPerfStatistic);
Packit d345d1
  statistic->event = event;
Packit d345d1
Packit d345d1
  statistic->initialized = FALSE;
Packit d345d1
  statistic->recorded = FALSE;
Packit d345d1
Packit d345d1
  g_ptr_array_add (perf_log->statistics, statistic);
Packit d345d1
  g_hash_table_insert (perf_log->statistics_by_name, event->name, statistic);
Packit d345d1
}
Packit d345d1
Packit d345d1
static ShellPerfStatistic *
Packit d345d1
lookup_statistic (ShellPerfLog *perf_log,
Packit d345d1
                  const char   *name,
Packit d345d1
                  const char   *signature)
Packit d345d1
{
Packit d345d1
  ShellPerfStatistic *statistic = g_hash_table_lookup (perf_log->statistics_by_name, name);
Packit d345d1
Packit d345d1
  if (G_UNLIKELY (statistic == NULL))
Packit d345d1
    {
Packit d345d1
      g_warning ("Unknown statistic '%s'\n", name);
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (G_UNLIKELY (strcmp (statistic->event->signature, signature) != 0))
Packit d345d1
    {
Packit d345d1
      g_warning ("Statistic '%s'; defined with signature '%s', used with '%s'\n",
Packit d345d1
                 name, statistic->event->signature, signature);
Packit d345d1
      return NULL;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  return statistic;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_update_statistic_i:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the statistic
Packit d345d1
 * @value: new value for the statistic
Packit d345d1
 *
Packit d345d1
 * Updates the current value of an 32-bit integer statistic.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_update_statistic_i (ShellPerfLog *perf_log,
Packit d345d1
                                   const char   *name,
Packit d345d1
                                   gint32        value)
Packit d345d1
{
Packit d345d1
  ShellPerfStatistic *statistic;
Packit d345d1
Packit d345d1
  statistic = lookup_statistic (perf_log, name, "i");
Packit d345d1
  if (G_UNLIKELY (statistic == NULL))
Packit d345d1
      return;
Packit d345d1
Packit d345d1
  statistic->current_value.i = value;
Packit d345d1
  statistic->initialized = TRUE;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_update_statistic_x:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @name: name of the statistic
Packit d345d1
 * @value: new value for the statistic
Packit d345d1
 *
Packit d345d1
 * Updates the current value of an 64-bit integer statistic.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_update_statistic_x (ShellPerfLog *perf_log,
Packit d345d1
                                   const char   *name,
Packit d345d1
                                   gint64        value)
Packit d345d1
{
Packit d345d1
  ShellPerfStatistic *statistic;
Packit d345d1
Packit d345d1
  statistic = lookup_statistic (perf_log, name, "x");
Packit d345d1
  if (G_UNLIKELY (statistic == NULL))
Packit d345d1
      return;
Packit d345d1
Packit d345d1
  statistic->current_value.x = value;
Packit d345d1
  statistic->initialized = TRUE;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_add_statistics_callback:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @callback: function to call before recording statistics
Packit d345d1
 * @user_data: data to pass to @callback
Packit d345d1
 * @notify: function to call when @user_data is no longer needed
Packit d345d1
 *
Packit d345d1
 * Adds a function that will be called before statistics are recorded.
Packit d345d1
 * The function would typically compute one or more statistics values
Packit d345d1
 * and call a function such as shell_perf_log_update_statistic_i()
Packit d345d1
 * to update the value that will be recorded.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_add_statistics_callback (ShellPerfLog               *perf_log,
Packit d345d1
                                        ShellPerfStatisticsCallback callback,
Packit d345d1
                                        gpointer                    user_data,
Packit d345d1
                                        GDestroyNotify              notify)
Packit d345d1
{
Packit d345d1
  ShellPerfStatisticsClosure *closure = g_slice_new (ShellPerfStatisticsClosure);
Packit d345d1
Packit d345d1
  closure->callback = callback;
Packit d345d1
  closure->user_data = user_data;
Packit d345d1
  closure->notify = notify;
Packit d345d1
Packit d345d1
  g_ptr_array_add (perf_log->statistics_closures, closure);
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_collect_statistics:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 *
Packit d345d1
 * Calls all the update functions added with
Packit d345d1
 * shell_perf_log_add_statistics_callback() and then records events
Packit d345d1
 * for all statistics, followed by a perf.statisticsCollected event.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_collect_statistics (ShellPerfLog *perf_log)
Packit d345d1
{
Packit d345d1
  gint64 event_time = get_time ();
Packit d345d1
  gint64 collection_time;
Packit d345d1
  guint i;
Packit d345d1
Packit d345d1
  if (!perf_log->enabled)
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  for (i = 0; i < perf_log->statistics_closures->len; i++)
Packit d345d1
    {
Packit d345d1
      ShellPerfStatisticsClosure *closure;
Packit d345d1
Packit d345d1
      closure = g_ptr_array_index (perf_log->statistics_closures, i);
Packit d345d1
      closure->callback (perf_log, closure->user_data);
Packit d345d1
    }
Packit d345d1
Packit d345d1
  collection_time = get_time() - event_time;
Packit d345d1
Packit d345d1
  for (i = 0; i < perf_log->statistics->len; i++)
Packit d345d1
    {
Packit d345d1
      ShellPerfStatistic *statistic = g_ptr_array_index (perf_log->statistics, i);
Packit d345d1
Packit d345d1
      if (!statistic->initialized)
Packit d345d1
        continue;
Packit d345d1
Packit d345d1
      switch (statistic->event->signature[0])
Packit d345d1
        {
Packit d345d1
        case 'i':
Packit d345d1
          if (!statistic->recorded ||
Packit d345d1
              statistic->current_value.i != statistic->last_value.i)
Packit d345d1
            {
Packit d345d1
              record_event (perf_log, event_time, statistic->event,
Packit d345d1
                            (const guchar *)&statistic->current_value.i,
Packit d345d1
                            sizeof (gint32));
Packit d345d1
              statistic->last_value.i = statistic->current_value.i;
Packit d345d1
              statistic->recorded = TRUE;
Packit d345d1
            }
Packit d345d1
          break;
Packit d345d1
        case 'x':
Packit d345d1
          if (!statistic->recorded ||
Packit d345d1
              statistic->current_value.x != statistic->last_value.x)
Packit d345d1
            {
Packit d345d1
              record_event (perf_log, event_time, statistic->event,
Packit d345d1
                            (const guchar *)&statistic->current_value.x,
Packit d345d1
                            sizeof (gint64));
Packit d345d1
              statistic->last_value.x = statistic->current_value.x;
Packit d345d1
              statistic->recorded = TRUE;
Packit d345d1
            }
Packit d345d1
          break;
Packit d345d1
        default:
Packit d345d1
          g_warning ("Unsupported signature in event");
Packit d345d1
          break;
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
  record_event (perf_log, event_time,
Packit d345d1
                g_ptr_array_index (perf_log->events, EVENT_STATISTICS_COLLECTED),
Packit d345d1
                (const guchar *)&collection_time, sizeof (gint64));
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_replay:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @replay_function: (scope call): function to call for each event in the log
Packit d345d1
 * @user_data: data to pass to @replay_function
Packit d345d1
 *
Packit d345d1
 * Replays the log by calling the given function for each event
Packit d345d1
 * in the log.
Packit d345d1
 */
Packit d345d1
void
Packit d345d1
shell_perf_log_replay (ShellPerfLog            *perf_log,
Packit d345d1
                       ShellPerfReplayFunction  replay_function,
Packit d345d1
                       gpointer                 user_data)
Packit d345d1
{
Packit d345d1
  gint64 event_time = perf_log->start_time;
Packit d345d1
  GList *iter;
Packit d345d1
Packit d345d1
  for (iter = perf_log->blocks->head; iter; iter = iter->next)
Packit d345d1
    {
Packit d345d1
      ShellPerfBlock *block = iter->data;
Packit d345d1
      guint32 pos = 0;
Packit d345d1
Packit d345d1
      while (pos < block->bytes)
Packit d345d1
        {
Packit d345d1
          ShellPerfEvent *event;
Packit d345d1
          guint16 id;
Packit d345d1
          guint32 time_delta;
Packit d345d1
          GValue arg = { 0, };
Packit d345d1
Packit d345d1
          memcpy (&time_delta, block->buffer + pos, sizeof (guint32));
Packit d345d1
          pos += sizeof (guint32);
Packit d345d1
          memcpy (&id, block->buffer + pos, sizeof (guint16));
Packit d345d1
          pos += sizeof (guint16);
Packit d345d1
Packit d345d1
          if (id == EVENT_SET_TIME)
Packit d345d1
            {
Packit d345d1
              /* Internal, we don't include in the replay */
Packit d345d1
              memcpy (&event_time, block->buffer + pos, sizeof (gint64));
Packit d345d1
              pos += sizeof (gint64);
Packit d345d1
              continue;
Packit d345d1
            }
Packit d345d1
          else
Packit d345d1
            {
Packit d345d1
              event_time += time_delta;
Packit d345d1
            }
Packit d345d1
Packit d345d1
          event = g_ptr_array_index (perf_log->events, id);
Packit d345d1
Packit d345d1
          if (strcmp (event->signature, "") == 0)
Packit d345d1
            {
Packit d345d1
              /* We need to pass something, so pass an empty string */
Packit d345d1
              g_value_init (&arg, G_TYPE_STRING);
Packit d345d1
            }
Packit d345d1
          else if (strcmp (event->signature, "i") == 0)
Packit d345d1
            {
Packit d345d1
              gint32 l;
Packit d345d1
Packit d345d1
              memcpy (&l, block->buffer + pos, sizeof (gint32));
Packit d345d1
              pos += sizeof (gint32);
Packit d345d1
Packit d345d1
              g_value_init (&arg, G_TYPE_INT);
Packit d345d1
              g_value_set_int (&arg, l);
Packit d345d1
            }
Packit d345d1
          else if (strcmp (event->signature, "x") == 0)
Packit d345d1
            {
Packit d345d1
              gint64 l;
Packit d345d1
Packit d345d1
              memcpy (&l, block->buffer + pos, sizeof (gint64));
Packit d345d1
              pos += sizeof (gint64);
Packit d345d1
Packit d345d1
              g_value_init (&arg, G_TYPE_INT64);
Packit d345d1
              g_value_set_int64 (&arg, l);
Packit d345d1
            }
Packit d345d1
          else if (strcmp (event->signature, "s") == 0)
Packit d345d1
            {
Packit d345d1
              g_value_init (&arg, G_TYPE_STRING);
Packit d345d1
              g_value_set_string (&arg, (char *)block->buffer + pos);
Packit d345d1
              pos += strlen ((char *)(block->buffer + pos)) + 1;
Packit d345d1
            }
Packit d345d1
Packit d345d1
          replay_function (event_time, event->name, event->signature, &arg, user_data);
Packit d345d1
          g_value_unset (&arg;;
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
}
Packit d345d1
Packit d345d1
static char *
Packit d345d1
escape_quotes (const char *input)
Packit d345d1
{
Packit d345d1
  GString *result;
Packit d345d1
  const char *p;
Packit d345d1
Packit d345d1
  if (strchr (input, '"') == NULL)
Packit d345d1
    return (char *)input;
Packit d345d1
Packit d345d1
  result = g_string_new (NULL);
Packit d345d1
  for (p = input; *p; p++)
Packit d345d1
    {
Packit d345d1
      if (*p == '"')
Packit d345d1
        g_string_append (result, "\\\"");
Packit d345d1
      else
Packit d345d1
        g_string_append_c (result, *p);
Packit d345d1
    }
Packit d345d1
Packit d345d1
  return g_string_free (result, FALSE);
Packit d345d1
}
Packit d345d1
Packit d345d1
static gboolean
Packit d345d1
write_string (GOutputStream *out,
Packit d345d1
              const char    *str,
Packit d345d1
              GError       **error)
Packit d345d1
{
Packit d345d1
  return g_output_stream_write_all (out, str, strlen (str),
Packit d345d1
                                    NULL, NULL,
Packit d345d1
                                    error);
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_dump_events:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @out: output stream into which to write the event definitions
Packit d345d1
 * @error: location to store #GError, or %NULL
Packit d345d1
 *
Packit d345d1
 * Dump the definition of currently defined events and statistics, formatted
Packit d345d1
 * as JSON, to the specified output stream. The JSON output is an array,
Packit d345d1
 * with each element being a dictionary of the form:
Packit d345d1
 *
Packit d345d1
 * { name: <name of event>,
Packit d345d1
 *   description: 
Packit d345d1
 *   statistic: true } (only for statistics)
Packit d345d1
 *
Packit d345d1
 * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
Packit d345d1
 */
Packit d345d1
gboolean
Packit d345d1
shell_perf_log_dump_events (ShellPerfLog   *perf_log,
Packit d345d1
                            GOutputStream  *out,
Packit d345d1
                            GError        **error)
Packit d345d1
{
Packit d345d1
  GString *output;
Packit d345d1
  guint i;
Packit d345d1
Packit d345d1
  output = g_string_new (NULL);
Packit d345d1
  g_string_append (output, "[ ");
Packit d345d1
Packit d345d1
  for (i = 0; i < perf_log->events->len; i++)
Packit d345d1
    {
Packit d345d1
      ShellPerfEvent *event = g_ptr_array_index (perf_log->events, i);
Packit d345d1
      char *escaped_description = escape_quotes (event->description);
Packit d345d1
      gboolean is_statistic = g_hash_table_lookup (perf_log->statistics_by_name, event->name) != NULL;
Packit d345d1
Packit d345d1
      if (i != 0)
Packit d345d1
        g_string_append (output, ",\n  ");
Packit d345d1
Packit d345d1
      g_string_append_printf (output,
Packit d345d1
                                "{ \"name\": \"%s\",\n"
Packit d345d1
                              "    \"description\": \"%s\"",
Packit d345d1
                              event->name, escaped_description);
Packit d345d1
      if (is_statistic)
Packit d345d1
        g_string_append (output, ",\n    \"statistic\": true");
Packit d345d1
Packit d345d1
      g_string_append (output, " }");
Packit d345d1
Packit d345d1
      if (escaped_description != event->description)
Packit d345d1
        g_free (escaped_description);
Packit d345d1
    }
Packit d345d1
Packit d345d1
  g_string_append (output, " ]");
Packit d345d1
Packit d345d1
  return write_string (out, g_string_free (output, FALSE), error);
Packit d345d1
}
Packit d345d1
Packit d345d1
typedef struct {
Packit d345d1
  GOutputStream *out;
Packit d345d1
  GError *error;
Packit d345d1
  gboolean first;
Packit d345d1
} ReplayToJsonClosure;
Packit d345d1
Packit d345d1
static void
Packit d345d1
replay_to_json (gint64      time,
Packit d345d1
                const char *name,
Packit d345d1
                const char *signature,
Packit d345d1
                GValue     *arg,
Packit d345d1
                gpointer    user_data)
Packit d345d1
{
Packit d345d1
  ReplayToJsonClosure *closure = user_data;
Packit d345d1
  char *event_str;
Packit d345d1
Packit d345d1
  if (closure->error != NULL)
Packit d345d1
    return;
Packit d345d1
Packit d345d1
  if (!closure->first)
Packit d345d1
    {
Packit d345d1
      if (!write_string (closure->out, ",\n  ", &closure->error))
Packit d345d1
        return;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  closure->first = FALSE;
Packit d345d1
Packit d345d1
  if (strcmp (signature, "") == 0)
Packit d345d1
    {
Packit d345d1
      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\"]", time, name);
Packit d345d1
    }
Packit d345d1
  else if (strcmp (signature, "i") == 0)
Packit d345d1
    {
Packit d345d1
      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %i]",
Packit d345d1
                                   time,
Packit d345d1
                                   name,
Packit d345d1
                                   g_value_get_int (arg));
Packit d345d1
    }
Packit d345d1
  else if (strcmp (signature, "x") == 0)
Packit d345d1
    {
Packit d345d1
      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", %"G_GINT64_FORMAT "]",
Packit d345d1
                                   time,
Packit d345d1
                                   name,
Packit d345d1
                                   g_value_get_int64 (arg));
Packit d345d1
    }
Packit d345d1
  else if (strcmp (signature, "s") == 0)
Packit d345d1
    {
Packit d345d1
      const char *arg_str = g_value_get_string (arg);
Packit d345d1
      char *escaped = escape_quotes (arg_str);
Packit d345d1
Packit d345d1
      event_str = g_strdup_printf ("[%" G_GINT64_FORMAT ", \"%s\", \"%s\"]",
Packit d345d1
                                   time,
Packit d345d1
                                   name,
Packit d345d1
                                   g_value_get_string (arg));
Packit d345d1
Packit d345d1
      if (escaped != arg_str)
Packit d345d1
        g_free (escaped);
Packit d345d1
    }
Packit d345d1
  else
Packit d345d1
    {
Packit d345d1
      g_assert_not_reached ();
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (!write_string (closure->out, event_str, &closure->error))
Packit d345d1
      return;
Packit d345d1
}
Packit d345d1
Packit d345d1
/**
Packit d345d1
 * shell_perf_log_dump_log:
Packit d345d1
 * @perf_log: a #ShellPerfLog
Packit d345d1
 * @out: output stream into which to write the event log
Packit d345d1
 * @error: location to store #GError, or %NULL
Packit d345d1
 *
Packit d345d1
 * Writes the performance event log, formatted as JSON, to the specified
Packit d345d1
 * output stream. For performance reasons, the output stream passed
Packit d345d1
 * in should generally be a buffered (or memory) output stream, since
Packit d345d1
 * it will be written to in small pieces. The JSON output is an array
Packit d345d1
 * with the elements of the array also being arrays, of the form
Packit d345d1
 * '[' <time>, <event name> [, <event_arg>... ] ']'.
Packit d345d1
 *
Packit d345d1
 * Return value: %TRUE if the dump succeeded. %FALSE if an IO error occurred
Packit d345d1
 */
Packit d345d1
gboolean
Packit d345d1
shell_perf_log_dump_log (ShellPerfLog   *perf_log,
Packit d345d1
                         GOutputStream  *out,
Packit d345d1
                         GError        **error)
Packit d345d1
{
Packit d345d1
  ReplayToJsonClosure closure;
Packit d345d1
Packit d345d1
  closure.out = out;
Packit d345d1
  closure.error = NULL;
Packit d345d1
  closure.first = TRUE;
Packit d345d1
Packit d345d1
  if (!write_string (out, "[ ", &closure.error))
Packit d345d1
    return FALSE;
Packit d345d1
Packit d345d1
  shell_perf_log_replay (perf_log, replay_to_json, &closure);
Packit d345d1
Packit d345d1
  if (closure.error != NULL)
Packit d345d1
    {
Packit d345d1
      g_propagate_error (error, closure.error);
Packit d345d1
      return FALSE;
Packit d345d1
    }
Packit d345d1
Packit d345d1
  if (!write_string (out, " ]", &closure.error))
Packit d345d1
    return FALSE;
Packit d345d1
Packit d345d1
  return TRUE;
Packit d345d1
}