[gtk/tracing: 1329/1335] gdk: Add a profiler



commit 4746bc94bc483bf6922f5c0d53aa86f852720b61
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat May 19 19:35:42 2018 +0100

    gdk: Add a profiler
    
    This is writing data in the capture format of sysprof, using
    the SpCaptureWriter. For now, this is using a vendored copy
    of libsysprof. Eventually, we want to use the static library
    that sysprof provides.

 gdk/capture/sp-capture-types.h  |  224 ++++++++
 gdk/capture/sp-capture-writer.c | 1123 +++++++++++++++++++++++++++++++++++++++
 gdk/capture/sp-capture-writer.h |  132 +++++
 gdk/capture/sp-clock.c          |   52 ++
 gdk/capture/sp-clock.h          |   55 ++
 gdk/gdkprofiler.c               |  165 ++++++
 gdk/gdkprofiler.h               |   46 ++
 gdk/meson.build                 |    3 +
 8 files changed, 1800 insertions(+)
---
diff --git a/gdk/capture/sp-capture-types.h b/gdk/capture/sp-capture-types.h
new file mode 100644
index 0000000000..11be4de154
--- /dev/null
+++ b/gdk/capture/sp-capture-types.h
@@ -0,0 +1,224 @@
+/* sp-capture-types.h
+ *
+ * Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CAPTURE_FORMAT_H
+#define SP_CAPTURE_FORMAT_H
+
+#include <glib.h>
+
+#ifndef SP_DISABLE_GOBJECT
+# include <glib-object.h>
+#endif
+
+#include "sp-clock.h"
+
+G_BEGIN_DECLS
+
+#define SP_CAPTURE_MAGIC (GUINT32_TO_LE(0xFDCA975E))
+#define SP_CAPTURE_ALIGN (sizeof(SpCaptureAddress))
+
+#if __WORDSIZE == 64
+# define SP_CAPTURE_JITMAP_MARK    G_GUINT64_CONSTANT(0xE000000000000000)
+# define SP_CAPTURE_ADDRESS_FORMAT "0x%016lx"
+#else
+# define SP_CAPTURE_JITMAP_MARK    G_GUINT64_CONSTANT(0xE0000000)
+# define SP_CAPTURE_ADDRESS_FORMAT "0x%016llx"
+#endif
+
+#define SP_CAPTURE_CURRENT_TIME   (sp_clock_get_current_time())
+#define SP_CAPTURE_COUNTER_INT64  0
+#define SP_CAPTURE_COUNTER_DOUBLE 1
+
+typedef struct _SpCaptureReader    SpCaptureReader;
+typedef struct _SpCaptureWriter    SpCaptureWriter;
+typedef struct _SpCaptureCursor    SpCaptureCursor;
+typedef struct _SpCaptureCondition SpCaptureCondition;
+
+typedef guint64 SpCaptureAddress;
+
+typedef union
+{
+  gint64  v64;
+  gdouble vdbl;
+} SpCaptureCounterValue;
+
+typedef enum
+{
+  SP_CAPTURE_FRAME_TIMESTAMP = 1,
+  SP_CAPTURE_FRAME_SAMPLE    = 2,
+  SP_CAPTURE_FRAME_MAP       = 3,
+  SP_CAPTURE_FRAME_PROCESS   = 4,
+  SP_CAPTURE_FRAME_FORK      = 5,
+  SP_CAPTURE_FRAME_EXIT      = 6,
+  SP_CAPTURE_FRAME_JITMAP    = 7,
+  SP_CAPTURE_FRAME_CTRDEF    = 8,
+  SP_CAPTURE_FRAME_CTRSET    = 9,
+  SP_CAPTURE_FRAME_MARK      = 10,
+} SpCaptureFrameType;
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+  guint32 magic;
+  guint8  version;
+  guint32 little_endian : 1;
+  guint32 padding : 23;
+  gchar   capture_time[64];
+  gint64  time;
+  gint64  end_time;
+  gchar   suffix[168];
+} SpCaptureFileHeader;
+
+typedef struct
+{
+  guint16 len;
+  gint16  cpu;
+  gint32  pid;
+  gint64  time;
+  guint8  type;
+  guint64 padding : 56;
+  guint8  data[0];
+} SpCaptureFrame;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+  guint64        start;
+  guint64        end;
+  guint64        offset;
+  guint64        inode;
+  gchar          filename[0];
+} SpCaptureMap;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+  guint32        n_jitmaps;
+  guint8         data[0];
+} SpCaptureJitmap;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+  gchar          cmdline[0];
+} SpCaptureProcess;
+
+typedef struct
+{
+  SpCaptureFrame   frame;
+  guint16          n_addrs;
+  guint64          padding : 48;
+  SpCaptureAddress addrs[0];
+} SpCaptureSample;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+  GPid           child_pid;
+} SpCaptureFork;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+} SpCaptureExit;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+} SpCaptureTimestamp;
+
+typedef struct
+{
+  gchar                 category[32];
+  gchar                 name[32];
+  gchar                 description[52];
+  guint32               id : 24;
+  guint8                type;
+  SpCaptureCounterValue value;
+} SpCaptureCounter;
+
+typedef struct
+{
+  SpCaptureFrame   frame;
+  guint16          n_counters;
+  guint64          padding : 48;
+  SpCaptureCounter counters[0];
+} SpCaptureFrameCounterDefine;
+
+typedef struct
+{
+  /*
+   * 96 bytes might seem a bit odd, but the counter frame header is 32
+   * bytes.  So this makes a nice 2-cacheline aligned size which is
+   * useful when the number of counters is rather small.
+   */
+  guint32               ids[8];
+  SpCaptureCounterValue values[8];
+} SpCaptureCounterValues;
+
+typedef struct
+{
+  SpCaptureFrame         frame;
+  guint16                n_values;
+  guint64                padding : 48;
+  SpCaptureCounterValues values[0];
+} SpCaptureFrameCounterSet;
+
+typedef struct
+{
+  SpCaptureFrame frame;
+  gint64         duration;
+  gchar          group[24];
+  gchar          name[40];
+  gchar          message[0];
+} SpCaptureMark;
+
+#pragma pack(pop)
+
+G_STATIC_ASSERT (sizeof (SpCaptureFileHeader) == 256);
+G_STATIC_ASSERT (sizeof (SpCaptureFrame) == 24);
+G_STATIC_ASSERT (sizeof (SpCaptureMap) == 56);
+G_STATIC_ASSERT (sizeof (SpCaptureJitmap) == 28);
+G_STATIC_ASSERT (sizeof (SpCaptureProcess) == 24);
+G_STATIC_ASSERT (sizeof (SpCaptureSample) == 32);
+G_STATIC_ASSERT (sizeof (SpCaptureFork) == 28);
+G_STATIC_ASSERT (sizeof (SpCaptureExit) == 24);
+G_STATIC_ASSERT (sizeof (SpCaptureTimestamp) == 24);
+G_STATIC_ASSERT (sizeof (SpCaptureCounter) == 128);
+G_STATIC_ASSERT (sizeof (SpCaptureCounterValues) == 96);
+G_STATIC_ASSERT (sizeof (SpCaptureFrameCounterDefine) == 32);
+G_STATIC_ASSERT (sizeof (SpCaptureFrameCounterSet) == 32);
+G_STATIC_ASSERT (sizeof (SpCaptureMark) == 96);
+
+static inline gint
+sp_capture_address_compare (SpCaptureAddress a,
+                            SpCaptureAddress b)
+{
+  if (a < b)
+    return -1;
+  if (a > b)
+    return 1;
+  else
+    return 0;
+}
+
+G_END_DECLS
+
+#endif /* SP_CAPTURE_FORMAT_H */
+
diff --git a/gdk/capture/sp-capture-writer.c b/gdk/capture/sp-capture-writer.c
new file mode 100644
index 0000000000..5f53379f87
--- /dev/null
+++ b/gdk/capture/sp-capture-writer.c
@@ -0,0 +1,1123 @@
+/* sp-capture-writer.c
+ *
+ * Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "capture/sp-capture-writer.h"
+
+#define DEFAULT_BUFFER_SIZE (getpagesize() * 64L)
+#define INVALID_ADDRESS     (G_GUINT64_CONSTANT(0))
+
+typedef struct
+{
+  /* A pinter into the string buffer */
+  const gchar *str;
+
+  /* The unique address for the string */
+  guint64 addr;
+} SpCaptureJitmapBucket;
+
+struct _SpCaptureWriter
+{
+  /*
+   * This is our buffer location for incoming strings. This is used
+   * similarly to GStringChunk except there is only one-page, and after
+   * it fills, we flush to disk.
+   *
+   * This is paired with a closed hash table for deduplication.
+   */
+  gchar addr_buf[4096*4];
+
+  /* Our hashtable for deduplication. */
+  SpCaptureJitmapBucket addr_hash[512];
+
+  /* We keep the large fields above so that our allocation will be page
+   * alinged for the write buffer. This improves the performance of large
+   * writes to the target file-descriptor.
+   */
+  volatile gint ref_count;
+
+  /*
+   * Our address sequence counter. The value that comes from
+   * monotonically increasing this is OR'd with JITMAP_MARK to denote
+   * the address name should come from the JIT map.
+   */
+  gsize addr_seq;
+
+  /* Our position in addr_buf. */
+  gsize addr_buf_pos;
+
+  /*
+   * The number of hash table items in @addr_hash. This is an
+   * optimization so that we can avoid calculating the number of strings
+   * when flushing out the jitmap.
+   */
+  guint addr_hash_size;
+
+  /* Capture file handle */
+  int fd;
+
+  /* Our write buffer for fd */
+  guint8 *buf;
+  gsize pos;
+  gsize len;
+
+  /* counter id sequence */
+  gint next_counter_id;
+
+  /* Statistics while recording */
+  SpCaptureStat stat;
+};
+
+#ifndef SP_DISABLE_GOBJECT
+G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer,
+                     sp_capture_writer_ref, sp_capture_writer_unref)
+#endif
+
+static inline void
+sp_capture_writer_frame_init (SpCaptureFrame     *frame_,
+                              gint                len,
+                              gint                cpu,
+                              GPid                pid,
+                              gint64              time_,
+                              SpCaptureFrameType  type)
+{
+  g_assert (frame_ != NULL);
+
+  frame_->len = len;
+  frame_->cpu = cpu;
+  frame_->pid = pid;
+  frame_->time = time_;
+  frame_->type = type;
+  frame_->padding = 0;
+}
+
+static void
+sp_capture_writer_finalize (SpCaptureWriter *self)
+{
+  if (self != NULL)
+    {
+      sp_capture_writer_flush (self);
+      close (self->fd);
+      g_free (self->buf);
+      g_free (self);
+    }
+}
+
+SpCaptureWriter *
+sp_capture_writer_ref (SpCaptureWriter *self)
+{
+  g_assert (self != NULL);
+  g_assert (self->ref_count > 0);
+
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+void
+sp_capture_writer_unref (SpCaptureWriter *self)
+{
+  g_assert (self != NULL);
+  g_assert (self->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    sp_capture_writer_finalize (self);
+}
+
+static gboolean
+sp_capture_writer_flush_data (SpCaptureWriter *self)
+{
+  const guint8 *buf;
+  gssize written;
+  gsize to_write;
+
+  g_assert (self != NULL);
+  g_assert (self->pos <= self->len);
+  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+  buf = self->buf;
+  to_write = self->pos;
+
+  while (to_write > 0)
+    {
+      written = write (self->fd, buf, to_write);
+      if (written < 0)
+        return FALSE;
+
+      if (written == 0 && errno != EAGAIN)
+        return FALSE;
+
+      g_assert (written <= (gssize)to_write);
+
+      buf += written;
+      to_write -= written;
+    }
+
+  self->pos = 0;
+
+  return TRUE;
+}
+
+static inline void
+sp_capture_writer_realign (gsize *pos)
+{
+  *pos = (*pos + SP_CAPTURE_ALIGN - 1) & ~(SP_CAPTURE_ALIGN - 1);
+}
+
+static inline gboolean
+sp_capture_writer_ensure_space_for (SpCaptureWriter *self,
+                                    gsize            len)
+{
+  /* Check for max frame size */
+  if (len > G_MAXUSHORT)
+    return FALSE;
+
+  if ((self->len - self->pos) < len)
+    {
+      if (!sp_capture_writer_flush_data (self))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static inline gpointer
+sp_capture_writer_allocate (SpCaptureWriter *self,
+                            gsize           *len)
+{
+  gpointer p;
+
+  g_assert (self != NULL);
+  g_assert (len != NULL);
+  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+  sp_capture_writer_realign (len);
+
+  if (!sp_capture_writer_ensure_space_for (self, *len))
+    return NULL;
+
+  p = (gpointer)&self->buf[self->pos];
+
+  self->pos += *len;
+
+  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+  return p;
+}
+
+static gboolean
+sp_capture_writer_flush_jitmap (SpCaptureWriter *self)
+{
+  SpCaptureJitmap jitmap;
+  gssize r;
+  gsize len;
+
+  g_assert (self != NULL);
+
+  if (self->addr_hash_size == 0)
+    return TRUE;
+
+  g_assert (self->addr_buf_pos > 0);
+
+  len = sizeof jitmap + self->addr_buf_pos;
+
+  sp_capture_writer_realign (&len);
+
+  sp_capture_writer_frame_init (&jitmap.frame,
+                                len,
+                                -1,
+                                getpid (),
+                                SP_CAPTURE_CURRENT_TIME,
+                                SP_CAPTURE_FRAME_JITMAP);
+  jitmap.n_jitmaps = self->addr_hash_size;
+
+  if (sizeof jitmap != write (self->fd, &jitmap, sizeof jitmap))
+    return FALSE;
+
+  r = write (self->fd, self->addr_buf, len - sizeof jitmap);
+  if (r < 0 || (gsize)r != (len - sizeof jitmap))
+    return FALSE;
+
+  self->addr_buf_pos = 0;
+  self->addr_hash_size = 0;
+  memset (self->addr_hash, 0, sizeof self->addr_hash);
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_JITMAP]++;
+
+  return TRUE;
+}
+
+static gboolean
+sp_capture_writer_lookup_jitmap (SpCaptureWriter  *self,
+                                 const gchar      *name,
+                                 SpCaptureAddress *addr)
+{
+  guint hash;
+  guint i;
+
+  g_assert (self != NULL);
+  g_assert (name != NULL);
+  g_assert (addr != NULL);
+
+  hash = g_str_hash (name) % G_N_ELEMENTS (self->addr_hash);
+
+  for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
+    {
+      SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
+
+      if (bucket->str == NULL)
+        return FALSE;
+
+      if (strcmp (bucket->str, name) == 0)
+        {
+          *addr = bucket->addr;
+          return TRUE;
+        }
+    }
+
+  for (i = 0; i < hash; i++)
+    {
+      SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
+
+      if (bucket->str == NULL)
+        return FALSE;
+
+      if (strcmp (bucket->str, name) == 0)
+        {
+          *addr = bucket->addr;
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static SpCaptureAddress
+sp_capture_writer_insert_jitmap (SpCaptureWriter *self,
+                                 const gchar     *str)
+{
+  SpCaptureAddress addr;
+  gchar *dst;
+  gsize len;
+  guint hash;
+  guint i;
+
+  g_assert (self != NULL);
+  g_assert (str != NULL);
+  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+  len = sizeof addr + strlen (str) + 1;
+
+  if ((self->addr_hash_size == G_N_ELEMENTS (self->addr_hash)) ||
+      ((sizeof self->addr_buf - self->addr_buf_pos) < len))
+    {
+      if (!sp_capture_writer_flush_jitmap (self))
+        return INVALID_ADDRESS;
+
+      g_assert (self->addr_hash_size == 0);
+      g_assert (self->addr_buf_pos == 0);
+    }
+
+  g_assert (self->addr_hash_size < G_N_ELEMENTS (self->addr_hash));
+  g_assert (len > sizeof addr);
+
+  /* Allocate the next unique address */
+  addr = SP_CAPTURE_JITMAP_MARK | ++self->addr_seq;
+
+  /* Copy the address into the buffer */
+  dst = (gchar *)&self->addr_buf[self->addr_buf_pos];
+  memcpy (dst, &addr, sizeof addr);
+
+  /*
+   * Copy the string into the buffer, keeping dst around for
+   * when we insert into the hashtable.
+   */
+  dst += sizeof addr;
+  memcpy (dst, str, len - sizeof addr);
+
+  /* Advance our string cache position */
+  self->addr_buf_pos += len;
+  g_assert (self->addr_buf_pos <= sizeof self->addr_buf);
+
+  /* Now place the address into the hashtable */
+  hash = g_str_hash (str) % G_N_ELEMENTS (self->addr_hash);
+
+  /* Start from the current hash bucket and go forward */
+  for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
+    {
+      SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
+
+      if (G_LIKELY (bucket->str == NULL))
+        {
+          bucket->str = dst;
+          bucket->addr = addr;
+          self->addr_hash_size++;
+          return addr;
+        }
+    }
+
+  /* Wrap around to the beginning */
+  for (i = 0; i < hash; i++)
+    {
+      SpCaptureJitmapBucket *bucket = &self->addr_hash[i];
+
+      if (G_LIKELY (bucket->str == NULL))
+        {
+          bucket->str = dst;
+          bucket->addr = addr;
+          self->addr_hash_size++;
+          return addr;
+        }
+    }
+
+  g_assert_not_reached ();
+
+  return INVALID_ADDRESS;
+}
+
+SpCaptureWriter *
+sp_capture_writer_new_from_fd (int   fd,
+                               gsize buffer_size)
+{
+  g_autofree gchar *nowstr = NULL;
+  SpCaptureWriter *self;
+  SpCaptureFileHeader *header;
+  GTimeVal tv;
+  gsize header_len = sizeof(*header);
+
+  if (buffer_size == 0)
+    buffer_size = DEFAULT_BUFFER_SIZE;
+
+  g_assert (fd != -1);
+  g_assert (buffer_size % getpagesize() == 0);
+
+  if (ftruncate (fd, 0) != 0)
+    return NULL;
+
+  self = g_new0 (SpCaptureWriter, 1);
+  self->ref_count = 1;
+  self->fd = fd;
+  self->buf = (guint8 *)g_malloc0 (buffer_size);
+  self->len = buffer_size;
+  self->next_counter_id = 1;
+
+  g_get_current_time (&tv);
+  nowstr = g_time_val_to_iso8601 (&tv);
+
+  header = sp_capture_writer_allocate (self, &header_len);
+
+  if (header == NULL)
+    {
+      sp_capture_writer_finalize (self);
+      return NULL;
+    }
+
+  header->magic = SP_CAPTURE_MAGIC;
+  header->version = 1;
+#ifdef G_LITTLE_ENDIAN
+  header->little_endian = TRUE;
+#else
+  header->little_endian = FALSE;
+#endif
+  header->padding = 0;
+  g_strlcpy (header->capture_time, nowstr, sizeof header->capture_time);
+  header->time = SP_CAPTURE_CURRENT_TIME;
+  header->end_time = 0;
+  memset (header->suffix, 0, sizeof header->suffix);
+
+  if (!sp_capture_writer_flush_data (self))
+    {
+      sp_capture_writer_finalize (self);
+      return NULL;
+    }
+
+  g_assert (self->pos == 0);
+  g_assert (self->len > 0);
+  g_assert (self->len % getpagesize() == 0);
+  g_assert (self->buf != NULL);
+  g_assert (self->addr_hash_size == 0);
+  g_assert (self->fd != -1);
+
+  return self;
+}
+
+SpCaptureWriter *
+sp_capture_writer_new (const gchar *filename,
+                       gsize        buffer_size)
+{
+  SpCaptureWriter *self;
+  int fd;
+
+  g_assert (filename != NULL);
+  g_assert (buffer_size % getpagesize() == 0);
+
+  if ((-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640))) ||
+      (-1 == ftruncate (fd, 0L)))
+    return NULL;
+
+  self = sp_capture_writer_new_from_fd (fd, buffer_size);
+
+  if (self == NULL)
+    close (fd);
+
+  return self;
+}
+
+gboolean
+sp_capture_writer_add_map (SpCaptureWriter *self,
+                           gint64           time,
+                           gint             cpu,
+                           GPid             pid,
+                           guint64          start,
+                           guint64          end,
+                           guint64          offset,
+                           guint64          inode,
+                           const gchar     *filename)
+{
+  SpCaptureMap *ev;
+  gsize len;
+
+  if (filename == NULL)
+    filename = "";
+
+  g_assert (self != NULL);
+  g_assert (filename != NULL);
+
+  len = sizeof *ev + strlen (filename) + 1;
+
+  ev = (SpCaptureMap *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_MAP);
+  ev->start = start;
+  ev->end = end;
+  ev->offset = offset;
+  ev->inode = inode;
+
+  g_strlcpy (ev->filename, filename, len - sizeof *ev);
+  ev->filename[len - sizeof *ev - 1] = '\0';
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_MAP]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_add_mark (SpCaptureWriter *self,
+                            gint64           time,
+                            gint             cpu,
+                            GPid             pid,
+                            guint64          duration,
+                            const gchar     *group,
+                            const gchar     *name,
+                            const gchar     *message)
+{
+  SpCaptureMark *ev;
+  gsize message_len;
+  gsize len;
+
+  g_assert (self != NULL);
+  g_assert (name != NULL);
+  g_assert (group != NULL);
+
+  if (message == NULL)
+    message = "";
+  message_len = strlen (message) + 1;
+
+  len = sizeof *ev + message_len;
+  ev = (SpCaptureMark *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_MARK);
+
+  ev->duration = duration;
+  g_strlcpy (ev->group, group, sizeof ev->group);
+  g_strlcpy (ev->name, name, sizeof ev->name);
+  memcpy (ev->message, message, message_len);
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_MARK]++;
+
+  return TRUE;
+}
+
+SpCaptureAddress
+sp_capture_writer_add_jitmap (SpCaptureWriter *self,
+                              const gchar     *name)
+{
+  SpCaptureAddress addr = INVALID_ADDRESS;
+
+  if (name == NULL)
+    name = "";
+
+  g_assert (self != NULL);
+  g_assert (name != NULL);
+
+  if (!sp_capture_writer_lookup_jitmap (self, name, &addr))
+    addr = sp_capture_writer_insert_jitmap (self, name);
+
+  return addr;
+}
+
+gboolean
+sp_capture_writer_add_process (SpCaptureWriter *self,
+                               gint64           time,
+                               gint             cpu,
+                               GPid             pid,
+                               const gchar     *cmdline)
+{
+  SpCaptureProcess *ev;
+  gsize len;
+
+  if (cmdline == NULL)
+    cmdline = "";
+
+  g_assert (self != NULL);
+  g_assert (cmdline != NULL);
+
+  len = sizeof *ev + strlen (cmdline) + 1;
+
+  ev = (SpCaptureProcess *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_PROCESS);
+
+  g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
+  ev->cmdline[len - sizeof *ev - 1] = '\0';
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_PROCESS]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_add_sample (SpCaptureWriter        *self,
+                              gint64                  time,
+                              gint                    cpu,
+                              GPid                    pid,
+                              const SpCaptureAddress *addrs,
+                              guint                   n_addrs)
+{
+  SpCaptureSample *ev;
+  gsize len;
+
+  g_assert (self != NULL);
+
+  len = sizeof *ev + (n_addrs * sizeof (SpCaptureAddress));
+
+  ev = (SpCaptureSample *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_SAMPLE);
+  ev->n_addrs = n_addrs;
+
+  memcpy (ev->addrs, addrs, (n_addrs * sizeof (SpCaptureAddress)));
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_SAMPLE]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_add_fork (SpCaptureWriter *self,
+                            gint64           time,
+                            gint             cpu,
+                            GPid             pid,
+                            GPid             child_pid)
+{
+  SpCaptureFork *ev;
+  gsize len = sizeof *ev;
+
+  g_assert (self != NULL);
+
+  ev = (SpCaptureFork *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_FORK);
+  ev->child_pid = child_pid;
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_FORK]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_add_exit (SpCaptureWriter *self,
+                            gint64           time,
+                            gint             cpu,
+                            GPid             pid)
+{
+  SpCaptureExit *ev;
+  gsize len = sizeof *ev;
+
+  g_assert (self != NULL);
+
+  ev = (SpCaptureExit *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_EXIT);
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_EXIT]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_add_timestamp (SpCaptureWriter *self,
+                                 gint64           time,
+                                 gint             cpu,
+                                 GPid             pid)
+{
+  SpCaptureTimestamp *ev;
+  gsize len = sizeof *ev;
+
+  g_assert (self != NULL);
+
+  ev = (SpCaptureTimestamp *)sp_capture_writer_allocate (self, &len);
+  if (!ev)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&ev->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_TIMESTAMP);
+
+    self->stat.frame_count[SP_CAPTURE_FRAME_TIMESTAMP]++;
+
+  return TRUE;
+}
+
+static gboolean
+sp_capture_writer_flush_end_time (SpCaptureWriter *self)
+{
+  gint64 end_time = SP_CAPTURE_CURRENT_TIME;
+  ssize_t ret;
+
+  g_assert (self != NULL);
+
+  /* This field is opportunistic, so a failure is okay. */
+
+again:
+  ret = pwrite (self->fd,
+                &end_time,
+                sizeof (end_time),
+                G_STRUCT_OFFSET (SpCaptureFileHeader, end_time));
+
+  if (ret < 0 && errno == EAGAIN)
+    goto again;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_flush (SpCaptureWriter *self)
+{
+  g_assert (self != NULL);
+
+  return (sp_capture_writer_flush_jitmap (self) &&
+          sp_capture_writer_flush_data (self) &&
+          sp_capture_writer_flush_end_time (self));
+}
+
+/**
+ * sp_capture_writer_save_as:
+ * @self: A #SpCaptureWriter
+ * @filename: the file to save the capture as
+ * @error: a location for a #GError or %NULL.
+ *
+ * Saves the captured data as the file @filename.
+ *
+ * This is primarily useful if the writer was created with a memory-backed
+ * file-descriptor such as a memfd or tmpfs file on Linux.
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is set.
+ */
+gboolean
+sp_capture_writer_save_as (SpCaptureWriter            *self,
+                           const gchar                *filename,
+                           GError                    **error)
+{
+  gsize to_write;
+  off_t in_off;
+  off_t pos;
+  int fd = -1;
+
+  g_assert (self != NULL);
+  g_assert (self->fd != -1);
+  g_assert (filename != NULL);
+
+  if (-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640)))
+    goto handle_errno;
+
+  if (!sp_capture_writer_flush (self))
+    goto handle_errno;
+
+  if (-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
+    goto handle_errno;
+
+  to_write = pos;
+  in_off = 0;
+
+  while (to_write > 0)
+    {
+      gssize written;
+
+      written = sendfile (fd, self->fd, &in_off, pos);
+
+      if (written < 0)
+        goto handle_errno;
+
+      if (written == 0 && errno != EAGAIN)
+        goto handle_errno;
+
+      g_assert (written <= (gssize)to_write);
+
+      to_write -= written;
+    }
+
+  close (fd);
+
+  return TRUE;
+
+handle_errno:
+  g_set_error (error,
+               G_FILE_ERROR,
+               g_file_error_from_errno (errno),
+               "%s", g_strerror (errno));
+
+  if (fd != -1)
+    {
+      close (fd);
+      g_unlink (filename);
+    }
+
+  return FALSE;
+}
+
+/**
+ * _sp_capture_writer_splice_from_fd:
+ * @self: An #SpCaptureWriter
+ * @fd: the fd to read from.
+ * @error: A location for a #GError, or %NULL.
+ *
+ * This is internal API for SpCaptureWriter and SpCaptureReader to
+ * communicate when splicing a reader into a writer.
+ *
+ * This should not be used outside of #SpCaptureReader or
+ * #SpCaptureWriter.
+ *
+ * This will not advance the position of @fd.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+_sp_capture_writer_splice_from_fd (SpCaptureWriter  *self,
+                                   int               fd,
+                                   GError          **error)
+{
+  struct stat stbuf;
+  off_t in_off;
+  gsize to_write;
+
+  g_assert (self != NULL);
+  g_assert (self->fd != -1);
+
+  if (-1 == fstat (fd, &stbuf))
+    goto handle_errno;
+
+  if (stbuf.st_size < 256)
+    {
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   G_FILE_ERROR_INVAL,
+                   "Cannot splice, possibly corrupt file.");
+      return FALSE;
+    }
+
+  in_off = 256;
+  to_write = stbuf.st_size - in_off;
+
+  while (to_write > 0)
+    {
+      gssize written;
+
+      written = sendfile (self->fd, fd, &in_off, to_write);
+
+      if (written < 0)
+        goto handle_errno;
+
+      if (written == 0 && errno != EAGAIN)
+        goto handle_errno;
+
+      g_assert (written <= (gssize)to_write);
+
+      to_write -= written;
+    }
+
+  return TRUE;
+
+handle_errno:
+  g_set_error (error,
+               G_FILE_ERROR,
+               g_file_error_from_errno (errno),
+               "%s", g_strerror (errno));
+
+  return FALSE;
+}
+
+/**
+ * sp_capture_writer_splice:
+ * @self: An #SpCaptureWriter
+ * @dest: An #SpCaptureWriter
+ * @error: A location for a #GError, or %NULL.
+ *
+ * This function will copy the capture @self into the capture @dest.  This
+ * tries to be semi-efficient by using sendfile() to copy the contents between
+ * the captures. @self and @dest will be flushed before the contents are copied
+ * into the @dest file-descriptor.
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and and @error is set.
+ */
+gboolean
+sp_capture_writer_splice (SpCaptureWriter  *self,
+                          SpCaptureWriter  *dest,
+                          GError          **error)
+{
+  gboolean ret;
+  off_t pos;
+
+  g_assert (self != NULL);
+  g_assert (self->fd != -1);
+  g_assert (dest != NULL);
+  g_assert (dest->fd != -1);
+
+  /* Flush before writing anything to ensure consistency */
+  if (!sp_capture_writer_flush (self) || !sp_capture_writer_flush (dest))
+    goto handle_errno;
+
+  /* Track our current position so we can reset */
+  if ((off_t)-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
+    goto handle_errno;
+
+  /* Perform the splice */
+  ret = _sp_capture_writer_splice_from_fd (dest, self->fd, error);
+
+  /* Now reset or file-descriptor position (it should be the same */
+  if (pos != lseek (self->fd, pos, SEEK_SET))
+    {
+      ret = FALSE;
+      goto handle_errno;
+    }
+
+  return ret;
+
+handle_errno:
+  g_set_error (error,
+               G_FILE_ERROR,
+               g_file_error_from_errno (errno),
+               "%s", g_strerror (errno));
+
+  return FALSE;
+}
+
+/**
+ * sp_capture_writer_stat:
+ * @self: A #SpCaptureWriter
+ * @stat: (out): A location for an #SpCaptureStat
+ *
+ * This function will fill @stat with statistics generated while capturing
+ * the profiler session.
+ */
+void
+sp_capture_writer_stat (SpCaptureWriter *self,
+                        SpCaptureStat   *stat)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (stat != NULL);
+
+  *stat = self->stat;
+}
+
+gboolean
+sp_capture_writer_define_counters (SpCaptureWriter        *self,
+                                   gint64                  time,
+                                   gint                    cpu,
+                                   GPid                    pid,
+                                   const SpCaptureCounter *counters,
+                                   guint                   n_counters)
+{
+  SpCaptureFrameCounterDefine *def;
+  gsize len;
+  guint i;
+
+  g_assert (self != NULL);
+  g_assert (counters != NULL);
+
+  if (n_counters == 0)
+    return TRUE;
+
+  len = sizeof *def + (sizeof *counters * n_counters);
+
+  def = (SpCaptureFrameCounterDefine *)sp_capture_writer_allocate (self, &len);
+  if (!def)
+    return FALSE;
+
+  sp_capture_writer_frame_init (&def->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_CTRDEF);
+  def->padding = 0;
+  def->n_counters = n_counters;
+
+  for (i = 0; i < n_counters; i++)
+    def->counters[i] = counters[i];
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_CTRDEF]++;
+
+  return TRUE;
+}
+
+gboolean
+sp_capture_writer_set_counters (SpCaptureWriter             *self,
+                                gint64                       time,
+                                gint                         cpu,
+                                GPid                         pid,
+                                const guint                 *counters_ids,
+                                const SpCaptureCounterValue *values,
+                                guint                        n_counters)
+{
+  SpCaptureFrameCounterSet *set;
+  gsize len;
+  guint n_groups;
+  guint group;
+  guint field;
+  guint i;
+
+  g_assert (self != NULL);
+  g_assert (counters_ids != NULL);
+  g_assert (values != NULL || !n_counters);
+
+  if (n_counters == 0)
+    return TRUE;
+
+  /* Determine how many value groups we need */
+  n_groups = n_counters / G_N_ELEMENTS (set->values[0].values);
+  if ((n_groups * G_N_ELEMENTS (set->values[0].values)) != n_counters)
+    n_groups++;
+
+  len = sizeof *set + (n_groups * sizeof (SpCaptureCounterValues));
+
+  set = (SpCaptureFrameCounterSet *)sp_capture_writer_allocate (self, &len);
+  if (!set)
+    return FALSE;
+
+  memset (set, 0, len);
+
+  sp_capture_writer_frame_init (&set->frame,
+                                len,
+                                cpu,
+                                pid,
+                                time,
+                                SP_CAPTURE_FRAME_CTRSET);
+  set->padding = 0;
+  set->n_values = n_groups;
+
+  for (i = 0, group = 0, field = 0; i < n_counters; i++)
+    {
+      set->values[group].ids[field] = counters_ids[i];
+      set->values[group].values[field] = values[i];
+
+      field++;
+
+      if (field == G_N_ELEMENTS (set->values[0].values))
+        {
+          field = 0;
+          group++;
+        }
+    }
+
+  self->stat.frame_count[SP_CAPTURE_FRAME_CTRSET]++;
+
+  return TRUE;
+}
+
+gint
+sp_capture_writer_request_counter (SpCaptureWriter *self,
+                                   guint            n_counters)
+{
+  gint ret;
+
+  g_assert (self != NULL);
+
+  ret = self->next_counter_id;
+  self->next_counter_id += n_counters;
+
+  return ret;
+}
+
diff --git a/gdk/capture/sp-capture-writer.h b/gdk/capture/sp-capture-writer.h
new file mode 100644
index 0000000000..f29b18a798
--- /dev/null
+++ b/gdk/capture/sp-capture-writer.h
@@ -0,0 +1,132 @@
+/* sp-capture-writer.h
+ *
+ * Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CAPTURE_WRITER_H
+#define SP_CAPTURE_WRITER_H
+
+#include "capture/sp-capture-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SpCaptureWriter SpCaptureWriter;
+
+typedef struct
+{
+  /*
+   * The number of frames indexed by SpCaptureFrameType
+   */
+  gsize frame_count[16];
+
+  /*
+   * Padding for future expansion.
+   */
+  gsize padding[48];
+} SpCaptureStat;
+
+SpCaptureWriter    *sp_capture_writer_new             (const gchar             *filename,
+                                                       gsize                    buffer_size);
+SpCaptureWriter    *sp_capture_writer_new_from_fd     (int                      fd,
+                                                       gsize                    buffer_size);
+SpCaptureWriter    *sp_capture_writer_ref             (SpCaptureWriter         *self);
+void                sp_capture_writer_unref           (SpCaptureWriter         *self);
+void                sp_capture_writer_stat            (SpCaptureWriter         *self,
+                                                       SpCaptureStat           *stat);
+gboolean            sp_capture_writer_add_map         (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       guint64                  start,
+                                                       guint64                  end,
+                                                       guint64                  offset,
+                                                       guint64                  inode,
+                                                       const gchar             *filename);
+gboolean            sp_capture_writer_add_mark        (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       guint64                  duration,
+                                                       const gchar             *group,
+                                                       const gchar             *name,
+                                                       const gchar             *message);
+guint64             sp_capture_writer_add_jitmap      (SpCaptureWriter         *self,
+                                                       const gchar             *name);
+gboolean            sp_capture_writer_add_process     (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       const gchar             *cmdline);
+gboolean            sp_capture_writer_add_sample      (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       const SpCaptureAddress  *addrs,
+                                                       guint                    n_addrs);
+gboolean            sp_capture_writer_add_fork        (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       GPid                     child_pid);
+gboolean            sp_capture_writer_add_exit        (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid);
+gboolean            sp_capture_writer_add_timestamp   (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid);
+gboolean            sp_capture_writer_define_counters (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       const SpCaptureCounter  *counters,
+                                                       guint                    n_counters);
+gboolean            sp_capture_writer_set_counters    (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       const guint             *counters_ids,
+                                                       const SpCaptureCounterValue *values,
+                                                       guint                    n_counters);
+gboolean            sp_capture_writer_flush           (SpCaptureWriter         *self);
+gboolean            sp_capture_writer_save_as         (SpCaptureWriter         *self,
+                                                       const gchar             *filename,
+                                                       GError                 **error);
+gint                sp_capture_writer_request_counter (SpCaptureWriter         *self,
+                                                       guint                    n_counters);
+SpCaptureReader    *sp_capture_writer_create_reader   (SpCaptureWriter         *self,
+                                                       GError                 **error);
+gboolean            sp_capture_writer_splice          (SpCaptureWriter         *self,
+                                                       SpCaptureWriter         *dest,
+                                                       GError                 **error);
+gboolean            _sp_capture_writer_splice_from_fd (SpCaptureWriter         *self,
+                                                       int                      fd,
+                                                       GError                 **error) G_GNUC_INTERNAL;
+
+#ifndef SP_DISABLE_GOBJECT
+# define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
+  GType sp_capture_writer_get_type (void);
+#endif
+
+#if GLIB_CHECK_VERSION(2, 44, 0)
+  G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureWriter, sp_capture_writer_unref)
+#endif
+
+G_END_DECLS
+
+#endif /* SP_CAPTURE_WRITER_H */
+
diff --git a/gdk/capture/sp-clock.c b/gdk/capture/sp-clock.c
new file mode 100644
index 0000000000..8811ac44ca
--- /dev/null
+++ b/gdk/capture/sp-clock.c
@@ -0,0 +1,52 @@
+/* sp-clock.c
+ *
+ * Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sp-clock.h"
+
+gint sp_clock = -1;
+
+void
+sp_clock_init (void)
+{
+  static const gint clock_ids[] = {
+    CLOCK_MONOTONIC_RAW,
+    CLOCK_MONOTONIC_COARSE,
+    CLOCK_MONOTONIC,
+    CLOCK_REALTIME_COARSE,
+    CLOCK_REALTIME,
+  };
+  guint i;
+
+  if (sp_clock != -1)
+    return;
+
+  for (i = 0; i < G_N_ELEMENTS (clock_ids); i++)
+    {
+      struct timespec ts;
+      int clock_id = clock_ids [i];
+
+      if (0 == clock_gettime (clock_id, &ts))
+        {
+          sp_clock = clock_id;
+          return;
+        }
+    }
+
+  g_assert_not_reached ();
+}
+
diff --git a/gdk/capture/sp-clock.h b/gdk/capture/sp-clock.h
new file mode 100644
index 0000000000..09dd11809f
--- /dev/null
+++ b/gdk/capture/sp-clock.h
@@ -0,0 +1,55 @@
+/* sp-clock.h
+ *
+ * Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CLOCK_H
+#define SP_CLOCK_H
+
+#include <glib.h>
+#include <time.h>
+
+G_BEGIN_DECLS
+
+typedef gint SpClock;
+typedef gint64 SpTimeStamp;
+typedef gint32 SpTimeSpan;
+
+extern SpClock sp_clock;
+
+static inline SpTimeStamp
+sp_clock_get_current_time (void)
+{
+  struct timespec ts;
+
+  clock_gettime (sp_clock, &ts);
+
+  return (ts.tv_sec * G_GINT64_CONSTANT (1000000000)) + ts.tv_nsec;
+}
+
+static inline SpTimeSpan
+sp_clock_get_relative_time (SpTimeStamp epoch)
+{
+  return sp_clock_get_current_time () - epoch;
+}
+
+void sp_clock_init (void);
+
+G_END_DECLS
+
+#endif /* SP_CLOCK_H */
+
+
diff --git a/gdk/gdkprofiler.c b/gdk/gdkprofiler.c
new file mode 100644
index 0000000000..c515b5f89c
--- /dev/null
+++ b/gdk/gdkprofiler.c
@@ -0,0 +1,165 @@
+/* GDK - The GIMP Drawing Kit
+ *
+ * gdkprofiler.c: A simple profiler
+ *
+ * Copyright © 2018 Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "capture/sp-capture-writer.h"
+#include "gdkprofiler.h"
+#include "gdkframeclockprivate.h"
+
+static SpCaptureWriter *writer = NULL;
+static gboolean running = FALSE;
+
+static void
+profiler_stop (void)
+{
+  if (writer)
+    sp_capture_writer_unref (writer);
+}
+
+void
+gdk_profiler_start (int fd)
+{
+  running = TRUE;
+
+  if (writer)
+    return;
+
+  sp_clock_init ();
+
+  if (fd == -1)
+    {
+      gchar *filename;
+
+      filename = g_strdup_printf ("gtk.%d.syscap", getpid ());
+      writer = sp_capture_writer_new (filename, 16*1024);
+      g_free (filename);
+    }
+  else
+    writer = sp_capture_writer_new_from_fd (fd, 16*1024);
+
+  atexit (profiler_stop);
+}
+
+void
+gdk_profiler_stop (void)
+{
+  running = FALSE;
+}
+
+gboolean
+gdk_profiler_is_running (void)
+{
+  return running;
+}
+
+void
+gdk_profiler_add_mark (gint64      start,
+                       guint64     duration,
+                       const char *name,
+                       const char *message)
+{
+  if (!running)
+    return;
+
+  sp_capture_writer_add_mark (writer,
+                              start,
+                              -1, getpid (),
+                              duration,
+                              "gtk", name, message);
+}
+
+static guint
+define_counter (const char *name,
+                const char *description,
+                int         type)
+{
+  SpCaptureCounter counter;
+
+  if (!writer)
+    return 0;
+
+  counter.id = (guint) sp_capture_writer_request_counter (writer, 1);
+  counter.type = type;
+  counter.value.vdbl = 0;
+  g_strlcpy (counter.category, "gtk", sizeof counter.category);
+  g_strlcpy (counter.name, name, sizeof counter.name);
+  g_strlcpy (counter.description, description, sizeof counter.name);
+
+  sp_capture_writer_define_counters (writer,
+                                     SP_CAPTURE_CURRENT_TIME,
+                                     -1,
+                                     getpid (),
+                                     &counter,
+                                     1);
+
+  return counter.id;
+}
+
+guint
+gdk_profiler_define_counter (const char *name,
+                             const char *description)
+{
+  return define_counter (name, description, SP_CAPTURE_COUNTER_DOUBLE);
+}
+
+guint
+gdk_profiler_define_int_counter (const char *name,
+                                 const char *description)
+{
+  return define_counter (name, description, SP_CAPTURE_COUNTER_INT64);
+}
+
+void
+gdk_profiler_set_counter (guint  id,
+                          gint64 time,
+                          double val)
+{
+  SpCaptureCounterValue value;
+
+  if (!running)
+    return;
+
+  value.vdbl = val;
+  sp_capture_writer_set_counters (writer,
+                                  time,
+                                  -1, getpid (),
+                                  &id, &value, 1);
+}
+
+void
+gdk_profiler_set_int_counter (guint  id,
+                              gint64 time,
+                              gint64 val)
+{
+  SpCaptureCounterValue value;
+
+  if (!running)
+    return;
+
+  value.v64 = val;
+  sp_capture_writer_set_counters (writer,
+                                  time,
+                                  -1, getpid (),
+                                  &id, &value, 1);
+}
diff --git a/gdk/gdkprofiler.h b/gdk/gdkprofiler.h
new file mode 100644
index 0000000000..ca790f39f2
--- /dev/null
+++ b/gdk/gdkprofiler.h
@@ -0,0 +1,46 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2018 Red Hat, Inc. 
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_PROFILER_H__
+#define __GDK_PROFILER_H__
+
+#include "gdk/gdkframeclock.h"
+#include "gdk/gdkdisplay.h"
+
+G_BEGIN_DECLS
+
+void     gdk_profiler_start      (int fd);
+void     gdk_profiler_stop       (void);
+gboolean gdk_profiler_is_running (void);
+void     gdk_profiler_add_mark   (gint64           start,
+                                  guint64          duration,
+                                  const char      *name,
+                                  const char      *message);
+guint    gdk_profiler_define_counter (const char *name,
+                                      const char *description);
+void     gdk_profiler_set_counter    (guint  id,
+                                      gint64 time,
+                                      double value);
+guint    gdk_profiler_define_int_counter (const char *name,
+                                          const char *description);
+void     gdk_profiler_set_int_counter (guint  id,
+                                       gint64 time,
+                                       gint64 value);
+
+G_END_DECLS
+
+#endif  /* __GDK_PROFILER_H__ */
diff --git a/gdk/meson.build b/gdk/meson.build
index a7b5f2e630..a9db0425dd 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -45,6 +45,9 @@ gdk_public_sources = files([
   'gdkvulkancontext.c',
   'gdksurface.c',
   'gdksurfaceimpl.c',
+  'gdkprofiler.c',
+  'capture/sp-capture-writer.c',
+  'capture/sp-clock.c'
 ])
 
 gdk_public_headers = files([


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