rockyxing | fa57f43 | 2022-02-23 09:07:36 +0800 | [diff] [blame] | 1 | #!/usr/bin/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 | |
| 12 | from __future__ import print_function |
| 13 | from bcc import BPF |
| 14 | from time import sleep, strftime |
| 15 | import argparse |
| 16 | import os |
| 17 | |
| 18 | examples = """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 | """ |
| 23 | parser = argparse.ArgumentParser( |
| 24 | description="Show block device I/O pattern.", |
| 25 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 26 | epilog=examples) |
| 27 | parser.add_argument("-d", "--disk", type=str, |
| 28 | help="Trace this disk only") |
| 29 | parser.add_argument("interval", nargs="?", default=99999999, |
| 30 | help="Output interval in seconds") |
| 31 | parser.add_argument("count", nargs="?", default=99999999, |
| 32 | help="Number of outputs") |
| 33 | args = parser.parse_args() |
| 34 | countdown = int(args.count) |
| 35 | |
| 36 | bpf_text=""" |
| 37 | struct counter { |
| 38 | u64 last_sector; |
| 39 | u64 bytes; |
| 40 | u32 sequential; |
| 41 | u32 random; |
| 42 | }; |
| 43 | |
| 44 | BPF_HASH(counters, u32, struct counter); |
| 45 | |
| 46 | TRACEPOINT_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 | |
| 75 | dev_minor_bits = 20 |
| 76 | |
| 77 | def mkdev(major, minor): |
| 78 | return (major << dev_minor_bits) | minor |
| 79 | |
| 80 | |
| 81 | partitions = {} |
| 82 | |
| 83 | with 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 | |
| 92 | if 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)) |
| 103 | else: |
| 104 | bpf_text = bpf_text.replace('DISK_FILTER', '') |
| 105 | |
| 106 | b = BPF(text=bpf_text) |
| 107 | |
| 108 | exiting = 0 if args.interval else 1 |
| 109 | counters = b.get_table("counters") |
| 110 | |
| 111 | print("%-9s %-7s %5s %5s %8s %10s" % |
| 112 | ("TIME", "DISK", "%RND", "%SEQ", "COUNT", "KBYTES")) |
| 113 | |
| 114 | while 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 | |
| 127 | print("%-9s %-7s %5d %5d %8d %10d" % ( |
| 128 | strftime("%H:%M:%S"), |
| 129 | part_name, |
| 130 | v.random * 100 / total, |
| 131 | v.sequential * 100 / total, |
| 132 | total, |
| 133 | v.bytes / 1024)) |
| 134 | |
| 135 | counters.clear() |
| 136 | |
| 137 | countdown -= 1 |
| 138 | if exiting or countdown == 0: |
| 139 | exit() |
| 140 | |