/* sysprof-local-profiler.c * * Copyright 2016-2019 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "sysprof-local-profiler" #include "config.h" #include #include #include #include #include #include #include #include "sysprof-helpers.h" #include "sysprof-local-profiler.h" #include "sysprof-platform.h" #include "sysprof-capture-autocleanups.h" #include "sysprof-control-source.h" #include "sysprof-gjs-source.h" #include "sysprof-hostinfo-source.h" #ifdef __linux__ # include "sysprof-perf-source.h" #endif #include "sysprof-proc-source.h" #include "sysprof-proxy-source.h" #define CSTRV(s) ((const gchar * const *)s) typedef struct { SysprofCaptureWriter *writer; /* All sources added */ GPtrArray *sources; /* Array of GError failures */ GPtrArray *failures; /* Sources currently starting */ GPtrArray *starting; /* Sources currently stopping */ GPtrArray *stopping; /* Sources that have failed or finished */ GPtrArray *finished_or_failed; /* Pids to notify children about before prepare */ GArray *pids; /* Timer for simple time tracking */ GTimer *timer; guint timer_notify_source; /* Arguments and environment variables for spawning */ gchar **spawn_argv; gchar **spawn_env; gchar *spawn_cwd; /* State flags */ guint is_running : 1; guint is_stopping : 1; guint is_starting : 1; /* * If we should spawn argv when starting up. This allows UI to set * spawn argv/env but enable disable with a toggle. */ guint spawn : 1; /* If we should inherit the environment when spawning */ guint spawn_inherit_environ : 1; /* * If we should profile the entire system. Setting this results in pids * being ignored. This is primarily useful for UI to toggle on/off the * feature of per-process vs whole-system. */ guint whole_system : 1; /* * If we got a stop request after calling start() but before we have had * a chance to settle, then we need to stop immediately after starting. * We do this to avoid a more complex state machine (for now). */ guint stop_after_starting : 1; } SysprofLocalProfilerPrivate; static void profiler_iface_init (SysprofProfilerInterface *iface); G_DEFINE_TYPE_EXTENDED (SysprofLocalProfiler, sysprof_local_profiler, G_TYPE_OBJECT, 0, G_ADD_PRIVATE (SysprofLocalProfiler) G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILER, profiler_iface_init)) enum { PROP_0, N_PROPS, PROP_ELAPSED, PROP_IS_MUTABLE, PROP_IS_RUNNING, PROP_SPAWN, PROP_SPAWN_ARGV, PROP_SPAWN_CWD, PROP_SPAWN_ENV, PROP_SPAWN_INHERIT_ENVIRON, PROP_WHOLE_SYSTEM, }; static inline gint _g_ptr_array_find (GPtrArray *ar, gpointer item) { guint i; for (i = 0; i < ar->len; i++) { if (item == g_ptr_array_index (ar, i)) return i; } return -1; } static inline gboolean _g_ptr_array_contains (GPtrArray *ar, gpointer item) { return (-1 != _g_ptr_array_find (ar, item)); } static void sysprof_local_profiler_clear_timer (SysprofLocalProfiler *self) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_clear_pointer (&priv->timer, g_timer_destroy); if (priv->timer_notify_source != 0) { g_source_remove (priv->timer_notify_source); priv->timer_notify_source = 0; } } static void sysprof_local_profiler_real_stopped (SysprofProfiler *profiler) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); sysprof_local_profiler_clear_timer (self); } static gboolean sysprof_local_profiler_notify_elapsed_cb (gpointer data) { SysprofLocalProfiler *self = data; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_object_notify (G_OBJECT (self), "elapsed"); return G_SOURCE_CONTINUE; } static void sysprof_local_profiler_finish_stopping (SysprofLocalProfiler *self) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_autoptr(SysprofCaptureReader) reader = NULL; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (priv->is_starting == FALSE); g_assert (priv->is_stopping == TRUE); g_assert (priv->stopping->len == 0); reader = sysprof_capture_writer_create_reader (priv->writer); g_assert (reader != NULL); for (guint i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); sysprof_capture_reader_reset (reader); sysprof_source_supplement (source, reader); } if (priv->failures->len > 0) { const GError *error = g_ptr_array_index (priv->failures, 0); sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), error); } priv->is_running = FALSE; priv->is_stopping = FALSE; sysprof_profiler_emit_stopped (SYSPROF_PROFILER (self)); g_object_notify (G_OBJECT (self), "is-mutable"); g_object_notify (G_OBJECT (self), "is-running"); } static void sysprof_local_profiler_stop (SysprofProfiler *profiler) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); guint i; g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); if (priv->is_starting) { priv->stop_after_starting = TRUE; return; } if (priv->is_stopping || !priv->is_running) return; priv->is_stopping = TRUE; /* * First we add everything to the stopping list, so that we can * be notified of when they have completed. If everything stopped * synchronously, the stopping list will be empty after calling * sysprof_source_stop() for every source. Otherwise, we need to delay * stopping for a little bit. */ for (i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); if (!_g_ptr_array_contains (priv->finished_or_failed, source)) g_ptr_array_add (priv->stopping, g_object_ref (source)); } for (i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); sysprof_source_stop (source); } if (priv->is_stopping && priv->stopping->len == 0) sysprof_local_profiler_finish_stopping (self); } static void sysprof_local_profiler_dispose (GObject *object) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)object; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); if (priv->is_running || priv->is_starting) { sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); return; } sysprof_local_profiler_clear_timer (self); G_OBJECT_CLASS (sysprof_local_profiler_parent_class)->dispose (object); } static void sysprof_local_profiler_finalize (GObject *object) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)object; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); g_clear_pointer (&priv->sources, g_ptr_array_unref); g_clear_pointer (&priv->starting, g_ptr_array_unref); g_clear_pointer (&priv->stopping, g_ptr_array_unref); g_clear_pointer (&priv->failures, g_ptr_array_unref); g_clear_pointer (&priv->finished_or_failed, g_ptr_array_unref); g_clear_pointer (&priv->pids, g_array_unref); G_OBJECT_CLASS (sysprof_local_profiler_parent_class)->finalize (object); } static void sysprof_local_profiler_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SysprofLocalProfiler *self = SYSPROF_LOCAL_PROFILER (object); SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); switch (prop_id) { case PROP_ELAPSED: g_value_set_double (value, priv->timer ? g_timer_elapsed (priv->timer, NULL) : 0.0); break; case PROP_IS_MUTABLE: g_value_set_boolean (value, !(priv->is_starting || priv->is_starting || priv->is_running)); break; case PROP_IS_RUNNING: g_value_set_boolean (value, priv->is_running); break; case PROP_WHOLE_SYSTEM: g_value_set_boolean (value, priv->whole_system); break; case PROP_SPAWN: g_value_set_boolean (value, priv->spawn); break; case PROP_SPAWN_INHERIT_ENVIRON: g_value_set_boolean (value, priv->spawn_inherit_environ); break; case PROP_SPAWN_ARGV: g_value_set_boxed (value, priv->spawn_argv); break; case PROP_SPAWN_CWD: g_value_set_string (value, priv->spawn_cwd); break; case PROP_SPAWN_ENV: g_value_set_boxed (value, priv->spawn_env); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_local_profiler_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SysprofLocalProfiler *self = SYSPROF_LOCAL_PROFILER (object); SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); switch (prop_id) { case PROP_WHOLE_SYSTEM: priv->whole_system = g_value_get_boolean (value); break; case PROP_SPAWN: priv->spawn = g_value_get_boolean (value); break; case PROP_SPAWN_INHERIT_ENVIRON: priv->spawn_inherit_environ = g_value_get_boolean (value); break; case PROP_SPAWN_ARGV: g_strfreev (priv->spawn_argv); priv->spawn_argv = g_value_dup_boxed (value); break; case PROP_SPAWN_CWD: g_free (priv->spawn_cwd); priv->spawn_cwd = g_value_dup_string (value); break; case PROP_SPAWN_ENV: g_strfreev (priv->spawn_env); priv->spawn_env = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_local_profiler_class_init (SysprofLocalProfilerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = sysprof_local_profiler_dispose; object_class->finalize = sysprof_local_profiler_finalize; object_class->get_property = sysprof_local_profiler_get_property; object_class->set_property = sysprof_local_profiler_set_property; g_object_class_override_property (object_class, PROP_ELAPSED, "elapsed"); g_object_class_override_property (object_class, PROP_IS_MUTABLE, "is-mutable"); g_object_class_override_property (object_class, PROP_IS_RUNNING, "is-running"); g_object_class_override_property (object_class, PROP_SPAWN, "spawn"); g_object_class_override_property (object_class, PROP_SPAWN_ARGV, "spawn-argv"); g_object_class_override_property (object_class, PROP_SPAWN_CWD, "spawn-cwd"); g_object_class_override_property (object_class, PROP_SPAWN_ENV, "spawn-env"); g_object_class_override_property (object_class, PROP_SPAWN_INHERIT_ENVIRON, "spawn-inherit-environ"); g_object_class_override_property (object_class, PROP_WHOLE_SYSTEM, "whole-system"); g_type_ensure (SYSPROF_TYPE_GJS_SOURCE); #ifdef __linux__ g_type_ensure (SYSPROF_TYPE_HOSTINFO_SOURCE); g_type_ensure (SYSPROF_TYPE_PROC_SOURCE); g_type_ensure (SYSPROF_TYPE_PERF_SOURCE); #endif g_type_ensure (SYSPROF_TYPE_PROXY_SOURCE); } static void sysprof_local_profiler_init (SysprofLocalProfiler *self) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); priv->whole_system = TRUE; priv->failures = g_ptr_array_new_with_free_func ((GDestroyNotify)g_error_free); priv->sources = g_ptr_array_new_with_free_func (g_object_unref); priv->starting = g_ptr_array_new_with_free_func (g_object_unref); priv->stopping = g_ptr_array_new_with_free_func (g_object_unref); priv->finished_or_failed = g_ptr_array_new_with_free_func (g_object_unref); priv->pids = g_array_new (FALSE, FALSE, sizeof (GPid)); } SysprofProfiler * sysprof_local_profiler_new (void) { return g_object_new (SYSPROF_TYPE_LOCAL_PROFILER, NULL); } static void sysprof_local_profiler_finish_startup (SysprofLocalProfiler *self) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); guint i; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (priv->is_starting == TRUE); g_assert (priv->starting->len == 0); sysprof_local_profiler_clear_timer (self); priv->timer = g_timer_new (); /* * Add a source to update our watchers of elapsed time. * We use 1000 instead of add_seconds(1) so that we are * not subject to as much drift. */ priv->timer_notify_source = g_timeout_add (1000, sysprof_local_profiler_notify_elapsed_cb, self); for (i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); sysprof_source_start (source); } priv->is_starting = FALSE; /* * If any of the sources failed during startup, we will have a non-empty * failures list. */ if (priv->failures->len > 0) { const GError *error = g_ptr_array_index (priv->failures, 0); g_object_ref (self); sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), error); sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); g_object_unref (self); return; } priv->is_running = TRUE; g_object_notify (G_OBJECT (self), "is-mutable"); g_object_notify (G_OBJECT (self), "is-running"); /* * If all the sources are transient (in that they just generate information * and then exit), we could be finished as soon as we complete startup. * * If we detect this, we stop immediately. */ if (priv->finished_or_failed->len == priv->sources->len || priv->stop_after_starting) sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); } static void sysprof_local_profiler_wait_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = (GSubprocess *)object; g_autoptr(SysprofLocalProfiler) self = user_data; g_autoptr(GError) error = NULL; g_assert (G_IS_SUBPROCESS (subprocess)); g_assert (G_IS_ASYNC_RESULT (result)); g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); if (!g_subprocess_wait_finish (subprocess, result, &error)) g_warning ("Wait on subprocess failed: %s", error->message); sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); } static void sysprof_local_profiler_start (SysprofProfiler *profiler) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_autoptr(SysprofControlSource) control_source = NULL; g_autofree gchar *keydata = NULL; g_autoptr(GError) error = NULL; g_autoptr(GKeyFile) keyfile = NULL; gsize keylen = 0; g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); g_return_if_fail (priv->is_running == FALSE); g_return_if_fail (priv->is_stopping == FALSE); g_return_if_fail (priv->is_starting == FALSE); g_clear_pointer (&priv->timer, g_timer_destroy); g_object_notify (G_OBJECT (self), "elapsed"); control_source = sysprof_control_source_new (); sysprof_profiler_add_source (SYSPROF_PROFILER (self), SYSPROF_SOURCE (control_source)); keyfile = g_key_file_new (); if (priv->writer == NULL) { SysprofCaptureWriter *writer; int fd; if ((-1 == (fd = sysprof_memfd_create ("[sysprof]"))) || (NULL == (writer = sysprof_capture_writer_new_from_fd (fd, 0)))) { const GError werror = { G_FILE_ERROR, g_file_error_from_errno (errno), (gchar *)g_strerror (errno) }; if (fd != -1) close (fd); sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), &werror); return; } sysprof_profiler_set_writer (SYSPROF_PROFILER (self), writer); g_clear_pointer (&writer, sysprof_capture_writer_unref); } priv->is_running = TRUE; priv->is_starting = TRUE; if (priv->failures->len > 0) g_ptr_array_remove_range (priv->failures, 0, priv->failures->len); g_key_file_set_boolean (keyfile, "profiler", "whole-system", priv->whole_system); if (priv->pids->len > 0) g_key_file_set_integer_list (keyfile, "profiler", "pids", (gint *)(gpointer)priv->pids->data, priv->pids->len); g_key_file_set_boolean (keyfile, "profiler", "spawn", priv->spawn); g_key_file_set_boolean (keyfile, "profiler", "spawn-inherit-environ", priv->spawn_inherit_environ); g_key_file_set_string (keyfile, "profiler", "spawn-cwd", priv->spawn_cwd ? priv->spawn_cwd : ""); if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0]) { g_autoptr(GPtrArray) env = g_ptr_array_new_with_free_func (g_free); g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); g_autoptr(GSubprocess) subprocess = NULL; GPid pid; if (priv->spawn_inherit_environ) { gchar **environ_ = g_get_environ (); for (guint i = 0; environ_[i]; i++) g_ptr_array_add (env, environ_[i]); g_free (environ_); } if (priv->spawn_env) { g_key_file_set_string_list (keyfile, "profiler", "spawn-env", (const gchar * const *)priv->spawn_env, g_strv_length (priv->spawn_env)); for (guint i = 0; priv->spawn_env[i]; i++) g_ptr_array_add (env, g_strdup (priv->spawn_env[i])); } g_ptr_array_add (env, NULL); sysprof_spawnable_set_environ (spawnable, (const gchar * const *)env->pdata); sysprof_spawnable_append_args (spawnable, (const gchar * const *)priv->spawn_argv); if (priv->spawn_cwd != NULL) sysprof_spawnable_set_cwd (spawnable, priv->spawn_cwd); /* Save argv before modifying */ if (priv->spawn_argv != NULL) g_key_file_set_string_list (keyfile, "profiler", "spawn-argv", (const gchar * const *)priv->spawn_argv, g_strv_length (priv->spawn_argv)); for (guint i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); sysprof_source_modify_spawn (source, spawnable); } if (!(subprocess = sysprof_spawnable_spawn (spawnable, &error))) { g_ptr_array_add (priv->failures, g_steal_pointer (&error)); } else { const gchar *ident = g_subprocess_get_identifier (subprocess); pid = atoi (ident); g_array_append_val (priv->pids, pid); g_subprocess_wait_async (subprocess, NULL, sysprof_local_profiler_wait_cb, g_object_ref (self)); } } g_key_file_set_integer (keyfile, "profiler", "n-sources", priv->sources->len); for (guint i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); g_autofree gchar *group = g_strdup_printf ("source-%u", i); g_key_file_set_string (keyfile, group, "gtype", G_OBJECT_TYPE_NAME (source)); sysprof_source_serialize (source, keyfile, group); if (priv->whole_system == FALSE) { for (guint j = 0; j < priv->pids->len; j++) { GPid pid = g_array_index (priv->pids, GPid, j); sysprof_source_add_pid (source, pid); } } sysprof_source_set_writer (source, priv->writer); sysprof_source_prepare (source); } for (guint i = 0; i < priv->sources->len; i++) { SysprofSource *source = g_ptr_array_index (priv->sources, i); if (!sysprof_source_get_is_ready (source)) g_ptr_array_add (priv->starting, g_object_ref (source)); } if ((keydata = g_key_file_to_data (keyfile, &keylen, NULL))) sysprof_capture_writer_add_metadata (priv->writer, SYSPROF_CAPTURE_CURRENT_TIME, -1, -1, "local-profiler", keydata, keylen); if (priv->starting->len == 0) sysprof_local_profiler_finish_startup (self); } static void sysprof_local_profiler_set_writer (SysprofProfiler *profiler, SysprofCaptureWriter *writer) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); g_return_if_fail (priv->is_running == FALSE); g_return_if_fail (priv->is_stopping == FALSE); g_return_if_fail (writer != NULL); if (priv->writer != writer) { g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); if (writer != NULL) priv->writer = sysprof_capture_writer_ref (writer); } } static void sysprof_local_profiler_track_completed (SysprofLocalProfiler *self, SysprofSource *source) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); gint i; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (SYSPROF_IS_SOURCE (source)); if (!_g_ptr_array_contains (priv->finished_or_failed, source)) g_ptr_array_add (priv->finished_or_failed, g_object_ref (source)); if (priv->is_starting) { i = _g_ptr_array_find (priv->starting, source); if (i >= 0) { g_ptr_array_remove_index (priv->starting, i); if (priv->starting->len == 0) sysprof_local_profiler_finish_startup (self); } } if (priv->is_stopping) { i = _g_ptr_array_find (priv->stopping, source); if (i >= 0) { g_ptr_array_remove_index_fast (priv->stopping, i); if ((priv->is_stopping == TRUE) && (priv->stopping->len == 0)) sysprof_local_profiler_finish_stopping (self); } } if (!priv->is_starting) { if (priv->finished_or_failed->len == priv->sources->len) sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); } } static void sysprof_local_profiler_source_finished (SysprofLocalProfiler *self, SysprofSource *source) { g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (SYSPROF_IS_SOURCE (source)); sysprof_local_profiler_track_completed (self, source); } static void sysprof_local_profiler_source_ready (SysprofLocalProfiler *self, SysprofSource *source) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); guint i; g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (SYSPROF_IS_SOURCE (source)); for (i = 0; i < priv->starting->len; i++) { SysprofSource *ele = g_ptr_array_index (priv->starting, i); if (ele == source) { g_ptr_array_remove_index_fast (priv->starting, i); if ((priv->is_starting == TRUE) && (priv->starting->len == 0)) sysprof_local_profiler_finish_startup (self); break; } } } static void sysprof_local_profiler_source_failed (SysprofLocalProfiler *self, const GError *reason, SysprofSource *source) { SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); g_assert (reason != NULL); g_assert (SYSPROF_IS_SOURCE (source)); g_warning ("%s failed: %s", G_OBJECT_TYPE_NAME (source), reason ? reason->message : "unknown error"); sysprof_local_profiler_track_completed (self, source); /* Failure emitted out of band */ if (!priv->is_starting && !priv->is_stopping && !priv->is_running) return; g_ptr_array_add (priv->failures, g_error_copy (reason)); /* Ignore during start/stop, we handle this in other places */ if (priv->is_starting || priv->is_stopping) return; if (priv->is_running) sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); } static void sysprof_local_profiler_add_source (SysprofProfiler *profiler, SysprofSource *source) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); g_return_if_fail (SYSPROF_IS_SOURCE (source)); g_return_if_fail (priv->is_running == FALSE); g_return_if_fail (priv->is_starting == FALSE); g_return_if_fail (priv->is_stopping == FALSE); g_signal_connect_object (source, "failed", G_CALLBACK (sysprof_local_profiler_source_failed), self, G_CONNECT_SWAPPED); g_signal_connect_object (source, "finished", G_CALLBACK (sysprof_local_profiler_source_finished), self, G_CONNECT_SWAPPED); g_signal_connect_object (source, "ready", G_CALLBACK (sysprof_local_profiler_source_ready), self, G_CONNECT_SWAPPED); g_ptr_array_add (priv->sources, g_object_ref (source)); } static void sysprof_local_profiler_add_pid (SysprofProfiler *profiler, GPid pid) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); g_return_if_fail (pid > -1); g_return_if_fail (priv->is_starting == FALSE); g_return_if_fail (priv->is_stopping == FALSE); g_return_if_fail (priv->is_running == FALSE); g_array_append_val (priv->pids, pid); } static void sysprof_local_profiler_remove_pid (SysprofProfiler *profiler, GPid pid) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); guint i; g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); g_return_if_fail (pid > -1); g_return_if_fail (priv->is_starting == FALSE); g_return_if_fail (priv->is_stopping == FALSE); g_return_if_fail (priv->is_running == FALSE); for (i = 0; i < priv->pids->len; i++) { GPid ele = g_array_index (priv->pids, GPid, i); if (ele == pid) { g_array_remove_index_fast (priv->pids, i); break; } } } static const GPid * sysprof_local_profiler_get_pids (SysprofProfiler *profiler, guint *n_pids) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (self), NULL); g_return_val_if_fail (n_pids != NULL, NULL); *n_pids = priv->pids->len; return (GPid *)(gpointer)priv->pids->data; } static SysprofCaptureWriter * sysprof_local_profiler_get_writer (SysprofProfiler *profiler) { SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (self), NULL); return priv->writer; } static void profiler_iface_init (SysprofProfilerInterface *iface) { iface->add_pid = sysprof_local_profiler_add_pid; iface->add_source = sysprof_local_profiler_add_source; iface->get_pids = sysprof_local_profiler_get_pids; iface->get_writer = sysprof_local_profiler_get_writer; iface->remove_pid = sysprof_local_profiler_remove_pid; iface->set_writer = sysprof_local_profiler_set_writer; iface->start = sysprof_local_profiler_start; iface->stop = sysprof_local_profiler_stop; iface->stopped = sysprof_local_profiler_real_stopped; } static bool find_profiler_meta_cb (const SysprofCaptureFrame *frame, void *user_data) { const SysprofCaptureMetadata *meta = (const SysprofCaptureMetadata *)frame; GKeyFile **keyfile = user_data; g_assert (frame != NULL); g_assert (frame->type == SYSPROF_CAPTURE_FRAME_METADATA); g_assert (keyfile != NULL); g_assert (*keyfile == NULL); if (g_strcmp0 (meta->id, "local-profiler") == 0) { g_autoptr(GKeyFile) kf = g_key_file_new (); /* Metadata is guaranteed to be \0 terminated by marshaller */ if (g_key_file_load_from_data (kf, meta->metadata, -1, 0, NULL)) *keyfile = g_steal_pointer (&kf); return *keyfile == NULL; } return true; } SysprofProfiler * sysprof_local_profiler_new_replay (SysprofCaptureReader *reader) { static const SysprofCaptureFrameType mtype[] = { SYSPROF_CAPTURE_FRAME_METADATA, }; g_autoptr(SysprofLocalProfiler) self = NULL; g_autoptr(SysprofCaptureCursor) cursor = NULL; g_autoptr(GKeyFile) keyfile = NULL; g_autofree gchar *cwd = NULL; g_auto(GStrv) argv = NULL; g_auto(GStrv) env = NULL; gboolean whole_system; gboolean inherit; gboolean spawn; gint n_sources; g_return_val_if_fail (reader != NULL, NULL); self = g_object_new (SYSPROF_TYPE_LOCAL_PROFILER, NULL); cursor = sysprof_capture_cursor_new (reader); sysprof_capture_cursor_add_condition (cursor, sysprof_capture_condition_new_where_type_in (1, mtype)); sysprof_capture_cursor_foreach (cursor, find_profiler_meta_cb, &keyfile); /* No metadata, bail */ if (keyfile == NULL) return NULL; spawn = g_key_file_get_boolean (keyfile, "profiler", "spawn", NULL); inherit = g_key_file_get_boolean (keyfile, "profiler", "spawn-inherit-environ", NULL); argv = g_key_file_get_string_list (keyfile, "profiler", "spawn-argv", NULL, NULL); env = g_key_file_get_string_list (keyfile, "profiler", "spawn-env", NULL, NULL); cwd = g_key_file_get_string (keyfile, "profiler", "spawn-cwd", NULL); n_sources = g_key_file_get_integer (keyfile, "profiler", "n-sources", NULL); whole_system = g_key_file_get_boolean (keyfile, "profiler", "whole-system", NULL); /* Ignore empty string */ if (cwd != NULL && *cwd == 0) g_clear_pointer (&cwd, g_free); sysprof_profiler_set_spawn (SYSPROF_PROFILER (self), spawn); sysprof_profiler_set_spawn_argv (SYSPROF_PROFILER (self), CSTRV (argv)); sysprof_profiler_set_spawn_cwd (SYSPROF_PROFILER (self), cwd); sysprof_profiler_set_spawn_env (SYSPROF_PROFILER (self), CSTRV (env)); sysprof_profiler_set_spawn_inherit_environ (SYSPROF_PROFILER (self), inherit); sysprof_profiler_set_whole_system (SYSPROF_PROFILER (self), whole_system); for (guint i = 0; i < n_sources; i++) { g_autofree gchar *group = g_strdup_printf ("source-%u", i); g_autofree gchar *type_name = NULL; g_autoptr(SysprofSource) source = NULL; GType gtype; if (!g_key_file_has_group (keyfile, group) || !(type_name = g_key_file_get_string (keyfile, group, "gtype", NULL)) || !(gtype = g_type_from_name (type_name)) || !g_type_is_a (gtype, SYSPROF_TYPE_SOURCE) || !(source = g_object_new (gtype, NULL))) continue; sysprof_source_deserialize (source, keyfile, group); sysprof_local_profiler_add_source (SYSPROF_PROFILER (self), source); } return SYSPROF_PROFILER (g_steal_pointer (&self)); }