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