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