1#!/usr/bin/python 2# @lint-avoid-python-3-compatibility-imports 3# 4# virtiostat Show virtio devices input/output statistics. 5# For Linux, uses BCC, eBPF. 6# 7# USAGE: virtiostat [-h] [-T] [-D] [-d DRIVER] [-n DEVNAME] [INTERVAL] [COUNT] 8# 9# Copyright (c) 2021 zhenwei pi 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 13-Feb-2021 zhenwei pi Created this. 13 14from __future__ import print_function 15from bcc import BPF 16from time import sleep, strftime 17import argparse 18 19# arguments 20examples = """examples: 21 ./virtiostat # print 3(default) second summaries 22 ./virtiostat 1 10 # print 1 second summaries, 10 times 23 ./virtiostat -T # show timestamps 24 ./virtiostat -d virtio_blk # only show virtio block devices 25 ./virtiostat -n virtio0 # only show virtio0 device 26 ./virtiostat -D # show debug bpf text 27""" 28parser = argparse.ArgumentParser( 29 description="Show virtio devices input/output statistics", 30 formatter_class=argparse.RawDescriptionHelpFormatter, 31 epilog=examples) 32parser.add_argument("interval", nargs="?", default=3, 33 help="output interval, in seconds") 34parser.add_argument("count", nargs="?", default=99999999, 35 help="number of outputs") 36parser.add_argument("-T", "--timestamp", action="store_true", 37 help="show timestamp on output") 38parser.add_argument("-d", "--driver", 39 help="filter for driver name") 40parser.add_argument("-n", "--devname", 41 help="filter for device name") 42parser.add_argument("-D", "--debug", action="store_true", 43 help="print BPF program before starting (for debugging purposes)") 44parser.add_argument("--ebpf", action="store_true", 45 help=argparse.SUPPRESS) 46args = parser.parse_args() 47 48# define BPF program 49bpf_text = """ 50#ifndef KBUILD_MODNAME 51#define KBUILD_MODNAME "bcc" 52#endif 53#include <linux/virtio.h> 54#include <bcc/proto.h> 55 56/* typically virtio scsi has max SGs of 6 */ 57#define VIRTIO_MAX_SGS 6 58/* typically virtio blk has max SEG of 128 */ 59#define SG_MAX 128 60 61/* local strcmp function, max length 16 to protect instruction loops */ 62#define CMPMAX 16 63 64static int local_strcmp(const char *cs, const char *ct) 65{ 66 int len = 0; 67 unsigned char c1, c2; 68 69 while (len++ < CMPMAX) { 70 c1 = *cs++; 71 c2 = *ct++; 72 if (c1 != c2) 73 return c1 < c2 ? -1 : 1; 74 if (!c1) 75 break; 76 } 77 return 0; 78} 79 80typedef struct virtio_stat { 81 char driver[16]; 82 char dev[12]; 83 char vqname[12]; 84 u32 in_sgs; 85 u32 out_sgs; 86 u64 in_bw; 87 u64 out_bw; 88} virtio_stat_t; 89 90BPF_HASH(stats, u64, virtio_stat_t); 91 92static struct scatterlist *__sg_next(struct scatterlist *sgp) 93{ 94 struct scatterlist sg; 95 96 bpf_probe_read_kernel(&sg, sizeof(sg), sgp); 97 if (sg_is_last(&sg)) 98 return NULL; 99 100 sgp++; 101 102 bpf_probe_read_kernel(&sg, sizeof(sg), sgp); 103 if (unlikely(sg_is_chain(&sg))) 104 sgp = sg_chain_ptr(&sg); 105 106 return sgp; 107} 108 109static u64 count_len(struct scatterlist **sgs, unsigned int num) 110{ 111 u64 length = 0; 112 unsigned int i, n; 113 struct scatterlist *sgp = NULL; 114 115 for (i = 0; (i < VIRTIO_MAX_SGS) && (i < num); i++) { 116 for (n = 0, sgp = sgs[i]; sgp && (n < SG_MAX); sgp = __sg_next(sgp)) { 117 length += sgp->length; 118 n++; 119 } 120 121 /* Suggested by Yonghong Song: 122 * IndVarSimplifyPass with clang 12 may cause verifier failure: 123 * ; for (i = 0; (i < VIRTIO_MAX_SGS) && (i < num); i++) { // Line 60 124 * 90: 15 08 15 00 00 00 00 00 if r8 == 0 goto +21 125 * 91: bf 81 00 00 00 00 00 00 r1 = r8 126 * 92: 07 01 00 00 ff ff ff ff r1 += -1 127 * 93: 67 01 00 00 20 00 00 00 r1 <<= 32 128 * 94: 77 01 00 00 20 00 00 00 r1 >>= 32 129 * 95: b7 02 00 00 05 00 00 00 r2 = 5 130 * 96: 2d 12 01 00 00 00 00 00 if r2 > r1 goto +1 131 * 97: b7 08 00 00 06 00 00 00 r8 = 6 132 * 98: b7 02 00 00 00 00 00 00 r2 = 0 133 * 99: b7 09 00 00 00 00 00 00 r9 = 0 134 * 100: 7b 8a 68 ff 00 00 00 00 *(u64 *)(r10 - 152) = r8 135 * 101: 05 00 35 00 00 00 00 00 goto +53 136 * Note that r1 is refined by r8 is saved to stack for later use. 137 * This will give verifier u64_max loop bound and eventually cause 138 * verification failure. Workaround with the below asm code. 139 */ 140#if __clang_major__ >= 7 141 asm volatile("" : "=r"(i) : "0"(i)); 142#endif 143 } 144 145 return length; 146} 147 148static void record(struct virtqueue *vq, struct scatterlist **sgs, 149 unsigned int out_sgs, unsigned int in_sgs) 150{ 151 virtio_stat_t newvs = {0}; 152 virtio_stat_t *vs; 153 u64 key = (u64)vq; 154 u64 in_bw = 0; 155 156 DRIVERFILTER 157 DEVNAMEFILTER 158 159 /* Workaround: separate two count_len() calls, one here and the 160 * other below. Otherwise, compiler may generate some spills which 161 * harms verifier pruning. This happens in llvm12, but not llvm4. 162 * Below code works on both cases. 163 */ 164 if (in_sgs) 165 in_bw = count_len(sgs + out_sgs, in_sgs); 166 167 vs = stats.lookup(&key); 168 if (!vs) { 169 bpf_probe_read_kernel_str(newvs.driver, sizeof(newvs.driver), vq->vdev->dev.driver->name); 170 bpf_probe_read_kernel_str(newvs.dev, sizeof(newvs.dev), vq->vdev->dev.kobj.name); 171 bpf_probe_read_kernel_str(newvs.vqname, sizeof(newvs.vqname), vq->name); 172 newvs.out_sgs = out_sgs; 173 newvs.in_sgs = in_sgs; 174 if (out_sgs) 175 newvs.out_bw = count_len(sgs, out_sgs); 176 newvs.in_bw = in_bw; 177 stats.update(&key, &newvs); 178 } else { 179 vs->out_sgs += out_sgs; 180 vs->in_sgs += in_sgs; 181 if (out_sgs) 182 vs->out_bw += count_len(sgs, out_sgs); 183 vs->in_bw += in_bw; 184 } 185} 186 187int trace_virtqueue_add_sgs(struct pt_regs *ctx, struct virtqueue *vq, 188 struct scatterlist **sgs, unsigned int out_sgs, 189 unsigned int in_sgs, void *data, gfp_t gfp) 190 191{ 192 record(vq, sgs, out_sgs, in_sgs); 193 194 return 0; 195} 196 197int trace_virtqueue_add_outbuf(struct pt_regs *ctx, struct virtqueue *vq, 198 struct scatterlist *sg, unsigned int num, 199 void *data, gfp_t gfp) 200{ 201 record(vq, &sg, 1, 0); 202 203 return 0; 204} 205 206int trace_virtqueue_add_inbuf(struct pt_regs *ctx, struct virtqueue *vq, 207 struct scatterlist *sg, unsigned int num, 208 void *data, gfp_t gfp) 209{ 210 record(vq, &sg, 0, 1); 211 212 return 0; 213} 214 215int trace_virtqueue_add_inbuf_ctx(struct pt_regs *ctx, struct virtqueue *vq, 216 struct scatterlist *sg, unsigned int num, 217 void *data, void *_ctx, gfp_t gfp) 218{ 219 record(vq, &sg, 0, 1); 220 221 return 0; 222} 223""" 224 225# filter for driver name 226if args.driver: 227 bpf_text = bpf_text.replace('DRIVERFILTER', 228 """char filter_driver[] = \"%s\"; 229 char driver[16]; 230 bpf_probe_read_kernel_str(driver, sizeof(driver), vq->vdev->dev.driver->name); 231 if (local_strcmp(filter_driver, driver)) 232 return;""" % (args.driver)) 233else: 234 bpf_text = bpf_text.replace('DRIVERFILTER', '') 235 236# filter for dev name 237if args.devname: 238 bpf_text = bpf_text.replace('DEVNAMEFILTER', 239 """char filter_devname[] = \"%s\"; 240 char devname[16]; 241 bpf_probe_read_kernel_str(devname, sizeof(devname), vq->vdev->dev.kobj.name); 242 if (local_strcmp(filter_devname, devname)) 243 return;""" % (args.devname)) 244else: 245 bpf_text = bpf_text.replace('DEVNAMEFILTER', '') 246 247 248# debug mode: print bpf text 249if args.debug: 250 print(bpf_text) 251 252# dump mode: print bpf text and exit 253if args.ebpf: 254 print(bpf_text) 255 exit() 256 257# load BPF program 258b = BPF(text=bpf_text) 259b.attach_kprobe(event="virtqueue_add_sgs", fn_name="trace_virtqueue_add_sgs") 260b.attach_kprobe(event="virtqueue_add_outbuf", fn_name="trace_virtqueue_add_outbuf") 261b.attach_kprobe(event="virtqueue_add_inbuf", fn_name="trace_virtqueue_add_inbuf") 262b.attach_kprobe(event="virtqueue_add_inbuf_ctx", fn_name="trace_virtqueue_add_inbuf_ctx") 263 264print("Tracing virtio devices statistics ... Hit Ctrl-C to end.") 265 266# start main loop 267exiting = 0 if args.interval else 1 268seconds = 0 269while (1): 270 try: 271 sleep(int(args.interval)) 272 seconds = seconds + int(args.interval) 273 except KeyboardInterrupt: 274 exiting = 1 275 276 if args.timestamp: 277 print("%-8s\n" % strftime("%H:%M:%S"), end="") 278 else: 279 print("--------", end="\n") 280 281 print("%14s %8s %10s %7s %7s %14s %14s" % ("Driver", "Device", "VQ Name", "In SGs", "Out SGs", "In BW", "Out BW")) 282 stats = b.get_table("stats") 283 for k, v in sorted(stats.items(), key=lambda vs: vs[1].dev): 284 print("%14s %8s %10s %7d %7d %14d %14d" % (v.driver, v.dev, v.vqname, v.in_sgs, v.out_sgs, v.in_bw, v.out_bw)) 285 286 stats.clear() 287 288 if exiting or seconds >= int(args.count): 289 exit() 290