qemu/trace/control.c
Daniel P. Berrange fe4db84d49 trace: provide mechanism for registering trace events
Remove the notion of there being a single global array
of trace events, by introducing a method for registering
groups of events.

The module_call_init() needs to be invoked at the start
of any program that wants to make use of the trace
support. Currently this covers system emulators qemu-nbd,
qemu-img and qemu-io.

[Squashed the following fix from Daniel P. Berrange
<berrange@redhat.com>:

linux-user/bsd-user: initialize trace events subsystem

The bsd-user/linux-user programs make use of the CPU emulation
code and this now requires that the trace events subsystem
is enabled, otherwise it'll crash trying to allocate an empty
trace events bitmap for the CPU object.

--Stefan]

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Lluís Vilanova <vilanova@ac.upc.edu>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Message-id: 1475588159-30598-14-git-send-email-berrange@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2016-10-12 09:52:50 +02:00

293 lines
6.8 KiB
C

/*
* Interface for configuring and controlling the state of tracing events.
*
* Copyright (C) 2011-2016 Lluís Vilanova <vilanova@ac.upc.edu>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "trace/control.h"
#include "qemu/help_option.h"
#ifdef CONFIG_TRACE_SIMPLE
#include "trace/simple.h"
#endif
#ifdef CONFIG_TRACE_FTRACE
#include "trace/ftrace.h"
#endif
#ifdef CONFIG_TRACE_LOG
#include "qemu/log.h"
#endif
#ifdef CONFIG_TRACE_SYSLOG
#include <syslog.h>
#endif
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/config-file.h"
#include "monitor/monitor.h"
int trace_events_enabled_count;
typedef struct TraceEventGroup {
TraceEvent **events;
} TraceEventGroup;
static TraceEventGroup *event_groups;
static size_t nevent_groups;
QemuOptsList qemu_trace_opts = {
.name = "trace",
.implied_opt_name = "enable",
.head = QTAILQ_HEAD_INITIALIZER(qemu_trace_opts.head),
.desc = {
{
.name = "enable",
.type = QEMU_OPT_STRING,
},
{
.name = "events",
.type = QEMU_OPT_STRING,
},{
.name = "file",
.type = QEMU_OPT_STRING,
},
{ /* end of list */ }
},
};
void trace_event_register_group(TraceEvent **events)
{
event_groups = g_renew(TraceEventGroup, event_groups, nevent_groups + 1);
event_groups[nevent_groups].events = events;
nevent_groups++;
}
TraceEvent *trace_event_name(const char *name)
{
assert(name != NULL);
TraceEventIter iter;
TraceEvent *ev;
trace_event_iter_init(&iter, NULL);
while ((ev = trace_event_iter_next(&iter)) != NULL) {
if (strcmp(trace_event_get_name(ev), name) == 0) {
return ev;
}
}
return NULL;
}
static bool pattern_glob(const char *pat, const char *ev)
{
while (*pat != '\0' && *ev != '\0') {
if (*pat == *ev) {
pat++;
ev++;
}
else if (*pat == '*') {
if (pattern_glob(pat, ev+1)) {
return true;
} else if (pattern_glob(pat+1, ev)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
while (*pat == '*') {
pat++;
}
if (*pat == '\0' && *ev == '\0') {
return true;
} else {
return false;
}
}
void trace_event_iter_init(TraceEventIter *iter, const char *pattern)
{
iter->event = 0;
iter->group = 0;
iter->pattern = pattern;
}
TraceEvent *trace_event_iter_next(TraceEventIter *iter)
{
while (iter->group < nevent_groups &&
event_groups[iter->group].events[iter->event] != NULL) {
TraceEvent *ev = event_groups[iter->group].events[iter->event];
iter->event++;
if (event_groups[iter->group].events[iter->event] == NULL) {
iter->event = 0;
iter->group++;
}
if (!iter->pattern ||
pattern_glob(iter->pattern,
trace_event_get_name(ev))) {
return ev;
}
}
return NULL;
}
void trace_list_events(void)
{
TraceEventIter iter;
TraceEvent *ev;
trace_event_iter_init(&iter, NULL);
while ((ev = trace_event_iter_next(&iter)) != NULL) {
fprintf(stderr, "%s\n", trace_event_get_name(ev));
}
}
static void do_trace_enable_events(const char *line_buf)
{
const bool enable = ('-' != line_buf[0]);
const char *line_ptr = enable ? line_buf : line_buf + 1;
TraceEventIter iter;
TraceEvent *ev;
bool is_pattern = trace_event_is_pattern(line_ptr);
trace_event_iter_init(&iter, line_ptr);
while ((ev = trace_event_iter_next(&iter)) != NULL) {
if (!trace_event_get_state_static(ev)) {
if (!is_pattern) {
error_report("WARNING: trace event '%s' is not traceable",
line_ptr);
return;
}
continue;
}
/* start tracing */
trace_event_set_state_dynamic(ev, enable);
if (!is_pattern) {
return;
}
}
if (!is_pattern) {
error_report("WARNING: trace event '%s' does not exist",
line_ptr);
}
}
void trace_enable_events(const char *line_buf)
{
if (is_help_option(line_buf)) {
trace_list_events();
if (cur_mon == NULL) {
exit(0);
}
} else {
do_trace_enable_events(line_buf);
}
}
static void trace_init_events(const char *fname)
{
Location loc;
FILE *fp;
char line_buf[1024];
size_t line_idx = 0;
if (fname == NULL) {
return;
}
loc_push_none(&loc);
loc_set_file(fname, 0);
fp = fopen(fname, "r");
if (!fp) {
error_report("%s", strerror(errno));
exit(1);
}
while (fgets(line_buf, sizeof(line_buf), fp)) {
loc_set_file(fname, ++line_idx);
size_t len = strlen(line_buf);
if (len > 1) { /* skip empty lines */
line_buf[len - 1] = '\0';
if ('#' == line_buf[0]) { /* skip commented lines */
continue;
}
trace_enable_events(line_buf);
}
}
if (fclose(fp) != 0) {
loc_set_file(fname, 0);
error_report("%s", strerror(errno));
exit(1);
}
loc_pop(&loc);
}
void trace_init_file(const char *file)
{
#ifdef CONFIG_TRACE_SIMPLE
st_set_trace_file(file);
#elif defined CONFIG_TRACE_LOG
/* If both the simple and the log backends are enabled, "-trace file"
* only applies to the simple backend; use "-D" for the log backend.
*/
if (file) {
qemu_set_log_filename(file, &error_fatal);
}
#else
if (file) {
fprintf(stderr, "error: -trace file=...: "
"option not supported by the selected tracing backends\n");
exit(1);
}
#endif
}
bool trace_init_backends(void)
{
#ifdef CONFIG_TRACE_SIMPLE
if (!st_init()) {
fprintf(stderr, "failed to initialize simple tracing backend.\n");
return false;
}
#endif
#ifdef CONFIG_TRACE_FTRACE
if (!ftrace_init()) {
fprintf(stderr, "failed to initialize ftrace backend.\n");
return false;
}
#endif
#ifdef CONFIG_TRACE_SYSLOG
openlog(NULL, LOG_PID, LOG_DAEMON);
#endif
return true;
}
char *trace_opt_parse(const char *optarg)
{
char *trace_file;
QemuOpts *opts = qemu_opts_parse_noisily(qemu_find_opts("trace"),
optarg, true);
if (!opts) {
exit(1);
}
if (qemu_opt_get(opts, "enable")) {
trace_enable_events(qemu_opt_get(opts, "enable"));
}
trace_init_events(qemu_opt_get(opts, "events"));
trace_file = g_strdup(qemu_opt_get(opts, "file"));
qemu_opts_del(opts);
return trace_file;
}