mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
557 lines
14 KiB
C
557 lines
14 KiB
C
/* helpers.c
|
|
*
|
|
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "helpers"
|
|
|
|
#include "config.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <gio/gio.h>
|
|
#include <errno.h>
|
|
#ifdef __linux__
|
|
# include <linux/perf_event.h>
|
|
# include <sys/syscall.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "helpers.h"
|
|
|
|
#ifdef __linux__
|
|
static gboolean
|
|
linux_list_processes (gint32 **processes,
|
|
gsize *n_processes)
|
|
{
|
|
g_autoptr(GDir) dir = NULL;
|
|
g_autoptr(GArray) pids = NULL;
|
|
const gchar *name;
|
|
|
|
if (!(dir = g_dir_open ("/proc/", 0, NULL)))
|
|
return FALSE;
|
|
|
|
pids = g_array_new (FALSE, FALSE, sizeof (gint32));
|
|
|
|
while ((name = g_dir_read_name (dir)))
|
|
{
|
|
if (g_ascii_isalnum (*name))
|
|
{
|
|
gchar *endptr = NULL;
|
|
gint64 val = g_ascii_strtoll (name, &endptr, 10);
|
|
|
|
if (endptr != NULL && *endptr == 0 && val < G_MAXINT && val >= 0)
|
|
{
|
|
gint32 v32 = val;
|
|
g_array_append_val (pids, v32);
|
|
}
|
|
}
|
|
}
|
|
|
|
*n_processes = pids->len;
|
|
*processes = (gint32 *)(gpointer)g_array_free (g_steal_pointer (&pids), FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
gboolean
|
|
helpers_list_processes (gint32 **processes,
|
|
gsize *n_processes)
|
|
{
|
|
g_return_val_if_fail (processes != NULL, FALSE);
|
|
g_return_val_if_fail (n_processes != NULL, FALSE);
|
|
|
|
*processes = NULL;
|
|
*n_processes = 0;
|
|
|
|
#ifdef __linux__
|
|
return linux_list_processes (processes, n_processes);
|
|
#else
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
#ifdef __linux__
|
|
static int
|
|
_perf_event_open (struct perf_event_attr *attr,
|
|
pid_t pid,
|
|
int cpu,
|
|
int group_fd,
|
|
unsigned long flags)
|
|
{
|
|
g_assert (attr != NULL);
|
|
|
|
/* Quick sanity check */
|
|
if (attr->sample_period < 100000 && attr->type != PERF_TYPE_TRACEPOINT)
|
|
return -EINVAL;
|
|
|
|
return syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
|
|
}
|
|
#endif
|
|
|
|
gboolean
|
|
helpers_perf_event_open (GVariant *options,
|
|
gint32 pid,
|
|
gint32 cpu,
|
|
gint group_fd,
|
|
guint64 flags,
|
|
gint *out_fd)
|
|
{
|
|
#ifndef __linux__
|
|
*out_fd = -1;
|
|
return FALSE;
|
|
#else
|
|
struct perf_event_attr attr = {0};
|
|
GVariantIter iter;
|
|
GVariant *value;
|
|
gchar *key;
|
|
gint32 disabled = 0;
|
|
gint32 wakeup_events = 149;
|
|
gint32 type = 0;
|
|
guint64 sample_period = 0;
|
|
guint64 sample_type = 0;
|
|
guint64 config = 0;
|
|
gint clockid = CLOCK_MONOTONIC;
|
|
gint comm = 0;
|
|
gint mmap_ = 0;
|
|
gint task = 0;
|
|
gint exclude_idle = 0;
|
|
gint use_clockid = 0;
|
|
gint sample_id_all = 0;
|
|
|
|
g_assert (out_fd != NULL);
|
|
|
|
*out_fd = -1;
|
|
|
|
g_variant_iter_init (&iter, options);
|
|
|
|
while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
|
|
{
|
|
if (FALSE) {}
|
|
else if (strcmp (key, "disabled") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
disabled = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "wakeup_events") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
|
|
goto bad_arg;
|
|
wakeup_events = g_variant_get_uint32 (value);
|
|
}
|
|
else if (strcmp (key, "sample_id_all") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
sample_id_all = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "clockid") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_INT32))
|
|
goto bad_arg;
|
|
clockid = g_variant_get_int32 (value);
|
|
}
|
|
else if (strcmp (key, "comm") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
comm = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "exclude_idle") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
exclude_idle = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "mmap") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
mmap_ = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "config") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64))
|
|
goto bad_arg;
|
|
config = g_variant_get_uint64 (value);
|
|
}
|
|
else if (strcmp (key, "sample_period") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64))
|
|
goto bad_arg;
|
|
sample_period = g_variant_get_uint64 (value);
|
|
}
|
|
else if (strcmp (key, "sample_type") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64))
|
|
goto bad_arg;
|
|
sample_type = g_variant_get_uint64 (value);
|
|
}
|
|
else if (strcmp (key, "task") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
task = g_variant_get_boolean (value);
|
|
}
|
|
else if (strcmp (key, "type") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
|
|
goto bad_arg;
|
|
type = g_variant_get_uint32 (value);
|
|
}
|
|
else if (strcmp (key, "use_clockid") == 0)
|
|
{
|
|
if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
|
|
goto bad_arg;
|
|
use_clockid = g_variant_get_boolean (value);
|
|
}
|
|
|
|
continue;
|
|
|
|
bad_arg:
|
|
errno = EINVAL;
|
|
return FALSE;
|
|
}
|
|
|
|
attr.comm = !!comm;
|
|
attr.config = config;
|
|
attr.disabled = disabled;
|
|
attr.exclude_idle = !!exclude_idle;
|
|
attr.mmap = !!mmap_;
|
|
attr.sample_id_all = sample_id_all;
|
|
attr.sample_period = sample_period;
|
|
attr.sample_type = sample_type;
|
|
attr.task = !!task;
|
|
attr.type = type;
|
|
attr.wakeup_events = wakeup_events;
|
|
|
|
#ifdef HAVE_PERF_CLOCKID
|
|
if (!use_clockid || clockid < 0)
|
|
attr.clockid = CLOCK_MONOTONIC;
|
|
else
|
|
attr.clockid = clockid;
|
|
attr.use_clockid = use_clockid;
|
|
#endif
|
|
|
|
attr.size = sizeof attr;
|
|
|
|
errno = 0;
|
|
*out_fd = _perf_event_open (&attr, pid, cpu, group_fd, flags);
|
|
return *out_fd >= 0;
|
|
#endif
|
|
}
|
|
|
|
gboolean
|
|
helpers_get_proc_file (const gchar *path,
|
|
gchar **contents,
|
|
gsize *len)
|
|
{
|
|
g_autofree gchar *canon = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
g_assert (path != NULL);
|
|
g_assert (contents != NULL);
|
|
g_assert (len != NULL);
|
|
|
|
*contents = NULL;
|
|
*len = 0;
|
|
|
|
/* Canonicalize filename */
|
|
file = g_file_new_for_path (path);
|
|
canon = g_file_get_path (file);
|
|
|
|
return g_file_is_native (file) &&
|
|
(g_str_has_prefix (canon, "/proc/") || g_str_has_prefix (canon, "/sys/")) &&
|
|
g_file_get_contents (canon, contents, len, NULL);
|
|
}
|
|
|
|
gboolean
|
|
helpers_get_proc_fd (const gchar *path,
|
|
gint *out_fd)
|
|
{
|
|
g_autofree gchar *canon = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
g_assert (path != NULL);
|
|
g_assert (out_fd != NULL);
|
|
|
|
/* Canonicalize filename */
|
|
file = g_file_new_for_path (path);
|
|
canon = g_file_get_path (file);
|
|
|
|
return g_file_is_native (file) &&
|
|
(g_str_has_prefix (canon, "/proc/") || g_str_has_prefix (canon, "/sys/")) &&
|
|
-1 != (*out_fd = open (canon, O_RDONLY | O_CLOEXEC));
|
|
}
|
|
|
|
gboolean
|
|
helpers_can_see_pids (void)
|
|
{
|
|
g_autofree gchar *contents = NULL;
|
|
gsize len = 0;
|
|
|
|
if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS))
|
|
return FALSE;
|
|
|
|
if (helpers_get_proc_file ("/proc/mounts", &contents, &len))
|
|
{
|
|
g_auto(GStrv) lines = g_strsplit (contents, "\n", 0);
|
|
|
|
for (guint i = 0; lines[i]; i++)
|
|
{
|
|
if (!g_str_has_prefix (lines[i], "proc /proc "))
|
|
continue;
|
|
|
|
if (strstr (lines[i], "hidepid=") && !strstr (lines[i], "hidepid=0"))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
helpers_list_processes_worker (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autofree gint32 *processes = NULL;
|
|
gsize n_processes;
|
|
|
|
g_assert (G_IS_TASK (task));
|
|
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
if (helpers_list_processes (&processes, &n_processes))
|
|
{
|
|
GArray *ar;
|
|
|
|
ar = g_array_new (FALSE, FALSE, sizeof (gint32));
|
|
g_array_append_vals (ar, processes, n_processes);
|
|
g_task_return_pointer (task,
|
|
g_steal_pointer (&ar),
|
|
(GDestroyNotify) g_array_unref);
|
|
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to list processes");
|
|
}
|
|
|
|
void
|
|
helpers_list_processes_async (GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(GTask) task = NULL;
|
|
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
task = g_task_new (NULL, cancellable, callback, user_data);
|
|
g_task_set_source_tag (task, helpers_list_processes_async);
|
|
g_task_run_in_thread (task, helpers_list_processes_worker);
|
|
}
|
|
|
|
gboolean
|
|
helpers_list_processes_finish (GAsyncResult *result,
|
|
gint32 **processes,
|
|
gsize *n_processes,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GArray) ret = NULL;
|
|
|
|
g_return_val_if_fail (G_IS_TASK (result), FALSE);
|
|
|
|
if ((ret = g_task_propagate_pointer (G_TASK (result), error)))
|
|
{
|
|
if (n_processes)
|
|
*n_processes = ret->len;
|
|
|
|
if (processes)
|
|
*processes = (gint32 *)(gpointer)g_array_free (ret, FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (processes)
|
|
*processes = NULL;
|
|
if (n_processes)
|
|
*n_processes = 0;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
needs_escape (const gchar *str)
|
|
{
|
|
for (; *str; str++)
|
|
{
|
|
if (g_ascii_isspace (*str) || *str == '\'' || *str == '"')
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
postprocess_cmdline (gchar **str,
|
|
gsize len)
|
|
{
|
|
g_autoptr(GPtrArray) parts = g_ptr_array_new_with_free_func (g_free);
|
|
g_autofree gchar *instr = NULL;
|
|
const gchar *begin = NULL;
|
|
|
|
if (len == 0)
|
|
return;
|
|
|
|
instr = *str;
|
|
|
|
for (gsize i = 0; i < len; i++)
|
|
{
|
|
if (!begin && instr[i])
|
|
{
|
|
begin = &instr[i];
|
|
}
|
|
else if (begin && instr[i] == '\0')
|
|
{
|
|
if (needs_escape (begin))
|
|
g_ptr_array_add (parts, g_shell_quote (begin));
|
|
else
|
|
g_ptr_array_add (parts, g_strdup (begin));
|
|
|
|
begin = NULL;
|
|
}
|
|
}
|
|
|
|
/* If the last byte was not \0, as can happen with prctl(), then we need
|
|
* to add it here manually.
|
|
*/
|
|
if (begin)
|
|
{
|
|
if (needs_escape (begin))
|
|
g_ptr_array_add (parts, g_shell_quote (begin));
|
|
else
|
|
g_ptr_array_add (parts, g_strdup (begin));
|
|
}
|
|
|
|
g_ptr_array_add (parts, NULL);
|
|
|
|
*str = g_strjoinv (" ", (gchar **)parts->pdata);
|
|
}
|
|
|
|
static void
|
|
postprocess_rstrip (gchar **str,
|
|
gsize len)
|
|
{
|
|
g_strchomp (*str);
|
|
}
|
|
|
|
static void
|
|
add_pid_proc_file_to (gint pid,
|
|
const gchar *name,
|
|
GVariantDict *dict,
|
|
void (*postprocess) (gchar **, gsize))
|
|
{
|
|
g_autofree gchar *path = NULL;
|
|
g_autofree gchar *contents = NULL;
|
|
gsize len;
|
|
|
|
g_assert (pid > -1);
|
|
g_assert (name != NULL);
|
|
g_assert (dict != NULL);
|
|
|
|
path = g_strdup_printf ("/proc/%d/%s", pid, name);
|
|
|
|
if (g_file_get_contents (path, &contents, &len, NULL))
|
|
{
|
|
if (postprocess)
|
|
postprocess (&contents, len);
|
|
g_variant_dict_insert (dict, name, "s", contents);
|
|
}
|
|
}
|
|
|
|
|
|
GVariant *
|
|
helpers_get_process_info (const gchar *attributes)
|
|
{
|
|
GVariantBuilder builder;
|
|
g_autofree gint *processes = NULL;
|
|
gsize n_processes = 0;
|
|
gboolean want_statm;
|
|
gboolean want_cmdline;
|
|
gboolean want_comm;
|
|
gboolean want_maps;
|
|
gboolean want_mountinfo;
|
|
gboolean want_cgroup;
|
|
|
|
if (attributes == NULL)
|
|
attributes = "";
|
|
|
|
want_statm = !!strstr (attributes, "statm");
|
|
want_cmdline = !!strstr (attributes, "cmdline");
|
|
want_maps = !!strstr (attributes, "maps");
|
|
want_mountinfo = !!strstr (attributes, "mountinfo");
|
|
want_comm = !!strstr (attributes, "comm");
|
|
want_cgroup = !!strstr (attributes, "cgroup");
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
|
|
|
|
if (helpers_list_processes (&processes, &n_processes))
|
|
{
|
|
for (guint i = 0; i < n_processes; i++)
|
|
{
|
|
gint pid = processes[i];
|
|
GVariantDict dict;
|
|
|
|
g_variant_dict_init (&dict, NULL);
|
|
g_variant_dict_insert (&dict, "pid", "i", pid, NULL);
|
|
|
|
if (want_statm)
|
|
add_pid_proc_file_to (pid, "statm", &dict, postprocess_rstrip);
|
|
|
|
if (want_cmdline)
|
|
add_pid_proc_file_to (pid, "cmdline", &dict, postprocess_cmdline);
|
|
|
|
if (want_comm)
|
|
add_pid_proc_file_to (pid, "comm", &dict, postprocess_rstrip);
|
|
|
|
if (want_maps)
|
|
add_pid_proc_file_to (pid, "maps", &dict, postprocess_rstrip);
|
|
|
|
if (want_mountinfo)
|
|
add_pid_proc_file_to (pid, "mountinfo", &dict, postprocess_rstrip);
|
|
|
|
if (want_cgroup)
|
|
add_pid_proc_file_to (pid, "cgroup", &dict, postprocess_rstrip);
|
|
|
|
g_variant_builder_add_value (&builder, g_variant_dict_end (&dict));
|
|
}
|
|
}
|
|
|
|
return g_variant_take_ref (g_variant_builder_end (&builder));
|
|
}
|