From a3d0ddc231ad3c6f3741be5243dac447b45b2aa8 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Tue, 30 Jul 2019 15:29:43 -0700 Subject: [PATCH] sysprofd: add RAPL profiler on the org.gnome.Sysprof3 D-Bus peer This allows consumers to get a RAPL profiler object on the D-Bus at org.gnome.Sysprof3 with path /org/gnome/Sysprof3/RAPL. This can be used by the clients to record extra power statistics. It requires the `turbostat` program to be installed, and is provided in packages such as `kernel-tools` on Fedora. Distributions may want to ensure that is available as a dependency of Sysprof, but it is not strictly required. --- src/sysprofd/ipc-rapl-profiler.c | 421 ++++++++++++++++++++++++++++++ src/sysprofd/ipc-rapl-profiler.h | 33 +++ src/sysprofd/meson.build | 4 + src/sysprofd/sysprof-turbostat.c | 427 +++++++++++++++++++++++++++++++ src/sysprofd/sysprof-turbostat.h | 49 ++++ src/sysprofd/sysprofd.c | 10 +- 6 files changed, 943 insertions(+), 1 deletion(-) create mode 100644 src/sysprofd/ipc-rapl-profiler.c create mode 100644 src/sysprofd/ipc-rapl-profiler.h create mode 100644 src/sysprofd/sysprof-turbostat.c create mode 100644 src/sysprofd/sysprof-turbostat.h diff --git a/src/sysprofd/ipc-rapl-profiler.c b/src/sysprofd/ipc-rapl-profiler.c new file mode 100644 index 00000000..648c1e7b --- /dev/null +++ b/src/sysprofd/ipc-rapl-profiler.c @@ -0,0 +1,421 @@ +/* ipc-rapl-profiler.c + * + * Copyright 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 "ipc-rapl-profiler" + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "ipc-rapl-profiler.h" +#include "sysprof-turbostat.h" + +#define DEFAULT_POLL_FREQ_SECONDS 1 + +struct _IpcRaplProfiler +{ + IpcProfilerSkeleton parent_instance; + + GMutex mutex; + GArray *counter_ids; + SysprofTurbostat *turbostat; + SysprofCaptureWriter *writer; + guint poll_source; +}; + +typedef struct +{ + gint core; + gint cpu; + guint counter_base; +} CounterId; + +static void profiler_iface_init (IpcProfilerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (IpcRaplProfiler, ipc_rapl_profiler, IPC_TYPE_PROFILER_SKELETON, + G_IMPLEMENT_INTERFACE (IPC_TYPE_PROFILER, profiler_iface_init)) + +enum { + ACTIVITY, + N_SIGNALS +}; + +static guint signals [N_SIGNALS]; + +static void +ipc_rapl_profiler_activity (IpcRaplProfiler *self) +{ + g_assert (IPC_IS_RAPL_PROFILER (self)); + + g_signal_emit (self, signals [ACTIVITY], 0); +} + +static void +ipc_rapl_profiler_stop_locked (IpcRaplProfiler *self) +{ + g_assert (IPC_IS_RAPL_PROFILER (self)); + + g_clear_handle_id (&self->poll_source, g_source_remove); + + if (self->turbostat != NULL) + sysprof_turbostat_stop (self->turbostat); + + g_clear_pointer (&self->turbostat, sysprof_turbostat_free); + g_clear_pointer (&self->counter_ids, g_array_unref); +} + +static guint +add_counter_base (SysprofCaptureWriter *writer, + GArray *counters, + gint core, + gint cpu) +{ + static const gchar *names[] = { "Package", "Core", "GFX", "RAM" }; + g_autofree gchar *desc = NULL; + CounterId id; + + g_assert (writer != NULL); + g_assert (counters != NULL); + + id.core = core; + id.cpu = cpu; + id.counter_base = sysprof_capture_writer_request_counter (writer, G_N_ELEMENTS (names)); + + g_array_append_val (counters, id); + + desc = g_strdup_printf ("Core:%u CPU:%u", core, cpu); + + { + SysprofCaptureCounter ctrs[4] = { + { .name = "Package Watt", + .id = id.counter_base, + .type = SYSPROF_CAPTURE_COUNTER_DOUBLE }, + + { .name = "Core Watt", + .id = id.counter_base + 1, + .type = SYSPROF_CAPTURE_COUNTER_DOUBLE }, + + { .name = "GFX Watt", + .id = id.counter_base + 2, + .type = SYSPROF_CAPTURE_COUNTER_DOUBLE }, + + { .name = "RAM Watt", + .id = id.counter_base + 3, + .type = SYSPROF_CAPTURE_COUNTER_DOUBLE }, + }; + + for (guint j = 0; j < G_N_ELEMENTS (ctrs); j++) + { + if (cpu == -1) + { + g_snprintf (ctrs[j].category, sizeof ctrs[j].category, "RAPL"); + g_snprintf (ctrs[j].name, sizeof ctrs[j].name, "%s Watt", names[j]); + } + else + { + g_snprintf (ctrs[j].category, sizeof ctrs[j].category, "RAPL %d:%d", core, cpu); + g_snprintf (ctrs[j].name, sizeof ctrs[j].name, "%s Watt (%d:%d)", names[j], core, cpu); + } + } + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctrs, + G_N_ELEMENTS (ctrs)); + } + + return id.counter_base; +} + +static guint +get_counter_base (SysprofCaptureWriter *writer, + GArray *counters, + gint core, + gint cpu) +{ + for (guint i = 0; i < counters->len; i++) + { + const CounterId *ele = &g_array_index (counters, CounterId, i); + + if (core == ele->core && cpu == ele->cpu) + return ele->counter_base; + } + + return add_counter_base (writer, counters, core, cpu); +} + +static gboolean +ipc_rapl_profiler_g_authorize_method (GDBusInterfaceSkeleton *skeleton, + GDBusMethodInvocation *invocation) +{ + PolkitAuthorizationResult *res = NULL; + PolkitAuthority *authority = NULL; + PolkitSubject *subject = NULL; + const gchar *peer_name; + gboolean ret = TRUE; + + g_assert (IPC_IS_RAPL_PROFILER (skeleton)); + g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); + + ipc_rapl_profiler_activity (IPC_RAPL_PROFILER (skeleton)); + + peer_name = g_dbus_method_invocation_get_sender (invocation); + + if (!(authority = polkit_authority_get_sync (NULL, NULL)) || + !(subject = polkit_system_bus_name_new (peer_name)) || + !(res = polkit_authority_check_authorization_sync (authority, + POLKIT_SUBJECT (subject), + "org.gnome.sysprof3.profile", + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + NULL, + NULL)) || + !polkit_authorization_result_get_is_authorized (res)) + { + g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Not authorized to make request"); + ret = FALSE; + } + + g_clear_object (&authority); + g_clear_object (&subject); + g_clear_object (&res); + + return ret; +} + +static void +ipc_rapl_profiler_finalize (GObject *object) +{ + IpcRaplProfiler *self = (IpcRaplProfiler *)object; + + ipc_rapl_profiler_stop_locked (self); + g_mutex_clear (&self->mutex); + + G_OBJECT_CLASS (ipc_rapl_profiler_parent_class)->finalize (object); +} + +static void +ipc_rapl_profiler_class_init (IpcRaplProfilerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass); + + object_class->finalize = ipc_rapl_profiler_finalize; + + skeleton_class->g_authorize_method = ipc_rapl_profiler_g_authorize_method; + + /** + * IpcRaplProfiler::activity: + * + * The "activity" signal is used to denote that some amount of activity + * has occurred and therefore the process should be kept alive longer. + */ + signals [ACTIVITY] = + g_signal_new ("activity", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +static void +ipc_rapl_profiler_init (IpcRaplProfiler *self) +{ + g_mutex_init (&self->mutex); + g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (self), + G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); +} + +IpcProfiler * +ipc_rapl_profiler_new (void) +{ + return g_object_new (IPC_TYPE_RAPL_PROFILER, NULL); +} + +static gboolean +ipc_rapl_profiler_poll_cb (gpointer data) +{ + IpcRaplProfiler *self = data; + g_autoptr(GMutexLocker) locker = NULL; + g_autoptr(GArray) samples = NULL; + g_autoptr(GError) error = NULL; + + g_assert (IPC_IS_RAPL_PROFILER (self)); + + ipc_rapl_profiler_activity (self); + + locker = g_mutex_locker_new (&self->mutex); + + if (self->turbostat == NULL) + return G_SOURCE_REMOVE; + + g_assert (self->counter_ids != NULL); + g_assert (self->writer != NULL); + + if (!(samples = sysprof_turbostat_sample (self->turbostat, &error))) + return G_SOURCE_REMOVE; + + for (guint i = 0; i < samples->len; i++) + { + const SysprofTurbostatSample *sample = &g_array_index (samples, SysprofTurbostatSample, i); + guint base = get_counter_base (self->writer, self->counter_ids, sample->core, sample->cpu); + const guint counter_ids[4] = { base, base + 1, base + 2, base + 3 }; + const SysprofCaptureCounterValue values[4] = { + { .vdbl = sample->pkg_watt }, + { .vdbl = sample->core_watt }, + { .vdbl = sample->gfx_watt }, + { .vdbl = sample->ram_watt }, + }; + gboolean r; + + r = sysprof_capture_writer_set_counters (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + sample->cpu, + -1, + counter_ids, + values, + G_N_ELEMENTS (values)); + + if (r == FALSE) + { + ipc_rapl_profiler_stop_locked (self); + return G_SOURCE_REMOVE; + } + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +ipc_rapl_profiler_handle_start (IpcProfiler *profiler, + GDBusMethodInvocation *invocation, + GVariant *arg_options, + GVariant *arg_fd) +{ + IpcRaplProfiler *self = (IpcRaplProfiler *)profiler; + g_autoptr(SysprofTurbostat) turbostat = NULL; + g_autoptr(GMutexLocker) locker = NULL; + g_autoptr(GError) error = NULL; + g_autofree gchar *path = NULL; + GDBusMessage *message; + GUnixFDList *in_fd_list = NULL; + gint fd = -1; + gint handle; + + g_assert (IPC_IS_RAPL_PROFILER (self)); + g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); + g_assert (arg_options != NULL); + g_assert (g_variant_is_of_type (arg_options, G_VARIANT_TYPE_VARDICT)); + g_assert (arg_fd != NULL); + g_assert (g_variant_is_of_type (arg_fd, G_VARIANT_TYPE_HANDLE)); + + if (!(path = g_find_program_in_path ("turbostat"))) + { + g_dbus_method_invocation_return_dbus_error (g_steal_pointer (&invocation), + "org.freedesktop.DBus.Error.Failed", + "Program `turbostat` is not available. Try installing kernel-tools."); + return TRUE; + } + + /* Get the FD for capture writing */ + message = g_dbus_method_invocation_get_message (invocation); + if ((in_fd_list = g_dbus_message_get_unix_fd_list (message)) && + (handle = g_variant_get_handle (arg_fd)) > -1) + fd = g_unix_fd_list_get (in_fd_list, handle, NULL); + + /* Make sure the FD is reasonable */ + if (fd < 0) + { + g_dbus_method_invocation_return_dbus_error (g_steal_pointer (&invocation), + "org.freedesktop.DBus.Error.Failed", + "Invalid param 'fd'"); + return TRUE; + } + + locker = g_mutex_locker_new (&self->mutex); + + if (self->turbostat != NULL) + { + close (fd); + g_dbus_method_invocation_return_dbus_error (g_steal_pointer (&invocation), + "org.freedesktop.DBus.Error.Failed", + "RAPL collector already running"); + return TRUE; + } + + turbostat = sysprof_turbostat_new (); + + if (!sysprof_turbostat_start (turbostat, &error)) + { + close (fd); + g_dbus_method_invocation_return_dbus_error (g_steal_pointer (&invocation), + "org.freedesktop.DBus.Error.Failed", + error->message); + return TRUE; + } + + /* A small buffer size is fine for our use case. */ + self->writer = sysprof_capture_writer_new_from_fd (fd, 4096); + self->counter_ids = g_array_new (FALSE, FALSE, sizeof (CounterId)); + + self->turbostat = g_steal_pointer (&turbostat); + self->poll_source = g_timeout_add_seconds (DEFAULT_POLL_FREQ_SECONDS, + ipc_rapl_profiler_poll_cb, + self); + + ipc_profiler_complete_start (profiler, g_steal_pointer (&invocation)); + + return TRUE; +} + +static gboolean +ipc_rapl_profiler_handle_stop (IpcProfiler *profiler, + GDBusMethodInvocation *invocation) +{ + IpcRaplProfiler *self = (IpcRaplProfiler *)profiler; + g_autoptr(GMutexLocker) locker = NULL; + + g_assert (IPC_IS_RAPL_PROFILER (self)); + g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); + + locker = g_mutex_locker_new (&self->mutex); + + ipc_rapl_profiler_stop_locked (self); + + ipc_profiler_complete_stop (profiler, g_steal_pointer (&invocation)); + + return TRUE; +} + +static void +profiler_iface_init (IpcProfilerIface *iface) +{ + iface->handle_start = ipc_rapl_profiler_handle_start; + iface->handle_stop = ipc_rapl_profiler_handle_stop; +} diff --git a/src/sysprofd/ipc-rapl-profiler.h b/src/sysprofd/ipc-rapl-profiler.h new file mode 100644 index 00000000..a56fb65d --- /dev/null +++ b/src/sysprofd/ipc-rapl-profiler.h @@ -0,0 +1,33 @@ +/* ipc-rapl-profiler.h + * + * Copyright 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 + */ + +#pragma once + +#include "ipc-profiler.h" + +G_BEGIN_DECLS + +#define IPC_TYPE_RAPL_PROFILER (ipc_rapl_profiler_get_type()) + +G_DECLARE_FINAL_TYPE (IpcRaplProfiler, ipc_rapl_profiler, IPC, RAPL_PROFILER, IpcProfilerSkeleton) + +IpcProfiler *ipc_rapl_profiler_new (void); + +G_END_DECLS diff --git a/src/sysprofd/meson.build b/src/sysprofd/meson.build index f605c265..bf1a652d 100644 --- a/src/sysprofd/meson.build +++ b/src/sysprofd/meson.build @@ -4,9 +4,12 @@ sysprofd_sources = [ '../libsysprof/sysprof-kallsyms.c', 'sysprofd.c', 'ipc-legacy-impl.c', + 'ipc-rapl-profiler.c', 'ipc-service-impl.c', + 'sysprof-turbostat.c', helpers_sources, ipc_legacy_src, + ipc_profiler_src, ipc_service_src, ] @@ -17,6 +20,7 @@ sysprofd_deps = [ gio_dep, gio_unix_dep, polkit_dep, + libsysprof_capture_dep, ] sysprofd = executable('sysprofd', sysprofd_sources, diff --git a/src/sysprofd/sysprof-turbostat.c b/src/sysprofd/sysprof-turbostat.c new file mode 100644 index 00000000..c84758b1 --- /dev/null +++ b/src/sysprofd/sysprof-turbostat.c @@ -0,0 +1,427 @@ +/* sysprof-turbostat.c + * + * Copyright 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-ntifier: GPL-3.0-or-later + */ + +#define _GNU_SOURCE + +#include "sysprof-turbostat.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +# include +#endif + +#define PTY_FD_INVALID (-1) + +typedef int PtyFd; + +struct _SysprofTurbostat +{ + GPid pid; + GIOChannel *stdin; + GIOChannel *stdout; +}; + +enum { + KIND_0, + KIND_DOUBLE, + KIND_INT, +}; + +static inline PtyFd +pty_fd_steal (PtyFd *fd) +{ + PtyFd ret = *fd; + *fd = -1; + return ret; +} + +static void +pty_fd_clear (PtyFd *fd) +{ + if (fd != NULL && *fd != -1) + { + int rfd = *fd; + *fd = -1; + close (rfd); + } +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (PtyFd, pty_fd_clear) + +PtyFd +pty_create_slave (PtyFd master_fd, + gboolean blocking) +{ + g_auto(PtyFd) ret = PTY_FD_INVALID; + gint extra = blocking ? 0 : O_NONBLOCK; +#if defined(HAVE_PTSNAME_R) || defined(__FreeBSD__) + char name[256]; +#else + const char *name; +#endif + + g_assert (master_fd != -1); + + if (grantpt (master_fd) != 0) + return PTY_FD_INVALID; + + if (unlockpt (master_fd) != 0) + return PTY_FD_INVALID; + +#ifdef HAVE_PTSNAME_R + if (ptsname_r (master_fd, name, sizeof name - 1) != 0) + return PTY_FD_INVALID; + name[sizeof name - 1] = '\0'; +#elif defined(__FreeBSD__) + if (fdevname_r (master_fd, name + 5, sizeof name - 6) == NULL) + return PTY_FD_INVALID; + memcpy (name, "/dev/", 5); + name[sizeof name - 1] = '\0'; +#else + if (NULL == (name = ptsname (master_fd))) + return PTY_FD_INVALID; +#endif + + ret = open (name, O_NOCTTY | O_RDWR | O_CLOEXEC | extra); + + if (ret == PTY_FD_INVALID && errno == EINVAL) + { + gint flags; + + ret = open (name, O_NOCTTY | O_RDWR | O_CLOEXEC); + if (ret == PTY_FD_INVALID && errno == EINVAL) + ret = open (name, O_NOCTTY | O_RDWR); + + if (ret == PTY_FD_INVALID) + return PTY_FD_INVALID; + + /* Add FD_CLOEXEC if O_CLOEXEC failed */ + flags = fcntl (ret, F_GETFD, 0); + if ((flags & FD_CLOEXEC) == 0) + { + if (fcntl (ret, F_SETFD, flags | FD_CLOEXEC) < 0) + return PTY_FD_INVALID; + } + + if (!blocking) + { + if (!g_unix_set_fd_nonblocking (ret, TRUE, NULL)) + return PTY_FD_INVALID; + } + } + + return pty_fd_steal (&ret); +} + +PtyFd +pty_create_master (void) +{ + g_auto(PtyFd) master_fd = PTY_FD_INVALID; + + master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC); + +#ifndef __linux__ + /* Fallback for operating systems that don't support + * O_NONBLOCK and O_CLOEXEC when opening. + */ + if (master_fd == PTY_FD_INVALID && errno == EINVAL) + { + master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_CLOEXEC); + + if (master_fd == PTY_FD_INVALID && errno == EINVAL) + { + gint flags; + + master_fd = posix_openpt (O_RDWR | O_NOCTTY); + if (master_fd == -1) + return PTY_FD_INVALID; + + flags = fcntl (master_fd, F_GETFD, 0); + if (flags < 0) + return PTY_FD_INVALID; + + if (fcntl (master_fd, F_SETFD, flags | FD_CLOEXEC) < 0) + return PTY_FD_INVALID; + } + + if (!g_unix_set_fd_nonblocking (master_fd, TRUE, NULL)) + return PTY_FD_INVALID; + } +#endif + + return pty_fd_steal (&master_fd); +} + +SysprofTurbostat * +sysprof_turbostat_new (void) +{ + SysprofTurbostat *self; + + self = g_rc_box_new0 (SysprofTurbostat); + self->stdin = NULL; + self->stdout = NULL; + self->pid = 0; + + return g_steal_pointer (&self); +} + +static void +sysprof_turbostat_finalize (gpointer data) +{ + SysprofTurbostat *self = data; + + if (self->pid != 0) + sysprof_turbostat_stop (self); + + g_assert (self->pid == 0); + g_assert (self->stdin == NULL); + g_assert (self->stdout == NULL); +} + +void +sysprof_turbostat_free (SysprofTurbostat *self) +{ + g_rc_box_release_full (self, sysprof_turbostat_finalize); +} + +static void +child_setup_cb (gpointer data) +{ +#ifdef __linux__ + prctl (PR_SET_PDEATHSIG, SIGTERM); +#endif +} + +gboolean +sysprof_turbostat_start (SysprofTurbostat *self, + GError **error) +{ + /* We use a long interval and just send \n to force a sample */ + static const gchar *argv[] = { "turbostat", "-T", "Celcius", "-i", "100000", NULL }; + g_auto(GStrv) env = NULL; + g_auto(PtyFd) stdin_master = PTY_FD_INVALID; + g_auto(PtyFd) stdin_slave = PTY_FD_INVALID; + g_auto(PtyFd) stdout_read = PTY_FD_INVALID; + g_auto(PtyFd) stdout_write = PTY_FD_INVALID; + gint pipes[2]; + gboolean ret; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (self->pid == 0, FALSE); + g_return_val_if_fail (self->stdin == NULL, FALSE); + g_return_val_if_fail (self->stdout == NULL, FALSE); + + env = g_get_environ (); + env = g_environ_setenv (env, "LANG", "C", TRUE); + + if (-1 == (stdin_master = pty_create_master ()) || + -1 == (stdin_slave = pty_create_slave (stdin_master, FALSE)) || + 0 != pipe2 (pipes, O_CLOEXEC | O_NONBLOCK)) + { + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + return FALSE; + } + + stdout_read = pipes[0]; + stdout_write = pipes[1]; + + ret = g_spawn_async_with_fds (NULL, + (gchar **)argv, + env, + (G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL), + child_setup_cb, + NULL, + &self->pid, + stdin_slave, + stdout_write, + -1, + error); + + if (ret) + { + self->stdin = g_io_channel_unix_new (pty_fd_steal (&stdin_master)); + g_io_channel_set_close_on_unref (self->stdin, TRUE); + g_io_channel_set_buffer_size (self->stdin, 4096); + + self->stdout = g_io_channel_unix_new (pty_fd_steal (&stdout_read)); + g_io_channel_set_close_on_unref (self->stdout, TRUE); + g_io_channel_set_buffer_size (self->stdout, 4096); + g_io_channel_set_flags (self->stdout, G_IO_FLAG_NONBLOCK, NULL); + } + + return ret; +} + +void +sysprof_turbostat_stop (SysprofTurbostat *self) +{ + GPid pid; + + g_return_if_fail (self != NULL); + + if (self->pid == 0) + return; + + pid = self->pid; + self->pid = 0; + kill (pid, SIGTERM); + + g_clear_pointer (&self->stdin, g_io_channel_unref); + g_clear_pointer (&self->stdout, g_io_channel_unref); +} + +GArray * +sysprof_turbostat_sample (SysprofTurbostat *self, + GError **error) +{ + g_autoptr(GArray) ret = NULL; + g_auto(GStrv) columns = NULL; + g_autoptr(GString) str = NULL; + GIOStatus r; + gint lineno = 0; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (self->stdin != NULL, NULL); + g_return_val_if_fail (self->stdout != NULL, NULL); + + r = g_io_channel_write_chars (self->stdin, "\n", 1, NULL, error) && + g_io_channel_flush (self->stdin, error); + if (r != G_IO_STATUS_NORMAL) + return NULL; + + /* Sleep for just a bit to wait for all results */ + g_usleep (G_USEC_PER_SEC * 0.01); + + ret = g_array_new (FALSE, FALSE, sizeof (SysprofTurbostatSample)); + str = g_string_new (NULL); + + for (;;) + { + SysprofTurbostatSample sample = {0}; + g_auto(GStrv) parts = NULL; + gsize pos = 0; + + lineno++; + + r = g_io_channel_read_line_string (self->stdout, str, &pos, NULL); + if (r != G_IO_STATUS_NORMAL || str->len == 0 || pos == 0) + break; + + g_string_truncate (str, pos - 1); + + parts = g_strsplit (str->str, "\t", 0); + + if (lineno == 1) + { + columns = g_steal_pointer (&parts); + continue; + } + + g_return_val_if_fail (columns != NULL, NULL); + + for (guint i = 0; columns[i] != NULL && parts[i] != NULL; i++) + { + gdouble *addr = NULL; + gint *iaddr = NULL; + int kind = KIND_0; + + if (g_strcmp0 (columns[i], "GFXWatt") == 0) + { + addr = &sample.gfx_watt; + kind = KIND_DOUBLE; + } + else if (g_strcmp0 (columns[i], "CorWatt") == 0) + { + addr = &sample.core_watt; + kind = KIND_DOUBLE; + } + else if (g_strcmp0 (columns[i], "PkgWatt") == 0) + { + addr = &sample.pkg_watt; + kind = KIND_DOUBLE; + } + else if (g_strcmp0 (columns[i], "RAMWatt") == 0) + { + addr = &sample.ram_watt; + kind = KIND_DOUBLE; + } + else if (g_strcmp0 (columns[i], "Core") == 0) + { + iaddr = &sample.core; + kind = KIND_INT; + } + else if (g_strcmp0 (columns[i], "CPU") == 0) + { + iaddr = &sample.cpu; + kind = KIND_INT; + } + + if (kind == KIND_0) + continue; + + g_assert ((kind == KIND_DOUBLE && addr != NULL) || + (kind == KIND_INT && iaddr != NULL)); + + if (kind == KIND_DOUBLE) + { + *addr = g_ascii_strtod (parts[i], NULL); + + /* Reset to zero if we failed to parse */ + if (*addr == HUGE_VAL && errno == ERANGE) + *addr = 0.0; + } + else if (kind == KIND_INT) + { + /* Some columns are "-" and we use -1 to indicate that. */ + if (parts[i][0] == '-' && parts[i][1] == 0) + { + *iaddr = -1; + } + else + { + gint64 v = g_ascii_strtoll (parts[i], NULL, 10); + + /* Reset if we failed to parse */ + if ((v == G_MAXINT64 || v == G_MININT64) && errno == ERANGE) + v = 0; + + *iaddr = v; + } + } + } + + g_array_append_val (ret, sample); + } + + return g_steal_pointer (&ret); +} diff --git a/src/sysprofd/sysprof-turbostat.h b/src/sysprofd/sysprof-turbostat.h new file mode 100644 index 00000000..f988e575 --- /dev/null +++ b/src/sysprofd/sysprof-turbostat.h @@ -0,0 +1,49 @@ +/* sysprof-turbostat.h + * + * Copyright 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 + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef struct _SysprofTurbostat SysprofTurbostat; + +typedef struct +{ + gint core; + gint cpu; + gdouble pkg_watt; + gdouble core_watt; + gdouble gfx_watt; + gdouble ram_watt; +} SysprofTurbostatSample; + +SysprofTurbostat *sysprof_turbostat_new (void); +gboolean sysprof_turbostat_start (SysprofTurbostat *self, + GError **error); +void sysprof_turbostat_stop (SysprofTurbostat *self); +GArray *sysprof_turbostat_sample (SysprofTurbostat *self, + GError **error); +void sysprof_turbostat_free (SysprofTurbostat *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofTurbostat, sysprof_turbostat_free) + +G_END_DECLS diff --git a/src/sysprofd/sysprofd.c b/src/sysprofd/sysprofd.c index e084a17d..12fc1c24 100644 --- a/src/sysprofd/sysprofd.c +++ b/src/sysprofd/sysprofd.c @@ -24,15 +24,18 @@ #include #include +#include #include "ipc-legacy.h" #include "ipc-service.h" #include "ipc-legacy-impl.h" +#include "ipc-rapl-profiler.h" #include "ipc-service-impl.h" #define V2_PATH "/org/gnome/Sysprof2" #define V3_PATH "/org/gnome/Sysprof3" +#define RAPL_PATH "/org/gnome/Sysprof3/RAPL" #define NAME_ACQUIRE_DELAY_SECS 3 #define INACTIVITY_TIMEOUT_SECS 120 @@ -115,6 +118,8 @@ main (gint argc, g_autoptr(GError) error = NULL; GBusType bus_type = G_BUS_TYPE_SYSTEM; + sysprof_clock_init (); + g_set_prgname ("sysprofd"); g_set_application_name ("sysprofd"); @@ -123,15 +128,18 @@ main (gint argc, if ((bus = g_bus_get_sync (bus_type, NULL, &error))) { g_autoptr(IpcLegacySysprof2) v2_service = ipc_legacy_impl_new (); + g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new (); g_autoptr(IpcService) v3_service = ipc_service_impl_new (); g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL); g_signal_connect (v2_service, "activity", G_CALLBACK (activity_cb), NULL); + g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL); activity_cb (NULL, NULL); if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) && - g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v2_service), bus, V2_PATH, &error)) + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v2_service), bus, V2_PATH, &error) && + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error)) { for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++) {