blob: 8cd434c558599f792953f7d5110be0585cd4f9d0 [file] [log] [blame]
Sasha Goldshtein40975ab2016-06-29 03:57:01 +03001#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
Sasha Goldshtein9972f272016-06-29 01:48:08 -07004# cpudist Summarize on- and off-CPU time per task as a histogram.
Sasha Goldshtein40975ab2016-06-29 03:57:01 +03005#
Sasha Goldshtein9972f272016-06-29 01:48:08 -07006# USAGE: cpudist [-h] [-O] [-T] [-m] [-P] [-L] [-p PID] [interval] [count]
Sasha Goldshtein40975ab2016-06-29 03:57:01 +03007#
Sasha Goldshtein9972f272016-06-29 01:48:08 -07008# This measures the time a task spends on or off the CPU, and shows this time
9# as a histogram, optionally per-process.
Sasha Goldshtein40975ab2016-06-29 03:57:01 +030010#
11# Copyright 2016 Sasha Goldshtein
12# Licensed under the Apache License, Version 2.0 (the "License")
13
14from __future__ import print_function
15from bcc import BPF, Tracepoint
16from time import sleep, strftime
17import argparse
18
19examples = """examples:
20 cpudist # summarize on-CPU time as a histogram
Sasha Goldshtein9972f272016-06-29 01:48:08 -070021 cpudist -O # summarize off-CPU time as a histogram
Sasha Goldshtein40975ab2016-06-29 03:57:01 +030022 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"""
27parser = argparse.ArgumentParser(
28 description="Summarize on-CPU time per task as a histogram.",
29 formatter_class=argparse.RawDescriptionHelpFormatter,
30 epilog=examples)
Sasha Goldshtein9972f272016-06-29 01:48:08 -070031parser.add_argument("-O", "--offcpu", action="store_true",
32 help="measure off-CPU time")
Sasha Goldshtein40975ab2016-06-29 03:57:01 +030033parser.add_argument("-T", "--timestamp", action="store_true",
34 help="include timestamp on output")
35parser.add_argument("-m", "--milliseconds", action="store_true",
36 help="millisecond histogram")
37parser.add_argument("-P", "--pids", action="store_true",
38 help="print a histogram per process ID")
39parser.add_argument("-L", "--tids", action="store_true",
40 help="print a histogram per thread ID")
41parser.add_argument("-p", "--pid",
42 help="trace this PID only")
43parser.add_argument("interval", nargs="?", default=99999999,
44 help="output interval, in seconds")
45parser.add_argument("count", nargs="?", default=99999999,
46 help="number of outputs")
47args = parser.parse_args()
48countdown = int(args.count)
49debug = 0
50
51tp = Tracepoint.enable_tracepoint("sched", "sched_switch")
52bpf_text = "#include <uapi/linux/ptrace.h>\n"
53bpf_text += "#include <linux/sched.h>\n"
54bpf_text += tp.generate_decl()
55bpf_text += tp.generate_entry_probe()
56bpf_text += tp.generate_struct()
57
Sasha Goldshtein9972f272016-06-29 01:48:08 -070058if not args.offcpu:
59 bpf_text += "#define ONCPU\n"
60
Sasha Goldshtein40975ab2016-06-29 03:57:01 +030061bpf_text += """
62typedef struct pid_key {
63 u64 id;
64 u64 slot;
65} pid_key_t;
66
Sasha Goldshtein9972f272016-06-29 01:48:08 -070067
Sasha Goldshtein40975ab2016-06-29 03:57:01 +030068BPF_HASH(start, u32, u64);
69BPF_HASH(tgid_for_pid, u32, u32);
70STORAGE
71
Sasha Goldshtein9972f272016-06-29 01:48:08 -070072#define INVALID_TGID 0xffffffff
73
74static inline u32 get_tgid_if_missing(u32 tgid, u32 pid)
75{
76 if (tgid == INVALID_TGID) {
77 u32 *stored_tgid = tgid_for_pid.lookup(&pid);
78 if (stored_tgid != 0)
79 return *stored_tgid;
80 }
81 return tgid;
82}
83
84static inline void store_start(u32 tgid, u32 pid, u64 ts)
85{
86 tgid = get_tgid_if_missing(tgid, pid);
87
88 if (FILTER)
89 return;
90
91 start.update(&pid, &ts);
92}
93
94static inline void update_hist(u32 tgid, u32 pid, u64 ts)
95{
96 tgid = get_tgid_if_missing(tgid, pid);
97
98 if (FILTER)
99 return;
100
101 u64 *tsp = start.lookup(&pid);
102 if (tsp == 0)
103 return;
104
105 u64 delta = ts - *tsp;
106 FACTOR
107 STORE
108}
109
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300110int sched_switch(struct pt_regs *ctx)
111{
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700112 u64 ts = bpf_ktime_get_ns();
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300113 u64 pid_tgid = bpf_get_current_pid_tgid();
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700114 u32 tgid = pid_tgid >> 32, pid = pid_tgid;
115 // Keep a mapping of tgid for pid because when sched_switch hits,
116 // we only have the tgid information for the *current* pid, but not
117 // for the previous one.
118 tgid_for_pid.update(&pid, &tgid);
119
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300120 u64 *di = __trace_di.lookup(&pid_tgid);
121 if (di == 0)
122 return 0;
123
124 struct sched_switch_trace_entry args = {};
125 bpf_probe_read(&args, sizeof(args), (void *)*di);
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700126 // TODO: Store the comm as well
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300127
128 if (args.prev_state == TASK_RUNNING) {
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700129 u32 prev_pid = args.prev_pid;
130#ifdef ONCPU
131 update_hist(INVALID_TGID, prev_pid, ts);
132#else
133 store_start(INVALID_TGID, prev_pid, ts);
134#endif
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300135 }
136
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700137#ifdef ONCPU
138 store_start(tgid, pid, ts);
139#else
140 update_hist(tgid, pid, ts);
141#endif
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300142
143 return 0;
144}
145"""
146
147if args.pid:
148 bpf_text = bpf_text.replace('FILTER', 'tgid != %s' % args.pid)
149else:
150 bpf_text = bpf_text.replace('FILTER', '0')
151if args.milliseconds:
152 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000000;')
153 label = "msecs"
154else:
155 bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000;')
156 label = "usecs"
157if args.pids or args.tids:
158 section = "pid"
159 pid = "tgid"
160 if args.tids:
161 pid = "pid"
162 section = "tid"
163 bpf_text = bpf_text.replace('STORAGE',
164 'BPF_HISTOGRAM(dist, pid_key_t);')
165 bpf_text = bpf_text.replace('STORE',
166 'pid_key_t key = {.id = ' + pid + ', .slot = bpf_log2l(delta)}; ' +
167 'dist.increment(key);')
168else:
169 section = ""
170 bpf_text = bpf_text.replace('STORAGE', 'BPF_HISTOGRAM(dist);')
171 bpf_text = bpf_text.replace('STORE',
172 'dist.increment(bpf_log2l(delta));')
173if debug:
174 print(bpf_text)
175
176b = BPF(text=bpf_text)
177Tracepoint.attach(b)
178b.attach_kprobe(event="perf_trace_sched_switch", fn_name="sched_switch")
179
Sasha Goldshtein9972f272016-06-29 01:48:08 -0700180print("Tracing %s-CPU time... Hit Ctrl-C to end." %
181 ("off" if args.offcpu else "on"))
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300182
183exiting = 0 if args.interval else 1
184dist = b.get_table("dist")
185while (1):
186 try:
187 sleep(int(args.interval))
188 except KeyboardInterrupt:
189 exiting = 1
190
191 print()
192 if args.timestamp:
193 print("%-8s\n" % strftime("%H:%M:%S"), end="")
194
Sasha Goldshtein4b72f052016-06-29 02:18:06 -0700195 def pid_to_comm(pid):
196 try:
197 comm = open("/proc/%d/comm" % pid, "r").read()
198 return "%d %s" % (pid, comm)
199 except IOError:
200 return str(pid)
201
202 dist.print_log2_hist(label, section, section_print_fn=pid_to_comm)
Sasha Goldshtein40975ab2016-06-29 03:57:01 +0300203 dist.clear()
204
205 countdown -= 1
206 if exiting or countdown == 0:
207 exit()
208