[gjs: 1/11] profiler: add SPSProfiler implementation



commit 0ce69797fe453e16c1fd25c8ed92535c730b94cd
Author: Christian Hergert <christian hergert me>
Date:   Mon Jul 31 15:39:10 2017 +0100

    profiler: add SPSProfiler implementation
    
    Currently this is Linux only as it requires the Linux implementation of
    POSIX signals.
    
    There is a simple API, and the profile output is written to
    /tmp/gjs-profile-<pid>. Description of the capture format is provided
    in profile.cpp.
    
    For apps started with gjs-console, you can toggle on/off profiling by
    sending SIGUSR2 to the process.
    
    (Later modified by Philip Chimento; trimmed unneeded functionality out of
    the sysprof capture code, fixed coding style, fixed crash by unsetting
    the profiling stack on stop, and switched the license to conform to the
    rest of the project.)
    
    Closes #31.

 gjs-srcs.mk              |   5 +
 gjs/console.cpp          |  14 ++
 gjs/gjs.h                |   1 +
 gjs/profiler.cpp         | 548 +++++++++++++++++++++++++++++++++++++++++
 gjs/profiler.h           |  70 ++++++
 test/gjs-tests.cpp       |  27 +-
 util/sp-capture-types.h  | 127 ++++++++++
 util/sp-capture-writer.c | 628 +++++++++++++++++++++++++++++++++++++++++++++++
 util/sp-capture-writer.h |  81 ++++++
 9 files changed, 1500 insertions(+), 1 deletion(-)
---
diff --git a/gjs-srcs.mk b/gjs-srcs.mk
index 1c61c60..0f9cf82 100644
--- a/gjs-srcs.mk
+++ b/gjs-srcs.mk
@@ -3,6 +3,7 @@ gjs_public_headers =            \
        gjs/coverage.h          \
        gjs/gjs.h               \
        gjs/macros.h            \
+       gjs/profiler.h          \
        util/error.h            \
        $(NULL)
 
@@ -74,6 +75,7 @@ gjs_srcs =                            \
        gjs/module.cpp                  \
        gjs/native.cpp                  \
        gjs/native.h                    \
+       gjs/profiler.cpp                \
        gjs/stack.cpp                   \
        modules/modules.cpp             \
        modules/modules.h               \
@@ -84,6 +86,9 @@ gjs_srcs =                            \
        util/log.h                      \
        util/misc.cpp                   \
        util/misc.h                     \
+       util/sp-capture-types.h         \
+       util/sp-capture-writer.c        \
+       util/sp-capture-writer.h        \
        $(NULL)
 
 # These files were part of a separate library
diff --git a/gjs/console.cpp b/gjs/console.cpp
index 0956e84..993c692 100644
--- a/gjs/console.cpp
+++ b/gjs/console.cpp
@@ -33,6 +33,7 @@
 static char **include_path = NULL;
 static char **coverage_prefixes = NULL;
 static char *coverage_output_path = NULL;
+static char *profile_output_path = nullptr;
 static char *command = NULL;
 static gboolean print_version = false;
 
