mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
source tree cleanup
The lib/ directory was getting a bit out of hand, so this tries to organize things a bit so it is easier going forward to locate the code people want to patch.
This commit is contained in:
245
lib/sources/sp-gjs-source.c
Normal file
245
lib/sources/sp-gjs-source.c
Normal file
@ -0,0 +1,245 @@
|
||||
/* sp-gjs-source.c
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "capture/sp-capture-reader.h"
|
||||
#include "sources/sp-gjs-source.h"
|
||||
|
||||
struct _SpGjsSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *pids;
|
||||
GArray *enabled;
|
||||
};
|
||||
|
||||
#define ENABLE_PROFILER 0x1
|
||||
#define DISABLE_PROFILER 0x0
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpGjsSource, sp_gjs_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static void
|
||||
sp_gjs_source_finalize (GObject *object)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)object;
|
||||
|
||||
g_clear_pointer (&self->pids, g_array_unref);
|
||||
g_clear_pointer (&self->enabled, g_array_unref);
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_gjs_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_class_init (SpGjsSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_gjs_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_init (SpGjsSource *self)
|
||||
{
|
||||
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
self->enabled = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_capture (SpGjsSource *self,
|
||||
GPid pid,
|
||||
const gchar *path)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(SpCaptureReader) reader = NULL;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
g_assert (path != NULL);
|
||||
|
||||
if (!(reader = sp_capture_reader_new (path, &error)))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sp_capture_reader_splice (reader, self->writer, &error))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_captures (SpGjsSource *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
for (i = 0; i < self->enabled->len; i++)
|
||||
{
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
GPid pid = g_array_index (self->enabled, GPid, i);
|
||||
|
||||
filename = g_strdup_printf ("gjs-profile-%u", (guint)pid);
|
||||
path = g_build_filename (g_get_tmp_dir (), filename, NULL);
|
||||
|
||||
sp_gjs_source_process_capture (self, pid, path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pid_is_profileable (GPid pid)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autofree gchar *contents = NULL;
|
||||
const gchar *libgjs;
|
||||
gsize len = 0;
|
||||
|
||||
g_assert (pid != -1);
|
||||
|
||||
/*
|
||||
* Make sure this process has linked in libgjs. No sense in sending it a
|
||||
* signal unless we know it can handle it.
|
||||
*/
|
||||
|
||||
path = g_strdup_printf ("/proc/%d/maps", pid);
|
||||
if (!g_file_get_contents (path, &contents, &len, NULL))
|
||||
return FALSE;
|
||||
|
||||
if (NULL == (libgjs = strstr (contents, "libgjs."G_MODULE_SUFFIX)))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_enable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = ENABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
else
|
||||
g_array_append_val (self->enabled, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_disable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = DISABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_start (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_enable_pid (self, pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_stop (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_disable_pid (self, pid);
|
||||
}
|
||||
|
||||
sp_gjs_source_process_captures (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid > -1);
|
||||
|
||||
g_array_append_val (self->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_gjs_source_set_writer;
|
||||
iface->start = sp_gjs_source_start;
|
||||
iface->stop = sp_gjs_source_stop;
|
||||
iface->add_pid = sp_gjs_source_add_pid;
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_gjs_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_GJS_SOURCE, NULL);
|
||||
}
|
||||
34
lib/sources/sp-gjs-source.h
Normal file
34
lib/sources/sp-gjs-source.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-gjs-source.h
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_GJS_SOURCE_H
|
||||
#define SP_GJS_SOURCE_H
|
||||
|
||||
#include "sources/sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_GJS_SOURCE (sp_gjs_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpGjsSource, sp_gjs_source, SP, GJS_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_gjs_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_GJS_SOURCE_H */
|
||||
343
lib/sources/sp-hostinfo-source.c
Normal file
343
lib/sources/sp-hostinfo-source.c
Normal file
@ -0,0 +1,343 @@
|
||||
/* sp-hostinfo-source.c
|
||||
*
|
||||
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sources/sp-hostinfo-source.h"
|
||||
|
||||
struct _SpHostinfoSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
guint handler;
|
||||
gint n_cpu;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *cpu_info;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint counter_base;
|
||||
gdouble total;
|
||||
gdouble freq;
|
||||
glong last_user;
|
||||
glong last_idle;
|
||||
glong last_system;
|
||||
glong last_nice;
|
||||
glong last_iowait;
|
||||
glong last_irq;
|
||||
glong last_softirq;
|
||||
glong last_steal;
|
||||
glong last_guest;
|
||||
glong last_guest_nice;
|
||||
} CpuInfo;
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpHostinfoSource, sp_hostinfo_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
SpSource *
|
||||
sp_hostinfo_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_HOSTINFO_SOURCE, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
poll_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
gchar cpu[64] = { 0 };
|
||||
glong user;
|
||||
glong sys;
|
||||
glong nice;
|
||||
glong idle;
|
||||
glong iowait;
|
||||
glong irq;
|
||||
glong softirq;
|
||||
glong steal;
|
||||
glong guest;
|
||||
glong guest_nice;
|
||||
glong user_calc;
|
||||
glong system_calc;
|
||||
glong nice_calc;
|
||||
glong idle_calc;
|
||||
glong iowait_calc;
|
||||
glong irq_calc;
|
||||
glong softirq_calc;
|
||||
glong steal_calc;
|
||||
glong guest_calc;
|
||||
glong guest_nice_calc;
|
||||
gchar *buf = NULL;
|
||||
glong total;
|
||||
gchar *line;
|
||||
gint ret;
|
||||
gint id;
|
||||
gint i;
|
||||
|
||||
if (g_file_get_contents("/proc/stat", &buf, NULL, NULL))
|
||||
{
|
||||
line = buf;
|
||||
for (i = 0; buf[i]; i++)
|
||||
{
|
||||
if (buf[i] == '\n') {
|
||||
buf[i] = '\0';
|
||||
if (g_str_has_prefix(line, "cpu"))
|
||||
{
|
||||
if (isdigit(line[3]))
|
||||
{
|
||||
CpuInfo *cpu_info;
|
||||
|
||||
user = nice = sys = idle = id = 0;
|
||||
ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
|
||||
cpu, &user, &nice, &sys, &idle,
|
||||
&iowait, &irq, &softirq, &steal, &guest, &guest_nice);
|
||||
if (ret != 11)
|
||||
goto next;
|
||||
|
||||
ret = sscanf(cpu, "cpu%d", &id);
|
||||
|
||||
if (ret != 1 || id < 0 || id >= self->n_cpu)
|
||||
goto next;
|
||||
|
||||
cpu_info = &g_array_index (self->cpu_info, CpuInfo, id);
|
||||
|
||||
user_calc = user - cpu_info->last_user;
|
||||
nice_calc = nice - cpu_info->last_nice;
|
||||
system_calc = sys - cpu_info->last_system;
|
||||
idle_calc = idle - cpu_info->last_idle;
|
||||
iowait_calc = iowait - cpu_info->last_iowait;
|
||||
irq_calc = irq - cpu_info->last_irq;
|
||||
softirq_calc = softirq - cpu_info->last_softirq;
|
||||
steal_calc = steal - cpu_info->last_steal;
|
||||
guest_calc = guest - cpu_info->last_guest;
|
||||
guest_nice_calc = guest_nice - cpu_info->last_guest_nice;
|
||||
|
||||
total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc;
|
||||
cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0;
|
||||
|
||||
cpu_info->last_user = user;
|
||||
cpu_info->last_nice = nice;
|
||||
cpu_info->last_idle = idle;
|
||||
cpu_info->last_system = sys;
|
||||
cpu_info->last_iowait = iowait;
|
||||
cpu_info->last_irq = irq;
|
||||
cpu_info->last_softirq = softirq;
|
||||
cpu_info->last_steal = steal;
|
||||
cpu_info->last_guest = guest;
|
||||
cpu_info->last_guest_nice = guest_nice;
|
||||
}
|
||||
} else {
|
||||
/* CPU info comes first. Skip further lines. */
|
||||
break;
|
||||
}
|
||||
|
||||
next:
|
||||
line = &buf[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_free (buf);
|
||||
}
|
||||
|
||||
static void
|
||||
publish_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
SpCaptureCounterValue *counter_values;
|
||||
guint *counter_ids;
|
||||
gint i;
|
||||
|
||||
counter_ids = alloca (sizeof *counter_ids * self->n_cpu * 2);
|
||||
counter_values = alloca (sizeof *counter_values * self->n_cpu * 2);
|
||||
|
||||
for (i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
CpuInfo *info = &g_array_index (self->cpu_info, CpuInfo, i);
|
||||
SpCaptureCounterValue *value = &counter_values[i*2];
|
||||
guint *id = &counter_ids[i*2];
|
||||
|
||||
*id = info->counter_base;
|
||||
value->vdbl = info->total;
|
||||
|
||||
id++;
|
||||
value++;
|
||||
|
||||
*id = info->counter_base + 1;
|
||||
value->vdbl = info->freq;
|
||||
}
|
||||
|
||||
sp_capture_writer_set_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
getpid (),
|
||||
-1,
|
||||
counter_ids,
|
||||
counter_values,
|
||||
self->n_cpu * 2);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_hostinfo_cb (gpointer data)
|
||||
{
|
||||
SpHostinfoSource *self = data;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
poll_cpu (self);
|
||||
publish_cpu (self);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_finalize (GObject *object)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)object;
|
||||
|
||||
if (self->handler)
|
||||
{
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->cpu_info, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_hostinfo_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_class_init (SpHostinfoSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_hostinfo_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_init (SpHostinfoSource *self)
|
||||
{
|
||||
self->cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_start (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
self->handler = g_timeout_add (250, collect_hostinfo_cb, self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_stop (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
|
||||
sp_source_emit_finished (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_prepare (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
SpCaptureCounter *counters;
|
||||
gint i;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
self->n_cpu = g_get_num_processors ();
|
||||
|
||||
g_array_set_size (self->cpu_info, 0);
|
||||
|
||||
counters = alloca (sizeof *counters * self->n_cpu * 2);
|
||||
|
||||
for (i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
SpCaptureCounter *ctr = &counters[i*2];
|
||||
CpuInfo info = { 0 };
|
||||
|
||||
/*
|
||||
* Request 2 counter values.
|
||||
* One for CPU and one for Frequency.
|
||||
*/
|
||||
info.counter_base = sp_capture_writer_request_counter (self->writer, 2);
|
||||
|
||||
/*
|
||||
* Define counters for capture file.
|
||||
*/
|
||||
ctr->id = info.counter_base;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Percent", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "Total CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Total CPU usage %d", i);
|
||||
|
||||
ctr++;
|
||||
|
||||
ctr->id = info.counter_base + 1;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Frequency", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Frequency of CPU %d", i);
|
||||
|
||||
g_array_append_val (self->cpu_info, info);
|
||||
}
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
getpid (),
|
||||
-1,
|
||||
counters,
|
||||
self->n_cpu * 2);
|
||||
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_hostinfo_source_set_writer;
|
||||
iface->prepare = sp_hostinfo_source_prepare;
|
||||
iface->start = sp_hostinfo_source_start;
|
||||
iface->stop = sp_hostinfo_source_stop;
|
||||
}
|
||||
35
lib/sources/sp-hostinfo-source.h
Normal file
35
lib/sources/sp-hostinfo-source.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* sp-hostinfo-source.h
|
||||
*
|
||||
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_HOSTINFO_SOURCE_H
|
||||
#define SP_HOSTINFO_SOURCE_H
|
||||
|
||||
#include "sources/sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_HOSTINFO_SOURCE (sp_hostinfo_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpHostinfoSource, sp_hostinfo_source, SP, HOSTINFO_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_hostinfo_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_HOSTINFO_SOURCE_H */
|
||||
|
||||
848
lib/sources/sp-perf-counter.c
Normal file
848
lib/sources/sp-perf-counter.c
Normal file
@ -0,0 +1,848 @@
|
||||
/* sp-perf-counter.c
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, Red Hat, Inc.
|
||||
* Copyright 2004, 2005, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
# include <polkit/polkit.h>
|
||||
#endif
|
||||
|
||||
#include "sources/sp-perf-counter.h"
|
||||
|
||||
/*
|
||||
* Number of pages to map for the ring buffer. We map one additional buffer
|
||||
* at the beginning for header information to communicate with perf.
|
||||
*/
|
||||
#define N_PAGES 32
|
||||
|
||||
/*
|
||||
* This represents a stream coming to us from perf. All SpPerfCounterInfo
|
||||
* share a single GSource used for watching incoming G_IO_IN requests.
|
||||
* The map is the mmap() zone we are using as a ring buffer for communicating
|
||||
* with perf. The rest is for managing the ring buffer.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
gint fd;
|
||||
gpointer fdtag;
|
||||
struct perf_event_mmap_page *map;
|
||||
guint8 *data;
|
||||
guint64 tail;
|
||||
gint cpu;
|
||||
guint in_callback : 1;
|
||||
} SpPerfCounterInfo;
|
||||
|
||||
struct _SpPerfCounter
|
||||
{
|
||||
volatile gint ref_count;
|
||||
|
||||
/*
|
||||
* If we are should currently be enabled. We allow calling
|
||||
* multiple times and disabling when we reach zero.
|
||||
*/
|
||||
guint enabled;
|
||||
|
||||
/*
|
||||
* Our main context and source for delivering callbacks.
|
||||
*/
|
||||
GMainContext *context;
|
||||
GSource *source;
|
||||
|
||||
/*
|
||||
* An array of SpPerfCounterInfo, indicating all of our open
|
||||
* perf stream.s
|
||||
*/
|
||||
GPtrArray *info;
|
||||
|
||||
/*
|
||||
* The callback to execute for every discovered perf event.
|
||||
*/
|
||||
SpPerfCounterCallback callback;
|
||||
gpointer callback_data;
|
||||
GDestroyNotify callback_data_destroy;
|
||||
|
||||
/*
|
||||
* The number of samples we've recorded.
|
||||
*/
|
||||
guint64 n_samples;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSource source;
|
||||
SpPerfCounter *counter;
|
||||
} PerfGSource;
|
||||
|
||||
G_DEFINE_BOXED_TYPE (SpPerfCounter,
|
||||
sp_perf_counter,
|
||||
(GBoxedCopyFunc)sp_perf_counter_ref,
|
||||
(GBoxedFreeFunc)sp_perf_counter_unref)
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
static GDBusConnection *shared_conn;
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
perf_gsource_dispatch (GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
return callback ? callback (user_data) : G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static GSourceFuncs source_funcs = {
|
||||
NULL, NULL, perf_gsource_dispatch, NULL
|
||||
};
|
||||
|
||||
static void
|
||||
sp_perf_counter_info_free (SpPerfCounterInfo *info)
|
||||
{
|
||||
if (info->map)
|
||||
{
|
||||
gsize map_size;
|
||||
|
||||
map_size = N_PAGES * getpagesize () + getpagesize ();
|
||||
munmap (info->map, map_size);
|
||||
|
||||
info->map = NULL;
|
||||
info->data = NULL;
|
||||
}
|
||||
|
||||
if (info->fd != -1)
|
||||
{
|
||||
close (info->fd);
|
||||
info->fd = 0;
|
||||
}
|
||||
|
||||
g_slice_free (SpPerfCounterInfo, info);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_finalize (SpPerfCounter *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->ref_count == 0);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (info->fdtag)
|
||||
g_source_remove_unix_fd (self->source, info->fdtag);
|
||||
|
||||
sp_perf_counter_info_free (info);
|
||||
}
|
||||
|
||||
if (self->callback_data_destroy)
|
||||
self->callback_data_destroy (self->callback_data);
|
||||
|
||||
g_clear_pointer (&self->source, g_source_destroy);
|
||||
g_clear_pointer (&self->info, g_ptr_array_free);
|
||||
g_clear_pointer (&self->context, g_main_context_unref);
|
||||
g_slice_free (SpPerfCounter, self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_unref (SpPerfCounter *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
sp_perf_counter_finalize (self);
|
||||
}
|
||||
|
||||
SpPerfCounter *
|
||||
sp_perf_counter_ref (SpPerfCounter *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (self->ref_count > 0, NULL);
|
||||
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_flush (SpPerfCounter *self,
|
||||
SpPerfCounterInfo *info)
|
||||
{
|
||||
guint64 head;
|
||||
guint64 tail;
|
||||
guint64 n_bytes = N_PAGES * getpagesize ();
|
||||
guint64 mask = n_bytes - 1;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (info != NULL);
|
||||
|
||||
tail = info->tail;
|
||||
head = info->map->data_head;
|
||||
|
||||
atomic_thread_fence (memory_order_acquire);
|
||||
|
||||
if (head < tail)
|
||||
tail = head;
|
||||
|
||||
while ((head - tail) >= sizeof (struct perf_event_header))
|
||||
{
|
||||
g_autofree guint8 *free_me = NULL;
|
||||
struct perf_event_header *header;
|
||||
guint8 buffer[4096];
|
||||
|
||||
/* Note that:
|
||||
*
|
||||
* - perf events are a multiple of 64 bits
|
||||
* - the perf event header is 64 bits
|
||||
* - the data area is a multiple of 64 bits
|
||||
*
|
||||
* which means there will always be space for one header, which means we
|
||||
* can safely dereference the size field.
|
||||
*/
|
||||
header = (struct perf_event_header *)(gpointer)(info->data + (tail & mask));
|
||||
|
||||
if (header->size > head - tail)
|
||||
{
|
||||
/* The kernel did not generate a complete event.
|
||||
* I don't think that can happen, but we may as well
|
||||
* be paranoid.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
if (info->data + (tail & mask) + header->size > info->data + n_bytes)
|
||||
{
|
||||
gint n_before;
|
||||
gint n_after;
|
||||
guint8 *b;
|
||||
|
||||
if (header->size > sizeof buffer)
|
||||
free_me = b = g_malloc (header->size);
|
||||
else
|
||||
b = buffer;
|
||||
|
||||
n_after = (tail & mask) + header->size - n_bytes;
|
||||
n_before = header->size - n_after;
|
||||
|
||||
memcpy (b, info->data + (tail & mask), n_before);
|
||||
memcpy (b + n_before, info->data, n_after);
|
||||
|
||||
header = (struct perf_event_header *)(gpointer)b;
|
||||
}
|
||||
|
||||
if (header->type == PERF_RECORD_SAMPLE)
|
||||
self->n_samples++;
|
||||
|
||||
if (self->callback != NULL)
|
||||
{
|
||||
info->in_callback = TRUE;
|
||||
self->callback ((SpPerfCounterEvent *)header, info->cpu, self->callback_data);
|
||||
info->in_callback = FALSE;
|
||||
}
|
||||
|
||||
tail += header->size;
|
||||
}
|
||||
|
||||
info->tail = tail;
|
||||
|
||||
atomic_thread_fence (memory_order_seq_cst);
|
||||
info->map->data_tail = tail;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_counter_dispatch (gpointer user_data)
|
||||
{
|
||||
SpPerfCounter *self = user_data;
|
||||
guint i;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->info != NULL);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
sp_perf_counter_flush (self, info);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_enable_info (SpPerfCounter *self,
|
||||
SpPerfCounterInfo *info)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (info != NULL);
|
||||
|
||||
if (0 != ioctl (info->fd, PERF_EVENT_IOC_ENABLE))
|
||||
g_warning ("Failed to enable counters");
|
||||
|
||||
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_IN);
|
||||
}
|
||||
|
||||
SpPerfCounter *
|
||||
sp_perf_counter_new (GMainContext *context)
|
||||
{
|
||||
SpPerfCounter *ret;
|
||||
|
||||
if (context == NULL)
|
||||
context = g_main_context_default ();
|
||||
|
||||
ret = g_slice_new0 (SpPerfCounter);
|
||||
ret->ref_count = 1;
|
||||
ret->info = g_ptr_array_new ();
|
||||
ret->context = g_main_context_ref (context);
|
||||
ret->source = g_source_new (&source_funcs, sizeof (PerfGSource));
|
||||
|
||||
((PerfGSource *)ret->source)->counter = ret;
|
||||
g_source_set_callback (ret->source, sp_perf_counter_dispatch, ret, NULL);
|
||||
g_source_set_name (ret->source, "[perf]");
|
||||
g_source_attach (ret->source, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_close (SpPerfCounter *self,
|
||||
gint fd)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (fd != -1);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (info->fd == fd)
|
||||
{
|
||||
g_ptr_array_remove_index (self->info, i);
|
||||
if (self->source)
|
||||
g_source_remove_unix_fd (self->source, info->fdtag);
|
||||
sp_perf_counter_info_free (info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_add_info (SpPerfCounter *self,
|
||||
int fd,
|
||||
int cpu)
|
||||
{
|
||||
SpPerfCounterInfo *info;
|
||||
guint8 *map;
|
||||
gsize map_size;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (fd != -1);
|
||||
|
||||
map_size = N_PAGES * getpagesize () + getpagesize ();
|
||||
map = mmap (NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
|
||||
if (map == MAP_FAILED)
|
||||
{
|
||||
close (fd);
|
||||
return;
|
||||
}
|
||||
|
||||
info = g_slice_new0 (SpPerfCounterInfo);
|
||||
info->fd = fd;
|
||||
info->map = (gpointer)map;
|
||||
info->data = map + getpagesize ();
|
||||
info->tail = 0;
|
||||
info->cpu = cpu;
|
||||
|
||||
g_ptr_array_add (self->info, info);
|
||||
|
||||
info->fdtag = g_source_add_unix_fd (self->source, info->fd, G_IO_ERR);
|
||||
|
||||
if (self->enabled)
|
||||
sp_perf_counter_enable_info (self, info);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_take_fd (SpPerfCounter *self,
|
||||
int fd)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (fd > -1);
|
||||
|
||||
sp_perf_counter_add_info (self, fd, -1);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
static GDBusProxy *
|
||||
get_proxy (void)
|
||||
{
|
||||
static GDBusProxy *proxy;
|
||||
|
||||
if (proxy != NULL)
|
||||
return g_object_ref (proxy);
|
||||
|
||||
if (shared_conn == NULL)
|
||||
shared_conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
|
||||
|
||||
if (shared_conn == NULL)
|
||||
return NULL;
|
||||
|
||||
proxy = g_dbus_proxy_new_sync (shared_conn,
|
||||
(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION),
|
||||
NULL,
|
||||
"org.gnome.Sysprof2",
|
||||
"/org/gnome/Sysprof2",
|
||||
"org.gnome.Sysprof2",
|
||||
NULL, NULL);
|
||||
|
||||
if (proxy != NULL)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (proxy), (gpointer *)&proxy);
|
||||
return g_object_ref (proxy);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
authorize_proxy (GDBusProxy *proxy)
|
||||
{
|
||||
PolkitSubject *subject = NULL;
|
||||
GPermission *permission = NULL;
|
||||
GDBusConnection *conn;
|
||||
const gchar *name;
|
||||
|
||||
g_assert (G_IS_DBUS_PROXY (proxy));
|
||||
|
||||
conn = g_dbus_proxy_get_connection (proxy);
|
||||
if (conn == NULL)
|
||||
goto failure;
|
||||
|
||||
name = g_dbus_connection_get_unique_name (conn);
|
||||
if (name == NULL)
|
||||
goto failure;
|
||||
|
||||
subject = polkit_system_bus_name_new (name);
|
||||
if (subject == NULL)
|
||||
goto failure;
|
||||
|
||||
permission = polkit_permission_new_sync ("org.gnome.sysprof2.perf-event-open", subject, NULL, NULL);
|
||||
if (permission == NULL)
|
||||
goto failure;
|
||||
|
||||
if (!g_permission_acquire (permission, NULL, NULL))
|
||||
goto failure;
|
||||
|
||||
return TRUE;
|
||||
|
||||
failure:
|
||||
g_clear_object (&subject);
|
||||
g_clear_object (&permission);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GDBusProxy *
|
||||
get_authorized_proxy (void)
|
||||
{
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
|
||||
proxy = get_proxy ();
|
||||
if (proxy != NULL && authorize_proxy (proxy))
|
||||
return g_steal_pointer (&proxy);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_ping_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GDBusProxy *proxy = (GDBusProxy *)object;
|
||||
g_autoptr(GTask) task = user_data;
|
||||
g_autoptr(GVariant) ret = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_DBUS_PROXY (proxy));
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
|
||||
ret = g_dbus_proxy_call_finish (proxy, result, &error);
|
||||
|
||||
if (error != NULL)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_acquire_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GPermission *permission = (GPermission *)object;
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_PERMISSION (permission));
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (!g_permission_acquire_finish (permission, result, &error))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
proxy = get_proxy ();
|
||||
|
||||
if (proxy == NULL)
|
||||
{
|
||||
/* We don't connect at startup, shouldn't happen */
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Failed to create proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
g_dbus_proxy_call (proxy,
|
||||
"org.freedesktop.DBus.Peer.Ping",
|
||||
NULL,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
5000,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_ping_cb,
|
||||
g_object_ref (task));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_permission_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GPermission *permission;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (NULL == (permission = polkit_permission_new_finish (result, &error)))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_permission_acquire_async (permission,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_acquire_cb,
|
||||
g_object_ref (task));
|
||||
|
||||
g_object_unref (permission);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_get_bus_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GDBusConnection) bus = NULL;
|
||||
g_autoptr(GTask) task = user_data;
|
||||
PolkitSubject *subject = NULL;
|
||||
const gchar *name;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (NULL == (bus = g_bus_get_finish (result, &error)))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
shared_conn = g_object_ref (bus);
|
||||
|
||||
name = g_dbus_connection_get_unique_name (bus);
|
||||
subject = polkit_system_bus_name_new (name);
|
||||
|
||||
polkit_permission_new ("org.gnome.sysprof2.perf-event-open",
|
||||
subject,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_permission_cb,
|
||||
g_object_ref (task));
|
||||
|
||||
g_object_unref (subject);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
sp_perf_counter_authorize_async (GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
task = g_task_new (NULL, cancellable, callback, user_data);
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
g_bus_get (G_BUS_TYPE_SYSTEM,
|
||||
cancellable,
|
||||
sp_perf_counter_get_bus_cb,
|
||||
g_object_ref (task));
|
||||
#else
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"Sysprofd is not supported in current configuration");
|
||||
#endif
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_perf_counter_authorize_finish (GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
||||
|
||||
gint
|
||||
sp_perf_counter_open (SpPerfCounter *self,
|
||||
struct perf_event_attr *attr,
|
||||
GPid pid,
|
||||
gint cpu,
|
||||
gint group_fd,
|
||||
gulong flags)
|
||||
{
|
||||
#ifdef ENABLE_POLKIT
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
g_autoptr(GUnixFDList) fdlist = NULL;
|
||||
g_autoptr(GVariant) res = NULL;
|
||||
g_autoptr(GVariant) params = NULL;
|
||||
gint handle = -1;
|
||||
#endif
|
||||
gint ret = -1;
|
||||
|
||||
g_return_val_if_fail (self != NULL, -1);
|
||||
g_return_val_if_fail (attr != NULL, -1);
|
||||
|
||||
/*
|
||||
* First, we try to run the syscall locally, since we should avoid the
|
||||
* polkit request unless we have to use it for elevated privileges.
|
||||
*/
|
||||
if (-1 != (ret = syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags)))
|
||||
{
|
||||
sp_perf_counter_take_fd (self, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
params = g_variant_new_parsed (
|
||||
"("
|
||||
"["
|
||||
"{'comm', <%b>},"
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
"{'clockid', <%i>},"
|
||||
"{'use_clockid', <%b>},"
|
||||
#endif
|
||||
"{'config', <%t>},"
|
||||
"{'disabled', <%b>},"
|
||||
"{'exclude_idle', <%b>},"
|
||||
"{'mmap', <%b>},"
|
||||
"{'wakeup_events', <%u>},"
|
||||
"{'sample_id_all', <%b>},"
|
||||
"{'sample_period', <%t>},"
|
||||
"{'sample_type', <%t>},"
|
||||
"{'task', <%b>},"
|
||||
"{'type', <%u>}"
|
||||
"],"
|
||||
"%i,"
|
||||
"%i,"
|
||||
"%t"
|
||||
")",
|
||||
(gboolean)!!attr->comm,
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
(gint32)attr->clockid,
|
||||
(gboolean)!!attr->use_clockid,
|
||||
#endif
|
||||
(guint64)attr->config,
|
||||
(gboolean)!!attr->disabled,
|
||||
(gboolean)!!attr->exclude_idle,
|
||||
(gboolean)!!attr->mmap,
|
||||
(guint32)attr->wakeup_events,
|
||||
(gboolean)!!attr->sample_id_all,
|
||||
(guint64)attr->sample_period,
|
||||
(guint64)attr->sample_type,
|
||||
(gboolean)!!attr->task,
|
||||
(guint32)attr->type,
|
||||
(gint32)pid,
|
||||
(gint32)cpu,
|
||||
(guint64)flags);
|
||||
|
||||
params = g_variant_ref_sink (params);
|
||||
|
||||
if (NULL == (proxy = get_authorized_proxy ()))
|
||||
{
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = g_dbus_proxy_call_with_unix_fd_list_sync (proxy,
|
||||
"PerfEventOpen",
|
||||
params,
|
||||
G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
|
||||
60000,
|
||||
NULL,
|
||||
&fdlist,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (res == NULL)
|
||||
{
|
||||
g_autofree gchar *str = g_variant_print (params, TRUE);
|
||||
|
||||
g_warning ("PerfEventOpen: %s: %s", error->message, str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_variant_is_of_type (res, (const GVariantType *)"(h)"))
|
||||
{
|
||||
g_warning ("Received something other than a handle");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fdlist == NULL)
|
||||
{
|
||||
g_warning ("Failed to receive fdlist");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_variant_get (res, "(h)", &handle);
|
||||
|
||||
if (-1 == (ret = g_unix_fd_list_get (fdlist, handle, &error)))
|
||||
{
|
||||
g_warning ("%s", error->message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sp_perf_counter_take_fd (self, ret);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_set_callback (SpPerfCounter *self,
|
||||
SpPerfCounterCallback callback,
|
||||
gpointer callback_data,
|
||||
GDestroyNotify callback_data_destroy)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (self->callback_data_destroy)
|
||||
self->callback_data_destroy (self->callback_data);
|
||||
|
||||
self->callback = callback;
|
||||
self->callback_data = callback_data;
|
||||
self->callback_data_destroy = callback_data_destroy;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_enable (SpPerfCounter *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (g_atomic_int_add (&self->enabled, 1) == 0)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
sp_perf_counter_enable_info (self, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_disable (SpPerfCounter *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->enabled))
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (0 != ioctl (info->fd, PERF_EVENT_IOC_DISABLE))
|
||||
g_warning ("Failed to disable counters");
|
||||
|
||||
if (!info->in_callback)
|
||||
sp_perf_counter_flush (self, info);
|
||||
|
||||
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
lib/sources/sp-perf-counter.h
Normal file
132
lib/sources/sp-perf-counter.h
Normal file
@ -0,0 +1,132 @@
|
||||
/* sp-perf-counter.h
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_PERF_COUNTER_H
|
||||
#define SP_PERF_COUNTER_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PERF_COUNTER (sp_perf_counter_get_type())
|
||||
|
||||
typedef struct _SpPerfCounter SpPerfCounter;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 ppid;
|
||||
guint32 tid;
|
||||
guint32 ptid;
|
||||
guint64 time;
|
||||
} SpPerfCounterEventFork;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
gchar comm[0];
|
||||
} SpPerfCounterEventComm;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 ppid;
|
||||
guint32 tid;
|
||||
guint32 ptid;
|
||||
guint64 time;
|
||||
} SpPerfCounterEventExit;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
guint64 addr;
|
||||
guint64 len;
|
||||
guint64 pgoff;
|
||||
char filename[0];
|
||||
} SpPerfCounterEventMmap;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint64 ip;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
guint64 time;
|
||||
guint64 n_ips;
|
||||
guint64 ips[0];
|
||||
} SpPerfCounterEventSample;
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint8 raw[0];
|
||||
SpPerfCounterEventFork fork;
|
||||
SpPerfCounterEventComm comm;
|
||||
SpPerfCounterEventExit exit;
|
||||
SpPerfCounterEventMmap mmap;
|
||||
SpPerfCounterEventSample sample;
|
||||
} SpPerfCounterEvent;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef void (*SpPerfCounterCallback) (SpPerfCounterEvent *event,
|
||||
guint cpu,
|
||||
gpointer user_data);
|
||||
|
||||
void sp_perf_counter_authorize_async (GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gboolean sp_perf_counter_authorize_finish (GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GType sp_perf_counter_get_type (void);
|
||||
SpPerfCounter *sp_perf_counter_new (GMainContext *context);
|
||||
void sp_perf_counter_set_callback (SpPerfCounter *self,
|
||||
SpPerfCounterCallback callback,
|
||||
gpointer callback_data,
|
||||
GDestroyNotify callback_data_destroy);
|
||||
void sp_perf_counter_add_pid (SpPerfCounter *self,
|
||||
GPid pid);
|
||||
gint sp_perf_counter_open (SpPerfCounter *self,
|
||||
struct perf_event_attr *attr,
|
||||
GPid pid,
|
||||
gint cpu,
|
||||
gint group_fd,
|
||||
gulong flags);
|
||||
void sp_perf_counter_take_fd (SpPerfCounter *self,
|
||||
int fd);
|
||||
void sp_perf_counter_enable (SpPerfCounter *self);
|
||||
void sp_perf_counter_disable (SpPerfCounter *self);
|
||||
void sp_perf_counter_close (SpPerfCounter *self,
|
||||
gint fd);
|
||||
SpPerfCounter *sp_perf_counter_ref (SpPerfCounter *self);
|
||||
void sp_perf_counter_unref (SpPerfCounter *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PERF_COUNTER_H */
|
||||
611
lib/sources/sp-perf-source.c
Normal file
611
lib/sources/sp-perf-source.c
Normal file
@ -0,0 +1,611 @@
|
||||
/* sp-perf-source.c
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, Red Hat, Inc.
|
||||
* Copyright 2004, 2005, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-clock.h"
|
||||
#include "util/sp-line-reader.h"
|
||||
#include "sources/sp-perf-counter.h"
|
||||
#include "sources/sp-perf-source.h"
|
||||
|
||||
#define N_WAKEUP_EVENTS 149
|
||||
|
||||
struct _SpPerfSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
SpPerfCounter *counter;
|
||||
GHashTable *pids;
|
||||
|
||||
guint running : 1;
|
||||
guint is_ready : 1;
|
||||
};
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpPerfSource, sp_perf_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
enum {
|
||||
TARGET_EXITED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static void
|
||||
sp_perf_source_real_target_exited (SpPerfSource *self)
|
||||
{
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
sp_source_emit_finished (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_finalize (GObject *object)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)object;
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->counter, sp_perf_counter_unref);
|
||||
g_clear_pointer (&self->pids, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_perf_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_class_init (SpPerfSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_perf_source_finalize;
|
||||
|
||||
signals [TARGET_EXITED] =
|
||||
g_signal_new_class_handler ("target-exited",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_CALLBACK (sp_perf_source_real_target_exited),
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_init (SpPerfSource *self)
|
||||
{
|
||||
self->pids = g_hash_table_new (NULL, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_emit_exited (gpointer data)
|
||||
{
|
||||
g_autoptr(SpPerfSource) self = data;
|
||||
|
||||
g_signal_emit (self, signals [TARGET_EXITED], 0);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_handle_sample (SpPerfSource *self,
|
||||
gint cpu,
|
||||
const SpPerfCounterEventSample *sample)
|
||||
{
|
||||
const guint64 *ips;
|
||||
gint n_ips;
|
||||
guint64 trace[3];
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (sample != NULL);
|
||||
|
||||
ips = sample->ips;
|
||||
n_ips = sample->n_ips;
|
||||
|
||||
if (n_ips == 0)
|
||||
{
|
||||
if (sample->header.misc & PERF_RECORD_MISC_KERNEL)
|
||||
{
|
||||
trace[0] = PERF_CONTEXT_KERNEL;
|
||||
trace[1] = sample->ip;
|
||||
trace[2] = PERF_CONTEXT_USER;
|
||||
|
||||
ips = trace;
|
||||
n_ips = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace[0] = PERF_CONTEXT_USER;
|
||||
trace[1] = sample->ip;
|
||||
|
||||
ips = trace;
|
||||
n_ips = 2;
|
||||
}
|
||||
}
|
||||
|
||||
sp_capture_writer_add_sample (self->writer,
|
||||
sample->time,
|
||||
cpu,
|
||||
sample->pid,
|
||||
ips,
|
||||
n_ips);
|
||||
}
|
||||
|
||||
static inline void
|
||||
realign (gsize *pos,
|
||||
gsize align)
|
||||
{
|
||||
*pos = (*pos + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_handle_event (SpPerfCounterEvent *event,
|
||||
guint cpu,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpPerfSource *self = user_data;
|
||||
gsize offset;
|
||||
gint64 time;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (event != NULL);
|
||||
|
||||
switch (event->header.type)
|
||||
{
|
||||
case PERF_RECORD_COMM:
|
||||
offset = strlen (event->comm.comm) + 1;
|
||||
realign (&offset, sizeof (guint64));
|
||||
offset += sizeof (GPid) + sizeof (GPid);
|
||||
memcpy (&time, event->comm.comm + offset, sizeof time);
|
||||
|
||||
sp_capture_writer_add_process (self->writer,
|
||||
time,
|
||||
cpu,
|
||||
event->comm.pid,
|
||||
event->comm.comm);
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_EXIT:
|
||||
sp_capture_writer_add_exit (self->writer,
|
||||
event->exit.time,
|
||||
cpu,
|
||||
event->exit.pid);
|
||||
|
||||
if (g_hash_table_contains (self->pids, GINT_TO_POINTER (event->exit.pid)))
|
||||
{
|
||||
g_hash_table_remove (self->pids, GINT_TO_POINTER (event->exit.pid));
|
||||
|
||||
if (self->running && (g_hash_table_size (self->pids) == 0))
|
||||
{
|
||||
self->running = FALSE;
|
||||
sp_perf_counter_disable (self->counter);
|
||||
g_timeout_add (0, do_emit_exited, g_object_ref (self));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_FORK:
|
||||
sp_capture_writer_add_fork (self->writer,
|
||||
event->fork.time,
|
||||
cpu,
|
||||
event->fork.ppid,
|
||||
event->fork.pid);
|
||||
|
||||
/*
|
||||
* TODO: We should add support for "follow fork" of the GPid if we are
|
||||
* targetting it.
|
||||
*/
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_LOST:
|
||||
break;
|
||||
|
||||
case PERF_RECORD_MMAP:
|
||||
offset = strlen (event->mmap.filename) + 1;
|
||||
realign (&offset, sizeof (guint64));
|
||||
offset += sizeof (GPid) + sizeof (GPid);
|
||||
memcpy (&time, event->mmap.filename + offset, sizeof time);
|
||||
|
||||
sp_capture_writer_add_map (self->writer,
|
||||
time,
|
||||
cpu,
|
||||
event->mmap.pid,
|
||||
event->mmap.addr,
|
||||
event->mmap.addr + event->mmap.len,
|
||||
event->mmap.pgoff,
|
||||
0,
|
||||
event->mmap.filename);
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_READ:
|
||||
break;
|
||||
|
||||
case PERF_RECORD_SAMPLE:
|
||||
sp_perf_source_handle_sample (self, cpu, &event->sample);
|
||||
break;
|
||||
|
||||
case PERF_RECORD_THROTTLE:
|
||||
case PERF_RECORD_UNTHROTTLE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_start_pid (SpPerfSource *self,
|
||||
GPid pid,
|
||||
GError **error)
|
||||
{
|
||||
struct perf_event_attr attr = { 0 };
|
||||
gulong flags = 0;
|
||||
gint ncpu = g_get_num_processors ();
|
||||
gint cpu = 0;
|
||||
gint fd;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
attr.sample_type = PERF_SAMPLE_IP
|
||||
| PERF_SAMPLE_TID
|
||||
| PERF_SAMPLE_CALLCHAIN
|
||||
| PERF_SAMPLE_TIME;
|
||||
attr.wakeup_events = N_WAKEUP_EVENTS;
|
||||
attr.disabled = TRUE;
|
||||
attr.mmap = 1;
|
||||
attr.comm = 1;
|
||||
attr.task = 1;
|
||||
attr.exclude_idle = 1;
|
||||
attr.sample_id_all = 1;
|
||||
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
attr.clockid = sp_clock;
|
||||
attr.use_clockid = 1;
|
||||
#endif
|
||||
|
||||
attr.size = sizeof attr;
|
||||
|
||||
if (pid != -1)
|
||||
{
|
||||
ncpu = 0;
|
||||
cpu = -1;
|
||||
}
|
||||
|
||||
for (; cpu < ncpu; cpu++)
|
||||
{
|
||||
attr.type = PERF_TYPE_HARDWARE;
|
||||
attr.config = PERF_COUNT_HW_CPU_CYCLES;
|
||||
attr.sample_period = 1200000;
|
||||
|
||||
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
/*
|
||||
* We might just not have access to hardware counters, so try to
|
||||
* gracefully fallback to software counters.
|
||||
*/
|
||||
attr.type = PERF_TYPE_SOFTWARE;
|
||||
attr.config = PERF_COUNT_SW_CPU_CLOCK;
|
||||
attr.sample_period = 1000000;
|
||||
|
||||
errno = 0;
|
||||
|
||||
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
if (errno == EPERM || errno == EACCES)
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_PERMISSION_DENIED,
|
||||
_("Sysprof requires authorization to access your computers performance counters."));
|
||||
else
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("An error occurred while attempting to access performance counters: %s"),
|
||||
g_strerror (errno));
|
||||
|
||||
sp_source_stop (SP_SOURCE (self));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_start (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
self->counter = sp_perf_counter_new (NULL);
|
||||
|
||||
sp_perf_counter_set_callback (self->counter,
|
||||
sp_perf_source_handle_event,
|
||||
self, NULL);
|
||||
|
||||
if (g_hash_table_size (self->pids) > 0)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
|
||||
g_hash_table_iter_init (&iter, self->pids);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, NULL))
|
||||
{
|
||||
GPid pid = GPOINTER_TO_INT (key);
|
||||
|
||||
if (!sp_perf_source_start_pid (self, pid, &error))
|
||||
{
|
||||
sp_source_emit_failed (source, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!sp_perf_source_start_pid (self, -1, &error))
|
||||
{
|
||||
sp_source_emit_failed (source, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->running = TRUE;
|
||||
|
||||
sp_perf_counter_enable (self->counter);
|
||||
|
||||
sp_source_emit_ready (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_stop (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (self->running)
|
||||
{
|
||||
self->running = FALSE;
|
||||
sp_perf_counter_disable (self->counter);
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->counter, sp_perf_counter_unref);
|
||||
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_return_if_fail (SP_IS_PERF_SOURCE (self));
|
||||
g_return_if_fail (pid >= -1);
|
||||
g_return_if_fail (self->writer == NULL);
|
||||
|
||||
g_hash_table_add (self->pids, GINT_TO_POINTER (pid));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_emit_ready (SpPerfSource *self)
|
||||
{
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
self->is_ready = TRUE;
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_authorize_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(SpPerfSource) self = user_data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
|
||||
if (!sp_perf_counter_authorize_finish (result, &error))
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||||
{
|
||||
sp_source_emit_failed (SP_SOURCE (self), error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sp_perf_source_emit_ready (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
user_owns_pid (uid_t uid,
|
||||
GPid pid)
|
||||
{
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autoptr(SpLineReader) reader = NULL;
|
||||
gchar *line;
|
||||
gsize len;
|
||||
gsize line_len;
|
||||
|
||||
path = g_strdup_printf ("/proc/%u/status", (guint)pid);
|
||||
|
||||
if (!g_file_get_contents (path, &contents, &len, NULL))
|
||||
return FALSE;
|
||||
|
||||
reader = sp_line_reader_new (contents, len);
|
||||
|
||||
while (NULL != (line = (gchar *)sp_line_reader_next (reader, &line_len)))
|
||||
{
|
||||
if (g_str_has_prefix (line, "Uid:"))
|
||||
{
|
||||
g_auto(GStrv) parts = NULL;
|
||||
guint i;
|
||||
|
||||
line[line_len] = '\0';
|
||||
parts = g_strsplit (line, "\t", 0);
|
||||
|
||||
for (i = 1; parts[i]; i++)
|
||||
{
|
||||
gint64 v64;
|
||||
|
||||
v64 = g_ascii_strtoll (parts[i], NULL, 10);
|
||||
|
||||
if (v64 > 0 && v64 <= G_MAXUINT)
|
||||
{
|
||||
if ((uid_t)v64 == uid)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_needs_auth (SpPerfSource *self)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
uid_t uid;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (g_hash_table_size (self->pids) == 0)
|
||||
return TRUE;
|
||||
|
||||
uid = getuid ();
|
||||
|
||||
g_hash_table_iter_init (&iter, self->pids);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, NULL))
|
||||
{
|
||||
GPid pid = GPOINTER_TO_INT (key);
|
||||
|
||||
if (!user_owns_pid (uid, pid))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_prepare (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (sp_perf_source_needs_auth (self))
|
||||
sp_perf_counter_authorize_async (NULL,
|
||||
sp_perf_source_authorize_cb,
|
||||
g_object_ref (self));
|
||||
else
|
||||
sp_perf_source_emit_ready (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_get_is_ready (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
return self->is_ready;
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->start = sp_perf_source_start;
|
||||
iface->stop = sp_perf_source_stop;
|
||||
iface->set_writer = sp_perf_source_set_writer;
|
||||
iface->add_pid = sp_perf_source_add_pid;
|
||||
iface->prepare = sp_perf_source_prepare;
|
||||
iface->get_is_ready = sp_perf_source_get_is_ready;
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_perf_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PERF_SOURCE, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_source_set_target_pid (SpPerfSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PERF_SOURCE (self));
|
||||
g_return_if_fail (pid >= -1);
|
||||
|
||||
if (pid == -1)
|
||||
g_hash_table_remove_all (self->pids);
|
||||
else
|
||||
sp_perf_source_add_pid (SP_SOURCE (self), pid);
|
||||
}
|
||||
36
lib/sources/sp-perf-source.h
Normal file
36
lib/sources/sp-perf-source.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-perf-source.h
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_PERF_SOURCE_H
|
||||
#define SP_PERF_SOURCE_H
|
||||
|
||||
#include "sources/sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PERF_SOURCE (sp_perf_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpPerfSource, sp_perf_source, SP, PERF_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_perf_source_new (void);
|
||||
void sp_perf_source_set_target_pid (SpPerfSource *self,
|
||||
GPid pid);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PERF_SOURCE_H */
|
||||
517
lib/sources/sp-proc-source.c
Normal file
517
lib/sources/sp-proc-source.c
Normal file
@ -0,0 +1,517 @@
|
||||
/* sp-proc-source.c
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, Red Hat, Inc.
|
||||
* Copyright 2004, 2005, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sources/sp-proc-source.h"
|
||||
|
||||
struct _SpProcSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
SpCaptureWriter *writer;
|
||||
GArray *pids;
|
||||
};
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
static gchar **proc_readlines (const gchar *format,
|
||||
...)
|
||||
G_GNUC_PRINTF (1, 2);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpProcSource, sp_proc_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static gchar **
|
||||
proc_readlines (const gchar *format,
|
||||
...)
|
||||
{
|
||||
gchar **ret = NULL;
|
||||
gchar *filename = NULL;
|
||||
gchar *contents = NULL;
|
||||
va_list args;
|
||||
gsize len;
|
||||
|
||||
g_assert (format != NULL);
|
||||
|
||||
va_start (args, format);
|
||||
filename = g_strdup_vprintf (format, args);
|
||||
va_end (args);
|
||||
|
||||
if (g_file_get_contents (filename, &contents, &len, NULL))
|
||||
ret = g_strsplit (contents, "\n", 0);
|
||||
|
||||
g_free (contents);
|
||||
g_free (filename);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
gchar *
|
||||
sp_proc_source_get_command_line (GPid pid,
|
||||
gboolean *is_kernel)
|
||||
{
|
||||
gchar *ret;
|
||||
gchar **lines;
|
||||
|
||||
|
||||
if (is_kernel)
|
||||
*is_kernel = FALSE;
|
||||
|
||||
/*
|
||||
* Get the full command line from /proc/pid/cmdline.
|
||||
*/
|
||||
if (NULL != (lines = proc_readlines ("/proc/%d/cmdline", pid)))
|
||||
{
|
||||
if (lines [0] && lines [0][0])
|
||||
{
|
||||
ret = g_strdup (lines [0]);
|
||||
g_strfreev (lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
g_strfreev (lines);
|
||||
}
|
||||
|
||||
/*
|
||||
* We are guessing this is a kernel process based on cmdline being null.
|
||||
*/
|
||||
if (is_kernel)
|
||||
*is_kernel = TRUE;
|
||||
|
||||
/*
|
||||
* Check the first line of /proc/pid/status for Name: foo
|
||||
*/
|
||||
if (NULL != (lines = proc_readlines ("/proc/%d/status", pid)))
|
||||
{
|
||||
if (lines [0] && g_str_has_prefix (lines [0], "Name:"))
|
||||
{
|
||||
ret = g_strstrip (g_strdup (lines [0] + 5));
|
||||
g_strfreev (lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
g_strfreev (lines);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate_process (SpProcSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
gchar *cmdline;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > 0);
|
||||
|
||||
if (NULL != (cmdline = sp_proc_source_get_command_line (pid, NULL)))
|
||||
{
|
||||
sp_capture_writer_add_process (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
pid,
|
||||
cmdline);
|
||||
g_free (cmdline);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
strv_at_least_len (GStrv strv,
|
||||
guint len)
|
||||
{
|
||||
for (guint i = 0; i < len; i++)
|
||||
{
|
||||
if (strv[i] == NULL)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
find_mount (GStrv mounts,
|
||||
const gchar *mount)
|
||||
{
|
||||
gsize len = strlen (mount);
|
||||
|
||||
for (guint i = 0; mounts[i]; i++)
|
||||
{
|
||||
const gchar *endptr;
|
||||
const gchar *begin;
|
||||
|
||||
if (!g_str_has_prefix (mounts[i], mount))
|
||||
continue;
|
||||
|
||||
if (mounts[i][len] != ' ')
|
||||
continue;
|
||||
|
||||
begin = &mounts[i][len + 1];
|
||||
endptr = strchr (begin, ' ');
|
||||
if (endptr == NULL)
|
||||
continue;
|
||||
|
||||
return g_strndup (begin, endptr - begin);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_proc_source_translate_path:
|
||||
* @file: the path to the file inside target mount namespace
|
||||
* @mountinfo: mount info to locate translated path
|
||||
* @out_file: (out): location for the translated path
|
||||
* @out_file_len: length of @out_file
|
||||
*
|
||||
* This function will use @mountinfo to locate the longest common prefix
|
||||
* to determine which mount contains @file. That will be used to translate
|
||||
* the path pointed to by @file into the host namespace.
|
||||
*
|
||||
* The result is stored in @out_file and will always be NULL terminated.
|
||||
*/
|
||||
static void
|
||||
sp_proc_source_translate_path (const gchar *file,
|
||||
GStrv mountinfo,
|
||||
GStrv mounts,
|
||||
gchar *out_file,
|
||||
gsize out_file_len)
|
||||
{
|
||||
g_autofree gchar *closest_host = NULL;
|
||||
g_autofree gchar *closest_guest = NULL;
|
||||
g_autofree gchar *closest_mount = NULL;
|
||||
gsize closest_len = 0;
|
||||
|
||||
g_assert (file != NULL);
|
||||
g_assert (g_str_has_prefix (file, "/newroot/"));
|
||||
g_assert (mountinfo != NULL);
|
||||
g_assert (out_file != NULL);
|
||||
|
||||
if (!g_str_has_prefix (file, "/newroot/"))
|
||||
goto failure;
|
||||
|
||||
file += strlen ("/newroot");
|
||||
|
||||
for (guint i = 0; mountinfo[i] != NULL; i++)
|
||||
{
|
||||
g_auto(GStrv) parts = g_strsplit (mountinfo[i], " ", 11);
|
||||
const gchar *host;
|
||||
const gchar *guest;
|
||||
const gchar *mount;
|
||||
|
||||
/*
|
||||
* Not ideal to do the string split here, but it is much easier
|
||||
* to just do that until we get this right, and then improve
|
||||
* things later when a strok()/etc parser.
|
||||
*/
|
||||
|
||||
if (!strv_at_least_len (parts, 10))
|
||||
continue;
|
||||
|
||||
host = parts[3];
|
||||
guest = parts[4];
|
||||
mount = parts[9];
|
||||
|
||||
if (g_str_has_prefix (file, guest))
|
||||
{
|
||||
gsize len = strlen (guest);
|
||||
|
||||
if (len > closest_len && (file[len] == '\0' || file[len] == '/'))
|
||||
{
|
||||
g_free (closest_host);
|
||||
g_free (closest_guest);
|
||||
g_free (closest_mount);
|
||||
|
||||
closest_guest = g_strdup (guest);
|
||||
closest_host = g_strdup (host);
|
||||
closest_mount = g_strdup (mount);
|
||||
|
||||
closest_len = len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_len > 0)
|
||||
{
|
||||
/*
|
||||
* The translated path is relative to the mount. So we need to add that
|
||||
* prefix to this as well, based on matching it from the closest_mount.
|
||||
*/
|
||||
g_autofree gchar *mount = NULL;
|
||||
|
||||
mount = find_mount (mounts, closest_mount);
|
||||
|
||||
if (mount != NULL)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
path = g_build_filename (mount, closest_host, file + strlen (closest_guest), NULL);
|
||||
g_strlcpy (out_file, path, out_file_len);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
failure:
|
||||
/* Fallback to just copying the source */
|
||||
g_strlcpy (out_file, file, out_file_len);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate_maps (SpProcSource *self,
|
||||
GPid pid,
|
||||
GStrv mounts)
|
||||
{
|
||||
g_auto(GStrv) lines = NULL;
|
||||
g_auto(GStrv) mountinfo = NULL;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > 0);
|
||||
|
||||
if (NULL == (lines = proc_readlines ("/proc/%d/maps", pid)))
|
||||
return;
|
||||
|
||||
if (NULL == (mountinfo = proc_readlines ("/proc/%d/mountinfo", pid)))
|
||||
return;
|
||||
|
||||
for (i = 0; lines [i] != NULL; i++)
|
||||
{
|
||||
gchar file[256];
|
||||
gchar translated[256];
|
||||
const gchar *fileptr = file;
|
||||
gulong start;
|
||||
gulong end;
|
||||
gulong offset;
|
||||
gulong inode;
|
||||
gint r;
|
||||
|
||||
r = sscanf (lines [i],
|
||||
"%lx-%lx %*15s %lx %*x:%*x %lu %255s",
|
||||
&start, &end, &offset, &inode, file);
|
||||
|
||||
file [sizeof file - 1] = '\0';
|
||||
|
||||
if (r != 5)
|
||||
continue;
|
||||
|
||||
if (strcmp ("[vdso]", file) == 0)
|
||||
{
|
||||
/*
|
||||
* Søren Sandmann Pedersen says:
|
||||
*
|
||||
* For the vdso, the kernel reports 'offset' as the
|
||||
* the same as the mapping addres. This doesn't make
|
||||
* any sense to me, so we just zero it here. There
|
||||
* is code in binfile.c (read_inode) that returns 0
|
||||
* for [vdso].
|
||||
*/
|
||||
offset = 0;
|
||||
inode = 0;
|
||||
}
|
||||
|
||||
if (g_str_has_prefix (file, "/newroot/"))
|
||||
{
|
||||
/*
|
||||
* If this file starts with /newroot/, then it is in a different
|
||||
* mount-namespace from our profiler process. This means that we need
|
||||
* to translate the filename to the real path on disk inside our
|
||||
* (hopefully the default) mount-namespace. To do this, we have to
|
||||
* look at /proc/$pid/mountinfo to locate the longest-common-prefix
|
||||
* for the path.
|
||||
*/
|
||||
sp_proc_source_translate_path (file,
|
||||
mountinfo,
|
||||
mounts,
|
||||
translated,
|
||||
sizeof translated);
|
||||
fileptr = translated;
|
||||
}
|
||||
|
||||
sp_capture_writer_add_map (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
pid,
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
inode,
|
||||
fileptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate (SpProcSource *self)
|
||||
{
|
||||
g_auto(GStrv) mounts = NULL;
|
||||
const gchar *name;
|
||||
GDir *dir;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
|
||||
if (NULL == (mounts = proc_readlines ("/proc/mounts")))
|
||||
return;
|
||||
|
||||
if (self->pids->len > 0)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
sp_proc_source_populate_process (self, pid);
|
||||
sp_proc_source_populate_maps (self, pid, mounts);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (NULL == (dir = g_dir_open ("/proc", 0, NULL)))
|
||||
return;
|
||||
|
||||
while (NULL != (name = g_dir_read_name (dir)))
|
||||
{
|
||||
GPid pid;
|
||||
char *end;
|
||||
|
||||
pid = strtol (name, &end, 10);
|
||||
if (pid <= 0 || *end != '\0')
|
||||
continue;
|
||||
|
||||
sp_proc_source_populate_process (self, pid);
|
||||
sp_proc_source_populate_maps (self, pid, mounts);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_start (SpSource *source)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
sp_proc_source_populate (self);
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_stop (SpSource *source)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > -1);
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid ele = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (ele == pid)
|
||||
return;
|
||||
}
|
||||
|
||||
g_array_append_val (self->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_proc_source_set_writer;
|
||||
iface->start = sp_proc_source_start;
|
||||
iface->stop = sp_proc_source_stop;
|
||||
iface->add_pid = sp_proc_source_add_pid;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_finalize (GObject *object)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)object;
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->pids, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_proc_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_class_init (SpProcSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_proc_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_init (SpProcSource *self)
|
||||
{
|
||||
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_proc_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PROC_SOURCE, NULL);
|
||||
}
|
||||
36
lib/sources/sp-proc-source.h
Normal file
36
lib/sources/sp-proc-source.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-proc-source.h
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_PROC_SOURCE_H
|
||||
#define SP_PROC_SOURCE_H
|
||||
|
||||
#include "sources/sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROC_SOURCE (sp_proc_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpProcSource, sp_proc_source, SP, PROC_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_proc_source_new (void);
|
||||
gchar *sp_proc_source_get_command_line (GPid pid,
|
||||
gboolean *is_kernel);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROC_SOURCE_H */
|
||||
137
lib/sources/sp-source.c
Normal file
137
lib/sources/sp-source.c
Normal file
@ -0,0 +1,137 @@
|
||||
/* sp-source.c
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#include "sources/sp-source.h"
|
||||
|
||||
G_DEFINE_INTERFACE (SpSource, sp_source, G_TYPE_OBJECT)
|
||||
|
||||
enum {
|
||||
FAILED,
|
||||
FINISHED,
|
||||
READY,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static void
|
||||
sp_source_default_init (SpSourceInterface *iface)
|
||||
{
|
||||
signals [FAILED] = g_signal_new ("failed",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_ERROR);
|
||||
|
||||
signals [FINISHED] = g_signal_new ("finished",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
signals [READY] = g_signal_new ("ready",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_add_pid (SpSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (pid != FALSE);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->add_pid)
|
||||
SP_SOURCE_GET_IFACE (self)->add_pid (self, pid);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_finished (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
g_signal_emit (self, signals [FINISHED], 0);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_failed (SpSource *self,
|
||||
const GError *error)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (error != NULL);
|
||||
|
||||
g_signal_emit (self, signals [FAILED], 0, error);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_ready (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
g_signal_emit (self, signals [READY], 0);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_source_get_is_ready (SpSource *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_SOURCE (self), FALSE);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->get_is_ready)
|
||||
return SP_SOURCE_GET_IFACE (self)->get_is_ready (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_prepare (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->prepare)
|
||||
SP_SOURCE_GET_IFACE (self)->prepare (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_set_writer (SpSource *self,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (writer != NULL);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->set_writer)
|
||||
SP_SOURCE_GET_IFACE (self)->set_writer (self, writer);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_start (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->start)
|
||||
SP_SOURCE_GET_IFACE (self)->start (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_stop (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->stop)
|
||||
SP_SOURCE_GET_IFACE (self)->stop (self);
|
||||
}
|
||||
133
lib/sources/sp-source.h
Normal file
133
lib/sources/sp-source.h
Normal file
@ -0,0 +1,133 @@
|
||||
/* sp-source.h
|
||||
*
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_SOURCE_H
|
||||
#define SP_SOURCE_H
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "capture/sp-capture-writer.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_SOURCE (sp_source_get_type())
|
||||
|
||||
G_DECLARE_INTERFACE (SpSource, sp_source, SP, SOURCE, GObject)
|
||||
|
||||
struct _SpSourceInterface
|
||||
{
|
||||
GTypeInterface parent_iface;
|
||||
|
||||
/**
|
||||
* SpSource::get_is_ready:
|
||||
* @self: A SpSource.
|
||||
*
|
||||
* This function should return %TRUE if the source is ready to start
|
||||
* profiling. If the source is not ready until after sp_source_start() has
|
||||
* been called, use sp_source_emit_ready() to notify the profiler that the
|
||||
* source is ready for profiling.
|
||||
*
|
||||
* Returns: %TRUE if the source is ready to start profiling.
|
||||
*/
|
||||
gboolean (*get_is_ready) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::set_writer:
|
||||
* @self: A #SpSource.
|
||||
* @writer: A #SpCaptureWriter
|
||||
*
|
||||
* Sets the #SpCaptureWriter to use when profiling. @writer is only safe to
|
||||
* use from the main thread. If you need to capture from a thread, you should
|
||||
* create a memory-based #SpCaptureWriter and then splice that into this
|
||||
* writer from the main thread when profiling completes.
|
||||
*
|
||||
* See sp_capture_writer_splice() for information on splicing writers.
|
||||
*/
|
||||
void (*set_writer) (SpSource *self,
|
||||
SpCaptureWriter *writer);
|
||||
|
||||
/**
|
||||
* SpSource::prepare:
|
||||
*
|
||||
* This function is called before profiling has started. The source should
|
||||
* prepare any pre-profiling setup here. It may perform this work
|
||||
* asynchronously, but must g_object_notify() the SpSource::is-ready
|
||||
* property once that asynchronous work has been performed. Until it
|
||||
* is ready, #SpSource::is-ready must return FALSE.
|
||||
*/
|
||||
void (*prepare) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::add_pid:
|
||||
* @self: A #SpSource
|
||||
* @pid: A pid_t > -1
|
||||
*
|
||||
* This function is used to notify the #SpSource that a new process,
|
||||
* identified by @pid, should be profiled. By default, sources should
|
||||
* assume all processes, and only restrict to a given set of pids if
|
||||
* this function is called.
|
||||
*/
|
||||
void (*add_pid) (SpSource *self,
|
||||
GPid pid);
|
||||
|
||||
/**
|
||||
* SpSource::start:
|
||||
* @self: A #SpSource.
|
||||
*
|
||||
* Start profiling as configured.
|
||||
*
|
||||
* If a failure occurs while processing, the source should notify the
|
||||
* profiling session via sp_source_emit_failed() from the main thread.
|
||||
*/
|
||||
void (*start) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::stop:
|
||||
* @self: A #SpSource.
|
||||
*
|
||||
* Stop capturing a profile. The source should immediately stop
|
||||
* profiling and perform any cleanup tasks required. If doing
|
||||
* off-main-thread capturing, this is a good time to splice your
|
||||
* capture into the capture file set with sp_source_set_writer().
|
||||
*
|
||||
* If you need to perform asynchronous cleanup, call
|
||||
* sp_source_emit_finished() once that work has completed. If you do
|
||||
* not need to perform asynchronous cleanup, call
|
||||
* sp_source_emit_finished() from this function.
|
||||
*
|
||||
* sp_source_emit_finished() must be called from the main-thread.
|
||||
*/
|
||||
void (*stop) (SpSource *self);
|
||||
};
|
||||
|
||||
void sp_source_add_pid (SpSource *self,
|
||||
GPid pid);
|
||||
void sp_source_emit_ready (SpSource *self);
|
||||
void sp_source_emit_finished (SpSource *self);
|
||||
void sp_source_emit_failed (SpSource *self,
|
||||
const GError *error);
|
||||
gboolean sp_source_get_is_ready (SpSource *self);
|
||||
void sp_source_prepare (SpSource *self);
|
||||
void sp_source_set_writer (SpSource *self,
|
||||
SpCaptureWriter *writer);
|
||||
void sp_source_start (SpSource *self);
|
||||
void sp_source_stop (SpSource *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_SOURCE_H */
|
||||
Reference in New Issue
Block a user