• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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