@@ -42,6 +43,7 @@ static GOptionEntry entries[] = {
     { "coverage-prefix", 'C', 0, G_OPTION_ARG_STRING_ARRAY, &coverage_prefixes, "Add the prefix PREFIX to 
the list of files to generate coverage info for", "PREFIX" },
     { "coverage-output", 0, 0, G_OPTION_ARG_STRING, &coverage_output_path, "Write coverage output to a 
directory DIR. This option is mandatory when using --coverage-path", "DIR", },
     { "include-path", 'I', 0, G_OPTION_ARG_STRING_ARRAY, &include_path, "Add the directory DIR to the list 
of directories to search for js files.", "DIR" },
+    { "profile-output", 0, 0, G_OPTION_ARG_FILENAME, &profile_output_path, "Enable the profiler and Write 
output to FILE", "FILE" },
     { NULL }
 };
 
@@ -155,6 +157,7 @@ main(int argc, char **argv)
     GError *error = NULL;
     GjsContext *js_context;
     GjsCoverage *coverage = NULL;
+    GjsProfiler *profiler;
     char *script;
     const char *filename;
     const char *program_name;
@@ -270,6 +273,16 @@ main(int argc, char **argv)
         g_object_unref(output);
     }
 
+    profiler = gjs_profiler_new(js_context);
+
+    /* Allow SIGUSR2 (with sigaction param) to enable/disable */
+    gjs_profiler_setup_signals();
+
+    if (profile_output_path) {
+        gjs_profiler_set_filename(profiler, profile_output_path);
+        gjs_profiler_start(profiler);
+    }
+
     /* prepare command line arguments */
     if (!gjs_context_define_string_array(js_context, "ARGV",
                                          script_argc, (const char **) script_argv,
@@ -300,6 +313,7 @@ main(int argc, char **argv)
     g_strfreev(coverage_prefixes);
     if (coverage)
         g_object_unref(coverage);
+    gjs_profiler_free(profiler);
     g_object_unref(js_context);
     g_free(script);
     exit(code);
diff --git a/gjs/gjs.h b/gjs/gjs.h
index dbee71c..b044d0f 100644
--- a/gjs/gjs.h
+++ b/gjs/gjs.h
@@ -27,6 +27,7 @@
 #include <gjs/macros.h>
 #include <gjs/context.h>
 #include <gjs/coverage.h>
+#include <gjs/profiler.h>
 #include <util/error.h>
 
 #endif /* __GJS_GJS_H__ */
diff --git a/gjs/profiler.cpp b/gjs/profiler.cpp
new file mode 100644
index 0000000..cfa6ffb
--- /dev/null
+++ b/gjs/profiler.cpp
@@ -0,0 +1,548 @@
+/* profiler.cpp
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <algorithm>
+#include <alloca.h>
+#include <errno.h>
+#include <memory>
+#include <signal.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib-unix.h>
+
+#include "jsapi-wrapper.h"
+#include <js/ProfilingStack.h>
+
+#include "jsapi-util.h"
+#include "profiler.h"
+#include "util/sp-capture-writer.h"
+
+/*
+ * This is mostly non-exciting code wrapping the builtin Profiler in
+ * mozjs. In particular, the profiler consumer is required to "bring your
+ * own sampler".  We do the very non-surprising thing of using POSIX
+ * timers to deliver SIGPROF to the thread containing the JSContext.
+ *
+ * However, we do use a Linux'ism that allows us to deliver the signal
+ * to only a single thread. Doing this in a generic fashion would
+ * require thread-registration so that we can mask SIGPROF from all
+ * threads execpt the JS thread. The gecko engine uses tgkill() to do
+ * this with a secondary thread instead of using POSIX timers. We could
+ * do this too, but it would still be Linux-only.
+ *
+ * Another option might be to use pthread_kill() and a secondary thread
+ * to perform the notification.
+ *
+ * From within the signal handler, we process the current stack as
+ * delivered to us from the JSContext. Any pointer data that comes from
+ * the runtime has to be copied, so we keep our own dedup'd string
+ * pointers for JavaScript file/line information. Non-JS instruction
+ * pointers are just fine, as they can be resolved by parsing the ELF for
+ * the file mapped on disk containing that address.
+ *
+ * As much of this code has to run from signal handlers, it is very
+ * important that we don't use anything that can malloc() or lock, or
+ * deadlocks are very likely. Most of GjsProfilerCapture is signal-safe.
+ */
+
+#define SAMPLES_PER_SEC G_GUINT64_CONSTANT(1000)
+#define NSEC_PER_SEC G_GUINT64_CONSTANT(1000000000)
+
+G_DEFINE_POINTER_TYPE(GjsProfiler, gjs_profiler)
+
+struct _GjsProfiler {
+    /* The stack for the JSContext profiler to use for current stack
+     * information while executing. We will look into this during our
+     * SIGPROF handler.
+     */
+    js::ProfileEntry stack[1024];
+
+    /* The context being profiled */
+    JSContext *cx;
+
+    /* Buffers and writes our sampled stacks */
+    SpCaptureWriter *capture;
+
+    /* The filename to write to */
+    char *filename;
+
+    /* Our POSIX timer to wakeup SIGPROF */
+    timer_t timer;
+
+    /* The depth of @stack. This value may be larger than the
+     * number of elements in stack, and so you MUST ensure you
+     * don't walk past the end of stack[] when iterating.
+     */
+    uint32_t stack_depth;
+
+    /* Cached copy of our pid */
+    GPid pid;
+
+    /* If we are currently sampling */
+    unsigned running : 1;
+};
+
+static GjsProfiler *current_profiler;
+
+/*
+ * gjs_profiler_extract_maps:
+ *
+ * This function will write the mapped section information to the
+ * capture file so that the callgraph builder can generate symbols
+ * from the stack addresses provided.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and the profile
+ *   should abort.
+ */
+static bool
+gjs_profiler_extract_maps(GjsProfiler *self)
+{
+    using AutoStrv = std::unique_ptr<char *, decltype(&g_strfreev)>;
+
+    int64_t now = g_get_monotonic_time() * 1000L;
+
+    g_assert(((void) "Profiler must be set up before extracting maps", self));
+
+    GjsAutoChar path = g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid));
+
+    char *content_tmp;
+    size_t len;
+    if (!g_file_get_contents(path, &content_tmp, &len, nullptr))
+      return false;
+    GjsAutoChar content = content_tmp;
+
+    AutoStrv lines(g_strsplit(content, "\n", 0), g_strfreev);
+
+    for (size_t ix = 0; lines.get()[ix]; ix++) {
+        char file[256];
+        unsigned long start;
+        unsigned long end;
+        unsigned long offset;
+        unsigned long inode;
+
+        file[sizeof file - 1] = '\0';
+
+        int r = sscanf(lines.get()[ix], "%lx-%lx %*15s %lx %*x:%*x %lu %255s",
+                       &start, &end, &offset, &inode, file);
+        if (r != 5)
+            continue;
+
+        if (strcmp("[vdso]", file) == 0) {
+            offset = 0;
+            inode = 0;
+        }
+
+        if (!sp_capture_writer_add_map(self->capture, now, -1, self->pid, start,
+                                       end, offset, inode, file))
+            return false;
+    }
+
+    return true;
+}
+
+/**
+ * gjs_profiler_new:
+ * @context: The #GjsContext to profile
+ *
+ * This creates a new profiler for the #JSContext. It is important that
+ * this instance is freed with gjs_profiler_free() before the context is
+ * destroyed.
+ *
+ * Call gjs_profiler_start() to enable the profiler, and gjs_profiler_stop()
+ * when you have finished.
+ *
+ * The profiler works by enabling the JS profiler in spidermonkey so that
+ * sample information is available. A POSIX timer is used to signal SIGPROF
+ * to the process on a regular interval to collect the most recent profile
+ * sample and stash it away. It is a programming error to mask SIGPROF from
+ * the thread controlling the JS context.
+ *
+ * Returns: (transfer full): A newly allocated #GjsProfiler
+ */
+GjsProfiler *
+gjs_profiler_new(GjsContext *context)
+{
+    g_return_val_if_fail(context, nullptr);
+
+    g_assert(((void)"You can ony create one profiler at a time.",
+              !current_profiler));
+
+    auto cx = static_cast<JSContext *>(gjs_context_get_native_context(context));
+
+    GjsProfiler *self = g_new0(GjsProfiler, 1);
+    self->cx = cx;
+
+    self->pid = getpid();
+
+    current_profiler = self;
+
+    return self;
+}
+
+/**
+ * gjs_profiler_free:
+ * @self: A #GjsProfiler
+ *
+ * Frees a profiler instance and cleans up any allocated data.
+ *
+ * If the profiler is running, it will be stopped. This may result in blocking
+ * to write the contents of the buffer to the underlying file-descriptor.
+ */
+void
+gjs_profiler_free(GjsProfiler *self)
+{
+    if (!self)
+        return;
+
+    if (self->running)
+        gjs_profiler_stop(self);
+
+    current_profiler = nullptr;
+
+    g_clear_pointer(&self->filename, g_free);
+    g_clear_pointer(&self->capture, sp_capture_writer_unref);
+    g_free(self);
+}
+
+/**
+ * gjs_profiler_is_running:
+ * @self: A #GjsProfiler
+ *
+ * Checks if the profiler is currently running. This means that the JS
+ * profiler is enabled and POSIX signal timers are registered.
+ *
+ * Returns: %TRUE if the profiler is active.
+ */
+bool
+gjs_profiler_is_running(GjsProfiler *self)
+{
+    g_return_val_if_fail(self, false);
+
+    return self->running;
+}
+
+/* Run from a signal handler */
+static inline unsigned
+gjs_profiler_get_stack_size(GjsProfiler *self)
+{
+    g_assert(((void) "Profiler must be set up before getting stack size", self));
+
+    /*
+     * Note that stack_depth could be larger than the number of
+     * items we have in our stack space. We must protect ourselves
+     * against overflowing by discarding anything after that depth
+     * of the stack.
+     */
+    return std::min(self->stack_depth, uint32_t(G_N_ELEMENTS(self->stack)));
+}
+
+static void
+gjs_profiler_sigprof(int        signum,
+                     siginfo_t *info,
+                     void      *unused)
+{
+    GjsProfiler *self = current_profiler;
+
+    g_assert(((void) "SIGPROF handler called with invalid signal info", info));
+    g_assert(((void) "SIGPROF handler called with other signal",
+              info->si_signo == SIGPROF));
+
+    /*
+     * NOTE:
+     *
+     * This is the SIGPROF signal handler. Everything done in this thread
+     * needs to be things that are safe to do in a signal handler. One thing
+     * that is not okay to do, is *malloc*.
+     */
+
+    if (!self || info->si_code != SI_TIMER)
+        return;
+
+    size_t depth = gjs_profiler_get_stack_size(self);
+    if (depth == 0)
+        return;
+
+    static_assert(G_N_ELEMENTS(self->stack) < G_MAXUSHORT,
+                  "Number of elements in profiler stack should be expressible"
+                  "in an unsigned short");
+
+    int64_t now = g_get_monotonic_time() * 1000L;
+    SpCaptureAddress *addrs = static_cast<SpCaptureAddress *>(alloca(sizeof *addrs * depth));
+
+    for (size_t ix = 0; ix < depth; ix++) {
+        js::ProfileEntry& entry = self->stack[ix];
+        const char *label = entry.label();
+        size_t flipped = depth - 1 - ix;
+
+        /*
+         * SPSProfiler will put "js::RunScript" on the stack, but it has
+         * a stack address of "this", which is not terribly useful since
+         * everything will show up as [stack] when building callgraphs.
+         */
+        if (label)
+            addrs[flipped] = sp_capture_writer_add_jitmap(self->capture, label);
+        else
+            addrs[flipped] = SpCaptureAddress(entry.stackAddress());
+    }
+
+    if (!sp_capture_writer_add_sample(self->capture, now, -1, self->pid, addrs, depth))
+        gjs_profiler_stop(self);
+}
+
+/**
+ * gjs_profiler_start:
+ * @self: A #GjsProfiler
+ *
+ * As expected, this starts the GjsProfiler.
+ *
+ * This will enable the underlying JS profiler and register a POSIX timer to
+ * deliver SIGPROF on the configured sampling frequency.
+ *
+ * To reduce sampling overhead, #GjsProfiler stashes information about the
+ * profile to be calculated once the profiler has been disabled. Calling
+ * gjs_profiler_stop() will result in that delayed work to be completed.
+ *
+ * You should call gjs_profiler_stop() when the profiler is no longer needed.
+ */
+void
+gjs_profiler_start(GjsProfiler *self)
+{
+    g_return_if_fail(self);
+    g_return_if_fail(!self->capture);
+
+    struct sigaction sa = {{ 0 }};
+    struct sigevent sev = {{ 0 }};
+    struct itimerspec its = {{ 0 }};
+    struct itimerspec old_its;
+
+    if (self->running)
+        return;
+
+    GjsAutoChar path = g_strdup(self->filename);
+    if (!path) {
+        GjsAutoChar filename = g_strdup_printf("gjs-profile-%jd", intmax_t(self->pid));
+        path = g_build_filename(g_get_tmp_dir(), filename.get(), nullptr);
+    }
+
+    self->capture = sp_capture_writer_new(path, 0);
+
+    if (!self->capture) {
+        g_warning("Failed to open profile capture");
+        return;
+    }
+
+    if (!gjs_profiler_extract_maps(self)) {
+        g_warning("Failed to extract proc maps");
+        g_clear_pointer(&self->capture, sp_capture_writer_unref);
+        return;
+    }
+
+    self->stack_depth = 0;
+
+    /* Setup our signal handler for SIGPROF delivery */
+    sa.sa_flags = SA_RESTART | SA_SIGINFO;
+    sa.sa_sigaction = gjs_profiler_sigprof;
+    sigemptyset(&sa.sa_mask);
+
+    if (sigaction(SIGPROF, &sa, nullptr) == -1) {
+        g_warning("Failed to register sigaction handler: %s", g_strerror(errno));
+        g_clear_pointer(&self->capture, sp_capture_writer_unref);
+        return;
+    }
+
+    /*
+     * Create our SIGPROF timer
+     *
+     * We want to receive a SIGPROF signal on the JS thread using our
+     * configured sampling frequency. Instead of allowing any thread to be
+     * notified, we set the _tid value to ensure that only our thread gets
+     * delivery of the signal. This feature is generally just for
+     * threading implementations, but it works for us as well and ensures
+     * that the thread is blocked while we capture the stack.
+     */
+    sev.sigev_notify = SIGEV_THREAD_ID;
+    sev.sigev_signo = SIGPROF;
+    sev._sigev_un._tid = syscall(__NR_gettid);
+
+    if (timer_create(CLOCK_MONOTONIC, &sev, &self->timer) == -1) {
+        g_warning("Failed to create profiler timer: %s", g_strerror(errno));
+        g_clear_pointer(&self->capture, sp_capture_writer_unref);
+        return;
+    }
+
+    /* Calculate sampling interval */
+    its.it_interval.tv_sec = 0;
+    its.it_interval.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC;
+    its.it_value.tv_sec = 0;
+    its.it_value.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC;
+
+    /* Now start this timer */
+    if (timer_settime(self->timer, 0, &its, &old_its) != 0) {
+        g_warning("Failed to enable profiler timer: %s", g_strerror(errno));
+        timer_delete(self->timer);
+        g_clear_pointer(&self->capture, sp_capture_writer_unref);
+        return;
+    }
+
+    self->running = true;
+
+    /* Notify the JS runtime of where to put stack info */
+    js::SetContextProfilingStack(self->cx, self->stack, &self->stack_depth,
+                                 G_N_ELEMENTS(self->stack));
+
+    /* Start recording stack info */
+    js::EnableContextProfilingStack(self->cx, true);
+
+    g_message("Profiler started");
+}
+
+/**
+ * gjs_profiler_stop:
+ * @self: A #GjsProfiler
+ *
+ * Stops a currently running #GjsProfiler. If the profiler is not running,
+ * this function will do nothing.
+ *
+ * Some work may be delayed until the end of the capture. Such delayed work
+ * includes flushing the resulting samples and file location information to
+ * disk.
+ *
+ * This may block while writing to disk. Generally, the writes are delivered
+ * to a tmpfs device, and are therefore negligible.
+ */
+void
+gjs_profiler_stop(GjsProfiler *self)
+{
+    /* Note: can be called from a signal handler */
+
+    struct itimerspec its = {{ 0 }};
+
+    g_assert(self);
+
+    if (!self->running)
+        return;
+
+    timer_settime(self->timer, 0, &its, nullptr);
+    timer_delete(self->timer);
+
+    js::EnableContextProfilingStack(self->cx, false);
+    js::SetContextProfilingStack(self->cx, nullptr, nullptr, 0);
+
+    sp_capture_writer_flush(self->capture);
+
+    g_clear_pointer(&self->capture, sp_capture_writer_unref);
+
+    self->stack_depth = 0;
+    self->running = false;
+
+    g_message("Profiler stopped");
+}
+
+static gboolean
+gjs_profiler_sigusr2(void *unused)
+{
+    if (current_profiler) {
+        if (gjs_profiler_is_running(current_profiler))
+            gjs_profiler_stop(current_profiler);
+        else
+            gjs_profiler_start(current_profiler);
+    }
+
+    return G_SOURCE_CONTINUE;
+}
+
+/**
+ * gjs_profiler_setup_signals:
+ *
+ * If you want to simply allow profiling of your process with minimal
+ * fuss, simply call gjs_profiler_setup_signals(). This will allow
+ * enabling and disabling the profiler with SIGUSR2. You must call
+ * this from main() immediately when your program starts and must not
+ * block SIGUSR2 from your signal mask.
+ *
+ * If this is not sufficient, use gjs_profiler_chain_signal() from your
+ * own signal handler to pass the signal to a GjsProfiler.
+ */
+void
+gjs_profiler_setup_signals(void)
+{
+    static bool initialized = false;
+
+    if (!initialized) {
+        initialized = true;
+        g_unix_signal_add(SIGUSR2, gjs_profiler_sigusr2, nullptr);
+    }
+}
+
+/**
+ * gjs_profiler_chain_signal:
+ * @info: #siginfo_t passed in to signal handler
+ *
+ * Use this to pass a signal info caught by another signal handler to a
+ * GjsProfiler. This might be needed if you have your own complex signal
+ * handling system for which GjsProfiler cannot simply add a SIGUSR2 handler.
+ *
+ * This function should only be called from the JS thread.
+ *
+ * Returns: %TRUE if the signal was handled.
+ */
+bool
+gjs_profiler_chain_signal(siginfo_t *info)
+{
+    if (info) {
+        if (info->si_signo == SIGPROF) {
+            gjs_profiler_sigprof(SIGPROF, info, nullptr);
+            return true;
+        }
+
+        if (info->si_signo == SIGUSR2) {
+            gjs_profiler_sigusr2(nullptr);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * gjs_profiler_set_filename:
+ * @self: A #GjsProfiler
+ * @filename: string containing a filename
+ *
+ * Set the file to which profiling data is written when the @self is stopped.
+ * By default, this is $TMPDIR/gjs-profile-$PID.
+ */
+void
+gjs_profiler_set_filename(GjsProfiler *self,
+                          const char  *filename)
+{
+    g_return_if_fail(self);
+    g_return_if_fail(!self->running);
+
+    g_free(self->filename);
+    self->filename = g_strdup(filename);
+}
diff --git a/gjs/profiler.h b/gjs/profiler.h
new file mode 100644
index 0000000..f0fd971
--- /dev/null
+++ b/gjs/profiler.h
@@ -0,0 +1,70 @@
+/* profiler.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef GJS_PROFILER_H
+#define GJS_PROFILER_H
+
+#include <signal.h>
+#include <stdbool.h>
+
+#include <glib.h>
+
+#include <gjs/context.h>
+
+G_BEGIN_DECLS
+
+#define GJS_TYPE_PROFILER (gjs_profiler_get_type())
+
+typedef struct _GjsProfiler GjsProfiler;
+
+GJS_EXPORT
+GType gjs_profiler_get_type(void);
+
+GJS_EXPORT
+GjsProfiler *gjs_profiler_new(GjsContext *context);
+
+GJS_EXPORT
+void gjs_profiler_free(GjsProfiler *self);
+
+GJS_EXPORT
+void gjs_profiler_set_filename(GjsProfiler *self,
+                               const char  *filename);
+
+GJS_EXPORT
+void gjs_profiler_start(GjsProfiler *self);
+
+GJS_EXPORT
+void gjs_profiler_stop(GjsProfiler *self);
+
+GJS_EXPORT
+bool gjs_profiler_is_running(GjsProfiler *self);
+
+GJS_EXPORT
+void gjs_profiler_setup_signals(void);
+
+GJS_EXPORT
+bool gjs_profiler_chain_signal(siginfo_t *info);
+
+G_END_DECLS
+
+#endif /* GJS_PROFILER_H */
diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp
index 03f34d3..2c9477e 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -29,7 +29,7 @@
 #include <glib-object.h>
 #include <util/glib.h>
 
-#include <gjs/context.h>
+#include <gjs/gjs.h>
 #include "gjs/jsapi-util.h"
 #include "gjs/jsapi-wrapper.h"
 #include "gjs-test-utils.h"
@@ -376,6 +376,30 @@ gjstest_test_strip_shebang_return_null_for_just_shebang(void)
     g_assert(line_number == -1);
 }
 
+static void
+gjstest_test_profiler_start_stop(void)
+{
+    GjsAutoUnref<GjsContext> context = gjs_context_new();
+    GjsProfiler *profiler = gjs_profiler_new(context);
+
+    gjs_profiler_start(profiler);
+
+    for (size_t ix = 0; ix < 100000; ix++) {
+        GError *error = nullptr;
+        int estatus;
+
+#define TESTJS "[1,5,7,1,2,3,67,8].sort()"
+
+        if (!gjs_context_eval(context, TESTJS, -1, "<input>", &estatus, &error))
+            g_printerr("ERROR: %s", error->message);
+
+#undef TESTJS
+    }
+
+    gjs_profiler_stop(profiler);
+    gjs_profiler_free(profiler);
+}
+
 int
 main(int    argc,
      char **argv)
@@ -394,6 +418,7 @@ main(int    argc,
     g_test_add_func("/gjs/jsutil/strip_shebang/no_shebang", 
gjstest_test_strip_shebang_no_advance_for_no_shebang);
     g_test_add_func("/gjs/jsutil/strip_shebang/have_shebang", 
gjstest_test_strip_shebang_advance_for_shebang);
     g_test_add_func("/gjs/jsutil/strip_shebang/only_shebang", 
gjstest_test_strip_shebang_return_null_for_just_shebang);
+    g_test_add_func("/gjs/profiler/start_stop", gjstest_test_profiler_start_stop);
     g_test_add_func("/util/glib/strv/concat/null", gjstest_test_func_util_glib_strv_concat_null);
     g_test_add_func("/util/glib/strv/concat/pointers", gjstest_test_func_util_glib_strv_concat_pointers);
 
diff --git a/util/sp-capture-types.h b/util/sp-capture-types.h
new file mode 100644
index 0000000..7fd52c6
--- /dev/null
+++ b/util/sp-capture-types.h
@@ -0,0 +1,127 @@
+/* sp-capture-types.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* The original source of this file is:
+ * https://git.gnome.org/browse/sysprof/tree/lib/capture/sp-capture-types.c
+ * It has been modified to remove unneeded functionality.
+ */
+
+#ifndef SP_CAPTURE_FORMAT_H
+#define SP_CAPTURE_FORMAT_H
+
+#include <glib.h>
+#include <glib-object.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 (g_get_monotonic_time() * 1000L)
+
+typedef struct _SpCaptureWriter SpCaptureWriter;
+
+typedef guint64 SpCaptureAddress;
+
+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,
+} 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;
+  guint16          n_addrs;
+  guint64          padding : 48;
+  SpCaptureAddress addrs[0];
+} SpCaptureSample;
+
+#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 (SpCaptureSample) == 32);
+
+G_END_DECLS
+
+#endif /* SP_CAPTURE_FORMAT_H */
diff --git a/util/sp-capture-writer.c b/util/sp-capture-writer.c
new file mode 100644
index 0000000..3299db8
--- /dev/null
+++ b/util/sp-capture-writer.c
@@ -0,0 +1,628 @@
+/* sp-capture-writer.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* The original source of this file is:
+ * https://git.gnome.org/browse/sysprof/tree/lib/capture/sp-capture-writer.c
+ * It has been modified to remove unneeded functionality.
+ */
+
+#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 "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;
+};
+
+G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer,
+                     sp_capture_writer_ref, sp_capture_writer_unref)
+
+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_malloc (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;
+}
+
+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_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;
+}
+
+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));
+}
diff --git a/util/sp-capture-writer.h b/util/sp-capture-writer.h
new file mode 100644
index 0000000..c545013
--- /dev/null
+++ b/util/sp-capture-writer.h
@@ -0,0 +1,81 @@
+/* sp-capture-writer.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* The original source of this file is:
+ * https://git.gnome.org/browse/sysprof/tree/lib/capture/sp-capture-writer.h
+ * It has been modified to remove unneeded functionality.
+ */
+
+#ifndef SP_CAPTURE_WRITER_H
+#define SP_CAPTURE_WRITER_H
+
+#include "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);
+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);
+guint64             sp_capture_writer_add_jitmap      (SpCaptureWriter         *self,
+                                                       const gchar             *name);
+gboolean            sp_capture_writer_add_sample      (SpCaptureWriter         *self,
+                                                       gint64                   time,
+                                                       gint                     cpu,
+                                                       GPid                     pid,
+                                                       const SpCaptureAddress  *addrs,
+                                                       guint                    n_addrs);
+gboolean            sp_capture_writer_flush           (SpCaptureWriter         *self);
+
+#define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
+GType sp_capture_writer_get_type (void);
+
+G_END_DECLS
+
+#endif /* SP_CAPTURE_WRITER_H */


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