[gjs/wip/chergert/profiler] profiler: add SPSProfiler implementation
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/wip/chergert/profiler] profiler: add SPSProfiler implementation
- Date: Mon, 31 Jul 2017 15:04:04 +0000 (UTC)
commit 149e9a8279d5a9a3d6c6b9444308d8f7e7d7ec9b
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.
gjs-srcs.mk | 7 +
gjs/console.cpp | 14 +
gjs/gjs.h | 1 +
gjs/profiler.cpp | 552 +++++++++++++++++++++++++++
gjs/profiler.h | 44 +++
test/gjs-tests.cpp | 32 ++
util/sp-capture-reader.c | 686 +++++++++++++++++++++++++++++++++
util/sp-capture-reader.h | 65 ++++
util/sp-capture-types.h | 155 ++++++++
util/sp-capture-writer.c | 954 ++++++++++++++++++++++++++++++++++++++++++++++
util/sp-capture-writer.h | 108 ++++++
11 files changed, 2618 insertions(+), 0 deletions(-)
---
diff --git a/gjs-srcs.mk b/gjs-srcs.mk
index 5d41951..25fa7c2 100644
--- a/gjs-srcs.mk
+++ b/gjs-srcs.mk
@@ -79,6 +79,8 @@ gjs_srcs = \
gjs/module.cpp \
gjs/native.cpp \
gjs/native.h \
+ gjs/profiler.cpp \
+ gjs/profiler.h \
gjs/stack.cpp \
modules/modules.cpp \
modules/modules.h \
@@ -91,6 +93,11 @@ gjs_srcs = \
util/log.h \
util/misc.cpp \
util/misc.h \
+ util/sp-capture-reader.c \
+ util/sp-capture-reader.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 fa6c579..0192f88 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 }
};
@@ -154,6 +156,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;
@@ -267,6 +270,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,
@@ -297,6 +310,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..bff5dad
--- /dev/null
+++ b/gjs/profiler.cpp
@@ -0,0 +1,552 @@
+/* profiler.cpp
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glib-unix.h>
+#include <signal.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "jsapi-wrapper.h"
+#include <js/ProfilingStack.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 JSRuntime. 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 JSRuntime 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 *context;
+
+ /* Buffers and writes our sampled stacks */
+ SpCaptureWriter *capture;
+
+ /* The filename to write to */
+ gchar *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 */
+ guint running : 1;
+
+ /* If we should shutdown */
+ guint shutdown : 1;
+};
+
+static GjsProfiler *current_profiler;
+
+/*
+ * sample_capture_write_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 gboolean
+gjs_profiler_extract_maps (GjsProfiler *self)
+{
+ g_auto(GStrv) lines = NULL;
+ g_autofree gchar *path = NULL;
+ g_autofree gchar *content = NULL;
+ gint64 now = g_get_monotonic_time () * 1000L;
+ gsize len;
+ guint i;
+
+ g_assert (self != NULL);
+
+ path = g_strdup_printf ("/proc/%u/maps", (guint)getpid ());
+ if (!g_file_get_contents (path, &content, &len, NULL))
+ return FALSE;
+
+ lines = g_strsplit (content, "\n", 0);
+
+ for (i = 0; lines [i] != NULL; i++)
+ {
+ gchar file[256];
+ gulong start;
+ gulong end;
+ gulong offset;
+ gulong inode;
+ gint r;
+
+ file [sizeof file - 1] = '\0';
+
+ r = sscanf (lines [i],
+ "%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 #JSRuntime. It is important that
+ * this structure is freed with gjs_profiler_free() before runtime 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)
+{
+ JSContext *js_context;
+ GjsProfiler *self = NULL;
+
+ g_return_val_if_fail (context != NULL, NULL);
+
+ js_context = (JSContext *)gjs_context_get_native_context (context);
+
+ self = g_new0 (GjsProfiler, 1);
+ self->context = js_context;
+
+ self->pid = getpid ();
+
+ current_profiler = self;
+
+ return self;
+}
+
+/**
+ * gjs_profiler_free:
+ * @self: A #GjsProfile
+ *
+ * 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 != NULL)
+ {
+ if (self->running)
+ gjs_profiler_stop (self);
+ g_clear_pointer (&self->capture, sp_capture_writer_unref);
+ g_free (self);
+ }
+}
+
+/**
+ * gjs_profile_is_running:
+ *
+ * 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.
+ */
+gboolean
+gjs_profiler_is_running (GjsProfiler *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ return self->running;
+}
+
+static inline guint
+gjs_profiler_get_stack_size (GjsProfiler *self)
+{
+ g_assert (self != NULL);
+
+ /*
+ * 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 MIN (self->stack_depth, G_N_ELEMENTS (self->stack));
+}
+
+static void
+gjs_profiler_sigprof (int signum,
+ siginfo_t *info,
+ void *context)
+{
+ GjsProfiler *self = current_profiler;
+ SpCaptureAddress *addrs;
+ gint64 now;
+ guint depth;
+ guint i;
+
+ g_assert (info != NULL);
+ g_assert (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 == NULL) || (info->si_code != SI_TIMER))
+ return;
+
+ if (0 == (depth = gjs_profiler_get_stack_size (self)))
+ return;
+
+ G_STATIC_ASSERT (G_N_ELEMENTS (self->stack) < G_MAXUSHORT);
+
+ now = g_get_monotonic_time () * 1000L;
+ addrs = (SpCaptureAddress *)alloca (sizeof *addrs * depth);
+
+ for (i = 0; i < depth; i++)
+ {
+ js::ProfileEntry *entry = &self->stack[i];
+ const gchar *label = entry->label();
+ guint flipped = depth - 1 - i;
+
+ /*
+ * 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 != NULL)
+ 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_profile_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_autofree gchar *filename = NULL;
+ g_autofree gchar *path = NULL;
+ struct sigaction sa = { 0 };
+ struct sigevent sev = { 0 };
+ struct itimerspec its = { 0 };
+ struct itimerspec old_its;
+
+ g_assert (self != NULL);
+ g_assert (self->capture == NULL);
+
+ if (self->running)
+ return;
+
+ path = g_strdup (self->filename);
+
+ if (path == NULL)
+ {
+ filename = g_strdup_printf ("gjs-profile-%u", getpid ());
+ path = g_build_filename (g_get_tmp_dir (), filename, NULL);
+ }
+
+ self->capture = sp_capture_writer_new (path, 0);
+
+ if (self->capture == NULL)
+ {
+ g_warning ("Failed to open profile capture");
+ return;
+ }
+
+ if (!gjs_profiler_extract_maps (self))
+ {
+ g_warning ("Failed to extract proc maps");
+ 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, NULL) == -1)
+ {
+ g_warning ("Failed to register sigaction handler: %s",
+ g_strerror (errno));
+ 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 (0 != timer_settime (self->timer, 0, &its, &old_its))
+ {
+ 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->context,
+ self->stack,
+ &self->stack_depth,
+ G_N_ELEMENTS (self->stack));
+
+ /* Start recording stack info */
+ js::EnableContextProfilingStack (self->context, 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)
+{
+ struct itimerspec its = { 0 };
+
+ g_assert (self != NULL);
+
+ if (!self->running)
+ return;
+
+ if (self == current_profiler)
+ current_profiler = NULL;
+
+ timer_settime (self->timer, 0, &its, NULL);
+ timer_delete (self->timer);
+
+ js::EnableContextProfilingStack (self->context, FALSE);
+
+ 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 (gpointer user_data)
+{
+ if (current_profiler != NULL)
+ {
+ 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 gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+ g_unix_signal_add (SIGUSR2, gjs_profiler_sigusr2, NULL);
+ }
+}
+
+/**
+ * gjs_profiler_chain_signal:
+ *
+ * Use this to pass a signal handler 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.
+ */
+gboolean
+gjs_profiler_chain_signal (siginfo_t *info)
+{
+ if (info != NULL)
+ {
+ if (info->si_signo == SIGPROF)
+ {
+ gjs_profiler_sigprof (SIGPROF, info, NULL);
+ return TRUE;
+ }
+
+ if (info->si_signo == SIGUSR2)
+ {
+ gjs_profiler_sigusr2 (NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void
+gjs_profiler_set_filename (GjsProfiler *self,
+ const gchar *filename)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (self->running == FALSE);
+
+ 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..4b11914
--- /dev/null
+++ b/gjs/profiler.h
@@ -0,0 +1,44 @@
+/* profiler.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GJS_PROFILER_H
+#define GJS_PROFILER_H
+
+#include <gjs/context.h>
+#include <signal.h>
+
+G_BEGIN_DECLS
+
+#define GJS_TYPE_PROFILER (gjs_profiler_get_type())
+
+typedef struct _GjsProfiler GjsProfiler;
+
+GType gjs_profiler_get_type (void);
+GjsProfiler *gjs_profiler_new (GjsContext *context);
+void gjs_profiler_free (GjsProfiler *self);
+void gjs_profiler_set_filename (GjsProfiler *self,
+ const gchar *filename);
+void gjs_profiler_start (GjsProfiler *self);
+void gjs_profiler_stop (GjsProfiler *self);
+gboolean gjs_profiler_is_running (GjsProfiler *self);
+void gjs_profiler_setup_signals (void);
+gboolean 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 fefc2ef..2ab203d 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -375,6 +375,37 @@ gjstest_test_strip_shebang_return_null_for_just_shebang(void)
g_assert(line_number == -1);
}
+static void
+gjstest_test_profiler_start_stop(void)
+{
+ GjsContext *context;
+ GjsProfiler *profiler;
+ guint i;
+
+ context = gjs_context_new ();
+ profiler = gjs_profiler_new (context);
+
+ gjs_profiler_start(profiler);
+
+ for (i = 0; i < 100000; i++)
+ {
+ GError *error = NULL;
+ 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);
+
+ g_object_unref (context);
+}
+
int
main(int argc,
char **argv)
@@ -393,6 +424,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-reader.c b/util/sp-capture-reader.c
new file mode 100644
index 0000000..f5d4544
--- /dev/null
+++ b/util/sp-capture-reader.c
@@ -0,0 +1,686 @@
+/* sp-capture-reader.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sp-capture-reader.h"
+#include "sp-capture-writer.h"
+
+struct _SpCaptureReader
+{
+ volatile gint ref_count;
+ gchar *filename;
+ guint8 *buf;
+ gsize bufsz;
+ gsize len;
+ gsize pos;
+ gsize fd_off;
+ int fd;
+ gint endian;
+ SpCaptureFileHeader header;
+};
+
+#ifndef SP_DISABLE_GOBJECT
+G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader,
+ sp_capture_reader_ref, sp_capture_reader_unref)
+#endif
+
+static gboolean
+sp_capture_reader_read_file_header (SpCaptureReader *self,
+ SpCaptureFileHeader *header,
+ GError **error)
+{
+ g_assert (self != NULL);
+ g_assert (header != NULL);
+
+ if (sizeof *header != pread (self->fd, header, sizeof *header, 0L))
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return FALSE;
+ }
+
+ if (header->magic != SP_CAPTURE_MAGIC)
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ G_FILE_ERROR_FAILED,
+ "Capture file magic does not match");
+ return FALSE;
+ }
+
+ header->capture_time[sizeof header->capture_time - 1] = '\0';
+
+ return TRUE;
+}
+
+static void
+sp_capture_reader_finalize (SpCaptureReader *self)
+{
+ if (self != NULL)
+ {
+ close (self->fd);
+ g_free (self->buf);
+ g_free (self->filename);
+ g_free (self);
+ }
+}
+
+const gchar *
+sp_capture_reader_get_time (SpCaptureReader *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return self->header.capture_time;
+}
+
+const gchar *
+sp_capture_reader_get_filename (SpCaptureReader *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ return self->filename;
+}
+
+SpCaptureReader *
+sp_capture_reader_new_from_fd (int fd,
+ GError **error)
+{
+ SpCaptureReader *self;
+
+ g_assert (fd > -1);
+
+ self = g_new0 (SpCaptureReader, 1);
+ self->ref_count = 1;
+ self->bufsz = G_MAXUSHORT * 2;
+ self->buf = g_malloc (self->bufsz);
+ self->len = 0;
+ self->pos = 0;
+ self->fd = fd;
+ self->fd_off = sizeof (SpCaptureFileHeader);
+
+ if (!sp_capture_reader_read_file_header (self, &self->header, error))
+ {
+ sp_capture_reader_finalize (self);
+ return NULL;
+ }
+
+ if (self->header.little_endian)
+ self->endian = G_LITTLE_ENDIAN;
+ else
+ self->endian = G_BIG_ENDIAN;
+
+ return self;
+}
+
+SpCaptureReader *
+sp_capture_reader_new (const gchar *filename,
+ GError **error)
+{
+ SpCaptureReader *self;
+ int fd;
+
+ g_assert (filename != NULL);
+
+ if (-1 == (fd = open (filename, O_RDONLY, 0)))
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return NULL;
+ }
+
+ if (NULL == (self = sp_capture_reader_new_from_fd (fd, error)))
+ {
+ close (fd);
+ return NULL;
+ }
+
+ self->filename = g_strdup (filename);
+
+ return self;
+}
+
+static inline void
+sp_capture_reader_bswap_frame (SpCaptureReader *self,
+ SpCaptureFrame *frame)
+{
+ g_assert (self != NULL);
+ g_assert (frame!= NULL);
+
+ if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
+ {
+ frame->len = GUINT16_SWAP_LE_BE (frame->len);
+ frame->cpu = GUINT16_SWAP_LE_BE (frame->len);
+ frame->pid = GUINT32_SWAP_LE_BE (frame->len);
+ frame->time = GUINT64_SWAP_LE_BE (frame->len);
+ }
+}
+
+static inline void
+sp_capture_reader_bswap_map (SpCaptureReader *self,
+ SpCaptureMap *map)
+{
+ g_assert (self != NULL);
+ g_assert (map != NULL);
+
+ if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
+ {
+ map->start = GUINT64_SWAP_LE_BE (map->start);
+ map->end = GUINT64_SWAP_LE_BE (map->end);
+ map->offset = GUINT64_SWAP_LE_BE (map->offset);
+ map->inode = GUINT64_SWAP_LE_BE (map->inode);
+ }
+}
+
+static inline void
+sp_capture_reader_bswap_jitmap (SpCaptureReader *self,
+ SpCaptureJitmap *jitmap)
+{
+ g_assert (self != NULL);
+ g_assert (jitmap != NULL);
+
+ if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
+ jitmap->n_jitmaps = GUINT64_SWAP_LE_BE (jitmap->n_jitmaps);
+}
+
+static gboolean
+sp_capture_reader_ensure_space_for (SpCaptureReader *self,
+ gsize len)
+{
+ g_assert (self != NULL);
+ g_assert (len > 0);
+
+ if ((self->len - self->pos) < len)
+ {
+ gssize r;
+
+ g_assert (self->len >= self->pos);
+
+ memmove (self->buf, &self->buf[self->pos], self->len - self->pos);
+ self->len -= self->pos;
+ self->pos = 0;
+
+ while ((self->len - self->pos) <= len)
+ {
+ g_assert (self->pos + self->len < self->bufsz);
+
+ /* Read into our buffer after our current read position */
+ r = pread (self->fd,
+ &self->buf[self->len],
+ self->bufsz - self->len,
+ self->fd_off);
+
+ if (r <= 0)
+ break;
+
+ self->fd_off += r;
+ self->len += r;
+ }
+ }
+
+ return (self->len - self->pos) >= len;
+}
+
+gboolean
+sp_capture_reader_skip (SpCaptureReader *self)
+{
+ SpCaptureFrame *frame;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof (SpCaptureFrame)))
+ return FALSE;
+
+ frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
+ sp_capture_reader_bswap_frame (self, frame);
+
+ if (frame->len < sizeof (SpCaptureFrame))
+ return FALSE;
+
+ if (!sp_capture_reader_ensure_space_for (self, frame->len))
+ return FALSE;
+
+ frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
+
+ self->pos += frame->len;
+
+ if ((self->pos % SP_CAPTURE_ALIGN) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+sp_capture_reader_peek_type (SpCaptureReader *self,
+ SpCaptureFrameType *type)
+{
+ SpCaptureFrame *frame;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+ g_assert (type != NULL);
+
+ *type = 0;
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof *frame))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
+
+ *type = frame->type;
+
+ return TRUE;
+}
+
+static const SpCaptureFrame *
+sp_capture_reader_read_basic (SpCaptureReader *self,
+ SpCaptureFrameType type,
+ gsize extra)
+{
+ SpCaptureFrame *frame;
+ gsize len = sizeof *frame + extra;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+
+ if (!sp_capture_reader_ensure_space_for (self, len))
+ return NULL;
+
+ frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
+
+ sp_capture_reader_bswap_frame (self, frame);
+
+ if (frame->len < len)
+ return NULL;
+
+ if (frame->type != type)
+ return NULL;
+
+ self->pos += frame->len;
+
+ return frame;
+}
+
+const SpCaptureTimestamp *
+sp_capture_reader_read_timestamp (SpCaptureReader *self)
+{
+ return (SpCaptureTimestamp *)
+ sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_TIMESTAMP, 0);
+}
+
+const SpCaptureExit *
+sp_capture_reader_read_exit (SpCaptureReader *self)
+{
+ return (SpCaptureExit *)
+ sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_EXIT, 0);
+}
+
+const SpCaptureFork *
+sp_capture_reader_read_fork (SpCaptureReader *self)
+{
+ SpCaptureFork *fk;
+
+ g_assert (self != NULL);
+
+ fk = (SpCaptureFork *)
+ sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_FORK, sizeof(guint32));
+
+ if (fk != NULL)
+ {
+ if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
+ fk->child_pid = GUINT32_SWAP_LE_BE (fk->child_pid);
+ }
+
+ return fk;
+}
+
+const SpCaptureMap *
+sp_capture_reader_read_map (SpCaptureReader *self)
+{
+ SpCaptureMap *map;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof *map))
+ return NULL;
+
+ map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
+
+ sp_capture_reader_bswap_frame (self, &map->frame);
+
+ if (map->frame.type != SP_CAPTURE_FRAME_MAP)
+ return NULL;
+
+ if (map->frame.len < (sizeof *map + 1))
+ return NULL;
+
+ if (!sp_capture_reader_ensure_space_for (self, map->frame.len))
+ return NULL;
+
+ map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
+
+ if (self->buf[self->pos + map->frame.len - 1] != '\0')
+ return NULL;
+
+ sp_capture_reader_bswap_map (self, map);
+
+ self->pos += map->frame.len;
+
+ if ((self->pos % SP_CAPTURE_ALIGN) != 0)
+ return NULL;
+
+ return map;
+}
+
+const SpCaptureProcess *
+sp_capture_reader_read_process (SpCaptureReader *self)
+{
+ SpCaptureProcess *process;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof *process))
+ return NULL;
+
+ process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
+
+ sp_capture_reader_bswap_frame (self, &process->frame);
+
+ if (process->frame.type != SP_CAPTURE_FRAME_PROCESS)
+ return NULL;
+
+ if (process->frame.len < (sizeof *process + 1))
+ return NULL;
+
+ if (!sp_capture_reader_ensure_space_for (self, process->frame.len))
+ return NULL;
+
+ process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
+
+ if (self->buf[self->pos + process->frame.len - 1] != '\0')
+ return NULL;
+
+ self->pos += process->frame.len;
+
+ if ((self->pos % SP_CAPTURE_ALIGN) != 0)
+ return NULL;
+
+ return process;
+}
+
+GHashTable *
+sp_capture_reader_read_jitmap (SpCaptureReader *self)
+{
+ g_autoptr(GHashTable) ret = NULL;
+ SpCaptureJitmap *jitmap;
+ guint8 *buf;
+ guint8 *endptr;
+ guint i;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof *jitmap))
+ return NULL;
+
+ jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
+
+ sp_capture_reader_bswap_frame (self, &jitmap->frame);
+
+ if (jitmap->frame.type != SP_CAPTURE_FRAME_JITMAP)
+ return NULL;
+
+ if (jitmap->frame.len < sizeof *jitmap)
+ return NULL;
+
+ if (!sp_capture_reader_ensure_space_for (self, jitmap->frame.len))
+ return NULL;
+
+ jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
+
+ ret = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+
+ buf = jitmap->data;
+ endptr = &self->buf[self->pos + jitmap->frame.len];
+
+ for (i = 0; i < jitmap->n_jitmaps; i++)
+ {
+ SpCaptureAddress addr;
+ const gchar *str;
+
+ if (buf + sizeof addr >= endptr)
+ return NULL;
+
+ memcpy (&addr, buf, sizeof addr);
+ buf += sizeof addr;
+
+ str = (gchar *)buf;
+
+ buf = memchr (buf, '\0', (endptr - buf));
+
+ if (buf == NULL)
+ return NULL;
+
+ buf++;
+
+ g_hash_table_insert (ret, GSIZE_TO_POINTER (addr), g_strdup (str));
+ }
+
+ self->pos += jitmap->frame.len;
+
+ return g_steal_pointer (&ret);
+}
+
+const SpCaptureSample *
+sp_capture_reader_read_sample (SpCaptureReader *self)
+{
+ SpCaptureSample *sample;
+
+ g_assert (self != NULL);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (self->pos <= self->bufsz);
+
+ if (!sp_capture_reader_ensure_space_for (self, sizeof *sample))
+ return NULL;
+
+ sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
+
+ sp_capture_reader_bswap_frame (self, &sample->frame);
+
+ if (sample->frame.type != SP_CAPTURE_FRAME_SAMPLE)
+ return NULL;
+
+ if (sample->frame.len < sizeof *sample)
+ return NULL;
+
+ if (self->endian != G_BYTE_ORDER)
+ sample->n_addrs = GUINT16_SWAP_LE_BE (sample->n_addrs);
+
+ if (sample->frame.len < (sizeof *sample + (sizeof(SpCaptureAddress) * sample->n_addrs)))
+ return NULL;
+
+ if (!sp_capture_reader_ensure_space_for (self, sample->frame.len))
+ return NULL;
+
+ sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
+
+ if (self->endian != G_BYTE_ORDER)
+ {
+ guint i;
+
+ for (i = 0; i < sample->n_addrs; i++)
+ sample->addrs[i] = GUINT64_SWAP_LE_BE (sample->addrs[i]);
+ }
+
+ self->pos += sample->frame.len;
+
+ return sample;
+}
+
+gboolean
+sp_capture_reader_reset (SpCaptureReader *self)
+{
+ g_assert (self != NULL);
+
+ self->fd_off = sizeof (SpCaptureFileHeader);
+ self->pos = 0;
+ self->len = 0;
+
+ return TRUE;
+}
+
+SpCaptureReader *
+sp_capture_reader_ref (SpCaptureReader *self)
+{
+ g_assert (self != NULL);
+ g_assert (self->ref_count > 0);
+
+ g_atomic_int_inc (&self->ref_count);
+
+ return self;
+}
+
+void
+sp_capture_reader_unref (SpCaptureReader *self)
+{
+ g_assert (self != NULL);
+ g_assert (self->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&self->ref_count))
+ sp_capture_reader_finalize (self);
+}
+
+gboolean
+sp_capture_reader_splice (SpCaptureReader *self,
+ SpCaptureWriter *dest,
+ GError **error)
+{
+ g_assert (self != NULL);
+ g_assert (self->fd != -1);
+ g_assert (dest != NULL);
+
+ /* Flush before writing anything to ensure consistency */
+ if (!sp_capture_writer_flush (dest))
+ {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ return FALSE;
+ }
+
+ /*
+ * We don't need to track position because writer will
+ * track the current position to avoid reseting it.
+ */
+
+ /* Perform the splice */
+ return _sp_capture_writer_splice_from_fd (dest, self->fd, error);
+}
+
+/**
+ * sp_capture_reader_save_as:
+ * @self: An #SpCaptureReader
+ * @filename: the file to save the capture as
+ * @error: a location for a #GError or %NULL.
+ *
+ * This is a convenience function for copying a capture file for which
+ * you may have already discarded the writer for.
+ *
+ * Returns: %TRUE on success; otherwise %FALSE and @error is set.
+ */
+gboolean
+sp_capture_reader_save_as (SpCaptureReader *self,
+ const gchar *filename,
+ GError **error)
+{
+ struct stat stbuf;
+ off_t in_off;
+ gsize to_write;
+ int fd = -1;
+
+ g_assert (self != NULL);
+ g_assert (filename != NULL);
+
+ if (-1 == (fd = open (filename, O_CREAT | O_WRONLY, 0640)))
+ goto handle_errno;
+
+ if (-1 == fstat (self->fd, &stbuf))
+ goto handle_errno;
+
+ if (-1 == ftruncate (fd, stbuf.st_size))
+ goto handle_errno;
+
+ if ((off_t)-1 == lseek (fd, 0L, SEEK_SET))
+ goto handle_errno;
+
+ in_off = 0;
+ to_write = stbuf.st_size;
+
+ while (to_write > 0)
+ {
+ gssize written;
+
+ written = sendfile (fd, self->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;
+ }
+
+ close (fd);
+
+ return TRUE;
+
+handle_errno:
+ if (fd != -1)
+ close (fd);
+
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+
+ return FALSE;
+}
diff --git a/util/sp-capture-reader.h b/util/sp-capture-reader.h
new file mode 100644
index 0000000..14d93c8
--- /dev/null
+++ b/util/sp-capture-reader.h
@@ -0,0 +1,65 @@
+/* sp-capture-reader.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CAPTURE_READER_H
+#define SP_CAPTURE_READER_H
+
+#include "sp-capture-types.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SpCaptureReader SpCaptureReader;
+
+SpCaptureReader *sp_capture_reader_new (const gchar *filename,
+ GError **error);
+SpCaptureReader *sp_capture_reader_new_from_fd (int fd,
+ GError **error);
+SpCaptureReader *sp_capture_reader_ref (SpCaptureReader *self);
+void sp_capture_reader_unref (SpCaptureReader *self);
+const gchar *sp_capture_reader_get_filename (SpCaptureReader *self);
+const gchar *sp_capture_reader_get_time (SpCaptureReader *self);
+gboolean sp_capture_reader_skip (SpCaptureReader *self);
+gboolean sp_capture_reader_peek_type (SpCaptureReader *self,
+ SpCaptureFrameType *type);
+const SpCaptureMap *sp_capture_reader_read_map (SpCaptureReader *self);
+const SpCaptureExit *sp_capture_reader_read_exit (SpCaptureReader *self);
+const SpCaptureFork *sp_capture_reader_read_fork (SpCaptureReader *self);
+const SpCaptureTimestamp *sp_capture_reader_read_timestamp (SpCaptureReader *self);
+const SpCaptureProcess *sp_capture_reader_read_process (SpCaptureReader *self);
+const SpCaptureSample *sp_capture_reader_read_sample (SpCaptureReader *self);
+GHashTable *sp_capture_reader_read_jitmap (SpCaptureReader *self);
+gboolean sp_capture_reader_reset (SpCaptureReader *self);
+gboolean sp_capture_reader_splice (SpCaptureReader *self,
+ SpCaptureWriter *dest,
+ GError **error);
+gboolean sp_capture_reader_save_as (SpCaptureReader *self,
+ const gchar *filename,
+ GError **error);
+
+#ifndef SP_DISABLE_GOBJECT
+# define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
+ GType sp_capture_reader_get_type (void);
+#endif
+
+#if GLIB_CHECK_VERSION(2, 44, 0)
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureReader, sp_capture_reader_unref)
+#endif
+
+G_END_DECLS
+
+#endif /* SP_CAPTURE_READER_H */
diff --git a/util/sp-capture-types.h b/util/sp-capture-types.h
new file mode 100644
index 0000000..7bb3ab7
--- /dev/null
+++ b/util/sp-capture-types.h
@@ -0,0 +1,155 @@
+/* sp-capture-types.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CAPTURE_FORMAT_H
+#define SP_CAPTURE_FORMAT_H
+
+#include <glib.h>
+
+#ifndef SP_DISABLE_GOBJECT
+# include <glib-object.h>
+#endif
+
+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 _SpCaptureReader SpCaptureReader;
+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,
+} SpCaptureFrameType;
+
+#pragma pack(push, 1)
+
+typedef struct
+{
+ guint32 magic;
+ guint8 version;
+ guint32 little_endian : 1;
+ guint32 padding : 23;
+ gchar capture_time[64];
+ gchar suffix[184];
+} 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;
+
+#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);
+
+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/util/sp-capture-writer.c b/util/sp-capture-writer.c
new file mode 100644
index 0000000..f0f725f
--- /dev/null
+++ b/util/sp-capture-writer.c
@@ -0,0 +1,954 @@
+/* sp-capture-writer.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glib/gstdio.h>
+#include <string.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "sp-capture-reader.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;
+
+ /* 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 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)
+{
+ if ((self->len - self->pos) < len)
+ {
+ if (!sp_capture_writer_flush_data (self))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+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);
+
+ jitmap.frame.len = len;
+ jitmap.frame.cpu = -1;
+ jitmap.frame.pid = getpid ();
+ jitmap.frame.time = g_get_monotonic_time ();
+ jitmap.frame.type = 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;
+
+ if (buffer_size == 0)
+ buffer_size = DEFAULT_BUFFER_SIZE;
+
+ g_assert (fd != -1);
+ g_assert (buffer_size % getpagesize() == 0);
+
+ self = g_new0 (SpCaptureWriter, 1);
+ self->ref_count = 1;
+ self->fd = fd;
+ self->buf = (guint8 *)g_malloc (buffer_size);
+ self->len = buffer_size;
+
+ g_get_current_time (&tv);
+ nowstr = g_time_val_to_iso8601 (&tv);
+
+ header = (SpCaptureFileHeader *)(gpointer)self->buf;
+ 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);
+ memset (header->suffix, 0, sizeof header->suffix);
+
+ self->pos += sizeof *header;
+
+ 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 ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (filename != NULL);
+
+ len = sizeof *ev + strlen (filename) + 1;
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_MAP;
+ ev->frame.padding = 0;
+ 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->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 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_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 ((self->pos % SP_CAPTURE_ALIGN) == 0);
+ g_assert (cmdline != NULL);
+
+ len = sizeof *ev + strlen (cmdline) + 1;
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_PROCESS;
+ ev->frame.padding = 0;
+
+ g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
+ ev->cmdline[len - sizeof *ev - 1] = '\0';
+
+ self->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 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);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ len = sizeof *ev + (n_addrs * sizeof (SpCaptureAddress));
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_SAMPLE;
+ ev->frame.padding = 0;
+ ev->n_addrs = n_addrs;
+
+ memcpy (ev->addrs, addrs, (n_addrs * sizeof (SpCaptureAddress)));
+
+ self->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ 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);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureFork *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_FORK;
+ ev->frame.padding = 0;
+ ev->child_pid = child_pid;
+
+ self->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ 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);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureExit *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_EXIT;
+ ev->frame.padding = 0;
+
+ self->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ 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);
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ sp_capture_writer_realign (&len);
+
+ if (!sp_capture_writer_ensure_space_for (self, len))
+ return FALSE;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ ev = (SpCaptureTimestamp *)(gpointer)&self->buf[self->pos];
+ ev->frame.len = len;
+ ev->frame.cpu = cpu;
+ ev->frame.pid = pid;
+ ev->frame.time = time;
+ ev->frame.type = SP_CAPTURE_FRAME_TIMESTAMP;
+ ev->frame.padding = 0;
+
+ self->pos += ev->frame.len;
+
+ g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
+
+ self->stat.frame_count[SP_CAPTURE_FRAME_TIMESTAMP]++;
+
+ 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_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_create_reader:
+ * @self: A #SpCaptureWriter
+ * @error: a location for a #GError, or %NULL
+ *
+ * Creates a new reader for the writer.
+ *
+ * Since readers use positioned reads, this uses the same file-descriptor for
+ * the #SpCaptureReader. Therefore, if you are writing to the capture while
+ * also consuming from the reader, you could get transient failures unless you
+ * synchronize the operations.
+ *
+ * Returns: (transfer full): A #SpCaptureReader.
+ */
+SpCaptureReader *
+sp_capture_writer_create_reader (SpCaptureWriter *self,
+ GError **error)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (self->fd != -1, NULL);
+
+ return sp_capture_reader_new_from_fd (self->fd, error);
+}
+
+/**
+ * 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;
+}
diff --git a/util/sp-capture-writer.h b/util/sp-capture-writer.h
new file mode 100644
index 0000000..1846fd1
--- /dev/null
+++ b/util/sp-capture-writer.h
@@ -0,0 +1,108 @@
+/* sp-capture-writer.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SP_CAPTURE_WRITER_H
+#define SP_CAPTURE_WRITER_H
+
+#include "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);
+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_flush (SpCaptureWriter *self);
+gboolean sp_capture_writer_save_as (SpCaptureWriter *self,
+ const gchar *filename,
+ GError **error);
+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 */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]