2009-09-12 12:53:05 +07:00
|
|
|
/*
|
|
|
|
* builtin-timechart.c - make an svg timechart of system activity
|
|
|
|
*
|
|
|
|
* (C) Copyright 2009 Intel Corporation
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Arjan van de Ven <arjan@linux.intel.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; version 2
|
|
|
|
* of the License.
|
|
|
|
*/
|
|
|
|
|
2013-07-11 22:28:29 +07:00
|
|
|
#include <traceevent/event-parse.h>
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
#include "builtin.h"
|
|
|
|
|
|
|
|
#include "util/util.h"
|
|
|
|
|
|
|
|
#include "util/color.h"
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include "util/cache.h"
|
2013-07-11 22:28:30 +07:00
|
|
|
#include "util/evlist.h"
|
2011-11-17 02:02:54 +07:00
|
|
|
#include "util/evsel.h"
|
2009-09-12 12:53:05 +07:00
|
|
|
#include <linux/rbtree.h>
|
|
|
|
#include "util/symbol.h"
|
|
|
|
#include "util/callchain.h"
|
|
|
|
#include "util/strlist.h"
|
|
|
|
|
|
|
|
#include "perf.h"
|
|
|
|
#include "util/header.h"
|
|
|
|
#include "util/parse-options.h"
|
|
|
|
#include "util/parse-events.h"
|
2009-12-01 13:05:16 +07:00
|
|
|
#include "util/event.h"
|
2009-12-14 04:50:25 +07:00
|
|
|
#include "util/session.h"
|
2009-09-12 12:53:05 +07:00
|
|
|
#include "util/svghelper.h"
|
2011-11-28 17:30:20 +07:00
|
|
|
#include "util/tool.h"
|
2013-10-15 21:27:32 +07:00
|
|
|
#include "util/data.h"
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2011-01-03 23:50:45 +07:00
|
|
|
#define SUPPORT_OLD_POWER_EVENTS 1
|
|
|
|
#define PWR_EVENT_EXIT -1
|
|
|
|
|
2013-11-01 23:25:46 +07:00
|
|
|
static int proc_num = 15;
|
2011-01-03 23:50:45 +07:00
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
static unsigned int numcpus;
|
|
|
|
static u64 min_freq; /* Lowest CPU frequency seen */
|
|
|
|
static u64 max_freq; /* Highest CPU frequency seen */
|
|
|
|
static u64 turbo_frequency;
|
|
|
|
|
|
|
|
static u64 first_time, last_time;
|
|
|
|
|
2010-04-13 15:37:33 +07:00
|
|
|
static bool power_only;
|
2013-11-01 23:25:48 +07:00
|
|
|
static bool tasks_only;
|
2013-11-01 23:25:51 +07:00
|
|
|
static bool with_backtrace;
|
2009-09-24 20:40:13 +07:00
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
struct per_pidcomm;
|
|
|
|
struct cpu_sample;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Datastructure layout:
|
|
|
|
* We keep an list of "pid"s, matching the kernels notion of a task struct.
|
|
|
|
* Each "pid" entry, has a list of "comm"s.
|
|
|
|
* this is because we want to track different programs different, while
|
|
|
|
* exec will reuse the original pid (by design).
|
|
|
|
* Each comm has a list of samples that will be used to draw
|
|
|
|
* final graph.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct per_pid {
|
|
|
|
struct per_pid *next;
|
|
|
|
|
|
|
|
int pid;
|
|
|
|
int ppid;
|
|
|
|
|
|
|
|
u64 start_time;
|
|
|
|
u64 end_time;
|
|
|
|
u64 total_time;
|
|
|
|
int display;
|
|
|
|
|
|
|
|
struct per_pidcomm *all;
|
|
|
|
struct per_pidcomm *current;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct per_pidcomm {
|
|
|
|
struct per_pidcomm *next;
|
|
|
|
|
|
|
|
u64 start_time;
|
|
|
|
u64 end_time;
|
|
|
|
u64 total_time;
|
|
|
|
|
|
|
|
int Y;
|
|
|
|
int display;
|
|
|
|
|
|
|
|
long state;
|
|
|
|
u64 state_since;
|
|
|
|
|
|
|
|
char *comm;
|
|
|
|
|
|
|
|
struct cpu_sample *samples;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct sample_wrapper {
|
|
|
|
struct sample_wrapper *next;
|
|
|
|
|
|
|
|
u64 timestamp;
|
|
|
|
unsigned char data[0];
|
|
|
|
};
|
|
|
|
|
|
|
|
#define TYPE_NONE 0
|
|
|
|
#define TYPE_RUNNING 1
|
|
|
|
#define TYPE_WAITING 2
|
|
|
|
#define TYPE_BLOCKED 3
|
|
|
|
|
|
|
|
struct cpu_sample {
|
|
|
|
struct cpu_sample *next;
|
|
|
|
|
|
|
|
u64 start_time;
|
|
|
|
u64 end_time;
|
|
|
|
int type;
|
|
|
|
int cpu;
|
2013-11-01 23:25:51 +07:00
|
|
|
const char *backtrace;
|
2009-09-12 12:53:05 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct per_pid *all_data;
|
|
|
|
|
|
|
|
#define CSTATE 1
|
|
|
|
#define PSTATE 2
|
|
|
|
|
|
|
|
struct power_event {
|
|
|
|
struct power_event *next;
|
|
|
|
int type;
|
|
|
|
int state;
|
|
|
|
u64 start_time;
|
|
|
|
u64 end_time;
|
|
|
|
int cpu;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct wake_event {
|
|
|
|
struct wake_event *next;
|
|
|
|
int waker;
|
|
|
|
int wakee;
|
|
|
|
u64 time;
|
2013-11-01 23:25:51 +07:00
|
|
|
const char *backtrace;
|
2009-09-12 12:53:05 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct power_event *power_events;
|
|
|
|
static struct wake_event *wake_events;
|
|
|
|
|
2009-10-20 05:09:39 +07:00
|
|
|
struct process_filter {
|
2009-12-01 13:05:16 +07:00
|
|
|
char *name;
|
|
|
|
int pid;
|
|
|
|
struct process_filter *next;
|
2009-10-20 05:09:39 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct process_filter *process_filter;
|
|
|
|
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
static struct per_pid *find_create_pid(int pid)
|
|
|
|
{
|
|
|
|
struct per_pid *cursor = all_data;
|
|
|
|
|
|
|
|
while (cursor) {
|
|
|
|
if (cursor->pid == pid)
|
|
|
|
return cursor;
|
|
|
|
cursor = cursor->next;
|
|
|
|
}
|
2012-09-24 21:16:40 +07:00
|
|
|
cursor = zalloc(sizeof(*cursor));
|
2009-09-12 12:53:05 +07:00
|
|
|
assert(cursor != NULL);
|
|
|
|
cursor->pid = pid;
|
|
|
|
cursor->next = all_data;
|
|
|
|
all_data = cursor;
|
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pid_set_comm(int pid, char *comm)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
p = find_create_pid(pid);
|
|
|
|
c = p->all;
|
|
|
|
while (c) {
|
|
|
|
if (c->comm && strcmp(c->comm, comm) == 0) {
|
|
|
|
p->current = c;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!c->comm) {
|
|
|
|
c->comm = strdup(comm);
|
|
|
|
p->current = c;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c = c->next;
|
|
|
|
}
|
2012-09-24 21:16:40 +07:00
|
|
|
c = zalloc(sizeof(*c));
|
2009-09-12 12:53:05 +07:00
|
|
|
assert(c != NULL);
|
|
|
|
c->comm = strdup(comm);
|
|
|
|
p->current = c;
|
|
|
|
c->next = p->all;
|
|
|
|
p->all = c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pid_fork(int pid, int ppid, u64 timestamp)
|
|
|
|
{
|
|
|
|
struct per_pid *p, *pp;
|
|
|
|
p = find_create_pid(pid);
|
|
|
|
pp = find_create_pid(ppid);
|
|
|
|
p->ppid = ppid;
|
|
|
|
if (pp->current && pp->current->comm && !p->current)
|
|
|
|
pid_set_comm(pid, pp->current->comm);
|
|
|
|
|
|
|
|
p->start_time = timestamp;
|
|
|
|
if (p->current) {
|
|
|
|
p->current->start_time = timestamp;
|
|
|
|
p->current->state_since = timestamp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pid_exit(int pid, u64 timestamp)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
p = find_create_pid(pid);
|
|
|
|
p->end_time = timestamp;
|
|
|
|
if (p->current)
|
|
|
|
p->current->end_time = timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2013-11-01 23:25:51 +07:00
|
|
|
pid_put_sample(int pid, int type, unsigned int cpu, u64 start, u64 end,
|
|
|
|
const char *backtrace)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
struct cpu_sample *sample;
|
|
|
|
|
|
|
|
p = find_create_pid(pid);
|
|
|
|
c = p->current;
|
|
|
|
if (!c) {
|
2012-09-24 21:16:40 +07:00
|
|
|
c = zalloc(sizeof(*c));
|
2009-09-12 12:53:05 +07:00
|
|
|
assert(c != NULL);
|
|
|
|
p->current = c;
|
|
|
|
c->next = p->all;
|
|
|
|
p->all = c;
|
|
|
|
}
|
|
|
|
|
2012-09-24 21:16:40 +07:00
|
|
|
sample = zalloc(sizeof(*sample));
|
2009-09-12 12:53:05 +07:00
|
|
|
assert(sample != NULL);
|
|
|
|
sample->start_time = start;
|
|
|
|
sample->end_time = end;
|
|
|
|
sample->type = type;
|
|
|
|
sample->next = c->samples;
|
|
|
|
sample->cpu = cpu;
|
2013-11-01 23:25:51 +07:00
|
|
|
sample->backtrace = backtrace;
|
2009-09-12 12:53:05 +07:00
|
|
|
c->samples = sample;
|
|
|
|
|
|
|
|
if (sample->type == TYPE_RUNNING && end > start && start > 0) {
|
|
|
|
c->total_time += (end-start);
|
|
|
|
p->total_time += (end-start);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->start_time == 0 || c->start_time > start)
|
|
|
|
c->start_time = start;
|
|
|
|
if (p->start_time == 0 || p->start_time > start)
|
|
|
|
p->start_time = start;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_CPUS 4096
|
|
|
|
|
|
|
|
static u64 cpus_cstate_start_times[MAX_CPUS];
|
|
|
|
static int cpus_cstate_state[MAX_CPUS];
|
|
|
|
static u64 cpus_pstate_start_times[MAX_CPUS];
|
|
|
|
static u64 cpus_pstate_state[MAX_CPUS];
|
|
|
|
|
2012-09-11 05:15:03 +07:00
|
|
|
static int process_comm_event(struct perf_tool *tool __maybe_unused,
|
2011-11-25 17:19:45 +07:00
|
|
|
union perf_event *event,
|
2012-09-11 05:15:03 +07:00
|
|
|
struct perf_sample *sample __maybe_unused,
|
|
|
|
struct machine *machine __maybe_unused)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
2010-01-17 03:53:19 +07:00
|
|
|
pid_set_comm(event->comm.tid, event->comm.comm);
|
2009-09-12 12:53:05 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2009-12-14 04:50:24 +07:00
|
|
|
|
2012-09-11 05:15:03 +07:00
|
|
|
static int process_fork_event(struct perf_tool *tool __maybe_unused,
|
2011-11-25 17:19:45 +07:00
|
|
|
union perf_event *event,
|
2012-09-11 05:15:03 +07:00
|
|
|
struct perf_sample *sample __maybe_unused,
|
|
|
|
struct machine *machine __maybe_unused)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
|
|
|
pid_fork(event->fork.pid, event->fork.ppid, event->fork.time);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-09-11 05:15:03 +07:00
|
|
|
static int process_exit_event(struct perf_tool *tool __maybe_unused,
|
2011-11-25 17:19:45 +07:00
|
|
|
union perf_event *event,
|
2012-09-11 05:15:03 +07:00
|
|
|
struct perf_sample *sample __maybe_unused,
|
|
|
|
struct machine *machine __maybe_unused)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
|
|
|
pid_exit(event->fork.pid, event->fork.time);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-03 23:50:45 +07:00
|
|
|
#ifdef SUPPORT_OLD_POWER_EVENTS
|
|
|
|
static int use_old_power_events;
|
|
|
|
#endif
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
static void c_state_start(int cpu, u64 timestamp, int state)
|
|
|
|
{
|
|
|
|
cpus_cstate_start_times[cpu] = timestamp;
|
|
|
|
cpus_cstate_state[cpu] = state;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void c_state_end(int cpu, u64 timestamp)
|
|
|
|
{
|
2012-09-24 21:16:40 +07:00
|
|
|
struct power_event *pwr = zalloc(sizeof(*pwr));
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
if (!pwr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pwr->state = cpus_cstate_state[cpu];
|
|
|
|
pwr->start_time = cpus_cstate_start_times[cpu];
|
|
|
|
pwr->end_time = timestamp;
|
|
|
|
pwr->cpu = cpu;
|
|
|
|
pwr->type = CSTATE;
|
|
|
|
pwr->next = power_events;
|
|
|
|
|
|
|
|
power_events = pwr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void p_state_change(int cpu, u64 timestamp, u64 new_freq)
|
|
|
|
{
|
|
|
|
struct power_event *pwr;
|
|
|
|
|
|
|
|
if (new_freq > 8000000) /* detect invalid data */
|
|
|
|
return;
|
|
|
|
|
2012-09-24 21:16:40 +07:00
|
|
|
pwr = zalloc(sizeof(*pwr));
|
2009-09-12 12:53:05 +07:00
|
|
|
if (!pwr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pwr->state = cpus_pstate_state[cpu];
|
|
|
|
pwr->start_time = cpus_pstate_start_times[cpu];
|
|
|
|
pwr->end_time = timestamp;
|
|
|
|
pwr->cpu = cpu;
|
|
|
|
pwr->type = PSTATE;
|
|
|
|
pwr->next = power_events;
|
|
|
|
|
|
|
|
if (!pwr->start_time)
|
|
|
|
pwr->start_time = first_time;
|
|
|
|
|
|
|
|
power_events = pwr;
|
|
|
|
|
|
|
|
cpus_pstate_state[cpu] = new_freq;
|
|
|
|
cpus_pstate_start_times[cpu] = timestamp;
|
|
|
|
|
|
|
|
if ((u64)new_freq > max_freq)
|
|
|
|
max_freq = new_freq;
|
|
|
|
|
|
|
|
if (new_freq < min_freq || min_freq == 0)
|
|
|
|
min_freq = new_freq;
|
|
|
|
|
|
|
|
if (new_freq == max_freq - 1000)
|
|
|
|
turbo_frequency = max_freq;
|
|
|
|
}
|
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
static void sched_wakeup(int cpu, u64 timestamp, int waker, int wakee,
|
|
|
|
u8 flags, const char *backtrace)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
|
|
|
struct per_pid *p;
|
2012-09-24 21:16:40 +07:00
|
|
|
struct wake_event *we = zalloc(sizeof(*we));
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
if (!we)
|
|
|
|
return;
|
|
|
|
|
|
|
|
we->time = timestamp;
|
2013-11-27 17:45:00 +07:00
|
|
|
we->waker = waker;
|
2013-11-01 23:25:51 +07:00
|
|
|
we->backtrace = backtrace;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
if ((flags & TRACE_FLAG_HARDIRQ) || (flags & TRACE_FLAG_SOFTIRQ))
|
2009-09-12 12:53:05 +07:00
|
|
|
we->waker = -1;
|
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
we->wakee = wakee;
|
2009-09-12 12:53:05 +07:00
|
|
|
we->next = wake_events;
|
|
|
|
wake_events = we;
|
|
|
|
p = find_create_pid(we->wakee);
|
|
|
|
|
|
|
|
if (p && p->current && p->current->state == TYPE_NONE) {
|
|
|
|
p->current->state_since = timestamp;
|
|
|
|
p->current->state = TYPE_WAITING;
|
|
|
|
}
|
|
|
|
if (p && p->current && p->current->state == TYPE_BLOCKED) {
|
2013-11-01 23:25:51 +07:00
|
|
|
pid_put_sample(p->pid, p->current->state, cpu,
|
|
|
|
p->current->state_since, timestamp, NULL);
|
2009-09-12 12:53:05 +07:00
|
|
|
p->current->state_since = timestamp;
|
|
|
|
p->current->state = TYPE_WAITING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
static void sched_switch(int cpu, u64 timestamp, int prev_pid, int next_pid,
|
|
|
|
u64 prev_state, const char *backtrace)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
|
|
|
struct per_pid *p = NULL, *prev_p;
|
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
prev_p = find_create_pid(prev_pid);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
p = find_create_pid(next_pid);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
if (prev_p->current && prev_p->current->state != TYPE_NONE)
|
2013-11-27 17:45:00 +07:00
|
|
|
pid_put_sample(prev_pid, TYPE_RUNNING, cpu,
|
2013-11-01 23:25:51 +07:00
|
|
|
prev_p->current->state_since, timestamp,
|
|
|
|
backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
if (p && p->current) {
|
|
|
|
if (p->current->state != TYPE_NONE)
|
2013-11-27 17:45:00 +07:00
|
|
|
pid_put_sample(next_pid, p->current->state, cpu,
|
2013-11-01 23:25:51 +07:00
|
|
|
p->current->state_since, timestamp,
|
|
|
|
backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2010-08-06 03:27:51 +07:00
|
|
|
p->current->state_since = timestamp;
|
|
|
|
p->current->state = TYPE_RUNNING;
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (prev_p->current) {
|
|
|
|
prev_p->current->state = TYPE_NONE;
|
|
|
|
prev_p->current->state_since = timestamp;
|
2013-11-27 17:45:00 +07:00
|
|
|
if (prev_state & 2)
|
2009-09-12 12:53:05 +07:00
|
|
|
prev_p->current->state = TYPE_BLOCKED;
|
2013-11-27 17:45:00 +07:00
|
|
|
if (prev_state == 0)
|
2009-09-12 12:53:05 +07:00
|
|
|
prev_p->current->state = TYPE_WAITING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-01 23:25:51 +07:00
|
|
|
static const char *cat_backtrace(union perf_event *event,
|
|
|
|
struct perf_sample *sample,
|
|
|
|
struct machine *machine)
|
|
|
|
{
|
|
|
|
struct addr_location al;
|
|
|
|
unsigned int i;
|
|
|
|
char *p = NULL;
|
|
|
|
size_t p_len;
|
|
|
|
u8 cpumode = PERF_RECORD_MISC_USER;
|
|
|
|
struct addr_location tal;
|
|
|
|
struct ip_callchain *chain = sample->callchain;
|
|
|
|
FILE *f = open_memstream(&p, &p_len);
|
|
|
|
|
|
|
|
if (!f) {
|
|
|
|
perror("open_memstream error");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chain)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
|
|
|
|
fprintf(stderr, "problem processing %d event, skipping it.\n",
|
|
|
|
event->header.type);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < chain->nr; i++) {
|
|
|
|
u64 ip;
|
|
|
|
|
|
|
|
if (callchain_param.order == ORDER_CALLEE)
|
|
|
|
ip = chain->ips[i];
|
|
|
|
else
|
|
|
|
ip = chain->ips[chain->nr - i - 1];
|
|
|
|
|
|
|
|
if (ip >= PERF_CONTEXT_MAX) {
|
|
|
|
switch (ip) {
|
|
|
|
case PERF_CONTEXT_HV:
|
|
|
|
cpumode = PERF_RECORD_MISC_HYPERVISOR;
|
|
|
|
break;
|
|
|
|
case PERF_CONTEXT_KERNEL:
|
|
|
|
cpumode = PERF_RECORD_MISC_KERNEL;
|
|
|
|
break;
|
|
|
|
case PERF_CONTEXT_USER:
|
|
|
|
cpumode = PERF_RECORD_MISC_USER;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pr_debug("invalid callchain context: "
|
|
|
|
"%"PRId64"\n", (s64) ip);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It seems the callchain is corrupted.
|
|
|
|
* Discard all.
|
|
|
|
*/
|
|
|
|
free(p);
|
|
|
|
p = NULL;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
tal.filtered = false;
|
|
|
|
thread__find_addr_location(al.thread, machine, cpumode,
|
|
|
|
MAP__FUNCTION, ip, &tal);
|
|
|
|
|
|
|
|
if (tal.sym)
|
|
|
|
fprintf(f, "..... %016" PRIx64 " %s\n", ip,
|
|
|
|
tal.sym->name);
|
|
|
|
else
|
|
|
|
fprintf(f, "..... %016" PRIx64 "\n", ip);
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2013-07-11 22:28:30 +07:00
|
|
|
typedef int (*tracepoint_handler)(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2012-09-11 05:15:03 +07:00
|
|
|
static int process_sample_event(struct perf_tool *tool __maybe_unused,
|
|
|
|
union perf_event *event __maybe_unused,
|
2011-01-29 22:02:00 +07:00
|
|
|
struct perf_sample *sample,
|
2011-11-17 02:02:54 +07:00
|
|
|
struct perf_evsel *evsel,
|
2012-09-11 05:15:03 +07:00
|
|
|
struct machine *machine __maybe_unused)
|
2009-09-12 12:53:05 +07:00
|
|
|
{
|
2011-11-17 02:02:54 +07:00
|
|
|
if (evsel->attr.sample_type & PERF_SAMPLE_TIME) {
|
perf session: Parse sample earlier
At perf_session__process_event, so that we reduce the number of lines in eache
tool sample processing routine that now receives a sample_data pointer already
parsed.
This will also be useful in the next patch, where we'll allow sample the
identity fields in MMAP, FORK, EXIT, etc, when it will be possible to see (cpu,
timestamp) just after before every event.
Also validate callchains in perf_session__process_event, i.e. as early as
possible, and keep a counter of the number of events discarded due to invalid
callchains, warning the user about it if it happens.
There is an assumption that was kept that all events have the same sample_type,
that will be dealt with in the future, when this preexisting limitation will be
removed.
Tested-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Ian Munsie <imunsie@au1.ibm.com>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Frédéric Weisbecker <fweisbec@gmail.com>
Cc: Ian Munsie <imunsie@au1.ibm.com>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
LKML-Reference: <1291318772-30880-4-git-send-email-acme@infradead.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2010-12-02 23:10:21 +07:00
|
|
|
if (!first_time || first_time > sample->time)
|
|
|
|
first_time = sample->time;
|
|
|
|
if (last_time < sample->time)
|
|
|
|
last_time = sample->time;
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
2009-12-06 18:08:24 +07:00
|
|
|
|
2013-07-11 22:28:30 +07:00
|
|
|
if (sample->cpu > numcpus)
|
|
|
|
numcpus = sample->cpu;
|
|
|
|
|
2013-11-06 20:17:38 +07:00
|
|
|
if (evsel->handler != NULL) {
|
|
|
|
tracepoint_handler f = evsel->handler;
|
2013-11-01 23:25:51 +07:00
|
|
|
return f(evsel, sample, cat_backtrace(event, sample, machine));
|
2013-07-11 22:28:30 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_cpu_idle(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace __maybe_unused)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
u32 state = perf_evsel__intval(evsel, sample, "state");
|
|
|
|
u32 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
|
2013-07-11 22:28:30 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
if (state == (u32)PWR_EVENT_EXIT)
|
|
|
|
c_state_end(cpu_id, sample->time);
|
2013-07-11 22:28:30 +07:00
|
|
|
else
|
2013-11-27 17:45:00 +07:00
|
|
|
c_state_start(cpu_id, sample->time, state);
|
2013-07-11 22:28:30 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_cpu_frequency(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace __maybe_unused)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
u32 state = perf_evsel__intval(evsel, sample, "state");
|
|
|
|
u32 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
|
2013-07-11 22:28:30 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
p_state_change(cpu_id, sample->time, state);
|
2013-07-11 22:28:30 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_sched_wakeup(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
u8 flags = perf_evsel__intval(evsel, sample, "common_flags");
|
|
|
|
int waker = perf_evsel__intval(evsel, sample, "common_pid");
|
|
|
|
int wakee = perf_evsel__intval(evsel, sample, "pid");
|
2013-07-11 22:28:30 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
sched_wakeup(sample->cpu, sample->time, waker, wakee, flags, backtrace);
|
2013-07-11 22:28:30 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-07-11 22:28:30 +07:00
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_sched_switch(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
int prev_pid = perf_evsel__intval(evsel, sample, "prev_pid");
|
|
|
|
int next_pid = perf_evsel__intval(evsel, sample, "next_pid");
|
|
|
|
u64 prev_state = perf_evsel__intval(evsel, sample, "prev_state");
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
sched_switch(sample->cpu, sample->time, prev_pid, next_pid, prev_state,
|
|
|
|
backtrace);
|
2013-07-11 22:28:30 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2011-01-03 23:50:45 +07:00
|
|
|
|
|
|
|
#ifdef SUPPORT_OLD_POWER_EVENTS
|
2013-07-11 22:28:30 +07:00
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_power_start(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace __maybe_unused)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
u64 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
|
|
|
|
u64 value = perf_evsel__intval(evsel, sample, "value");
|
2013-07-11 22:28:30 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
c_state_start(cpu_id, sample->time, value);
|
2013-07-11 22:28:30 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
process_sample_power_end(struct perf_evsel *evsel __maybe_unused,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace __maybe_unused)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
|
|
|
c_state_end(sample->cpu, sample->time);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2013-11-27 17:45:00 +07:00
|
|
|
process_sample_power_frequency(struct perf_evsel *evsel,
|
2013-11-01 23:25:51 +07:00
|
|
|
struct perf_sample *sample,
|
|
|
|
const char *backtrace __maybe_unused)
|
2013-07-11 22:28:30 +07:00
|
|
|
{
|
2013-11-27 17:45:00 +07:00
|
|
|
u64 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
|
|
|
|
u64 value = perf_evsel__intval(evsel, sample, "value");
|
2013-07-11 22:28:30 +07:00
|
|
|
|
2013-11-27 17:45:00 +07:00
|
|
|
p_state_change(cpu_id, sample->time, value);
|
2009-09-12 12:53:05 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2013-07-11 22:28:30 +07:00
|
|
|
#endif /* SUPPORT_OLD_POWER_EVENTS */
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* After the last sample we need to wrap up the current C/P state
|
|
|
|
* and close out each CPU for these.
|
|
|
|
*/
|
|
|
|
static void end_sample_processing(void)
|
|
|
|
{
|
|
|
|
u64 cpu;
|
|
|
|
struct power_event *pwr;
|
|
|
|
|
2009-09-24 20:40:13 +07:00
|
|
|
for (cpu = 0; cpu <= numcpus; cpu++) {
|
2012-09-24 21:16:40 +07:00
|
|
|
/* C state */
|
|
|
|
#if 0
|
|
|
|
pwr = zalloc(sizeof(*pwr));
|
2009-09-12 12:53:05 +07:00
|
|
|
if (!pwr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pwr->state = cpus_cstate_state[cpu];
|
|
|
|
pwr->start_time = cpus_cstate_start_times[cpu];
|
|
|
|
pwr->end_time = last_time;
|
|
|
|
pwr->cpu = cpu;
|
|
|
|
pwr->type = CSTATE;
|
|
|
|
pwr->next = power_events;
|
|
|
|
|
|
|
|
power_events = pwr;
|
|
|
|
#endif
|
|
|
|
/* P state */
|
|
|
|
|
2012-09-24 21:16:40 +07:00
|
|
|
pwr = zalloc(sizeof(*pwr));
|
2009-09-12 12:53:05 +07:00
|
|
|
if (!pwr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pwr->state = cpus_pstate_state[cpu];
|
|
|
|
pwr->start_time = cpus_pstate_start_times[cpu];
|
|
|
|
pwr->end_time = last_time;
|
|
|
|
pwr->cpu = cpu;
|
|
|
|
pwr->type = PSTATE;
|
|
|
|
pwr->next = power_events;
|
|
|
|
|
|
|
|
if (!pwr->start_time)
|
|
|
|
pwr->start_time = first_time;
|
|
|
|
if (!pwr->state)
|
|
|
|
pwr->state = min_freq;
|
|
|
|
power_events = pwr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sort the pid datastructure
|
|
|
|
*/
|
|
|
|
static void sort_pids(void)
|
|
|
|
{
|
|
|
|
struct per_pid *new_list, *p, *cursor, *prev;
|
|
|
|
/* sort by ppid first, then by pid, lowest to highest */
|
|
|
|
|
|
|
|
new_list = NULL;
|
|
|
|
|
|
|
|
while (all_data) {
|
|
|
|
p = all_data;
|
|
|
|
all_data = p->next;
|
|
|
|
p->next = NULL;
|
|
|
|
|
|
|
|
if (new_list == NULL) {
|
|
|
|
new_list = p;
|
|
|
|
p->next = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
prev = NULL;
|
|
|
|
cursor = new_list;
|
|
|
|
while (cursor) {
|
|
|
|
if (cursor->ppid > p->ppid ||
|
|
|
|
(cursor->ppid == p->ppid && cursor->pid > p->pid)) {
|
|
|
|
/* must insert before */
|
|
|
|
if (prev) {
|
|
|
|
p->next = prev->next;
|
|
|
|
prev->next = p;
|
|
|
|
cursor = NULL;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
p->next = new_list;
|
|
|
|
new_list = p;
|
|
|
|
cursor = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = cursor;
|
|
|
|
cursor = cursor->next;
|
|
|
|
if (!cursor)
|
|
|
|
prev->next = p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
all_data = new_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void draw_c_p_states(void)
|
|
|
|
{
|
|
|
|
struct power_event *pwr;
|
|
|
|
pwr = power_events;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* two pass drawing so that the P state bars are on top of the C state blocks
|
|
|
|
*/
|
|
|
|
while (pwr) {
|
|
|
|
if (pwr->type == CSTATE)
|
|
|
|
svg_cstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
|
|
|
|
pwr = pwr->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
pwr = power_events;
|
|
|
|
while (pwr) {
|
|
|
|
if (pwr->type == PSTATE) {
|
|
|
|
if (!pwr->state)
|
|
|
|
pwr->state = min_freq;
|
|
|
|
svg_pstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
|
|
|
|
}
|
|
|
|
pwr = pwr->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_wakeups(void)
|
|
|
|
{
|
|
|
|
struct wake_event *we;
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
|
|
|
|
we = wake_events;
|
|
|
|
while (we) {
|
|
|
|
int from = 0, to = 0;
|
2009-09-20 23:13:28 +07:00
|
|
|
char *task_from = NULL, *task_to = NULL;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
/* locate the column of the waker and wakee */
|
|
|
|
p = all_data;
|
|
|
|
while (p) {
|
|
|
|
if (p->pid == we->waker || p->pid == we->wakee) {
|
|
|
|
c = p->all;
|
|
|
|
while (c) {
|
|
|
|
if (c->Y && c->start_time <= we->time && c->end_time >= we->time) {
|
2009-10-20 05:09:39 +07:00
|
|
|
if (p->pid == we->waker && !from) {
|
2009-09-12 12:53:05 +07:00
|
|
|
from = c->Y;
|
2009-10-20 04:46:49 +07:00
|
|
|
task_from = strdup(c->comm);
|
2009-09-20 23:13:28 +07:00
|
|
|
}
|
2009-10-20 05:09:39 +07:00
|
|
|
if (p->pid == we->wakee && !to) {
|
2009-09-12 12:53:05 +07:00
|
|
|
to = c->Y;
|
2009-10-20 04:46:49 +07:00
|
|
|
task_to = strdup(c->comm);
|
2009-09-20 23:13:28 +07:00
|
|
|
}
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
|
|
|
c = c->next;
|
|
|
|
}
|
2009-10-20 04:46:49 +07:00
|
|
|
c = p->all;
|
|
|
|
while (c) {
|
|
|
|
if (p->pid == we->waker && !from) {
|
|
|
|
from = c->Y;
|
|
|
|
task_from = strdup(c->comm);
|
|
|
|
}
|
|
|
|
if (p->pid == we->wakee && !to) {
|
|
|
|
to = c->Y;
|
|
|
|
task_to = strdup(c->comm);
|
|
|
|
}
|
|
|
|
c = c->next;
|
|
|
|
}
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
|
2009-10-20 04:46:49 +07:00
|
|
|
if (!task_from) {
|
|
|
|
task_from = malloc(40);
|
|
|
|
sprintf(task_from, "[%i]", we->waker);
|
|
|
|
}
|
|
|
|
if (!task_to) {
|
|
|
|
task_to = malloc(40);
|
|
|
|
sprintf(task_to, "[%i]", we->wakee);
|
|
|
|
}
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
if (we->waker == -1)
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_interrupt(we->time, to, we->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
else if (from && to && abs(from - to) == 1)
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_wakeline(we->time, from, to, we->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
else
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_partial_wakeline(we->time, from, task_from, to,
|
|
|
|
task_to, we->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
we = we->next;
|
2009-10-20 04:46:49 +07:00
|
|
|
|
|
|
|
free(task_from);
|
|
|
|
free(task_to);
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_cpu_usage(void)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
struct cpu_sample *sample;
|
|
|
|
p = all_data;
|
|
|
|
while (p) {
|
|
|
|
c = p->all;
|
|
|
|
while (c) {
|
|
|
|
sample = c->samples;
|
|
|
|
while (sample) {
|
|
|
|
if (sample->type == TYPE_RUNNING)
|
|
|
|
svg_process(sample->cpu, sample->start_time, sample->end_time, "sample", c->comm);
|
|
|
|
|
|
|
|
sample = sample->next;
|
|
|
|
}
|
|
|
|
c = c->next;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_process_bars(void)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
struct cpu_sample *sample;
|
|
|
|
int Y = 0;
|
|
|
|
|
|
|
|
Y = 2 * numcpus + 2;
|
|
|
|
|
|
|
|
p = all_data;
|
|
|
|
while (p) {
|
|
|
|
c = p->all;
|
|
|
|
while (c) {
|
|
|
|
if (!c->display) {
|
|
|
|
c->Y = 0;
|
|
|
|
c = c->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-09-20 23:13:53 +07:00
|
|
|
svg_box(Y, c->start_time, c->end_time, "process");
|
2009-09-12 12:53:05 +07:00
|
|
|
sample = c->samples;
|
|
|
|
while (sample) {
|
|
|
|
if (sample->type == TYPE_RUNNING)
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_running(Y, sample->cpu,
|
|
|
|
sample->start_time,
|
|
|
|
sample->end_time,
|
|
|
|
sample->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
if (sample->type == TYPE_BLOCKED)
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_blocked(Y, sample->cpu,
|
|
|
|
sample->start_time,
|
|
|
|
sample->end_time,
|
|
|
|
sample->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
if (sample->type == TYPE_WAITING)
|
2013-11-01 23:25:51 +07:00
|
|
|
svg_waiting(Y, sample->cpu,
|
|
|
|
sample->start_time,
|
|
|
|
sample->end_time,
|
|
|
|
sample->backtrace);
|
2009-09-12 12:53:05 +07:00
|
|
|
sample = sample->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->comm) {
|
|
|
|
char comm[256];
|
|
|
|
if (c->total_time > 5000000000) /* 5 seconds */
|
|
|
|
sprintf(comm, "%s:%i (%2.2fs)", c->comm, p->pid, c->total_time / 1000000000.0);
|
|
|
|
else
|
|
|
|
sprintf(comm, "%s:%i (%3.1fms)", c->comm, p->pid, c->total_time / 1000000.0);
|
|
|
|
|
|
|
|
svg_text(Y, c->start_time, comm);
|
|
|
|
}
|
|
|
|
c->Y = Y;
|
|
|
|
Y++;
|
|
|
|
c = c->next;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-20 05:09:39 +07:00
|
|
|
static void add_process_filter(const char *string)
|
|
|
|
{
|
2012-09-24 21:16:40 +07:00
|
|
|
int pid = strtoull(string, NULL, 10);
|
|
|
|
struct process_filter *filt = malloc(sizeof(*filt));
|
2009-10-20 05:09:39 +07:00
|
|
|
|
|
|
|
if (!filt)
|
|
|
|
return;
|
|
|
|
|
|
|
|
filt->name = strdup(string);
|
|
|
|
filt->pid = pid;
|
|
|
|
filt->next = process_filter;
|
|
|
|
|
|
|
|
process_filter = filt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int passes_filter(struct per_pid *p, struct per_pidcomm *c)
|
|
|
|
{
|
|
|
|
struct process_filter *filt;
|
|
|
|
if (!process_filter)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
filt = process_filter;
|
|
|
|
while (filt) {
|
|
|
|
if (filt->pid && p->pid == filt->pid)
|
|
|
|
return 1;
|
|
|
|
if (strcmp(filt->name, c->comm) == 0)
|
|
|
|
return 1;
|
|
|
|
filt = filt->next;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int determine_display_tasks_filtered(void)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
p = all_data;
|
|
|
|
while (p) {
|
|
|
|
p->display = 0;
|
|
|
|
if (p->start_time == 1)
|
|
|
|
p->start_time = first_time;
|
|
|
|
|
|
|
|
/* no exit marker, task kept running to the end */
|
|
|
|
if (p->end_time == 0)
|
|
|
|
p->end_time = last_time;
|
|
|
|
|
|
|
|
c = p->all;
|
|
|
|
|
|
|
|
while (c) {
|
|
|
|
c->display = 0;
|
|
|
|
|
|
|
|
if (c->start_time == 1)
|
|
|
|
c->start_time = first_time;
|
|
|
|
|
|
|
|
if (passes_filter(p, c)) {
|
|
|
|
c->display = 1;
|
|
|
|
p->display = 1;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->end_time == 0)
|
|
|
|
c->end_time = last_time;
|
|
|
|
|
|
|
|
c = c->next;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
static int determine_display_tasks(u64 threshold)
|
|
|
|
{
|
|
|
|
struct per_pid *p;
|
|
|
|
struct per_pidcomm *c;
|
|
|
|
int count = 0;
|
|
|
|
|
2009-10-20 05:09:39 +07:00
|
|
|
if (process_filter)
|
|
|
|
return determine_display_tasks_filtered();
|
|
|
|
|
2009-09-12 12:53:05 +07:00
|
|
|
p = all_data;
|
|
|
|
while (p) {
|
|
|
|
p->display = 0;
|
|
|
|
if (p->start_time == 1)
|
|
|
|
p->start_time = first_time;
|
|
|
|
|
|
|
|
/* no exit marker, task kept running to the end */
|
|
|
|
if (p->end_time == 0)
|
|
|
|
p->end_time = last_time;
|
2013-11-01 23:25:47 +07:00
|
|
|
if (p->total_time >= threshold)
|
2009-09-12 12:53:05 +07:00
|
|
|
p->display = 1;
|
|
|
|
|
|
|
|
c = p->all;
|
|
|
|
|
|
|
|
while (c) {
|
|
|
|
c->display = 0;
|
|
|
|
|
|
|
|
if (c->start_time == 1)
|
|
|
|
c->start_time = first_time;
|
|
|
|
|
2013-11-01 23:25:47 +07:00
|
|
|
if (c->total_time >= threshold) {
|
2009-09-12 12:53:05 +07:00
|
|
|
c->display = 1;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->end_time == 0)
|
|
|
|
c->end_time = last_time;
|
|
|
|
|
|
|
|
c = c->next;
|
|
|
|
}
|
|
|
|
p = p->next;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define TIME_THRESH 10000000
|
|
|
|
|
|
|
|
static void write_svg_file(const char *filename)
|
|
|
|
{
|
|
|
|
u64 i;
|
|
|
|
int count;
|
2013-11-01 23:25:45 +07:00
|
|
|
int thresh = TIME_THRESH;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
numcpus++;
|
|
|
|
|
2013-11-01 23:25:47 +07:00
|
|
|
if (power_only)
|
|
|
|
proc_num = 0;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-01 23:25:45 +07:00
|
|
|
/* We'd like to show at least proc_num tasks;
|
|
|
|
* be less picky if we have fewer */
|
|
|
|
do {
|
|
|
|
count = determine_display_tasks(thresh);
|
|
|
|
thresh /= 10;
|
2013-11-01 23:25:46 +07:00
|
|
|
} while (!process_filter && thresh && count < proc_num);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2009-09-20 23:14:16 +07:00
|
|
|
open_svg(filename, numcpus, count, first_time, last_time);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2009-09-20 23:14:16 +07:00
|
|
|
svg_time_grid();
|
2009-09-12 12:53:05 +07:00
|
|
|
svg_legenda();
|
|
|
|
|
|
|
|
for (i = 0; i < numcpus; i++)
|
|
|
|
svg_cpu_box(i, max_freq, turbo_frequency);
|
|
|
|
|
|
|
|
draw_cpu_usage();
|
2013-11-01 23:25:47 +07:00
|
|
|
if (proc_num)
|
|
|
|
draw_process_bars();
|
2013-11-01 23:25:48 +07:00
|
|
|
if (!tasks_only)
|
|
|
|
draw_c_p_states();
|
2013-11-01 23:25:47 +07:00
|
|
|
if (proc_num)
|
|
|
|
draw_wakeups();
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
svg_close();
|
|
|
|
}
|
|
|
|
|
2012-10-30 10:56:02 +07:00
|
|
|
static int __cmd_timechart(const char *output_name)
|
2009-12-01 13:05:16 +07:00
|
|
|
{
|
2012-10-02 01:20:58 +07:00
|
|
|
struct perf_tool perf_timechart = {
|
|
|
|
.comm = process_comm_event,
|
|
|
|
.fork = process_fork_event,
|
|
|
|
.exit = process_exit_event,
|
|
|
|
.sample = process_sample_event,
|
|
|
|
.ordered_samples = true,
|
|
|
|
};
|
2013-07-11 22:28:30 +07:00
|
|
|
const struct perf_evsel_str_handler power_tracepoints[] = {
|
|
|
|
{ "power:cpu_idle", process_sample_cpu_idle },
|
|
|
|
{ "power:cpu_frequency", process_sample_cpu_frequency },
|
|
|
|
{ "sched:sched_wakeup", process_sample_sched_wakeup },
|
|
|
|
{ "sched:sched_switch", process_sample_sched_switch },
|
|
|
|
#ifdef SUPPORT_OLD_POWER_EVENTS
|
|
|
|
{ "power:power_start", process_sample_power_start },
|
|
|
|
{ "power:power_end", process_sample_power_end },
|
|
|
|
{ "power:power_frequency", process_sample_power_frequency },
|
|
|
|
#endif
|
|
|
|
};
|
2013-10-15 21:27:32 +07:00
|
|
|
struct perf_data_file file = {
|
|
|
|
.path = input_name,
|
|
|
|
.mode = PERF_DATA_MODE_READ,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct perf_session *session = perf_session__new(&file, false,
|
|
|
|
&perf_timechart);
|
2009-12-28 06:37:02 +07:00
|
|
|
int ret = -EINVAL;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2009-12-12 06:24:02 +07:00
|
|
|
if (session == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2009-12-28 06:37:02 +07:00
|
|
|
if (!perf_session__has_traces(session, "timechart record"))
|
|
|
|
goto out_delete;
|
|
|
|
|
2013-07-11 22:28:30 +07:00
|
|
|
if (perf_session__set_tracepoints_handlers(session,
|
|
|
|
power_tracepoints)) {
|
|
|
|
pr_err("Initializing session tracepoint handlers failed\n");
|
|
|
|
goto out_delete;
|
|
|
|
}
|
|
|
|
|
2011-11-28 17:30:20 +07:00
|
|
|
ret = perf_session__process_events(session, &perf_timechart);
|
2009-12-01 13:05:16 +07:00
|
|
|
if (ret)
|
2009-12-12 06:24:02 +07:00
|
|
|
goto out_delete;
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
end_sample_processing();
|
|
|
|
|
|
|
|
sort_pids();
|
|
|
|
|
|
|
|
write_svg_file(output_name);
|
|
|
|
|
2009-10-22 02:34:06 +07:00
|
|
|
pr_info("Written %2.1f seconds of trace to %s.\n",
|
|
|
|
(last_time - first_time) / 1000000000.0, output_name);
|
2009-12-12 06:24:02 +07:00
|
|
|
out_delete:
|
|
|
|
perf_session__delete(session);
|
|
|
|
return ret;
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|
|
|
|
|
2009-09-19 18:34:42 +07:00
|
|
|
static int __cmd_record(int argc, const char **argv)
|
|
|
|
{
|
2013-11-01 23:25:50 +07:00
|
|
|
unsigned int rec_argc, i, j;
|
|
|
|
const char **rec_argv;
|
|
|
|
const char **p;
|
|
|
|
unsigned int record_elems;
|
|
|
|
|
|
|
|
const char * const common_args[] = {
|
2013-06-05 18:37:21 +07:00
|
|
|
"record", "-a", "-R", "-c", "1",
|
2013-11-01 23:25:50 +07:00
|
|
|
};
|
|
|
|
unsigned int common_args_nr = ARRAY_SIZE(common_args);
|
|
|
|
|
2013-11-01 23:25:51 +07:00
|
|
|
const char * const backtrace_args[] = {
|
|
|
|
"-g",
|
|
|
|
};
|
|
|
|
unsigned int backtrace_args_no = ARRAY_SIZE(backtrace_args);
|
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
const char * const power_args[] = {
|
|
|
|
"-e", "power:cpu_frequency",
|
|
|
|
"-e", "power:cpu_idle",
|
|
|
|
};
|
|
|
|
unsigned int power_args_nr = ARRAY_SIZE(power_args);
|
|
|
|
|
|
|
|
const char * const old_power_args[] = {
|
|
|
|
#ifdef SUPPORT_OLD_POWER_EVENTS
|
2012-10-02 01:20:58 +07:00
|
|
|
"-e", "power:power_start",
|
|
|
|
"-e", "power:power_end",
|
|
|
|
"-e", "power:power_frequency",
|
|
|
|
#endif
|
2013-11-01 23:25:50 +07:00
|
|
|
};
|
|
|
|
unsigned int old_power_args_nr = ARRAY_SIZE(old_power_args);
|
|
|
|
|
|
|
|
const char * const tasks_args[] = {
|
2012-10-02 01:20:58 +07:00
|
|
|
"-e", "sched:sched_wakeup",
|
|
|
|
"-e", "sched:sched_switch",
|
|
|
|
};
|
2013-11-01 23:25:50 +07:00
|
|
|
unsigned int tasks_args_nr = ARRAY_SIZE(tasks_args);
|
2011-01-03 23:50:45 +07:00
|
|
|
|
|
|
|
#ifdef SUPPORT_OLD_POWER_EVENTS
|
|
|
|
if (!is_valid_tracepoint("power:cpu_idle") &&
|
|
|
|
is_valid_tracepoint("power:power_start")) {
|
|
|
|
use_old_power_events = 1;
|
2013-11-01 23:25:50 +07:00
|
|
|
power_args_nr = 0;
|
|
|
|
} else {
|
|
|
|
old_power_args_nr = 0;
|
2011-01-03 23:50:45 +07:00
|
|
|
}
|
|
|
|
#endif
|
2009-09-19 18:34:42 +07:00
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
if (power_only)
|
|
|
|
tasks_args_nr = 0;
|
|
|
|
|
|
|
|
if (tasks_only) {
|
|
|
|
power_args_nr = 0;
|
|
|
|
old_power_args_nr = 0;
|
|
|
|
}
|
|
|
|
|
2013-11-01 23:25:51 +07:00
|
|
|
if (!with_backtrace)
|
|
|
|
backtrace_args_no = 0;
|
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
record_elems = common_args_nr + tasks_args_nr +
|
2013-11-01 23:25:51 +07:00
|
|
|
power_args_nr + old_power_args_nr + backtrace_args_no;
|
2013-11-01 23:25:50 +07:00
|
|
|
|
|
|
|
rec_argc = record_elems + argc;
|
2009-09-19 18:34:42 +07:00
|
|
|
rec_argv = calloc(rec_argc + 1, sizeof(char *));
|
|
|
|
|
2010-11-13 09:35:06 +07:00
|
|
|
if (rec_argv == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
p = rec_argv;
|
|
|
|
for (i = 0; i < common_args_nr; i++)
|
|
|
|
*p++ = strdup(common_args[i]);
|
|
|
|
|
2013-11-01 23:25:51 +07:00
|
|
|
for (i = 0; i < backtrace_args_no; i++)
|
|
|
|
*p++ = strdup(backtrace_args[i]);
|
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
for (i = 0; i < tasks_args_nr; i++)
|
|
|
|
*p++ = strdup(tasks_args[i]);
|
|
|
|
|
|
|
|
for (i = 0; i < power_args_nr; i++)
|
|
|
|
*p++ = strdup(power_args[i]);
|
2009-09-19 18:34:42 +07:00
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
for (i = 0; i < old_power_args_nr; i++)
|
|
|
|
*p++ = strdup(old_power_args[i]);
|
2009-09-19 18:34:42 +07:00
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
for (j = 1; j < (unsigned int)argc; j++)
|
|
|
|
*p++ = argv[j];
|
|
|
|
|
|
|
|
return cmd_record(rec_argc, rec_argv, NULL);
|
2009-09-19 18:34:42 +07:00
|
|
|
}
|
|
|
|
|
2009-10-20 05:09:39 +07:00
|
|
|
static int
|
2012-09-11 05:15:03 +07:00
|
|
|
parse_process(const struct option *opt __maybe_unused, const char *arg,
|
|
|
|
int __maybe_unused unset)
|
2009-10-20 05:09:39 +07:00
|
|
|
{
|
|
|
|
if (arg)
|
|
|
|
add_process_filter(arg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-02 01:20:58 +07:00
|
|
|
int cmd_timechart(int argc, const char **argv,
|
|
|
|
const char *prefix __maybe_unused)
|
|
|
|
{
|
|
|
|
const char *output_name = "output.svg";
|
2013-11-01 23:25:50 +07:00
|
|
|
const struct option timechart_options[] = {
|
2012-10-02 01:20:58 +07:00
|
|
|
OPT_STRING('i', "input", &input_name, "file", "input file name"),
|
|
|
|
OPT_STRING('o', "output", &output_name, "file", "output file name"),
|
|
|
|
OPT_INTEGER('w', "width", &svg_page_width, "page width"),
|
|
|
|
OPT_BOOLEAN('P', "power-only", &power_only, "output power data only"),
|
2013-11-01 23:25:48 +07:00
|
|
|
OPT_BOOLEAN('T', "tasks-only", &tasks_only,
|
|
|
|
"output processes data only"),
|
2009-10-20 05:09:39 +07:00
|
|
|
OPT_CALLBACK('p', "process", NULL, "process",
|
|
|
|
"process selector. Pass a pid or process name.",
|
|
|
|
parse_process),
|
2010-12-10 03:27:07 +07:00
|
|
|
OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
|
|
|
|
"Look for files with symbols relative to this directory"),
|
2013-11-01 23:25:46 +07:00
|
|
|
OPT_INTEGER('n', "proc-num", &proc_num,
|
|
|
|
"min. number of tasks to print"),
|
2009-09-12 12:53:05 +07:00
|
|
|
OPT_END()
|
2012-10-02 01:20:58 +07:00
|
|
|
};
|
|
|
|
const char * const timechart_usage[] = {
|
|
|
|
"perf timechart [<options>] {record}",
|
|
|
|
NULL
|
|
|
|
};
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
const struct option record_options[] = {
|
|
|
|
OPT_BOOLEAN('P', "power-only", &power_only, "output power data only"),
|
|
|
|
OPT_BOOLEAN('T', "tasks-only", &tasks_only,
|
|
|
|
"output processes data only"),
|
2013-11-01 23:25:51 +07:00
|
|
|
OPT_BOOLEAN('g', "callchain", &with_backtrace, "record callchain"),
|
2013-11-01 23:25:50 +07:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
const char * const record_usage[] = {
|
|
|
|
"perf timechart record [<options>]",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
argc = parse_options(argc, argv, timechart_options, timechart_usage,
|
2009-09-19 18:34:42 +07:00
|
|
|
PARSE_OPT_STOP_AT_NON_OPTION);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
2013-11-01 23:25:48 +07:00
|
|
|
if (power_only && tasks_only) {
|
|
|
|
pr_err("-P and -T options cannot be used at the same time.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-12-16 05:04:40 +07:00
|
|
|
symbol__init();
|
|
|
|
|
2013-11-01 23:25:50 +07:00
|
|
|
if (argc && !strncmp(argv[0], "rec", 3)) {
|
|
|
|
argc = parse_options(argc, argv, record_options, record_usage,
|
|
|
|
PARSE_OPT_STOP_AT_NON_OPTION);
|
|
|
|
|
|
|
|
if (power_only && tasks_only) {
|
|
|
|
pr_err("-P and -T options cannot be used at the same time.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-09-19 18:34:42 +07:00
|
|
|
return __cmd_record(argc, argv);
|
2013-11-01 23:25:50 +07:00
|
|
|
} else if (argc)
|
|
|
|
usage_with_options(timechart_usage, timechart_options);
|
2009-09-12 12:53:05 +07:00
|
|
|
|
|
|
|
setup_pager();
|
|
|
|
|
2012-10-30 10:56:02 +07:00
|
|
|
return __cmd_timechart(output_name);
|
2009-09-12 12:53:05 +07:00
|
|
|
}
|