7323 lines
162 KiB
C
7323 lines
162 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
/*
|
||
|
|
* Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
||
|
|
*
|
||
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
*/
|
||
|
|
#include <dirent.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdarg.h>
|
||
|
|
#include <getopt.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include <sys/types.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
#include <sys/time.h>
|
||
|
|
#include <sys/wait.h>
|
||
|
|
#include <sys/socket.h>
|
||
|
|
#include <sys/syscall.h>
|
||
|
|
#include <sys/utsname.h>
|
||
|
|
#ifndef NO_PTRACE
|
||
|
|
#include <sys/ptrace.h>
|
||
|
|
#else
|
||
|
|
#ifdef WARN_NO_PTRACE
|
||
|
|
#warning ptrace not supported. -c feature will not work
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
#include <netdb.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <sched.h>
|
||
|
|
#include <glob.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <limits.h>
|
||
|
|
#include <libgen.h>
|
||
|
|
#include <poll.h>
|
||
|
|
#include <pwd.h>
|
||
|
|
#include <grp.h>
|
||
|
|
|
||
|
|
#include "tracefs.h"
|
||
|
|
#include "version.h"
|
||
|
|
#include "trace-local.h"
|
||
|
|
#include "trace-msg.h"
|
||
|
|
|
||
|
|
#define _STR(x) #x
|
||
|
|
#define STR(x) _STR(x)
|
||
|
|
|
||
|
|
#define TRACE_CTRL "tracing_on"
|
||
|
|
#define TRACE "trace"
|
||
|
|
#define AVAILABLE "available_tracers"
|
||
|
|
#define CURRENT "current_tracer"
|
||
|
|
#define ITER_CTRL "trace_options"
|
||
|
|
#define MAX_LATENCY "tracing_max_latency"
|
||
|
|
#define STAMP "stamp"
|
||
|
|
#define FUNC_STACK_TRACE "func_stack_trace"
|
||
|
|
#define TSC_CLOCK "x86-tsc"
|
||
|
|
|
||
|
|
#define dprint(fmt, ...) tracecmd_debug(fmt, ##__VA_ARGS__)
|
||
|
|
|
||
|
|
enum trace_type {
|
||
|
|
TRACE_TYPE_RECORD = 1,
|
||
|
|
TRACE_TYPE_START = (1 << 1),
|
||
|
|
TRACE_TYPE_STREAM = (1 << 2),
|
||
|
|
TRACE_TYPE_EXTRACT = (1 << 3),
|
||
|
|
TRACE_TYPE_SET = (1 << 4),
|
||
|
|
};
|
||
|
|
|
||
|
|
static tracecmd_handle_init_func handle_init = NULL;
|
||
|
|
|
||
|
|
static int rt_prio;
|
||
|
|
|
||
|
|
static int keep;
|
||
|
|
|
||
|
|
static int latency;
|
||
|
|
static int sleep_time = 1000;
|
||
|
|
static int recorder_threads;
|
||
|
|
static struct pid_record_data *pids;
|
||
|
|
static int buffers;
|
||
|
|
|
||
|
|
/* Clear all function filters */
|
||
|
|
static int clear_function_filters;
|
||
|
|
|
||
|
|
static bool no_fifos;
|
||
|
|
|
||
|
|
static char *host;
|
||
|
|
|
||
|
|
static const char *gai_err;
|
||
|
|
|
||
|
|
static bool quiet;
|
||
|
|
|
||
|
|
static bool fork_process;
|
||
|
|
|
||
|
|
/* Max size to let a per cpu file get */
|
||
|
|
static int max_kb;
|
||
|
|
|
||
|
|
static int do_ptrace;
|
||
|
|
|
||
|
|
static int filter_task;
|
||
|
|
static bool no_filter = false;
|
||
|
|
|
||
|
|
static int local_cpu_count;
|
||
|
|
|
||
|
|
static int finished;
|
||
|
|
|
||
|
|
/* setting of /proc/sys/kernel/ftrace_enabled */
|
||
|
|
static int fset;
|
||
|
|
|
||
|
|
static unsigned recorder_flags;
|
||
|
|
|
||
|
|
/* Try a few times to get an accurate date */
|
||
|
|
static int date2ts_tries = 50;
|
||
|
|
|
||
|
|
static struct func_list *graph_funcs;
|
||
|
|
|
||
|
|
static int func_stack;
|
||
|
|
|
||
|
|
static int save_stdout = -1;
|
||
|
|
|
||
|
|
static struct hook_list *hooks;
|
||
|
|
|
||
|
|
struct event_list {
|
||
|
|
struct event_list *next;
|
||
|
|
const char *event;
|
||
|
|
char *trigger;
|
||
|
|
char *filter;
|
||
|
|
char *pid_filter;
|
||
|
|
char *filter_file;
|
||
|
|
char *trigger_file;
|
||
|
|
char *enable_file;
|
||
|
|
int neg;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct tracecmd_event_list *listed_events;
|
||
|
|
|
||
|
|
struct events {
|
||
|
|
struct events *sibling;
|
||
|
|
struct events *children;
|
||
|
|
struct events *next;
|
||
|
|
char *name;
|
||
|
|
};
|
||
|
|
|
||
|
|
/* Files to be reset when done recording */
|
||
|
|
struct reset_file {
|
||
|
|
struct reset_file *next;
|
||
|
|
char *path;
|
||
|
|
char *reset;
|
||
|
|
int prio;
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct reset_file *reset_files;
|
||
|
|
|
||
|
|
/* Triggers need to be cleared in a special way */
|
||
|
|
static struct reset_file *reset_triggers;
|
||
|
|
|
||
|
|
struct buffer_instance top_instance;
|
||
|
|
struct buffer_instance *buffer_instances;
|
||
|
|
struct buffer_instance *first_instance;
|
||
|
|
|
||
|
|
static struct tracecmd_recorder *recorder;
|
||
|
|
|
||
|
|
static int ignore_event_not_found = 0;
|
||
|
|
|
||
|
|
static inline int is_top_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
return instance == &top_instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline int no_top_instance(void)
|
||
|
|
{
|
||
|
|
return first_instance != &top_instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void init_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
instance->event_next = &instance->events;
|
||
|
|
}
|
||
|
|
|
||
|
|
enum {
|
||
|
|
RESET_DEFAULT_PRIO = 0,
|
||
|
|
RESET_HIGH_PRIO = 100000,
|
||
|
|
};
|
||
|
|
|
||
|
|
enum trace_cmd {
|
||
|
|
CMD_extract,
|
||
|
|
CMD_start,
|
||
|
|
CMD_stream,
|
||
|
|
CMD_profile,
|
||
|
|
CMD_record,
|
||
|
|
CMD_record_agent,
|
||
|
|
CMD_set,
|
||
|
|
};
|
||
|
|
|
||
|
|
struct common_record_context {
|
||
|
|
enum trace_cmd curr_cmd;
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
const char *output;
|
||
|
|
char *date2ts;
|
||
|
|
char *user;
|
||
|
|
const char *clock;
|
||
|
|
const char *compression;
|
||
|
|
struct tsc_nsec tsc2nsec;
|
||
|
|
int data_flags;
|
||
|
|
int tsync_loop_interval;
|
||
|
|
|
||
|
|
int record_all;
|
||
|
|
int total_disable;
|
||
|
|
int disable;
|
||
|
|
int events;
|
||
|
|
int global;
|
||
|
|
int filtered;
|
||
|
|
int date;
|
||
|
|
int manual;
|
||
|
|
int topt;
|
||
|
|
int run_command;
|
||
|
|
int saved_cmdlines_size;
|
||
|
|
int file_version;
|
||
|
|
};
|
||
|
|
|
||
|
|
static void add_reset_file(const char *file, const char *val, int prio)
|
||
|
|
{
|
||
|
|
struct reset_file *reset;
|
||
|
|
struct reset_file **last = &reset_files;
|
||
|
|
|
||
|
|
/* Only reset if we are not keeping the state */
|
||
|
|
if (keep)
|
||
|
|
return;
|
||
|
|
|
||
|
|
reset = malloc(sizeof(*reset));
|
||
|
|
if (!reset)
|
||
|
|
die("Failed to allocate reset");
|
||
|
|
reset->path = strdup(file);
|
||
|
|
reset->reset = strdup(val);
|
||
|
|
reset->prio = prio;
|
||
|
|
if (!reset->path || !reset->reset)
|
||
|
|
die("Failed to allocate reset path or val");
|
||
|
|
|
||
|
|
while (*last && (*last)->prio > prio)
|
||
|
|
last = &(*last)->next;
|
||
|
|
|
||
|
|
reset->next = *last;
|
||
|
|
*last = reset;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_reset_trigger(const char *file)
|
||
|
|
{
|
||
|
|
struct reset_file *reset;
|
||
|
|
|
||
|
|
/* Only reset if we are not keeping the state */
|
||
|
|
if (keep)
|
||
|
|
return;
|
||
|
|
|
||
|
|
reset = malloc(sizeof(*reset));
|
||
|
|
if (!reset)
|
||
|
|
die("Failed to allocate reset");
|
||
|
|
reset->path = strdup(file);
|
||
|
|
|
||
|
|
reset->next = reset_triggers;
|
||
|
|
reset_triggers = reset;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* To save the contents of the file */
|
||
|
|
static void reset_save_file(const char *file, int prio)
|
||
|
|
{
|
||
|
|
char *content;
|
||
|
|
|
||
|
|
content = get_file_content(file);
|
||
|
|
if (content) {
|
||
|
|
add_reset_file(file, content, prio);
|
||
|
|
free(content);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* @file: the file to check
|
||
|
|
* @nop: If the content of the file is this, use the reset value
|
||
|
|
* @reset: What to write if the file == @nop
|
||
|
|
*/
|
||
|
|
static void reset_save_file_cond(const char *file, int prio,
|
||
|
|
const char *nop, const char *reset)
|
||
|
|
{
|
||
|
|
char *content;
|
||
|
|
char *cond;
|
||
|
|
|
||
|
|
if (keep)
|
||
|
|
return;
|
||
|
|
|
||
|
|
content = get_file_content(file);
|
||
|
|
|
||
|
|
cond = strstrip(content);
|
||
|
|
|
||
|
|
if (strcmp(cond, nop) == 0)
|
||
|
|
add_reset_file(file, reset, prio);
|
||
|
|
else
|
||
|
|
add_reset_file(file, content, prio);
|
||
|
|
|
||
|
|
free(content);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* add_instance - add a buffer instance to the internal list
|
||
|
|
* @instance: The buffer instance to add
|
||
|
|
*/
|
||
|
|
void add_instance(struct buffer_instance *instance, int cpu_count)
|
||
|
|
{
|
||
|
|
init_instance(instance);
|
||
|
|
instance->next = buffer_instances;
|
||
|
|
if (first_instance == buffer_instances)
|
||
|
|
first_instance = instance;
|
||
|
|
buffer_instances = instance;
|
||
|
|
instance->cpu_count = cpu_count;
|
||
|
|
buffers++;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void instance_reset_file_save(struct buffer_instance *instance, char *file, int prio)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, file);
|
||
|
|
if (path)
|
||
|
|
reset_save_file(path, prio);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void test_set_event_pid(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
static int have_set_event_pid;
|
||
|
|
static int have_event_fork;
|
||
|
|
static int have_func_fork;
|
||
|
|
|
||
|
|
if (!have_set_event_pid &&
|
||
|
|
tracefs_file_exists(top_instance.tracefs, "set_event_pid"))
|
||
|
|
have_set_event_pid = 1;
|
||
|
|
if (!have_event_fork &&
|
||
|
|
tracefs_file_exists(top_instance.tracefs, "options/event-fork"))
|
||
|
|
have_event_fork = 1;
|
||
|
|
if (!have_func_fork &&
|
||
|
|
tracefs_file_exists(top_instance.tracefs, "options/function-fork"))
|
||
|
|
have_func_fork = 1;
|
||
|
|
|
||
|
|
if (!instance->have_set_event_pid && have_set_event_pid) {
|
||
|
|
instance->have_set_event_pid = 1;
|
||
|
|
instance_reset_file_save(instance, "set_event_pid",
|
||
|
|
RESET_DEFAULT_PRIO);
|
||
|
|
}
|
||
|
|
if (!instance->have_event_fork && have_event_fork) {
|
||
|
|
instance->have_event_fork = 1;
|
||
|
|
instance_reset_file_save(instance, "options/event-fork",
|
||
|
|
RESET_DEFAULT_PRIO);
|
||
|
|
}
|
||
|
|
if (!instance->have_func_fork && have_func_fork) {
|
||
|
|
instance->have_func_fork = 1;
|
||
|
|
instance_reset_file_save(instance, "options/function-fork",
|
||
|
|
RESET_DEFAULT_PRIO);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* allocate_instance - allocate a new buffer instance,
|
||
|
|
* it must exist in the ftrace system
|
||
|
|
* @name: The name of the instance (instance will point to this)
|
||
|
|
*
|
||
|
|
* Returns a newly allocated instance. In case of an error or if the
|
||
|
|
* instance does not exist in the ftrace system, NULL is returned.
|
||
|
|
*/
|
||
|
|
struct buffer_instance *allocate_instance(const char *name)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
instance = calloc(1, sizeof(*instance));
|
||
|
|
if (!instance)
|
||
|
|
return NULL;
|
||
|
|
if (name)
|
||
|
|
instance->name = strdup(name);
|
||
|
|
if (tracefs_instance_exists(name)) {
|
||
|
|
instance->tracefs = tracefs_instance_create(name);
|
||
|
|
if (!instance->tracefs)
|
||
|
|
goto error;
|
||
|
|
}
|
||
|
|
|
||
|
|
return instance;
|
||
|
|
|
||
|
|
error:
|
||
|
|
if (instance) {
|
||
|
|
free(instance->name);
|
||
|
|
tracefs_instance_free(instance->tracefs);
|
||
|
|
free(instance);
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int __add_all_instances(const char *tracing_dir)
|
||
|
|
{
|
||
|
|
struct dirent *dent;
|
||
|
|
char *instances_dir;
|
||
|
|
struct stat st;
|
||
|
|
DIR *dir;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (!tracing_dir)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
instances_dir = append_file(tracing_dir, "instances");
|
||
|
|
if (!instances_dir)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
ret = stat(instances_dir, &st);
|
||
|
|
if (ret < 0 || !S_ISDIR(st.st_mode)) {
|
||
|
|
ret = -1;
|
||
|
|
goto out_free;
|
||
|
|
}
|
||
|
|
|
||
|
|
dir = opendir(instances_dir);
|
||
|
|
if (!dir) {
|
||
|
|
ret = -1;
|
||
|
|
goto out_free;
|
||
|
|
}
|
||
|
|
|
||
|
|
while ((dent = readdir(dir))) {
|
||
|
|
const char *name = strdup(dent->d_name);
|
||
|
|
char *instance_path;
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
if (strcmp(name, ".") == 0 ||
|
||
|
|
strcmp(name, "..") == 0)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
instance_path = append_file(instances_dir, name);
|
||
|
|
ret = stat(instance_path, &st);
|
||
|
|
if (ret < 0 || !S_ISDIR(st.st_mode)) {
|
||
|
|
free(instance_path);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
free(instance_path);
|
||
|
|
|
||
|
|
instance = allocate_instance(name);
|
||
|
|
if (!instance)
|
||
|
|
die("Failed to create instance");
|
||
|
|
add_instance(instance, local_cpu_count);
|
||
|
|
}
|
||
|
|
|
||
|
|
closedir(dir);
|
||
|
|
ret = 0;
|
||
|
|
|
||
|
|
out_free:
|
||
|
|
free(instances_dir);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* add_all_instances - Add all pre-existing instances to the internal list
|
||
|
|
* @tracing_dir: The top-level tracing directory
|
||
|
|
*
|
||
|
|
* Returns whether the operation succeeded
|
||
|
|
*/
|
||
|
|
void add_all_instances(void)
|
||
|
|
{
|
||
|
|
const char *tracing_dir = tracefs_tracing_dir();
|
||
|
|
if (!tracing_dir)
|
||
|
|
die("can't get the tracing directory");
|
||
|
|
|
||
|
|
__add_all_instances(tracing_dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* tracecmd_stat_cpu - show the buffer stats of a particular CPU
|
||
|
|
* @s: the trace_seq to record the data in.
|
||
|
|
* @cpu: the CPU to stat
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
void tracecmd_stat_cpu_instance(struct buffer_instance *instance,
|
||
|
|
struct trace_seq *s, int cpu)
|
||
|
|
{
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
char *path;
|
||
|
|
char *file;
|
||
|
|
int fd;
|
||
|
|
int r;
|
||
|
|
|
||
|
|
file = malloc(40);
|
||
|
|
if (!file)
|
||
|
|
return;
|
||
|
|
snprintf(file, 40, "per_cpu/cpu%d/stats", cpu);
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, file);
|
||
|
|
free(file);
|
||
|
|
fd = open(path, O_RDONLY);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (fd < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
while ((r = read(fd, buf, BUFSIZ)) > 0)
|
||
|
|
trace_seq_printf(s, "%.*s", r, buf);
|
||
|
|
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* tracecmd_stat_cpu - show the buffer stats of a particular CPU
|
||
|
|
* @s: the trace_seq to record the data in.
|
||
|
|
* @cpu: the CPU to stat
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
void tracecmd_stat_cpu(struct trace_seq *s, int cpu)
|
||
|
|
{
|
||
|
|
tracecmd_stat_cpu_instance(&top_instance, s, cpu);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_event(struct buffer_instance *instance, struct event_list *event)
|
||
|
|
{
|
||
|
|
*instance->event_next = event;
|
||
|
|
instance->event_next = &event->next;
|
||
|
|
event->next = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_event_list(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
instance->events = NULL;
|
||
|
|
init_instance(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *get_temp_file(struct buffer_instance *instance, int cpu)
|
||
|
|
{
|
||
|
|
const char *output_file = instance->output_file;
|
||
|
|
const char *name;
|
||
|
|
char *file = NULL;
|
||
|
|
int size;
|
||
|
|
|
||
|
|
name = tracefs_instance_get_name(instance->tracefs);
|
||
|
|
if (name) {
|
||
|
|
size = snprintf(file, 0, "%s.%s.cpu%d", output_file, name, cpu);
|
||
|
|
file = malloc(size + 1);
|
||
|
|
if (!file)
|
||
|
|
die("Failed to allocate temp file for %s", name);
|
||
|
|
sprintf(file, "%s.%s.cpu%d", output_file, name, cpu);
|
||
|
|
} else {
|
||
|
|
size = snprintf(file, 0, "%s.cpu%d", output_file, cpu);
|
||
|
|
file = malloc(size + 1);
|
||
|
|
if (!file)
|
||
|
|
die("Failed to allocate temp file for %s", name);
|
||
|
|
sprintf(file, "%s.cpu%d", output_file, cpu);
|
||
|
|
}
|
||
|
|
|
||
|
|
return file;
|
||
|
|
}
|
||
|
|
|
||
|
|
char *trace_get_guest_file(const char *file, const char *guest)
|
||
|
|
{
|
||
|
|
const char *p;
|
||
|
|
char *out = NULL;
|
||
|
|
int ret, base_len;
|
||
|
|
|
||
|
|
p = strrchr(file, '.');
|
||
|
|
if (p && p != file)
|
||
|
|
base_len = p - file;
|
||
|
|
else
|
||
|
|
base_len = strlen(file);
|
||
|
|
|
||
|
|
ret = asprintf(&out, "%.*s-%s%s", base_len, file,
|
||
|
|
guest, file + base_len);
|
||
|
|
if (ret < 0)
|
||
|
|
return NULL;
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void put_temp_file(char *file)
|
||
|
|
{
|
||
|
|
free(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void delete_temp_file(struct buffer_instance *instance, int cpu)
|
||
|
|
{
|
||
|
|
const char *output_file = instance->output_file;
|
||
|
|
const char *name;
|
||
|
|
char file[PATH_MAX];
|
||
|
|
|
||
|
|
name = tracefs_instance_get_name(instance->tracefs);
|
||
|
|
if (name)
|
||
|
|
snprintf(file, PATH_MAX, "%s.%s.cpu%d", output_file, name, cpu);
|
||
|
|
else
|
||
|
|
snprintf(file, PATH_MAX, "%s.cpu%d", output_file, cpu);
|
||
|
|
unlink(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int kill_thread_instance(int start, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int n = start;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < instance->cpu_count; i++) {
|
||
|
|
if (pids[n].pid > 0) {
|
||
|
|
kill(pids[n].pid, SIGKILL);
|
||
|
|
delete_temp_file(instance, i);
|
||
|
|
pids[n].pid = 0;
|
||
|
|
if (pids[n].brass[0] >= 0)
|
||
|
|
close(pids[n].brass[0]);
|
||
|
|
}
|
||
|
|
n++;
|
||
|
|
}
|
||
|
|
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kill_threads(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
if (!recorder_threads || !pids)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
i = kill_thread_instance(i, instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
void die(const char *fmt, ...)
|
||
|
|
{
|
||
|
|
va_list ap;
|
||
|
|
int ret = errno;
|
||
|
|
|
||
|
|
if (errno)
|
||
|
|
perror("trace-cmd");
|
||
|
|
else
|
||
|
|
ret = -1;
|
||
|
|
|
||
|
|
kill_threads();
|
||
|
|
va_start(ap, fmt);
|
||
|
|
fprintf(stderr, " ");
|
||
|
|
vfprintf(stderr, fmt, ap);
|
||
|
|
va_end(ap);
|
||
|
|
|
||
|
|
fprintf(stderr, "\n");
|
||
|
|
exit(ret);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int delete_thread_instance(int start, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int n = start;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < instance->cpu_count; i++) {
|
||
|
|
if (pids) {
|
||
|
|
if (pids[n].pid) {
|
||
|
|
delete_temp_file(instance, i);
|
||
|
|
if (pids[n].pid < 0)
|
||
|
|
pids[n].pid = 0;
|
||
|
|
}
|
||
|
|
n++;
|
||
|
|
} else
|
||
|
|
/* Extract does not allocate pids */
|
||
|
|
delete_temp_file(instance, i);
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void delete_thread_data(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
i = delete_thread_instance(i, instance);
|
||
|
|
/*
|
||
|
|
* Top instance temp files are still created even if it
|
||
|
|
* isn't used.
|
||
|
|
*/
|
||
|
|
if (no_top_instance()) {
|
||
|
|
for (i = 0; i < local_cpu_count; i++)
|
||
|
|
delete_temp_file(&top_instance, i);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_tsc2nsec(struct tracecmd_output *handle, struct tsc_nsec *tsc2nsec)
|
||
|
|
{
|
||
|
|
/* multiplier, shift, offset */
|
||
|
|
struct iovec vector[3];
|
||
|
|
|
||
|
|
vector[0].iov_len = 4;
|
||
|
|
vector[0].iov_base = &tsc2nsec->mult;
|
||
|
|
vector[1].iov_len = 4;
|
||
|
|
vector[1].iov_base = &tsc2nsec->shift;
|
||
|
|
vector[2].iov_len = 8;
|
||
|
|
vector[2].iov_base = &tsc2nsec->offset;
|
||
|
|
|
||
|
|
tracecmd_add_option_v(handle, TRACECMD_OPTION_TSC2NSEC, vector, 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void host_tsync_complete(struct common_record_context *ctx,
|
||
|
|
struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *handle = NULL;
|
||
|
|
int fd = -1;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = tracecmd_tsync_with_guest_stop(instance->tsync);
|
||
|
|
if (!ret) {
|
||
|
|
fd = open(instance->output_file, O_RDWR);
|
||
|
|
if (fd < 0)
|
||
|
|
die("error opening %s", instance->output_file);
|
||
|
|
handle = tracecmd_get_output_handle_fd(fd);
|
||
|
|
if (!handle)
|
||
|
|
die("cannot create output handle");
|
||
|
|
|
||
|
|
if (ctx->tsc2nsec.mult)
|
||
|
|
add_tsc2nsec(handle, &ctx->tsc2nsec);
|
||
|
|
|
||
|
|
tracecmd_write_guest_time_shift(handle, instance->tsync);
|
||
|
|
tracecmd_append_options(handle);
|
||
|
|
tracecmd_output_close(handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
tracecmd_tsync_free(instance->tsync);
|
||
|
|
instance->tsync = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void tell_guests_to_stop(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
/* Send close message to guests */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
tracecmd_msg_send_close_msg(instance->msg_handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
host_tsync_complete(ctx, instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Wait for guests to acknowledge */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance)) {
|
||
|
|
tracecmd_msg_wait_close_resp(instance->msg_handle);
|
||
|
|
tracecmd_msg_handle_close(instance->msg_handle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void stop_threads(enum trace_type type)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (!recorder_threads)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* Tell all threads to finish up */
|
||
|
|
for (i = 0; i < recorder_threads; i++) {
|
||
|
|
if (pids[i].pid > 0) {
|
||
|
|
kill(pids[i].pid, SIGUSR1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Flush out the pipes */
|
||
|
|
if (type & TRACE_TYPE_STREAM) {
|
||
|
|
do {
|
||
|
|
ret = trace_stream_read(pids, recorder_threads, NULL);
|
||
|
|
} while (ret > 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void wait_threads()
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < recorder_threads; i++) {
|
||
|
|
if (pids[i].pid > 0) {
|
||
|
|
waitpid(pids[i].pid, NULL, 0);
|
||
|
|
pids[i].pid = -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int create_recorder(struct buffer_instance *instance, int cpu,
|
||
|
|
enum trace_type type, int *brass);
|
||
|
|
|
||
|
|
static void flush_threads(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
long ret;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
for (i = 0; i < instance->cpu_count; i++) {
|
||
|
|
/* Extract doesn't support sub buffers yet */
|
||
|
|
ret = create_recorder(instance, i, TRACE_TYPE_EXTRACT, NULL);
|
||
|
|
if (ret < 0)
|
||
|
|
die("error reading ring buffer");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int set_ftrace_enable(const char *path, int set)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
int fd;
|
||
|
|
char *val = set ? "1" : "0";
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
/* if ftace_enable does not exist, simply ignore it */
|
||
|
|
fd = stat(path, &st);
|
||
|
|
if (fd < 0)
|
||
|
|
return -ENODEV;
|
||
|
|
|
||
|
|
reset_save_file(path, RESET_DEFAULT_PRIO);
|
||
|
|
|
||
|
|
ret = -1;
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
/* Now set or clear the function option */
|
||
|
|
ret = write(fd, val, 1);
|
||
|
|
close(fd);
|
||
|
|
|
||
|
|
out:
|
||
|
|
return ret < 0 ? ret : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int set_ftrace_proc(int set)
|
||
|
|
{
|
||
|
|
const char *path = "/proc/sys/kernel/ftrace_enabled";
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = set_ftrace_enable(path, set);
|
||
|
|
if (ret == -1)
|
||
|
|
die ("Can't %s ftrace", set ? "enable" : "disable");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int set_ftrace(struct buffer_instance *instance, int set, int use_proc)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "options/function-trace");
|
||
|
|
if (!path)
|
||
|
|
return -1;
|
||
|
|
ret = set_ftrace_enable(path, set);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
/* Always enable ftrace_enable proc file when set is true */
|
||
|
|
if (ret < 0 || set || use_proc)
|
||
|
|
ret = set_ftrace_proc(set);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int write_file(const char *file, const char *str)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
fd = open(file, O_WRONLY | O_TRUNC);
|
||
|
|
if (fd < 0)
|
||
|
|
die("opening to '%s'", file);
|
||
|
|
ret = write(fd, str, strlen(str));
|
||
|
|
close(fd);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void __clear_trace(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
FILE *fp;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* reset the trace */
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "trace");
|
||
|
|
fp = fopen(path, "w");
|
||
|
|
if (!fp)
|
||
|
|
die("writing to '%s'", path);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
fwrite("0", 1, 1, fp);
|
||
|
|
fclose(fp);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_trace_instances(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
__clear_trace(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_max_latency(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
tracefs_instance_file_write(instance->tracefs,
|
||
|
|
"tracing_max_latency", "0");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int add_filter_pid(struct buffer_instance *instance, int pid, int exclude)
|
||
|
|
{
|
||
|
|
struct filter_pids *p;
|
||
|
|
char buf[100];
|
||
|
|
|
||
|
|
for (p = instance->filter_pids; p; p = p->next) {
|
||
|
|
if (p->pid == pid) {
|
||
|
|
p->exclude = exclude;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
p = malloc(sizeof(*p));
|
||
|
|
if (!p)
|
||
|
|
die("Failed to allocate pid filter");
|
||
|
|
p->next = instance->filter_pids;
|
||
|
|
p->exclude = exclude;
|
||
|
|
p->pid = pid;
|
||
|
|
instance->filter_pids = p;
|
||
|
|
instance->nr_filter_pids++;
|
||
|
|
|
||
|
|
instance->len_filter_pids += sprintf(buf, "%d", pid);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_filter_pid_all(int pid, int exclude)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
add_filter_pid(instance, pid, exclude);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_save_ftrace_pid(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
static char *path;
|
||
|
|
|
||
|
|
if (!tracefs_file_exists(instance->tracefs, "set_ftrace_pid"))
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_pid");
|
||
|
|
if (!path)
|
||
|
|
return;
|
||
|
|
|
||
|
|
reset_save_file_cond(path, RESET_DEFAULT_PRIO, "no pid", "");
|
||
|
|
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_ftrace_pid(struct buffer_instance *instance,
|
||
|
|
const char *pid, int reset)
|
||
|
|
{
|
||
|
|
int fd = -1;
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (!tracefs_file_exists(instance->tracefs, "set_ftrace_pid"))
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_pid");
|
||
|
|
if (!path)
|
||
|
|
return;
|
||
|
|
|
||
|
|
fd = open(path, O_WRONLY | O_CLOEXEC | (reset ? O_TRUNC : 0));
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (fd < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
ret = write(fd, pid, strlen(pid));
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Older kernels required "-1" to disable pid
|
||
|
|
*/
|
||
|
|
if (ret < 0 && !strlen(pid))
|
||
|
|
ret = write(fd, "-1", 2);
|
||
|
|
|
||
|
|
if (ret < 0)
|
||
|
|
die("error writing to %s", path);
|
||
|
|
/* add whitespace in case another pid is written */
|
||
|
|
write(fd, " ", 1);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_ftrace_pids(int reset)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *pid;
|
||
|
|
static int first = 1;
|
||
|
|
char buf[100];
|
||
|
|
int rst;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (first)
|
||
|
|
reset_save_ftrace_pid(instance);
|
||
|
|
rst = reset;
|
||
|
|
for (pid = instance->filter_pids; pid; pid = pid->next) {
|
||
|
|
if (pid->exclude)
|
||
|
|
continue;
|
||
|
|
snprintf(buf, 100, "%d ", pid->pid);
|
||
|
|
update_ftrace_pid(instance, buf, rst);
|
||
|
|
/* Only reset the first entry */
|
||
|
|
rst = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (first)
|
||
|
|
first = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_event_filters(struct buffer_instance *instance);
|
||
|
|
static void update_pid_event_filters(struct buffer_instance *instance);
|
||
|
|
|
||
|
|
static void append_filter_pid_range(char **filter, int *curr_len,
|
||
|
|
const char *field,
|
||
|
|
int start_pid, int end_pid, bool exclude)
|
||
|
|
{
|
||
|
|
const char *op = "", *op1, *op2, *op3;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
if (*filter && **filter)
|
||
|
|
op = exclude ? "&&" : "||";
|
||
|
|
|
||
|
|
/* Handle thus case explicitly so that we get `pid==3` instead of
|
||
|
|
* `pid>=3&&pid<=3` for singleton ranges
|
||
|
|
*/
|
||
|
|
if (start_pid == end_pid) {
|
||
|
|
#define FMT "%s(%s%s%d)"
|
||
|
|
len = snprintf(NULL, 0, FMT, op,
|
||
|
|
field, exclude ? "!=" : "==", start_pid);
|
||
|
|
*filter = realloc(*filter, *curr_len + len + 1);
|
||
|
|
if (!*filter)
|
||
|
|
die("realloc");
|
||
|
|
|
||
|
|
len = snprintf(*filter + *curr_len, len + 1, FMT, op,
|
||
|
|
field, exclude ? "!=" : "==", start_pid);
|
||
|
|
*curr_len += len;
|
||
|
|
|
||
|
|
return;
|
||
|
|
#undef FMT
|
||
|
|
}
|
||
|
|
|
||
|
|
if (exclude) {
|
||
|
|
op1 = "<";
|
||
|
|
op2 = "||";
|
||
|
|
op3 = ">";
|
||
|
|
} else {
|
||
|
|
op1 = ">=";
|
||
|
|
op2 = "&&";
|
||
|
|
op3 = "<=";
|
||
|
|
}
|
||
|
|
|
||
|
|
#define FMT "%s(%s%s%d%s%s%s%d)"
|
||
|
|
len = snprintf(NULL, 0, FMT, op,
|
||
|
|
field, op1, start_pid, op2,
|
||
|
|
field, op3, end_pid);
|
||
|
|
*filter = realloc(*filter, *curr_len + len + 1);
|
||
|
|
if (!*filter)
|
||
|
|
die("realloc");
|
||
|
|
|
||
|
|
len = snprintf(*filter + *curr_len, len + 1, FMT, op,
|
||
|
|
field, op1, start_pid, op2,
|
||
|
|
field, op3, end_pid);
|
||
|
|
*curr_len += len;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* make_pid_filter - create a filter string to all pids against @field
|
||
|
|
* @curr_filter: Append to a previous filter (may realloc). Can be NULL
|
||
|
|
* @field: The field to compare the pids against
|
||
|
|
*
|
||
|
|
* Creates a new string or appends to an existing one if @curr_filter
|
||
|
|
* is not NULL. The new string will contain a filter with all pids
|
||
|
|
* in pid_filter list with the format (@field == pid) || ..
|
||
|
|
* If @curr_filter is not NULL, it will add this string as:
|
||
|
|
* (@curr_filter) && ((@field == pid) || ...)
|
||
|
|
*/
|
||
|
|
static char *make_pid_filter(struct buffer_instance *instance,
|
||
|
|
char *curr_filter, const char *field)
|
||
|
|
{
|
||
|
|
int start_pid = -1, last_pid = -1;
|
||
|
|
int last_exclude = -1;
|
||
|
|
struct filter_pids *p;
|
||
|
|
char *filter = NULL;
|
||
|
|
int curr_len = 0;
|
||
|
|
|
||
|
|
/* Use the new method if possible */
|
||
|
|
if (instance->have_set_event_pid)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
if (!instance->filter_pids)
|
||
|
|
return curr_filter;
|
||
|
|
|
||
|
|
for (p = instance->filter_pids; p; p = p->next) {
|
||
|
|
/*
|
||
|
|
* PIDs are inserted in `filter_pids` from the front and that's
|
||
|
|
* why we expect them in descending order here.
|
||
|
|
*/
|
||
|
|
if (p->pid == last_pid - 1 && p->exclude == last_exclude) {
|
||
|
|
last_pid = p->pid;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (start_pid != -1)
|
||
|
|
append_filter_pid_range(&filter, &curr_len, field,
|
||
|
|
last_pid, start_pid,
|
||
|
|
last_exclude);
|
||
|
|
|
||
|
|
start_pid = last_pid = p->pid;
|
||
|
|
last_exclude = p->exclude;
|
||
|
|
|
||
|
|
}
|
||
|
|
append_filter_pid_range(&filter, &curr_len, field,
|
||
|
|
last_pid, start_pid, last_exclude);
|
||
|
|
|
||
|
|
if (curr_filter) {
|
||
|
|
char *save = filter;
|
||
|
|
asprintf(&filter, "(%s)&&(%s)", curr_filter, filter);
|
||
|
|
free(save);
|
||
|
|
}
|
||
|
|
|
||
|
|
return filter;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define _STRINGIFY(x) #x
|
||
|
|
#define STRINGIFY(x) _STRINGIFY(x)
|
||
|
|
|
||
|
|
static int get_pid_addr_maps(struct buffer_instance *instance, int pid)
|
||
|
|
{
|
||
|
|
struct pid_addr_maps *maps = instance->pid_maps;
|
||
|
|
struct tracecmd_proc_addr_map *map;
|
||
|
|
unsigned long long begin, end;
|
||
|
|
struct pid_addr_maps *m;
|
||
|
|
char mapname[PATH_MAX+1];
|
||
|
|
char fname[PATH_MAX+1];
|
||
|
|
char buf[PATH_MAX+100];
|
||
|
|
FILE *f;
|
||
|
|
int ret;
|
||
|
|
int res;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
sprintf(fname, "/proc/%d/exe", pid);
|
||
|
|
ret = readlink(fname, mapname, PATH_MAX);
|
||
|
|
if (ret >= PATH_MAX || ret < 0)
|
||
|
|
return -ENOENT;
|
||
|
|
mapname[ret] = 0;
|
||
|
|
|
||
|
|
sprintf(fname, "/proc/%d/maps", pid);
|
||
|
|
f = fopen(fname, "r");
|
||
|
|
if (!f)
|
||
|
|
return -ENOENT;
|
||
|
|
|
||
|
|
while (maps) {
|
||
|
|
if (pid == maps->pid)
|
||
|
|
break;
|
||
|
|
maps = maps->next;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = -ENOMEM;
|
||
|
|
if (!maps) {
|
||
|
|
maps = calloc(1, sizeof(*maps));
|
||
|
|
if (!maps)
|
||
|
|
goto out_fail;
|
||
|
|
maps->pid = pid;
|
||
|
|
maps->next = instance->pid_maps;
|
||
|
|
instance->pid_maps = maps;
|
||
|
|
} else {
|
||
|
|
for (i = 0; i < maps->nr_lib_maps; i++)
|
||
|
|
free(maps->lib_maps[i].lib_name);
|
||
|
|
free(maps->lib_maps);
|
||
|
|
maps->lib_maps = NULL;
|
||
|
|
maps->nr_lib_maps = 0;
|
||
|
|
free(maps->proc_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
maps->proc_name = strdup(mapname);
|
||
|
|
if (!maps->proc_name)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
while (fgets(buf, sizeof(buf), f)) {
|
||
|
|
mapname[0] = '\0';
|
||
|
|
res = sscanf(buf, "%llx-%llx %*s %*x %*s %*d %"STRINGIFY(PATH_MAX)"s",
|
||
|
|
&begin, &end, mapname);
|
||
|
|
if (res == 3 && mapname[0] != '\0') {
|
||
|
|
map = realloc(maps->lib_maps,
|
||
|
|
(maps->nr_lib_maps + 1) * sizeof(*map));
|
||
|
|
if (!map)
|
||
|
|
goto out_fail;
|
||
|
|
map[maps->nr_lib_maps].end = end;
|
||
|
|
map[maps->nr_lib_maps].start = begin;
|
||
|
|
map[maps->nr_lib_maps].lib_name = strdup(mapname);
|
||
|
|
if (!map[maps->nr_lib_maps].lib_name)
|
||
|
|
goto out_fail;
|
||
|
|
maps->lib_maps = map;
|
||
|
|
maps->nr_lib_maps++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
out:
|
||
|
|
fclose(f);
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
out_fail:
|
||
|
|
fclose(f);
|
||
|
|
if (maps) {
|
||
|
|
for (i = 0; i < maps->nr_lib_maps; i++)
|
||
|
|
free(maps->lib_maps[i].lib_name);
|
||
|
|
if (instance->pid_maps != maps) {
|
||
|
|
m = instance->pid_maps;
|
||
|
|
while (m) {
|
||
|
|
if (m->next == maps) {
|
||
|
|
m->next = maps->next;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
m = m->next;
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
instance->pid_maps = maps->next;
|
||
|
|
free(maps->lib_maps);
|
||
|
|
maps->lib_maps = NULL;
|
||
|
|
maps->nr_lib_maps = 0;
|
||
|
|
free(maps->proc_name);
|
||
|
|
maps->proc_name = NULL;
|
||
|
|
free(maps);
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void get_filter_pid_maps(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *p;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (!instance->get_procmap)
|
||
|
|
continue;
|
||
|
|
for (p = instance->filter_pids; p; p = p->next) {
|
||
|
|
if (p->exclude)
|
||
|
|
continue;
|
||
|
|
get_pid_addr_maps(instance, p->pid);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_task_filter(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int pid = getpid();
|
||
|
|
|
||
|
|
if (no_filter)
|
||
|
|
return;
|
||
|
|
|
||
|
|
get_filter_pid_maps();
|
||
|
|
|
||
|
|
if (filter_task)
|
||
|
|
add_filter_pid_all(pid, 0);
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (!instance->filter_pids)
|
||
|
|
continue;
|
||
|
|
if (instance->common_pid_filter)
|
||
|
|
free(instance->common_pid_filter);
|
||
|
|
instance->common_pid_filter = make_pid_filter(instance, NULL,
|
||
|
|
"common_pid");
|
||
|
|
}
|
||
|
|
update_ftrace_pids(1);
|
||
|
|
for_all_instances(instance)
|
||
|
|
update_pid_event_filters(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static pid_t trace_waitpid(enum trace_type type, pid_t pid, int *status, int options)
|
||
|
|
{
|
||
|
|
struct timeval tv = { 1, 0 };
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (type & TRACE_TYPE_STREAM)
|
||
|
|
options |= WNOHANG;
|
||
|
|
|
||
|
|
do {
|
||
|
|
ret = waitpid(pid, status, options);
|
||
|
|
if (ret != 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
if (type & TRACE_TYPE_STREAM)
|
||
|
|
trace_stream_read(pids, recorder_threads, &tv);
|
||
|
|
} while (1);
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifndef __NR_pidfd_open
|
||
|
|
#define __NR_pidfd_open 434
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static int pidfd_open(pid_t pid, unsigned int flags) {
|
||
|
|
return syscall(__NR_pidfd_open, pid, flags);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int trace_waitpidfd(id_t pidfd) {
|
||
|
|
struct pollfd pollfd;
|
||
|
|
|
||
|
|
pollfd.fd = pidfd;
|
||
|
|
pollfd.events = POLLIN;
|
||
|
|
|
||
|
|
while (!finished) {
|
||
|
|
int ret = poll(&pollfd, 1, -1);
|
||
|
|
/* If waitid was interrupted, keep waiting */
|
||
|
|
if (ret < 0 && errno == EINTR)
|
||
|
|
continue;
|
||
|
|
else if (ret < 0)
|
||
|
|
return 1;
|
||
|
|
else
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int trace_wait_for_processes(struct buffer_instance *instance) {
|
||
|
|
int ret = 0;
|
||
|
|
int nr_fds = 0;
|
||
|
|
int i;
|
||
|
|
int *pidfds;
|
||
|
|
struct filter_pids *pid;
|
||
|
|
|
||
|
|
pidfds = malloc(sizeof(int) * instance->nr_process_pids);
|
||
|
|
if (!pidfds)
|
||
|
|
return 1;
|
||
|
|
|
||
|
|
for (pid = instance->process_pids;
|
||
|
|
pid && instance->nr_process_pids;
|
||
|
|
pid = pid->next) {
|
||
|
|
if (pid->exclude) {
|
||
|
|
instance->nr_process_pids--;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
pidfds[nr_fds] = pidfd_open(pid->pid, 0);
|
||
|
|
|
||
|
|
/* If the pid doesn't exist, the process has probably exited */
|
||
|
|
if (pidfds[nr_fds] < 0 && errno == ESRCH) {
|
||
|
|
instance->nr_process_pids--;
|
||
|
|
continue;
|
||
|
|
} else if (pidfds[nr_fds] < 0) {
|
||
|
|
ret = 1;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
nr_fds++;
|
||
|
|
instance->nr_process_pids--;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < nr_fds; i++) {
|
||
|
|
if (trace_waitpidfd(pidfds[i])) {
|
||
|
|
ret = 1;
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
out:
|
||
|
|
for (i = 0; i < nr_fds; i++)
|
||
|
|
close(pidfds[i]);
|
||
|
|
free(pidfds);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_event_pid(struct buffer_instance *instance, const char *buf)
|
||
|
|
{
|
||
|
|
tracefs_instance_file_write(instance->tracefs, "set_event_pid", buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifndef NO_PTRACE
|
||
|
|
/**
|
||
|
|
* append_pid_filter - add a new pid to an existing filter
|
||
|
|
* @curr_filter: the filter to append to. If NULL, then allocate one
|
||
|
|
* @field: The fild to compare the pid to
|
||
|
|
* @pid: The pid to add to.
|
||
|
|
*/
|
||
|
|
static char *append_pid_filter(char *curr_filter, const char *field, int pid)
|
||
|
|
{
|
||
|
|
char *filter;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
len = snprintf(NULL, 0, "(%s==%d)||", field, pid);
|
||
|
|
|
||
|
|
if (!curr_filter) {
|
||
|
|
/* No need for +1 as we don't use the "||" */
|
||
|
|
filter = malloc(len);
|
||
|
|
if (!filter)
|
||
|
|
die("Failed to allocate pid filter");
|
||
|
|
sprintf(filter, "(%s==%d)", field, pid);
|
||
|
|
} else {
|
||
|
|
int indx = strlen(curr_filter);
|
||
|
|
|
||
|
|
len += indx;
|
||
|
|
filter = realloc(curr_filter, len + indx + 1);
|
||
|
|
if (!filter)
|
||
|
|
die("realloc");
|
||
|
|
sprintf(filter + indx, "||(%s==%d)", field, pid);
|
||
|
|
}
|
||
|
|
|
||
|
|
return filter;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void append_sched_event(struct event_list *event, const char *field, int pid)
|
||
|
|
{
|
||
|
|
if (!event || !event->pid_filter)
|
||
|
|
return;
|
||
|
|
|
||
|
|
event->pid_filter = append_pid_filter(event->pid_filter, field, pid);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_sched_events(struct buffer_instance *instance, int pid)
|
||
|
|
{
|
||
|
|
/*
|
||
|
|
* Also make sure that the sched_switch to this pid
|
||
|
|
* and wakeups of this pid are also traced.
|
||
|
|
* Only need to do this if the events are active.
|
||
|
|
*/
|
||
|
|
append_sched_event(instance->sched_switch_event, "next_pid", pid);
|
||
|
|
append_sched_event(instance->sched_wakeup_event, "pid", pid);
|
||
|
|
append_sched_event(instance->sched_wakeup_new_event, "pid", pid);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int open_instance_fd(struct buffer_instance *instance,
|
||
|
|
const char *file, int flags);
|
||
|
|
|
||
|
|
static void add_new_filter_child_pid(int pid, int child)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *fpid;
|
||
|
|
char buf[100];
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (!instance->ptrace_child || !instance->filter_pids)
|
||
|
|
continue;
|
||
|
|
for (fpid = instance->filter_pids; fpid; fpid = fpid->next) {
|
||
|
|
if (fpid->pid == pid)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (!fpid)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
add_filter_pid(instance, child, 0);
|
||
|
|
sprintf(buf, "%d", child);
|
||
|
|
update_ftrace_pid(instance, buf, 0);
|
||
|
|
|
||
|
|
instance->common_pid_filter = append_pid_filter(instance->common_pid_filter,
|
||
|
|
"common_pid", pid);
|
||
|
|
if (instance->have_set_event_pid) {
|
||
|
|
add_event_pid(instance, buf);
|
||
|
|
} else {
|
||
|
|
update_sched_events(instance, pid);
|
||
|
|
update_event_filters(instance);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ptrace_attach(struct buffer_instance *instance, int pid)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = ptrace(PTRACE_ATTACH, pid, NULL, 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
warning("Unable to trace process %d children", pid);
|
||
|
|
do_ptrace = 0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (instance)
|
||
|
|
add_filter_pid(instance, pid, 0);
|
||
|
|
else
|
||
|
|
add_filter_pid_all(pid, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void enable_ptrace(void)
|
||
|
|
{
|
||
|
|
if (!do_ptrace || !filter_task)
|
||
|
|
return;
|
||
|
|
|
||
|
|
ptrace(PTRACE_TRACEME, 0, NULL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct buffer_instance *get_intance_fpid(int pid)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *fpid;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
for (fpid = instance->filter_pids; fpid; fpid = fpid->next) {
|
||
|
|
if (fpid->exclude)
|
||
|
|
continue;
|
||
|
|
if (fpid->pid == pid)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (fpid)
|
||
|
|
return instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ptrace_wait(enum trace_type type)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *fpid;
|
||
|
|
unsigned long send_sig;
|
||
|
|
unsigned long child;
|
||
|
|
int nr_pids = 0;
|
||
|
|
siginfo_t sig;
|
||
|
|
int main_pids;
|
||
|
|
int cstatus;
|
||
|
|
int status;
|
||
|
|
int i = 0;
|
||
|
|
int *pids;
|
||
|
|
int event;
|
||
|
|
int pid;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
nr_pids += instance->nr_filter_pids;
|
||
|
|
|
||
|
|
pids = calloc(nr_pids, sizeof(int));
|
||
|
|
if (!pids) {
|
||
|
|
warning("Unable to allocate array for %d PIDs", nr_pids);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (!instance->ptrace_child && !instance->get_procmap)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
for (fpid = instance->filter_pids; fpid && i < nr_pids; fpid = fpid->next) {
|
||
|
|
if (fpid->exclude)
|
||
|
|
continue;
|
||
|
|
pids[i++] = fpid->pid;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
main_pids = i;
|
||
|
|
|
||
|
|
do {
|
||
|
|
ret = trace_waitpid(type, -1, &status, WSTOPPED | __WALL);
|
||
|
|
if (ret < 0)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
pid = ret;
|
||
|
|
|
||
|
|
if (WIFSTOPPED(status)) {
|
||
|
|
event = (status >> 16) & 0xff;
|
||
|
|
ptrace(PTRACE_GETSIGINFO, pid, NULL, &sig);
|
||
|
|
send_sig = sig.si_signo;
|
||
|
|
/* Don't send ptrace sigs to child */
|
||
|
|
if (send_sig == SIGTRAP || send_sig == SIGSTOP)
|
||
|
|
send_sig = 0;
|
||
|
|
switch (event) {
|
||
|
|
case PTRACE_EVENT_FORK:
|
||
|
|
case PTRACE_EVENT_VFORK:
|
||
|
|
case PTRACE_EVENT_CLONE:
|
||
|
|
/* forked a child */
|
||
|
|
ptrace(PTRACE_GETEVENTMSG, pid, NULL, &child);
|
||
|
|
ptrace(PTRACE_SETOPTIONS, child, NULL,
|
||
|
|
PTRACE_O_TRACEFORK |
|
||
|
|
PTRACE_O_TRACEVFORK |
|
||
|
|
PTRACE_O_TRACECLONE |
|
||
|
|
PTRACE_O_TRACEEXIT);
|
||
|
|
add_new_filter_child_pid(pid, child);
|
||
|
|
ptrace(PTRACE_CONT, child, NULL, 0);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PTRACE_EVENT_EXIT:
|
||
|
|
instance = get_intance_fpid(pid);
|
||
|
|
if (instance && instance->get_procmap)
|
||
|
|
get_pid_addr_maps(instance, pid);
|
||
|
|
ptrace(PTRACE_GETEVENTMSG, pid, NULL, &cstatus);
|
||
|
|
ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
ptrace(PTRACE_SETOPTIONS, pid, NULL,
|
||
|
|
PTRACE_O_TRACEFORK |
|
||
|
|
PTRACE_O_TRACEVFORK |
|
||
|
|
PTRACE_O_TRACECLONE |
|
||
|
|
PTRACE_O_TRACEEXIT);
|
||
|
|
ptrace(PTRACE_CONT, pid, NULL, send_sig);
|
||
|
|
}
|
||
|
|
if (WIFEXITED(status) ||
|
||
|
|
(WIFSTOPPED(status) && event == PTRACE_EVENT_EXIT)) {
|
||
|
|
for (i = 0; i < nr_pids; i++) {
|
||
|
|
if (pid == pids[i]) {
|
||
|
|
pids[i] = 0;
|
||
|
|
main_pids--;
|
||
|
|
if (!main_pids)
|
||
|
|
finished = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} while (!finished && ret > 0);
|
||
|
|
|
||
|
|
free(pids);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
static inline void ptrace_wait(enum trace_type type) { }
|
||
|
|
static inline void enable_ptrace(void) { }
|
||
|
|
static inline void ptrace_attach(struct buffer_instance *instance, int pid) { }
|
||
|
|
|
||
|
|
#endif /* NO_PTRACE */
|
||
|
|
|
||
|
|
static void trace_or_sleep(enum trace_type type, bool pwait)
|
||
|
|
{
|
||
|
|
struct timeval tv = { 1 , 0 };
|
||
|
|
|
||
|
|
if (pwait)
|
||
|
|
ptrace_wait(type);
|
||
|
|
else if (type & TRACE_TYPE_STREAM)
|
||
|
|
trace_stream_read(pids, recorder_threads, &tv);
|
||
|
|
else
|
||
|
|
sleep(10);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int change_user(const char *user)
|
||
|
|
{
|
||
|
|
struct passwd *pwd;
|
||
|
|
|
||
|
|
if (!user)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
pwd = getpwnam(user);
|
||
|
|
if (!pwd)
|
||
|
|
return -1;
|
||
|
|
if (initgroups(user, pwd->pw_gid) < 0)
|
||
|
|
return -1;
|
||
|
|
if (setgid(pwd->pw_gid) < 0)
|
||
|
|
return -1;
|
||
|
|
if (setuid(pwd->pw_uid) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (setenv("HOME", pwd->pw_dir, 1) < 0)
|
||
|
|
return -1;
|
||
|
|
if (setenv("USER", pwd->pw_name, 1) < 0)
|
||
|
|
return -1;
|
||
|
|
if (setenv("LOGNAME", pwd->pw_name, 1) < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void run_cmd(enum trace_type type, const char *user, int argc, char **argv)
|
||
|
|
{
|
||
|
|
int status;
|
||
|
|
int pid;
|
||
|
|
|
||
|
|
if ((pid = fork()) < 0)
|
||
|
|
die("failed to fork");
|
||
|
|
if (!pid) {
|
||
|
|
/* child */
|
||
|
|
update_task_filter();
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
if (!fork_process)
|
||
|
|
enable_ptrace();
|
||
|
|
/*
|
||
|
|
* If we are using stderr for stdout, switch
|
||
|
|
* it back to the saved stdout for the code we run.
|
||
|
|
*/
|
||
|
|
if (save_stdout >= 0) {
|
||
|
|
close(1);
|
||
|
|
dup2(save_stdout, 1);
|
||
|
|
close(save_stdout);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (change_user(user) < 0)
|
||
|
|
die("Failed to change user to %s", user);
|
||
|
|
|
||
|
|
if (execvp(argv[0], argv)) {
|
||
|
|
fprintf(stderr, "\n********************\n");
|
||
|
|
fprintf(stderr, " Unable to exec %s\n", argv[0]);
|
||
|
|
fprintf(stderr, "********************\n");
|
||
|
|
die("Failed to exec %s", argv[0]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (fork_process)
|
||
|
|
exit(0);
|
||
|
|
if (do_ptrace) {
|
||
|
|
ptrace_attach(NULL, pid);
|
||
|
|
ptrace_wait(type);
|
||
|
|
} else
|
||
|
|
trace_waitpid(type, pid, &status, 0);
|
||
|
|
if (type & (TRACE_TYPE_START | TRACE_TYPE_SET))
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
set_plugin_instance(struct buffer_instance *instance, const char *name)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
char zero = '0';
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "current_tracer");
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0) {
|
||
|
|
/*
|
||
|
|
* Legacy kernels do not have current_tracer file, and they
|
||
|
|
* always use nop. So, it doesn't need to try to change the
|
||
|
|
* plugin for those if name is "nop".
|
||
|
|
*/
|
||
|
|
if (!strncmp(name, "nop", 3)) {
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
die("Opening '%s'", path);
|
||
|
|
}
|
||
|
|
ret = write(fd, name, strlen(name));
|
||
|
|
close(fd);
|
||
|
|
|
||
|
|
if (ret < 0)
|
||
|
|
die("writing to '%s'", path);
|
||
|
|
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
if (strncmp(name, "function", 8) != 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* Make sure func_stack_trace option is disabled */
|
||
|
|
/* First try instance file, then top level */
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "options/func_stack_trace");
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0) {
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
path = tracefs_get_tracing_file("options/func_stack_trace");
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0) {
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* Always reset func_stack_trace to zero. Don't bother saving
|
||
|
|
* the original content.
|
||
|
|
*/
|
||
|
|
add_reset_file(path, "0", RESET_HIGH_PRIO);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
write(fd, &zero, 1);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_plugin(const char *name)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
set_plugin_instance(instance, name);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void save_option(struct buffer_instance *instance, const char *option)
|
||
|
|
{
|
||
|
|
struct opt_list *opt;
|
||
|
|
|
||
|
|
opt = malloc(sizeof(*opt));
|
||
|
|
if (!opt)
|
||
|
|
die("Failed to allocate option");
|
||
|
|
opt->next = instance->options;
|
||
|
|
instance->options = opt;
|
||
|
|
opt->option = option;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int set_option(struct buffer_instance *instance, const char *option)
|
||
|
|
{
|
||
|
|
FILE *fp;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "trace_options");
|
||
|
|
fp = fopen(path, "w");
|
||
|
|
if (!fp)
|
||
|
|
warning("writing to '%s'", path);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
if (!fp)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
fwrite(option, 1, strlen(option), fp);
|
||
|
|
fclose(fp);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void disable_func_stack_trace_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
char *content;
|
||
|
|
char *path;
|
||
|
|
char *cond;
|
||
|
|
int size;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "current_tracer");
|
||
|
|
ret = stat(path, &st);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (ret < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
content = tracefs_instance_file_read(instance->tracefs,
|
||
|
|
"current_tracer", &size);
|
||
|
|
cond = strstrip(content);
|
||
|
|
if (memcmp(cond, "function", size - (cond - content)) !=0)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
set_option(instance, "nofunc_stack_trace");
|
||
|
|
out:
|
||
|
|
free(content);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void disable_func_stack_trace(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
disable_func_stack_trace_instance(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_reset_options(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct opt_list *opt;
|
||
|
|
const char *option;
|
||
|
|
char *content;
|
||
|
|
char *path;
|
||
|
|
char *ptr;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
if (keep)
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "trace_options");
|
||
|
|
content = get_file_content(path);
|
||
|
|
|
||
|
|
for (opt = instance->options; opt; opt = opt->next) {
|
||
|
|
option = opt->option;
|
||
|
|
len = strlen(option);
|
||
|
|
ptr = content;
|
||
|
|
again:
|
||
|
|
ptr = strstr(ptr, option);
|
||
|
|
if (ptr) {
|
||
|
|
/* First make sure its the option we want */
|
||
|
|
if (ptr[len] != '\n') {
|
||
|
|
ptr += len;
|
||
|
|
goto again;
|
||
|
|
}
|
||
|
|
if (ptr - content >= 2 && strncmp(ptr - 2, "no", 2) == 0) {
|
||
|
|
/* Make sure this isn't ohno-option */
|
||
|
|
if (ptr > content + 2 && *(ptr - 3) != '\n') {
|
||
|
|
ptr += len;
|
||
|
|
goto again;
|
||
|
|
}
|
||
|
|
/* we enabled it */
|
||
|
|
ptr[len] = 0;
|
||
|
|
add_reset_file(path, ptr-2, RESET_DEFAULT_PRIO);
|
||
|
|
ptr[len] = '\n';
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
/* make sure this is our option */
|
||
|
|
if (ptr > content && *(ptr - 1) != '\n') {
|
||
|
|
ptr += len;
|
||
|
|
goto again;
|
||
|
|
}
|
||
|
|
/* this option hasn't changed, ignore it */
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ptr is NULL, not found, maybe option is a no */
|
||
|
|
if (strncmp(option, "no", 2) != 0)
|
||
|
|
/* option is really not found? */
|
||
|
|
continue;
|
||
|
|
|
||
|
|
option += 2;
|
||
|
|
len = strlen(option);
|
||
|
|
ptr = content;
|
||
|
|
loop:
|
||
|
|
ptr = strstr(content, option);
|
||
|
|
if (!ptr)
|
||
|
|
/* Really not found? */
|
||
|
|
continue;
|
||
|
|
|
||
|
|
/* make sure this is our option */
|
||
|
|
if (ptr[len] != '\n') {
|
||
|
|
ptr += len;
|
||
|
|
goto loop;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ptr > content && *(ptr - 1) != '\n') {
|
||
|
|
ptr += len;
|
||
|
|
goto loop;
|
||
|
|
}
|
||
|
|
|
||
|
|
add_reset_file(path, option, RESET_DEFAULT_PRIO);
|
||
|
|
}
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
free(content);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_options(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct opt_list *opt;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
add_reset_options(instance);
|
||
|
|
while (instance->options) {
|
||
|
|
opt = instance->options;
|
||
|
|
instance->options = opt->next;
|
||
|
|
ret = set_option(instance, opt->option);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to set ftrace option %s",
|
||
|
|
opt->option);
|
||
|
|
free(opt);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_saved_cmdlines_size(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
int fd, len, ret = -1;
|
||
|
|
char *path, *str;
|
||
|
|
|
||
|
|
if (!ctx->saved_cmdlines_size)
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_get_tracing_file("saved_cmdlines_size");
|
||
|
|
if (!path)
|
||
|
|
goto err;
|
||
|
|
|
||
|
|
reset_save_file(path, RESET_DEFAULT_PRIO);
|
||
|
|
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (fd < 0)
|
||
|
|
goto err;
|
||
|
|
|
||
|
|
len = asprintf(&str, "%d", ctx->saved_cmdlines_size);
|
||
|
|
if (len < 0)
|
||
|
|
die("%s couldn't allocate memory", __func__);
|
||
|
|
|
||
|
|
if (write(fd, str, len) > 0)
|
||
|
|
ret = 0;
|
||
|
|
|
||
|
|
close(fd);
|
||
|
|
free(str);
|
||
|
|
err:
|
||
|
|
if (ret)
|
||
|
|
warning("Couldn't set saved_cmdlines_size");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int trace_check_file_exists(struct buffer_instance *instance, char *file)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, file);
|
||
|
|
ret = stat(path, &st);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
return ret < 0 ? 0 : 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int use_old_event_method(void)
|
||
|
|
{
|
||
|
|
static int old_event_method;
|
||
|
|
static int processed;
|
||
|
|
|
||
|
|
if (processed)
|
||
|
|
return old_event_method;
|
||
|
|
|
||
|
|
/* Check if the kernel has the events/enable file */
|
||
|
|
if (!trace_check_file_exists(&top_instance, "events/enable"))
|
||
|
|
old_event_method = 1;
|
||
|
|
|
||
|
|
processed = 1;
|
||
|
|
|
||
|
|
return old_event_method;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void old_update_events(const char *name, char update)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
FILE *fp;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (strcmp(name, "all") == 0)
|
||
|
|
name = "*:*";
|
||
|
|
|
||
|
|
/* need to use old way */
|
||
|
|
path = tracefs_get_tracing_file("set_event");
|
||
|
|
fp = fopen(path, "w");
|
||
|
|
if (!fp)
|
||
|
|
die("opening '%s'", path);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
/* Disable the event with "!" */
|
||
|
|
if (update == '0')
|
||
|
|
fwrite("!", 1, 1, fp);
|
||
|
|
|
||
|
|
ret = fwrite(name, 1, strlen(name), fp);
|
||
|
|
if (ret < 0)
|
||
|
|
die("bad event '%s'", name);
|
||
|
|
|
||
|
|
ret = fwrite("\n", 1, 1, fp);
|
||
|
|
if (ret < 0)
|
||
|
|
die("bad event '%s'", name);
|
||
|
|
|
||
|
|
fclose(fp);
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
reset_events_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
glob_t globbuf;
|
||
|
|
char *path;
|
||
|
|
char c;
|
||
|
|
int fd;
|
||
|
|
int i;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (use_old_event_method()) {
|
||
|
|
/* old way only had top instance */
|
||
|
|
if (!is_top_instance(instance))
|
||
|
|
return;
|
||
|
|
old_update_events("all", '0');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
c = '0';
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "events/enable");
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0)
|
||
|
|
die("opening to '%s'", path);
|
||
|
|
ret = write(fd, &c, 1);
|
||
|
|
close(fd);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "events/*/filter");
|
||
|
|
globbuf.gl_offs = 0;
|
||
|
|
ret = glob(path, 0, NULL, &globbuf);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (ret < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (i = 0; i < globbuf.gl_pathc; i++) {
|
||
|
|
path = globbuf.gl_pathv[i];
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0)
|
||
|
|
die("opening to '%s'", path);
|
||
|
|
ret = write(fd, &c, 1);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
globfree(&globbuf);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_events(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
reset_events_instance(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum {
|
||
|
|
STATE_NEWLINE,
|
||
|
|
STATE_SKIP,
|
||
|
|
STATE_COPY,
|
||
|
|
};
|
||
|
|
|
||
|
|
static char *read_file(const char *file)
|
||
|
|
{
|
||
|
|
char stbuf[BUFSIZ];
|
||
|
|
char *buf = NULL;
|
||
|
|
int size = 0;
|
||
|
|
char *nbuf;
|
||
|
|
int fd;
|
||
|
|
int r;
|
||
|
|
|
||
|
|
fd = open(file, O_RDONLY);
|
||
|
|
if (fd < 0)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
do {
|
||
|
|
r = read(fd, stbuf, BUFSIZ);
|
||
|
|
if (r <= 0)
|
||
|
|
continue;
|
||
|
|
nbuf = realloc(buf, size+r+1);
|
||
|
|
if (!nbuf) {
|
||
|
|
free(buf);
|
||
|
|
buf = NULL;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
buf = nbuf;
|
||
|
|
memcpy(buf+size, stbuf, r);
|
||
|
|
size += r;
|
||
|
|
} while (r > 0);
|
||
|
|
|
||
|
|
close(fd);
|
||
|
|
if (r == 0 && size > 0)
|
||
|
|
buf[size] = '\0';
|
||
|
|
|
||
|
|
return buf;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void read_error_log(const char *log)
|
||
|
|
{
|
||
|
|
char *buf, *line;
|
||
|
|
char *start = NULL;
|
||
|
|
char *p;
|
||
|
|
|
||
|
|
buf = read_file(log);
|
||
|
|
if (!buf)
|
||
|
|
return;
|
||
|
|
|
||
|
|
line = buf;
|
||
|
|
|
||
|
|
/* Only the last lines have meaning */
|
||
|
|
while ((p = strstr(line, "\n")) && p[1]) {
|
||
|
|
if (line[0] != ' ')
|
||
|
|
start = line;
|
||
|
|
line = p + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (start)
|
||
|
|
printf("%s", start);
|
||
|
|
|
||
|
|
free(buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void show_error(const char *file, const char *type)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
char *path = strdup(file);
|
||
|
|
char *p;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (!path)
|
||
|
|
die("Could not allocate memory");
|
||
|
|
|
||
|
|
p = strstr(path, "tracing");
|
||
|
|
if (p) {
|
||
|
|
if (strncmp(p + sizeof("tracing"), "instances", sizeof("instances") - 1) == 0) {
|
||
|
|
p = strstr(p + sizeof("tracing") + sizeof("instances"), "/");
|
||
|
|
if (!p)
|
||
|
|
goto read_file;
|
||
|
|
} else {
|
||
|
|
p += sizeof("tracing") - 1;
|
||
|
|
}
|
||
|
|
ret = asprintf(&p, "%.*s/error_log", (int)(p - path), path);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Could not allocate memory");
|
||
|
|
ret = stat(p, &st);
|
||
|
|
if (ret < 0) {
|
||
|
|
free(p);
|
||
|
|
goto read_file;
|
||
|
|
}
|
||
|
|
read_error_log(p);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
read_file:
|
||
|
|
p = read_file(path);
|
||
|
|
if (p)
|
||
|
|
printf("%s", p);
|
||
|
|
|
||
|
|
out:
|
||
|
|
printf("Failed %s of %s\n", type, file);
|
||
|
|
free(path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void write_filter(const char *file, const char *filter)
|
||
|
|
{
|
||
|
|
if (write_file(file, filter) < 0)
|
||
|
|
show_error(file, "filter");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_filter(const char *file)
|
||
|
|
{
|
||
|
|
write_filter(file, "0");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void write_trigger(const char *file, const char *trigger)
|
||
|
|
{
|
||
|
|
if (write_file(file, trigger) < 0)
|
||
|
|
show_error(file, "trigger");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int clear_trigger(const char *file)
|
||
|
|
{
|
||
|
|
char trigger[BUFSIZ];
|
||
|
|
char *save = NULL;
|
||
|
|
char *line;
|
||
|
|
char *buf;
|
||
|
|
int len;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
buf = read_file(file);
|
||
|
|
if (!buf) {
|
||
|
|
perror(file);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
trigger[0] = '!';
|
||
|
|
|
||
|
|
for (line = strtok_r(buf, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) {
|
||
|
|
if (line[0] == '#')
|
||
|
|
continue;
|
||
|
|
len = strlen(line);
|
||
|
|
if (len > BUFSIZ - 2)
|
||
|
|
len = BUFSIZ - 2;
|
||
|
|
strncpy(trigger + 1, line, len);
|
||
|
|
trigger[len + 1] = '\0';
|
||
|
|
/* We don't want any filters or extra on the line */
|
||
|
|
strtok(trigger, " ");
|
||
|
|
write_file(file, trigger);
|
||
|
|
}
|
||
|
|
|
||
|
|
free(buf);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Some triggers have an order in removing them.
|
||
|
|
* They will not be removed if done in the wrong order.
|
||
|
|
*/
|
||
|
|
buf = read_file(file);
|
||
|
|
if (!buf)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ret = 0;
|
||
|
|
for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) {
|
||
|
|
if (line[0] == '#')
|
||
|
|
continue;
|
||
|
|
ret = 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
free(buf);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_func_filter(const char *file)
|
||
|
|
{
|
||
|
|
char filter[BUFSIZ];
|
||
|
|
struct stat st;
|
||
|
|
char *line;
|
||
|
|
char *buf;
|
||
|
|
char *p;
|
||
|
|
int len;
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
/* Function filters may not exist */
|
||
|
|
ret = stat(file, &st);
|
||
|
|
if (ret < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* First zero out normal filters */
|
||
|
|
fd = open(file, O_WRONLY | O_TRUNC);
|
||
|
|
if (fd < 0)
|
||
|
|
die("opening to '%s'", file);
|
||
|
|
close(fd);
|
||
|
|
|
||
|
|
buf = read_file(file);
|
||
|
|
if (!buf) {
|
||
|
|
perror(file);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Now remove filters */
|
||
|
|
filter[0] = '!';
|
||
|
|
|
||
|
|
/*
|
||
|
|
* To delete a filter, we need to write a '!filter'
|
||
|
|
* to the file for each filter.
|
||
|
|
*/
|
||
|
|
for (line = strtok(buf, "\n"); line; line = strtok(NULL, "\n")) {
|
||
|
|
if (line[0] == '#')
|
||
|
|
continue;
|
||
|
|
len = strlen(line);
|
||
|
|
if (len > BUFSIZ - 2)
|
||
|
|
len = BUFSIZ - 2;
|
||
|
|
|
||
|
|
strncpy(filter + 1, line, len);
|
||
|
|
filter[len + 1] = '\0';
|
||
|
|
/*
|
||
|
|
* To remove "unlimited" filters, we must remove
|
||
|
|
* the ":unlimited" from what we write.
|
||
|
|
*/
|
||
|
|
if ((p = strstr(filter, ":unlimited"))) {
|
||
|
|
*p = '\0';
|
||
|
|
len = p - filter;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
* The write to this file expects white space
|
||
|
|
* at the end :-p
|
||
|
|
*/
|
||
|
|
filter[len] = '\n';
|
||
|
|
filter[len+1] = '\0';
|
||
|
|
write_file(file, filter);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_reset_triggers(void)
|
||
|
|
{
|
||
|
|
struct reset_file *reset;
|
||
|
|
|
||
|
|
while (reset_triggers) {
|
||
|
|
reset = reset_triggers;
|
||
|
|
reset_triggers = reset->next;
|
||
|
|
|
||
|
|
clear_trigger(reset->path);
|
||
|
|
free(reset->path);
|
||
|
|
free(reset);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_reset_files(void)
|
||
|
|
{
|
||
|
|
struct reset_file *reset;
|
||
|
|
|
||
|
|
while (reset_files) {
|
||
|
|
reset = reset_files;
|
||
|
|
reset_files = reset->next;
|
||
|
|
|
||
|
|
if (!keep)
|
||
|
|
write_file(reset->path, reset->reset);
|
||
|
|
free(reset->path);
|
||
|
|
free(reset->reset);
|
||
|
|
free(reset);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
update_event(struct event_list *event, const char *filter,
|
||
|
|
int filter_only, char update)
|
||
|
|
{
|
||
|
|
const char *name = event->event;
|
||
|
|
FILE *fp;
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (use_old_event_method()) {
|
||
|
|
if (filter_only)
|
||
|
|
return;
|
||
|
|
old_update_events(name, update);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (filter && event->filter_file) {
|
||
|
|
add_reset_file(event->filter_file, "0", RESET_DEFAULT_PRIO);
|
||
|
|
write_filter(event->filter_file, filter);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (event->trigger_file) {
|
||
|
|
add_reset_trigger(event->trigger_file);
|
||
|
|
clear_trigger(event->trigger_file);
|
||
|
|
write_trigger(event->trigger_file, event->trigger);
|
||
|
|
/* Make sure we don't write this again */
|
||
|
|
free(event->trigger_file);
|
||
|
|
free(event->trigger);
|
||
|
|
event->trigger_file = NULL;
|
||
|
|
event->trigger = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (filter_only || !event->enable_file)
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = event->enable_file;
|
||
|
|
|
||
|
|
fp = fopen(path, "w");
|
||
|
|
if (!fp)
|
||
|
|
die("writing to '%s'", path);
|
||
|
|
ret = fwrite(&update, 1, 1, fp);
|
||
|
|
fclose(fp);
|
||
|
|
if (ret < 0)
|
||
|
|
die("writing to '%s'", path);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The debugfs file tracing_enabled needs to be deprecated.
|
||
|
|
* But just in case anyone fiddled with it. If it exists,
|
||
|
|
* make sure it is one.
|
||
|
|
* No error checking needed here.
|
||
|
|
*/
|
||
|
|
static void check_tracing_enabled(void)
|
||
|
|
{
|
||
|
|
static int fd = -1;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
if (fd < 0) {
|
||
|
|
path = tracefs_get_tracing_file("tracing_enabled");
|
||
|
|
fd = open(path, O_WRONLY | O_CLOEXEC);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
if (fd < 0)
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
write(fd, "1", 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int open_instance_fd(struct buffer_instance *instance,
|
||
|
|
const char *file, int flags)
|
||
|
|
{
|
||
|
|
int fd;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, file);
|
||
|
|
fd = open(path, flags);
|
||
|
|
if (fd < 0) {
|
||
|
|
/* instances may not be created yet */
|
||
|
|
if (is_top_instance(instance))
|
||
|
|
die("opening '%s'", path);
|
||
|
|
}
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int open_tracing_on(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int fd = instance->tracing_on_fd;
|
||
|
|
|
||
|
|
/* OK, we keep zero for stdin */
|
||
|
|
if (fd > 0)
|
||
|
|
return fd;
|
||
|
|
|
||
|
|
fd = open_instance_fd(instance, "tracing_on", O_RDWR | O_CLOEXEC);
|
||
|
|
if (fd < 0) {
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
instance->tracing_on_fd = fd;
|
||
|
|
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void write_tracing_on(struct buffer_instance *instance, int on)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
fd = open_tracing_on(instance);
|
||
|
|
if (fd < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (on)
|
||
|
|
ret = write(fd, "1", 1);
|
||
|
|
else
|
||
|
|
ret = write(fd, "0", 1);
|
||
|
|
|
||
|
|
if (ret < 0)
|
||
|
|
die("writing 'tracing_on'");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int read_tracing_on(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int fd;
|
||
|
|
char buf[10];
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
fd = open_tracing_on(instance);
|
||
|
|
if (fd < 0)
|
||
|
|
return fd;
|
||
|
|
|
||
|
|
ret = read(fd, buf, 10);
|
||
|
|
if (ret <= 0)
|
||
|
|
die("Reading 'tracing_on'");
|
||
|
|
buf[9] = 0;
|
||
|
|
ret = atoi(buf);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_max_latency_instance(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
reset_max_latency(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
void tracecmd_enable_tracing(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
check_tracing_enabled();
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
write_tracing_on(instance, 1);
|
||
|
|
|
||
|
|
if (latency)
|
||
|
|
reset_max_latency_instance();
|
||
|
|
}
|
||
|
|
|
||
|
|
void tracecmd_disable_tracing(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
write_tracing_on(instance, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void tracecmd_disable_all_tracing(int disable_tracer)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
tracecmd_disable_tracing();
|
||
|
|
|
||
|
|
if (disable_tracer) {
|
||
|
|
disable_func_stack_trace();
|
||
|
|
set_plugin("nop");
|
||
|
|
}
|
||
|
|
|
||
|
|
reset_events();
|
||
|
|
|
||
|
|
/* Force close and reset of ftrace pid file */
|
||
|
|
for_all_instances(instance)
|
||
|
|
update_ftrace_pid(instance, "", 1);
|
||
|
|
|
||
|
|
clear_trace_instances();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
update_sched_event(struct buffer_instance *instance,
|
||
|
|
struct event_list *event, const char *field)
|
||
|
|
{
|
||
|
|
if (!event)
|
||
|
|
return;
|
||
|
|
|
||
|
|
event->pid_filter = make_pid_filter(instance, event->pid_filter, field);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_event_filters(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct event_list *event;
|
||
|
|
char *event_filter;
|
||
|
|
int free_it;
|
||
|
|
int len;
|
||
|
|
int common_len = 0;
|
||
|
|
|
||
|
|
if (instance->common_pid_filter)
|
||
|
|
common_len = strlen(instance->common_pid_filter);
|
||
|
|
|
||
|
|
for (event = instance->events; event; event = event->next) {
|
||
|
|
if (!event->neg) {
|
||
|
|
|
||
|
|
free_it = 0;
|
||
|
|
if (event->filter) {
|
||
|
|
if (!instance->common_pid_filter)
|
||
|
|
/*
|
||
|
|
* event->pid_filter is only created if
|
||
|
|
* common_pid_filter is. No need to check that.
|
||
|
|
* Just use the current event->filter.
|
||
|
|
*/
|
||
|
|
event_filter = event->filter;
|
||
|
|
else if (event->pid_filter) {
|
||
|
|
free_it = 1;
|
||
|
|
len = common_len + strlen(event->pid_filter) +
|
||
|
|
strlen(event->filter) + strlen("()&&(||)") + 1;
|
||
|
|
event_filter = malloc(len);
|
||
|
|
if (!event_filter)
|
||
|
|
die("Failed to allocate event_filter");
|
||
|
|
sprintf(event_filter, "(%s)&&(%s||%s)",
|
||
|
|
event->filter, instance->common_pid_filter,
|
||
|
|
event->pid_filter);
|
||
|
|
} else {
|
||
|
|
free_it = 1;
|
||
|
|
len = common_len + strlen(event->filter) +
|
||
|
|
strlen("()&&()") + 1;
|
||
|
|
event_filter = malloc(len);
|
||
|
|
if (!event_filter)
|
||
|
|
die("Failed to allocate event_filter");
|
||
|
|
sprintf(event_filter, "(%s)&&(%s)",
|
||
|
|
event->filter, instance->common_pid_filter);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
/* event->pid_filter only exists when common_pid_filter does */
|
||
|
|
if (!instance->common_pid_filter)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if (event->pid_filter) {
|
||
|
|
free_it = 1;
|
||
|
|
len = common_len + strlen(event->pid_filter) +
|
||
|
|
strlen("||") + 1;
|
||
|
|
event_filter = malloc(len);
|
||
|
|
if (!event_filter)
|
||
|
|
die("Failed to allocate event_filter");
|
||
|
|
sprintf(event_filter, "%s||%s",
|
||
|
|
instance->common_pid_filter, event->pid_filter);
|
||
|
|
} else
|
||
|
|
event_filter = instance->common_pid_filter;
|
||
|
|
}
|
||
|
|
|
||
|
|
update_event(event, event_filter, 1, '1');
|
||
|
|
if (free_it)
|
||
|
|
free(event_filter);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_pid_filters(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct filter_pids *p;
|
||
|
|
char *filter;
|
||
|
|
char *str;
|
||
|
|
int len;
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
fd = open_instance_fd(instance, "set_event_pid",
|
||
|
|
O_WRONLY | O_CLOEXEC | O_TRUNC);
|
||
|
|
if (fd < 0)
|
||
|
|
die("Failed to access set_event_pid");
|
||
|
|
|
||
|
|
len = instance->len_filter_pids + instance->nr_filter_pids;
|
||
|
|
filter = malloc(len);
|
||
|
|
if (!filter)
|
||
|
|
die("Failed to allocate pid filter");
|
||
|
|
|
||
|
|
str = filter;
|
||
|
|
|
||
|
|
for (p = instance->filter_pids; p; p = p->next) {
|
||
|
|
if (p->exclude)
|
||
|
|
continue;
|
||
|
|
len = sprintf(str, "%d ", p->pid);
|
||
|
|
str += len;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (filter == str)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
len = str - filter;
|
||
|
|
str = filter;
|
||
|
|
do {
|
||
|
|
ret = write(fd, str, len);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to write to set_event_pid");
|
||
|
|
str += ret;
|
||
|
|
len -= ret;
|
||
|
|
} while (ret >= 0 && len);
|
||
|
|
|
||
|
|
out:
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_pid_event_filters(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
if (instance->have_set_event_pid)
|
||
|
|
return update_pid_filters(instance);
|
||
|
|
/*
|
||
|
|
* Also make sure that the sched_switch to this pid
|
||
|
|
* and wakeups of this pid are also traced.
|
||
|
|
* Only need to do this if the events are active.
|
||
|
|
*/
|
||
|
|
update_sched_event(instance, instance->sched_switch_event, "next_pid");
|
||
|
|
update_sched_event(instance, instance->sched_wakeup_event, "pid");
|
||
|
|
update_sched_event(instance, instance->sched_wakeup_new_event, "pid");
|
||
|
|
|
||
|
|
update_event_filters(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
#define MASK_STR_MAX 4096 /* Don't expect more than 32768 CPUS */
|
||
|
|
|
||
|
|
static char *alloc_mask_from_hex(struct buffer_instance *instance, const char *str)
|
||
|
|
{
|
||
|
|
char *cpumask;
|
||
|
|
|
||
|
|
if (strcmp(str, "-1") == 0) {
|
||
|
|
/* set all CPUs */
|
||
|
|
int bytes = (instance->cpu_count + 7) / 8;
|
||
|
|
int last = instance->cpu_count % 8;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
cpumask = malloc(MASK_STR_MAX);
|
||
|
|
if (!cpumask)
|
||
|
|
die("can't allocate cpumask");
|
||
|
|
|
||
|
|
if (bytes > (MASK_STR_MAX-1)) {
|
||
|
|
warning("cpumask can't handle more than 32768 CPUS!");
|
||
|
|
bytes = MASK_STR_MAX-1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sprintf(cpumask, "%x", (1 << last) - 1);
|
||
|
|
|
||
|
|
for (i = 1; i < bytes; i++)
|
||
|
|
cpumask[i] = 'f';
|
||
|
|
|
||
|
|
cpumask[i+1] = 0;
|
||
|
|
} else {
|
||
|
|
cpumask = strdup(str);
|
||
|
|
if (!cpumask)
|
||
|
|
die("can't allocate cpumask");
|
||
|
|
}
|
||
|
|
|
||
|
|
return cpumask;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_mask(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
char *path;
|
||
|
|
int fd;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!instance->cpumask)
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "tracing_cpumask");
|
||
|
|
if (!path)
|
||
|
|
die("could not allocate path");
|
||
|
|
reset_save_file(path, RESET_DEFAULT_PRIO);
|
||
|
|
|
||
|
|
ret = stat(path, &st);
|
||
|
|
if (ret < 0) {
|
||
|
|
warning("%s not found", path);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
fd = open(path, O_WRONLY | O_TRUNC);
|
||
|
|
if (fd < 0)
|
||
|
|
die("could not open %s\n", path);
|
||
|
|
|
||
|
|
write(fd, instance->cpumask, strlen(instance->cpumask));
|
||
|
|
|
||
|
|
close(fd);
|
||
|
|
out:
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
free(instance->cpumask);
|
||
|
|
instance->cpumask = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void enable_events(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct event_list *event;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (event = instance->events; event; event = event->next) {
|
||
|
|
if (!event->neg)
|
||
|
|
update_event(event, event->filter, 0, '1');
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Now disable any events */
|
||
|
|
for (event = instance->events; event; event = event->next) {
|
||
|
|
if (event->neg)
|
||
|
|
update_event(event, NULL, 0, '0');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void tracecmd_enable_events(void)
|
||
|
|
{
|
||
|
|
enable_events(first_instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_clock(struct common_record_context *ctx, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
const char *clock;
|
||
|
|
char *path;
|
||
|
|
char *content;
|
||
|
|
char *str;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (instance->clock)
|
||
|
|
clock = instance->clock;
|
||
|
|
else
|
||
|
|
clock = ctx->clock;
|
||
|
|
|
||
|
|
if (!clock)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* The current clock is in brackets, reset it when we are done */
|
||
|
|
content = tracefs_instance_file_read(instance->tracefs,
|
||
|
|
"trace_clock", NULL);
|
||
|
|
|
||
|
|
/* check if first clock is set */
|
||
|
|
if (*content == '[')
|
||
|
|
str = strtok(content+1, "]");
|
||
|
|
else {
|
||
|
|
str = strtok(content, "[");
|
||
|
|
if (!str)
|
||
|
|
die("Can not find clock in trace_clock");
|
||
|
|
str = strtok(NULL, "]");
|
||
|
|
}
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "trace_clock");
|
||
|
|
add_reset_file(path, str, RESET_DEFAULT_PRIO);
|
||
|
|
|
||
|
|
free(content);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
tracefs_instance_file_write(instance->tracefs,
|
||
|
|
"trace_clock", clock);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_max_graph_depth(struct buffer_instance *instance, char *max_graph_depth)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "max_graph_depth");
|
||
|
|
reset_save_file(path, RESET_DEFAULT_PRIO);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
ret = tracefs_instance_file_write(instance->tracefs, "max_graph_depth",
|
||
|
|
max_graph_depth);
|
||
|
|
if (ret < 0)
|
||
|
|
die("could not write to max_graph_depth");
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool check_file_in_dir(char *dir, char *file)
|
||
|
|
{
|
||
|
|
struct stat st;
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = asprintf(&path, "%s/%s", dir, file);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate id file path for %s/%s", dir, file);
|
||
|
|
ret = stat(path, &st);
|
||
|
|
free(path);
|
||
|
|
if (ret < 0 || S_ISDIR(st.st_mode))
|
||
|
|
return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* create_event - create and event descriptor
|
||
|
|
* @instance: instance to use
|
||
|
|
* @path: path to event attribute
|
||
|
|
* @old_event: event descriptor to use as base
|
||
|
|
*
|
||
|
|
* NOTE: the function purpose is to create a data structure to describe
|
||
|
|
* an ftrace event. During the process it becomes handy to change the
|
||
|
|
* string `path`. So, do not rely on the content of `path` after you
|
||
|
|
* invoke this function.
|
||
|
|
*/
|
||
|
|
static struct event_list *
|
||
|
|
create_event(struct buffer_instance *instance, char *path, struct event_list *old_event)
|
||
|
|
{
|
||
|
|
struct event_list *event;
|
||
|
|
struct stat st;
|
||
|
|
char *path_dirname;
|
||
|
|
char *p;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
event = malloc(sizeof(*event));
|
||
|
|
if (!event)
|
||
|
|
die("Failed to allocate event");
|
||
|
|
*event = *old_event;
|
||
|
|
add_event(instance, event);
|
||
|
|
|
||
|
|
if (event->filter || filter_task || instance->filter_pids) {
|
||
|
|
event->filter_file = strdup(path);
|
||
|
|
if (!event->filter_file)
|
||
|
|
die("malloc filter file");
|
||
|
|
}
|
||
|
|
|
||
|
|
path_dirname = dirname(path);
|
||
|
|
|
||
|
|
ret = asprintf(&p, "%s/enable", path_dirname);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate enable path for %s", path);
|
||
|
|
ret = stat(p, &st);
|
||
|
|
if (ret >= 0)
|
||
|
|
event->enable_file = p;
|
||
|
|
else
|
||
|
|
free(p);
|
||
|
|
|
||
|
|
if (old_event->trigger) {
|
||
|
|
if (check_file_in_dir(path_dirname, "trigger")) {
|
||
|
|
event->trigger = strdup(old_event->trigger);
|
||
|
|
ret = asprintf(&p, "%s/trigger", path_dirname);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate trigger path for %s", path);
|
||
|
|
event->trigger_file = p;
|
||
|
|
} else {
|
||
|
|
/* Check if this is event or system.
|
||
|
|
* Systems do not have trigger files by design
|
||
|
|
*/
|
||
|
|
if (check_file_in_dir(path_dirname, "id"))
|
||
|
|
die("trigger specified but not supported by this kernel");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return event;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void make_sched_event(struct buffer_instance *instance,
|
||
|
|
struct event_list **event, struct event_list *sched,
|
||
|
|
const char *sched_path)
|
||
|
|
{
|
||
|
|
char *path_dirname;
|
||
|
|
char *tmp_file;
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
/* Do nothing if the event already exists */
|
||
|
|
if (*event)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* we do not want to corrupt sched->filter_file when using dirname() */
|
||
|
|
tmp_file = strdup(sched->filter_file);
|
||
|
|
if (!tmp_file)
|
||
|
|
die("Failed to allocate path for %s", sched_path);
|
||
|
|
path_dirname = dirname(tmp_file);
|
||
|
|
|
||
|
|
ret = asprintf(&path, "%s/%s/filter", path_dirname, sched_path);
|
||
|
|
free(tmp_file);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate path for %s", sched_path);
|
||
|
|
|
||
|
|
*event = create_event(instance, path, sched);
|
||
|
|
free(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void test_event(struct event_list *event, const char *path,
|
||
|
|
const char *name, struct event_list **save, int len)
|
||
|
|
{
|
||
|
|
path += len - strlen(name);
|
||
|
|
|
||
|
|
if (strcmp(path, name) != 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
*save = event;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void print_event(const char *fmt, ...)
|
||
|
|
{
|
||
|
|
va_list ap;
|
||
|
|
|
||
|
|
if (!show_status)
|
||
|
|
return;
|
||
|
|
|
||
|
|
va_start(ap, fmt);
|
||
|
|
vprintf(fmt, ap);
|
||
|
|
va_end(ap);
|
||
|
|
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static int expand_event_files(struct buffer_instance *instance,
|
||
|
|
const char *file, struct event_list *old_event)
|
||
|
|
{
|
||
|
|
struct event_list **save_event_tail = instance->event_next;
|
||
|
|
struct event_list *sched_event = NULL;
|
||
|
|
struct event_list *event;
|
||
|
|
glob_t globbuf;
|
||
|
|
char *path;
|
||
|
|
char *p;
|
||
|
|
int ret;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
ret = asprintf(&p, "events/%s/filter", file);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate event filter path for %s", file);
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, p);
|
||
|
|
|
||
|
|
globbuf.gl_offs = 0;
|
||
|
|
ret = glob(path, 0, NULL, &globbuf);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
free(p);
|
||
|
|
|
||
|
|
if (ret < 0)
|
||
|
|
die("No filters found");
|
||
|
|
|
||
|
|
for (i = 0; i < globbuf.gl_pathc; i++) {
|
||
|
|
int len;
|
||
|
|
|
||
|
|
path = globbuf.gl_pathv[i];
|
||
|
|
|
||
|
|
event = create_event(instance, path, old_event);
|
||
|
|
print_event("%s\n", path);
|
||
|
|
|
||
|
|
len = strlen(path);
|
||
|
|
|
||
|
|
test_event(event, path, "sched", &sched_event, len);
|
||
|
|
test_event(event, path, "sched/sched_switch", &instance->sched_switch_event, len);
|
||
|
|
test_event(event, path, "sched/sched_wakeup_new", &instance->sched_wakeup_new_event, len);
|
||
|
|
test_event(event, path, "sched/sched_wakeup", &instance->sched_wakeup_event, len);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sched_event && sched_event->filter_file) {
|
||
|
|
/* make sure all sched events exist */
|
||
|
|
make_sched_event(instance, &instance->sched_switch_event,
|
||
|
|
sched_event, "sched_switch");
|
||
|
|
make_sched_event(instance, &instance->sched_wakeup_event,
|
||
|
|
sched_event, "sched_wakeup");
|
||
|
|
make_sched_event(instance, &instance->sched_wakeup_new_event,
|
||
|
|
sched_event, "sched_wakeup_new");
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
globfree(&globbuf);
|
||
|
|
|
||
|
|
/* If the event list tail changed, that means events were added */
|
||
|
|
return save_event_tail == instance->event_next;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int expand_events_all(struct buffer_instance *instance,
|
||
|
|
char *system_name, char *event_name,
|
||
|
|
struct event_list *event)
|
||
|
|
{
|
||
|
|
char *name;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = asprintf(&name, "%s/%s", system_name, event_name);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate system/event for %s/%s",
|
||
|
|
system_name, event_name);
|
||
|
|
ret = expand_event_files(instance, name, event);
|
||
|
|
free(name);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void expand_event(struct buffer_instance *instance, struct event_list *event)
|
||
|
|
{
|
||
|
|
const char *name = event->event;
|
||
|
|
char *str;
|
||
|
|
char *ptr;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* We allow the user to use "all" to enable all events.
|
||
|
|
* Expand event_selection to all systems.
|
||
|
|
*/
|
||
|
|
if (strcmp(name, "all") == 0) {
|
||
|
|
expand_event_files(instance, "*", event);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
str = strdup(name);
|
||
|
|
if (!str)
|
||
|
|
die("Failed to allocate %s string", name);
|
||
|
|
|
||
|
|
ptr = strchr(str, ':');
|
||
|
|
if (ptr) {
|
||
|
|
*ptr = '\0';
|
||
|
|
ptr++;
|
||
|
|
|
||
|
|
if (strlen(ptr))
|
||
|
|
ret = expand_events_all(instance, str, ptr, event);
|
||
|
|
else
|
||
|
|
ret = expand_events_all(instance, str, "*", event);
|
||
|
|
|
||
|
|
if (!ignore_event_not_found && ret)
|
||
|
|
die("No events enabled with %s", name);
|
||
|
|
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* No ':' so enable all matching systems and events */
|
||
|
|
ret = expand_event_files(instance, str, event);
|
||
|
|
ret &= expand_events_all(instance, "*", str, event);
|
||
|
|
if (event->trigger)
|
||
|
|
ret &= expand_events_all(instance, str, "*", event);
|
||
|
|
|
||
|
|
if (!ignore_event_not_found && ret)
|
||
|
|
die("No events enabled with %s", name);
|
||
|
|
|
||
|
|
out:
|
||
|
|
free(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void expand_event_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct event_list *compressed_list = instance->events;
|
||
|
|
struct event_list *event;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
reset_event_list(instance);
|
||
|
|
|
||
|
|
while (compressed_list) {
|
||
|
|
event = compressed_list;
|
||
|
|
compressed_list = event->next;
|
||
|
|
expand_event(instance, event);
|
||
|
|
free(event->trigger);
|
||
|
|
free(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void expand_event_list(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
if (use_old_event_method())
|
||
|
|
return;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
expand_event_instance(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void finish(int sig)
|
||
|
|
{
|
||
|
|
/* all done */
|
||
|
|
if (recorder)
|
||
|
|
tracecmd_stop_recording(recorder);
|
||
|
|
finished = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct addrinfo *do_getaddrinfo(const char *host, unsigned int port,
|
||
|
|
enum port_type type)
|
||
|
|
{
|
||
|
|
struct addrinfo *results;
|
||
|
|
struct addrinfo hints;
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
int s;
|
||
|
|
|
||
|
|
snprintf(buf, BUFSIZ, "%u", port);
|
||
|
|
|
||
|
|
memset(&hints, 0, sizeof(hints));
|
||
|
|
hints.ai_family = AF_UNSPEC;
|
||
|
|
hints.ai_socktype = type == USE_TCP ? SOCK_STREAM : SOCK_DGRAM;
|
||
|
|
|
||
|
|
s = getaddrinfo(host, buf, &hints, &results);
|
||
|
|
if (s != 0) {
|
||
|
|
gai_err = gai_strerror(s);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
dprint("Attached port %s: %d to results: %p\n",
|
||
|
|
type == USE_TCP ? "TCP" : "UDP", port, results);
|
||
|
|
|
||
|
|
return results;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int connect_addr(struct addrinfo *results)
|
||
|
|
{
|
||
|
|
struct addrinfo *rp;
|
||
|
|
int sfd = -1;
|
||
|
|
|
||
|
|
for (rp = results; rp != NULL; rp = rp->ai_next) {
|
||
|
|
sfd = socket(rp->ai_family, rp->ai_socktype,
|
||
|
|
rp->ai_protocol);
|
||
|
|
if (sfd == -1)
|
||
|
|
continue;
|
||
|
|
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
|
||
|
|
break;
|
||
|
|
close(sfd);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (rp == NULL)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
dprint("connect results: %p with fd: %d\n", results, sfd);
|
||
|
|
|
||
|
|
return sfd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int connect_port(const char *host, unsigned int port, enum port_type type)
|
||
|
|
{
|
||
|
|
struct addrinfo *results;
|
||
|
|
int sfd;
|
||
|
|
|
||
|
|
if (type == USE_VSOCK)
|
||
|
|
return trace_vsock_open(atoi(host), port);
|
||
|
|
|
||
|
|
results = do_getaddrinfo(host, port, type);
|
||
|
|
|
||
|
|
if (!results)
|
||
|
|
die("connecting to %s server %s:%u",
|
||
|
|
type == USE_TCP ? "TCP" : "UDP", host, port);
|
||
|
|
|
||
|
|
sfd = connect_addr(results);
|
||
|
|
|
||
|
|
freeaddrinfo(results);
|
||
|
|
|
||
|
|
if (sfd < 0)
|
||
|
|
die("Can not connect to %s server %s:%u",
|
||
|
|
type == USE_TCP ? "TCP" : "UDP", host, port);
|
||
|
|
|
||
|
|
return sfd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int do_accept(int sd)
|
||
|
|
{
|
||
|
|
int cd;
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
dprint("Wait on accept: %d\n", sd);
|
||
|
|
cd = accept(sd, NULL, NULL);
|
||
|
|
dprint("accepted: %d\n", cd);
|
||
|
|
if (cd < 0) {
|
||
|
|
if (errno == EINTR)
|
||
|
|
continue;
|
||
|
|
die("accept");
|
||
|
|
}
|
||
|
|
|
||
|
|
return cd;
|
||
|
|
}
|
||
|
|
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Find all the tasks associated with the guest pid */
|
||
|
|
static void find_tasks(struct trace_guest *guest)
|
||
|
|
{
|
||
|
|
struct dirent *dent;
|
||
|
|
char *path;
|
||
|
|
DIR *dir;
|
||
|
|
int ret;
|
||
|
|
int tasks = 0;
|
||
|
|
|
||
|
|
ret = asprintf(&path, "/proc/%d/task", guest->pid);
|
||
|
|
if (ret < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
dir = opendir(path);
|
||
|
|
free(path);
|
||
|
|
if (!dir)
|
||
|
|
return;
|
||
|
|
|
||
|
|
while ((dent = readdir(dir))) {
|
||
|
|
int *pids;
|
||
|
|
if (!(dent->d_type == DT_DIR && is_digits(dent->d_name)))
|
||
|
|
continue;
|
||
|
|
pids = realloc(guest->task_pids, sizeof(int) * (tasks + 2));
|
||
|
|
if (!pids)
|
||
|
|
break;
|
||
|
|
pids[tasks++] = strtol(dent->d_name, NULL, 0);
|
||
|
|
pids[tasks] = -1;
|
||
|
|
guest->task_pids = pids;
|
||
|
|
}
|
||
|
|
closedir(dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *parse_guest_name(char *gname, int *cid, int *port,
|
||
|
|
struct addrinfo **res)
|
||
|
|
{
|
||
|
|
struct trace_guest *guest = NULL;
|
||
|
|
struct addrinfo *result;
|
||
|
|
char *ip = NULL;
|
||
|
|
char *p;
|
||
|
|
|
||
|
|
*res = NULL;
|
||
|
|
|
||
|
|
*port = -1;
|
||
|
|
for (p = gname + strlen(gname); p > gname; p--) {
|
||
|
|
if (*p == ':')
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (p > gname) {
|
||
|
|
*p = '\0';
|
||
|
|
*port = atoi(p + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
*cid = -1;
|
||
|
|
p = strrchr(gname, '@');
|
||
|
|
if (p) {
|
||
|
|
*p = '\0';
|
||
|
|
*cid = atoi(p + 1);
|
||
|
|
} else if (is_digits(gname)) {
|
||
|
|
*cid = atoi(gname);
|
||
|
|
} else {
|
||
|
|
/* Check if this is an IP address */
|
||
|
|
if (strstr(gname, ":") || strstr(gname, "."))
|
||
|
|
ip = gname;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ip && *cid < 0)
|
||
|
|
read_qemu_guests();
|
||
|
|
|
||
|
|
if (!ip)
|
||
|
|
guest = trace_get_guest(*cid, gname);
|
||
|
|
if (guest) {
|
||
|
|
*cid = guest->cid;
|
||
|
|
/* Mapping not found, search for them */
|
||
|
|
if (!guest->cpu_pid)
|
||
|
|
find_tasks(guest);
|
||
|
|
return guest->name;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Test to see if this is an internet address */
|
||
|
|
result = do_getaddrinfo(gname, *port, USE_TCP);
|
||
|
|
if (!result)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
*res = result;
|
||
|
|
|
||
|
|
return gname;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_prio(int prio)
|
||
|
|
{
|
||
|
|
struct sched_param sp;
|
||
|
|
|
||
|
|
memset(&sp, 0, sizeof(sp));
|
||
|
|
sp.sched_priority = prio;
|
||
|
|
if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0)
|
||
|
|
warning("failed to set priority");
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tracecmd_recorder *
|
||
|
|
create_recorder_instance_pipe(struct buffer_instance *instance,
|
||
|
|
int cpu, int *brass)
|
||
|
|
{
|
||
|
|
struct tracecmd_recorder *recorder;
|
||
|
|
unsigned flags = recorder_flags | TRACECMD_RECORD_BLOCK_SPLICE;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_dir(instance->tracefs);
|
||
|
|
|
||
|
|
if (!path)
|
||
|
|
die("malloc");
|
||
|
|
|
||
|
|
/* This is already the child */
|
||
|
|
close(brass[0]);
|
||
|
|
|
||
|
|
recorder = tracecmd_create_buffer_recorder_fd(brass[1], cpu, flags, path);
|
||
|
|
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
return recorder;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tracecmd_recorder *
|
||
|
|
create_recorder_instance(struct buffer_instance *instance, const char *file, int cpu,
|
||
|
|
int *brass)
|
||
|
|
{
|
||
|
|
struct tracecmd_recorder *record;
|
||
|
|
struct addrinfo *result;
|
||
|
|
char *path;
|
||
|
|
|
||
|
|
if (is_guest(instance)) {
|
||
|
|
int fd;
|
||
|
|
unsigned int flags;
|
||
|
|
|
||
|
|
if (instance->use_fifos)
|
||
|
|
fd = instance->fds[cpu];
|
||
|
|
else if (is_network(instance)) {
|
||
|
|
result = do_getaddrinfo(instance->name,
|
||
|
|
instance->client_ports[cpu],
|
||
|
|
instance->port_type);
|
||
|
|
if (!result)
|
||
|
|
die("Failed to connect to %s port %d\n",
|
||
|
|
instance->name,
|
||
|
|
instance->client_ports[cpu]);
|
||
|
|
fd = connect_addr(result);
|
||
|
|
freeaddrinfo(result);
|
||
|
|
} else
|
||
|
|
fd = trace_vsock_open(instance->cid, instance->client_ports[cpu]);
|
||
|
|
if (fd < 0)
|
||
|
|
die("Failed to connect to agent");
|
||
|
|
|
||
|
|
flags = recorder_flags;
|
||
|
|
if (instance->use_fifos)
|
||
|
|
flags |= TRACECMD_RECORD_NOBRASS;
|
||
|
|
else if (!trace_vsock_can_splice_read())
|
||
|
|
flags |= TRACECMD_RECORD_NOSPLICE;
|
||
|
|
return tracecmd_create_recorder_virt(file, cpu, flags, fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (brass)
|
||
|
|
return create_recorder_instance_pipe(instance, cpu, brass);
|
||
|
|
|
||
|
|
if (!tracefs_instance_get_name(instance->tracefs))
|
||
|
|
return tracecmd_create_recorder_maxkb(file, cpu, recorder_flags, max_kb);
|
||
|
|
|
||
|
|
path = tracefs_instance_get_dir(instance->tracefs);
|
||
|
|
|
||
|
|
record = tracecmd_create_buffer_recorder_maxkb(file, cpu, recorder_flags,
|
||
|
|
path, max_kb);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
return record;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If extract is set, then this is going to set up the recorder,
|
||
|
|
* connections and exit as the tracing is serialized by a single thread.
|
||
|
|
*/
|
||
|
|
static int create_recorder(struct buffer_instance *instance, int cpu,
|
||
|
|
enum trace_type type, int *brass)
|
||
|
|
{
|
||
|
|
long ret;
|
||
|
|
char *file;
|
||
|
|
pid_t pid;
|
||
|
|
|
||
|
|
if (type != TRACE_TYPE_EXTRACT) {
|
||
|
|
|
||
|
|
pid = fork();
|
||
|
|
if (pid < 0)
|
||
|
|
die("fork");
|
||
|
|
|
||
|
|
if (pid)
|
||
|
|
return pid;
|
||
|
|
|
||
|
|
signal(SIGINT, SIG_IGN);
|
||
|
|
signal(SIGUSR1, finish);
|
||
|
|
|
||
|
|
if (rt_prio)
|
||
|
|
set_prio(rt_prio);
|
||
|
|
|
||
|
|
/* do not kill tasks on error */
|
||
|
|
instance->cpu_count = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((instance->client_ports && !is_guest(instance)) || is_agent(instance)) {
|
||
|
|
unsigned int flags = recorder_flags;
|
||
|
|
char *path = NULL;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (is_agent(instance)) {
|
||
|
|
if (instance->use_fifos)
|
||
|
|
fd = instance->fds[cpu];
|
||
|
|
else {
|
||
|
|
again:
|
||
|
|
fd = do_accept(instance->fds[cpu]);
|
||
|
|
if (instance->host &&
|
||
|
|
!trace_net_cmp_connection_fd(fd, instance->host)) {
|
||
|
|
dprint("Client does not match '%s' for cpu:%d\n",
|
||
|
|
instance->host, cpu);
|
||
|
|
goto again;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
fd = connect_port(host, instance->client_ports[cpu],
|
||
|
|
instance->port_type);
|
||
|
|
}
|
||
|
|
if (fd < 0)
|
||
|
|
die("Failed connecting to client");
|
||
|
|
if (tracefs_instance_get_name(instance->tracefs) && !is_agent(instance)) {
|
||
|
|
path = tracefs_instance_get_dir(instance->tracefs);
|
||
|
|
} else {
|
||
|
|
const char *dir = tracefs_tracing_dir();
|
||
|
|
|
||
|
|
if (dir)
|
||
|
|
path = strdup(dir);
|
||
|
|
}
|
||
|
|
if (!path)
|
||
|
|
die("can't get the tracing directory");
|
||
|
|
|
||
|
|
recorder = tracecmd_create_buffer_recorder_fd(fd, cpu, flags, path);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
} else {
|
||
|
|
file = get_temp_file(instance, cpu);
|
||
|
|
recorder = create_recorder_instance(instance, file, cpu, brass);
|
||
|
|
put_temp_file(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!recorder)
|
||
|
|
die ("can't create recorder");
|
||
|
|
|
||
|
|
if (type == TRACE_TYPE_EXTRACT) {
|
||
|
|
ret = tracecmd_flush_recording(recorder);
|
||
|
|
tracecmd_free_recorder(recorder);
|
||
|
|
recorder = NULL;
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (!finished) {
|
||
|
|
if (tracecmd_start_recording(recorder, sleep_time) < 0)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
tracecmd_free_recorder(recorder);
|
||
|
|
recorder = NULL;
|
||
|
|
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check_first_msg_from_server(struct tracecmd_msg_handle *msg_handle)
|
||
|
|
{
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
|
||
|
|
read(msg_handle->fd, buf, 8);
|
||
|
|
|
||
|
|
/* Make sure the server is the tracecmd server */
|
||
|
|
if (memcmp(buf, "tracecmd", 8) != 0)
|
||
|
|
die("server not tracecmd server");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void communicate_with_listener_v1(struct tracecmd_msg_handle *msg_handle,
|
||
|
|
struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
unsigned int *client_ports;
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
ssize_t n;
|
||
|
|
int cpu, i;
|
||
|
|
|
||
|
|
check_first_msg_from_server(msg_handle);
|
||
|
|
|
||
|
|
/* write the number of CPUs we have (in ASCII) */
|
||
|
|
sprintf(buf, "%d", local_cpu_count);
|
||
|
|
|
||
|
|
/* include \0 */
|
||
|
|
write(msg_handle->fd, buf, strlen(buf)+1);
|
||
|
|
|
||
|
|
/* write the pagesize (in ASCII) */
|
||
|
|
sprintf(buf, "%d", page_size);
|
||
|
|
|
||
|
|
/* include \0 */
|
||
|
|
write(msg_handle->fd, buf, strlen(buf)+1);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If we are using IPV4 and our page size is greater than
|
||
|
|
* or equal to 64K, we need to punt and use TCP. :-(
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* TODO, test for ipv4 */
|
||
|
|
if (page_size >= UDP_MAX_PACKET) {
|
||
|
|
warning("page size too big for UDP using TCP in live read");
|
||
|
|
instance->port_type = USE_TCP;
|
||
|
|
msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (instance->port_type == USE_TCP) {
|
||
|
|
/* Send one option */
|
||
|
|
write(msg_handle->fd, "1", 2);
|
||
|
|
/* Size 4 */
|
||
|
|
write(msg_handle->fd, "4", 2);
|
||
|
|
/* use TCP */
|
||
|
|
write(msg_handle->fd, "TCP", 4);
|
||
|
|
} else
|
||
|
|
/* No options */
|
||
|
|
write(msg_handle->fd, "0", 2);
|
||
|
|
|
||
|
|
client_ports = malloc(local_cpu_count * sizeof(*client_ports));
|
||
|
|
if (!client_ports)
|
||
|
|
die("Failed to allocate client ports for %d cpus", local_cpu_count);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Now we will receive back a comma deliminated list
|
||
|
|
* of client ports to connect to.
|
||
|
|
*/
|
||
|
|
for (cpu = 0; cpu < local_cpu_count; cpu++) {
|
||
|
|
for (i = 0; i < BUFSIZ; i++) {
|
||
|
|
n = read(msg_handle->fd, buf+i, 1);
|
||
|
|
if (n != 1)
|
||
|
|
die("Error, reading server ports");
|
||
|
|
if (!buf[i] || buf[i] == ',')
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (i == BUFSIZ)
|
||
|
|
die("read bad port number");
|
||
|
|
buf[i] = 0;
|
||
|
|
client_ports[cpu] = atoi(buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
instance->client_ports = client_ports;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void communicate_with_listener_v3(struct tracecmd_msg_handle *msg_handle,
|
||
|
|
unsigned int **client_ports)
|
||
|
|
{
|
||
|
|
if (tracecmd_msg_send_init_data(msg_handle, client_ports) < 0)
|
||
|
|
die("Cannot communicate with server");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check_protocol_version(struct tracecmd_msg_handle *msg_handle)
|
||
|
|
{
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
int fd = msg_handle->fd;
|
||
|
|
int n;
|
||
|
|
|
||
|
|
check_first_msg_from_server(msg_handle);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Write the protocol version, the magic number, and the dummy
|
||
|
|
* option(0) (in ASCII). The client understands whether the client
|
||
|
|
* uses the v3 protocol or not by checking a reply message from the
|
||
|
|
* server. If the message is "V3", the server uses v3 protocol. On the
|
||
|
|
* other hands, if the message is just number strings, the server
|
||
|
|
* returned port numbers. So, in that time, the client understands the
|
||
|
|
* server uses the v1 protocol. However, the old server tells the
|
||
|
|
* client port numbers after reading cpu_count, page_size, and option.
|
||
|
|
* So, we add the dummy number (the magic number and 0 option) to the
|
||
|
|
* first client message.
|
||
|
|
*/
|
||
|
|
write(fd, V3_CPU, sizeof(V3_CPU));
|
||
|
|
|
||
|
|
buf[0] = 0;
|
||
|
|
|
||
|
|
/* read a reply message */
|
||
|
|
n = read(fd, buf, BUFSIZ);
|
||
|
|
|
||
|
|
if (n < 0 || !buf[0]) {
|
||
|
|
/* the server uses the v1 protocol, so we'll use it */
|
||
|
|
msg_handle->version = V1_PROTOCOL;
|
||
|
|
tracecmd_plog("Use the v1 protocol\n");
|
||
|
|
} else {
|
||
|
|
if (memcmp(buf, "V3", n) != 0)
|
||
|
|
die("Cannot handle the protocol %s", buf);
|
||
|
|
/* OK, let's use v3 protocol */
|
||
|
|
write(fd, V3_MAGIC, sizeof(V3_MAGIC));
|
||
|
|
|
||
|
|
n = read(fd, buf, BUFSIZ - 1);
|
||
|
|
if (n != 2 || memcmp(buf, "OK", 2) != 0) {
|
||
|
|
if (n < 0)
|
||
|
|
n = 0;
|
||
|
|
buf[n] = 0;
|
||
|
|
die("Cannot handle the protocol %s", buf);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int connect_vsock(char *vhost)
|
||
|
|
{
|
||
|
|
char *cid;
|
||
|
|
char *port;
|
||
|
|
char *p;
|
||
|
|
int sd;
|
||
|
|
|
||
|
|
host = strdup(vhost);
|
||
|
|
if (!host)
|
||
|
|
die("alloctating server");
|
||
|
|
|
||
|
|
cid = strtok_r(host, ":", &p);
|
||
|
|
port = strtok_r(NULL, "", &p);
|
||
|
|
|
||
|
|
if (!port)
|
||
|
|
die("vsocket must have format of 'CID:PORT'");
|
||
|
|
|
||
|
|
sd = trace_vsock_open(atoi(cid), atoi(port));
|
||
|
|
|
||
|
|
return sd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int connect_ip(char *thost)
|
||
|
|
{
|
||
|
|
struct addrinfo *result;
|
||
|
|
int sfd;
|
||
|
|
char *server;
|
||
|
|
char *port;
|
||
|
|
char *p;
|
||
|
|
|
||
|
|
if (!strchr(host, ':')) {
|
||
|
|
server = strdup("localhost");
|
||
|
|
if (!server)
|
||
|
|
die("alloctating server");
|
||
|
|
port = thost;
|
||
|
|
host = server;
|
||
|
|
} else {
|
||
|
|
host = strdup(thost);
|
||
|
|
if (!host)
|
||
|
|
die("alloctating server");
|
||
|
|
server = strtok_r(host, ":", &p);
|
||
|
|
port = strtok_r(NULL, ":", &p);
|
||
|
|
}
|
||
|
|
|
||
|
|
result = do_getaddrinfo(server, atoi(port), USE_TCP);
|
||
|
|
if (!result)
|
||
|
|
die("getaddrinfo: %s", gai_err);
|
||
|
|
|
||
|
|
sfd = connect_addr(result);
|
||
|
|
|
||
|
|
freeaddrinfo(result);
|
||
|
|
|
||
|
|
if (sfd < 0)
|
||
|
|
die("Can not connect to %s:%s", server, port);
|
||
|
|
|
||
|
|
return sfd;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tracecmd_msg_handle *setup_network(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct tracecmd_msg_handle *msg_handle = NULL;
|
||
|
|
enum port_type type = instance->port_type;
|
||
|
|
int sfd;
|
||
|
|
|
||
|
|
again:
|
||
|
|
switch (type) {
|
||
|
|
case USE_VSOCK:
|
||
|
|
sfd = connect_vsock(host);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
sfd = connect_ip(host);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sfd < 0)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
if (msg_handle) {
|
||
|
|
msg_handle->fd = sfd;
|
||
|
|
} else {
|
||
|
|
msg_handle = tracecmd_msg_handle_alloc(sfd, 0);
|
||
|
|
if (!msg_handle)
|
||
|
|
die("Failed to allocate message handle");
|
||
|
|
|
||
|
|
msg_handle->cpu_count = local_cpu_count;
|
||
|
|
msg_handle->version = V3_PROTOCOL;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case USE_TCP:
|
||
|
|
msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP;
|
||
|
|
break;
|
||
|
|
case USE_VSOCK:
|
||
|
|
msg_handle->flags |= TRACECMD_MSG_FL_USE_VSOCK;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (msg_handle->version == V3_PROTOCOL) {
|
||
|
|
check_protocol_version(msg_handle);
|
||
|
|
if (msg_handle->version == V1_PROTOCOL) {
|
||
|
|
/* reconnect to the server for using the v1 protocol */
|
||
|
|
close(sfd);
|
||
|
|
free(host);
|
||
|
|
goto again;
|
||
|
|
}
|
||
|
|
communicate_with_listener_v3(msg_handle, &instance->client_ports);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (msg_handle->version == V1_PROTOCOL)
|
||
|
|
communicate_with_listener_v1(msg_handle, instance);
|
||
|
|
|
||
|
|
return msg_handle;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_options(struct tracecmd_output *handle, struct common_record_context *ctx);
|
||
|
|
|
||
|
|
static struct tracecmd_output *create_net_output(struct common_record_context *ctx,
|
||
|
|
struct tracecmd_msg_handle *msg_handle)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *out;
|
||
|
|
|
||
|
|
out = tracecmd_output_create(NULL);
|
||
|
|
if (!out)
|
||
|
|
return NULL;
|
||
|
|
if (ctx->file_version && tracecmd_output_set_version(out, ctx->file_version))
|
||
|
|
goto error;
|
||
|
|
if (tracecmd_output_set_msg(out, msg_handle))
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
if (ctx->compression) {
|
||
|
|
if (tracecmd_output_set_compression(out, ctx->compression))
|
||
|
|
goto error;
|
||
|
|
} else if (ctx->file_version >= FILE_VERSION_COMPRESSION) {
|
||
|
|
tracecmd_output_set_compression(out, "any");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tracecmd_output_write_headers(out, listed_events))
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
return out;
|
||
|
|
error:
|
||
|
|
tracecmd_output_close(out);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tracecmd_msg_handle *
|
||
|
|
setup_connection(struct buffer_instance *instance, struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct tracecmd_msg_handle *msg_handle = NULL;
|
||
|
|
struct tracecmd_output *network_handle = NULL;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
msg_handle = setup_network(instance);
|
||
|
|
if (!msg_handle)
|
||
|
|
die("Failed to make connection");
|
||
|
|
|
||
|
|
/* Now create the handle through this socket */
|
||
|
|
if (msg_handle->version == V3_PROTOCOL) {
|
||
|
|
network_handle = create_net_output(ctx, msg_handle);
|
||
|
|
if (!network_handle)
|
||
|
|
goto error;
|
||
|
|
tracecmd_set_quiet(network_handle, quiet);
|
||
|
|
add_options(network_handle, ctx);
|
||
|
|
ret = tracecmd_write_cmdlines(network_handle);
|
||
|
|
if (ret)
|
||
|
|
goto error;
|
||
|
|
ret = tracecmd_write_cpus(network_handle, instance->cpu_count);
|
||
|
|
if (ret)
|
||
|
|
goto error;
|
||
|
|
ret = tracecmd_write_buffer_info(network_handle);
|
||
|
|
if (ret)
|
||
|
|
goto error;
|
||
|
|
ret = tracecmd_write_options(network_handle);
|
||
|
|
if (ret)
|
||
|
|
goto error;
|
||
|
|
ret = tracecmd_msg_finish_sending_data(msg_handle);
|
||
|
|
if (ret)
|
||
|
|
goto error;
|
||
|
|
} else {
|
||
|
|
network_handle = tracecmd_output_create_fd(msg_handle->fd);
|
||
|
|
if (!network_handle)
|
||
|
|
goto error;
|
||
|
|
if (tracecmd_output_set_version(network_handle, ctx->file_version))
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
if (ctx->compression) {
|
||
|
|
if (tracecmd_output_set_compression(network_handle, ctx->compression))
|
||
|
|
goto error;
|
||
|
|
} else if (ctx->file_version >= FILE_VERSION_COMPRESSION) {
|
||
|
|
tracecmd_output_set_compression(network_handle, "any");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tracecmd_output_write_headers(network_handle, listed_events))
|
||
|
|
goto error;
|
||
|
|
tracecmd_set_quiet(network_handle, quiet);
|
||
|
|
}
|
||
|
|
|
||
|
|
instance->network_handle = network_handle;
|
||
|
|
|
||
|
|
/* OK, we are all set, let'r rip! */
|
||
|
|
return msg_handle;
|
||
|
|
|
||
|
|
error:
|
||
|
|
if (msg_handle)
|
||
|
|
tracecmd_msg_handle_close(msg_handle);
|
||
|
|
if (network_handle)
|
||
|
|
tracecmd_output_close(network_handle);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void finish_network(struct tracecmd_msg_handle *msg_handle)
|
||
|
|
{
|
||
|
|
if (msg_handle->version == V3_PROTOCOL)
|
||
|
|
tracecmd_msg_send_close_msg(msg_handle);
|
||
|
|
tracecmd_msg_handle_close(msg_handle);
|
||
|
|
free(host);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int open_guest_fifos(const char *guest, int **fds)
|
||
|
|
{
|
||
|
|
char path[PATH_MAX];
|
||
|
|
int i, fd, flags;
|
||
|
|
|
||
|
|
for (i = 0; ; i++) {
|
||
|
|
snprintf(path, sizeof(path), GUEST_FIFO_FMT ".out", guest, i);
|
||
|
|
|
||
|
|
/* O_NONBLOCK so we don't wait for writers */
|
||
|
|
fd = open(path, O_RDONLY | O_NONBLOCK);
|
||
|
|
if (fd < 0)
|
||
|
|
break;
|
||
|
|
|
||
|
|
/* Success, now clear O_NONBLOCK */
|
||
|
|
flags = fcntl(fd, F_GETFL);
|
||
|
|
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
||
|
|
|
||
|
|
*fds = realloc(*fds, i + 1);
|
||
|
|
(*fds)[i] = fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
return i;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct trace_mapping {
|
||
|
|
struct tep_event *kvm_entry;
|
||
|
|
struct tep_format_field *vcpu_id;
|
||
|
|
struct tep_format_field *common_pid;
|
||
|
|
int *pids;
|
||
|
|
int *map;
|
||
|
|
int max_cpus;
|
||
|
|
};
|
||
|
|
|
||
|
|
static void start_mapping_vcpus(struct trace_guest *guest)
|
||
|
|
{
|
||
|
|
char *pids = NULL;
|
||
|
|
char *t;
|
||
|
|
int len = 0;
|
||
|
|
int s;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (!guest->task_pids)
|
||
|
|
return;
|
||
|
|
|
||
|
|
guest->instance = tracefs_instance_create("map_guest_pids");
|
||
|
|
if (!guest->instance)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (i = 0; guest->task_pids[i] >= 0; i++) {
|
||
|
|
s = snprintf(NULL, 0, "%d ", guest->task_pids[i]);
|
||
|
|
t = realloc(pids, len + s + 1);
|
||
|
|
if (!t) {
|
||
|
|
free(pids);
|
||
|
|
pids = NULL;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
pids = t;
|
||
|
|
sprintf(pids + len, "%d ", guest->task_pids[i]);
|
||
|
|
len += s;
|
||
|
|
}
|
||
|
|
if (pids) {
|
||
|
|
tracefs_instance_file_write(guest->instance, "set_event_pid", pids);
|
||
|
|
free(pids);
|
||
|
|
}
|
||
|
|
tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "1");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int map_vcpus(struct tep_event *event, struct tep_record *record,
|
||
|
|
int cpu, void *context)
|
||
|
|
{
|
||
|
|
struct trace_mapping *tmap = context;
|
||
|
|
unsigned long long val;
|
||
|
|
int type;
|
||
|
|
int pid;
|
||
|
|
int ret;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/* Do we have junk in the buffer? */
|
||
|
|
type = tep_data_type(event->tep, record);
|
||
|
|
if (type != tmap->kvm_entry->id)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ret = tep_read_number_field(tmap->common_pid, record->data, &val);
|
||
|
|
if (ret < 0)
|
||
|
|
return 0;
|
||
|
|
pid = (int)val;
|
||
|
|
|
||
|
|
for (i = 0; tmap->pids[i] >= 0; i++) {
|
||
|
|
if (pid == tmap->pids[i])
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
/* Is this thread one we care about ? */
|
||
|
|
if (tmap->pids[i] < 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ret = tep_read_number_field(tmap->vcpu_id, record->data, &val);
|
||
|
|
if (ret < 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
cpu = (int)val;
|
||
|
|
|
||
|
|
/* Sanity check, warn? */
|
||
|
|
if (cpu >= tmap->max_cpus)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
/* Already have this one? Should we check if it is the same? */
|
||
|
|
if (tmap->map[cpu] >= 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
tmap->map[cpu] = pid;
|
||
|
|
|
||
|
|
/* Did we get them all */
|
||
|
|
for (i = 0; i < tmap->max_cpus; i++) {
|
||
|
|
if (tmap->map[i] < 0)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return i == tmap->max_cpus;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void stop_mapping_vcpus(struct buffer_instance *instance,
|
||
|
|
struct trace_guest *guest)
|
||
|
|
{
|
||
|
|
struct trace_mapping tmap = { };
|
||
|
|
struct tep_handle *tep;
|
||
|
|
const char *systems[] = { "kvm", NULL };
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (!guest->instance)
|
||
|
|
return;
|
||
|
|
|
||
|
|
tmap.pids = guest->task_pids;
|
||
|
|
tmap.max_cpus = instance->cpu_count;
|
||
|
|
|
||
|
|
tmap.map = malloc(sizeof(*tmap.map) * tmap.max_cpus);
|
||
|
|
if (!tmap.map)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (i = 0; i < tmap.max_cpus; i++)
|
||
|
|
tmap.map[i] = -1;
|
||
|
|
|
||
|
|
tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "0");
|
||
|
|
|
||
|
|
tep = tracefs_local_events_system(NULL, systems);
|
||
|
|
if (!tep)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
tmap.kvm_entry = tep_find_event_by_name(tep, "kvm", "kvm_entry");
|
||
|
|
if (!tmap.kvm_entry)
|
||
|
|
goto out_free;
|
||
|
|
|
||
|
|
tmap.vcpu_id = tep_find_field(tmap.kvm_entry, "vcpu_id");
|
||
|
|
if (!tmap.vcpu_id)
|
||
|
|
goto out_free;
|
||
|
|
|
||
|
|
tmap.common_pid = tep_find_any_field(tmap.kvm_entry, "common_pid");
|
||
|
|
if (!tmap.common_pid)
|
||
|
|
goto out_free;
|
||
|
|
|
||
|
|
tracefs_iterate_raw_events(tep, guest->instance, NULL, 0, map_vcpus, &tmap);
|
||
|
|
|
||
|
|
for (i = 0; i < tmap.max_cpus; i++) {
|
||
|
|
if (tmap.map[i] < 0)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
/* We found all the mapped CPUs */
|
||
|
|
if (i == tmap.max_cpus) {
|
||
|
|
guest->cpu_pid = tmap.map;
|
||
|
|
guest->cpu_max = tmap.max_cpus;
|
||
|
|
tmap.map = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
out_free:
|
||
|
|
tep_free(tep);
|
||
|
|
out:
|
||
|
|
free(tmap.map);
|
||
|
|
tracefs_instance_destroy(guest->instance);
|
||
|
|
tracefs_instance_free(guest->instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int host_tsync(struct common_record_context *ctx,
|
||
|
|
struct buffer_instance *instance,
|
||
|
|
unsigned int tsync_port, char *proto)
|
||
|
|
{
|
||
|
|
struct trace_guest *guest;
|
||
|
|
int guest_pid = -1;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (!proto)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (is_network(instance)) {
|
||
|
|
fd = connect_port(instance->name, tsync_port,
|
||
|
|
instance->port_type);
|
||
|
|
} else {
|
||
|
|
guest = trace_get_guest(instance->cid, NULL);
|
||
|
|
if (guest == NULL)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
guest_pid = guest->pid;
|
||
|
|
start_mapping_vcpus(guest);
|
||
|
|
fd = trace_vsock_open(instance->cid, tsync_port);
|
||
|
|
}
|
||
|
|
|
||
|
|
instance->tsync = tracecmd_tsync_with_guest(top_instance.trace_id,
|
||
|
|
instance->tsync_loop_interval,
|
||
|
|
fd, guest_pid,
|
||
|
|
instance->cpu_count,
|
||
|
|
proto, ctx->clock);
|
||
|
|
if (!is_network(instance))
|
||
|
|
stop_mapping_vcpus(instance, guest);
|
||
|
|
|
||
|
|
if (!instance->tsync)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void connect_to_agent(struct common_record_context *ctx,
|
||
|
|
struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct tracecmd_tsync_protos *protos = NULL;
|
||
|
|
int sd, ret, nr_fifos, nr_cpus, page_size;
|
||
|
|
struct tracecmd_msg_handle *msg_handle;
|
||
|
|
enum tracecmd_time_sync_role role;
|
||
|
|
char *tsync_protos_reply = NULL;
|
||
|
|
unsigned int tsync_port = 0;
|
||
|
|
unsigned int *ports;
|
||
|
|
int i, *fds = NULL;
|
||
|
|
bool use_fifos = false;
|
||
|
|
|
||
|
|
if (!no_fifos) {
|
||
|
|
nr_fifos = open_guest_fifos(instance->name, &fds);
|
||
|
|
use_fifos = nr_fifos > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ctx->instance->result) {
|
||
|
|
role = TRACECMD_TIME_SYNC_ROLE_CLIENT;
|
||
|
|
sd = connect_addr(ctx->instance->result);
|
||
|
|
if (sd < 0)
|
||
|
|
die("Failed to connect to host %s:%u",
|
||
|
|
instance->name, instance->port);
|
||
|
|
} else {
|
||
|
|
role = TRACECMD_TIME_SYNC_ROLE_HOST;
|
||
|
|
sd = trace_vsock_open(instance->cid, instance->port);
|
||
|
|
if (sd < 0)
|
||
|
|
die("Failed to connect to vsocket @%u:%u",
|
||
|
|
instance->cid, instance->port);
|
||
|
|
}
|
||
|
|
|
||
|
|
msg_handle = tracecmd_msg_handle_alloc(sd, 0);
|
||
|
|
if (!msg_handle)
|
||
|
|
die("Failed to allocate message handle");
|
||
|
|
|
||
|
|
if (!instance->clock)
|
||
|
|
instance->clock = tracefs_get_clock(NULL);
|
||
|
|
|
||
|
|
if (instance->tsync_loop_interval >= 0)
|
||
|
|
tracecmd_tsync_proto_getall(&protos, instance->clock, role);
|
||
|
|
|
||
|
|
ret = tracecmd_msg_send_trace_req(msg_handle, instance->argc,
|
||
|
|
instance->argv, use_fifos,
|
||
|
|
top_instance.trace_id, protos);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to send trace request");
|
||
|
|
|
||
|
|
if (protos) {
|
||
|
|
free(protos->names);
|
||
|
|
free(protos);
|
||
|
|
}
|
||
|
|
ret = tracecmd_msg_recv_trace_resp(msg_handle, &nr_cpus, &page_size,
|
||
|
|
&ports, &use_fifos,
|
||
|
|
&instance->trace_id,
|
||
|
|
&tsync_protos_reply, &tsync_port);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to receive trace response %d", ret);
|
||
|
|
if (tsync_protos_reply && tsync_protos_reply[0]) {
|
||
|
|
if (tsync_proto_is_supported(tsync_protos_reply)) {
|
||
|
|
printf("Negotiated %s time sync protocol with guest %s\n",
|
||
|
|
tsync_protos_reply,
|
||
|
|
instance->name);
|
||
|
|
instance->cpu_count = nr_cpus;
|
||
|
|
host_tsync(ctx, instance, tsync_port, tsync_protos_reply);
|
||
|
|
} else
|
||
|
|
warning("Failed to negotiate timestamps synchronization with the guest");
|
||
|
|
}
|
||
|
|
free(tsync_protos_reply);
|
||
|
|
|
||
|
|
if (use_fifos) {
|
||
|
|
if (nr_cpus != nr_fifos) {
|
||
|
|
warning("number of FIFOs (%d) for guest %s differs "
|
||
|
|
"from number of virtual CPUs (%d)",
|
||
|
|
nr_fifos, instance->name, nr_cpus);
|
||
|
|
nr_cpus = nr_cpus < nr_fifos ? nr_cpus : nr_fifos;
|
||
|
|
}
|
||
|
|
free(ports);
|
||
|
|
instance->fds = fds;
|
||
|
|
} else {
|
||
|
|
for (i = 0; i < nr_fifos; i++)
|
||
|
|
close(fds[i]);
|
||
|
|
free(fds);
|
||
|
|
instance->client_ports = ports;
|
||
|
|
}
|
||
|
|
|
||
|
|
instance->use_fifos = use_fifos;
|
||
|
|
instance->cpu_count = nr_cpus;
|
||
|
|
|
||
|
|
/* the msg_handle now points to the guest fd */
|
||
|
|
instance->msg_handle = msg_handle;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void setup_guest(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct tracecmd_msg_handle *msg_handle = instance->msg_handle;
|
||
|
|
const char *output_file = instance->output_file;
|
||
|
|
char *file;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
/* Create a place to store the guest meta data */
|
||
|
|
file = trace_get_guest_file(output_file, instance->name);
|
||
|
|
if (!file)
|
||
|
|
die("Failed to allocate memory");
|
||
|
|
|
||
|
|
free(instance->output_file);
|
||
|
|
instance->output_file = file;
|
||
|
|
|
||
|
|
fd = open(file, O_CREAT|O_WRONLY|O_TRUNC, 0644);
|
||
|
|
if (fd < 0)
|
||
|
|
die("Failed to open %s", file);
|
||
|
|
|
||
|
|
/* Start reading tracing metadata */
|
||
|
|
if (tracecmd_msg_read_data(msg_handle, fd))
|
||
|
|
die("Failed receiving metadata");
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void setup_agent(struct buffer_instance *instance,
|
||
|
|
struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *network_handle;
|
||
|
|
|
||
|
|
network_handle = create_net_output(ctx, instance->msg_handle);
|
||
|
|
add_options(network_handle, ctx);
|
||
|
|
tracecmd_write_cmdlines(network_handle);
|
||
|
|
tracecmd_write_cpus(network_handle, instance->cpu_count);
|
||
|
|
tracecmd_write_buffer_info(network_handle);
|
||
|
|
tracecmd_write_options(network_handle);
|
||
|
|
tracecmd_write_meta_strings(network_handle);
|
||
|
|
tracecmd_msg_finish_sending_data(instance->msg_handle);
|
||
|
|
instance->network_handle = network_handle;
|
||
|
|
}
|
||
|
|
|
||
|
|
void start_threads(enum trace_type type, struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int total_cpu_count = 0;
|
||
|
|
int i = 0;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
/* Start the connection now to find out how many CPUs we need */
|
||
|
|
if (is_guest(instance))
|
||
|
|
connect_to_agent(ctx, instance);
|
||
|
|
total_cpu_count += instance->cpu_count;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* make a thread for every CPU we have */
|
||
|
|
pids = calloc(total_cpu_count * (buffers + 1), sizeof(*pids));
|
||
|
|
if (!pids)
|
||
|
|
die("Failed to allocate pids for %d cpus", total_cpu_count);
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
int *brass = NULL;
|
||
|
|
int x, pid;
|
||
|
|
|
||
|
|
if (is_agent(instance)) {
|
||
|
|
setup_agent(instance, ctx);
|
||
|
|
} else if (is_guest(instance)) {
|
||
|
|
setup_guest(instance);
|
||
|
|
} else if (host) {
|
||
|
|
instance->msg_handle = setup_connection(instance, ctx);
|
||
|
|
if (!instance->msg_handle)
|
||
|
|
die("Failed to make connection");
|
||
|
|
}
|
||
|
|
|
||
|
|
for (x = 0; x < instance->cpu_count; x++) {
|
||
|
|
if (type & TRACE_TYPE_STREAM) {
|
||
|
|
brass = pids[i].brass;
|
||
|
|
ret = pipe(brass);
|
||
|
|
if (ret < 0)
|
||
|
|
die("pipe");
|
||
|
|
pids[i].stream = trace_stream_init(instance, x,
|
||
|
|
brass[0],
|
||
|
|
instance->cpu_count,
|
||
|
|
hooks, handle_init,
|
||
|
|
ctx->global);
|
||
|
|
if (!pids[i].stream)
|
||
|
|
die("Creating stream for %d", i);
|
||
|
|
} else
|
||
|
|
pids[i].brass[0] = -1;
|
||
|
|
pids[i].cpu = x;
|
||
|
|
pids[i].instance = instance;
|
||
|
|
/* Make sure all output is flushed before forking */
|
||
|
|
fflush(stdout);
|
||
|
|
pid = pids[i++].pid = create_recorder(instance, x, type, brass);
|
||
|
|
if (brass)
|
||
|
|
close(brass[1]);
|
||
|
|
if (pid > 0)
|
||
|
|
add_filter_pid(instance, pid, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
recorder_threads = i;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void touch_file(const char *file)
|
||
|
|
{
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
|
|
if (fd < 0)
|
||
|
|
die("could not create file %s\n", file);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void append_buffer(struct tracecmd_output *handle,
|
||
|
|
struct buffer_instance *instance,
|
||
|
|
char **temp_files)
|
||
|
|
{
|
||
|
|
int cpu_count = instance->cpu_count;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Since we can record remote and virtual machines in the same file
|
||
|
|
* as the host, the buffers may no longer have matching number of
|
||
|
|
* CPU data as the host. For backward compatibility for older
|
||
|
|
* trace-cmd versions, which will blindly read the number of CPUs
|
||
|
|
* for each buffer instance as there are for the host, if there are
|
||
|
|
* fewer CPUs on the remote machine than on the host, an "empty"
|
||
|
|
* CPU is needed for each CPU that the host has that the remote does
|
||
|
|
* not. If there are more CPUs on the remote, older executables will
|
||
|
|
* simply ignore them (which is OK, we only need to guarantee that
|
||
|
|
* old executables don't crash).
|
||
|
|
*/
|
||
|
|
if (instance->cpu_count < local_cpu_count)
|
||
|
|
cpu_count = local_cpu_count;
|
||
|
|
|
||
|
|
for (i = 0; i < cpu_count; i++) {
|
||
|
|
temp_files[i] = get_temp_file(instance, i);
|
||
|
|
if (i >= instance->cpu_count)
|
||
|
|
touch_file(temp_files[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
tracecmd_append_buffer_cpu_data(handle, tracefs_instance_get_name(instance->tracefs),
|
||
|
|
cpu_count, temp_files);
|
||
|
|
|
||
|
|
for (i = 0; i < instance->cpu_count; i++) {
|
||
|
|
if (i >= instance->cpu_count)
|
||
|
|
delete_temp_file(instance, i);
|
||
|
|
put_temp_file(temp_files[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_guest_info(struct tracecmd_output *handle, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct trace_guest *guest;
|
||
|
|
const char *name;
|
||
|
|
char *buf, *p;
|
||
|
|
int size;
|
||
|
|
int pid;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (is_network(instance)) {
|
||
|
|
name = instance->name;
|
||
|
|
} else {
|
||
|
|
guest = trace_get_guest(instance->cid, NULL);
|
||
|
|
if (!guest)
|
||
|
|
return;
|
||
|
|
name = guest->name;
|
||
|
|
}
|
||
|
|
|
||
|
|
size = strlen(name) + 1;
|
||
|
|
size += sizeof(long long); /* trace_id */
|
||
|
|
size += sizeof(int); /* cpu count */
|
||
|
|
size += instance->cpu_count * 2 * sizeof(int); /* cpu,pid pair */
|
||
|
|
|
||
|
|
buf = calloc(1, size);
|
||
|
|
if (!buf)
|
||
|
|
return;
|
||
|
|
p = buf;
|
||
|
|
strcpy(p, name);
|
||
|
|
p += strlen(name) + 1;
|
||
|
|
|
||
|
|
memcpy(p, &instance->trace_id, sizeof(long long));
|
||
|
|
p += sizeof(long long);
|
||
|
|
|
||
|
|
memcpy(p, &instance->cpu_count, sizeof(int));
|
||
|
|
p += sizeof(int);
|
||
|
|
for (i = 0; i < instance->cpu_count; i++) {
|
||
|
|
pid = -1;
|
||
|
|
if (!is_network(instance)) {
|
||
|
|
if (i < guest->cpu_max)
|
||
|
|
pid = guest->cpu_pid[i];
|
||
|
|
}
|
||
|
|
memcpy(p, &i, sizeof(int));
|
||
|
|
p += sizeof(int);
|
||
|
|
memcpy(p, &pid, sizeof(int));
|
||
|
|
p += sizeof(int);
|
||
|
|
}
|
||
|
|
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_GUEST, size, buf);
|
||
|
|
free(buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_pid_maps(struct tracecmd_output *handle, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct pid_addr_maps *maps = instance->pid_maps;
|
||
|
|
struct trace_seq s;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
trace_seq_init(&s);
|
||
|
|
while (maps) {
|
||
|
|
if (!maps->nr_lib_maps) {
|
||
|
|
maps = maps->next;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
trace_seq_reset(&s);
|
||
|
|
trace_seq_printf(&s, "%x %x %s\n",
|
||
|
|
maps->pid, maps->nr_lib_maps, maps->proc_name);
|
||
|
|
for (i = 0; i < maps->nr_lib_maps; i++)
|
||
|
|
trace_seq_printf(&s, "%llx %llx %s\n",
|
||
|
|
maps->lib_maps[i].start,
|
||
|
|
maps->lib_maps[i].end,
|
||
|
|
maps->lib_maps[i].lib_name);
|
||
|
|
trace_seq_terminate(&s);
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_PROCMAPS,
|
||
|
|
s.len + 1, s.buffer);
|
||
|
|
maps = maps->next;
|
||
|
|
}
|
||
|
|
trace_seq_destroy(&s);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_trace_id(struct tracecmd_output *handle, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_TRACEID,
|
||
|
|
sizeof(long long), &instance->trace_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_buffer_stat(struct tracecmd_output *handle, struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct trace_seq s;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
trace_seq_init(&s);
|
||
|
|
trace_seq_printf(&s, "\nBuffer: %s\n\n",
|
||
|
|
tracefs_instance_get_name(instance->tracefs));
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT,
|
||
|
|
s.len+1, s.buffer);
|
||
|
|
trace_seq_destroy(&s);
|
||
|
|
|
||
|
|
for (i = 0; i < instance->cpu_count; i++)
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT,
|
||
|
|
instance->s_save[i].len+1,
|
||
|
|
instance->s_save[i].buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_option_hooks(struct tracecmd_output *handle)
|
||
|
|
{
|
||
|
|
struct hook_list *hook;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
for (hook = hooks; hook; hook = hook->next) {
|
||
|
|
len = strlen(hook->hook);
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_HOOK,
|
||
|
|
len + 1, hook->hook);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_uname(struct tracecmd_output *handle)
|
||
|
|
{
|
||
|
|
struct utsname buf;
|
||
|
|
char *str;
|
||
|
|
int len;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = uname(&buf);
|
||
|
|
/* if this fails for some reason, just ignore it */
|
||
|
|
if (ret < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
len = strlen(buf.sysname) + strlen(buf.nodename) +
|
||
|
|
strlen(buf.release) + strlen(buf.machine) + 4;
|
||
|
|
str = malloc(len);
|
||
|
|
if (!str)
|
||
|
|
return;
|
||
|
|
sprintf(str, "%s %s %s %s", buf.sysname, buf.nodename, buf.release, buf.machine);
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_UNAME, len, str);
|
||
|
|
free(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_version(struct tracecmd_output *handle)
|
||
|
|
{
|
||
|
|
char *str;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
len = asprintf(&str, "%s %s", VERSION_STRING, VERSION_GIT);
|
||
|
|
if (len < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_VERSION, len+1, str);
|
||
|
|
free(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void print_stat(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int cpu;
|
||
|
|
|
||
|
|
if (quiet)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!is_top_instance(instance))
|
||
|
|
printf("\nBuffer: %s\n\n",
|
||
|
|
tracefs_instance_get_name(instance->tracefs));
|
||
|
|
|
||
|
|
for (cpu = 0; cpu < instance->cpu_count; cpu++)
|
||
|
|
trace_seq_do_printf(&instance->s_print[cpu]);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *get_trace_clock(bool selected)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
continue;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (selected)
|
||
|
|
return tracefs_get_clock(instance ? instance->tracefs : NULL);
|
||
|
|
else
|
||
|
|
return tracefs_instance_file_read(instance ? instance->tracefs : NULL,
|
||
|
|
"trace_clock", NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum {
|
||
|
|
DATA_FL_NONE = 0,
|
||
|
|
DATA_FL_DATE = 1,
|
||
|
|
DATA_FL_OFFSET = 2,
|
||
|
|
DATA_FL_GUEST = 4,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void add_options(struct tracecmd_output *handle, struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
int type = 0;
|
||
|
|
char *clocks;
|
||
|
|
|
||
|
|
if (ctx->date2ts) {
|
||
|
|
if (ctx->data_flags & DATA_FL_DATE)
|
||
|
|
type = TRACECMD_OPTION_DATE;
|
||
|
|
else if (ctx->data_flags & DATA_FL_OFFSET)
|
||
|
|
type = TRACECMD_OPTION_OFFSET;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type)
|
||
|
|
tracecmd_add_option(handle, type, strlen(ctx->date2ts)+1, ctx->date2ts);
|
||
|
|
|
||
|
|
clocks = get_trace_clock(false);
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_TRACECLOCK,
|
||
|
|
clocks ? strlen(clocks)+1 : 0, clocks);
|
||
|
|
add_option_hooks(handle);
|
||
|
|
add_uname(handle);
|
||
|
|
add_version(handle);
|
||
|
|
if (!no_top_instance())
|
||
|
|
add_trace_id(handle, &top_instance);
|
||
|
|
free(clocks);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void write_guest_file(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *handle;
|
||
|
|
int cpu_count = instance->cpu_count;
|
||
|
|
char *file;
|
||
|
|
char **temp_files;
|
||
|
|
int i, fd;
|
||
|
|
|
||
|
|
file = instance->output_file;
|
||
|
|
fd = open(file, O_RDWR);
|
||
|
|
if (fd < 0)
|
||
|
|
die("error opening %s", file);
|
||
|
|
|
||
|
|
handle = tracecmd_get_output_handle_fd(fd);
|
||
|
|
if (!handle)
|
||
|
|
die("error writing to %s", file);
|
||
|
|
if (instance->flags & BUFFER_FL_TSC2NSEC)
|
||
|
|
tracecmd_set_out_clock(handle, TSCNSEC_CLOCK);
|
||
|
|
temp_files = malloc(sizeof(*temp_files) * cpu_count);
|
||
|
|
if (!temp_files)
|
||
|
|
die("failed to allocate temp_files for %d cpus",
|
||
|
|
cpu_count);
|
||
|
|
|
||
|
|
for (i = 0; i < cpu_count; i++) {
|
||
|
|
temp_files[i] = get_temp_file(instance, i);
|
||
|
|
if (!temp_files[i])
|
||
|
|
die("failed to allocate memory");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tracecmd_write_cpu_data(handle, cpu_count, temp_files, NULL) < 0)
|
||
|
|
die("failed to write CPU data");
|
||
|
|
tracecmd_output_close(handle);
|
||
|
|
|
||
|
|
for (i = 0; i < cpu_count; i++)
|
||
|
|
put_temp_file(temp_files[i]);
|
||
|
|
free(temp_files);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tracecmd_output *create_output(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *out;
|
||
|
|
|
||
|
|
if (!ctx->output)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
out = tracecmd_output_create(ctx->output);
|
||
|
|
if (!out)
|
||
|
|
goto error;
|
||
|
|
if (ctx->file_version && tracecmd_output_set_version(out, ctx->file_version))
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
if (ctx->compression) {
|
||
|
|
if (tracecmd_output_set_compression(out, ctx->compression))
|
||
|
|
goto error;
|
||
|
|
} else if (ctx->file_version >= FILE_VERSION_COMPRESSION) {
|
||
|
|
tracecmd_output_set_compression(out, "any");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tracecmd_output_write_headers(out, listed_events))
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
return out;
|
||
|
|
error:
|
||
|
|
if (out)
|
||
|
|
tracecmd_output_close(out);
|
||
|
|
unlink(ctx->output);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void record_data(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct tracecmd_output *handle;
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
bool local = false;
|
||
|
|
int max_cpu_count = local_cpu_count;
|
||
|
|
char **temp_files;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
write_guest_file(instance);
|
||
|
|
else if (host && instance->msg_handle)
|
||
|
|
finish_network(instance->msg_handle);
|
||
|
|
else
|
||
|
|
local = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!local)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (latency) {
|
||
|
|
handle = tracecmd_create_file_latency(ctx->output, local_cpu_count,
|
||
|
|
ctx->file_version, ctx->compression);
|
||
|
|
tracecmd_set_quiet(handle, quiet);
|
||
|
|
} else {
|
||
|
|
if (!local_cpu_count)
|
||
|
|
return;
|
||
|
|
|
||
|
|
/* Allocate enough temp files to handle each instance */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (instance->msg_handle)
|
||
|
|
continue;
|
||
|
|
if (instance->cpu_count > max_cpu_count)
|
||
|
|
max_cpu_count = instance->cpu_count;
|
||
|
|
}
|
||
|
|
|
||
|
|
temp_files = malloc(sizeof(*temp_files) * max_cpu_count);
|
||
|
|
if (!temp_files)
|
||
|
|
die("Failed to allocate temp_files for %d cpus",
|
||
|
|
local_cpu_count);
|
||
|
|
|
||
|
|
for (i = 0; i < max_cpu_count; i++)
|
||
|
|
temp_files[i] = get_temp_file(&top_instance, i);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If top_instance was not used, we still need to create
|
||
|
|
* empty trace.dat files for it.
|
||
|
|
*/
|
||
|
|
if (no_top_instance() || top_instance.msg_handle) {
|
||
|
|
for (i = 0; i < local_cpu_count; i++)
|
||
|
|
touch_file(temp_files[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
handle = create_output(ctx);
|
||
|
|
if (!handle)
|
||
|
|
die("Error creating output file");
|
||
|
|
tracecmd_set_quiet(handle, quiet);
|
||
|
|
|
||
|
|
add_options(handle, ctx);
|
||
|
|
|
||
|
|
/* Only record the top instance under TRACECMD_OPTION_CPUSTAT*/
|
||
|
|
if (!no_top_instance() && !top_instance.msg_handle) {
|
||
|
|
struct trace_seq *s = top_instance.s_save;
|
||
|
|
|
||
|
|
for (i = 0; i < local_cpu_count; i++)
|
||
|
|
tracecmd_add_option(handle, TRACECMD_OPTION_CPUSTAT,
|
||
|
|
s[i].len+1, s[i].buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (buffers) {
|
||
|
|
i = 0;
|
||
|
|
for_each_instance(instance) {
|
||
|
|
int cpus = instance->cpu_count != local_cpu_count ?
|
||
|
|
instance->cpu_count : 0;
|
||
|
|
|
||
|
|
if (instance->msg_handle)
|
||
|
|
continue;
|
||
|
|
tracecmd_add_buffer_info(handle,
|
||
|
|
tracefs_instance_get_name(instance->tracefs),
|
||
|
|
cpus);
|
||
|
|
add_buffer_stat(handle, instance);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!no_top_instance() && !top_instance.msg_handle)
|
||
|
|
print_stat(&top_instance);
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
add_pid_maps(handle, instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
add_guest_info(handle, instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ctx->tsc2nsec.mult) {
|
||
|
|
add_tsc2nsec(handle, &ctx->tsc2nsec);
|
||
|
|
tracecmd_set_out_clock(handle, TSCNSEC_CLOCK);
|
||
|
|
}
|
||
|
|
if (tracecmd_write_cmdlines(handle))
|
||
|
|
die("Writing cmdlines");
|
||
|
|
|
||
|
|
tracecmd_append_cpu_data(handle, local_cpu_count, temp_files);
|
||
|
|
|
||
|
|
for (i = 0; i < max_cpu_count; i++)
|
||
|
|
put_temp_file(temp_files[i]);
|
||
|
|
|
||
|
|
if (buffers) {
|
||
|
|
i = 0;
|
||
|
|
for_each_instance(instance) {
|
||
|
|
if (instance->msg_handle)
|
||
|
|
continue;
|
||
|
|
print_stat(instance);
|
||
|
|
append_buffer(handle, instance, temp_files);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
free(temp_files);
|
||
|
|
}
|
||
|
|
if (!handle)
|
||
|
|
die("could not write to file");
|
||
|
|
tracecmd_output_close(handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum filter_type {
|
||
|
|
FUNC_FILTER,
|
||
|
|
FUNC_NOTRACE,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int filter_command(struct tracefs_instance *instance, const char *cmd)
|
||
|
|
{
|
||
|
|
return tracefs_instance_file_append(instance, "set_ftrace_filter", cmd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int write_func_filter(enum filter_type type, struct buffer_instance *instance,
|
||
|
|
struct func_list **list)
|
||
|
|
{
|
||
|
|
struct func_list *item, *cmds = NULL;
|
||
|
|
const char *file;
|
||
|
|
int ret = -1;
|
||
|
|
int (*filter_function)(struct tracefs_instance *instance, const char *filter,
|
||
|
|
const char *module, unsigned int flags);
|
||
|
|
|
||
|
|
if (!*list)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case FUNC_FILTER:
|
||
|
|
filter_function = tracefs_function_filter;
|
||
|
|
file = "set_ftrace_filter";
|
||
|
|
break;
|
||
|
|
case FUNC_NOTRACE:
|
||
|
|
filter_function = tracefs_function_notrace;
|
||
|
|
file = "set_ftrace_notrace";
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = filter_function(instance->tracefs, NULL, NULL,
|
||
|
|
TRACEFS_FL_RESET | TRACEFS_FL_CONTINUE);
|
||
|
|
if (ret < 0)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
while (*list) {
|
||
|
|
item = *list;
|
||
|
|
*list = item->next;
|
||
|
|
/* Do commands separately at the end */
|
||
|
|
if (type == FUNC_FILTER && strstr(item->func, ":")) {
|
||
|
|
item->next = cmds;
|
||
|
|
cmds = item;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
ret = filter_function(instance->tracefs, item->func, item->mod,
|
||
|
|
TRACEFS_FL_CONTINUE);
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
free(item);
|
||
|
|
}
|
||
|
|
ret = filter_function(instance->tracefs, NULL, NULL, 0);
|
||
|
|
|
||
|
|
/* Now add any commands */
|
||
|
|
while (cmds) {
|
||
|
|
item = cmds;
|
||
|
|
cmds = item->next;
|
||
|
|
ret = filter_command(instance->tracefs, item->func);
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
free(item);
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
failed:
|
||
|
|
die("Failed to write %s to %s.\n"
|
||
|
|
"Perhaps this function is not available for tracing.\n"
|
||
|
|
"run 'trace-cmd list -f %s' to see if it is.",
|
||
|
|
item->func, file, item->func);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int write_func_file(struct buffer_instance *instance,
|
||
|
|
const char *file, struct func_list **list)
|
||
|
|
{
|
||
|
|
struct func_list *item;
|
||
|
|
const char *prefix = ":mod:";
|
||
|
|
char *path;
|
||
|
|
int fd;
|
||
|
|
int ret = -1;
|
||
|
|
|
||
|
|
if (!*list)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, file);
|
||
|
|
|
||
|
|
fd = open(path, O_WRONLY | O_TRUNC);
|
||
|
|
if (fd < 0)
|
||
|
|
goto free;
|
||
|
|
|
||
|
|
while (*list) {
|
||
|
|
item = *list;
|
||
|
|
*list = item->next;
|
||
|
|
ret = write(fd, item->func, strlen(item->func));
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
if (item->mod) {
|
||
|
|
ret = write(fd, prefix, strlen(prefix));
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
ret = write(fd, item->mod, strlen(item->mod));
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
}
|
||
|
|
ret = write(fd, " ", 1);
|
||
|
|
if (ret < 0)
|
||
|
|
goto failed;
|
||
|
|
free(item);
|
||
|
|
}
|
||
|
|
close(fd);
|
||
|
|
ret = 0;
|
||
|
|
free:
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
return ret;
|
||
|
|
failed:
|
||
|
|
die("Failed to write %s to %s.\n"
|
||
|
|
"Perhaps this function is not available for tracing.\n"
|
||
|
|
"run 'trace-cmd list -f %s' to see if it is.",
|
||
|
|
item->func, file, item->func);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int functions_filtered(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
char buf[1] = { '#' };
|
||
|
|
char *path;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "set_ftrace_filter");
|
||
|
|
fd = open(path, O_RDONLY);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
if (fd < 0) {
|
||
|
|
if (is_top_instance(instance))
|
||
|
|
warning("Can not set set_ftrace_filter");
|
||
|
|
else
|
||
|
|
warning("Can not set set_ftrace_filter for %s",
|
||
|
|
tracefs_instance_get_name(instance->tracefs));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If functions are not filtered, than the first character
|
||
|
|
* will be '#'. Make sure it is not an '#' and also not space.
|
||
|
|
*/
|
||
|
|
read(fd, buf, 1);
|
||
|
|
close(fd);
|
||
|
|
|
||
|
|
if (buf[0] == '#' || isspace(buf[0]))
|
||
|
|
return 0;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_funcs(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int set_notrace = 0;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
ret = write_func_filter(FUNC_FILTER, instance, &instance->filter_funcs);
|
||
|
|
if (ret < 0)
|
||
|
|
die("set_ftrace_filter does not exist. Can not filter functions");
|
||
|
|
|
||
|
|
/* graph tracing currently only works for top instance */
|
||
|
|
if (is_top_instance(instance)) {
|
||
|
|
ret = write_func_file(instance, "set_graph_function", &graph_funcs);
|
||
|
|
if (ret < 0)
|
||
|
|
die("set_graph_function does not exist.");
|
||
|
|
if (instance->plugin && strcmp(instance->plugin, "function_graph") == 0) {
|
||
|
|
ret = write_func_file(instance, "set_graph_notrace",
|
||
|
|
&instance->notrace_funcs);
|
||
|
|
if (!ret)
|
||
|
|
set_notrace = 1;
|
||
|
|
}
|
||
|
|
if (!set_notrace) {
|
||
|
|
ret = write_func_filter(FUNC_NOTRACE, instance,
|
||
|
|
&instance->notrace_funcs);
|
||
|
|
if (ret < 0)
|
||
|
|
die("set_ftrace_notrace does not exist. Can not filter functions");
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
write_func_filter(FUNC_NOTRACE, instance, &instance->notrace_funcs);
|
||
|
|
|
||
|
|
/* make sure we are filtering functions */
|
||
|
|
if (func_stack && is_top_instance(instance)) {
|
||
|
|
if (!functions_filtered(instance))
|
||
|
|
die("Function stack trace set, but functions not filtered");
|
||
|
|
save_option(instance, FUNC_STACK_TRACE);
|
||
|
|
}
|
||
|
|
clear_function_filters = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_func(struct func_list **list, const char *mod, const char *func)
|
||
|
|
{
|
||
|
|
struct func_list *item;
|
||
|
|
|
||
|
|
item = malloc(sizeof(*item));
|
||
|
|
if (!item)
|
||
|
|
die("Failed to allocate function descriptor");
|
||
|
|
item->func = func;
|
||
|
|
item->mod = mod;
|
||
|
|
item->next = *list;
|
||
|
|
*list = item;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int find_ts(struct tep_event *event, struct tep_record *record,
|
||
|
|
int cpu, void *context)
|
||
|
|
{
|
||
|
|
unsigned long long *ts = (unsigned long long *)context;
|
||
|
|
struct tep_format_field *field;
|
||
|
|
|
||
|
|
if (!ts)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
field = tep_find_field(event, "buf");
|
||
|
|
if (field && strcmp(STAMP"\n", record->data + field->offset) == 0) {
|
||
|
|
*ts = record->ts;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static unsigned long long find_time_stamp(struct tep_handle *tep,
|
||
|
|
struct tracefs_instance *instance)
|
||
|
|
{
|
||
|
|
unsigned long long ts = 0;
|
||
|
|
|
||
|
|
if (!tracefs_iterate_raw_events(tep, instance, NULL, 0, find_ts, &ts))
|
||
|
|
return ts;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static char *read_top_file(char *file, int *psize)
|
||
|
|
{
|
||
|
|
return tracefs_instance_file_read(top_instance.tracefs, file, psize);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tep_handle *get_ftrace_tep(void)
|
||
|
|
{
|
||
|
|
const char *systems[] = {"ftrace", NULL};
|
||
|
|
struct tep_handle *tep;
|
||
|
|
char *buf;
|
||
|
|
int size;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
tep = tracefs_local_events_system(NULL, systems);
|
||
|
|
if (!tep)
|
||
|
|
return NULL;
|
||
|
|
tep_set_file_bigendian(tep, tracecmd_host_bigendian());
|
||
|
|
buf = read_top_file("events/header_page", &size);
|
||
|
|
if (!buf)
|
||
|
|
goto error;
|
||
|
|
ret = tep_parse_header_page(tep, buf, size, sizeof(unsigned long));
|
||
|
|
free(buf);
|
||
|
|
if (ret < 0)
|
||
|
|
goto error;
|
||
|
|
|
||
|
|
return tep;
|
||
|
|
|
||
|
|
error:
|
||
|
|
tep_free(tep);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Try to write the date into the ftrace buffer and then
|
||
|
|
* read it back, mapping the timestamp to the date.
|
||
|
|
*/
|
||
|
|
static char *get_date_to_ts(void)
|
||
|
|
{
|
||
|
|
struct tep_handle *tep;
|
||
|
|
unsigned long long min = -1ULL;
|
||
|
|
unsigned long long diff;
|
||
|
|
unsigned long long stamp;
|
||
|
|
unsigned long long min_stamp;
|
||
|
|
unsigned long long min_ts;
|
||
|
|
unsigned long long ts;
|
||
|
|
struct timespec start;
|
||
|
|
struct timespec end;
|
||
|
|
char *date2ts = NULL;
|
||
|
|
int tfd;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/* Set up a tep to read the raw format */
|
||
|
|
tep = get_ftrace_tep();
|
||
|
|
if (!tep) {
|
||
|
|
warning("failed to alloc tep, --date ignored");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
tfd = tracefs_instance_file_open(NULL, "trace_marker", O_WRONLY);
|
||
|
|
if (tfd < 0) {
|
||
|
|
warning("Can not open 'trace_marker', --date ignored");
|
||
|
|
goto out_pevent;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < date2ts_tries; i++) {
|
||
|
|
tracecmd_disable_tracing();
|
||
|
|
clear_trace_instances();
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
|
||
|
|
clock_gettime(CLOCK_REALTIME, &start);
|
||
|
|
write(tfd, STAMP, 5);
|
||
|
|
clock_gettime(CLOCK_REALTIME, &end);
|
||
|
|
|
||
|
|
tracecmd_disable_tracing();
|
||
|
|
ts = find_time_stamp(tep, NULL);
|
||
|
|
if (!ts)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
diff = (unsigned long long)end.tv_sec * 1000000000LL;
|
||
|
|
diff += (unsigned long long)end.tv_nsec;
|
||
|
|
stamp = diff;
|
||
|
|
diff -= (unsigned long long)start.tv_sec * 1000000000LL;
|
||
|
|
diff -= (unsigned long long)start.tv_nsec;
|
||
|
|
|
||
|
|
if (diff < min) {
|
||
|
|
min_ts = ts;
|
||
|
|
min_stamp = stamp - diff / 2;
|
||
|
|
min = diff;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
close(tfd);
|
||
|
|
|
||
|
|
if (min == -1ULL) {
|
||
|
|
warning("Failed to make date offset, --date ignored");
|
||
|
|
goto out_pevent;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 16 hex chars + 0x + \0 */
|
||
|
|
date2ts = malloc(19);
|
||
|
|
if (!date2ts)
|
||
|
|
goto out_pevent;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The difference between the timestamp and the gtod is
|
||
|
|
* stored as an ASCII string in hex.
|
||
|
|
*/
|
||
|
|
diff = min_stamp - min_ts;
|
||
|
|
snprintf(date2ts, 19, "0x%llx", diff/1000);
|
||
|
|
out_pevent:
|
||
|
|
tep_free(tep);
|
||
|
|
|
||
|
|
return date2ts;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_buffer_size_instance(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int buffer_size = instance->buffer_size;
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
char *path;
|
||
|
|
int ret;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!buffer_size)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (buffer_size < 0)
|
||
|
|
die("buffer size must be positive");
|
||
|
|
|
||
|
|
snprintf(buf, BUFSIZ, "%d", buffer_size);
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "buffer_size_kb");
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0) {
|
||
|
|
warning("can't open %s", path);
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = write(fd, buf, strlen(buf));
|
||
|
|
if (ret < 0)
|
||
|
|
warning("Can't write to %s", path);
|
||
|
|
close(fd);
|
||
|
|
out:
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
void set_buffer_size(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
set_buffer_size_instance(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
process_event_trigger(char *path, struct event_iter *iter)
|
||
|
|
{
|
||
|
|
const char *system = iter->system_dent->d_name;
|
||
|
|
const char *event = iter->event_dent->d_name;
|
||
|
|
struct stat st;
|
||
|
|
char *trigger = NULL;
|
||
|
|
char *file;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
path = append_file(path, system);
|
||
|
|
file = append_file(path, event);
|
||
|
|
free(path);
|
||
|
|
|
||
|
|
ret = stat(file, &st);
|
||
|
|
if (ret < 0 || !S_ISDIR(st.st_mode))
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
trigger = append_file(file, "trigger");
|
||
|
|
|
||
|
|
ret = stat(trigger, &st);
|
||
|
|
if (ret < 0)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
ret = clear_trigger(trigger);
|
||
|
|
out:
|
||
|
|
free(trigger);
|
||
|
|
free(file);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_instance_triggers(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
enum event_iter_type type;
|
||
|
|
struct event_iter *iter;
|
||
|
|
char *system;
|
||
|
|
char *path;
|
||
|
|
int retry = 0;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "events");
|
||
|
|
if (!path)
|
||
|
|
die("malloc");
|
||
|
|
|
||
|
|
iter = trace_event_iter_alloc(path);
|
||
|
|
|
||
|
|
system = NULL;
|
||
|
|
while ((type = trace_event_iter_next(iter, path, system))) {
|
||
|
|
|
||
|
|
if (type == EVENT_ITER_SYSTEM) {
|
||
|
|
system = iter->system_dent->d_name;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = process_event_trigger(path, iter);
|
||
|
|
if (ret > 0)
|
||
|
|
retry++;
|
||
|
|
}
|
||
|
|
|
||
|
|
trace_event_iter_free(iter);
|
||
|
|
|
||
|
|
if (retry) {
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/* Order matters for some triggers */
|
||
|
|
for (i = 0; i < retry; i++) {
|
||
|
|
int tries = 0;
|
||
|
|
|
||
|
|
iter = trace_event_iter_alloc(path);
|
||
|
|
system = NULL;
|
||
|
|
while ((type = trace_event_iter_next(iter, path, system))) {
|
||
|
|
|
||
|
|
if (type == EVENT_ITER_SYSTEM) {
|
||
|
|
system = iter->system_dent->d_name;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = process_event_trigger(path, iter);
|
||
|
|
if (ret > 0)
|
||
|
|
tries++;
|
||
|
|
}
|
||
|
|
trace_event_iter_free(iter);
|
||
|
|
if (!tries)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
process_event_filter(char *path, struct event_iter *iter, enum event_process *processed)
|
||
|
|
{
|
||
|
|
const char *system = iter->system_dent->d_name;
|
||
|
|
const char *event = iter->event_dent->d_name;
|
||
|
|
struct stat st;
|
||
|
|
char *filter = NULL;
|
||
|
|
char *file;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
path = append_file(path, system);
|
||
|
|
file = append_file(path, event);
|
||
|
|
free(path);
|
||
|
|
|
||
|
|
ret = stat(file, &st);
|
||
|
|
if (ret < 0 || !S_ISDIR(st.st_mode))
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
filter = append_file(file, "filter");
|
||
|
|
|
||
|
|
ret = stat(filter, &st);
|
||
|
|
if (ret < 0)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
clear_filter(filter);
|
||
|
|
out:
|
||
|
|
free(filter);
|
||
|
|
free(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_instance_filters(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
struct event_iter *iter;
|
||
|
|
char *path;
|
||
|
|
char *system;
|
||
|
|
enum event_iter_type type;
|
||
|
|
enum event_process processed = PROCESSED_NONE;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, "events");
|
||
|
|
if (!path)
|
||
|
|
die("malloc");
|
||
|
|
|
||
|
|
iter = trace_event_iter_alloc(path);
|
||
|
|
|
||
|
|
processed = PROCESSED_NONE;
|
||
|
|
system = NULL;
|
||
|
|
while ((type = trace_event_iter_next(iter, path, system))) {
|
||
|
|
|
||
|
|
if (type == EVENT_ITER_SYSTEM) {
|
||
|
|
system = iter->system_dent->d_name;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
process_event_filter(path, iter, &processed);
|
||
|
|
}
|
||
|
|
|
||
|
|
trace_event_iter_free(iter);
|
||
|
|
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_filters(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
clear_instance_filters(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_clock(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
tracefs_instance_file_write(instance->tracefs,
|
||
|
|
"trace_clock", "local");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_cpu_mask(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int cpus = tracecmd_count_cpus();
|
||
|
|
int fullwords = (cpus - 1) / 32;
|
||
|
|
int bits = (cpus - 1) % 32 + 1;
|
||
|
|
int len = (fullwords + 1) * 9;
|
||
|
|
char buf[len + 1];
|
||
|
|
|
||
|
|
buf[0] = '\0';
|
||
|
|
|
||
|
|
sprintf(buf, "%x", (unsigned int)((1ULL << bits) - 1));
|
||
|
|
while (fullwords-- > 0)
|
||
|
|
strcat(buf, ",ffffffff");
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
tracefs_instance_file_write(instance->tracefs,
|
||
|
|
"tracing_cpumask", buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void reset_event_pid(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
add_event_pid(instance, "");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_triggers(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
clear_instance_triggers(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_instance_error_log(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
char *file;
|
||
|
|
|
||
|
|
if (!tracefs_file_exists(instance->tracefs, "error_log"))
|
||
|
|
return;
|
||
|
|
|
||
|
|
file = tracefs_instance_get_file(instance->tracefs, "error_log");
|
||
|
|
if (!file)
|
||
|
|
return;
|
||
|
|
write_file(file, " ");
|
||
|
|
tracefs_put_tracing_file(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_error_log(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
clear_instance_error_log(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_all_dynamic_events(void)
|
||
|
|
{
|
||
|
|
/* Clear event probes first, as they may be attached to other dynamic event */
|
||
|
|
tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_EPROBE, true);
|
||
|
|
tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_ALL, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void clear_func_filters(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
char *path;
|
||
|
|
int i;
|
||
|
|
const char * const files[] = { "set_ftrace_filter",
|
||
|
|
"set_ftrace_notrace",
|
||
|
|
"set_graph_function",
|
||
|
|
"set_graph_notrace",
|
||
|
|
NULL };
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
for (i = 0; files[i]; i++) {
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs, files[i]);
|
||
|
|
clear_func_filter(path);
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void make_instances(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_each_instance(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
continue;
|
||
|
|
if (instance->name && !instance->tracefs) {
|
||
|
|
instance->tracefs = tracefs_instance_create(instance->name);
|
||
|
|
/* Don't delete instances that already exist */
|
||
|
|
if (instance->tracefs && !tracefs_instance_is_new(instance->tracefs))
|
||
|
|
instance->flags |= BUFFER_FL_KEEP;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void tracecmd_remove_instances(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_each_instance(instance) {
|
||
|
|
/* Only delete what we created */
|
||
|
|
if (is_guest(instance) || (instance->flags & BUFFER_FL_KEEP))
|
||
|
|
continue;
|
||
|
|
if (instance->tracing_on_fd > 0) {
|
||
|
|
close(instance->tracing_on_fd);
|
||
|
|
instance->tracing_on_fd = 0;
|
||
|
|
}
|
||
|
|
tracefs_instance_destroy(instance->tracefs);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check_plugin(const char *plugin)
|
||
|
|
{
|
||
|
|
char *buf;
|
||
|
|
char *str;
|
||
|
|
char *tok;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* nop is special. We may want to just trace
|
||
|
|
* trace_printks, that are in the kernel.
|
||
|
|
*/
|
||
|
|
if (strcmp(plugin, "nop") == 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
buf = read_top_file("available_tracers", NULL);
|
||
|
|
if (!buf)
|
||
|
|
die("No plugins available");
|
||
|
|
|
||
|
|
str = buf;
|
||
|
|
while ((tok = strtok(str, " "))) {
|
||
|
|
str = NULL;
|
||
|
|
if (strcmp(tok, plugin) == 0)
|
||
|
|
goto out;
|
||
|
|
}
|
||
|
|
die ("Plugin '%s' does not exist", plugin);
|
||
|
|
out:
|
||
|
|
if (!quiet)
|
||
|
|
fprintf(stderr, " plugin '%s'\n", plugin);
|
||
|
|
free(buf);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check_function_plugin(void)
|
||
|
|
{
|
||
|
|
const char *plugin;
|
||
|
|
|
||
|
|
/* We only care about the top_instance */
|
||
|
|
if (no_top_instance())
|
||
|
|
return;
|
||
|
|
|
||
|
|
plugin = top_instance.plugin;
|
||
|
|
if (!plugin)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (plugin && strncmp(plugin, "function", 8) == 0 &&
|
||
|
|
func_stack && !top_instance.filter_funcs)
|
||
|
|
die("Must supply function filtering with --func-stack\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int __check_doing_something(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
return is_guest(instance) || (instance->flags & BUFFER_FL_PROFILE) ||
|
||
|
|
instance->plugin || instance->events || instance->get_procmap;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check_doing_something(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (__check_doing_something(instance))
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
die("no event or plugin was specified... aborting");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
update_plugin_instance(struct buffer_instance *instance,
|
||
|
|
enum trace_type type)
|
||
|
|
{
|
||
|
|
const char *plugin = instance->plugin;
|
||
|
|
|
||
|
|
if (is_guest(instance))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!plugin)
|
||
|
|
return;
|
||
|
|
|
||
|
|
check_plugin(plugin);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Latency tracers just save the trace and kill
|
||
|
|
* the threads.
|
||
|
|
*/
|
||
|
|
if (strcmp(plugin, "irqsoff") == 0 ||
|
||
|
|
strcmp(plugin, "preemptoff") == 0 ||
|
||
|
|
strcmp(plugin, "preemptirqsoff") == 0 ||
|
||
|
|
strcmp(plugin, "wakeup") == 0 ||
|
||
|
|
strcmp(plugin, "wakeup_rt") == 0) {
|
||
|
|
latency = 1;
|
||
|
|
if (host)
|
||
|
|
die("Network tracing not available with latency tracer plugins");
|
||
|
|
if (type & TRACE_TYPE_STREAM)
|
||
|
|
die("Streaming is not available with latency tracer plugins");
|
||
|
|
} else if (type == TRACE_TYPE_RECORD) {
|
||
|
|
if (latency)
|
||
|
|
die("Can not record latency tracer and non latency trace together");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (fset < 0 && (strcmp(plugin, "function") == 0 ||
|
||
|
|
strcmp(plugin, "function_graph") == 0))
|
||
|
|
die("function tracing not configured on this kernel");
|
||
|
|
|
||
|
|
if (type != TRACE_TYPE_EXTRACT)
|
||
|
|
set_plugin_instance(instance, plugin);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void update_plugins(enum trace_type type)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
update_plugin_instance(instance, type);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void allocate_seq(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
instance->s_save = malloc(sizeof(struct trace_seq) * instance->cpu_count);
|
||
|
|
instance->s_print = malloc(sizeof(struct trace_seq) * instance->cpu_count);
|
||
|
|
if (!instance->s_save || !instance->s_print)
|
||
|
|
die("Failed to allocate instance info");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Find the overrun output, and add it to the print seq */
|
||
|
|
static void add_overrun(int cpu, struct trace_seq *src, struct trace_seq *dst)
|
||
|
|
{
|
||
|
|
const char overrun_str[] = "overrun: ";
|
||
|
|
const char commit_overrun_str[] = "commit overrun: ";
|
||
|
|
const char *p;
|
||
|
|
int overrun;
|
||
|
|
int commit_overrun;
|
||
|
|
|
||
|
|
p = strstr(src->buffer, overrun_str);
|
||
|
|
if (!p) {
|
||
|
|
/* Warn? */
|
||
|
|
trace_seq_printf(dst, "CPU %d: no overrun found?\n", cpu);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
overrun = atoi(p + strlen(overrun_str));
|
||
|
|
|
||
|
|
p = strstr(p + 9, commit_overrun_str);
|
||
|
|
if (p)
|
||
|
|
commit_overrun = atoi(p + strlen(commit_overrun_str));
|
||
|
|
else
|
||
|
|
commit_overrun = -1;
|
||
|
|
|
||
|
|
if (!overrun && !commit_overrun)
|
||
|
|
return;
|
||
|
|
|
||
|
|
trace_seq_printf(dst, "CPU %d:", cpu);
|
||
|
|
|
||
|
|
if (overrun)
|
||
|
|
trace_seq_printf(dst, " %d events lost", overrun);
|
||
|
|
|
||
|
|
if (commit_overrun)
|
||
|
|
trace_seq_printf(dst, " %d events lost due to commit overrun",
|
||
|
|
commit_overrun);
|
||
|
|
|
||
|
|
trace_seq_putc(dst, '\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
static void record_stats(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct trace_seq *s_save;
|
||
|
|
struct trace_seq *s_print;
|
||
|
|
int cpu;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
s_save = instance->s_save;
|
||
|
|
s_print = instance->s_print;
|
||
|
|
for (cpu = 0; cpu < instance->cpu_count; cpu++) {
|
||
|
|
trace_seq_init(&s_save[cpu]);
|
||
|
|
trace_seq_init(&s_print[cpu]);
|
||
|
|
trace_seq_printf(&s_save[cpu], "CPU: %d\n", cpu);
|
||
|
|
tracecmd_stat_cpu_instance(instance, &s_save[cpu], cpu);
|
||
|
|
add_overrun(cpu, &s_save[cpu], &s_print[cpu]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void print_stats(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
print_stat(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void destroy_stats(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int cpu;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
for (cpu = 0; cpu < instance->cpu_count; cpu++) {
|
||
|
|
trace_seq_destroy(&instance->s_save[cpu]);
|
||
|
|
trace_seq_destroy(&instance->s_print[cpu]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void list_event(const char *event)
|
||
|
|
{
|
||
|
|
struct tracecmd_event_list *list;
|
||
|
|
|
||
|
|
list = malloc(sizeof(*list));
|
||
|
|
if (!list)
|
||
|
|
die("Failed to allocate list for event");
|
||
|
|
list->next = listed_events;
|
||
|
|
list->glob = event;
|
||
|
|
listed_events = list;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define ALL_EVENTS "*/*"
|
||
|
|
|
||
|
|
static void record_all_events(void)
|
||
|
|
{
|
||
|
|
struct tracecmd_event_list *list;
|
||
|
|
|
||
|
|
while (listed_events) {
|
||
|
|
list = listed_events;
|
||
|
|
listed_events = list->next;
|
||
|
|
free(list);
|
||
|
|
}
|
||
|
|
list = malloc(sizeof(*list));
|
||
|
|
if (!list)
|
||
|
|
die("Failed to allocate list for all events");
|
||
|
|
list->next = NULL;
|
||
|
|
list->glob = ALL_EVENTS;
|
||
|
|
listed_events = list;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int recording_all_events(void)
|
||
|
|
{
|
||
|
|
return listed_events && strcmp(listed_events->glob, ALL_EVENTS) == 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_trigger(struct event_list *event, const char *trigger)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (event->trigger) {
|
||
|
|
event->trigger = realloc(event->trigger,
|
||
|
|
strlen(event->trigger) + strlen("\n") +
|
||
|
|
strlen(trigger) + 1);
|
||
|
|
strcat(event->trigger, "\n");
|
||
|
|
strcat(event->trigger, trigger);
|
||
|
|
} else {
|
||
|
|
ret = asprintf(&event->trigger, "%s", trigger);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate event trigger");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int test_stacktrace_trigger(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
int ret = 0;
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
path = tracefs_instance_get_file(instance->tracefs,
|
||
|
|
"events/sched/sched_switch/trigger");
|
||
|
|
|
||
|
|
clear_trigger(path);
|
||
|
|
|
||
|
|
fd = open(path, O_WRONLY);
|
||
|
|
if (fd < 0)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
ret = write(fd, "stacktrace", 10);
|
||
|
|
if (ret != 10)
|
||
|
|
ret = 0;
|
||
|
|
else
|
||
|
|
ret = 1;
|
||
|
|
close(fd);
|
||
|
|
out:
|
||
|
|
tracefs_put_tracing_file(path);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
profile_add_event(struct buffer_instance *instance, const char *event_str, int stack)
|
||
|
|
{
|
||
|
|
struct event_list *event;
|
||
|
|
char buf[BUFSIZ];
|
||
|
|
char *p;
|
||
|
|
|
||
|
|
strcpy(buf, "events/");
|
||
|
|
strncpy(buf + 7, event_str, BUFSIZ - 7);
|
||
|
|
buf[BUFSIZ-1] = 0;
|
||
|
|
|
||
|
|
if ((p = strstr(buf, ":"))) {
|
||
|
|
*p = '/';
|
||
|
|
p++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!trace_check_file_exists(instance, buf))
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
/* Only add event if it isn't already added */
|
||
|
|
for (event = instance->events; event; event = event->next) {
|
||
|
|
if (p && strcmp(event->event, p) == 0)
|
||
|
|
break;
|
||
|
|
if (strcmp(event->event, event_str) == 0)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!event) {
|
||
|
|
event = malloc(sizeof(*event));
|
||
|
|
if (!event)
|
||
|
|
die("Failed to allocate event");
|
||
|
|
memset(event, 0, sizeof(*event));
|
||
|
|
event->event = event_str;
|
||
|
|
add_event(instance, event);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!recording_all_events())
|
||
|
|
list_event(event_str);
|
||
|
|
|
||
|
|
if (stack) {
|
||
|
|
if (!event->trigger || !strstr(event->trigger, "stacktrace"))
|
||
|
|
add_trigger(event, "stacktrace");
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int tracecmd_add_event(const char *event_str, int stack)
|
||
|
|
{
|
||
|
|
return profile_add_event(first_instance, event_str, stack);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void enable_profile(struct buffer_instance *instance)
|
||
|
|
{
|
||
|
|
int stacktrace = 0;
|
||
|
|
int i;
|
||
|
|
char *trigger_events[] = {
|
||
|
|
"sched:sched_switch",
|
||
|
|
"sched:sched_wakeup",
|
||
|
|
NULL,
|
||
|
|
};
|
||
|
|
char *events[] = {
|
||
|
|
"exceptions:page_fault_user",
|
||
|
|
"irq:irq_handler_entry",
|
||
|
|
"irq:irq_handler_exit",
|
||
|
|
"irq:softirq_entry",
|
||
|
|
"irq:softirq_exit",
|
||
|
|
"irq:softirq_raise",
|
||
|
|
"sched:sched_process_exec",
|
||
|
|
"raw_syscalls",
|
||
|
|
NULL,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!instance->plugin) {
|
||
|
|
if (trace_check_file_exists(instance, "max_graph_depth")) {
|
||
|
|
instance->plugin = "function_graph";
|
||
|
|
set_max_graph_depth(instance, "1");
|
||
|
|
} else
|
||
|
|
warning("Kernel does not support max_graph_depth\n"
|
||
|
|
" Skipping user/kernel profiling");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (test_stacktrace_trigger(instance))
|
||
|
|
stacktrace = 1;
|
||
|
|
else
|
||
|
|
/*
|
||
|
|
* The stacktrace trigger is not implemented with this
|
||
|
|
* kernel, then we need to default to the stack trace option.
|
||
|
|
* This is less efficient but still works.
|
||
|
|
*/
|
||
|
|
save_option(instance, "stacktrace");
|
||
|
|
|
||
|
|
|
||
|
|
for (i = 0; trigger_events[i]; i++)
|
||
|
|
profile_add_event(instance, trigger_events[i], stacktrace);
|
||
|
|
|
||
|
|
for (i = 0; events[i]; i++)
|
||
|
|
profile_add_event(instance, events[i], 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct event_list *
|
||
|
|
create_hook_event(struct buffer_instance *instance,
|
||
|
|
const char *system, const char *event)
|
||
|
|
{
|
||
|
|
struct event_list *event_list;
|
||
|
|
char *event_name;
|
||
|
|
int len;
|
||
|
|
|
||
|
|
if (!system)
|
||
|
|
system = "*";
|
||
|
|
|
||
|
|
len = strlen(event);
|
||
|
|
len += strlen(system) + 2;
|
||
|
|
|
||
|
|
event_name = malloc(len);
|
||
|
|
if (!event_name)
|
||
|
|
die("Failed to allocate %s/%s", system, event);
|
||
|
|
sprintf(event_name, "%s:%s", system, event);
|
||
|
|
|
||
|
|
event_list = malloc(sizeof(*event_list));
|
||
|
|
if (!event_list)
|
||
|
|
die("Failed to allocate event list for %s", event_name);
|
||
|
|
memset(event_list, 0, sizeof(*event_list));
|
||
|
|
event_list->event = event_name;
|
||
|
|
add_event(instance, event_list);
|
||
|
|
|
||
|
|
list_event(event_name);
|
||
|
|
|
||
|
|
return event_list;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_hook(struct buffer_instance *instance, const char *arg)
|
||
|
|
{
|
||
|
|
struct event_list *event;
|
||
|
|
struct hook_list *hook;
|
||
|
|
|
||
|
|
hook = tracecmd_create_event_hook(arg);
|
||
|
|
if (!hook)
|
||
|
|
die("Failed to create event hook %s", arg);
|
||
|
|
|
||
|
|
hook->instance = instance;
|
||
|
|
hook->next = hooks;
|
||
|
|
hooks = hook;
|
||
|
|
|
||
|
|
/* Make sure the event is enabled */
|
||
|
|
event = create_hook_event(instance, hook->start_system, hook->start_event);
|
||
|
|
create_hook_event(instance, hook->end_system, hook->end_event);
|
||
|
|
|
||
|
|
if (hook->stack) {
|
||
|
|
if (!event->trigger || !strstr(event->trigger, "stacktrace"))
|
||
|
|
add_trigger(event, "stacktrace");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void update_first_instance(struct buffer_instance *instance, int topt)
|
||
|
|
{
|
||
|
|
if (topt || instance == &top_instance)
|
||
|
|
first_instance = &top_instance;
|
||
|
|
else
|
||
|
|
first_instance = buffer_instances;
|
||
|
|
}
|
||
|
|
|
||
|
|
void init_top_instance(void)
|
||
|
|
{
|
||
|
|
if (!top_instance.tracefs)
|
||
|
|
top_instance.tracefs = tracefs_instance_create(NULL);
|
||
|
|
top_instance.cpu_count = tracecmd_count_cpus();
|
||
|
|
top_instance.flags = BUFFER_FL_KEEP;
|
||
|
|
top_instance.trace_id = tracecmd_generate_traceid();
|
||
|
|
init_instance(&top_instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
enum {
|
||
|
|
OPT_compression = 237,
|
||
|
|
OPT_file_ver = 238,
|
||
|
|
OPT_verbose = 239,
|
||
|
|
OPT_tsc2nsec = 240,
|
||
|
|
OPT_fork = 241,
|
||
|
|
OPT_tsyncinterval = 242,
|
||
|
|
OPT_user = 243,
|
||
|
|
OPT_procmap = 244,
|
||
|
|
OPT_quiet = 245,
|
||
|
|
OPT_debug = 246,
|
||
|
|
OPT_no_filter = 247,
|
||
|
|
OPT_max_graph_depth = 248,
|
||
|
|
OPT_tsoffset = 249,
|
||
|
|
OPT_bycomm = 250,
|
||
|
|
OPT_stderr = 251,
|
||
|
|
OPT_profile = 252,
|
||
|
|
OPT_nosplice = 253,
|
||
|
|
OPT_funcstack = 254,
|
||
|
|
OPT_date = 255,
|
||
|
|
OPT_module = 256,
|
||
|
|
OPT_nofifos = 257,
|
||
|
|
OPT_cmdlines_size = 258,
|
||
|
|
OPT_poll = 259,
|
||
|
|
OPT_name = 260,
|
||
|
|
};
|
||
|
|
|
||
|
|
void trace_stop(int argc, char **argv)
|
||
|
|
{
|
||
|
|
int topt = 0;
|
||
|
|
struct buffer_instance *instance = &top_instance;
|
||
|
|
|
||
|
|
init_top_instance();
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
int c;
|
||
|
|
|
||
|
|
c = getopt(argc-1, argv+1, "hatB:");
|
||
|
|
if (c == -1)
|
||
|
|
break;
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'h':
|
||
|
|
usage(argv);
|
||
|
|
break;
|
||
|
|
case 'B':
|
||
|
|
instance = allocate_instance(optarg);
|
||
|
|
if (!instance)
|
||
|
|
die("Failed to create instance");
|
||
|
|
add_instance(instance, local_cpu_count);
|
||
|
|
break;
|
||
|
|
case 'a':
|
||
|
|
add_all_instances();
|
||
|
|
break;
|
||
|
|
case 't':
|
||
|
|
/* Force to use top instance */
|
||
|
|
topt = 1;
|
||
|
|
instance = &top_instance;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
usage(argv);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
update_first_instance(instance, topt);
|
||
|
|
tracecmd_disable_tracing();
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_restart(int argc, char **argv)
|
||
|
|
{
|
||
|
|
int topt = 0;
|
||
|
|
struct buffer_instance *instance = &top_instance;
|
||
|
|
|
||
|
|
init_top_instance();
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
int c;
|
||
|
|
|
||
|
|
c = getopt(argc-1, argv+1, "hatB:");
|
||
|
|
if (c == -1)
|
||
|
|
break;
|
||
|
|
switch (c) {
|
||
|
|
case 'h':
|
||
|
|
usage(argv);
|
||
|
|
break;
|
||
|
|
case 'B':
|
||
|
|
instance = allocate_instance(optarg);
|
||
|
|
if (!instance)
|
||
|
|
die("Failed to create instance");
|
||
|
|
add_instance(instance, local_cpu_count);
|
||
|
|
break;
|
||
|
|
case 'a':
|
||
|
|
add_all_instances();
|
||
|
|
break;
|
||
|
|
case 't':
|
||
|
|
/* Force to use top instance */
|
||
|
|
topt = 1;
|
||
|
|
instance = &top_instance;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
usage(argv);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
update_first_instance(instance, topt);
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_reset(int argc, char **argv)
|
||
|
|
{
|
||
|
|
int c;
|
||
|
|
int topt = 0;
|
||
|
|
struct buffer_instance *instance = &top_instance;
|
||
|
|
|
||
|
|
init_top_instance();
|
||
|
|
|
||
|
|
/* if last arg is -a, then -b and -d apply to all instances */
|
||
|
|
int last_specified_all = 0;
|
||
|
|
struct buffer_instance *inst; /* iterator */
|
||
|
|
|
||
|
|
while ((c = getopt(argc-1, argv+1, "hab:B:td")) >= 0) {
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'h':
|
||
|
|
usage(argv);
|
||
|
|
break;
|
||
|
|
case 'b':
|
||
|
|
{
|
||
|
|
int size = atoi(optarg);
|
||
|
|
/* Min buffer size is 1 */
|
||
|
|
if (size <= 1)
|
||
|
|
size = 1;
|
||
|
|
if (last_specified_all) {
|
||
|
|
for_each_instance(inst) {
|
||
|
|
inst->buffer_size = size;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
instance->buffer_size = size;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 'B':
|
||
|
|
last_specified_all = 0;
|
||
|
|
instance = allocate_instance(optarg);
|
||
|
|
if (!instance)
|
||
|
|
die("Failed to create instance");
|
||
|
|
add_instance(instance, local_cpu_count);
|
||
|
|
/* -d will remove keep */
|
||
|
|
instance->flags |= BUFFER_FL_KEEP;
|
||
|
|
break;
|
||
|
|
case 't':
|
||
|
|
/* Force to use top instance */
|
||
|
|
last_specified_all = 0;
|
||
|
|
topt = 1;
|
||
|
|
instance = &top_instance;
|
||
|
|
break;
|
||
|
|
case 'a':
|
||
|
|
last_specified_all = 1;
|
||
|
|
add_all_instances();
|
||
|
|
for_each_instance(inst) {
|
||
|
|
inst->flags |= BUFFER_FL_KEEP;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'd':
|
||
|
|
if (last_specified_all) {
|
||
|
|
for_each_instance(inst) {
|
||
|
|
inst->flags &= ~BUFFER_FL_KEEP;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (is_top_instance(instance))
|
||
|
|
die("Can not delete top level buffer");
|
||
|
|
instance->flags &= ~BUFFER_FL_KEEP;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
update_first_instance(instance, topt);
|
||
|
|
tracecmd_disable_all_tracing(1);
|
||
|
|
set_buffer_size();
|
||
|
|
clear_filters();
|
||
|
|
clear_triggers();
|
||
|
|
clear_all_dynamic_events();
|
||
|
|
clear_error_log();
|
||
|
|
/* set clock to "local" */
|
||
|
|
reset_clock();
|
||
|
|
reset_event_pid();
|
||
|
|
reset_max_latency_instance();
|
||
|
|
reset_cpu_mask();
|
||
|
|
tracecmd_remove_instances();
|
||
|
|
clear_func_filters();
|
||
|
|
/* restore tracing_on to 1 */
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void init_common_record_context(struct common_record_context *ctx,
|
||
|
|
enum trace_cmd curr_cmd)
|
||
|
|
{
|
||
|
|
memset(ctx, 0, sizeof(*ctx));
|
||
|
|
ctx->instance = &top_instance;
|
||
|
|
ctx->curr_cmd = curr_cmd;
|
||
|
|
local_cpu_count = tracecmd_count_cpus();
|
||
|
|
ctx->file_version = tracecmd_default_file_version();
|
||
|
|
init_top_instance();
|
||
|
|
}
|
||
|
|
|
||
|
|
#define IS_EXTRACT(ctx) ((ctx)->curr_cmd == CMD_extract)
|
||
|
|
#define IS_START(ctx) ((ctx)->curr_cmd == CMD_start)
|
||
|
|
#define IS_CMDSET(ctx) ((ctx)->curr_cmd == CMD_set)
|
||
|
|
#define IS_STREAM(ctx) ((ctx)->curr_cmd == CMD_stream)
|
||
|
|
#define IS_PROFILE(ctx) ((ctx)->curr_cmd == CMD_profile)
|
||
|
|
#define IS_RECORD(ctx) ((ctx)->curr_cmd == CMD_record)
|
||
|
|
#define IS_RECORD_AGENT(ctx) ((ctx)->curr_cmd == CMD_record_agent)
|
||
|
|
|
||
|
|
static void add_argv(struct buffer_instance *instance, char *arg, bool prepend)
|
||
|
|
{
|
||
|
|
instance->argv = realloc(instance->argv,
|
||
|
|
(instance->argc + 1) * sizeof(char *));
|
||
|
|
if (!instance->argv)
|
||
|
|
die("Can not allocate instance args");
|
||
|
|
if (prepend) {
|
||
|
|
memmove(instance->argv + 1, instance->argv,
|
||
|
|
instance->argc * sizeof(*instance->argv));
|
||
|
|
instance->argv[0] = arg;
|
||
|
|
} else {
|
||
|
|
instance->argv[instance->argc] = arg;
|
||
|
|
}
|
||
|
|
instance->argc++;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void add_arg(struct buffer_instance *instance,
|
||
|
|
int c, const char *opts,
|
||
|
|
struct option *long_options, char *optarg)
|
||
|
|
{
|
||
|
|
char *ptr, *arg;
|
||
|
|
int i, ret;
|
||
|
|
|
||
|
|
/* Short or long arg */
|
||
|
|
if (!(c & 0x80)) {
|
||
|
|
ptr = strchr(opts, c);
|
||
|
|
if (!ptr)
|
||
|
|
return; /* Not found? */
|
||
|
|
ret = asprintf(&arg, "-%c", c);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Can not allocate argument");
|
||
|
|
add_argv(instance, arg, false);
|
||
|
|
if (ptr[1] == ':') {
|
||
|
|
arg = strdup(optarg);
|
||
|
|
if (!arg)
|
||
|
|
die("Can not allocate arguments");
|
||
|
|
add_argv(instance, arg, false);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (i = 0; long_options[i].name; i++) {
|
||
|
|
if (c != long_options[i].val)
|
||
|
|
continue;
|
||
|
|
ret = asprintf(&arg, "--%s", long_options[i].name);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Can not allocate argument");
|
||
|
|
add_argv(instance, arg, false);
|
||
|
|
if (long_options[i].has_arg) {
|
||
|
|
arg = strdup(optarg);
|
||
|
|
if (!arg)
|
||
|
|
die("Can not allocate arguments");
|
||
|
|
add_argv(instance, arg, false);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
/* Not found? */
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline void cmd_check_die(struct common_record_context *ctx,
|
||
|
|
enum trace_cmd id, char *cmd, char *param)
|
||
|
|
{
|
||
|
|
if (ctx->curr_cmd == id)
|
||
|
|
die("%s has no effect with the command %s\n"
|
||
|
|
"Did you mean 'record'?", param, cmd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline void remove_instances(struct buffer_instance *instances)
|
||
|
|
{
|
||
|
|
struct buffer_instance *del;
|
||
|
|
|
||
|
|
while (instances) {
|
||
|
|
del = instances;
|
||
|
|
instances = instances->next;
|
||
|
|
free(del->name);
|
||
|
|
tracefs_instance_destroy(del->tracefs);
|
||
|
|
tracefs_instance_free(del->tracefs);
|
||
|
|
free(del);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline void
|
||
|
|
check_instance_die(struct buffer_instance *instance, char *param)
|
||
|
|
{
|
||
|
|
if (instance->delete)
|
||
|
|
die("Instance %s is marked for deletion, invalid option %s",
|
||
|
|
tracefs_instance_get_name(instance->tracefs), param);
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool clock_is_supported(struct tracefs_instance *instance, const char *clock)
|
||
|
|
{
|
||
|
|
char *all_clocks = NULL;
|
||
|
|
char *ret = NULL;
|
||
|
|
|
||
|
|
all_clocks = tracefs_instance_file_read(instance, "trace_clock", NULL);
|
||
|
|
if (!all_clocks)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
ret = strstr(all_clocks, clock);
|
||
|
|
if (ret && (ret == all_clocks || ret[-1] == ' ' || ret[-1] == '[')) {
|
||
|
|
switch (ret[strlen(clock)]) {
|
||
|
|
case ' ':
|
||
|
|
case '\0':
|
||
|
|
case ']':
|
||
|
|
case '\n':
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
ret = NULL;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
ret = NULL;
|
||
|
|
}
|
||
|
|
free(all_clocks);
|
||
|
|
|
||
|
|
return ret != NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef PERF
|
||
|
|
static int get_tsc_nsec(int *shift, int *mult)
|
||
|
|
{
|
||
|
|
static int cpu_shift, cpu_mult;
|
||
|
|
static int supported;
|
||
|
|
int cpus = tracecmd_count_cpus();
|
||
|
|
struct trace_perf perf;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
if (supported)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
supported = -1;
|
||
|
|
if (trace_perf_init(&perf, 1, 0, getpid()))
|
||
|
|
return -1;
|
||
|
|
if (trace_perf_open(&perf))
|
||
|
|
return -1;
|
||
|
|
cpu_shift = perf.mmap->time_shift;
|
||
|
|
cpu_mult = perf.mmap->time_mult;
|
||
|
|
for (i = 1; i < cpus; i++) {
|
||
|
|
trace_perf_close(&perf);
|
||
|
|
if (trace_perf_init(&perf, 1, i, getpid()))
|
||
|
|
break;
|
||
|
|
if (trace_perf_open(&perf))
|
||
|
|
break;
|
||
|
|
if (perf.mmap->time_shift != cpu_shift ||
|
||
|
|
perf.mmap->time_mult != cpu_mult) {
|
||
|
|
warning("Found different TSC multiplier and shift for CPU %d: %d;%d instead of %d;%d",
|
||
|
|
i, perf.mmap->time_mult, perf.mmap->time_shift, cpu_mult, cpu_shift);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
trace_perf_close(&perf);
|
||
|
|
if (i < cpus)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (cpu_shift || cpu_mult)
|
||
|
|
supported = 1;
|
||
|
|
out:
|
||
|
|
if (supported < 0)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (shift)
|
||
|
|
*shift = cpu_shift;
|
||
|
|
if (mult)
|
||
|
|
*mult = cpu_mult;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
static int get_tsc_nsec(int *shift, int *mult)
|
||
|
|
{
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
bool trace_tsc2nsec_is_supported(void)
|
||
|
|
{
|
||
|
|
return get_tsc_nsec(NULL, NULL) == 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void parse_record_options(int argc,
|
||
|
|
char **argv,
|
||
|
|
enum trace_cmd curr_cmd,
|
||
|
|
struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
const char *plugin = NULL;
|
||
|
|
const char *option;
|
||
|
|
struct event_list *event = NULL;
|
||
|
|
struct event_list *last_event = NULL;
|
||
|
|
struct addrinfo *result;
|
||
|
|
char *pids;
|
||
|
|
char *pid;
|
||
|
|
char *sav;
|
||
|
|
int name_counter = 0;
|
||
|
|
int negative = 0;
|
||
|
|
struct buffer_instance *instance, *del_list = NULL;
|
||
|
|
int do_children = 0;
|
||
|
|
int fpids_count = 0;
|
||
|
|
|
||
|
|
init_common_record_context(ctx, curr_cmd);
|
||
|
|
|
||
|
|
if (IS_CMDSET(ctx))
|
||
|
|
keep = 1;
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
int option_index = 0;
|
||
|
|
int ret;
|
||
|
|
int c;
|
||
|
|
const char *opts;
|
||
|
|
static struct option long_options[] = {
|
||
|
|
{"date", no_argument, NULL, OPT_date},
|
||
|
|
{"func-stack", no_argument, NULL, OPT_funcstack},
|
||
|
|
{"nosplice", no_argument, NULL, OPT_nosplice},
|
||
|
|
{"nofifos", no_argument, NULL, OPT_nofifos},
|
||
|
|
{"profile", no_argument, NULL, OPT_profile},
|
||
|
|
{"stderr", no_argument, NULL, OPT_stderr},
|
||
|
|
{"by-comm", no_argument, NULL, OPT_bycomm},
|
||
|
|
{"ts-offset", required_argument, NULL, OPT_tsoffset},
|
||
|
|
{"max-graph-depth", required_argument, NULL, OPT_max_graph_depth},
|
||
|
|
{"cmdlines-size", required_argument, NULL, OPT_cmdlines_size},
|
||
|
|
{"no-filter", no_argument, NULL, OPT_no_filter},
|
||
|
|
{"debug", no_argument, NULL, OPT_debug},
|
||
|
|
{"quiet", no_argument, NULL, OPT_quiet},
|
||
|
|
{"help", no_argument, NULL, '?'},
|
||
|
|
{"proc-map", no_argument, NULL, OPT_procmap},
|
||
|
|
{"user", required_argument, NULL, OPT_user},
|
||
|
|
{"module", required_argument, NULL, OPT_module},
|
||
|
|
{"tsync-interval", required_argument, NULL, OPT_tsyncinterval},
|
||
|
|
{"fork", no_argument, NULL, OPT_fork},
|
||
|
|
{"tsc2nsec", no_argument, NULL, OPT_tsc2nsec},
|
||
|
|
{"poll", no_argument, NULL, OPT_poll},
|
||
|
|
{"name", required_argument, NULL, OPT_name},
|
||
|
|
{"verbose", optional_argument, NULL, OPT_verbose},
|
||
|
|
{"compression", required_argument, NULL, OPT_compression},
|
||
|
|
{"file-version", required_argument, NULL, OPT_file_ver},
|
||
|
|
{NULL, 0, NULL, 0}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (IS_EXTRACT(ctx))
|
||
|
|
opts = "+haf:Fp:co:O:sr:g:l:n:P:N:tb:B:ksiT";
|
||
|
|
else
|
||
|
|
opts = "+hae:f:FA:p:cC:dDGo:O:s:r:V:vg:l:n:P:N:tb:R:B:ksSiTm:M:H:q";
|
||
|
|
c = getopt_long (argc-1, argv+1, opts, long_options, &option_index);
|
||
|
|
if (c == -1)
|
||
|
|
break;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If the current instance is to record a guest, then save
|
||
|
|
* all the arguments for this instance.
|
||
|
|
*/
|
||
|
|
if (c != 'B' && c != 'A' && c != OPT_name && is_guest(ctx->instance)) {
|
||
|
|
add_arg(ctx->instance, c, opts, long_options, optarg);
|
||
|
|
if (c == 'C')
|
||
|
|
ctx->instance->flags |= BUFFER_FL_HAS_CLOCK;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'h':
|
||
|
|
usage(argv);
|
||
|
|
break;
|
||
|
|
case 'a':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-a");
|
||
|
|
if (IS_EXTRACT(ctx)) {
|
||
|
|
add_all_instances();
|
||
|
|
} else {
|
||
|
|
ctx->record_all = 1;
|
||
|
|
record_all_events();
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'e':
|
||
|
|
check_instance_die(ctx->instance, "-e");
|
||
|
|
ctx->events = 1;
|
||
|
|
event = malloc(sizeof(*event));
|
||
|
|
if (!event)
|
||
|
|
die("Failed to allocate event %s", optarg);
|
||
|
|
memset(event, 0, sizeof(*event));
|
||
|
|
event->event = optarg;
|
||
|
|
add_event(ctx->instance, event);
|
||
|
|
event->neg = negative;
|
||
|
|
event->filter = NULL;
|
||
|
|
last_event = event;
|
||
|
|
|
||
|
|
if (!ctx->record_all)
|
||
|
|
list_event(optarg);
|
||
|
|
break;
|
||
|
|
case 'f':
|
||
|
|
if (!last_event)
|
||
|
|
die("filter must come after event");
|
||
|
|
if (last_event->filter) {
|
||
|
|
last_event->filter =
|
||
|
|
realloc(last_event->filter,
|
||
|
|
strlen(last_event->filter) +
|
||
|
|
strlen("&&()") +
|
||
|
|
strlen(optarg) + 1);
|
||
|
|
strcat(last_event->filter, "&&(");
|
||
|
|
strcat(last_event->filter, optarg);
|
||
|
|
strcat(last_event->filter, ")");
|
||
|
|
} else {
|
||
|
|
ret = asprintf(&last_event->filter, "(%s)", optarg);
|
||
|
|
if (ret < 0)
|
||
|
|
die("Failed to allocate filter %s", optarg);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'R':
|
||
|
|
if (!last_event)
|
||
|
|
die("trigger must come after event");
|
||
|
|
add_trigger(event, optarg);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case OPT_name:
|
||
|
|
if (!ctx->instance)
|
||
|
|
die("No instance defined for name option\n");
|
||
|
|
if (!is_guest(ctx->instance))
|
||
|
|
die(" --name is only used for -A options\n");
|
||
|
|
free(ctx->instance->name);
|
||
|
|
ctx->instance->name = strdup(optarg);
|
||
|
|
if (!ctx->instance->name)
|
||
|
|
die("Failed to allocate name");
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'A': {
|
||
|
|
char *name = NULL;
|
||
|
|
int cid = -1, port = -1;
|
||
|
|
|
||
|
|
if (!IS_RECORD(ctx))
|
||
|
|
die("-A is only allowed for record operations");
|
||
|
|
|
||
|
|
name = parse_guest_name(optarg, &cid, &port, &result);
|
||
|
|
if (cid == -1 && !result)
|
||
|
|
die("guest %s not found", optarg);
|
||
|
|
if (port == -1)
|
||
|
|
port = TRACE_AGENT_DEFAULT_PORT;
|
||
|
|
if (!name || !*name) {
|
||
|
|
ret = asprintf(&name, "unnamed-%d", name_counter++);
|
||
|
|
if (ret < 0)
|
||
|
|
name = NULL;
|
||
|
|
} else {
|
||
|
|
/* Needs to be allocate */
|
||
|
|
name = strdup(name);
|
||
|
|
}
|
||
|
|
if (!name)
|
||
|
|
die("Failed to allocate guest name");
|
||
|
|
|
||
|
|
ctx->instance = allocate_instance(name);
|
||
|
|
if (!ctx->instance)
|
||
|
|
die("Failed to allocate instance");
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
ctx->instance->flags |= BUFFER_FL_NETWORK;
|
||
|
|
ctx->instance->port_type = USE_TCP;
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx->instance->flags |= BUFFER_FL_GUEST;
|
||
|
|
ctx->instance->result = result;
|
||
|
|
ctx->instance->cid = cid;
|
||
|
|
ctx->instance->port = port;
|
||
|
|
ctx->instance->name = name;
|
||
|
|
add_instance(ctx->instance, 0);
|
||
|
|
ctx->data_flags |= DATA_FL_GUEST;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case 'F':
|
||
|
|
test_set_event_pid(ctx->instance);
|
||
|
|
filter_task = 1;
|
||
|
|
break;
|
||
|
|
case 'G':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-G");
|
||
|
|
ctx->global = 1;
|
||
|
|
break;
|
||
|
|
case 'P':
|
||
|
|
check_instance_die(ctx->instance, "-P");
|
||
|
|
test_set_event_pid(ctx->instance);
|
||
|
|
pids = strdup(optarg);
|
||
|
|
if (!pids)
|
||
|
|
die("strdup");
|
||
|
|
pid = strtok_r(pids, ",", &sav);
|
||
|
|
while (pid) {
|
||
|
|
fpids_count += add_filter_pid(ctx->instance,
|
||
|
|
atoi(pid), 0);
|
||
|
|
pid = strtok_r(NULL, ",", &sav);
|
||
|
|
ctx->instance->nr_process_pids++;
|
||
|
|
}
|
||
|
|
ctx->instance->process_pids = ctx->instance->filter_pids;
|
||
|
|
free(pids);
|
||
|
|
break;
|
||
|
|
case 'c':
|
||
|
|
check_instance_die(ctx->instance, "-c");
|
||
|
|
test_set_event_pid(ctx->instance);
|
||
|
|
do_children = 1;
|
||
|
|
if (!ctx->instance->have_event_fork) {
|
||
|
|
#ifdef NO_PTRACE
|
||
|
|
die("-c invalid: ptrace not supported");
|
||
|
|
#endif
|
||
|
|
do_ptrace = 1;
|
||
|
|
ctx->instance->ptrace_child = 1;
|
||
|
|
|
||
|
|
} else {
|
||
|
|
save_option(ctx->instance, "event-fork");
|
||
|
|
}
|
||
|
|
if (ctx->instance->have_func_fork)
|
||
|
|
save_option(ctx->instance, "function-fork");
|
||
|
|
break;
|
||
|
|
case 'C':
|
||
|
|
check_instance_die(ctx->instance, "-C");
|
||
|
|
if (strcmp(optarg, TSCNSEC_CLOCK) == 0) {
|
||
|
|
ret = get_tsc_nsec(&ctx->tsc2nsec.shift,
|
||
|
|
&ctx->tsc2nsec.mult);
|
||
|
|
if (ret)
|
||
|
|
die("TSC to nanosecond is not supported");
|
||
|
|
ctx->instance->flags |= BUFFER_FL_TSC2NSEC;
|
||
|
|
ctx->instance->clock = TSC_CLOCK;
|
||
|
|
} else {
|
||
|
|
ctx->instance->clock = optarg;
|
||
|
|
}
|
||
|
|
if (!clock_is_supported(NULL, ctx->instance->clock))
|
||
|
|
die("Clock %s is not supported", ctx->instance->clock);
|
||
|
|
ctx->instance->clock = strdup(ctx->instance->clock);
|
||
|
|
if (!ctx->instance->clock)
|
||
|
|
die("Failed allocation");
|
||
|
|
ctx->instance->flags |= BUFFER_FL_HAS_CLOCK;
|
||
|
|
if (!ctx->clock && !is_guest(ctx->instance))
|
||
|
|
ctx->clock = ctx->instance->clock;
|
||
|
|
break;
|
||
|
|
case 'v':
|
||
|
|
negative = 1;
|
||
|
|
break;
|
||
|
|
case 'l':
|
||
|
|
add_func(&ctx->instance->filter_funcs,
|
||
|
|
ctx->instance->filter_mod, optarg);
|
||
|
|
ctx->filtered = 1;
|
||
|
|
break;
|
||
|
|
case 'n':
|
||
|
|
check_instance_die(ctx->instance, "-n");
|
||
|
|
add_func(&ctx->instance->notrace_funcs,
|
||
|
|
ctx->instance->filter_mod, optarg);
|
||
|
|
ctx->filtered = 1;
|
||
|
|
break;
|
||
|
|
case 'g':
|
||
|
|
check_instance_die(ctx->instance, "-g");
|
||
|
|
add_func(&graph_funcs, ctx->instance->filter_mod, optarg);
|
||
|
|
ctx->filtered = 1;
|
||
|
|
break;
|
||
|
|
case 'p':
|
||
|
|
check_instance_die(ctx->instance, "-p");
|
||
|
|
if (ctx->instance->plugin)
|
||
|
|
die("only one plugin allowed");
|
||
|
|
for (plugin = optarg; isspace(*plugin); plugin++)
|
||
|
|
;
|
||
|
|
ctx->instance->plugin = plugin;
|
||
|
|
for (optarg += strlen(optarg) - 1;
|
||
|
|
optarg > plugin && isspace(*optarg); optarg--)
|
||
|
|
;
|
||
|
|
optarg++;
|
||
|
|
optarg[0] = '\0';
|
||
|
|
break;
|
||
|
|
case 'D':
|
||
|
|
ctx->total_disable = 1;
|
||
|
|
/* fall through */
|
||
|
|
case 'd':
|
||
|
|
ctx->disable = 1;
|
||
|
|
break;
|
||
|
|
case 'o':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-o");
|
||
|
|
if (IS_RECORD_AGENT(ctx))
|
||
|
|
die("-o incompatible with agent recording");
|
||
|
|
if (host)
|
||
|
|
die("-o incompatible with -N");
|
||
|
|
if (IS_START(ctx))
|
||
|
|
die("start does not take output\n"
|
||
|
|
"Did you mean 'record'?");
|
||
|
|
if (IS_STREAM(ctx))
|
||
|
|
die("stream does not take output\n"
|
||
|
|
"Did you mean 'record'?");
|
||
|
|
if (ctx->output)
|
||
|
|
die("only one output file allowed");
|
||
|
|
ctx->output = optarg;
|
||
|
|
|
||
|
|
if (IS_PROFILE(ctx)) {
|
||
|
|
int fd;
|
||
|
|
|
||
|
|
/* pipe the output to this file instead of stdout */
|
||
|
|
save_stdout = dup(1);
|
||
|
|
close(1);
|
||
|
|
fd = open(optarg, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
|
|
if (fd < 0)
|
||
|
|
die("can't write to %s", optarg);
|
||
|
|
if (fd != 1) {
|
||
|
|
dup2(fd, 1);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'O':
|
||
|
|
check_instance_die(ctx->instance, "-O");
|
||
|
|
option = optarg;
|
||
|
|
save_option(ctx->instance, option);
|
||
|
|
break;
|
||
|
|
case 'T':
|
||
|
|
check_instance_die(ctx->instance, "-T");
|
||
|
|
save_option(ctx->instance, "stacktrace");
|
||
|
|
break;
|
||
|
|
case 'H':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-H");
|
||
|
|
check_instance_die(ctx->instance, "-H");
|
||
|
|
add_hook(ctx->instance, optarg);
|
||
|
|
ctx->events = 1;
|
||
|
|
break;
|
||
|
|
case 's':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-s");
|
||
|
|
if (IS_EXTRACT(ctx)) {
|
||
|
|
if (optarg)
|
||
|
|
usage(argv);
|
||
|
|
recorder_flags |= TRACECMD_RECORD_SNAPSHOT;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (!optarg)
|
||
|
|
usage(argv);
|
||
|
|
sleep_time = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 'S':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-S");
|
||
|
|
ctx->manual = 1;
|
||
|
|
/* User sets events for profiling */
|
||
|
|
if (!event)
|
||
|
|
ctx->events = 0;
|
||
|
|
break;
|
||
|
|
case 'r':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-r");
|
||
|
|
rt_prio = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 'N':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-N");
|
||
|
|
if (!IS_RECORD(ctx))
|
||
|
|
die("-N only available with record");
|
||
|
|
if (IS_RECORD_AGENT(ctx))
|
||
|
|
die("-N incompatible with agent recording");
|
||
|
|
if (ctx->output)
|
||
|
|
die("-N incompatible with -o");
|
||
|
|
host = optarg;
|
||
|
|
break;
|
||
|
|
case 'V':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-V");
|
||
|
|
if (!IS_RECORD(ctx))
|
||
|
|
die("-V only available with record");
|
||
|
|
if (IS_RECORD_AGENT(ctx))
|
||
|
|
die("-V incompatible with agent recording");
|
||
|
|
if (ctx->output)
|
||
|
|
die("-V incompatible with -o");
|
||
|
|
host = optarg;
|
||
|
|
ctx->instance->port_type = USE_VSOCK;
|
||
|
|
break;
|
||
|
|
case 'm':
|
||
|
|
if (max_kb)
|
||
|
|
die("-m can only be specified once");
|
||
|
|
if (!IS_RECORD(ctx))
|
||
|
|
die("only record take 'm' option");
|
||
|
|
max_kb = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 'M':
|
||
|
|
check_instance_die(ctx->instance, "-M");
|
||
|
|
ctx->instance->cpumask = alloc_mask_from_hex(ctx->instance, optarg);
|
||
|
|
break;
|
||
|
|
case 't':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-t");
|
||
|
|
if (IS_EXTRACT(ctx))
|
||
|
|
ctx->topt = 1; /* Extract top instance also */
|
||
|
|
else
|
||
|
|
ctx->instance->port_type = USE_TCP;
|
||
|
|
break;
|
||
|
|
case 'b':
|
||
|
|
check_instance_die(ctx->instance, "-b");
|
||
|
|
ctx->instance->buffer_size = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 'B':
|
||
|
|
ctx->instance = allocate_instance(optarg);
|
||
|
|
if (!ctx->instance)
|
||
|
|
die("Failed to create instance");
|
||
|
|
ctx->instance->delete = negative;
|
||
|
|
negative = 0;
|
||
|
|
if (ctx->instance->delete) {
|
||
|
|
ctx->instance->next = del_list;
|
||
|
|
del_list = ctx->instance;
|
||
|
|
} else
|
||
|
|
add_instance(ctx->instance, local_cpu_count);
|
||
|
|
if (IS_PROFILE(ctx))
|
||
|
|
ctx->instance->flags |= BUFFER_FL_PROFILE;
|
||
|
|
break;
|
||
|
|
case 'k':
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "-k");
|
||
|
|
keep = 1;
|
||
|
|
break;
|
||
|
|
case 'i':
|
||
|
|
ignore_event_not_found = 1;
|
||
|
|
break;
|
||
|
|
case OPT_user:
|
||
|
|
ctx->user = strdup(optarg);
|
||
|
|
if (!ctx->user)
|
||
|
|
die("Failed to allocate user name");
|
||
|
|
break;
|
||
|
|
case OPT_procmap:
|
||
|
|
cmd_check_die(ctx, CMD_start, *(argv+1), "--proc-map");
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--proc-map");
|
||
|
|
check_instance_die(ctx->instance, "--proc-map");
|
||
|
|
ctx->instance->get_procmap = 1;
|
||
|
|
break;
|
||
|
|
case OPT_date:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--date");
|
||
|
|
ctx->date = 1;
|
||
|
|
if (ctx->data_flags & DATA_FL_OFFSET)
|
||
|
|
die("Can not use both --date and --ts-offset");
|
||
|
|
ctx->data_flags |= DATA_FL_DATE;
|
||
|
|
break;
|
||
|
|
case OPT_funcstack:
|
||
|
|
func_stack = 1;
|
||
|
|
break;
|
||
|
|
case OPT_nosplice:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--nosplice");
|
||
|
|
recorder_flags |= TRACECMD_RECORD_NOSPLICE;
|
||
|
|
break;
|
||
|
|
case OPT_nofifos:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--nofifos");
|
||
|
|
no_fifos = true;
|
||
|
|
break;
|
||
|
|
case OPT_profile:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--profile");
|
||
|
|
check_instance_die(ctx->instance, "--profile");
|
||
|
|
handle_init = trace_init_profile;
|
||
|
|
ctx->instance->flags |= BUFFER_FL_PROFILE;
|
||
|
|
ctx->events = 1;
|
||
|
|
break;
|
||
|
|
case OPT_stderr:
|
||
|
|
/* if -o was used (for profile), ignore this */
|
||
|
|
if (save_stdout >= 0)
|
||
|
|
break;
|
||
|
|
save_stdout = dup(1);
|
||
|
|
close(1);
|
||
|
|
dup2(2, 1);
|
||
|
|
break;
|
||
|
|
case OPT_bycomm:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--by-comm");
|
||
|
|
trace_profile_set_merge_like_comms();
|
||
|
|
break;
|
||
|
|
case OPT_tsoffset:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--ts-offset");
|
||
|
|
ctx->date2ts = strdup(optarg);
|
||
|
|
if (ctx->data_flags & DATA_FL_DATE)
|
||
|
|
die("Can not use both --date and --ts-offset");
|
||
|
|
ctx->data_flags |= DATA_FL_OFFSET;
|
||
|
|
break;
|
||
|
|
case OPT_max_graph_depth:
|
||
|
|
check_instance_die(ctx->instance, "--max-graph-depth");
|
||
|
|
free(ctx->instance->max_graph_depth);
|
||
|
|
ctx->instance->max_graph_depth = strdup(optarg);
|
||
|
|
if (!ctx->instance->max_graph_depth)
|
||
|
|
die("Could not allocate option");
|
||
|
|
break;
|
||
|
|
case OPT_cmdlines_size:
|
||
|
|
ctx->saved_cmdlines_size = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case OPT_no_filter:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--no-filter");
|
||
|
|
no_filter = true;
|
||
|
|
break;
|
||
|
|
case OPT_debug:
|
||
|
|
tracecmd_set_debug(true);
|
||
|
|
break;
|
||
|
|
case OPT_module:
|
||
|
|
check_instance_die(ctx->instance, "--module");
|
||
|
|
if (ctx->instance->filter_mod)
|
||
|
|
add_func(&ctx->instance->filter_funcs,
|
||
|
|
ctx->instance->filter_mod, "*");
|
||
|
|
ctx->instance->filter_mod = optarg;
|
||
|
|
ctx->filtered = 0;
|
||
|
|
break;
|
||
|
|
case OPT_tsyncinterval:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--tsync-interval");
|
||
|
|
ctx->tsync_loop_interval = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case OPT_fork:
|
||
|
|
if (!IS_START(ctx))
|
||
|
|
die("--fork option used for 'start' command only");
|
||
|
|
fork_process = true;
|
||
|
|
break;
|
||
|
|
case OPT_tsc2nsec:
|
||
|
|
ret = get_tsc_nsec(&ctx->tsc2nsec.shift,
|
||
|
|
&ctx->tsc2nsec.mult);
|
||
|
|
if (ret)
|
||
|
|
die("TSC to nanosecond is not supported");
|
||
|
|
ctx->instance->flags |= BUFFER_FL_TSC2NSEC;
|
||
|
|
break;
|
||
|
|
case OPT_poll:
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--poll");
|
||
|
|
recorder_flags |= TRACECMD_RECORD_POLL;
|
||
|
|
break;
|
||
|
|
case OPT_compression:
|
||
|
|
cmd_check_die(ctx, CMD_start, *(argv+1), "--compression");
|
||
|
|
cmd_check_die(ctx, CMD_set, *(argv+1), "--compression");
|
||
|
|
cmd_check_die(ctx, CMD_extract, *(argv+1), "--compression");
|
||
|
|
cmd_check_die(ctx, CMD_stream, *(argv+1), "--compression");
|
||
|
|
cmd_check_die(ctx, CMD_profile, *(argv+1), "--compression");
|
||
|
|
if (strcmp(optarg, "any") && strcmp(optarg, "none") &&
|
||
|
|
!tracecmd_compress_is_supported(optarg, NULL))
|
||
|
|
die("Compression algorithm %s is not supported", optarg);
|
||
|
|
ctx->compression = strdup(optarg);
|
||
|
|
break;
|
||
|
|
case OPT_file_ver:
|
||
|
|
if (ctx->curr_cmd != CMD_record && ctx->curr_cmd != CMD_record_agent)
|
||
|
|
die("--file_version has no effect with the command %s\n",
|
||
|
|
*(argv+1));
|
||
|
|
ctx->file_version = atoi(optarg);
|
||
|
|
if (ctx->file_version < FILE_VERSION_MIN ||
|
||
|
|
ctx->file_version > FILE_VERSION_MAX)
|
||
|
|
die("Unsupported file version %d, "
|
||
|
|
"supported versions are from %d to %d",
|
||
|
|
ctx->file_version, FILE_VERSION_MIN, FILE_VERSION_MAX);
|
||
|
|
break;
|
||
|
|
case OPT_quiet:
|
||
|
|
case 'q':
|
||
|
|
quiet = true;
|
||
|
|
break;
|
||
|
|
case OPT_verbose:
|
||
|
|
if (trace_set_verbose(optarg) < 0)
|
||
|
|
die("invalid verbose level %s", optarg);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
usage(argv);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
remove_instances(del_list);
|
||
|
|
|
||
|
|
/* If --date is specified, prepend it to all guest VM flags */
|
||
|
|
if (ctx->date) {
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
add_argv(instance, "--date", true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ctx->filtered && ctx->instance->filter_mod)
|
||
|
|
add_func(&ctx->instance->filter_funcs,
|
||
|
|
ctx->instance->filter_mod, "*");
|
||
|
|
|
||
|
|
if (do_children && !filter_task && !fpids_count)
|
||
|
|
die(" -c can only be used with -F (or -P with event-fork support)");
|
||
|
|
|
||
|
|
if ((argc - optind) >= 2) {
|
||
|
|
if (IS_EXTRACT(ctx))
|
||
|
|
die("Command extract does not take any commands\n"
|
||
|
|
"Did you mean 'record'?");
|
||
|
|
ctx->run_command = 1;
|
||
|
|
}
|
||
|
|
if (ctx->user && !ctx->run_command)
|
||
|
|
warning("--user %s is ignored, no command is specified",
|
||
|
|
ctx->user);
|
||
|
|
|
||
|
|
if (top_instance.get_procmap) {
|
||
|
|
/* use ptrace to get procmap on the command exit */
|
||
|
|
if (ctx->run_command) {
|
||
|
|
do_ptrace = 1;
|
||
|
|
} else if (!top_instance.nr_filter_pids) {
|
||
|
|
warning("--proc-map is ignored for top instance, "
|
||
|
|
"no command or filtered PIDs are specified.");
|
||
|
|
top_instance.get_procmap = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (instance->get_procmap && !instance->nr_filter_pids) {
|
||
|
|
warning("--proc-map is ignored for instance %s, "
|
||
|
|
"no filtered PIDs are specified.",
|
||
|
|
tracefs_instance_get_name(instance->tracefs));
|
||
|
|
instance->get_procmap = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static enum trace_type get_trace_cmd_type(enum trace_cmd cmd)
|
||
|
|
{
|
||
|
|
const static struct {
|
||
|
|
enum trace_cmd cmd;
|
||
|
|
enum trace_type ttype;
|
||
|
|
} trace_type_per_command[] = {
|
||
|
|
{CMD_record, TRACE_TYPE_RECORD},
|
||
|
|
{CMD_stream, TRACE_TYPE_STREAM},
|
||
|
|
{CMD_extract, TRACE_TYPE_EXTRACT},
|
||
|
|
{CMD_profile, TRACE_TYPE_STREAM},
|
||
|
|
{CMD_start, TRACE_TYPE_START},
|
||
|
|
{CMD_record_agent, TRACE_TYPE_RECORD},
|
||
|
|
{CMD_set, TRACE_TYPE_SET}
|
||
|
|
};
|
||
|
|
|
||
|
|
for (int i = 0; i < ARRAY_SIZE(trace_type_per_command); i++) {
|
||
|
|
if (trace_type_per_command[i].cmd == cmd)
|
||
|
|
return trace_type_per_command[i].ttype;
|
||
|
|
}
|
||
|
|
|
||
|
|
die("Trace type UNKNOWN for the given cmd_fun");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void finalize_record_trace(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
if (keep)
|
||
|
|
return;
|
||
|
|
|
||
|
|
update_reset_files();
|
||
|
|
update_reset_triggers();
|
||
|
|
if (clear_function_filters)
|
||
|
|
clear_func_filters();
|
||
|
|
|
||
|
|
set_plugin("nop");
|
||
|
|
|
||
|
|
tracecmd_remove_instances();
|
||
|
|
|
||
|
|
/* If tracing_on was enabled before we started, set it on now */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (instance->flags & BUFFER_FL_KEEP)
|
||
|
|
write_tracing_on(instance,
|
||
|
|
instance->tracing_on_init_val);
|
||
|
|
if (is_agent(instance)) {
|
||
|
|
tracecmd_msg_send_close_resp_msg(instance->msg_handle);
|
||
|
|
tracecmd_output_close(instance->network_handle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (host)
|
||
|
|
tracecmd_output_close(ctx->instance->network_handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool has_local_instances(void)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (is_guest(instance))
|
||
|
|
continue;
|
||
|
|
if (host && instance->msg_handle)
|
||
|
|
continue;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_tsync_params(struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
int shift, mult;
|
||
|
|
bool force_tsc = false;
|
||
|
|
char *clock = NULL;
|
||
|
|
|
||
|
|
if (!ctx->clock) {
|
||
|
|
/*
|
||
|
|
* If no clock is configured &&
|
||
|
|
* KVM time sync protocol is available &&
|
||
|
|
* there is information of each guest PID process &&
|
||
|
|
* tsc-x86 clock is supported &&
|
||
|
|
* TSC to nsec multiplier and shift are available:
|
||
|
|
* force using the x86-tsc clock for this host-guest tracing session
|
||
|
|
* and store TSC to nsec multiplier and shift.
|
||
|
|
*/
|
||
|
|
if (tsync_proto_is_supported("kvm") &&
|
||
|
|
trace_have_guests_pid() &&
|
||
|
|
clock_is_supported(NULL, TSC_CLOCK) &&
|
||
|
|
!get_tsc_nsec(&shift, &mult) && mult) {
|
||
|
|
clock = strdup(TSC_CLOCK);
|
||
|
|
if (!clock)
|
||
|
|
die("Cannot not allocate clock");
|
||
|
|
ctx->tsc2nsec.mult = mult;
|
||
|
|
ctx->tsc2nsec.shift = shift;
|
||
|
|
force_tsc = true;
|
||
|
|
} else { /* Use the current clock of the first host instance */
|
||
|
|
clock = get_trace_clock(true);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
clock = strdup(ctx->clock);
|
||
|
|
if (!clock)
|
||
|
|
die("Cannot not allocate clock");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!clock && !ctx->tsync_loop_interval)
|
||
|
|
goto out;
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (clock && !(instance->flags & BUFFER_FL_HAS_CLOCK)) {
|
||
|
|
/* use the same clock in all tracing peers */
|
||
|
|
if (is_guest(instance)) {
|
||
|
|
if (!instance->clock) {
|
||
|
|
instance->clock = strdup(clock);
|
||
|
|
if (!instance->clock)
|
||
|
|
die("Can not allocate instance clock");
|
||
|
|
}
|
||
|
|
add_argv(instance, (char *)instance->clock, true);
|
||
|
|
add_argv(instance, "-C", true);
|
||
|
|
if (ctx->tsc2nsec.mult)
|
||
|
|
instance->flags |= BUFFER_FL_TSC2NSEC;
|
||
|
|
} else if (force_tsc && !instance->clock) {
|
||
|
|
instance->clock = strdup(clock);
|
||
|
|
if (!instance->clock)
|
||
|
|
die("Can not allocate instance clock");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
instance->tsync_loop_interval = ctx->tsync_loop_interval;
|
||
|
|
}
|
||
|
|
out:
|
||
|
|
free(clock);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void record_trace(int argc, char **argv,
|
||
|
|
struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
enum trace_type type = get_trace_cmd_type(ctx->curr_cmd);
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
struct filter_pids *pid;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If top_instance doesn't have any plugins or events, then
|
||
|
|
* remove it from being processed.
|
||
|
|
*/
|
||
|
|
if (!__check_doing_something(&top_instance) && !filter_task)
|
||
|
|
first_instance = buffer_instances;
|
||
|
|
else
|
||
|
|
ctx->topt = 1;
|
||
|
|
|
||
|
|
update_first_instance(ctx->instance, ctx->topt);
|
||
|
|
if (!IS_CMDSET(ctx)) {
|
||
|
|
check_doing_something();
|
||
|
|
check_function_plugin();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ctx->output)
|
||
|
|
ctx->output = DEFAULT_INPUT_FILE;
|
||
|
|
|
||
|
|
if (ctx->data_flags & DATA_FL_GUEST)
|
||
|
|
set_tsync_params(ctx);
|
||
|
|
|
||
|
|
make_instances();
|
||
|
|
|
||
|
|
/* Save the state of tracing_on before starting */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
instance->output_file = strdup(ctx->output);
|
||
|
|
if (!instance->output_file)
|
||
|
|
die("Failed to allocate output file name for instance");
|
||
|
|
if (!ctx->manual && instance->flags & BUFFER_FL_PROFILE)
|
||
|
|
enable_profile(instance);
|
||
|
|
|
||
|
|
instance->tracing_on_init_val = read_tracing_on(instance);
|
||
|
|
/* Some instances may not be created yet */
|
||
|
|
if (instance->tracing_on_init_val < 0)
|
||
|
|
instance->tracing_on_init_val = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ctx->events)
|
||
|
|
expand_event_list();
|
||
|
|
|
||
|
|
page_size = getpagesize();
|
||
|
|
|
||
|
|
if (!is_guest(ctx->instance))
|
||
|
|
fset = set_ftrace(ctx->instance, !ctx->disable, ctx->total_disable);
|
||
|
|
if (!IS_CMDSET(ctx))
|
||
|
|
tracecmd_disable_all_tracing(1);
|
||
|
|
|
||
|
|
for_all_instances(instance)
|
||
|
|
set_clock(ctx, instance);
|
||
|
|
|
||
|
|
|
||
|
|
/* Record records the date first */
|
||
|
|
if (ctx->date &&
|
||
|
|
((IS_RECORD(ctx) && has_local_instances()) || IS_RECORD_AGENT(ctx)))
|
||
|
|
ctx->date2ts = get_date_to_ts();
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
set_funcs(instance);
|
||
|
|
set_mask(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ctx->events) {
|
||
|
|
for_all_instances(instance)
|
||
|
|
enable_events(instance);
|
||
|
|
}
|
||
|
|
|
||
|
|
set_saved_cmdlines_size(ctx);
|
||
|
|
set_buffer_size();
|
||
|
|
update_plugins(type);
|
||
|
|
set_options();
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (instance->max_graph_depth) {
|
||
|
|
set_max_graph_depth(instance, instance->max_graph_depth);
|
||
|
|
free(instance->max_graph_depth);
|
||
|
|
instance->max_graph_depth = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
allocate_seq();
|
||
|
|
|
||
|
|
if (type & (TRACE_TYPE_RECORD | TRACE_TYPE_STREAM)) {
|
||
|
|
signal(SIGINT, finish);
|
||
|
|
if (!latency)
|
||
|
|
start_threads(type, ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ctx->run_command) {
|
||
|
|
run_cmd(type, ctx->user, (argc - optind) - 1, &argv[optind + 1]);
|
||
|
|
} else if (ctx->instance && is_agent(ctx->instance)) {
|
||
|
|
update_task_filter();
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
tracecmd_msg_wait_close(ctx->instance->msg_handle);
|
||
|
|
} else {
|
||
|
|
bool pwait = false;
|
||
|
|
bool wait_indefinitely = false;
|
||
|
|
|
||
|
|
update_task_filter();
|
||
|
|
|
||
|
|
if (!IS_CMDSET(ctx))
|
||
|
|
tracecmd_enable_tracing();
|
||
|
|
|
||
|
|
if (type & (TRACE_TYPE_START | TRACE_TYPE_SET))
|
||
|
|
exit(0);
|
||
|
|
|
||
|
|
/* We don't ptrace ourself */
|
||
|
|
if (do_ptrace) {
|
||
|
|
for_all_instances(instance) {
|
||
|
|
for (pid = instance->filter_pids; pid; pid = pid->next) {
|
||
|
|
if (!pid->exclude && instance->ptrace_child) {
|
||
|
|
ptrace_attach(instance, pid->pid);
|
||
|
|
pwait = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* sleep till we are woken with Ctrl^C */
|
||
|
|
printf("Hit Ctrl^C to stop recording\n");
|
||
|
|
for_all_instances(instance) {
|
||
|
|
/* If an instance is not tracing individual processes
|
||
|
|
* or there is an error while waiting for a process to
|
||
|
|
* exit, fallback to waiting indefinitely.
|
||
|
|
*/
|
||
|
|
if (!instance->nr_process_pids ||
|
||
|
|
trace_wait_for_processes(instance))
|
||
|
|
wait_indefinitely = true;
|
||
|
|
}
|
||
|
|
while (!finished && wait_indefinitely)
|
||
|
|
trace_or_sleep(type, pwait);
|
||
|
|
}
|
||
|
|
|
||
|
|
tell_guests_to_stop(ctx);
|
||
|
|
tracecmd_disable_tracing();
|
||
|
|
if (!latency)
|
||
|
|
stop_threads(type);
|
||
|
|
|
||
|
|
record_stats();
|
||
|
|
|
||
|
|
if (!latency)
|
||
|
|
wait_threads();
|
||
|
|
|
||
|
|
if (IS_RECORD(ctx)) {
|
||
|
|
record_data(ctx);
|
||
|
|
delete_thread_data();
|
||
|
|
} else
|
||
|
|
print_stats();
|
||
|
|
|
||
|
|
if (!keep)
|
||
|
|
tracecmd_disable_all_tracing(0);
|
||
|
|
|
||
|
|
destroy_stats();
|
||
|
|
finalize_record_trace(ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* This function contains common code for the following commands:
|
||
|
|
* record, start, stream, profile.
|
||
|
|
*/
|
||
|
|
static void record_trace_command(int argc, char **argv,
|
||
|
|
struct common_record_context *ctx)
|
||
|
|
{
|
||
|
|
tracecmd_tsync_init();
|
||
|
|
record_trace(argc, argv, ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_start(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_start, &ctx);
|
||
|
|
record_trace_command(argc, argv, &ctx);
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_set(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_set, &ctx);
|
||
|
|
record_trace_command(argc, argv, &ctx);
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_extract(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
struct buffer_instance *instance;
|
||
|
|
enum trace_type type;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_extract, &ctx);
|
||
|
|
|
||
|
|
type = get_trace_cmd_type(ctx.curr_cmd);
|
||
|
|
|
||
|
|
update_first_instance(ctx.instance, 1);
|
||
|
|
check_function_plugin();
|
||
|
|
|
||
|
|
if (!ctx.output)
|
||
|
|
ctx.output = DEFAULT_INPUT_FILE;
|
||
|
|
|
||
|
|
/* Save the state of tracing_on before starting */
|
||
|
|
for_all_instances(instance) {
|
||
|
|
instance->output_file = strdup(ctx.output);
|
||
|
|
if (!instance->output_file)
|
||
|
|
die("Failed to allocate output file name for instance");
|
||
|
|
|
||
|
|
if (!ctx.manual && instance->flags & BUFFER_FL_PROFILE)
|
||
|
|
enable_profile(ctx.instance);
|
||
|
|
|
||
|
|
instance->tracing_on_init_val = read_tracing_on(instance);
|
||
|
|
/* Some instances may not be created yet */
|
||
|
|
if (instance->tracing_on_init_val < 0)
|
||
|
|
instance->tracing_on_init_val = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Extracting data records all events in the system. */
|
||
|
|
if (!ctx.record_all)
|
||
|
|
record_all_events();
|
||
|
|
|
||
|
|
if (ctx.events)
|
||
|
|
expand_event_list();
|
||
|
|
|
||
|
|
page_size = getpagesize();
|
||
|
|
update_plugins(type);
|
||
|
|
set_options();
|
||
|
|
|
||
|
|
for_all_instances(instance) {
|
||
|
|
if (instance->max_graph_depth) {
|
||
|
|
set_max_graph_depth(instance, instance->max_graph_depth);
|
||
|
|
free(instance->max_graph_depth);
|
||
|
|
instance->max_graph_depth = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
allocate_seq();
|
||
|
|
flush_threads();
|
||
|
|
record_stats();
|
||
|
|
|
||
|
|
if (!keep)
|
||
|
|
tracecmd_disable_all_tracing(0);
|
||
|
|
|
||
|
|
/* extract records the date after extraction */
|
||
|
|
if (ctx.date) {
|
||
|
|
/*
|
||
|
|
* We need to start tracing, don't let other traces
|
||
|
|
* screw with our trace_marker.
|
||
|
|
*/
|
||
|
|
tracecmd_disable_all_tracing(1);
|
||
|
|
ctx.date2ts = get_date_to_ts();
|
||
|
|
}
|
||
|
|
|
||
|
|
record_data(&ctx);
|
||
|
|
delete_thread_data();
|
||
|
|
destroy_stats();
|
||
|
|
finalize_record_trace(&ctx);
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_stream(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_stream, &ctx);
|
||
|
|
record_trace_command(argc, argv, &ctx);
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_profile(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_profile, &ctx);
|
||
|
|
|
||
|
|
handle_init = trace_init_profile;
|
||
|
|
ctx.events = 1;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* If no instances were set, then enable profiling on the top instance.
|
||
|
|
*/
|
||
|
|
if (!buffer_instances)
|
||
|
|
top_instance.flags |= BUFFER_FL_PROFILE;
|
||
|
|
|
||
|
|
record_trace_command(argc, argv, &ctx);
|
||
|
|
do_trace_profile();
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void trace_record(int argc, char **argv)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv, CMD_record, &ctx);
|
||
|
|
record_trace_command(argc, argv, &ctx);
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
int trace_record_agent(struct tracecmd_msg_handle *msg_handle,
|
||
|
|
int cpus, int *fds,
|
||
|
|
int argc, char **argv,
|
||
|
|
bool use_fifos,
|
||
|
|
unsigned long long trace_id, const char *host)
|
||
|
|
{
|
||
|
|
struct common_record_context ctx;
|
||
|
|
char **argv_plus;
|
||
|
|
|
||
|
|
/* Reset optind for getopt_long */
|
||
|
|
optind = 1;
|
||
|
|
/*
|
||
|
|
* argc is the number of elements in argv, but we need to convert
|
||
|
|
* argc and argv into "trace-cmd", "record", argv.
|
||
|
|
* where argc needs to grow by two.
|
||
|
|
*/
|
||
|
|
argv_plus = calloc(argc + 2, sizeof(char *));
|
||
|
|
if (!argv_plus)
|
||
|
|
die("Failed to allocate record arguments");
|
||
|
|
|
||
|
|
argv_plus[0] = "trace-cmd";
|
||
|
|
argv_plus[1] = "record";
|
||
|
|
memmove(argv_plus + 2, argv, argc * sizeof(char *));
|
||
|
|
argc += 2;
|
||
|
|
|
||
|
|
parse_record_options(argc, argv_plus, CMD_record_agent, &ctx);
|
||
|
|
if (ctx.run_command)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
ctx.instance->fds = fds;
|
||
|
|
ctx.instance->use_fifos = use_fifos;
|
||
|
|
ctx.instance->flags |= BUFFER_FL_AGENT;
|
||
|
|
ctx.instance->msg_handle = msg_handle;
|
||
|
|
ctx.instance->host = host;
|
||
|
|
msg_handle->version = V3_PROTOCOL;
|
||
|
|
top_instance.trace_id = trace_id;
|
||
|
|
record_trace(argc, argv, &ctx);
|
||
|
|
|
||
|
|
free(argv_plus);
|
||
|
|
return 0;
|
||
|
|
}
|