1 #!/usr/bin/python 2 # @lint-avoid-python-3-compatibility-imports 3 # 4 # cpudist Summarize on- and off-CPU time per task as a histogram. 5 # 6 # USAGE: cpudist [-h] [-O] [-T] [-m] [-P] [-L] [-p PID] [interval] [count] 7 # 8 # This measures the time a task spends on or off the CPU, and shows this time 9 # as a histogram, optionally per-process. 10 # 11 # Copyright 2016 Sasha Goldshtein 12 # Licensed under the Apache License, Version 2.0 (the "License") 13 14 from __future__ import print_function 15 from bcc import BPF 16 from time import sleep, strftime 17 import argparse 18 19 examples = """examples: 20 cpudist # summarize on-CPU time as a histogram 21 cpudist -O # summarize off-CPU time as a histogram 22 cpudist 1 10 # print 1 second summaries, 10 times 23 cpudist -mT 1 # 1s summaries, milliseconds, and timestamps 24 cpudist -P # show each PID separately 25 cpudist -p 185 # trace PID 185 only 26 """ 27 parser = argparse.ArgumentParser( 28 description="Summarize on-CPU time per task as a histogram.", 29 formatter_class=argparse.RawDescriptionHelpFormatter, 30 epilog=examples) 31 parser.add_argument("-O", "--offcpu", action="store_true", 32 help="measure off-CPU time") 33 parser.add_argument("-T", "--timestamp", action="store_true", 34 help="include timestamp on output") 35 parser.add_argument("-m", "--milliseconds", action="store_true", 36 help="millisecond histogram") 37 parser.add_argument("-P", "--pids", action="store_true", 38 help="print a histogram per process ID") 39 parser.add_argument("-L", "--tids", action="store_true", 40 help="print a histogram per thread ID") 41 parser.add_argument("-p", "--pid", 42 help="trace this PID only") 43 parser.add_argument("interval", nargs="?", default=99999999, 44 help="output interval, in seconds") 45 parser.add_argument("count", nargs="?", default=99999999, 46 help="number of outputs") 47 parser.add_argument("--ebpf", action="store_true", 48 help=argparse.SUPPRESS) 49 args = parser.parse_args() 50 countdown = int(args.count) 51 debug = 0 52 53 bpf_text = """#include <uapi/linux/ptrace.h> 54 #include <linux/sched.h> 55 """ 56 57 if not args.offcpu: 58 bpf_text += "#define ONCPU\n" 59 60 bpf_text += """ 61 typedef struct pid_key { 62 u64 id; 63 u64 slot; 64 } pid_key_t; 65 66 67 BPF_HASH(start, u32, u64); 68 STORAGE 69 70 static inline void store_start(u32 tgid, u32 pid, u64 ts) 71 { 72 if (FILTER) 73 return; 74 75 start.update(&pid, &ts); 76 } 77 78 static inline void update_hist(u32 tgid, u32 pid, u64 ts) 79 { 80 if (FILTER) 81 return; 82 83 u64 *tsp = start.lookup(&pid); 84 if (tsp == 0) 85 return; 86 87 if (ts < *tsp) { 88 // Probably a clock issue where the recorded on-CPU event had a 89 // timestamp later than the recorded off-CPU event, or vice versa. 90 return; 91 } 92 u64 delta = ts - *tsp; 93 FACTOR 94 STORE 95 } 96 97 int sched_switch(struct pt_regs *ctx, struct task_struct *prev) 98 { 99 u64 ts = bpf_ktime_get_ns(); 100 u64 pid_tgid = bpf_get_current_pid_tgid(); 101 u32 tgid = pid_tgid >> 32, pid = pid_tgid; 102 103 #ifdef ONCPU 104 if (prev->state == TASK_RUNNING) { 105 #else 106 if (1) { 107 #endif 108 u32 prev_pid = prev->pid; 109 u32 prev_tgid = prev->tgid; 110 #ifdef ONCPU 111 update_hist(prev_tgid, prev_pid, ts); 112 #else 113 store_start(prev_tgid, prev_pid, ts); 114 #endif 115 } 116 117 BAIL: 118 #ifdef ONCPU 119 store_start(tgid, pid, ts); 120 #else 121 update_hist(tgid, pid, ts); 122 #endif 123 124 return 0; 125 } 126 """ 127 128 if args.pid: 129 bpf_text = bpf_text.replace('FILTER', 'tgid != %s' % args.pid) 130 else: 131 bpf_text = bpf_text.replace('FILTER', '0') 132 if args.milliseconds: 133 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000000;') 134 label = "msecs" 135 else: 136 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000;') 137 label = "usecs" 138 if args.pids or args.tids: 139 section = "pid" 140 pid = "tgid" 141 if args.tids: 142 pid = "pid" 143 section = "tid" 144 bpf_text = bpf_text.replace('STORAGE', 145 'BPF_HISTOGRAM(dist, pid_key_t);') 146 bpf_text = bpf_text.replace('STORE', 147 'pid_key_t key = {.id = ' + pid + ', .slot = bpf_log2l(delta)}; ' + 148 'dist.increment(key);') 149 else: 150 section = "" 151 bpf_text = bpf_text.replace('STORAGE', 'BPF_HISTOGRAM(dist);') 152 bpf_text = bpf_text.replace('STORE', 153 'dist.increment(bpf_log2l(delta));') 154 if debug or args.ebpf: 155 print(bpf_text) 156 if args.ebpf: 157 exit() 158 159 b = BPF(text=bpf_text) 160 b.attach_kprobe(event="finish_task_switch", fn_name="sched_switch") 161 162 print("Tracing %s-CPU time... Hit Ctrl-C to end." % 163 ("off" if args.offcpu else "on")) 164 165 exiting = 0 if args.interval else 1 166 dist = b.get_table("dist") 167 while (1): 168 try: 169 sleep(int(args.interval)) 170 except KeyboardInterrupt: 171 exiting = 1 172 173 print() 174 if args.timestamp: 175 print("%-8s\n" % strftime("%H:%M:%S"), end="") 176 177 def pid_to_comm(pid): 178 try: 179 comm = open("/proc/%d/comm" % pid, "r").read() 180 return "%d %s" % (pid, comm) 181 except IOError: 182 return str(pid) 183 184 dist.print_log2_hist(label, section, section_print_fn=pid_to_comm) 185 dist.clear() 186 187 countdown -= 1 188 if exiting or countdown == 0: 189 exit() 190