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