Newer
Older
#include "perf.h"
#include "util/util.h"
#include "util/cache.h"
#include "util/symbol.h"
#include "util/thread.h"
#include "util/header.h"
#include "util/session.h"
#include "util/trace-event.h"
#include <sys/prctl.h>
#include <semaphore.h>
#include <pthread.h>
#include <math.h>
static char const *input_name = "perf.data";
static char default_sort_order[] = "avg, max, switch, runtime";
static const char *sort_order = default_sort_order;
static int profile_cpu = -1;
#define PR_SET_NAME 15 /* Set process name */
#define MAX_CPUS 4096
static u64 run_measurement_overhead;
static u64 sleep_measurement_overhead;
#define COMM_LEN 20
#define SYM_LEN 129
#define MAX_PID 65536
static unsigned long nr_tasks;
struct task_desc {
unsigned long nr;
unsigned long pid;
char comm[COMM_LEN];
unsigned long nr_events;
unsigned long curr_event;
struct sched_atom **atoms;
pthread_t thread;
sem_t sleep_sem;
sem_t ready_for_work;
sem_t work_done_sem;
u64 cpu_usage;
};
enum sched_event_type {
SCHED_EVENT_RUN,
SCHED_EVENT_SLEEP,
SCHED_EVENT_WAKEUP,
SCHED_EVENT_MIGRATION,
enum sched_event_type type;
int specific_wait;
u64 timestamp;
u64 duration;
unsigned long nr;
sem_t *wait_sem;
struct task_desc *wakee;
};
static struct task_desc *pid_to_task[MAX_PID];
static struct task_desc **tasks;
static pthread_mutex_t start_work_mutex = PTHREAD_MUTEX_INITIALIZER;
static u64 start_time;
static pthread_mutex_t work_done_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned long nr_run_events;
static unsigned long nr_sleep_events;
static unsigned long nr_wakeup_events;
static unsigned long nr_sleep_corrections;
static unsigned long nr_run_events_optimized;
static unsigned long targetless_wakeups;
static unsigned long multitarget_wakeups;
static u64 cpu_usage;
static u64 runavg_cpu_usage;
static u64 parent_cpu_usage;
static u64 runavg_parent_cpu_usage;
static unsigned long nr_runs;
static u64 sum_runtime;
static u64 sum_fluct;
static u64 run_avg;
static unsigned int replay_repeat = 10;
static unsigned long nr_timestamps;
static unsigned long nr_unordered_timestamps;
static unsigned long nr_state_machine_bugs;
static unsigned long nr_context_switch_bugs;
static unsigned long nr_events;
static unsigned long nr_lost_chunks;
static unsigned long nr_lost_events;
#define TASK_STATE_TO_CHAR_STR "RSDTtZX"
enum thread_state {
THREAD_SLEEPING = 0,
THREAD_WAIT_CPU,
THREAD_SCHED_IN,
THREAD_IGNORE
};
struct work_atom {
struct list_head list;
enum thread_state state;
u64 sched_out_time;
u64 wake_up_time;
u64 sched_in_time;
u64 runtime;
};
struct work_atoms {
struct list_head work_list;
struct thread *thread;
struct rb_node node;
u64 max_lat;
u64 total_lat;
u64 nb_atoms;
u64 total_runtime;
};
typedef int (*sort_fn_t)(struct work_atoms *, struct work_atoms *);
static struct rb_root atom_root, sorted_atom_root;
static u64 all_runtime;
static u64 all_count;
static u64 get_nsecs(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
}
static void burn_nsecs(u64 nsecs)
u64 T0 = get_nsecs(), T1;
do {
T1 = get_nsecs();
} while (T1 + run_measurement_overhead < T0 + nsecs);
}
static void sleep_nsecs(u64 nsecs)
{
struct timespec ts;
ts.tv_nsec = nsecs % 999999999;
ts.tv_sec = nsecs / 999999999;
nanosleep(&ts, NULL);
}
static void calibrate_run_measurement_overhead(void)
{
u64 T0, T1, delta, min_delta = 1000000000ULL;
int i;
for (i = 0; i < 10; i++) {
T0 = get_nsecs();
burn_nsecs(0);
T1 = get_nsecs();
delta = T1-T0;
min_delta = min(min_delta, delta);
}
run_measurement_overhead = min_delta;
printf("run measurement overhead: %Ld nsecs\n", min_delta);
}
static void calibrate_sleep_measurement_overhead(void)
{
u64 T0, T1, delta, min_delta = 1000000000ULL;
int i;
for (i = 0; i < 10; i++) {
T0 = get_nsecs();
sleep_nsecs(10000);
T1 = get_nsecs();
delta = T1-T0;
min_delta = min(min_delta, delta);
}
min_delta -= 10000;
sleep_measurement_overhead = min_delta;
printf("sleep measurement overhead: %Ld nsecs\n", min_delta);
static struct sched_atom *
get_new_event(struct task_desc *task, u64 timestamp)
struct sched_atom *event = zalloc(sizeof(*event));
unsigned long idx = task->nr_events;
size_t size;
event->timestamp = timestamp;
event->nr = idx;
task->nr_events++;
size = sizeof(struct sched_atom *) * task->nr_events;
task->atoms = realloc(task->atoms, size);
BUG_ON(!task->atoms);
task->atoms[idx] = event;
static struct sched_atom *last_event(struct task_desc *task)
{
if (!task->nr_events)
return NULL;
return task->atoms[task->nr_events - 1];
add_sched_event_run(struct task_desc *task, u64 timestamp, u64 duration)
struct sched_atom *event, *curr_event = last_event(task);
* optimize an existing RUN event by merging this one
* to it:
*/
if (curr_event && curr_event->type == SCHED_EVENT_RUN) {
nr_run_events_optimized++;
curr_event->duration += duration;
return;
}
event = get_new_event(task, timestamp);
event->type = SCHED_EVENT_RUN;
event->duration = duration;
nr_run_events++;
}
static void
add_sched_event_wakeup(struct task_desc *task, u64 timestamp,
struct sched_atom *event, *wakee_event;
event = get_new_event(task, timestamp);
event->type = SCHED_EVENT_WAKEUP;
event->wakee = wakee;
wakee_event = last_event(wakee);
if (!wakee_event || wakee_event->type != SCHED_EVENT_SLEEP) {
targetless_wakeups++;
return;
}
if (wakee_event->wait_sem) {
multitarget_wakeups++;
return;
}
wakee_event->wait_sem = zalloc(sizeof(*wakee_event->wait_sem));
sem_init(wakee_event->wait_sem, 0, 0);
wakee_event->specific_wait = 1;
event->wait_sem = wakee_event->wait_sem;
nr_wakeup_events++;
}
static void
add_sched_event_sleep(struct task_desc *task, u64 timestamp,
struct sched_atom *event = get_new_event(task, timestamp);
event->type = SCHED_EVENT_SLEEP;
nr_sleep_events++;
}
static struct task_desc *register_pid(unsigned long pid, const char *comm)
{
struct task_desc *task;
BUG_ON(pid >= MAX_PID);
task = pid_to_task[pid];
if (task)
return task;
task = zalloc(sizeof(*task));
task->pid = pid;
task->nr = nr_tasks;
strcpy(task->comm, comm);
/*
* every task starts in sleeping state - this gets ignored
* if there's no wakeup pointing to this sleep state:
*/
add_sched_event_sleep(task, 0, 0);
pid_to_task[pid] = task;
nr_tasks++;
tasks = realloc(tasks, nr_tasks*sizeof(struct task_task *));
BUG_ON(!tasks);
tasks[task->nr] = task;
if (verbose)
printf("registered task #%ld, PID %ld (%s)\n", nr_tasks, pid, comm);
return task;
}
static void print_task_traces(void)
{
struct task_desc *task;
unsigned long i;
for (i = 0; i < nr_tasks; i++) {
task = tasks[i];
printf("task %6ld (%20s:%10ld), nr_events: %ld\n",
task->nr, task->comm, task->pid, task->nr_events);
}
}
static void add_cross_task_wakeups(void)
{
struct task_desc *task1, *task2;
unsigned long i, j;
for (i = 0; i < nr_tasks; i++) {
task1 = tasks[i];
j = i + 1;
if (j == nr_tasks)
j = 0;
task2 = tasks[j];
add_sched_event_wakeup(task1, 0, task2);
}
}
static void
process_sched_event(struct task_desc *this_task __used, struct sched_atom *atom)
long long delta;
now = get_nsecs();
delta = start_time + atom->timestamp - now;
switch (atom->type) {
burn_nsecs(atom->duration);
if (atom->wait_sem)
ret = sem_wait(atom->wait_sem);
BUG_ON(ret);
break;
case SCHED_EVENT_WAKEUP:
if (atom->wait_sem)
ret = sem_post(atom->wait_sem);
case SCHED_EVENT_MIGRATION:
break;
static u64 get_cpu_usage_nsec_parent(void)
int err;
err = getrusage(RUSAGE_SELF, &ru);
BUG_ON(err);
sum = ru.ru_utime.tv_sec*1e9 + ru.ru_utime.tv_usec*1e3;
sum += ru.ru_stime.tv_sec*1e9 + ru.ru_stime.tv_usec*1e3;
return sum;
}
static int self_open_counters(void)
struct perf_event_attr attr;
int fd;
memset(&attr, 0, sizeof(attr));
attr.type = PERF_TYPE_SOFTWARE;
attr.config = PERF_COUNT_SW_TASK_CLOCK;
fd = sys_perf_event_open(&attr, 0, -1, -1, 0);
if (fd < 0)
die("Error: sys_perf_event_open() syscall returned"
"with %d (%s)\n", fd, strerror(errno));
return fd;
}
static u64 get_cpu_usage_nsec_self(int fd)
{
u64 runtime;
int ret;
ret = read(fd, &runtime, sizeof(runtime));
BUG_ON(ret != sizeof(runtime));
return runtime;
}
static void *thread_func(void *ctx)
{
struct task_desc *this_task = ctx;
u64 cpu_usage_0, cpu_usage_1;
sprintf(comm2, ":%s", this_task->comm);
prctl(PR_SET_NAME, comm2);
fd = self_open_counters();
again:
ret = sem_post(&this_task->ready_for_work);
BUG_ON(ret);
ret = pthread_mutex_lock(&start_work_mutex);
BUG_ON(ret);
ret = pthread_mutex_unlock(&start_work_mutex);
BUG_ON(ret);
cpu_usage_0 = get_cpu_usage_nsec_self(fd);
for (i = 0; i < this_task->nr_events; i++) {
this_task->curr_event = i;
process_sched_event(this_task, this_task->atoms[i]);
cpu_usage_1 = get_cpu_usage_nsec_self(fd);
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
this_task->cpu_usage = cpu_usage_1 - cpu_usage_0;
ret = sem_post(&this_task->work_done_sem);
BUG_ON(ret);
ret = pthread_mutex_lock(&work_done_wait_mutex);
BUG_ON(ret);
ret = pthread_mutex_unlock(&work_done_wait_mutex);
BUG_ON(ret);
goto again;
}
static void create_tasks(void)
{
struct task_desc *task;
pthread_attr_t attr;
unsigned long i;
int err;
err = pthread_attr_init(&attr);
BUG_ON(err);
err = pthread_attr_setstacksize(&attr, (size_t)(16*1024));
BUG_ON(err);
err = pthread_mutex_lock(&start_work_mutex);
BUG_ON(err);
err = pthread_mutex_lock(&work_done_wait_mutex);
BUG_ON(err);
for (i = 0; i < nr_tasks; i++) {
task = tasks[i];
sem_init(&task->sleep_sem, 0, 0);
sem_init(&task->ready_for_work, 0, 0);
sem_init(&task->work_done_sem, 0, 0);
task->curr_event = 0;
err = pthread_create(&task->thread, &attr, thread_func, task);
BUG_ON(err);
}
}
static void wait_for_tasks(void)
{
u64 cpu_usage_0, cpu_usage_1;
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
struct task_desc *task;
unsigned long i, ret;
start_time = get_nsecs();
cpu_usage = 0;
pthread_mutex_unlock(&work_done_wait_mutex);
for (i = 0; i < nr_tasks; i++) {
task = tasks[i];
ret = sem_wait(&task->ready_for_work);
BUG_ON(ret);
sem_init(&task->ready_for_work, 0, 0);
}
ret = pthread_mutex_lock(&work_done_wait_mutex);
BUG_ON(ret);
cpu_usage_0 = get_cpu_usage_nsec_parent();
pthread_mutex_unlock(&start_work_mutex);
for (i = 0; i < nr_tasks; i++) {
task = tasks[i];
ret = sem_wait(&task->work_done_sem);
BUG_ON(ret);
sem_init(&task->work_done_sem, 0, 0);
cpu_usage += task->cpu_usage;
task->cpu_usage = 0;
}
cpu_usage_1 = get_cpu_usage_nsec_parent();
if (!runavg_cpu_usage)
runavg_cpu_usage = cpu_usage;
runavg_cpu_usage = (runavg_cpu_usage*9 + cpu_usage)/10;
parent_cpu_usage = cpu_usage_1 - cpu_usage_0;
if (!runavg_parent_cpu_usage)
runavg_parent_cpu_usage = parent_cpu_usage;
runavg_parent_cpu_usage = (runavg_parent_cpu_usage*9 +
parent_cpu_usage)/10;
ret = pthread_mutex_lock(&start_work_mutex);
BUG_ON(ret);
for (i = 0; i < nr_tasks; i++) {
task = tasks[i];
sem_init(&task->sleep_sem, 0, 0);
task->curr_event = 0;
}
}
static void run_one_test(void)
{
u64 T0, T1, delta, avg_delta, fluct, std_dev;
T0 = get_nsecs();
wait_for_tasks();
T1 = get_nsecs();
delta = T1 - T0;
sum_runtime += delta;
nr_runs++;
avg_delta = sum_runtime / nr_runs;
if (delta < avg_delta)
fluct = avg_delta - delta;
else
fluct = delta - avg_delta;
sum_fluct += fluct;
std_dev = sum_fluct / nr_runs / sqrt(nr_runs);
if (!run_avg)
run_avg = delta;
run_avg = (run_avg*9 + delta)/10;
(double)cpu_usage/1e6, (double)runavg_cpu_usage/1e6);
#if 0
/*
* rusage statistics done by the parent, these are less
* accurate than the sum_exec_runtime based statistics:
*/
(double)parent_cpu_usage/1e6,
(double)runavg_parent_cpu_usage/1e6);
#endif
printf(" (%ld sleep corrections)\n", nr_sleep_corrections);
nr_sleep_corrections = 0;
}
static void test_calibrations(void)
{
T0 = get_nsecs();
burn_nsecs(1e6);
T1 = get_nsecs();
printf("the run test took %Ld nsecs\n", T1-T0);
T0 = get_nsecs();
sleep_nsecs(1e6);
T1 = get_nsecs();
printf("the sleep test took %Ld nsecs\n", T1-T0);
#define FILL_FIELD(ptr, field, event, data) \
ptr.field = (typeof(ptr.field)) raw_field_value(event, #field, data)
#define FILL_ARRAY(ptr, array, event, data) \
do { \
void *__array = raw_field_ptr(event, #array, data); \
memcpy(ptr.array, __array, sizeof(ptr.array)); \
} while(0)
#define FILL_COMMON_FIELDS(ptr, event, data) \
do { \
FILL_FIELD(ptr, common_type, event, data); \
FILL_FIELD(ptr, common_flags, event, data); \
FILL_FIELD(ptr, common_preempt_count, event, data); \
FILL_FIELD(ptr, common_pid, event, data); \
FILL_FIELD(ptr, common_tgid, event, data); \
} while (0)
struct trace_switch_event {
u32 size;
u16 common_type;
u8 common_flags;
u8 common_preempt_count;
u32 common_pid;
u32 common_tgid;
char prev_comm[16];
u32 prev_pid;
u32 prev_prio;
u64 prev_state;
char next_comm[16];
u32 next_pid;
u32 next_prio;
};
struct trace_runtime_event {
u32 size;
u16 common_type;
u8 common_flags;
u8 common_preempt_count;
u32 common_pid;
u32 common_tgid;
char comm[16];
u32 pid;
u64 runtime;
u64 vruntime;
};
struct trace_wakeup_event {
u32 size;
u16 common_type;
u8 common_flags;
u8 common_preempt_count;
u32 common_pid;
u32 common_tgid;
char comm[16];
u32 pid;
u32 prio;
u32 success;
u32 cpu;
};
struct trace_fork_event {
u32 size;
u16 common_type;
u8 common_flags;
u8 common_preempt_count;
u32 common_pid;
u32 common_tgid;
char parent_comm[16];
u32 parent_pid;
char child_comm[16];
u32 child_pid;
};
struct trace_migrate_task_event {
u32 size;
u16 common_type;
u8 common_flags;
u8 common_preempt_count;
u32 common_pid;
u32 common_tgid;
char comm[16];
u32 pid;
u32 prio;
u32 cpu;
};
struct trace_sched_handler {
void (*switch_event)(struct trace_switch_event *,
struct perf_session *,
struct event *,
int cpu,
u64 timestamp,
struct thread *thread);
void (*runtime_event)(struct trace_runtime_event *,
struct perf_session *,
struct event *,
int cpu,
u64 timestamp,
struct thread *thread);
void (*wakeup_event)(struct trace_wakeup_event *,
struct perf_session *,
struct event *,
int cpu,
u64 timestamp,
struct thread *thread);
void (*fork_event)(struct trace_fork_event *,
struct event *,
int cpu,
u64 timestamp,
struct thread *thread);
void (*migrate_task_event)(struct trace_migrate_task_event *,
struct perf_session *session,
struct event *,
int cpu,
u64 timestamp,
struct thread *thread);
static void
replay_wakeup_event(struct trace_wakeup_event *wakeup_event,
struct perf_session *session __used,
struct event *event,
int cpu __used,
u64 timestamp __used,
struct thread *thread __used)
{
struct task_desc *waker, *wakee;
if (verbose) {
printf("sched_wakeup event %p\n", event);
wakeup_event->common_pid,
wakeup_event->comm,
wakeup_event->pid);
waker = register_pid(wakeup_event->common_pid, "<unknown>");
wakee = register_pid(wakeup_event->pid, wakeup_event->comm);
add_sched_event_wakeup(waker, timestamp, wakee);
static u64 cpu_last_switched[MAX_CPUS];
static void
replay_switch_event(struct trace_switch_event *switch_event,
struct perf_session *session __used,
struct event *event,
int cpu,
u64 timestamp,
struct thread *thread __used)
struct task_desc *prev, *next;
u64 timestamp0;
s64 delta;
if (verbose)
printf("sched_switch event %p\n", event);
if (cpu >= MAX_CPUS || cpu < 0)
return;
timestamp0 = cpu_last_switched[cpu];
if (timestamp0)
delta = timestamp - timestamp0;
else
delta = 0;
if (delta < 0)
die("hm, delta: %Ld < 0 ?\n", delta);
if (verbose) {
printf(" ... switch from %s/%d to %s/%d [ran %Ld nsecs]\n",
switch_event->prev_comm, switch_event->prev_pid,
switch_event->next_comm, switch_event->next_pid,
prev = register_pid(switch_event->prev_pid, switch_event->prev_comm);
next = register_pid(switch_event->next_pid, switch_event->next_comm);
cpu_last_switched[cpu] = timestamp;
add_sched_event_run(prev, timestamp, delta);
add_sched_event_sleep(prev, timestamp, switch_event->prev_state);
static void
replay_fork_event(struct trace_fork_event *fork_event,
struct event *event,
int cpu __used,
u64 timestamp __used,
struct thread *thread __used)
{
if (verbose) {
printf("sched_fork event %p\n", event);
printf("... parent: %s/%d\n", fork_event->parent_comm, fork_event->parent_pid);
printf("... child: %s/%d\n", fork_event->child_comm, fork_event->child_pid);
}
register_pid(fork_event->parent_pid, fork_event->parent_comm);
register_pid(fork_event->child_pid, fork_event->child_comm);
}
static struct trace_sched_handler replay_ops = {
.wakeup_event = replay_wakeup_event,
.switch_event = replay_switch_event,
.fork_event = replay_fork_event,
struct sort_dimension {
const char *name;
struct list_head list;
};
static LIST_HEAD(cmp_pid);
thread_lat_cmp(struct list_head *list, struct work_atoms *l, struct work_atoms *r)
{
struct sort_dimension *sort;
int ret = 0;
list_for_each_entry(sort, list, list) {
ret = sort->cmp(l, r);
if (ret)
return ret;
}
return ret;
}
static struct work_atoms *
thread_atoms_search(struct rb_root *root, struct thread *thread,
struct list_head *sort_list)
{
struct rb_node *node = root->rb_node;
struct work_atoms key = { .thread = thread };
struct work_atoms *atoms;
atoms = container_of(node, struct work_atoms, node);
cmp = thread_lat_cmp(sort_list, &key, atoms);
if (cmp > 0)
node = node->rb_left;
else if (cmp < 0)
node = node->rb_right;
else {
BUG_ON(thread != atoms->thread);
return atoms;
}
}
return NULL;
}
__thread_latency_insert(struct rb_root *root, struct work_atoms *data,
struct list_head *sort_list)
{
struct rb_node **new = &(root->rb_node), *parent = NULL;
while (*new) {
struct work_atoms *this;
this = container_of(*new, struct work_atoms, node);
cmp = thread_lat_cmp(sort_list, data, this);
if (cmp > 0)
new = &((*new)->rb_left);
else
new = &((*new)->rb_right);
}
rb_link_node(&data->node, parent, new);
rb_insert_color(&data->node, root);
}
static void thread_atoms_insert(struct thread *thread)
struct work_atoms *atoms = zalloc(sizeof(*atoms));
if (!atoms)
atoms->thread = thread;
INIT_LIST_HEAD(&atoms->work_list);
__thread_latency_insert(&atom_root, atoms, &cmp_pid);
}
static void
latency_fork_event(struct trace_fork_event *fork_event __used,
struct event *event __used,
int cpu __used,
u64 timestamp __used,
struct thread *thread __used)
{
/* should insert the newcomer */
}
static char sched_out_state(struct trace_switch_event *switch_event)
{
const char *str = TASK_STATE_TO_CHAR_STR;
return str[switch_event->prev_state];
}
static void
add_sched_out_event(struct work_atoms *atoms,
char run_state,
u64 timestamp)
struct work_atom *atom = zalloc(sizeof(*atom));
atom->sched_out_time = timestamp;
if (run_state == 'R') {
atom->state = THREAD_WAIT_CPU;
atom->wake_up_time = atom->sched_out_time;
list_add_tail(&atom->list, &atoms->work_list);
add_runtime_event(struct work_atoms *atoms, u64 delta, u64 timestamp __used)
{
struct work_atom *atom;
BUG_ON(list_empty(&atoms->work_list));
atom = list_entry(atoms->work_list.prev, struct work_atom, list);
atom->runtime += delta;
atoms->total_runtime += delta;
}
static void
add_sched_in_event(struct work_atoms *atoms, u64 timestamp)
struct work_atom *atom;