• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env 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
14from __future__ import print_function
15from bcc import BPF
16from time import sleep, strftime
17import argparse
18
19examples = """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"""
27parser = argparse.ArgumentParser(
28    description="Summarize on-CPU time per task as a histogram.",
29    formatter_class=argparse.RawDescriptionHelpFormatter,
30    epilog=examples)
31parser.add_argument("-O", "--offcpu", action="store_true",
32    help="measure off-CPU time")
33parser.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")
47parser.add_argument("--ebpf", action="store_true",
48    help=argparse.SUPPRESS)
49args = parser.parse_args()
50countdown = int(args.count)
51debug = 0
52
53bpf_text = """#include <uapi/linux/ptrace.h>
54#include <linux/sched.h>
55"""
56
57if not args.offcpu:
58    bpf_text += "#define ONCPU\n"
59
60bpf_text += """
61typedef struct pid_key {
62    u64 id;
63    u64 slot;
64} pid_key_t;
65
66
67BPF_HASH(start, u32, u64);
68STORAGE
69
70static inline void store_start(u32 tgid, u32 pid, u64 ts)
71{
72    if (FILTER)
73        return;
74
75    start.update(&pid, &ts);
76}
77
78static 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
97int 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
117BAIL:
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
128if args.pid:
129    bpf_text = bpf_text.replace('FILTER', 'tgid != %s' % args.pid)
130else:
131    bpf_text = bpf_text.replace('FILTER', '0')
132if args.milliseconds:
133    bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000000;')
134    label = "msecs"
135else:
136    bpf_text = bpf_text.replace('FACTOR', 'delta /= 1000;')
137    label = "usecs"
138if 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);')
149else:
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));')
154if debug or args.ebpf:
155    print(bpf_text)
156    if args.ebpf:
157        exit()
158
159b = BPF(text=bpf_text)
160b.attach_kprobe(event="finish_task_switch", fn_name="sched_switch")
161
162print("Tracing %s-CPU time... Hit Ctrl-C to end." %
163      ("off" if args.offcpu else "on"))
164
165exiting = 0 if args.interval else 1
166dist = b.get_table("dist")
167while (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