xref: /aosp_15_r20/external/bcc/tools/biopattern.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env python
2# @lint-avoid-python-3-compatibility-imports
3#
4# biopattern - Identify random/sequential disk access patterns.
5#              For Linux, uses BCC, eBPF.
6#
7# Copyright (c) 2022 Rocky Xing.
8# Licensed under the Apache License, Version 2.0 (the "License")
9#
10# 21-Feb-2022   Rocky Xing   Created this.
11
12from __future__ import print_function
13from bcc import BPF
14from time import sleep, strftime
15import argparse
16import os
17
18examples = """examples:
19    ./biopattern            # show block device I/O pattern.
20    ./biopattern 1 10       # print 1 second summaries, 10 times
21    ./biopattern -d sdb     # show sdb only
22"""
23parser = argparse.ArgumentParser(
24    description="Show block device I/O pattern.",
25    formatter_class=argparse.RawDescriptionHelpFormatter,
26    epilog=examples)
27parser.add_argument("-d", "--disk", type=str,
28    help="Trace this disk only")
29parser.add_argument("interval", nargs="?", default=99999999,
30    help="Output interval in seconds")
31parser.add_argument("count", nargs="?", default=99999999,
32    help="Number of outputs")
33args = parser.parse_args()
34countdown = int(args.count)
35
36bpf_text="""
37struct counter {
38    u64 last_sector;
39    u64 bytes;
40    u32 sequential;
41    u32 random;
42};
43
44BPF_HASH(counters, u32, struct counter);
45
46TRACEPOINT_PROBE(block, block_rq_complete)
47{
48    struct counter *counterp;
49    struct counter zero = {};
50    u32 dev = args->dev;
51    u64 sector = args->sector;
52    u32 nr_sector = args->nr_sector;
53
54    DISK_FILTER
55
56    counterp = counters.lookup_or_try_init(&dev, &zero);
57    if (counterp == 0) {
58        return 0;
59    }
60
61    if (counterp->last_sector) {
62        if (counterp->last_sector == sector) {
63            __sync_fetch_and_add(&counterp->sequential, 1);
64        } else {
65            __sync_fetch_and_add(&counterp->random, 1);
66        }
67        __sync_fetch_and_add(&counterp->bytes, nr_sector * 512);
68    }
69    counterp->last_sector = sector + nr_sector;
70
71    return 0;
72}
73"""
74
75dev_minor_bits = 20
76
77def mkdev(major, minor):
78   return (major << dev_minor_bits) | minor
79
80
81partitions = {}
82
83with open("/proc/partitions", 'r') as f:
84    lines = f.readlines()
85    for line in lines[2:]:
86        words = line.strip().split()
87        major = int(words[0])
88        minor = int(words[1])
89        part_name = words[3]
90        partitions[mkdev(major, minor)] = part_name
91
92if args.disk is not None:
93    disk_path = os.path.join('/dev', args.disk)
94    if os.path.exists(disk_path) == False:
95        print("no such disk '%s'" % args.disk)
96        exit(1)
97
98    stat_info = os.stat(disk_path)
99    major = os.major(stat_info.st_rdev)
100    minor = os.minor(stat_info.st_rdev)
101    bpf_text = bpf_text.replace('DISK_FILTER',
102                                'if (dev != %s) { return 0; }' % mkdev(major, minor))
103else:
104    bpf_text = bpf_text.replace('DISK_FILTER', '')
105
106b = BPF(text=bpf_text)
107
108exiting = 0 if args.interval else 1
109counters = b.get_table("counters")
110
111print("%-9s %-7s %5s %5s %8s %10s" %
112    ("TIME", "DISK", "%RND", "%SEQ", "COUNT", "KBYTES"))
113
114while True:
115    try:
116        sleep(int(args.interval))
117    except KeyboardInterrupt:
118        exiting = 1
119
120    for k, v in counters.items():
121        total = v.random + v.sequential
122        if total == 0:
123            continue
124
125        part_name = partitions.get(k.value, "Unknown")
126        random_percent = int(round(v.random * 100 / total))
127
128        print("%-9s %-7s %5d %5d %8d %10d" % (
129            strftime("%H:%M:%S"),
130            part_name,
131            random_percent,
132            100 - random_percent,
133            total,
134            v.bytes / 1024))
135
136    counters.clear()
137
138    countdown -= 1
139    if exiting or countdown == 0:
140        exit()
141
142