1#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# hardirqs  Summarize hard IRQ (interrupt) event time.
5#           For Linux, uses BCC, eBPF.
6#
7# USAGE: hardirqs [-h] [-T] [-N] [-C] [-d] [interval] [outputs]
8#
9# Thanks Amer Ather for help understanding irq behavior.
10#
11# Copyright (c) 2015 Brendan Gregg.
12# Licensed under the Apache License, Version 2.0 (the "License")
13#
14# 19-Oct-2015   Brendan Gregg   Created this.
15
16from __future__ import print_function
17from bcc import BPF
18from time import sleep, strftime
19import argparse
20
21# arguments
22examples = """examples:
23    ./hardirqs            # sum hard irq event time
24    ./hardirqs -d         # show hard irq event time as histograms
25    ./hardirqs 1 10       # print 1 second summaries, 10 times
26    ./hardirqs -NT 1      # 1s summaries, nanoseconds, and timestamps
27"""
28parser = argparse.ArgumentParser(
29    description="Summarize hard irq event time as histograms",
30    formatter_class=argparse.RawDescriptionHelpFormatter,
31    epilog=examples)
32parser.add_argument("-T", "--timestamp", action="store_true",
33    help="include timestamp on output")
34parser.add_argument("-N", "--nanoseconds", action="store_true",
35    help="output in nanoseconds")
36parser.add_argument("-C", "--count", action="store_true",
37    help="show event counts instead of timing")
38parser.add_argument("-d", "--dist", action="store_true",
39    help="show distributions as histograms")
40parser.add_argument("interval", nargs="?", default=99999999,
41    help="output interval, in seconds")
42parser.add_argument("outputs", nargs="?", default=99999999,
43    help="number of outputs")
44parser.add_argument("--ebpf", action="store_true",
45    help=argparse.SUPPRESS)
46args = parser.parse_args()
47countdown = int(args.outputs)
48if args.count and (args.dist or args.nanoseconds):
49    print("The --count option can't be used with time-based options")
50    exit()
51if args.count:
52    factor = 1
53    label = "count"
54elif args.nanoseconds:
55    factor = 1
56    label = "nsecs"
57else:
58    factor = 1000
59    label = "usecs"
60debug = 0
61
62# define BPF program
63bpf_text = """
64#include <uapi/linux/ptrace.h>
65#include <linux/irq.h>
66#include <linux/irqdesc.h>
67#include <linux/interrupt.h>
68
69typedef struct irq_key {
70    char name[32];
71    u64 slot;
72} irq_key_t;
73BPF_HASH(start, u32);
74BPF_HASH(irqdesc, u32, struct irq_desc *);
75BPF_HISTOGRAM(dist, irq_key_t);
76
77// count IRQ
78int count_only(struct pt_regs *ctx, struct irq_desc *desc)
79{
80    u32 pid = bpf_get_current_pid_tgid();
81
82    struct irqaction *action = desc->action;
83    char *name = (char *)action->name;
84
85    irq_key_t key = {.slot = 0 /* ignore */};
86    bpf_probe_read(&key.name, sizeof(key.name), name);
87    dist.increment(key);
88
89    return 0;
90}
91
92// time IRQ
93int trace_start(struct pt_regs *ctx, struct irq_desc *desc)
94{
95    u32 pid = bpf_get_current_pid_tgid();
96    u64 ts = bpf_ktime_get_ns();
97    start.update(&pid, &ts);
98    irqdesc.update(&pid, &desc);
99    return 0;
100}
101
102int trace_completion(struct pt_regs *ctx)
103{
104    u64 *tsp, delta;
105    struct irq_desc **descp;
106    u32 pid = bpf_get_current_pid_tgid();
107
108    // fetch timestamp and calculate delta
109    tsp = start.lookup(&pid);
110    descp = irqdesc.lookup(&pid);
111    if (tsp == 0 || descp == 0) {
112        return 0;   // missed start
113    }
114    struct irq_desc *desc = *descp;
115    struct irqaction *action = desc->action;
116    char *name = (char *)action->name;
117    delta = bpf_ktime_get_ns() - *tsp;
118
119    // store as sum or histogram
120    STORE
121
122    start.delete(&pid);
123    irqdesc.delete(&pid);
124    return 0;
125}
126"""
127
128# code substitutions
129if args.dist:
130    bpf_text = bpf_text.replace('STORE',
131        'irq_key_t key = {.slot = bpf_log2l(delta / %d)};' % factor +
132        'bpf_probe_read(&key.name, sizeof(key.name), name);' +
133        'dist.increment(key);')
134else:
135    bpf_text = bpf_text.replace('STORE',
136        'irq_key_t key = {.slot = 0 /* ignore */};' +
137        'bpf_probe_read(&key.name, sizeof(key.name), name);' +
138        'dist.increment(key, delta);')
139if debug or args.ebpf:
140    print(bpf_text)
141    if args.ebpf:
142        exit()
143
144# load BPF program
145b = BPF(text=bpf_text)
146
147# these should really use irq:irq_handler_entry/exit tracepoints:
148if args.count:
149    b.attach_kprobe(event="handle_irq_event_percpu", fn_name="count_only")
150    print("Tracing hard irq events... Hit Ctrl-C to end.")
151else:
152    b.attach_kprobe(event="handle_irq_event_percpu", fn_name="trace_start")
153    b.attach_kretprobe(event="handle_irq_event_percpu",
154        fn_name="trace_completion")
155    print("Tracing hard irq event time... Hit Ctrl-C to end.")
156
157# output
158exiting = 0 if args.interval else 1
159dist = b.get_table("dist")
160while (1):
161    try:
162        sleep(int(args.interval))
163    except KeyboardInterrupt:
164        exiting = 1
165
166    print()
167    if args.timestamp:
168        print("%-8s\n" % strftime("%H:%M:%S"), end="")
169
170    if args.dist:
171        dist.print_log2_hist(label, "hardirq")
172    else:
173        print("%-26s %11s" % ("HARDIRQ", "TOTAL_" + label))
174        for k, v in sorted(dist.items(), key=lambda dist: dist[1].value):
175            print("%-26s %11d" % (k.name.decode('utf-8', 'replace'), v.value / factor))
176    dist.clear()
177
178    countdown -= 1
179    if exiting or countdown == 0:
180        exit()
181