cpudist: Support off-cpu time reports
Add -O switch, which directs cpudist to collect off-CPU time
statistics. Also restructure the code slightly and added examples
as appropriate.
diff --git a/tools/cpudist.py b/tools/cpudist.py
index 560ad92..2df8172 100755
--- a/tools/cpudist.py
+++ b/tools/cpudist.py
@@ -1,12 +1,12 @@
#!/usr/bin/python
# @lint-avoid-python-3-compatibility-imports
#
-# cpudist Summarize on-CPU time per task as a histogram.
+# cpudist Summarize on- and off-CPU time per task as a histogram.
#
-# USAGE: cpudist [-h] [-T] [-m] [-P] [-L] [-p PID] [interval] [count]
+# USAGE: cpudist [-h] [-O] [-T] [-m] [-P] [-L] [-p PID] [interval] [count]
#
-# This measures the time a task spends on the CPU, and shows this time as a
-# histogram, optionally per-process.
+# This measures the time a task spends on or off the CPU, and shows this time
+# as a histogram, optionally per-process.
#
# Copyright 2016 Sasha Goldshtein
# Licensed under the Apache License, Version 2.0 (the "License")
@@ -18,6 +18,7 @@
examples = """examples:
cpudist # summarize on-CPU time as a histogram
+ cpudist -O # summarize off-CPU time as a histogram
cpudist 1 10 # print 1 second summaries, 10 times
cpudist -mT 1 # 1s summaries, milliseconds, and timestamps
cpudist -P # show each PID separately
@@ -27,6 +28,8 @@
description="Summarize on-CPU time per task as a histogram.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
+parser.add_argument("-O", "--offcpu", action="store_true",
+ help="measure off-CPU time")
parser.add_argument("-T", "--timestamp", action="store_true",
help="include timestamp on output")
parser.add_argument("-m", "--milliseconds", action="store_true",
@@ -52,60 +55,90 @@
bpf_text += tp.generate_entry_probe()
bpf_text += tp.generate_struct()
+if not args.offcpu:
+ bpf_text += "#define ONCPU\n"
+
bpf_text += """
typedef struct pid_key {
u64 id;
u64 slot;
} pid_key_t;
-// We need to store the start time, which is when the thread got switched in,
-// and the tgid for the pid because the sched_switch tracepoint doesn't provide
-// that information.
+
BPF_HASH(start, u32, u64);
BPF_HASH(tgid_for_pid, u32, u32);
STORAGE
+#define INVALID_TGID 0xffffffff
+
+static inline u32 get_tgid_if_missing(u32 tgid, u32 pid)
+{
+ if (tgid == INVALID_TGID) {
+ u32 *stored_tgid = tgid_for_pid.lookup(&pid);
+ if (stored_tgid != 0)
+ return *stored_tgid;
+ }
+ return tgid;
+}
+
+static inline void store_start(u32 tgid, u32 pid, u64 ts)
+{
+ tgid = get_tgid_if_missing(tgid, pid);
+
+ if (FILTER)
+ return;
+
+ start.update(&pid, &ts);
+}
+
+static inline void update_hist(u32 tgid, u32 pid, u64 ts)
+{
+ tgid = get_tgid_if_missing(tgid, pid);
+
+ if (FILTER)
+ return;
+
+ u64 *tsp = start.lookup(&pid);
+ if (tsp == 0)
+ return;
+
+ u64 delta = ts - *tsp;
+ FACTOR
+ STORE
+}
+
int sched_switch(struct pt_regs *ctx)
{
+ u64 ts = bpf_ktime_get_ns();
u64 pid_tgid = bpf_get_current_pid_tgid();
+ u32 tgid = pid_tgid >> 32, pid = pid_tgid;
+ // Keep a mapping of tgid for pid because when sched_switch hits,
+ // we only have the tgid information for the *current* pid, but not
+ // for the previous one.
+ tgid_for_pid.update(&pid, &tgid);
+
u64 *di = __trace_di.lookup(&pid_tgid);
if (di == 0)
return 0;
struct sched_switch_trace_entry args = {};
bpf_probe_read(&args, sizeof(args), (void *)*di);
-
- u32 tgid, pid;
- u64 ts = bpf_ktime_get_ns();
+ // TODO: Store the comm as well
if (args.prev_state == TASK_RUNNING) {
- pid = args.prev_pid;
-
- u32 *stored_tgid = tgid_for_pid.lookup(&pid);
- if (stored_tgid == 0)
- goto BAIL;
- tgid = *stored_tgid;
-
- if (FILTER)
- goto BAIL;
-
- u64 *tsp = start.lookup(&pid);
- if (tsp == 0)
- goto BAIL;
-
- u64 delta = ts - *tsp;
- FACTOR
- STORE
+ u32 prev_pid = args.prev_pid;
+#ifdef ONCPU
+ update_hist(INVALID_TGID, prev_pid, ts);
+#else
+ store_start(INVALID_TGID, prev_pid, ts);
+#endif
}
-BAIL:
- tgid = pid_tgid >> 32;
- pid = pid_tgid;
- if (FILTER)
- return 0;
-
- start.update(&pid, &ts);
- tgid_for_pid.update(&pid, &tgid);
+#ifdef ONCPU
+ store_start(tgid, pid, ts);
+#else
+ update_hist(tgid, pid, ts);
+#endif
return 0;
}
@@ -144,7 +177,8 @@
Tracepoint.attach(b)
b.attach_kprobe(event="perf_trace_sched_switch", fn_name="sched_switch")
-print("Tracing on-CPU time... Hit Ctrl-C to end.")
+print("Tracing %s-CPU time... Hit Ctrl-C to end." %
+ ("off" if args.offcpu else "on"))
exiting = 0 if args.interval else 1
dist = b.get_table("dist")