• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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