Files
sysprof/lib/sp-perf-source.c
Christian Hergert 29c4ec495f Land Sysprof 2.x
This is a major redesign a modernization of Sysprof. The core data
structures and design are largely the same, but it has been ported to
Gtk3 and has lots of additions that should make your profiling experience
smoother. Especially for those that are new to profiling.

There are some very simple help docs added, but we really need the
experts to come in and write some documentation here.
2016-04-13 05:24:03 -07:00

460 lines
12 KiB
C

/* 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.
*/
#include <gio/gio.h>
#include <glib/gi18n.h>
#include <errno.h>
#include <string.h>
#include "sp-clock.h"
#include "sp-perf-counter.h"
#include "sp-perf-source.h"
#define N_WAKEUP_EVENTS 149
struct _SpPerfSource
{
GObject parent_instance;
SpCaptureWriter *writer;
SpPerfCounter *counter;
GHashTable *pids;
guint running : 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 void
sp_perf_source_handle_event (SpPerfCounterEvent *event,
guint cpu,
gpointer user_data)
{
SpPerfSource *self = user_data;
SpPerfCounterSuffix *suffix;
g_assert (SP_IS_PERF_SOURCE (self));
g_assert (event != NULL);
switch (event->header.type)
{
case PERF_RECORD_COMM:
suffix = (SpPerfCounterSuffix *)event->raw
+ G_STRUCT_OFFSET (SpPerfCounterEventComm, comm)
+ strlen (event->comm.comm)
+ 1;
sp_capture_writer_add_process (self->writer,
suffix->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:
suffix = (SpPerfCounterSuffix *)event->raw
+ G_STRUCT_OFFSET (SpPerfCounterEventMmap, filename)
+ strlen (event->mmap.filename)
+ 1;
sp_capture_writer_add_map (self->writer,
suffix->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.size = sizeof attr;
attr.clockid = sp_clock;
attr.use_clockid = 1;
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
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;
}
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);
}