From 032bb45ce3fa91b7c0d6b8b76334756b036cecd5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 19 May 2018 19:35:42 +0100 Subject: [PATCH] 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. --- docs/reference/gdk/gdk4-sections.txt | 3 + 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/gdk.h | 1 + gdk/gdkprofiler.c | 259 ++++++ gdk/gdkprofiler.h | 32 + gdk/gdkprofilerprivate.h | 46 ++ gdk/meson.build | 8 + 11 files changed, 1935 insertions(+) create mode 100644 gdk/capture/sp-capture-types.h create mode 100644 gdk/capture/sp-capture-writer.c create mode 100644 gdk/capture/sp-capture-writer.h create mode 100644 gdk/capture/sp-clock.c create mode 100644 gdk/capture/sp-clock.h create mode 100644 gdk/gdkprofiler.c create mode 100644 gdk/gdkprofiler.h create mode 100644 gdk/gdkprofilerprivate.h diff --git a/docs/reference/gdk/gdk4-sections.txt b/docs/reference/gdk/gdk4-sections.txt index 8eb27f7c89..d78790e343 100644 --- a/docs/reference/gdk/gdk4-sections.txt +++ b/docs/reference/gdk/gdk4-sections.txt @@ -17,6 +17,9 @@ GDK_VERSION_MIN_REQUIRED GDK_VERSION_MAX_ALLOWED GDK_DISABLE_DEPRECATION_WARNINGS + +gdk_profiler_set_mark + GDK_TYPE_GRAB_STATUS 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 + * + * 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 . + */ + +#ifndef SP_CAPTURE_FORMAT_H +#define SP_CAPTURE_FORMAT_H + +#include + +#ifndef SP_DISABLE_GOBJECT +# include +#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 + * + * 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 . + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#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 + * + * 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 . + */ + +#ifndef SP_CLOCK_H +#define SP_CLOCK_H + +#include +#include + +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/gdk.h b/gdk/gdk.h index a537ed341f..d3baac51de 100644 --- a/gdk/gdk.h +++ b/gdk/gdk.h @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/gdk/gdkprofiler.c b/gdk/gdkprofiler.c new file mode 100644 index 0000000000..de04a3ce9f --- /dev/null +++ b/gdk/gdkprofiler.c @@ -0,0 +1,259 @@ +/* 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 . + */ + +#include "config.h" + +#include +#include + +#include "gdkversionmacros.h" +#include "gdkprofiler.h" +#include "gdkprofilerprivate.h" +#include "gdkframeclockprivate.h" + +#ifndef G_OS_WIN32 + +#include "capture/sp-capture-writer.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) +{ + if (writer) + return; + + sp_clock_init (); + + if (fd == -1) + { + gchar *filename; + + filename = g_strdup_printf ("gtk.%d.syscap", getpid ()); + g_print ("Writing profiling data to %s\n", filename); + writer = sp_capture_writer_new (filename, 16*1024); + g_free (filename); + } + else if (fd > 2) + writer = sp_capture_writer_new_from_fd (fd, 16*1024); + + if (writer) + running = TRUE; + + 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); +} + +#else + +void +gdk_profiler_start (int fd) +{ +} + +void +gdk_profiler_stop (void) +{ +} + +gboolean +gdk_profiler_is_running (void) +{ + return FALSE; +} + +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) +{ + return 0; +} + +void +gdk_profiler_set_counter (guint id, + gint64 time, + double value) +{ +} + +guint +gdk_profiler_define_int_counter (const char *name, + const char *description) +{ + return 0; +} + +void +gdk_profiler_set_int_counter (guint id, + gint64 time, + gint64 value) +{ +} + +#endif /* G_OS_WIN32 */ + +/** + * gdk_profiler_set_mark: + * @duration: the duration of the mark, or 0 + * @name: the name of the mark (up to 40 characters) + * @message: (optional): the message of the mark + * + * Insert a mark into the profiling data if we + * are currently profiling. + * This information will show up in tools like sysprof + * or GNOME Builder when viewing the profiling data. + * It can be used to mark interesting regions in the + * captured data. + * + * If the duration is non-zero, the mark applies to + * the timespan from @duration microseconds in the + * past to the current time. To mark just a point in + * time, pass 0 as duration. + * + * @name should be a short string, and @message is optional. + */ +void +gdk_profiler_set_mark (guint64 duration, + const char *name, + const char *message) +{ + guint64 start; + + start = g_get_monotonic_time () - duration; + gdk_profiler_add_mark (start, duration, name, message); +} diff --git a/gdk/gdkprofiler.h b/gdk/gdkprofiler.h new file mode 100644 index 0000000000..70c283df9e --- /dev/null +++ b/gdk/gdkprofiler.h @@ -0,0 +1,32 @@ +/* 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 . + */ + +#ifndef __GDK_PROFILER_H__ +#define __GDK_PROFILER_H__ + +#include + +G_BEGIN_DECLS + +GDK_AVAILABLE_IN_ALL +void gdk_profiler_set_mark (guint64 duration, + const char *name, + const char *message); + +G_END_DECLS + +#endif /* __GDK_PROFILER_H__ */ diff --git a/gdk/gdkprofilerprivate.h b/gdk/gdkprofilerprivate.h new file mode 100644 index 0000000000..9696c7e2dc --- /dev/null +++ b/gdk/gdkprofilerprivate.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 . + */ + +#ifndef __GDK_PROFILER_PRIVATE_H__ +#define __GDK_PROFILER_PRIVATE_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_PRIVATE_H__ */ diff --git a/gdk/meson.build b/gdk/meson.build index b2bf69620a..3c411c1fb9 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -45,8 +45,16 @@ gdk_public_sources = files([ 'gdkvulkancontext.c', 'gdksurface.c', 'gdksurfaceimpl.c', + 'gdkprofiler.c' ]) +if not win32_enabled + gdk_public_sources += files([ + 'capture/sp-capture-writer.c', + 'capture/sp-clock.c' + ]) +endif + gdk_public_headers = files([ 'gdk-autocleanup.h', 'gdk.h',