projects/linux·blob master · refs

2164 lines · 50.2 KiB

1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * builtin-timechart.c - make an svg timechart of system activity
4 *
5 * (C) Copyright 2009 Intel Corporation
6 *
7 * Authors:
8 * Arjan van de Ven <[email protected]>
9 */
10
11#include <errno.h>
12#include <inttypes.h>
13
14#include "builtin.h"
15#include "util/color.h"
16#include <linux/list.h>
17#include "util/evlist.h" // for struct evsel_str_handler
18#include "util/evsel.h"
19#include <linux/kernel.h>
20#include <linux/rbtree.h>
21#include <linux/time64.h>
22#include <linux/zalloc.h>
23#include "util/symbol.h"
24#include "util/thread.h"
25#include "util/callchain.h"
26
27#include "util/header.h"
28#include <subcmd/pager.h>
29#include <subcmd/parse-options.h>
30#include "util/parse-events.h"
31#include "util/event.h"
32#include "util/session.h"
33#include "util/svghelper.h"
34#include "util/tool.h"
35#include "util/data.h"
36#include "util/debug.h"
37#include "util/string2.h"
38#include "util/tracepoint.h"
39#include "util/util.h"
40#include <linux/err.h>
41#include <event-parse.h>
42
43#ifdef LACKS_OPEN_MEMSTREAM_PROTOTYPE
44FILE *open_memstream(char **ptr, size_t *sizeloc);
45#endif
46
47#define SUPPORT_OLD_POWER_EVENTS 1
48#define PWR_EVENT_EXIT -1
49
50struct per_pid;
51struct power_event;
52struct wake_event;
53
54struct timechart {
55 struct perf_tool tool;
56 struct per_pid *all_data;
57 struct power_event *power_events;
58 struct wake_event *wake_events;
59 struct perf_session *session;
60 int proc_num;
61 unsigned int numcpus;
62 u64 min_freq, /* Lowest CPU frequency seen */
63 max_freq, /* Highest CPU frequency seen */
64 turbo_frequency,
65 first_time, last_time;
66 bool power_only,
67 tasks_only,
68 with_backtrace,
69 topology;
70 bool force;
71 /* IO related settings */
72 bool io_only,
73 skip_eagain;
74 u64 io_events;
75 u64 min_time,
76 merge_dist;
77};
78
79struct per_pidcomm;
80struct cpu_sample;
81struct io_sample;
82
83/*
84 * Datastructure layout:
85 * We keep an list of "pid"s, matching the kernels notion of a task struct.
86 * Each "pid" entry, has a list of "comm"s.
87 * this is because we want to track different programs different, while
88 * exec will reuse the original pid (by design).
89 * Each comm has a list of samples that will be used to draw
90 * final graph.
91 */
92
93struct per_pid {
94 struct per_pid *next;
95
96 int pid;
97 int ppid;
98
99 u64 start_time;
100 u64 end_time;
101 u64 total_time;
102 u64 total_bytes;
103 int display;
104
105 struct per_pidcomm *all;
106 struct per_pidcomm *current;
107};
108
109
110struct per_pidcomm {
111 struct per_pidcomm *next;
112
113 u64 start_time;
114 u64 end_time;
115 u64 total_time;
116 u64 max_bytes;
117 u64 total_bytes;
118
119 int Y;
120 int display;
121
122 long state;
123 u64 state_since;
124
125 char *comm;
126
127 struct cpu_sample *samples;
128 struct io_sample *io_samples;
129};
130
131struct sample_wrapper {
132 struct sample_wrapper *next;
133
134 u64 timestamp;
135 unsigned char data[];
136};
137
138#define TYPE_NONE 0
139#define TYPE_RUNNING 1
140#define TYPE_WAITING 2
141#define TYPE_BLOCKED 3
142
143struct cpu_sample {
144 struct cpu_sample *next;
145
146 u64 start_time;
147 u64 end_time;
148 int type;
149 int cpu;
150 const char *backtrace;
151};
152
153enum {
154 IOTYPE_READ,
155 IOTYPE_WRITE,
156 IOTYPE_SYNC,
157 IOTYPE_TX,
158 IOTYPE_RX,
159 IOTYPE_POLL,
160};
161
162struct io_sample {
163 struct io_sample *next;
164
165 u64 start_time;
166 u64 end_time;
167 u64 bytes;
168 int type;
169 int fd;
170 int err;
171 int merges;
172};
173
174#define CSTATE 1
175#define PSTATE 2
176
177struct power_event {
178 struct power_event *next;
179 int type;
180 int state;
181 u64 start_time;
182 u64 end_time;
183 int cpu;
184};
185
186struct wake_event {
187 struct wake_event *next;
188 int waker;
189 int wakee;
190 u64 time;
191 const char *backtrace;
192};
193
194struct process_filter {
195 char *name;
196 int pid;
197 struct process_filter *next;
198};
199
200static struct process_filter *process_filter;
201
202
203static struct per_pid *find_create_pid(struct timechart *tchart, int pid)
204{
205 struct per_pid *cursor = tchart->all_data;
206
207 while (cursor) {
208 if (cursor->pid == pid)
209 return cursor;
210 cursor = cursor->next;
211 }
212 cursor = zalloc(sizeof(*cursor));
213 assert(cursor != NULL);
214 cursor->pid = pid;
215 cursor->next = tchart->all_data;
216 tchart->all_data = cursor;
217 return cursor;
218}
219
220static struct per_pidcomm *create_pidcomm(struct per_pid *p)
221{
222 struct per_pidcomm *c;
223
224 c = zalloc(sizeof(*c));
225 if (!c)
226 return NULL;
227 p->current = c;
228 c->next = p->all;
229 p->all = c;
230 return c;
231}
232
233static void pid_set_comm(struct timechart *tchart, int pid, char *comm)
234{
235 struct per_pid *p;
236 struct per_pidcomm *c;
237 p = find_create_pid(tchart, pid);
238 c = p->all;
239 while (c) {
240 if (c->comm && strcmp(c->comm, comm) == 0) {
241 p->current = c;
242 return;
243 }
244 if (!c->comm) {
245 c->comm = strdup(comm);
246 p->current = c;
247 return;
248 }
249 c = c->next;
250 }
251 c = create_pidcomm(p);
252 assert(c != NULL);
253 c->comm = strdup(comm);
254}
255
256static void pid_fork(struct timechart *tchart, int pid, int ppid, u64 timestamp)
257{
258 struct per_pid *p, *pp;
259 p = find_create_pid(tchart, pid);
260 pp = find_create_pid(tchart, ppid);
261 p->ppid = ppid;
262 if (pp->current && pp->current->comm && !p->current)
263 pid_set_comm(tchart, pid, pp->current->comm);
264
265 p->start_time = timestamp;
266 if (p->current && !p->current->start_time) {
267 p->current->start_time = timestamp;
268 p->current->state_since = timestamp;
269 }
270}
271
272static void pid_exit(struct timechart *tchart, int pid, u64 timestamp)
273{
274 struct per_pid *p;
275 p = find_create_pid(tchart, pid);
276 p->end_time = timestamp;
277 if (p->current)
278 p->current->end_time = timestamp;
279}
280
281static void pid_put_sample(struct timechart *tchart, int pid, int type,
282 unsigned int cpu, u64 start, u64 end,
283 const char *backtrace)
284{
285 struct per_pid *p;
286 struct per_pidcomm *c;
287 struct cpu_sample *sample;
288
289 p = find_create_pid(tchart, pid);
290 c = p->current;
291 if (!c) {
292 c = create_pidcomm(p);
293 assert(c != NULL);
294 }
295
296 sample = zalloc(sizeof(*sample));
297 assert(sample != NULL);
298 sample->start_time = start;
299 sample->end_time = end;
300 sample->type = type;
301 sample->next = c->samples;
302 sample->cpu = cpu;
303 sample->backtrace = backtrace;
304 c->samples = sample;
305
306 if (sample->type == TYPE_RUNNING && end > start && start > 0) {
307 c->total_time += (end-start);
308 p->total_time += (end-start);
309 }
310
311 if (c->start_time == 0 || c->start_time > start)
312 c->start_time = start;
313 if (p->start_time == 0 || p->start_time > start)
314 p->start_time = start;
315}
316
317#define MAX_CPUS 4096
318
319static u64 *cpus_cstate_start_times;
320static int *cpus_cstate_state;
321static u64 *cpus_pstate_start_times;
322static u64 *cpus_pstate_state;
323
324static int process_comm_event(const struct perf_tool *tool,
325 union perf_event *event,
326 struct perf_sample *sample __maybe_unused,
327 struct machine *machine __maybe_unused)
328{
329 struct timechart *tchart = container_of(tool, struct timechart, tool);
330 pid_set_comm(tchart, event->comm.tid, event->comm.comm);
331 return 0;
332}
333
334static int process_fork_event(const struct perf_tool *tool,
335 union perf_event *event,
336 struct perf_sample *sample __maybe_unused,
337 struct machine *machine __maybe_unused)
338{
339 struct timechart *tchart = container_of(tool, struct timechart, tool);
340 pid_fork(tchart, event->fork.pid, event->fork.ppid, event->fork.time);
341 return 0;
342}
343
344static int process_exit_event(const struct perf_tool *tool,
345 union perf_event *event,
346 struct perf_sample *sample __maybe_unused,
347 struct machine *machine __maybe_unused)
348{
349 struct timechart *tchart = container_of(tool, struct timechart, tool);
350 pid_exit(tchart, event->fork.pid, event->fork.time);
351 return 0;
352}
353
354#ifdef SUPPORT_OLD_POWER_EVENTS
355static int use_old_power_events;
356#endif
357
358static void c_state_start(int cpu, u64 timestamp, int state)
359{
360 cpus_cstate_start_times[cpu] = timestamp;
361 cpus_cstate_state[cpu] = state;
362}
363
364static void c_state_end(struct timechart *tchart, int cpu, u64 timestamp)
365{
366 struct power_event *pwr = zalloc(sizeof(*pwr));
367
368 if (!pwr)
369 return;
370
371 pwr->state = cpus_cstate_state[cpu];
372 pwr->start_time = cpus_cstate_start_times[cpu];
373 pwr->end_time = timestamp;
374 pwr->cpu = cpu;
375 pwr->type = CSTATE;
376 pwr->next = tchart->power_events;
377
378 tchart->power_events = pwr;
379}
380
381static struct power_event *p_state_end(struct timechart *tchart, int cpu,
382 u64 timestamp)
383{
384 struct power_event *pwr = zalloc(sizeof(*pwr));
385
386 if (!pwr)
387 return NULL;
388
389 pwr->state = cpus_pstate_state[cpu];
390 pwr->start_time = cpus_pstate_start_times[cpu];
391 pwr->end_time = timestamp;
392 pwr->cpu = cpu;
393 pwr->type = PSTATE;
394 pwr->next = tchart->power_events;
395 if (!pwr->start_time)
396 pwr->start_time = tchart->first_time;
397
398 tchart->power_events = pwr;
399 return pwr;
400}
401
402static void p_state_change(struct timechart *tchart, int cpu, u64 timestamp, u64 new_freq)
403{
404 struct power_event *pwr;
405
406 if (new_freq > 8000000) /* detect invalid data */
407 return;
408
409 pwr = p_state_end(tchart, cpu, timestamp);
410 if (!pwr)
411 return;
412
413 cpus_pstate_state[cpu] = new_freq;
414 cpus_pstate_start_times[cpu] = timestamp;
415
416 if ((u64)new_freq > tchart->max_freq)
417 tchart->max_freq = new_freq;
418
419 if (new_freq < tchart->min_freq || tchart->min_freq == 0)
420 tchart->min_freq = new_freq;
421
422 if (new_freq == tchart->max_freq - 1000)
423 tchart->turbo_frequency = tchart->max_freq;
424}
425
426static void sched_wakeup(struct timechart *tchart, int cpu, u64 timestamp,
427 int waker, int wakee, u8 flags, const char *backtrace)
428{
429 struct per_pid *p;
430 struct wake_event *we = zalloc(sizeof(*we));
431
432 if (!we) {
433 free((char *)backtrace);
434 return;
435 }
436
437 we->time = timestamp;
438 we->waker = waker;
439 we->backtrace = backtrace;
440
441 if ((flags & TRACE_FLAG_HARDIRQ) || (flags & TRACE_FLAG_SOFTIRQ))
442 we->waker = -1;
443
444 we->wakee = wakee;
445 we->next = tchart->wake_events;
446 tchart->wake_events = we;
447 p = find_create_pid(tchart, we->wakee);
448
449 if (p && p->current && p->current->state == TYPE_NONE) {
450 p->current->state_since = timestamp;
451 p->current->state = TYPE_WAITING;
452 }
453 if (p && p->current && p->current->state == TYPE_BLOCKED) {
454 pid_put_sample(tchart, p->pid, p->current->state, cpu,
455 p->current->state_since, timestamp, NULL);
456 p->current->state_since = timestamp;
457 p->current->state = TYPE_WAITING;
458 }
459}
460
461static void sched_switch(struct timechart *tchart, int cpu, u64 timestamp,
462 int prev_pid, int next_pid, u64 prev_state,
463 const char *backtrace)
464{
465 struct per_pid *p = NULL, *prev_p;
466 bool backtrace_used = false;
467
468 prev_p = find_create_pid(tchart, prev_pid);
469
470 p = find_create_pid(tchart, next_pid);
471
472 if (prev_p->current && prev_p->current->state != TYPE_NONE) {
473 pid_put_sample(tchart, prev_pid, TYPE_RUNNING, cpu,
474 prev_p->current->state_since, timestamp,
475 backtrace);
476 backtrace_used = true;
477 }
478 if (p && p->current) {
479 if (p->current->state != TYPE_NONE) {
480 if (backtrace && backtrace_used)
481 backtrace = strdup(backtrace);
482
483 pid_put_sample(tchart, next_pid, p->current->state, cpu,
484 p->current->state_since, timestamp,
485 backtrace);
486 backtrace_used = true;
487 }
488
489 p->current->state_since = timestamp;
490 p->current->state = TYPE_RUNNING;
491 }
492
493 if (prev_p->current) {
494 prev_p->current->state = TYPE_NONE;
495 prev_p->current->state_since = timestamp;
496 if (prev_state & 2)
497 prev_p->current->state = TYPE_BLOCKED;
498 if (prev_state == 0)
499 prev_p->current->state = TYPE_WAITING;
500 }
501
502 if (!backtrace_used)
503 free((char *)backtrace);
504}
505
506/*
507 * Returns a malloc'd backtrace string built via open_memstream, or NULL
508 * on error. Caller must free() the returned pointer.
509 */
510static char *cat_backtrace(struct perf_sample *sample,
511 struct machine *machine)
512{
513 struct addr_location al;
514 unsigned int i;
515 char *p = NULL;
516 size_t p_len;
517 u8 cpumode = PERF_RECORD_MISC_USER;
518 struct ip_callchain *chain = sample->callchain;
519 FILE *f = open_memstream(&p, &p_len);
520 bool corrupted = false;
521
522 if (!f) {
523 perror("open_memstream error");
524 return NULL;
525 }
526
527 addr_location__init(&al);
528 if (!chain)
529 goto exit;
530
531 if (machine__resolve(machine, &al, sample) < 0) {
532 pr_err("problem processing SAMPLE (%u) event at offset %#" PRIx64 ", skipping it.\n",
533 PERF_RECORD_SAMPLE, sample->file_offset);
534 goto exit;
535 }
536
537 for (i = 0; i < chain->nr; i++) {
538 u64 ip;
539 struct addr_location tal;
540
541 if (callchain_param.order == ORDER_CALLEE)
542 ip = chain->ips[i];
543 else
544 ip = chain->ips[chain->nr - i - 1];
545
546 if (ip >= PERF_CONTEXT_MAX) {
547 switch (ip) {
548 case PERF_CONTEXT_HV:
549 cpumode = PERF_RECORD_MISC_HYPERVISOR;
550 break;
551 case PERF_CONTEXT_KERNEL:
552 cpumode = PERF_RECORD_MISC_KERNEL;
553 break;
554 case PERF_CONTEXT_USER:
555 cpumode = PERF_RECORD_MISC_USER;
556 break;
557 default:
558 pr_debug("invalid callchain context: %" PRId64 "\n", (s64) ip);
559 corrupted = true;
560 goto exit;
561 }
562 continue;
563 }
564
565 addr_location__init(&tal);
566 tal.filtered = 0;
567 if (thread__find_symbol(al.thread, cpumode, ip, &tal))
568 fprintf(f, "..... %016" PRIx64 " %s\n", ip, tal.sym->name);
569 else
570 fprintf(f, "..... %016" PRIx64 "\n", ip);
571
572 addr_location__exit(&tal);
573 }
574exit:
575 addr_location__exit(&al);
576 /*
577 * fclose() on an open_memstream always sets p to a valid buffer,
578 * even if nothing was written — see open_memstream(3). So p is
579 * never NULL after fclose and we need the flag to discard it.
580 */
581 fclose(f);
582 if (corrupted)
583 zfree(&p);
584
585 return p;
586}
587
588typedef int (*tracepoint_handler)(struct timechart *tchart,
589 struct perf_sample *sample);
590
591static int process_sample_event(const struct perf_tool *tool,
592 union perf_event *event __maybe_unused,
593 struct perf_sample *sample,
594 struct machine *machine __maybe_unused)
595{
596 struct timechart *tchart = container_of(tool, struct timechart, tool);
597 struct evsel *evsel = sample->evsel;
598 int ret = 0;
599
600 if (evsel->core.attr.sample_type & PERF_SAMPLE_TIME) {
601 if (!tchart->first_time || tchart->first_time > sample->time)
602 tchart->first_time = sample->time;
603 if (tchart->last_time < sample->time)
604 tchart->last_time = sample->time;
605 }
606
607 if (evsel->handler != NULL) {
608 tracepoint_handler f = evsel->handler;
609
610 ret = f(tchart, sample);
611 }
612
613 return ret;
614}
615
616static int
617process_sample_cpu_idle(struct timechart *tchart __maybe_unused,
618 struct perf_sample *sample)
619{
620 u32 state = perf_sample__intval(sample, "state");
621 u32 cpu_id = perf_sample__intval(sample, "cpu_id");
622
623 /* perf.data is untrusted input — cpu_id may be corrupted */
624 if (cpu_id >= MAX_CPUS) {
625 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
626 sample->file_offset, cpu_id);
627 return -1;
628 }
629 if (state == (u32)PWR_EVENT_EXIT)
630 c_state_end(tchart, cpu_id, sample->time);
631 else
632 c_state_start(cpu_id, sample->time, state);
633 return 0;
634}
635
636static int
637process_sample_cpu_frequency(struct timechart *tchart,
638 struct perf_sample *sample)
639{
640 u32 state = perf_sample__intval(sample, "state");
641 u32 cpu_id = perf_sample__intval(sample, "cpu_id");
642
643 /* perf.data is untrusted input — cpu_id may be corrupted */
644 if (cpu_id >= MAX_CPUS) {
645 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %u\n",
646 sample->file_offset, cpu_id);
647 return -1;
648 }
649 p_state_change(tchart, cpu_id, sample->time, state);
650 return 0;
651}
652
653static int
654process_sample_sched_wakeup(struct timechart *tchart,
655 struct perf_sample *sample)
656{
657 u8 flags = perf_sample__intval(sample, "common_flags");
658 int waker = perf_sample__intval(sample, "common_pid");
659 int wakee = perf_sample__intval(sample, "pid");
660 char *backtrace;
661
662 /* perf.data is untrusted input — CPU may be absent or corrupted */
663 if (sample->cpu >= MAX_CPUS) {
664 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
665 sample->file_offset, sample->cpu);
666 return -1;
667 }
668
669 backtrace = cat_backtrace(sample, &tchart->session->machines.host);
670 sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, backtrace);
671 return 0;
672}
673
674static int
675process_sample_sched_switch(struct timechart *tchart,
676 struct perf_sample *sample)
677{
678 int prev_pid = perf_sample__intval(sample, "prev_pid");
679 int next_pid = perf_sample__intval(sample, "next_pid");
680 u64 prev_state = perf_sample__intval(sample, "prev_state");
681 char *backtrace;
682
683 /* perf.data is untrusted input — CPU may be absent or corrupted */
684 if (sample->cpu >= MAX_CPUS) {
685 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
686 sample->file_offset, sample->cpu);
687 return -1;
688 }
689
690 backtrace = cat_backtrace(sample, &tchart->session->machines.host);
691 sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid,
692 prev_state, backtrace);
693 return 0;
694}
695
696#ifdef SUPPORT_OLD_POWER_EVENTS
697static int
698process_sample_power_start(struct timechart *tchart __maybe_unused,
699 struct perf_sample *sample)
700{
701 u64 cpu_id = perf_sample__intval(sample, "cpu_id");
702 u64 value = perf_sample__intval(sample, "value");
703
704 /* perf.data is untrusted input — cpu_id may be corrupted */
705 if (cpu_id >= MAX_CPUS) {
706 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
707 sample->file_offset, (unsigned long long)cpu_id);
708 return -1;
709 }
710 c_state_start(cpu_id, sample->time, value);
711 return 0;
712}
713
714static int
715process_sample_power_end(struct timechart *tchart,
716 struct perf_sample *sample)
717{
718 /* perf.data is untrusted input — CPU may be absent or corrupted */
719 if (sample->cpu >= MAX_CPUS) {
720 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu %u\n",
721 sample->file_offset, sample->cpu);
722 return -1;
723 }
724 c_state_end(tchart, sample->cpu, sample->time);
725 return 0;
726}
727
728static int
729process_sample_power_frequency(struct timechart *tchart,
730 struct perf_sample *sample)
731{
732 u64 cpu_id = perf_sample__intval(sample, "cpu_id");
733 u64 value = perf_sample__intval(sample, "value");
734
735 /* perf.data is untrusted input — cpu_id may be corrupted */
736 if (cpu_id >= MAX_CPUS) {
737 pr_debug("at offset %#" PRIx64 ": out-of-bounds cpu_id %llu\n",
738 sample->file_offset, (unsigned long long)cpu_id);
739 return -1;
740 }
741 p_state_change(tchart, cpu_id, sample->time, value);
742 return 0;
743}
744#endif /* SUPPORT_OLD_POWER_EVENTS */
745
746/*
747 * After the last sample we need to wrap up the current C/P state
748 * and close out each CPU for these.
749 */
750static void end_sample_processing(struct timechart *tchart)
751{
752 for (u64 cpu = 0; cpu < tchart->numcpus; cpu++) {
753 struct power_event *pwr;
754
755 /* C state */
756#if 0
757 pwr = zalloc(sizeof(*pwr));
758 if (!pwr)
759 return;
760
761 pwr->state = cpus_cstate_state[cpu];
762 pwr->start_time = cpus_cstate_start_times[cpu];
763 pwr->end_time = tchart->last_time;
764 pwr->cpu = cpu;
765 pwr->type = CSTATE;
766 pwr->next = tchart->power_events;
767
768 tchart->power_events = pwr;
769#endif
770 /* P state */
771
772 pwr = p_state_end(tchart, cpu, tchart->last_time);
773 if (!pwr)
774 return;
775
776 if (!pwr->state)
777 pwr->state = tchart->min_freq;
778 }
779}
780
781static int pid_begin_io_sample(struct timechart *tchart, int pid, int type,
782 u64 start, int fd)
783{
784 struct per_pid *p = find_create_pid(tchart, pid);
785 struct per_pidcomm *c = p->current;
786 struct io_sample *sample;
787 struct io_sample *prev;
788
789 if (!c) {
790 c = create_pidcomm(p);
791 if (!c)
792 return -ENOMEM;
793 }
794
795 prev = c->io_samples;
796
797 if (prev && prev->start_time && !prev->end_time) {
798 pr_warning("Skip invalid start event: "
799 "previous event already started!\n");
800
801 /* remove previous event that has been started,
802 * we are not sure we will ever get an end for it */
803 c->io_samples = prev->next;
804 free(prev);
805 return 0;
806 }
807
808 sample = zalloc(sizeof(*sample));
809 if (!sample)
810 return -ENOMEM;
811 sample->start_time = start;
812 sample->type = type;
813 sample->fd = fd;
814 sample->next = c->io_samples;
815 c->io_samples = sample;
816
817 if (c->start_time == 0 || c->start_time > start)
818 c->start_time = start;
819
820 return 0;
821}
822
823static int pid_end_io_sample(struct timechart *tchart, int pid, int type,
824 u64 end, long ret)
825{
826 struct per_pid *p = find_create_pid(tchart, pid);
827 struct per_pidcomm *c = p->current;
828 struct io_sample *sample, *prev;
829
830 if (!c) {
831 pr_warning("Invalid pidcomm!\n");
832 return -1;
833 }
834
835 sample = c->io_samples;
836
837 if (!sample) /* skip partially captured events */
838 return 0;
839
840 if (sample->end_time) {
841 pr_warning("Skip invalid end event: "
842 "previous event already ended!\n");
843 return 0;
844 }
845
846 if (sample->type != type) {
847 pr_warning("Skip invalid end event: invalid event type!\n");
848 return 0;
849 }
850
851 sample->end_time = end;
852 prev = sample->next;
853
854 /* we want to be able to see small and fast transfers, so make them
855 * at least min_time long, but don't overlap them */
856 if (sample->end_time - sample->start_time < tchart->min_time)
857 sample->end_time = sample->start_time + tchart->min_time;
858 if (prev && sample->start_time < prev->end_time) {
859 if (prev->err) /* try to make errors more visible */
860 sample->start_time = prev->end_time;
861 else
862 prev->end_time = sample->start_time;
863 }
864
865 if (ret < 0) {
866 sample->err = ret;
867 } else if (type == IOTYPE_READ || type == IOTYPE_WRITE ||
868 type == IOTYPE_TX || type == IOTYPE_RX) {
869
870 if ((u64)ret > c->max_bytes)
871 c->max_bytes = ret;
872
873 c->total_bytes += ret;
874 p->total_bytes += ret;
875 sample->bytes = ret;
876 }
877
878 /* merge two requests to make svg smaller and render-friendly */
879 if (prev &&
880 prev->type == sample->type &&
881 prev->err == sample->err &&
882 prev->fd == sample->fd &&
883 prev->end_time + tchart->merge_dist >= sample->start_time) {
884
885 sample->bytes += prev->bytes;
886 sample->merges += prev->merges + 1;
887
888 sample->start_time = prev->start_time;
889 sample->next = prev->next;
890 free(prev);
891
892 if (!sample->err && sample->bytes > c->max_bytes)
893 c->max_bytes = sample->bytes;
894 }
895
896 tchart->io_events++;
897
898 return 0;
899}
900
901static int
902process_enter_read(struct timechart *tchart,
903 struct perf_sample *sample)
904{
905 long fd = perf_sample__intval(sample, "fd");
906 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_READ,
907 sample->time, fd);
908}
909
910static int
911process_exit_read(struct timechart *tchart,
912 struct perf_sample *sample)
913{
914 long ret = perf_sample__intval(sample, "ret");
915 return pid_end_io_sample(tchart, sample->tid, IOTYPE_READ,
916 sample->time, ret);
917}
918
919static int
920process_enter_write(struct timechart *tchart,
921 struct perf_sample *sample)
922{
923 long fd = perf_sample__intval(sample, "fd");
924 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_WRITE,
925 sample->time, fd);
926}
927
928static int
929process_exit_write(struct timechart *tchart,
930 struct perf_sample *sample)
931{
932 long ret = perf_sample__intval(sample, "ret");
933 return pid_end_io_sample(tchart, sample->tid, IOTYPE_WRITE,
934 sample->time, ret);
935}
936
937static int
938process_enter_sync(struct timechart *tchart,
939 struct perf_sample *sample)
940{
941 long fd = perf_sample__intval(sample, "fd");
942 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_SYNC,
943 sample->time, fd);
944}
945
946static int
947process_exit_sync(struct timechart *tchart,
948 struct perf_sample *sample)
949{
950 long ret = perf_sample__intval(sample, "ret");
951 return pid_end_io_sample(tchart, sample->tid, IOTYPE_SYNC,
952 sample->time, ret);
953}
954
955static int
956process_enter_tx(struct timechart *tchart,
957 struct perf_sample *sample)
958{
959 long fd = perf_sample__intval(sample, "fd");
960 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_TX,
961 sample->time, fd);
962}
963
964static int
965process_exit_tx(struct timechart *tchart,
966 struct perf_sample *sample)
967{
968 long ret = perf_sample__intval(sample, "ret");
969 return pid_end_io_sample(tchart, sample->tid, IOTYPE_TX,
970 sample->time, ret);
971}
972
973static int
974process_enter_rx(struct timechart *tchart,
975 struct perf_sample *sample)
976{
977 long fd = perf_sample__intval(sample, "fd");
978 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_RX,
979 sample->time, fd);
980}
981
982static int
983process_exit_rx(struct timechart *tchart,
984 struct perf_sample *sample)
985{
986 long ret = perf_sample__intval(sample, "ret");
987 return pid_end_io_sample(tchart, sample->tid, IOTYPE_RX,
988 sample->time, ret);
989}
990
991static int
992process_enter_poll(struct timechart *tchart,
993 struct perf_sample *sample)
994{
995 long fd = perf_sample__intval(sample, "fd");
996 return pid_begin_io_sample(tchart, sample->tid, IOTYPE_POLL,
997 sample->time, fd);
998}
999
1000static int
1001process_exit_poll(struct timechart *tchart,
1002 struct perf_sample *sample)
1003{
1004 long ret = perf_sample__intval(sample, "ret");
1005 return pid_end_io_sample(tchart, sample->tid, IOTYPE_POLL,
1006 sample->time, ret);
1007}
1008
1009/*
1010 * Sort the pid datastructure
1011 */
1012static void sort_pids(struct timechart *tchart)
1013{
1014 struct per_pid *new_list, *p, *cursor, *prev;
1015 /* sort by ppid first, then by pid, lowest to highest */
1016
1017 new_list = NULL;
1018
1019 while (tchart->all_data) {
1020 p = tchart->all_data;
1021 tchart->all_data = p->next;
1022 p->next = NULL;
1023
1024 if (new_list == NULL) {
1025 new_list = p;
1026 p->next = NULL;
1027 continue;
1028 }
1029 prev = NULL;
1030 cursor = new_list;
1031 while (cursor) {
1032 if (cursor->ppid > p->ppid ||
1033 (cursor->ppid == p->ppid && cursor->pid > p->pid)) {
1034 /* must insert before */
1035 if (prev) {
1036 p->next = prev->next;
1037 prev->next = p;
1038 cursor = NULL;
1039 continue;
1040 } else {
1041 p->next = new_list;
1042 new_list = p;
1043 cursor = NULL;
1044 continue;
1045 }
1046 }
1047
1048 prev = cursor;
1049 cursor = cursor->next;
1050 if (!cursor)
1051 prev->next = p;
1052 }
1053 }
1054 tchart->all_data = new_list;
1055}
1056
1057
1058static void draw_c_p_states(struct timechart *tchart)
1059{
1060 struct power_event *pwr;
1061 pwr = tchart->power_events;
1062
1063 /*
1064 * two pass drawing so that the P state bars are on top of the C state blocks
1065 */
1066 while (pwr) {
1067 if (pwr->type == CSTATE)
1068 svg_cstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
1069 pwr = pwr->next;
1070 }
1071
1072 pwr = tchart->power_events;
1073 while (pwr) {
1074 if (pwr->type == PSTATE) {
1075 if (!pwr->state)
1076 pwr->state = tchart->min_freq;
1077 svg_pstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
1078 }
1079 pwr = pwr->next;
1080 }
1081}
1082
1083static void draw_wakeups(struct timechart *tchart)
1084{
1085 struct wake_event *we;
1086 struct per_pid *p;
1087 struct per_pidcomm *c;
1088
1089 we = tchart->wake_events;
1090 while (we) {
1091 int from = 0, to = 0;
1092 char *task_from = NULL, *task_to = NULL;
1093
1094 /* locate the column of the waker and wakee */
1095 p = tchart->all_data;
1096 while (p) {
1097 if (p->pid == we->waker || p->pid == we->wakee) {
1098 c = p->all;
1099 while (c) {
1100 if (c->Y && c->start_time <= we->time && c->end_time >= we->time) {
1101 if (p->pid == we->waker && !from) {
1102 from = c->Y;
1103 task_from = strdup(c->comm);
1104 }
1105 if (p->pid == we->wakee && !to) {
1106 to = c->Y;
1107 task_to = strdup(c->comm);
1108 }
1109 }
1110 c = c->next;
1111 }
1112 c = p->all;
1113 while (c && (!from || !to)) {
1114 if (c->Y && p->pid == we->waker && !from) {
1115 from = c->Y;
1116 task_from = strdup(c->comm);
1117 }
1118 if (c->Y && p->pid == we->wakee && !to) {
1119 to = c->Y;
1120 task_to = strdup(c->comm);
1121 }
1122 c = c->next;
1123 }
1124 }
1125 p = p->next;
1126 }
1127
1128 if (!task_from) {
1129 task_from = malloc(40);
1130 sprintf(task_from, "[%i]", we->waker);
1131 }
1132 if (!task_to) {
1133 task_to = malloc(40);
1134 sprintf(task_to, "[%i]", we->wakee);
1135 }
1136
1137 if (we->waker == -1)
1138 svg_interrupt(we->time, to, we->backtrace);
1139 else if (from && to && abs(from - to) == 1)
1140 svg_wakeline(we->time, from, to, we->backtrace);
1141 else
1142 svg_partial_wakeline(we->time, from, task_from, to,
1143 task_to, we->backtrace);
1144 we = we->next;
1145
1146 free(task_from);
1147 free(task_to);
1148 }
1149}
1150
1151static void draw_cpu_usage(struct timechart *tchart)
1152{
1153 struct per_pid *p;
1154 struct per_pidcomm *c;
1155 struct cpu_sample *sample;
1156 p = tchart->all_data;
1157 while (p) {
1158 c = p->all;
1159 while (c) {
1160 sample = c->samples;
1161 while (sample) {
1162 if (sample->type == TYPE_RUNNING) {
1163 svg_process(sample->cpu,
1164 sample->start_time,
1165 sample->end_time,
1166 p->pid,
1167 c->comm,
1168 sample->backtrace);
1169 }
1170
1171 sample = sample->next;
1172 }
1173 c = c->next;
1174 }
1175 p = p->next;
1176 }
1177}
1178
1179static void draw_io_bars(struct timechart *tchart)
1180{
1181 const char *suf;
1182 double bytes;
1183 char comm[256];
1184 struct per_pid *p;
1185 struct per_pidcomm *c;
1186 struct io_sample *sample;
1187 int Y = 1;
1188
1189 p = tchart->all_data;
1190 while (p) {
1191 c = p->all;
1192 while (c) {
1193 if (!c->display) {
1194 c->Y = 0;
1195 c = c->next;
1196 continue;
1197 }
1198
1199 svg_box(Y, c->start_time, c->end_time, "process3");
1200 for (sample = c->io_samples; sample; sample = sample->next) {
1201 double h = (double)sample->bytes / c->max_bytes;
1202
1203 if (tchart->skip_eagain &&
1204 sample->err == -EAGAIN)
1205 continue;
1206
1207 if (sample->err)
1208 h = 1;
1209
1210 if (sample->type == IOTYPE_SYNC)
1211 svg_fbox(Y,
1212 sample->start_time,
1213 sample->end_time,
1214 1,
1215 sample->err ? "error" : "sync",
1216 sample->fd,
1217 sample->err,
1218 sample->merges);
1219 else if (sample->type == IOTYPE_POLL)
1220 svg_fbox(Y,
1221 sample->start_time,
1222 sample->end_time,
1223 1,
1224 sample->err ? "error" : "poll",
1225 sample->fd,
1226 sample->err,
1227 sample->merges);
1228 else if (sample->type == IOTYPE_READ)
1229 svg_ubox(Y,
1230 sample->start_time,
1231 sample->end_time,
1232 h,
1233 sample->err ? "error" : "disk",
1234 sample->fd,
1235 sample->err,
1236 sample->merges);
1237 else if (sample->type == IOTYPE_WRITE)
1238 svg_lbox(Y,
1239 sample->start_time,
1240 sample->end_time,
1241 h,
1242 sample->err ? "error" : "disk",
1243 sample->fd,
1244 sample->err,
1245 sample->merges);
1246 else if (sample->type == IOTYPE_RX)
1247 svg_ubox(Y,
1248 sample->start_time,
1249 sample->end_time,
1250 h,
1251 sample->err ? "error" : "net",
1252 sample->fd,
1253 sample->err,
1254 sample->merges);
1255 else if (sample->type == IOTYPE_TX)
1256 svg_lbox(Y,
1257 sample->start_time,
1258 sample->end_time,
1259 h,
1260 sample->err ? "error" : "net",
1261 sample->fd,
1262 sample->err,
1263 sample->merges);
1264 }
1265
1266 suf = "";
1267 bytes = c->total_bytes;
1268 if (bytes > 1024) {
1269 bytes = bytes / 1024;
1270 suf = "K";
1271 }
1272 if (bytes > 1024) {
1273 bytes = bytes / 1024;
1274 suf = "M";
1275 }
1276 if (bytes > 1024) {
1277 bytes = bytes / 1024;
1278 suf = "G";
1279 }
1280
1281
1282 sprintf(comm, "%s:%i (%3.1f %sbytes)", c->comm ?: "", p->pid, bytes, suf);
1283 svg_text(Y, c->start_time, comm);
1284
1285 c->Y = Y;
1286 Y++;
1287 c = c->next;
1288 }
1289 p = p->next;
1290 }
1291}
1292
1293static void draw_process_bars(struct timechart *tchart)
1294{
1295 struct per_pid *p;
1296 struct per_pidcomm *c;
1297 struct cpu_sample *sample;
1298 int Y = 0;
1299
1300 Y = 2 * tchart->numcpus + 2;
1301
1302 p = tchart->all_data;
1303 while (p) {
1304 c = p->all;
1305 while (c) {
1306 if (!c->display) {
1307 c->Y = 0;
1308 c = c->next;
1309 continue;
1310 }
1311
1312 svg_box(Y, c->start_time, c->end_time, "process");
1313 sample = c->samples;
1314 while (sample) {
1315 if (sample->type == TYPE_RUNNING)
1316 svg_running(Y, sample->cpu,
1317 sample->start_time,
1318 sample->end_time,
1319 sample->backtrace);
1320 if (sample->type == TYPE_BLOCKED)
1321 svg_blocked(Y, sample->cpu,
1322 sample->start_time,
1323 sample->end_time,
1324 sample->backtrace);
1325 if (sample->type == TYPE_WAITING)
1326 svg_waiting(Y, sample->cpu,
1327 sample->start_time,
1328 sample->end_time,
1329 sample->backtrace);
1330 sample = sample->next;
1331 }
1332
1333 if (c->comm) {
1334 char comm[256];
1335 if (c->total_time > 5000000000) /* 5 seconds */
1336 sprintf(comm, "%s:%i (%2.2fs)", c->comm, p->pid, c->total_time / (double)NSEC_PER_SEC);
1337 else
1338 sprintf(comm, "%s:%i (%3.1fms)", c->comm, p->pid, c->total_time / (double)NSEC_PER_MSEC);
1339
1340 svg_text(Y, c->start_time, comm);
1341 }
1342 c->Y = Y;
1343 Y++;
1344 c = c->next;
1345 }
1346 p = p->next;
1347 }
1348}
1349
1350static void add_process_filter(const char *string)
1351{
1352 int pid = strtoull(string, NULL, 10);
1353 struct process_filter *filt = malloc(sizeof(*filt));
1354
1355 if (!filt)
1356 return;
1357
1358 filt->name = strdup(string);
1359 filt->pid = pid;
1360 filt->next = process_filter;
1361
1362 process_filter = filt;
1363}
1364
1365static int passes_filter(struct per_pid *p, struct per_pidcomm *c)
1366{
1367 struct process_filter *filt;
1368 if (!process_filter)
1369 return 1;
1370
1371 filt = process_filter;
1372 while (filt) {
1373 if (filt->pid && p->pid == filt->pid)
1374 return 1;
1375 if (strcmp(filt->name, c->comm) == 0)
1376 return 1;
1377 filt = filt->next;
1378 }
1379 return 0;
1380}
1381
1382static int determine_display_tasks_filtered(struct timechart *tchart)
1383{
1384 struct per_pid *p;
1385 struct per_pidcomm *c;
1386 int count = 0;
1387
1388 p = tchart->all_data;
1389 while (p) {
1390 p->display = 0;
1391 if (p->start_time == 1)
1392 p->start_time = tchart->first_time;
1393
1394 /* no exit marker, task kept running to the end */
1395 if (p->end_time == 0)
1396 p->end_time = tchart->last_time;
1397
1398 c = p->all;
1399
1400 while (c) {
1401 c->display = 0;
1402
1403 if (c->start_time == 1)
1404 c->start_time = tchart->first_time;
1405
1406 if (passes_filter(p, c)) {
1407 c->display = 1;
1408 p->display = 1;
1409 count++;
1410 }
1411
1412 if (c->end_time == 0)
1413 c->end_time = tchart->last_time;
1414
1415 c = c->next;
1416 }
1417 p = p->next;
1418 }
1419 return count;
1420}
1421
1422static int determine_display_tasks(struct timechart *tchart, u64 threshold)
1423{
1424 struct per_pid *p;
1425 struct per_pidcomm *c;
1426 int count = 0;
1427
1428 p = tchart->all_data;
1429 while (p) {
1430 p->display = 0;
1431 if (p->start_time == 1)
1432 p->start_time = tchart->first_time;
1433
1434 /* no exit marker, task kept running to the end */
1435 if (p->end_time == 0)
1436 p->end_time = tchart->last_time;
1437 if (p->total_time >= threshold)
1438 p->display = 1;
1439
1440 c = p->all;
1441
1442 while (c) {
1443 c->display = 0;
1444
1445 if (c->start_time == 1)
1446 c->start_time = tchart->first_time;
1447
1448 if (c->total_time >= threshold) {
1449 c->display = 1;
1450 count++;
1451 }
1452
1453 if (c->end_time == 0)
1454 c->end_time = tchart->last_time;
1455
1456 c = c->next;
1457 }
1458 p = p->next;
1459 }
1460 return count;
1461}
1462
1463static int determine_display_io_tasks(struct timechart *timechart, u64 threshold)
1464{
1465 struct per_pid *p;
1466 struct per_pidcomm *c;
1467 int count = 0;
1468
1469 p = timechart->all_data;
1470 while (p) {
1471 /* no exit marker, task kept running to the end */
1472 if (p->end_time == 0)
1473 p->end_time = timechart->last_time;
1474
1475 c = p->all;
1476
1477 while (c) {
1478 c->display = 0;
1479
1480 if (c->total_bytes >= threshold) {
1481 c->display = 1;
1482 count++;
1483 }
1484
1485 if (c->end_time == 0)
1486 c->end_time = timechart->last_time;
1487
1488 c = c->next;
1489 }
1490 p = p->next;
1491 }
1492 return count;
1493}
1494
1495#define BYTES_THRESH (1 * 1024 * 1024)
1496#define TIME_THRESH 10000000
1497
1498static void write_svg_file(struct timechart *tchart, const char *filename)
1499{
1500 u64 i;
1501 int count;
1502 int thresh = tchart->io_events ? BYTES_THRESH : TIME_THRESH;
1503
1504 if (tchart->power_only)
1505 tchart->proc_num = 0;
1506
1507 /* We'd like to show at least proc_num tasks;
1508 * be less picky if we have fewer */
1509 do {
1510 if (process_filter)
1511 count = determine_display_tasks_filtered(tchart);
1512 else if (tchart->io_events)
1513 count = determine_display_io_tasks(tchart, thresh);
1514 else
1515 count = determine_display_tasks(tchart, thresh);
1516 thresh /= 10;
1517 } while (!process_filter && thresh && count < tchart->proc_num);
1518
1519 if (!tchart->proc_num)
1520 count = 0;
1521
1522 if (tchart->io_events) {
1523 open_svg(filename, 0, count, tchart->first_time, tchart->last_time);
1524
1525 svg_time_grid(0.5);
1526 svg_io_legenda();
1527
1528 draw_io_bars(tchart);
1529 } else {
1530 open_svg(filename, tchart->numcpus, count, tchart->first_time, tchart->last_time);
1531
1532 svg_time_grid(0);
1533
1534 svg_legenda();
1535
1536 for (i = 0; i < tchart->numcpus; i++)
1537 svg_cpu_box(i, tchart->max_freq, tchart->turbo_frequency);
1538
1539 draw_cpu_usage(tchart);
1540 if (tchart->proc_num)
1541 draw_process_bars(tchart);
1542 if (!tchart->tasks_only)
1543 draw_c_p_states(tchart);
1544 if (tchart->proc_num)
1545 draw_wakeups(tchart);
1546 }
1547
1548 svg_close();
1549}
1550
1551static void timechart__release(struct timechart *tchart)
1552{
1553 struct per_pid *p = tchart->all_data;
1554 struct power_event *pwr = tchart->power_events;
1555 struct wake_event *we = tchart->wake_events;
1556
1557 while (p) {
1558 struct per_pid *next_pid = p->next;
1559 struct per_pidcomm *c = p->all;
1560
1561 while (c) {
1562 struct per_pidcomm *next_comm = c->next;
1563 struct cpu_sample *cs = c->samples;
1564 struct io_sample *ios = c->io_samples;
1565
1566 while (cs) {
1567 struct cpu_sample *next = cs->next;
1568
1569 zfree(&cs->backtrace);
1570 cs->next = NULL;
1571 free(cs);
1572
1573 cs = next;
1574 }
1575
1576 while (ios) {
1577 struct io_sample *next = ios->next;
1578
1579 ios->next = NULL;
1580 free(ios);
1581
1582 ios = next;
1583 }
1584
1585 zfree(&c->comm);
1586 c->next = NULL;
1587 free(c);
1588
1589 c = next_comm;
1590 }
1591
1592 p->next = NULL;
1593 free(p);
1594
1595 p = next_pid;
1596 }
1597
1598 while (pwr) {
1599 struct power_event *next = pwr->next;
1600
1601 pwr->next = NULL;
1602 free(pwr);
1603
1604 pwr = next;
1605 }
1606
1607 while (we) {
1608 struct wake_event *next = we->next;
1609
1610 zfree(&we->backtrace);
1611 we->next = NULL;
1612 free(we);
1613
1614 we = next;
1615 }
1616}
1617
1618static int process_header(struct perf_file_section *section __maybe_unused,
1619 struct perf_header *ph,
1620 int feat,
1621 int fd __maybe_unused,
1622 void *data)
1623{
1624 struct timechart *tchart = data;
1625
1626 switch (feat) {
1627 case HEADER_NRCPUS:
1628 tchart->numcpus = ph->env.nr_cpus_avail;
1629 if (tchart->numcpus > MAX_CPUS)
1630 tchart->numcpus = MAX_CPUS;
1631 break;
1632
1633 case HEADER_CPU_TOPOLOGY:
1634 if (!tchart->topology)
1635 break;
1636
1637 if (svg_build_topology_map(&ph->env))
1638 fprintf(stderr, "problem building topology\n");
1639 break;
1640
1641 default:
1642 break;
1643 }
1644
1645 return 0;
1646}
1647
1648static int __cmd_timechart(struct timechart *tchart, const char *output_name)
1649{
1650 const struct evsel_str_handler power_tracepoints[] = {
1651 { "power:cpu_idle", process_sample_cpu_idle },
1652 { "power:cpu_frequency", process_sample_cpu_frequency },
1653 { "sched:sched_wakeup", process_sample_sched_wakeup },
1654 { "sched:sched_switch", process_sample_sched_switch },
1655#ifdef SUPPORT_OLD_POWER_EVENTS
1656 { "power:power_start", process_sample_power_start },
1657 { "power:power_end", process_sample_power_end },
1658 { "power:power_frequency", process_sample_power_frequency },
1659#endif
1660
1661 { "syscalls:sys_enter_read", process_enter_read },
1662 { "syscalls:sys_enter_pread64", process_enter_read },
1663 { "syscalls:sys_enter_readv", process_enter_read },
1664 { "syscalls:sys_enter_preadv", process_enter_read },
1665 { "syscalls:sys_enter_write", process_enter_write },
1666 { "syscalls:sys_enter_pwrite64", process_enter_write },
1667 { "syscalls:sys_enter_writev", process_enter_write },
1668 { "syscalls:sys_enter_pwritev", process_enter_write },
1669 { "syscalls:sys_enter_sync", process_enter_sync },
1670 { "syscalls:sys_enter_sync_file_range", process_enter_sync },
1671 { "syscalls:sys_enter_fsync", process_enter_sync },
1672 { "syscalls:sys_enter_msync", process_enter_sync },
1673 { "syscalls:sys_enter_recvfrom", process_enter_rx },
1674 { "syscalls:sys_enter_recvmmsg", process_enter_rx },
1675 { "syscalls:sys_enter_recvmsg", process_enter_rx },
1676 { "syscalls:sys_enter_sendto", process_enter_tx },
1677 { "syscalls:sys_enter_sendmsg", process_enter_tx },
1678 { "syscalls:sys_enter_sendmmsg", process_enter_tx },
1679 { "syscalls:sys_enter_epoll_pwait", process_enter_poll },
1680 { "syscalls:sys_enter_epoll_wait", process_enter_poll },
1681 { "syscalls:sys_enter_poll", process_enter_poll },
1682 { "syscalls:sys_enter_ppoll", process_enter_poll },
1683 { "syscalls:sys_enter_pselect6", process_enter_poll },
1684 { "syscalls:sys_enter_select", process_enter_poll },
1685
1686 { "syscalls:sys_exit_read", process_exit_read },
1687 { "syscalls:sys_exit_pread64", process_exit_read },
1688 { "syscalls:sys_exit_readv", process_exit_read },
1689 { "syscalls:sys_exit_preadv", process_exit_read },
1690 { "syscalls:sys_exit_write", process_exit_write },
1691 { "syscalls:sys_exit_pwrite64", process_exit_write },
1692 { "syscalls:sys_exit_writev", process_exit_write },
1693 { "syscalls:sys_exit_pwritev", process_exit_write },
1694 { "syscalls:sys_exit_sync", process_exit_sync },
1695 { "syscalls:sys_exit_sync_file_range", process_exit_sync },
1696 { "syscalls:sys_exit_fsync", process_exit_sync },
1697 { "syscalls:sys_exit_msync", process_exit_sync },
1698 { "syscalls:sys_exit_recvfrom", process_exit_rx },
1699 { "syscalls:sys_exit_recvmmsg", process_exit_rx },
1700 { "syscalls:sys_exit_recvmsg", process_exit_rx },
1701 { "syscalls:sys_exit_sendto", process_exit_tx },
1702 { "syscalls:sys_exit_sendmsg", process_exit_tx },
1703 { "syscalls:sys_exit_sendmmsg", process_exit_tx },
1704 { "syscalls:sys_exit_epoll_pwait", process_exit_poll },
1705 { "syscalls:sys_exit_epoll_wait", process_exit_poll },
1706 { "syscalls:sys_exit_poll", process_exit_poll },
1707 { "syscalls:sys_exit_ppoll", process_exit_poll },
1708 { "syscalls:sys_exit_pselect6", process_exit_poll },
1709 { "syscalls:sys_exit_select", process_exit_poll },
1710 };
1711 struct perf_data data = {
1712 .path = input_name,
1713 .mode = PERF_DATA_MODE_READ,
1714 .force = tchart->force,
1715 };
1716 struct perf_session *session;
1717 int ret = -EINVAL;
1718
1719 perf_tool__init(&tchart->tool, /*ordered_events=*/true);
1720 tchart->tool.comm = process_comm_event;
1721 tchart->tool.fork = process_fork_event;
1722 tchart->tool.exit = process_exit_event;
1723 tchart->tool.sample = process_sample_event;
1724
1725 session = perf_session__new(&data, &tchart->tool);
1726 if (IS_ERR(session))
1727 return PTR_ERR(session);
1728
1729 tchart->session = session;
1730 symbol__init(perf_session__env(session));
1731
1732 (void)perf_header__process_sections(&session->header,
1733 perf_data__fd(session->data),
1734 tchart,
1735 process_header);
1736
1737 if (!perf_session__has_traces(session, "timechart record"))
1738 goto out_delete;
1739
1740 if (perf_session__set_tracepoints_handlers(session,
1741 power_tracepoints)) {
1742 pr_err("Initializing session tracepoint handlers failed\n");
1743 goto out_delete;
1744 }
1745
1746 ret = perf_session__process_events(session);
1747 if (ret)
1748 goto out_delete;
1749
1750 end_sample_processing(tchart);
1751
1752 sort_pids(tchart);
1753
1754 write_svg_file(tchart, output_name);
1755
1756 pr_info("Written %2.1f seconds of trace to %s.\n",
1757 (tchart->last_time - tchart->first_time) / (double)NSEC_PER_SEC, output_name);
1758out_delete:
1759 perf_session__delete(session);
1760 return ret;
1761}
1762
1763static int timechart__io_record(int argc, const char **argv, const char *output_data)
1764{
1765 unsigned int rec_argc, i;
1766 const char **rec_argv;
1767 const char **p;
1768 char *filter = NULL;
1769
1770 const char * const common_args[] = {
1771 "record", "-a", "-R", "-c", "1", "-o", output_data,
1772 };
1773 unsigned int common_args_nr = ARRAY_SIZE(common_args);
1774
1775 const char * const disk_events[] = {
1776 "syscalls:sys_enter_read",
1777 "syscalls:sys_enter_pread64",
1778 "syscalls:sys_enter_readv",
1779 "syscalls:sys_enter_preadv",
1780 "syscalls:sys_enter_write",
1781 "syscalls:sys_enter_pwrite64",
1782 "syscalls:sys_enter_writev",
1783 "syscalls:sys_enter_pwritev",
1784 "syscalls:sys_enter_sync",
1785 "syscalls:sys_enter_sync_file_range",
1786 "syscalls:sys_enter_fsync",
1787 "syscalls:sys_enter_msync",
1788
1789 "syscalls:sys_exit_read",
1790 "syscalls:sys_exit_pread64",
1791 "syscalls:sys_exit_readv",
1792 "syscalls:sys_exit_preadv",
1793 "syscalls:sys_exit_write",
1794 "syscalls:sys_exit_pwrite64",
1795 "syscalls:sys_exit_writev",
1796 "syscalls:sys_exit_pwritev",
1797 "syscalls:sys_exit_sync",
1798 "syscalls:sys_exit_sync_file_range",
1799 "syscalls:sys_exit_fsync",
1800 "syscalls:sys_exit_msync",
1801 };
1802 unsigned int disk_events_nr = ARRAY_SIZE(disk_events);
1803
1804 const char * const net_events[] = {
1805 "syscalls:sys_enter_recvfrom",
1806 "syscalls:sys_enter_recvmmsg",
1807 "syscalls:sys_enter_recvmsg",
1808 "syscalls:sys_enter_sendto",
1809 "syscalls:sys_enter_sendmsg",
1810 "syscalls:sys_enter_sendmmsg",
1811
1812 "syscalls:sys_exit_recvfrom",
1813 "syscalls:sys_exit_recvmmsg",
1814 "syscalls:sys_exit_recvmsg",
1815 "syscalls:sys_exit_sendto",
1816 "syscalls:sys_exit_sendmsg",
1817 "syscalls:sys_exit_sendmmsg",
1818 };
1819 unsigned int net_events_nr = ARRAY_SIZE(net_events);
1820
1821 const char * const poll_events[] = {
1822 "syscalls:sys_enter_epoll_pwait",
1823 "syscalls:sys_enter_epoll_wait",
1824 "syscalls:sys_enter_poll",
1825 "syscalls:sys_enter_ppoll",
1826 "syscalls:sys_enter_pselect6",
1827 "syscalls:sys_enter_select",
1828
1829 "syscalls:sys_exit_epoll_pwait",
1830 "syscalls:sys_exit_epoll_wait",
1831 "syscalls:sys_exit_poll",
1832 "syscalls:sys_exit_ppoll",
1833 "syscalls:sys_exit_pselect6",
1834 "syscalls:sys_exit_select",
1835 };
1836 unsigned int poll_events_nr = ARRAY_SIZE(poll_events);
1837 int ret;
1838
1839 rec_argc = common_args_nr +
1840 disk_events_nr * 4 +
1841 net_events_nr * 4 +
1842 poll_events_nr * 4 +
1843 argc;
1844 rec_argv = calloc(rec_argc + 1, sizeof(char *));
1845
1846 if (rec_argv == NULL)
1847 return -ENOMEM;
1848
1849 if (asprintf(&filter, "common_pid != %d", getpid()) < 0) {
1850 free(rec_argv);
1851 return -ENOMEM;
1852 }
1853
1854 p = rec_argv;
1855 for (i = 0; i < common_args_nr; i++)
1856 *p++ = common_args[i];
1857
1858 for (i = 0; i < disk_events_nr; i++) {
1859 if (!is_valid_tracepoint(disk_events[i])) {
1860 rec_argc -= 4;
1861 continue;
1862 }
1863
1864 *p++ = "-e";
1865 *p++ = disk_events[i];
1866 *p++ = "--filter";
1867 *p++ = filter;
1868 }
1869 for (i = 0; i < net_events_nr; i++) {
1870 if (!is_valid_tracepoint(net_events[i])) {
1871 rec_argc -= 4;
1872 continue;
1873 }
1874
1875 *p++ = "-e";
1876 *p++ = net_events[i];
1877 *p++ = "--filter";
1878 *p++ = filter;
1879 }
1880 for (i = 0; i < poll_events_nr; i++) {
1881 if (!is_valid_tracepoint(poll_events[i])) {
1882 rec_argc -= 4;
1883 continue;
1884 }
1885
1886 *p++ = "-e";
1887 *p++ = poll_events[i];
1888 *p++ = "--filter";
1889 *p++ = filter;
1890 }
1891
1892 for (i = 0; i < (unsigned int)argc; i++)
1893 *p++ = argv[i];
1894
1895 ret = cmd_record(rec_argc, rec_argv);
1896
1897 free(rec_argv);
1898 free(filter);
1899 return ret;
1900}
1901
1902
1903static int timechart__record(struct timechart *tchart, int argc, const char **argv,
1904 const char *output_data)
1905{
1906 unsigned int rec_argc, i, j;
1907 const char **rec_argv;
1908 const char **p;
1909 unsigned int record_elems;
1910 int ret;
1911
1912 const char * const common_args[] = {
1913 "record", "-a", "-R", "-c", "1", "-o", output_data,
1914 };
1915 unsigned int common_args_nr = ARRAY_SIZE(common_args);
1916
1917 const char * const backtrace_args[] = {
1918 "-g",
1919 };
1920 unsigned int backtrace_args_no = ARRAY_SIZE(backtrace_args);
1921
1922 const char * const power_args[] = {
1923 "-e", "power:cpu_frequency",
1924 "-e", "power:cpu_idle",
1925 };
1926 unsigned int power_args_nr = ARRAY_SIZE(power_args);
1927
1928 const char * const old_power_args[] = {
1929#ifdef SUPPORT_OLD_POWER_EVENTS
1930 "-e", "power:power_start",
1931 "-e", "power:power_end",
1932 "-e", "power:power_frequency",
1933#endif
1934 };
1935 unsigned int old_power_args_nr = ARRAY_SIZE(old_power_args);
1936
1937 const char * const tasks_args[] = {
1938 "-e", "sched:sched_wakeup",
1939 "-e", "sched:sched_switch",
1940 };
1941 unsigned int tasks_args_nr = ARRAY_SIZE(tasks_args);
1942
1943#ifdef SUPPORT_OLD_POWER_EVENTS
1944 if (!is_valid_tracepoint("power:cpu_idle") &&
1945 is_valid_tracepoint("power:power_start")) {
1946 use_old_power_events = 1;
1947 power_args_nr = 0;
1948 } else {
1949 old_power_args_nr = 0;
1950 }
1951#endif
1952
1953 if (tchart->power_only)
1954 tasks_args_nr = 0;
1955
1956 if (tchart->tasks_only) {
1957 power_args_nr = 0;
1958 old_power_args_nr = 0;
1959 }
1960
1961 if (!tchart->with_backtrace)
1962 backtrace_args_no = 0;
1963
1964 record_elems = common_args_nr + tasks_args_nr +
1965 power_args_nr + old_power_args_nr + backtrace_args_no;
1966
1967 rec_argc = record_elems + argc;
1968 rec_argv = calloc(rec_argc + 1, sizeof(char *));
1969
1970 if (rec_argv == NULL)
1971 return -ENOMEM;
1972
1973 p = rec_argv;
1974 for (i = 0; i < common_args_nr; i++)
1975 *p++ = common_args[i];
1976
1977 for (i = 0; i < backtrace_args_no; i++)
1978 *p++ = backtrace_args[i];
1979
1980 for (i = 0; i < tasks_args_nr; i++)
1981 *p++ = tasks_args[i];
1982
1983 for (i = 0; i < power_args_nr; i++)
1984 *p++ = power_args[i];
1985
1986 for (i = 0; i < old_power_args_nr; i++)
1987 *p++ = old_power_args[i];
1988
1989 for (j = 0; j < (unsigned int)argc; j++)
1990 *p++ = argv[j];
1991
1992 ret = cmd_record(rec_argc, rec_argv);
1993
1994 free(rec_argv);
1995 return ret;
1996}
1997
1998static int
1999parse_process(const struct option *opt __maybe_unused, const char *arg,
2000 int __maybe_unused unset)
2001{
2002 if (arg)
2003 add_process_filter(arg);
2004 return 0;
2005}
2006
2007static int
2008parse_highlight(const struct option *opt __maybe_unused, const char *arg,
2009 int __maybe_unused unset)
2010{
2011 unsigned long duration = strtoul(arg, NULL, 0);
2012
2013 if (svg_highlight || svg_highlight_name)
2014 return -1;
2015
2016 if (duration)
2017 svg_highlight = duration;
2018 else
2019 svg_highlight_name = strdup(arg);
2020
2021 return 0;
2022}
2023
2024static int
2025parse_time(const struct option *opt, const char *arg, int __maybe_unused unset)
2026{
2027 char unit = 'n';
2028 u64 *value = opt->value;
2029
2030 if (sscanf(arg, "%" PRIu64 "%cs", value, &unit) > 0) {
2031 switch (unit) {
2032 case 'm':
2033 *value *= NSEC_PER_MSEC;
2034 break;
2035 case 'u':
2036 *value *= NSEC_PER_USEC;
2037 break;
2038 case 'n':
2039 break;
2040 default:
2041 return -1;
2042 }
2043 }
2044
2045 return 0;
2046}
2047
2048int cmd_timechart(int argc, const char **argv)
2049{
2050 struct timechart tchart = {
2051 .proc_num = 15,
2052 .min_time = NSEC_PER_MSEC,
2053 .merge_dist = 1000,
2054 };
2055 const char *output_name = "output.svg";
2056 const char *output_record_data = "perf.data";
2057 const struct option timechart_common_options[] = {
2058 OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
2059 OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only, "output processes data only"),
2060 OPT_END()
2061 };
2062 const struct option timechart_options[] = {
2063 OPT_STRING('i', "input", &input_name, "file", "input file name"),
2064 OPT_STRING('o', "output", &output_name, "file", "output file name"),
2065 OPT_INTEGER('w', "width", &svg_page_width, "page width"),
2066 OPT_CALLBACK(0, "highlight", NULL, "duration or task name",
2067 "highlight tasks. Pass duration in ns or process name.",
2068 parse_highlight),
2069 OPT_CALLBACK('p', "process", NULL, "process",
2070 "process selector. Pass a pid or process name.",
2071 parse_process),
2072 OPT_CALLBACK(0, "symfs", NULL, "directory[,layout]", SYMFS_HELP,
2073 symbol__config_symfs),
2074 OPT_INTEGER('n', "proc-num", &tchart.proc_num,
2075 "min. number of tasks to print"),
2076 OPT_BOOLEAN('t', "topology", &tchart.topology,
2077 "sort CPUs according to topology"),
2078 OPT_BOOLEAN(0, "io-skip-eagain", &tchart.skip_eagain,
2079 "skip EAGAIN errors"),
2080 OPT_CALLBACK(0, "io-min-time", &tchart.min_time, "time",
2081 "all IO faster than min-time will visually appear longer",
2082 parse_time),
2083 OPT_CALLBACK(0, "io-merge-dist", &tchart.merge_dist, "time",
2084 "merge events that are merge-dist us apart",
2085 parse_time),
2086 OPT_BOOLEAN('f', "force", &tchart.force, "don't complain, do it"),
2087 OPT_PARENT(timechart_common_options),
2088 };
2089 const char * const timechart_subcommands[] = { "record", NULL };
2090 const char *timechart_usage[] = {
2091 "perf timechart [<options>] {record}",
2092 NULL
2093 };
2094 const struct option timechart_record_options[] = {
2095 OPT_BOOLEAN('I', "io-only", &tchart.io_only,
2096 "record only IO data"),
2097 OPT_BOOLEAN('g', "callchain", &tchart.with_backtrace, "record callchain"),
2098 OPT_STRING('o', "output", &output_record_data, "file", "output data file name"),
2099 OPT_PARENT(timechart_common_options),
2100 };
2101 const char * const timechart_record_usage[] = {
2102 "perf timechart record [<options>]",
2103 NULL
2104 };
2105 int ret;
2106
2107 cpus_cstate_start_times = calloc(MAX_CPUS, sizeof(*cpus_cstate_start_times));
2108 if (!cpus_cstate_start_times)
2109 return -ENOMEM;
2110 cpus_cstate_state = calloc(MAX_CPUS, sizeof(*cpus_cstate_state));
2111 if (!cpus_cstate_state) {
2112 ret = -ENOMEM;
2113 goto out;
2114 }
2115 cpus_pstate_start_times = calloc(MAX_CPUS, sizeof(*cpus_pstate_start_times));
2116 if (!cpus_pstate_start_times) {
2117 ret = -ENOMEM;
2118 goto out;
2119 }
2120 cpus_pstate_state = calloc(MAX_CPUS, sizeof(*cpus_pstate_state));
2121 if (!cpus_pstate_state) {
2122 ret = -ENOMEM;
2123 goto out;
2124 }
2125
2126 argc = parse_options_subcommand(argc, argv, timechart_options, timechart_subcommands,
2127 timechart_usage, PARSE_OPT_STOP_AT_NON_OPTION);
2128
2129 if (tchart.power_only && tchart.tasks_only) {
2130 pr_err("-P and -T options cannot be used at the same time.\n");
2131 ret = -1;
2132 goto out;
2133 }
2134
2135 if (argc && strlen(argv[0]) > 2 && strstarts("record", argv[0])) {
2136 argc = parse_options(argc, argv, timechart_record_options,
2137 timechart_record_usage,
2138 PARSE_OPT_STOP_AT_NON_OPTION);
2139
2140 if (tchart.power_only && tchart.tasks_only) {
2141 pr_err("-P and -T options cannot be used at the same time.\n");
2142 ret = -1;
2143 goto out;
2144 }
2145
2146 if (tchart.io_only)
2147 ret = timechart__io_record(argc, argv, output_record_data);
2148 else
2149 ret = timechart__record(&tchart, argc, argv, output_record_data);
2150 goto out;
2151 } else if (argc)
2152 usage_with_options(timechart_usage, timechart_options);
2153
2154 setup_pager();
2155
2156 ret = __cmd_timechart(&tchart, output_name);
2157out:
2158 timechart__release(&tchart);
2159 zfree(&cpus_cstate_start_times);
2160 zfree(&cpus_cstate_state);
2161 zfree(&cpus_pstate_start_times);
2162 zfree(&cpus_pstate_state);
2163 return ret;
2164}