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