perf timechart: Add backtrace support
[linux-2.6-microblaze.git] / tools / perf / builtin-timechart.c
index 41c9bde..491662b 100644 (file)
@@ -41,6 +41,7 @@
 #define SUPPORT_OLD_POWER_EVENTS 1
 #define PWR_EVENT_EXIT -1
 
+static int proc_num = 15;
 
 static unsigned int    numcpus;
 static u64             min_freq;       /* Lowest CPU frequency seen */
@@ -50,6 +51,8 @@ static u64            turbo_frequency;
 static u64             first_time, last_time;
 
 static bool            power_only;
+static bool            tasks_only;
+static bool            with_backtrace;
 
 
 struct per_pid;
@@ -124,6 +127,7 @@ struct cpu_sample {
        u64 end_time;
        int type;
        int cpu;
+       const char *backtrace;
 };
 
 static struct per_pid *all_data;
@@ -145,6 +149,7 @@ struct wake_event {
        int waker;
        int wakee;
        u64 time;
+       const char *backtrace;
 };
 
 static struct power_event    *power_events;
@@ -229,7 +234,8 @@ static void pid_exit(int pid, u64 timestamp)
 }
 
 static void
-pid_put_sample(int pid, int type, unsigned int cpu, u64 start, u64 end)
+pid_put_sample(int pid, int type, unsigned int cpu, u64 start, u64 end,
+              const char *backtrace)
 {
        struct per_pid *p;
        struct per_pidcomm *c;
@@ -252,6 +258,7 @@ pid_put_sample(int pid, int type, unsigned int cpu, u64 start, u64 end)
        sample->type = type;
        sample->next = c->samples;
        sample->cpu = cpu;
+       sample->backtrace = backtrace;
        c->samples = sample;
 
        if (sample->type == TYPE_RUNNING && end > start && start > 0) {
@@ -403,7 +410,8 @@ static void p_state_change(int cpu, u64 timestamp, u64 new_freq)
 }
 
 static void
-sched_wakeup(int cpu, u64 timestamp, int pid, struct trace_entry *te)
+sched_wakeup(int cpu, u64 timestamp, int pid, struct trace_entry *te,
+            const char *backtrace)
 {
        struct per_pid *p;
        struct wakeup_entry *wake = (void *)te;
@@ -414,6 +422,7 @@ sched_wakeup(int cpu, u64 timestamp, int pid, struct trace_entry *te)
 
        we->time = timestamp;
        we->waker = pid;
+       we->backtrace = backtrace;
 
        if ((te->flags & TRACE_FLAG_HARDIRQ) || (te->flags & TRACE_FLAG_SOFTIRQ))
                we->waker = -1;
@@ -428,13 +437,15 @@ sched_wakeup(int cpu, u64 timestamp, int pid, struct trace_entry *te)
                p->current->state = TYPE_WAITING;
        }
        if (p && p->current && p->current->state == TYPE_BLOCKED) {
-               pid_put_sample(p->pid, p->current->state, cpu, p->current->state_since, timestamp);
+               pid_put_sample(p->pid, p->current->state, cpu,
+                              p->current->state_since, timestamp, NULL);
                p->current->state_since = timestamp;
                p->current->state = TYPE_WAITING;
        }
 }
 
-static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te)
+static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te,
+                        const char *backtrace)
 {
        struct per_pid *p = NULL, *prev_p;
        struct sched_switch *sw = (void *)te;
@@ -445,10 +456,14 @@ static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te)
        p = find_create_pid(sw->next_pid);
 
        if (prev_p->current && prev_p->current->state != TYPE_NONE)
-               pid_put_sample(sw->prev_pid, TYPE_RUNNING, cpu, prev_p->current->state_since, timestamp);
+               pid_put_sample(sw->prev_pid, TYPE_RUNNING, cpu,
+                              prev_p->current->state_since, timestamp,
+                              backtrace);
        if (p && p->current) {
                if (p->current->state != TYPE_NONE)
-                       pid_put_sample(sw->next_pid, p->current->state, cpu, p->current->state_since, timestamp);
+                       pid_put_sample(sw->next_pid, p->current->state, cpu,
+                                      p->current->state_since, timestamp,
+                                      backtrace);
 
                p->current->state_since = timestamp;
                p->current->state = TYPE_RUNNING;
@@ -464,8 +479,87 @@ static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te)
        }
 }
 
