mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
This should be useful to visualize system-wide events in the CPU timeline, especially once we add some more events. We may want to define some modes eventually, as not all captures will care about GPU visualization. However, it's pretty low overhead (60Hz * number of monitors), so maybe we can just filter it out at visualization time. We do a bit more setup work than necessary here (looking up the config number for the tracepoint per CPU, rather than looking it up once at startup), but I suspect I'll want to refactor the init anyway once we add more tracepoint markers.
426 lines
10 KiB
C
426 lines
10 KiB
C
/* sysprofd.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 "config.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <glib.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/perf_event.h>
|
|
#include <stdlib.h>
|
|
#include <sys/syscall.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sd-bus-helper.h"
|
|
#include "../lib/sp-kallsyms.h"
|
|
|
|
#define BUS_TIMEOUT_USEC (1000000L * 10L)
|
|
|
|
#if 0
|
|
#define GOTO(l) do { \
|
|
fprintf (stderr, "GOTO: %s:%d: " #l "\n", __FUNCTION__, __LINE__); \
|
|
goto l; \
|
|
} while (0)
|
|
#else
|
|
#define GOTO(l) goto l
|
|
#endif
|
|
|
|
static int
|
|
sysprofd_get_kernel_symbols (sd_bus_message *msg,
|
|
void *user_data,
|
|
sd_bus_error *error)
|
|
{
|
|
g_autoptr(SpKallsyms) kallsyms = NULL;
|
|
sd_bus_message *reply = NULL;
|
|
const gchar *name;
|
|
guint64 addr;
|
|
guint8 type;
|
|
bool challenge = false;
|
|
int r;
|
|
|
|
assert (msg);
|
|
assert (error);
|
|
|
|
/* Authorize peer */
|
|
r = bus_test_polkit (msg,
|
|
CAP_SYS_ADMIN,
|
|
"org.gnome.sysprof2.get-kernel-symbols",
|
|
NULL,
|
|
UID_INVALID,
|
|
&challenge,
|
|
error);
|
|
|
|
if (r <= 0)
|
|
fprintf (stderr, "GetKernelSymbols() Failure: %s\n", error->message);
|
|
|
|
if (r < 0)
|
|
return r;
|
|
else if (r == 0)
|
|
return -EACCES;
|
|
|
|
if (!(kallsyms = sp_kallsyms_new (NULL)))
|
|
{
|
|
sd_bus_error_set (error,
|
|
SD_BUS_ERROR_FILE_NOT_FOUND,
|
|
"Failed to open /proc/kallsyms");
|
|
return -ENOENT;
|
|
}
|
|
|
|
r = sd_bus_message_new_method_return (msg, &reply);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_open_container (reply, 'a', "(tys)");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
while (sp_kallsyms_next (kallsyms, &name, &addr, &type))
|
|
sd_bus_message_append (reply, "(tys)", addr, type, name);
|
|
|
|
r = sd_bus_message_close_container (reply);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_send (NULL, reply, NULL);
|
|
sd_bus_message_unref (reply);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
_perf_event_open (struct perf_event_attr *attr,
|
|
pid_t pid,
|
|
int cpu,
|
|
int group_fd,
|
|
unsigned long flags)
|
|
{
|
|
assert (attr != NULL);
|
|
|
|
/* Quick sanity check */
|
|
if (attr->sample_period < 100000 && attr->type != PERF_TYPE_TRACEPOINT)
|
|
return -EINVAL;
|
|
|
|
return syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
|
|
}
|
|
|
|
static int
|
|
sysprofd_perf_event_open (sd_bus_message *msg,
|
|
void *user_data,
|
|
sd_bus_error *error)
|
|
{
|
|
struct perf_event_attr attr = { 0 };
|
|
sd_bus_message *reply = NULL;
|
|
uint64_t flags = 0;
|
|
int disabled = 0;
|
|
int32_t wakeup_events = 149;
|
|
int32_t cpu = -1;
|
|
int32_t pid = -1;
|
|
bool challenge = false;
|
|
int32_t type = 0;
|
|
uint64_t sample_period = 0;
|
|
uint64_t sample_type = 0;
|
|
uint64_t config = 0;
|
|
int clockid = CLOCK_MONOTONIC_RAW;
|
|
int comm = 0;
|
|
int mmap_ = 0;
|
|
int task = 0;
|
|
int exclude_idle = 0;
|
|
int fd = -1;
|
|
int use_clockid = 0;
|
|
int sample_id_all = 0;
|
|
int r;
|
|
|
|
assert (msg);
|
|
|
|
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_ARRAY, "{sv}");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
for (;;)
|
|
{
|
|
const char *name = NULL;
|
|
|
|
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_DICT_ENTRY, "sv");
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (r == 0)
|
|
break;
|
|
|
|
r = sd_bus_message_read (msg, "s", &name);
|
|
if (r < 0)
|
|
goto cleanup;
|
|
|
|
r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_VARIANT, NULL);
|
|
if (r < 0)
|
|
goto cleanup;
|
|
|
|
if (strcmp (name, "disabled") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &disabled);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "wakeup_events") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "u", &wakeup_events);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "sample_id_all") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &sample_id_all);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "clockid") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "i", &clockid);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "comm") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &comm);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "exclude_idle") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &exclude_idle);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "mmap") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &mmap_);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "config") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "t", &config);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "sample_period") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "t", &sample_period);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "sample_type") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "t", &sample_type);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "task") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &task);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "type") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "u", &type);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
else if (strcmp (name, "use_clockid") == 0)
|
|
{
|
|
r = sd_bus_message_read (msg, "b", &use_clockid);
|
|
if (r < 0)
|
|
GOTO (cleanup);
|
|
}
|
|
|
|
r = sd_bus_message_exit_container (msg);
|
|
if (r < 0)
|
|
goto cleanup;
|
|
|
|
sd_bus_message_exit_container (msg);
|
|
if (r < 0)
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
r = sd_bus_message_exit_container (msg);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_read (msg, "iit", &pid, &cpu, &flags);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (pid < -1 || cpu < -1)
|
|
return -EINVAL;
|
|
|
|
r = sd_bus_message_new_method_return (msg, &reply);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Authorize peer */
|
|
r = bus_test_polkit (msg,
|
|
CAP_SYS_ADMIN,
|
|
"org.gnome.sysprof2.perf-event-open",
|
|
NULL,
|
|
UID_INVALID,
|
|
&challenge,
|
|
error);
|
|
if (r < 0)
|
|
return r;
|
|
else if (r == 0)
|
|
return -EACCES;
|
|
|
|
attr.comm = !!comm;
|
|
attr.config = config;
|
|
attr.disabled = disabled;
|
|
attr.exclude_idle = !!exclude_idle;
|
|
attr.mmap = !!mmap_;
|
|
attr.sample_id_all = sample_id_all;
|
|
attr.sample_period = sample_period;
|
|
attr.sample_type = sample_type;
|
|
attr.task = !!task;
|
|
attr.type = type;
|
|
attr.wakeup_events = wakeup_events;
|
|
|
|
#ifdef HAVE_PERF_CLOCKID
|
|
if (!use_clockid || clockid < 0)
|
|
attr.clockid = CLOCK_MONOTONIC_RAW;
|
|
else
|
|
attr.clockid = clockid;
|
|
attr.use_clockid = use_clockid;
|
|
#endif
|
|
|
|
attr.size = sizeof attr;
|
|
|
|
fd = _perf_event_open (&attr, pid, cpu, -1, 0);
|
|
if (fd < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to open perf event stream: %s\n",
|
|
strerror (errno));
|
|
return -EINVAL;
|
|
}
|
|
|
|
sd_bus_message_append_basic (reply, SD_BUS_TYPE_UNIX_FD, &fd);
|
|
r = sd_bus_send (NULL, reply, NULL);
|
|
sd_bus_message_unref (reply);
|
|
|
|
close (fd);
|
|
|
|
return r;
|
|
}
|
|
|
|
static const sd_bus_vtable sysprofd_vtable[] = {
|
|
SD_BUS_VTABLE_START (0),
|
|
SD_BUS_METHOD ("PerfEventOpen", "a{sv}iit", "h", sysprofd_perf_event_open, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD ("GetKernelSymbols", "", "a(tys)", sysprofd_get_kernel_symbols, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_VTABLE_END
|
|
};
|
|
|
|
int
|
|
main (int argc,
|
|
char *argv[])
|
|
{
|
|
sd_bus_slot *slot = NULL;
|
|
sd_bus *bus = NULL;
|
|
int r;
|
|
|
|
/* Connect to the system bus */
|
|
r = sd_bus_default_system (&bus);
|
|
if (r < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to connect to system bus: %s\n",
|
|
strerror (-r));
|
|
goto failure;
|
|
}
|
|
|
|
/* Install our object */
|
|
r = sd_bus_add_object_vtable (bus,
|
|
&slot,
|
|
"/org/gnome/Sysprof2",
|
|
"org.gnome.Sysprof2",
|
|
sysprofd_vtable,
|
|
NULL);
|
|
if (r < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to install object on bus: %s\n",
|
|
strerror (-r));
|
|
goto failure;
|
|
}
|
|
|
|
/* Request our well-known name on the bus */
|
|
r = sd_bus_request_name (bus, "org.gnome.Sysprof2", 0);
|
|
if (r < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to register name on the bus: %s\n",
|
|
strerror (-r));
|
|
goto failure;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
/* Process requests */
|
|
r = sd_bus_process (bus, NULL);
|
|
if (r < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to process bus: %s\n",
|
|
strerror (-r));
|
|
goto failure;
|
|
}
|
|
|
|
/* If we processed a request, continue processing */
|
|
if (r > 0)
|
|
continue;
|
|
|
|
/* Wait for the next request to process */
|
|
r = sd_bus_wait (bus, BUS_TIMEOUT_USEC);
|
|
if (r < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"Failed to wait on bus: %s\n",
|
|
strerror (-r));
|
|
goto failure;
|
|
}
|
|
|
|
/*
|
|
* If we timed out, we can expire, we will be auto-started by
|
|
* systemd or dbus on the next activation request.
|
|
*/
|
|
if (r == 0)
|
|
break;
|
|
}
|
|
|
|
failure:
|
|
sd_bus_slot_unref (slot);
|
|
sd_bus_unref (bus);
|
|
|
|
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|