[gtk/tracing: 1329/1335] gdk: Add a profiler
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/tracing: 1329/1335] gdk: Add a profiler
- Date: Wed, 6 Feb 2019 14:18:36 +0000 (UTC)
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]