+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;
+}
+
 typedef int (*tracepoint_handler)(struct perf_evsel *evsel,
-                                 struct perf_sample *sample);
+                                 struct perf_sample *sample,
+                                 const char *backtrace);
 
 static int process_sample_event(struct perf_tool *tool __maybe_unused,
                                union perf_event *event __maybe_unused,
@@ -485,7 +579,7 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused,
 
        if (evsel->handler != NULL) {
                tracepoint_handler f = evsel->handler;
-               return f(evsel, sample);
+               return f(evsel, sample, cat_backtrace(event, sample, machine));
        }
 
        return 0;
@@ -493,7 +587,8 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused,
 
 static int
 process_sample_cpu_idle(struct perf_evsel *evsel __maybe_unused,
-                       struct perf_sample *sample)
+                       struct perf_sample *sample,
+                       const char *backtrace __maybe_unused)
 {
        struct power_processor_entry *ppe = sample->raw_data;
 
@@ -506,7 +601,8 @@ process_sample_cpu_idle(struct perf_evsel *evsel __maybe_unused,
 
 static int
 process_sample_cpu_frequency(struct perf_evsel *evsel __maybe_unused,
-                            struct perf_sample *sample)
+                            struct perf_sample *sample,
+                            const char *backtrace __maybe_unused)
 {
        struct power_processor_entry *ppe = sample->raw_data;
 
@@ -516,28 +612,31 @@ process_sample_cpu_frequency(struct perf_evsel *evsel __maybe_unused,
 
 static int
 process_sample_sched_wakeup(struct perf_evsel *evsel __maybe_unused,
-                           struct perf_sample *sample)
+                           struct perf_sample *sample,
+                           const char *backtrace)
 {
        struct trace_entry *te = sample->raw_data;
 
-       sched_wakeup(sample->cpu, sample->time, sample->pid, te);
+       sched_wakeup(sample->cpu, sample->time, sample->pid, te, backtrace);
        return 0;
 }
 
 static int
 process_sample_sched_switch(struct perf_evsel *evsel __maybe_unused,
-                           struct perf_sample *sample)
+                           struct perf_sample *sample,
+                           const char *backtrace)
 {
        struct trace_entry *te = sample->raw_data;
 
-       sched_switch(sample->cpu, sample->time, te);
+       sched_switch(sample->cpu, sample->time, te, backtrace);
        return 0;
 }
 
 #ifdef SUPPORT_OLD_POWER_EVENTS
 static int
 process_sample_power_start(struct perf_evsel *evsel __maybe_unused,
-                          struct perf_sample *sample)
+                          struct perf_sample *sample,
+                          const char *backtrace __maybe_unused)
 {
        struct power_entry_old *peo = sample->raw_data;
 
@@ -547,7 +646,8 @@ process_sample_power_start(struct perf_evsel *evsel __maybe_unused,
 
 static int
 process_sample_power_end(struct perf_evsel *evsel __maybe_unused,
-                        struct perf_sample *sample)
+                        struct perf_sample *sample,
+                        const char *backtrace __maybe_unused)
 {
        c_state_end(sample->cpu, sample->time);
        return 0;
@@ -555,7 +655,8 @@ process_sample_power_end(struct perf_evsel *evsel __maybe_unused,
 
 static int
 process_sample_power_frequency(struct perf_evsel *evsel __maybe_unused,
-                              struct perf_sample *sample)
+                              struct perf_sample *sample,
+                              const char *backtrace __maybe_unused)
 {
        struct power_entry_old *peo = sample->raw_data;
 
@@ -739,11 +840,12 @@ static void draw_wakeups(void)
                }
 
                if (we->waker == -1)
-                       svg_interrupt(we->time, to);
+                       svg_interrupt(we->time, to, we->backtrace);
                else if (from && to && abs(from - to) == 1)
-                       svg_wakeline(we->time, from, to);
+                       svg_wakeline(we->time, from, to, we->backtrace);
                else
-                       svg_partial_wakeline(we->time, from, task_from, to, task_to);
+                       svg_partial_wakeline(we->time, from, task_from, to,
+                                            task_to, we->backtrace);
                we = we->next;
 
                free(task_from);
@@ -796,11 +898,20 @@ static void draw_process_bars(void)
                        sample = c->samples;
                        while (sample) {
                                if (sample->type == TYPE_RUNNING)
-                                       svg_sample(Y, sample->cpu, sample->start_time, sample->end_time);
+                                       svg_running(Y, sample->cpu,
+                                                   sample->start_time,
+                                                   sample->end_time,
+                                                   sample->backtrace);
                                if (sample->type == TYPE_BLOCKED)
-                                       svg_box(Y, sample->start_time, sample->end_time, "blocked");
+                                       svg_blocked(Y, sample->cpu,
+                                                   sample->start_time,
+                                                   sample->end_time,
+                                                   sample->backtrace);
                                if (sample->type == TYPE_WAITING)
-                                       svg_waiting(Y, sample->start_time, sample->end_time);
+                                       svg_waiting(Y, sample->cpu,
+                                                   sample->start_time,
+                                                   sample->end_time,
+                                                   sample->backtrace);
                                sample = sample->next;
                        }
 
@@ -911,7 +1022,7 @@ static int determine_display_tasks(u64 threshold)
                /* no exit marker, task kept running to the end */
                if (p->end_time == 0)
                        p->end_time = last_time;
-               if (p->total_time >= threshold && !power_only)
+               if (p->total_time >= threshold)
                        p->display = 1;
 
                c = p->all;
@@ -922,7 +1033,7 @@ static int determine_display_tasks(u64 threshold)
                        if (c->start_time == 1)
                                c->start_time = first_time;
 
-                       if (c->total_time >= threshold && !power_only) {
+                       if (c->total_time >= threshold) {
                                c->display = 1;
                                count++;
                        }
@@ -945,15 +1056,19 @@ static void write_svg_file(const char *filename)
 {
        u64 i;
        int count;
+       int thresh = TIME_THRESH;
 
        numcpus++;
 
+       if (power_only)
+               proc_num = 0;
 
-       count = determine_display_tasks(TIME_THRESH);
-
-       /* We'd like to show at least 15 tasks; be less picky if we have fewer */
-       if (count < 15)
-               count = determine_display_tasks(TIME_THRESH / 10);
+       /* 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;
+       } while (!process_filter && thresh && count < proc_num);
 
        open_svg(filename, numcpus, count, first_time, last_time);
 
@@ -964,9 +1079,12 @@ static void write_svg_file(const char *filename)
                svg_cpu_box(i, max_freq, turbo_frequency);
 
        draw_cpu_usage();
-       draw_process_bars();
-       draw_c_p_states();
-       draw_wakeups();
+       if (proc_num)
+               draw_process_bars();
+       if (!tasks_only)
+               draw_c_p_states();
+       if (proc_num)
+               draw_wakeups();
 
        svg_close();
 }
@@ -1031,50 +1149,92 @@ out_delete:
 
 static int __cmd_record(int argc, const char **argv)
 {
-#ifdef SUPPORT_OLD_POWER_EVENTS
-       const char * const record_old_args[] = {
+       unsigned int rec_argc, i, j;
+       const char **rec_argv;
+       const char **p;
+       unsigned int record_elems;
+
+       const char * const common_args[] = {
                "record", "-a", "-R", "-c", "1",
+       };
+       unsigned int common_args_nr = ARRAY_SIZE(common_args);
+
+       const char * const backtrace_args[] = {
+               "-g",
+       };
+       unsigned int backtrace_args_no = ARRAY_SIZE(backtrace_args);
+
+       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
                "-e", "power:power_start",
                "-e", "power:power_end",
                "-e", "power:power_frequency",
-               "-e", "sched:sched_wakeup",
-               "-e", "sched:sched_switch",
-       };
 #endif
-       const char * const record_new_args[] = {
-               "record", "-a", "-R", "-c", "1",
-               "-e", "power:cpu_frequency",
-               "-e", "power:cpu_idle",
+       };
+       unsigned int old_power_args_nr = ARRAY_SIZE(old_power_args);
+
+       const char * const tasks_args[] = {
                "-e", "sched:sched_wakeup",
                "-e", "sched:sched_switch",
        };
-       unsigned int rec_argc, i, j;
-       const char **rec_argv;
-       const char * const *record_args = record_new_args;
-       unsigned int record_elems = ARRAY_SIZE(record_new_args);
+       unsigned int tasks_args_nr = ARRAY_SIZE(tasks_args);
 
 #ifdef SUPPORT_OLD_POWER_EVENTS
        if (!is_valid_tracepoint("power:cpu_idle") &&
            is_valid_tracepoint("power:power_start")) {
                use_old_power_events = 1;
-               record_args = record_old_args;
-               record_elems = ARRAY_SIZE(record_old_args);
+               power_args_nr = 0;
+       } else {
+               old_power_args_nr = 0;
        }
 #endif
 
-       rec_argc = record_elems + argc - 1;
+       if (power_only)
+               tasks_args_nr = 0;
+
+       if (tasks_only) {
+               power_args_nr = 0;
+               old_power_args_nr = 0;
+       }
+
+       if (!with_backtrace)
+               backtrace_args_no = 0;
+
+       record_elems = common_args_nr + tasks_args_nr +
+               power_args_nr + old_power_args_nr + backtrace_args_no;
+
+       rec_argc = record_elems + argc;
        rec_argv = calloc(rec_argc + 1, sizeof(char *));
 
        if (rec_argv == NULL)
                return -ENOMEM;
 
-       for (i = 0; i < record_elems; i++)
-               rec_argv[i] = strdup(record_args[i]);
+       p = rec_argv;
+       for (i = 0; i < common_args_nr; i++)
+               *p++ = strdup(common_args[i]);
+
+       for (i = 0; i < backtrace_args_no; i++)
+               *p++ = strdup(backtrace_args[i]);
+
+       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]);
 
-       for (j = 1; j < (unsigned int)argc; j++, i++)
-               rec_argv[i] = argv[j];
+       for (i = 0; i < old_power_args_nr; i++)
+               *p++ = strdup(old_power_args[i]);
 
-       return cmd_record(i, rec_argv, NULL);
+       for (j = 1; j < (unsigned int)argc; j++)
+               *p++ = argv[j];
+
+       return cmd_record(rec_argc, rec_argv, NULL);
 }
 
 static int
@@ -1090,16 +1250,20 @@ int cmd_timechart(int argc, const char **argv,
                  const char *prefix __maybe_unused)
 {
        const char *output_name = "output.svg";
-       const struct option options[] = {
+       const struct option timechart_options[] = {
        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"),
+       OPT_BOOLEAN('T', "tasks-only", &tasks_only,
+                   "output processes data only"),
        OPT_CALLBACK('p', "process", NULL, "process",
                      "process selector. Pass a pid or process name.",
                       parse_process),
        OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
                    "Look for files with symbols relative to this directory"),
+       OPT_INTEGER('n', "proc-num", &proc_num,
+                   "min. number of tasks to print"),
        OPT_END()
        };
        const char * const timechart_usage[] = {
@@ -1107,15 +1271,39 @@ int cmd_timechart(int argc, const char **argv,
                NULL
        };
 
-       argc = parse_options(argc, argv, options, timechart_usage,
+       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"),
+       OPT_BOOLEAN('g', "callchain", &with_backtrace, "record callchain"),
+       OPT_END()
+       };
+       const char * const record_usage[] = {
+               "perf timechart record [<options>]",
+               NULL
+       };
+       argc = parse_options(argc, argv, timechart_options, timechart_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;
+       }
+
        symbol__init();
 
-       if (argc && !strncmp(argv[0], "rec", 3))
+       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;
+               }
+
                return __cmd_record(argc, argv);
-       else if (argc)
-               usage_with_options(timechart_usage, options);
+       else if (argc)
+               usage_with_options(timechart_usage, timechart_options);
 
        setup_pager();