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