1/* 2 * builtin-timechart.c - make an svg timechart of system activity 3 * 4 * (C) Copyright 2009 Intel Corporation 5 * 6 * Authors: 7 * Arjan van de Ven <arjan@linux.intel.com> 8 * 9 * This program is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU General Public License 11 * as published by the Free Software Foundation; version 2 12 * of the License. 13 */ 14 15#include <traceevent/event-parse.h> 16 17#include "builtin.h" 18 19#include "util/util.h" 20 21#include "util/color.h" 22#include <linux/list.h> 23#include "util/cache.h" 24#include "util/evlist.h" 25#include "util/evsel.h" 26#include <linux/rbtree.h> 27#include "util/symbol.h" 28#include "util/callchain.h" 29#include "util/strlist.h" 30 31#include "perf.h" 32#include "util/header.h" 33#include "util/parse-options.h" 34#include "util/parse-events.h" 35#include "util/event.h" 36#include "util/session.h" 37#include "util/svghelper.h" 38#include "util/tool.h" 39 40#define SUPPORT_OLD_POWER_EVENTS 1 41#define PWR_EVENT_EXIT -1 42 43 44static unsigned int numcpus; 45static u64 min_freq; /* Lowest CPU frequency seen */ 46static u64 max_freq; /* Highest CPU frequency seen */ 47static u64 turbo_frequency; 48 49static u64 first_time, last_time; 50 51static bool power_only; 52 53 54struct per_pid; 55struct per_pidcomm; 56 57struct cpu_sample; 58struct power_event; 59struct wake_event; 60 61struct sample_wrapper; 62 63/* 64 * Datastructure layout: 65 * We keep an list of "pid"s, matching the kernels notion of a task struct. 66 * Each "pid" entry, has a list of "comm"s. 67 * this is because we want to track different programs different, while 68 * exec will reuse the original pid (by design). 69 * Each comm has a list of samples that will be used to draw 70 * final graph. 71 */ 72 73struct per_pid { 74 struct per_pid *next; 75 76 int pid; 77 int ppid; 78 79 u64 start_time; 80 u64 end_time; 81 u64 total_time; 82 int display; 83 84 struct per_pidcomm *all; 85 struct per_pidcomm *current; 86}; 87 88 89struct per_pidcomm { 90 struct per_pidcomm *next; 91 92 u64 start_time; 93 u64 end_time; 94 u64 total_time; 95 96 int Y; 97 int display; 98 99 long state; 100 u64 state_since; 101 102 char *comm; 103 104 struct cpu_sample *samples; 105}; 106 107struct sample_wrapper { 108 struct sample_wrapper *next; 109 110 u64 timestamp; 111 unsigned char data[0]; 112}; 113 114#define TYPE_NONE 0 115#define TYPE_RUNNING 1 116#define TYPE_WAITING 2 117#define TYPE_BLOCKED 3 118 119struct cpu_sample { 120 struct cpu_sample *next; 121 122 u64 start_time; 123 u64 end_time; 124 int type; 125 int cpu; 126}; 127 128static struct per_pid *all_data; 129 130#define CSTATE 1 131#define PSTATE 2 132 133struct power_event { 134 struct power_event *next; 135 int type; 136 int state; 137 u64 start_time; 138 u64 end_time; 139 int cpu; 140}; 141 142struct wake_event { 143 struct wake_event *next; 144 int waker; 145 int wakee; 146 u64 time; 147}; 148 149static struct power_event *power_events; 150static struct wake_event *wake_events; 151 152struct process_filter; 153struct process_filter { 154 char *name; 155 int pid; 156 struct process_filter *next; 157}; 158 159static struct process_filter *process_filter; 160 161 162static struct per_pid *find_create_pid(int pid) 163{ 164 struct per_pid *cursor = all_data; 165 166 while (cursor) { 167 if (cursor->pid == pid) 168 return cursor; 169 cursor = cursor->next; 170 } 171 cursor = zalloc(sizeof(*cursor)); 172 assert(cursor != NULL); 173 cursor->pid = pid; 174 cursor->next = all_data; 175 all_data = cursor; 176 return cursor; 177} 178 179static void pid_set_comm(int pid, char *comm) 180{ 181 struct per_pid *p; 182 struct per_pidcomm *c; 183 p = find_create_pid(pid); 184 c = p->all; 185 while (c) { 186 if (c->comm && strcmp(c->comm, comm) == 0) { 187 p->current = c; 188 return; 189 } 190 if (!c->comm) { 191 c->comm = strdup(comm); 192 p->current = c; 193 return; 194 } 195 c = c->next; 196 } 197 c = zalloc(sizeof(*c)); 198 assert(c != NULL); 199 c->comm = strdup(comm); 200 p->current = c; 201 c->next = p->all; 202 p->all = c; 203} 204 205static void pid_fork(int pid, int ppid, u64 timestamp) 206{ 207 struct per_pid *p, *pp; 208 p = find_create_pid(pid); 209 pp = find_create_pid(ppid); 210 p->ppid = ppid; 211 if (pp->current && pp->current->comm && !p->current) 212 pid_set_comm(pid, pp->current->comm); 213 214 p->start_time = timestamp; 215 if (p->current) { 216 p->current->start_time = timestamp; 217 p->current->state_since = timestamp; 218 } 219} 220 221static void pid_exit(int pid, u64 timestamp) 222{ 223 struct per_pid *p; 224 p = find_create_pid(pid); 225 p->end_time = timestamp; 226 if (p->current) 227 p->current->end_time = timestamp; 228} 229 230static void 231pid_put_sample(int pid, int type, unsigned int cpu, u64 start, u64 end) 232{ 233 struct per_pid *p; 234 struct per_pidcomm *c; 235 struct cpu_sample *sample; 236 237 p = find_create_pid(pid); 238 c = p->current; 239 if (!c) { 240 c = zalloc(sizeof(*c)); 241 assert(c != NULL); 242 p->current = c; 243 c->next = p->all; 244 p->all = c; 245 } 246 247 sample = zalloc(sizeof(*sample)); 248 assert(sample != NULL); 249 sample->start_time = start; 250 sample->end_time = end; 251 sample->type = type; 252 sample->next = c->samples; 253 sample->cpu = cpu; 254 c->samples = sample; 255 256 if (sample->type == TYPE_RUNNING && end > start && start > 0) { 257 c->total_time += (end-start); 258 p->total_time += (end-start); 259 } 260 261 if (c->start_time == 0 || c->start_time > start) 262 c->start_time = start; 263 if (p->start_time == 0 || p->start_time > start) 264 p->start_time = start; 265} 266 267#define MAX_CPUS 4096 268 269static u64 cpus_cstate_start_times[MAX_CPUS]; 270static int cpus_cstate_state[MAX_CPUS]; 271static u64 cpus_pstate_start_times[MAX_CPUS]; 272static u64 cpus_pstate_state[MAX_CPUS]; 273 274static int process_comm_event(struct perf_tool *tool __maybe_unused, 275 union perf_event *event, 276 struct perf_sample *sample __maybe_unused, 277 struct machine *machine __maybe_unused) 278{ 279 pid_set_comm(event->comm.tid, event->comm.comm); 280 return 0; 281} 282 283static int process_fork_event(struct perf_tool *tool __maybe_unused, 284 union perf_event *event, 285 struct perf_sample *sample __maybe_unused, 286 struct machine *machine __maybe_unused) 287{ 288 pid_fork(event->fork.pid, event->fork.ppid, event->fork.time); 289 return 0; 290} 291 292static int process_exit_event(struct perf_tool *tool __maybe_unused, 293 union perf_event *event, 294 struct perf_sample *sample __maybe_unused, 295 struct machine *machine __maybe_unused) 296{ 297 pid_exit(event->fork.pid, event->fork.time); 298 return 0; 299} 300 301struct trace_entry { 302 unsigned short type; 303 unsigned char flags; 304 unsigned char preempt_count; 305 int pid; 306 int lock_depth; 307}; 308 309#ifdef SUPPORT_OLD_POWER_EVENTS 310static int use_old_power_events; 311struct power_entry_old { 312 struct trace_entry te; 313 u64 type; 314 u64 value; 315 u64 cpu_id; 316}; 317#endif 318 319struct power_processor_entry { 320 struct trace_entry te; 321 u32 state; 322 u32 cpu_id; 323}; 324 325#define TASK_COMM_LEN 16 326struct wakeup_entry { 327 struct trace_entry te; 328 char comm[TASK_COMM_LEN]; 329 int pid; 330 int prio; 331 int success; 332}; 333 334struct sched_switch { 335 struct trace_entry te; 336 char prev_comm[TASK_COMM_LEN]; 337 int prev_pid; 338 int prev_prio; 339 long prev_state; /* Arjan weeps. */ 340 char next_comm[TASK_COMM_LEN]; 341 int next_pid; 342 int next_prio; 343}; 344 345static void c_state_start(int cpu, u64 timestamp, int state) 346{ 347 cpus_cstate_start_times[cpu] = timestamp; 348 cpus_cstate_state[cpu] = state; 349} 350 351static void c_state_end(int cpu, u64 timestamp) 352{ 353 struct power_event *pwr = zalloc(sizeof(*pwr)); 354 355 if (!pwr) 356 return; 357 358 pwr->state = cpus_cstate_state[cpu]; 359 pwr->start_time = cpus_cstate_start_times[cpu]; 360 pwr->end_time = timestamp; 361 pwr->cpu = cpu; 362 pwr->type = CSTATE; 363 pwr->next = power_events; 364 365 power_events = pwr; 366} 367 368static void p_state_change(int cpu, u64 timestamp, u64 new_freq) 369{ 370 struct power_event *pwr; 371 372 if (new_freq > 8000000) /* detect invalid data */ 373 return; 374 375 pwr = zalloc(sizeof(*pwr)); 376 if (!pwr) 377 return; 378 379 pwr->state = cpus_pstate_state[cpu]; 380 pwr->start_time = cpus_pstate_start_times[cpu]; 381 pwr->end_time = timestamp; 382 pwr->cpu = cpu; 383 pwr->type = PSTATE; 384 pwr->next = power_events; 385 386 if (!pwr->start_time) 387 pwr->start_time = first_time; 388 389 power_events = pwr; 390 391 cpus_pstate_state[cpu] = new_freq; 392 cpus_pstate_start_times[cpu] = timestamp; 393 394 if ((u64)new_freq > max_freq) 395 max_freq = new_freq; 396 397 if (new_freq < min_freq || min_freq == 0) 398 min_freq = new_freq; 399 400 if (new_freq == max_freq - 1000) 401 turbo_frequency = max_freq; 402} 403 404static void 405sched_wakeup(int cpu, u64 timestamp, int pid, struct trace_entry *te) 406{ 407 struct per_pid *p; 408 struct wakeup_entry *wake = (void *)te; 409 struct wake_event *we = zalloc(sizeof(*we)); 410 411 if (!we) 412 return; 413 414 we->time = timestamp; 415 we->waker = pid; 416 417 if ((te->flags & TRACE_FLAG_HARDIRQ) || (te->flags & TRACE_FLAG_SOFTIRQ)) 418 we->waker = -1; 419 420 we->wakee = wake->pid; 421 we->next = wake_events; 422 wake_events = we; 423 p = find_create_pid(we->wakee); 424 425 if (p && p->current && p->current->state == TYPE_NONE) { 426 p->current->state_since = timestamp; 427 p->current->state = TYPE_WAITING; 428 } 429 if (p && p->current && p->current->state == TYPE_BLOCKED) { 430 pid_put_sample(p->pid, p->current->state, cpu, p->current->state_since, timestamp); 431 p->current->state_since = timestamp; 432 p->current->state = TYPE_WAITING; 433 } 434} 435 436static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te) 437{ 438 struct per_pid *p = NULL, *prev_p; 439 struct sched_switch *sw = (void *)te; 440 441 442 prev_p = find_create_pid(sw->prev_pid); 443 444 p = find_create_pid(sw->next_pid); 445 446 if (prev_p->current && prev_p->current->state != TYPE_NONE) 447 pid_put_sample(sw->prev_pid, TYPE_RUNNING, cpu, prev_p->current->state_since, timestamp); 448 if (p && p->current) { 449 if (p->current->state != TYPE_NONE) 450 pid_put_sample(sw->next_pid, p->current->state, cpu, p->current->state_since, timestamp); 451 452 p->current->state_since = timestamp; 453 p->current->state = TYPE_RUNNING; 454 } 455 456 if (prev_p->current) { 457 prev_p->current->state = TYPE_NONE; 458 prev_p->current->state_since = timestamp; 459 if (sw->prev_state & 2) 460 prev_p->current->state = TYPE_BLOCKED; 461 if (sw->prev_state == 0) 462 prev_p->current->state = TYPE_WAITING; 463 } 464} 465 466typedef int (*tracepoint_handler)(struct perf_evsel *evsel, 467 struct perf_sample *sample); 468 469static int process_sample_event(struct perf_tool *tool __maybe_unused, 470 union perf_event *event __maybe_unused, 471 struct perf_sample *sample, 472 struct perf_evsel *evsel, 473 struct machine *machine __maybe_unused) 474{ 475 if (evsel->attr.sample_type & PERF_SAMPLE_TIME) { 476 if (!first_time || first_time > sample->time) 477 first_time = sample->time; 478 if (last_time < sample->time) 479 last_time = sample->time; 480 } 481 482 if (sample->cpu > numcpus) 483 numcpus = sample->cpu; 484 485 if (evsel->handler.func != NULL) { 486 tracepoint_handler f = evsel->handler.func; 487 return f(evsel, sample); 488 } 489 490 return 0; 491} 492 493static int 494process_sample_cpu_idle(struct perf_evsel *evsel __maybe_unused, 495 struct perf_sample *sample) 496{ 497 struct power_processor_entry *ppe = sample->raw_data; 498 499 if (ppe->state == (u32) PWR_EVENT_EXIT) 500 c_state_end(ppe->cpu_id, sample->time); 501 else 502 c_state_start(ppe->cpu_id, sample->time, ppe->state); 503 return 0; 504} 505 506static int 507process_sample_cpu_frequency(struct perf_evsel *evsel __maybe_unused, 508 struct perf_sample *sample) 509{ 510 struct power_processor_entry *ppe = sample->raw_data; 511 512 p_state_change(ppe->cpu_id, sample->time, ppe->state); 513 return 0; 514} 515 516static int 517process_sample_sched_wakeup(struct perf_evsel *evsel __maybe_unused, 518 struct perf_sample *sample) 519{ 520 struct trace_entry *te = sample->raw_data; 521 522 sched_wakeup(sample->cpu, sample->time, sample->pid, te); 523 return 0; 524} 525 526static int 527process_sample_sched_switch(struct perf_evsel *evsel __maybe_unused, 528 struct perf_sample *sample) 529{ 530 struct trace_entry *te = sample->raw_data; 531 532 sched_switch(sample->cpu, sample->time, te); 533 return 0; 534} 535 536#ifdef SUPPORT_OLD_POWER_EVENTS 537static int 538process_sample_power_start(struct perf_evsel *evsel __maybe_unused, 539 struct perf_sample *sample) 540{ 541 struct power_entry_old *peo = sample->raw_data; 542 543 c_state_start(peo->cpu_id, sample->time, peo->value); 544 return 0; 545} 546 547static int 548process_sample_power_end(struct perf_evsel *evsel __maybe_unused, 549 struct perf_sample *sample) 550{ 551 c_state_end(sample->cpu, sample->time); 552 return 0; 553} 554 555static int 556process_sample_power_frequency(struct perf_evsel *evsel __maybe_unused, 557 struct perf_sample *sample) 558{ 559 struct power_entry_old *peo = sample->raw_data; 560 561 p_state_change(peo->cpu_id, sample->time, peo->value); 562 return 0; 563} 564#endif /* SUPPORT_OLD_POWER_EVENTS */ 565 566/* 567 * After the last sample we need to wrap up the current C/P state 568 * and close out each CPU for these. 569 */ 570static void end_sample_processing(void) 571{ 572 u64 cpu; 573 struct power_event *pwr; 574 575 for (cpu = 0; cpu <= numcpus; cpu++) { 576 /* C state */ 577#if 0 578 pwr = zalloc(sizeof(*pwr)); 579 if (!pwr) 580 return; 581 582 pwr->state = cpus_cstate_state[cpu]; 583 pwr->start_time = cpus_cstate_start_times[cpu]; 584 pwr->end_time = last_time; 585 pwr->cpu = cpu; 586 pwr->type = CSTATE; 587 pwr->next = power_events; 588 589 power_events = pwr; 590#endif 591 /* P state */ 592 593 pwr = zalloc(sizeof(*pwr)); 594 if (!pwr) 595 return; 596 597 pwr->state = cpus_pstate_state[cpu]; 598 pwr->start_time = cpus_pstate_start_times[cpu]; 599 pwr->end_time = last_time; 600 pwr->cpu = cpu; 601 pwr->type = PSTATE; 602 pwr->next = power_events; 603 604 if (!pwr->start_time) 605 pwr->start_time = first_time; 606 if (!pwr->state) 607 pwr->state = min_freq; 608 power_events = pwr; 609 } 610} 611 612/* 613 * Sort the pid datastructure 614 */ 615static void sort_pids(void) 616{ 617 struct per_pid *new_list, *p, *cursor, *prev; 618 /* sort by ppid first, then by pid, lowest to highest */ 619 620 new_list = NULL; 621 622 while (all_data) { 623 p = all_data; 624 all_data = p->next; 625 p->next = NULL; 626 627 if (new_list == NULL) { 628 new_list = p; 629 p->next = NULL; 630 continue; 631 } 632 prev = NULL; 633 cursor = new_list; 634 while (cursor) { 635 if (cursor->ppid > p->ppid || 636 (cursor->ppid == p->ppid && cursor->pid > p->pid)) { 637 /* must insert before */ 638 if (prev) { 639 p->next = prev->next; 640 prev->next = p; 641 cursor = NULL; 642 continue; 643 } else { 644 p->next = new_list; 645 new_list = p; 646 cursor = NULL; 647 continue; 648 } 649 } 650 651 prev = cursor; 652 cursor = cursor->next; 653 if (!cursor) 654 prev->next = p; 655 } 656 } 657 all_data = new_list; 658} 659 660 661static void draw_c_p_states(void) 662{ 663 struct power_event *pwr; 664 pwr = power_events; 665 666 /* 667 * two pass drawing so that the P state bars are on top of the C state blocks 668 */ 669 while (pwr) { 670 if (pwr->type == CSTATE) 671 svg_cstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state); 672 pwr = pwr->next; 673 } 674 675 pwr = power_events; 676 while (pwr) { 677 if (pwr->type == PSTATE) { 678 if (!pwr->state) 679 pwr->state = min_freq; 680 svg_pstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state); 681 } 682 pwr = pwr->next; 683 } 684} 685 686static void draw_wakeups(void) 687{ 688 struct wake_event *we; 689 struct per_pid *p; 690 struct per_pidcomm *c; 691 692 we = wake_events; 693 while (we) { 694 int from = 0, to = 0; 695 char *task_from = NULL, *task_to = NULL; 696 697 /* locate the column of the waker and wakee */ 698 p = all_data; 699 while (p) { 700 if (p->pid == we->waker || p->pid == we->wakee) { 701 c = p->all; 702 while (c) { 703 if (c->Y && c->start_time <= we->time && c->end_time >= we->time) { 704 if (p->pid == we->waker && !from) { 705 from = c->Y; 706 task_from = strdup(c->comm); 707 } 708 if (p->pid == we->wakee && !to) { 709 to = c->Y; 710 task_to = strdup(c->comm); 711 } 712 } 713 c = c->next; 714 } 715 c = p->all; 716 while (c) { 717 if (p->pid == we->waker && !from) { 718 from = c->Y; 719 task_from = strdup(c->comm); 720 } 721 if (p->pid == we->wakee && !to) { 722 to = c->Y; 723 task_to = strdup(c->comm); 724 } 725 c = c->next; 726 } 727 } 728 p = p->next; 729 } 730 731 if (!task_from) { 732 task_from = malloc(40); 733 sprintf(task_from, "[%i]", we->waker); 734 } 735 if (!task_to) { 736 task_to = malloc(40); 737 sprintf(task_to, "[%i]", we->wakee); 738 } 739 740 if (we->waker == -1) 741 svg_interrupt(we->time, to); 742 else if (from && to && abs(from - to) == 1) 743 svg_wakeline(we->time, from, to); 744 else 745 svg_partial_wakeline(we->time, from, task_from, to, task_to); 746 we = we->next; 747 748 free(task_from); 749 free(task_to); 750 } 751} 752 753static void draw_cpu_usage(void) 754{ 755 struct per_pid *p; 756 struct per_pidcomm *c; 757 struct cpu_sample *sample; 758 p = all_data; 759 while (p) { 760 c = p->all; 761 while (c) { 762 sample = c->samples; 763 while (sample) { 764 if (sample->type == TYPE_RUNNING) 765 svg_process(sample->cpu, sample->start_time, sample->end_time, "sample", c->comm); 766 767 sample = sample->next; 768 } 769 c = c->next; 770 } 771 p = p->next; 772 } 773} 774 775static void draw_process_bars(void) 776{ 777 struct per_pid *p; 778 struct per_pidcomm *c; 779 struct cpu_sample *sample; 780 int Y = 0; 781 782 Y = 2 * numcpus + 2; 783 784 p = all_data; 785 while (p) { 786 c = p->all; 787 while (c) { 788 if (!c->display) { 789 c->Y = 0; 790 c = c->next; 791 continue; 792 } 793 794 svg_box(Y, c->start_time, c->end_time, "process"); 795 sample = c->samples; 796 while (sample) { 797 if (sample->type == TYPE_RUNNING) 798 svg_sample(Y, sample->cpu, sample->start_time, sample->end_time); 799 if (sample->type == TYPE_BLOCKED) 800 svg_box(Y, sample->start_time, sample->end_time, "blocked"); 801 if (sample->type == TYPE_WAITING) 802 svg_waiting(Y, sample->start_time, sample->end_time); 803 sample = sample->next; 804 } 805 806 if (c->comm) { 807 char comm[256]; 808 if (c->total_time > 5000000000) /* 5 seconds */ 809 sprintf(comm, "%s:%i (%2.2fs)", c->comm, p->pid, c->total_time / 1000000000.0); 810 else 811 sprintf(comm, "%s:%i (%3.1fms)", c->comm, p->pid, c->total_time / 1000000.0); 812 813 svg_text(Y, c->start_time, comm); 814 } 815 c->Y = Y; 816 Y++; 817 c = c->next; 818 } 819 p = p->next; 820 } 821} 822 823static void add_process_filter(const char *string) 824{ 825 int pid = strtoull(string, NULL, 10); 826 struct process_filter *filt = malloc(sizeof(*filt)); 827 828 if (!filt) 829 return; 830 831 filt->name = strdup(string); 832 filt->pid = pid; 833 filt->next = process_filter; 834 835 process_filter = filt; 836} 837 838static int passes_filter(struct per_pid *p, struct per_pidcomm *c) 839{ 840 struct process_filter *filt; 841 if (!process_filter) 842 return 1; 843 844 filt = process_filter; 845 while (filt) { 846 if (filt->pid && p->pid == filt->pid) 847 return 1; 848 if (strcmp(filt->name, c->comm) == 0) 849 return 1; 850 filt = filt->next; 851 } 852 return 0; 853} 854 855static int determine_display_tasks_filtered(void) 856{ 857 struct per_pid *p; 858 struct per_pidcomm *c; 859 int count = 0; 860 861 p = all_data; 862 while (p) { 863 p->display = 0; 864 if (p->start_time == 1) 865 p->start_time = first_time; 866 867 /* no exit marker, task kept running to the end */ 868 if (p->end_time == 0) 869 p->end_time = last_time; 870 871 c = p->all; 872 873 while (c) { 874 c->display = 0; 875 876 if (c->start_time == 1) 877 c->start_time = first_time; 878 879 if (passes_filter(p, c)) { 880 c->display = 1; 881 p->display = 1; 882 count++; 883 } 884 885 if (c->end_time == 0) 886 c->end_time = last_time; 887 888 c = c->next; 889 } 890 p = p->next; 891 } 892 return count; 893} 894 895static int determine_display_tasks(u64 threshold) 896{ 897 struct per_pid *p; 898 struct per_pidcomm *c; 899 int count = 0; 900 901 if (process_filter) 902 return determine_display_tasks_filtered(); 903 904 p = all_data; 905 while (p) { 906 p->display = 0; 907 if (p->start_time == 1) 908 p->start_time = first_time; 909 910 /* no exit marker, task kept running to the end */ 911 if (p->end_time == 0) 912 p->end_time = last_time; 913 if (p->total_time >= threshold && !power_only) 914 p->display = 1; 915 916 c = p->all; 917 918 while (c) { 919 c->display = 0; 920 921 if (c->start_time == 1) 922 c->start_time = first_time; 923 924 if (c->total_time >= threshold && !power_only) { 925 c->display = 1; 926 count++; 927 } 928 929 if (c->end_time == 0) 930 c->end_time = last_time; 931 932 c = c->next; 933 } 934 p = p->next; 935 } 936 return count; 937} 938 939 940 941#define TIME_THRESH 10000000 942 943static void write_svg_file(const char *filename) 944{ 945 u64 i; 946 int count; 947 948 numcpus++; 949 950 951 count = determine_display_tasks(TIME_THRESH); 952 953 /* We'd like to show at least 15 tasks; be less picky if we have fewer */ 954 if (count < 15) 955 count = determine_display_tasks(TIME_THRESH / 10); 956 957 open_svg(filename, numcpus, count, first_time, last_time); 958 959 svg_time_grid(); 960 svg_legenda(); 961 962 for (i = 0; i < numcpus; i++) 963 svg_cpu_box(i, max_freq, turbo_frequency); 964 965 draw_cpu_usage(); 966 draw_process_bars(); 967 draw_c_p_states(); 968 draw_wakeups(); 969 970 svg_close(); 971} 972 973static int __cmd_timechart(const char *output_name) 974{ 975 struct perf_tool perf_timechart = { 976 .comm = process_comm_event, 977 .fork = process_fork_event, 978 .exit = process_exit_event, 979 .sample = process_sample_event, 980 .ordered_samples = true, 981 }; 982 const struct perf_evsel_str_handler power_tracepoints[] = { 983 { "power:cpu_idle", process_sample_cpu_idle }, 984 { "power:cpu_frequency", process_sample_cpu_frequency }, 985 { "sched:sched_wakeup", process_sample_sched_wakeup }, 986 { "sched:sched_switch", process_sample_sched_switch }, 987#ifdef SUPPORT_OLD_POWER_EVENTS 988 { "power:power_start", process_sample_power_start }, 989 { "power:power_end", process_sample_power_end }, 990 { "power:power_frequency", process_sample_power_frequency }, 991#endif 992 }; 993 struct perf_session *session = perf_session__new(input_name, O_RDONLY, 994 0, false, &perf_timechart); 995 int ret = -EINVAL; 996 997 if (session == NULL) 998 return -ENOMEM; 999 1000 if (!perf_session__has_traces(session, "timechart record")) 1001 goto out_delete; 1002 1003 if (perf_session__set_tracepoints_handlers(session, 1004 power_tracepoints)) { 1005 pr_err("Initializing session tracepoint handlers failed\n"); 1006 goto out_delete; 1007 } 1008 1009 ret = perf_session__process_events(session, &perf_timechart); 1010 if (ret) 1011 goto out_delete; 1012 1013 end_sample_processing(); 1014 1015 sort_pids(); 1016 1017 write_svg_file(output_name); 1018 1019 pr_info("Written %2.1f seconds of trace to %s.\n", 1020 (last_time - first_time) / 1000000000.0, output_name); 1021out_delete: 1022 perf_session__delete(session); 1023 return ret; 1024} 1025 1026static int __cmd_record(int argc, const char **argv) 1027{ 1028#ifdef SUPPORT_OLD_POWER_EVENTS 1029 const char * const record_old_args[] = { 1030 "record", "-a", "-R", "-c", "1", 1031 "-e", "power:power_start", 1032 "-e", "power:power_end", 1033 "-e", "power:power_frequency", 1034 "-e", "sched:sched_wakeup", 1035 "-e", "sched:sched_switch", 1036 }; 1037#endif 1038 const char * const record_new_args[] = { 1039 "record", "-a", "-R", "-c", "1", 1040 "-e", "power:cpu_frequency", 1041 "-e", "power:cpu_idle", 1042 "-e", "sched:sched_wakeup", 1043 "-e", "sched:sched_switch", 1044 }; 1045 unsigned int rec_argc, i, j; 1046 const char **rec_argv; 1047 const char * const *record_args = record_new_args; 1048 unsigned int record_elems = ARRAY_SIZE(record_new_args); 1049 1050#ifdef SUPPORT_OLD_POWER_EVENTS 1051 if (!is_valid_tracepoint("power:cpu_idle") && 1052 is_valid_tracepoint("power:power_start")) { 1053 use_old_power_events = 1; 1054 record_args = record_old_args; 1055 record_elems = ARRAY_SIZE(record_old_args); 1056 } 1057#endif 1058 1059 rec_argc = record_elems + argc - 1; 1060 rec_argv = calloc(rec_argc + 1, sizeof(char *)); 1061 1062 if (rec_argv == NULL) 1063 return -ENOMEM; 1064 1065 for (i = 0; i < record_elems; i++) 1066 rec_argv[i] = strdup(record_args[i]); 1067 1068 for (j = 1; j < (unsigned int)argc; j++, i++) 1069 rec_argv[i] = argv[j]; 1070 1071 return cmd_record(i, rec_argv, NULL); 1072} 1073 1074static int 1075parse_process(const struct option *opt __maybe_unused, const char *arg, 1076 int __maybe_unused unset) 1077{ 1078 if (arg) 1079 add_process_filter(arg); 1080 return 0; 1081} 1082 1083int cmd_timechart(int argc, const char **argv, 1084 const char *prefix __maybe_unused) 1085{ 1086 const char *output_name = "output.svg"; 1087 const struct option options[] = { 1088 OPT_STRING('i', "input", &input_name, "file", "input file name"), 1089 OPT_STRING('o', "output", &output_name, "file", "output file name"), 1090 OPT_INTEGER('w', "width", &svg_page_width, "page width"), 1091 OPT_BOOLEAN('P', "power-only", &power_only, "output power data only"), 1092 OPT_CALLBACK('p', "process", NULL, "process", 1093 "process selector. Pass a pid or process name.", 1094 parse_process), 1095 OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", 1096 "Look for files with symbols relative to this directory"), 1097 OPT_END() 1098 }; 1099 const char * const timechart_usage[] = { 1100 "perf timechart [<options>] {record}", 1101 NULL 1102 }; 1103 1104 argc = parse_options(argc, argv, options, timechart_usage, 1105 PARSE_OPT_STOP_AT_NON_OPTION); 1106 1107 symbol__init(); 1108 1109 if (argc && !strncmp(argv[0], "rec", 3)) 1110 return __cmd_record(argc, argv); 1111 else if (argc) 1112 usage_with_options(timechart_usage, options); 1113 1114 setup_pager(); 1115 1116 return __cmd_timechart(output_name); 1117} 1118