xref: /aosp_15_r20/external/bcc/tools/dirtop.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1*387f9dfdSAndroid Build Coastguard Worker#!/usr/bin/env python
2*387f9dfdSAndroid Build Coastguard Worker# @lint-avoid-python-3-compatibility-imports
3*387f9dfdSAndroid Build Coastguard Worker#
4*387f9dfdSAndroid Build Coastguard Worker# dirtop  file reads and writes by directory.
5*387f9dfdSAndroid Build Coastguard Worker#          For Linux, uses BCC, eBPF.
6*387f9dfdSAndroid Build Coastguard Worker#
7*387f9dfdSAndroid Build Coastguard Worker# USAGE: dirtop.py -d 'directory1,directory2' [-h] [-C] [-r MAXROWS] [interval] [count]
8*387f9dfdSAndroid Build Coastguard Worker#
9*387f9dfdSAndroid Build Coastguard Worker# This uses in-kernel eBPF maps to store per process summaries for efficiency.
10*387f9dfdSAndroid Build Coastguard Worker#
11*387f9dfdSAndroid Build Coastguard Worker# Copyright 2016 Netflix, Inc.
12*387f9dfdSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License")
13*387f9dfdSAndroid Build Coastguard Worker#
14*387f9dfdSAndroid Build Coastguard Worker# 13-Mar-2020   Erwan Velu      Created dirtop from filetop
15*387f9dfdSAndroid Build Coastguard Worker# 06-Feb-2016   Brendan Gregg   Created filetop.
16*387f9dfdSAndroid Build Coastguard Worker
17*387f9dfdSAndroid Build Coastguard Workerfrom __future__ import print_function
18*387f9dfdSAndroid Build Coastguard Workerfrom bcc import BPF
19*387f9dfdSAndroid Build Coastguard Workerfrom time import sleep, strftime
20*387f9dfdSAndroid Build Coastguard Workerimport argparse
21*387f9dfdSAndroid Build Coastguard Workerimport os
22*387f9dfdSAndroid Build Coastguard Workerimport stat
23*387f9dfdSAndroid Build Coastguard Workerfrom subprocess import call
24*387f9dfdSAndroid Build Coastguard Worker
25*387f9dfdSAndroid Build Coastguard Worker# arguments
26*387f9dfdSAndroid Build Coastguard Workerexamples = """examples:
27*387f9dfdSAndroid Build Coastguard Worker    ./dirtop -d '/hdfs/uuid/*/yarn'       # directory I/O top, 1 second refresh
28*387f9dfdSAndroid Build Coastguard Worker    ./dirtop -d '/hdfs/uuid/*/yarn' -C    # don't clear the screen
29*387f9dfdSAndroid Build Coastguard Worker    ./dirtop -d '/hdfs/uuid/*/yarn' 5     # 5 second summaries
30*387f9dfdSAndroid Build Coastguard Worker    ./dirtop -d '/hdfs/uuid/*/yarn' 5 10  # 5 second summaries, 10 times only
31*387f9dfdSAndroid Build Coastguard Worker    ./dirtop -d '/hdfs/uuid/*/yarn,/hdfs/uuid/*/data' # Running dirtop on two set of directories
32*387f9dfdSAndroid Build Coastguard Worker"""
33*387f9dfdSAndroid Build Coastguard Workerparser = argparse.ArgumentParser(
34*387f9dfdSAndroid Build Coastguard Worker    description="File reads and writes by process",
35*387f9dfdSAndroid Build Coastguard Worker    formatter_class=argparse.RawDescriptionHelpFormatter,
36*387f9dfdSAndroid Build Coastguard Worker    epilog=examples)
37*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-C", "--noclear", action="store_true",
38*387f9dfdSAndroid Build Coastguard Worker                    help="don't clear the screen")
39*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-r", "--maxrows", default=20,
40*387f9dfdSAndroid Build Coastguard Worker                    help="maximum rows to print, default 20")
41*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-s", "--sort", default="all",
42*387f9dfdSAndroid Build Coastguard Worker                    choices=["all", "reads", "writes", "rbytes", "wbytes"],
43*387f9dfdSAndroid Build Coastguard Worker                    help="sort column, default all")
44*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-p", "--pid", type=int, metavar="PID", dest="tgid",
45*387f9dfdSAndroid Build Coastguard Worker                    help="trace this PID only")
46*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("interval", nargs="?", default=1,
47*387f9dfdSAndroid Build Coastguard Worker                    help="output interval, in seconds")
48*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("count", nargs="?", default=99999999,
49*387f9dfdSAndroid Build Coastguard Worker                    help="number of outputs")
50*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("--ebpf", action="store_true",
51*387f9dfdSAndroid Build Coastguard Worker                    help=argparse.SUPPRESS)
52*387f9dfdSAndroid Build Coastguard Workerparser.add_argument("-d", "--root-directories", type=str, required=True, dest="rootdirs",
53*387f9dfdSAndroid Build Coastguard Worker                    help="select the directories to observe, separated by commas")
54*387f9dfdSAndroid Build Coastguard Workerargs = parser.parse_args()
55*387f9dfdSAndroid Build Coastguard Workerinterval = int(args.interval)
56*387f9dfdSAndroid Build Coastguard Workercountdown = int(args.count)
57*387f9dfdSAndroid Build Coastguard Workermaxrows = int(args.maxrows)
58*387f9dfdSAndroid Build Coastguard Workerclear = not int(args.noclear)
59*387f9dfdSAndroid Build Coastguard Workerdebug = 0
60*387f9dfdSAndroid Build Coastguard Worker
61*387f9dfdSAndroid Build Coastguard Worker# linux stats
62*387f9dfdSAndroid Build Coastguard Workerloadavg = "/proc/loadavg"
63*387f9dfdSAndroid Build Coastguard Worker
64*387f9dfdSAndroid Build Coastguard Worker# define BPF program
65*387f9dfdSAndroid Build Coastguard Workerbpf_text = """
66*387f9dfdSAndroid Build Coastguard Worker# include <uapi/linux/ptrace.h>
67*387f9dfdSAndroid Build Coastguard Worker# include <linux/blkdev.h>
68*387f9dfdSAndroid Build Coastguard Worker
69*387f9dfdSAndroid Build Coastguard Worker// the key for the output summary
70*387f9dfdSAndroid Build Coastguard Workerstruct info_t {
71*387f9dfdSAndroid Build Coastguard Worker    unsigned long inode_id;
72*387f9dfdSAndroid Build Coastguard Worker};
73*387f9dfdSAndroid Build Coastguard Worker
74*387f9dfdSAndroid Build Coastguard Worker// the value of the output summary
75*387f9dfdSAndroid Build Coastguard Workerstruct val_t {
76*387f9dfdSAndroid Build Coastguard Worker    u64 reads;
77*387f9dfdSAndroid Build Coastguard Worker    u64 writes;
78*387f9dfdSAndroid Build Coastguard Worker    u64 rbytes;
79*387f9dfdSAndroid Build Coastguard Worker    u64 wbytes;
80*387f9dfdSAndroid Build Coastguard Worker};
81*387f9dfdSAndroid Build Coastguard Worker
82*387f9dfdSAndroid Build Coastguard WorkerBPF_HASH(counts, struct info_t, struct val_t);
83*387f9dfdSAndroid Build Coastguard Worker
84*387f9dfdSAndroid Build Coastguard Workerstatic int do_entry(struct pt_regs *ctx, struct file *file,
85*387f9dfdSAndroid Build Coastguard Worker    char __user *buf, size_t count, int is_read)
86*387f9dfdSAndroid Build Coastguard Worker{
87*387f9dfdSAndroid Build Coastguard Worker    u32 tgid = bpf_get_current_pid_tgid() >> 32;
88*387f9dfdSAndroid Build Coastguard Worker    if (TGID_FILTER)
89*387f9dfdSAndroid Build Coastguard Worker        return 0;
90*387f9dfdSAndroid Build Coastguard Worker
91*387f9dfdSAndroid Build Coastguard Worker    // The directory inodes we look at
92*387f9dfdSAndroid Build Coastguard Worker    u32 dir_ids[INODES_NUMBER] =  DIRECTORY_INODES;
93*387f9dfdSAndroid Build Coastguard Worker    struct info_t info = {.inode_id = 0};
94*387f9dfdSAndroid Build Coastguard Worker    struct dentry *pde = file->f_path.dentry;
95*387f9dfdSAndroid Build Coastguard Worker    for (int i=0; i<50; i++) {
96*387f9dfdSAndroid Build Coastguard Worker        // If we don't have any parent, we reached the root
97*387f9dfdSAndroid Build Coastguard Worker        if (!pde->d_parent) {
98*387f9dfdSAndroid Build Coastguard Worker            break;
99*387f9dfdSAndroid Build Coastguard Worker        }
100*387f9dfdSAndroid Build Coastguard Worker        pde = pde->d_parent;
101*387f9dfdSAndroid Build Coastguard Worker        // Does the files is part of the directory we look for
102*387f9dfdSAndroid Build Coastguard Worker        for(int dir_id=0; dir_id<INODES_NUMBER; dir_id++) {
103*387f9dfdSAndroid Build Coastguard Worker            if (pde->d_inode->i_ino == dir_ids[dir_id]) {
104*387f9dfdSAndroid Build Coastguard Worker                // Yes, let's export the top directory inode
105*387f9dfdSAndroid Build Coastguard Worker                info.inode_id = pde->d_inode->i_ino;
106*387f9dfdSAndroid Build Coastguard Worker                break;
107*387f9dfdSAndroid Build Coastguard Worker            }
108*387f9dfdSAndroid Build Coastguard Worker        }
109*387f9dfdSAndroid Build Coastguard Worker    }
110*387f9dfdSAndroid Build Coastguard Worker    // If we didn't found any, let's abort
111*387f9dfdSAndroid Build Coastguard Worker    if (info.inode_id == 0) {
112*387f9dfdSAndroid Build Coastguard Worker        return 0;
113*387f9dfdSAndroid Build Coastguard Worker    }
114*387f9dfdSAndroid Build Coastguard Worker
115*387f9dfdSAndroid Build Coastguard Worker    struct val_t *valp, zero = {};
116*387f9dfdSAndroid Build Coastguard Worker    valp = counts.lookup_or_try_init(&info, &zero);
117*387f9dfdSAndroid Build Coastguard Worker    if (valp) {
118*387f9dfdSAndroid Build Coastguard Worker        if (is_read) {
119*387f9dfdSAndroid Build Coastguard Worker            valp->reads++;
120*387f9dfdSAndroid Build Coastguard Worker            valp->rbytes += count;
121*387f9dfdSAndroid Build Coastguard Worker        } else {
122*387f9dfdSAndroid Build Coastguard Worker            valp->writes++;
123*387f9dfdSAndroid Build Coastguard Worker            valp->wbytes += count;
124*387f9dfdSAndroid Build Coastguard Worker        }
125*387f9dfdSAndroid Build Coastguard Worker    }
126*387f9dfdSAndroid Build Coastguard Worker    return 0;
127*387f9dfdSAndroid Build Coastguard Worker}
128*387f9dfdSAndroid Build Coastguard Worker
129*387f9dfdSAndroid Build Coastguard Workerint trace_read_entry(struct pt_regs *ctx, struct file *file,
130*387f9dfdSAndroid Build Coastguard Worker    char __user *buf, size_t count)
131*387f9dfdSAndroid Build Coastguard Worker{
132*387f9dfdSAndroid Build Coastguard Worker    return do_entry(ctx, file, buf, count, 1);
133*387f9dfdSAndroid Build Coastguard Worker}
134*387f9dfdSAndroid Build Coastguard Worker
135*387f9dfdSAndroid Build Coastguard Workerint trace_write_entry(struct pt_regs *ctx, struct file *file,
136*387f9dfdSAndroid Build Coastguard Worker    char __user *buf, size_t count)
137*387f9dfdSAndroid Build Coastguard Worker{
138*387f9dfdSAndroid Build Coastguard Worker    return do_entry(ctx, file, buf, count, 0);
139*387f9dfdSAndroid Build Coastguard Worker}
140*387f9dfdSAndroid Build Coastguard Worker
141*387f9dfdSAndroid Build Coastguard Worker"""
142*387f9dfdSAndroid Build Coastguard Worker
143*387f9dfdSAndroid Build Coastguard Worker
144*387f9dfdSAndroid Build Coastguard Workerdef get_searched_ids(root_directories):
145*387f9dfdSAndroid Build Coastguard Worker    """Export the inode numbers of the selected directories."""
146*387f9dfdSAndroid Build Coastguard Worker    from glob import glob
147*387f9dfdSAndroid Build Coastguard Worker    inode_to_path = {}
148*387f9dfdSAndroid Build Coastguard Worker    inodes = "{"
149*387f9dfdSAndroid Build Coastguard Worker    total_dirs = 0
150*387f9dfdSAndroid Build Coastguard Worker    for root_directory in root_directories.split(','):
151*387f9dfdSAndroid Build Coastguard Worker        try:
152*387f9dfdSAndroid Build Coastguard Worker            searched_dirs = glob(root_directory, recursive=True)
153*387f9dfdSAndroid Build Coastguard Worker        except TypeError:
154*387f9dfdSAndroid Build Coastguard Worker            searched_dirs = glob(root_directory)
155*387f9dfdSAndroid Build Coastguard Worker        if not searched_dirs:
156*387f9dfdSAndroid Build Coastguard Worker            continue
157*387f9dfdSAndroid Build Coastguard Worker
158*387f9dfdSAndroid Build Coastguard Worker        for mydir in searched_dirs:
159*387f9dfdSAndroid Build Coastguard Worker            total_dirs = total_dirs + 1
160*387f9dfdSAndroid Build Coastguard Worker            # If we pass more than 15 dirs, ebpf program fails
161*387f9dfdSAndroid Build Coastguard Worker            if total_dirs > 15:
162*387f9dfdSAndroid Build Coastguard Worker                print('15 directories limit reached')
163*387f9dfdSAndroid Build Coastguard Worker                break
164*387f9dfdSAndroid Build Coastguard Worker            inode_id = os.lstat(mydir)[stat.ST_INO]
165*387f9dfdSAndroid Build Coastguard Worker            if inode_id in inode_to_path:
166*387f9dfdSAndroid Build Coastguard Worker                if inode_to_path[inode_id] == mydir:
167*387f9dfdSAndroid Build Coastguard Worker                    print('Skipping {} as already considered'.format(mydir))
168*387f9dfdSAndroid Build Coastguard Worker            else:
169*387f9dfdSAndroid Build Coastguard Worker                inodes = "{},{}".format(inodes, inode_id)
170*387f9dfdSAndroid Build Coastguard Worker                inode_to_path[inode_id] = mydir
171*387f9dfdSAndroid Build Coastguard Worker                print('Considering {} with inode_id {}'.format(mydir, inode_id))
172*387f9dfdSAndroid Build Coastguard Worker
173*387f9dfdSAndroid Build Coastguard Worker    inodes = inodes + '}'
174*387f9dfdSAndroid Build Coastguard Worker    if len(inode_to_path) == 0:
175*387f9dfdSAndroid Build Coastguard Worker        print('Cannot find any valid directory')
176*387f9dfdSAndroid Build Coastguard Worker        exit()
177*387f9dfdSAndroid Build Coastguard Worker    return inodes.replace('{,', '{'), inode_to_path
178*387f9dfdSAndroid Build Coastguard Worker
179*387f9dfdSAndroid Build Coastguard Worker
180*387f9dfdSAndroid Build Coastguard Workerif args.tgid:
181*387f9dfdSAndroid Build Coastguard Worker    bpf_text = bpf_text.replace('TGID_FILTER', 'tgid != %d' % args.tgid)
182*387f9dfdSAndroid Build Coastguard Workerelse:
183*387f9dfdSAndroid Build Coastguard Worker    bpf_text = bpf_text.replace('TGID_FILTER', '0')
184*387f9dfdSAndroid Build Coastguard Worker
185*387f9dfdSAndroid Build Coastguard Workerinodes, inodes_to_path = get_searched_ids(args.rootdirs)
186*387f9dfdSAndroid Build Coastguard Workerbpf_text = bpf_text.replace("DIRECTORY_INODES", inodes)
187*387f9dfdSAndroid Build Coastguard Workerbpf_text = bpf_text.replace(
188*387f9dfdSAndroid Build Coastguard Worker    "INODES_NUMBER", '{}'.format(len(inodes.split(','))))
189*387f9dfdSAndroid Build Coastguard Worker
190*387f9dfdSAndroid Build Coastguard Workerif debug or args.ebpf:
191*387f9dfdSAndroid Build Coastguard Worker    print(bpf_text)
192*387f9dfdSAndroid Build Coastguard Worker    if args.ebpf:
193*387f9dfdSAndroid Build Coastguard Worker        exit()
194*387f9dfdSAndroid Build Coastguard Worker
195*387f9dfdSAndroid Build Coastguard Worker# initialize BPF
196*387f9dfdSAndroid Build Coastguard Workerb = BPF(text=bpf_text)
197*387f9dfdSAndroid Build Coastguard Workerb.attach_kprobe(event="vfs_read", fn_name="trace_read_entry")
198*387f9dfdSAndroid Build Coastguard Workerb.attach_kprobe(event="vfs_write", fn_name="trace_write_entry")
199*387f9dfdSAndroid Build Coastguard Worker
200*387f9dfdSAndroid Build Coastguard WorkerDNAME_INLINE_LEN = 32  # linux/dcache.h
201*387f9dfdSAndroid Build Coastguard Worker
202*387f9dfdSAndroid Build Coastguard Workerprint('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
203*387f9dfdSAndroid Build Coastguard Worker
204*387f9dfdSAndroid Build Coastguard Worker
205*387f9dfdSAndroid Build Coastguard Workerdef sort_fn(counts):
206*387f9dfdSAndroid Build Coastguard Worker    """Define how to sort the columns"""
207*387f9dfdSAndroid Build Coastguard Worker    if args.sort == "all":
208*387f9dfdSAndroid Build Coastguard Worker        return (counts[1].rbytes + counts[1].wbytes + counts[1].reads + counts[1].writes)
209*387f9dfdSAndroid Build Coastguard Worker    else:
210*387f9dfdSAndroid Build Coastguard Worker        return getattr(counts[1], args.sort)
211*387f9dfdSAndroid Build Coastguard Worker
212*387f9dfdSAndroid Build Coastguard Worker
213*387f9dfdSAndroid Build Coastguard Worker# output
214*387f9dfdSAndroid Build Coastguard Workerexiting = 0
215*387f9dfdSAndroid Build Coastguard Workerwhile 1:
216*387f9dfdSAndroid Build Coastguard Worker    try:
217*387f9dfdSAndroid Build Coastguard Worker        sleep(interval)
218*387f9dfdSAndroid Build Coastguard Worker    except KeyboardInterrupt:
219*387f9dfdSAndroid Build Coastguard Worker        exiting = 1
220*387f9dfdSAndroid Build Coastguard Worker
221*387f9dfdSAndroid Build Coastguard Worker    # header
222*387f9dfdSAndroid Build Coastguard Worker    if clear:
223*387f9dfdSAndroid Build Coastguard Worker        call("clear")
224*387f9dfdSAndroid Build Coastguard Worker    else:
225*387f9dfdSAndroid Build Coastguard Worker        print()
226*387f9dfdSAndroid Build Coastguard Worker    with open(loadavg) as stats:
227*387f9dfdSAndroid Build Coastguard Worker        print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
228*387f9dfdSAndroid Build Coastguard Worker
229*387f9dfdSAndroid Build Coastguard Worker    print("%-6s %-6s %-8s %-8s %s" %
230*387f9dfdSAndroid Build Coastguard Worker          ("READS", "WRITES", "R_Kb", "W_Kb", "PATH"))
231*387f9dfdSAndroid Build Coastguard Worker    # by-TID output
232*387f9dfdSAndroid Build Coastguard Worker    counts = b.get_table("counts")
233*387f9dfdSAndroid Build Coastguard Worker    line = 0
234*387f9dfdSAndroid Build Coastguard Worker    reads = {}
235*387f9dfdSAndroid Build Coastguard Worker    writes = {}
236*387f9dfdSAndroid Build Coastguard Worker    reads_Kb = {}
237*387f9dfdSAndroid Build Coastguard Worker    writes_Kb = {}
238*387f9dfdSAndroid Build Coastguard Worker    for k, v in reversed(sorted(counts.items(),
239*387f9dfdSAndroid Build Coastguard Worker                                key=sort_fn)):
240*387f9dfdSAndroid Build Coastguard Worker        # If it's the first time we see this inode
241*387f9dfdSAndroid Build Coastguard Worker        if k.inode_id not in reads:
242*387f9dfdSAndroid Build Coastguard Worker            # let's create a new entry
243*387f9dfdSAndroid Build Coastguard Worker            reads[k.inode_id] = v.reads
244*387f9dfdSAndroid Build Coastguard Worker            writes[k.inode_id] = v.writes
245*387f9dfdSAndroid Build Coastguard Worker            reads_Kb[k.inode_id] = v.rbytes / 1024
246*387f9dfdSAndroid Build Coastguard Worker            writes_Kb[k.inode_id] = v.wbytes / 1024
247*387f9dfdSAndroid Build Coastguard Worker        else:
248*387f9dfdSAndroid Build Coastguard Worker            # unless add the current performance metrics
249*387f9dfdSAndroid Build Coastguard Worker            # to the previous ones
250*387f9dfdSAndroid Build Coastguard Worker            reads[k.inode_id] += v.reads
251*387f9dfdSAndroid Build Coastguard Worker            writes[k.inode_id] += v.writes
252*387f9dfdSAndroid Build Coastguard Worker            reads_Kb[k.inode_id] += v.rbytes / 1024
253*387f9dfdSAndroid Build Coastguard Worker            writes_Kb[k.inode_id] += v.wbytes / 1024
254*387f9dfdSAndroid Build Coastguard Worker
255*387f9dfdSAndroid Build Coastguard Worker    for node_id in reads:
256*387f9dfdSAndroid Build Coastguard Worker        print("%-6d %-6d %-8d %-8d %s" %
257*387f9dfdSAndroid Build Coastguard Worker              (reads[node_id], writes[node_id], reads_Kb[node_id], writes_Kb[node_id], inodes_to_path[node_id]))
258*387f9dfdSAndroid Build Coastguard Worker        line += 1
259*387f9dfdSAndroid Build Coastguard Worker        if line >= maxrows:
260*387f9dfdSAndroid Build Coastguard Worker            break
261*387f9dfdSAndroid Build Coastguard Worker
262*387f9dfdSAndroid Build Coastguard Worker    counts.clear()
263*387f9dfdSAndroid Build Coastguard Worker
264*387f9dfdSAndroid Build Coastguard Worker    countdown -= 1
265*387f9dfdSAndroid Build Coastguard Worker    if exiting or countdown == 0:
266*387f9dfdSAndroid Build Coastguard Worker        print("Detaching...")
267*387f9dfdSAndroid Build Coastguard Worker        exit()
268