Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      2 # @lint-avoid-python-3-compatibility-imports
      3 #
      4 # dcstat   Directory entry cache (dcache) stats.
      5 #          For Linux, uses BCC, eBPF.
      6 #
      7 # USAGE: dcstat [interval [count]]
      8 #
      9 # This uses kernel dynamic tracing of kernel functions, lookup_fast() and
     10 # d_lookup(), which will need to be modified to match kernel changes. See
     11 # code comments.
     12 #
     13 # Copyright 2016 Netflix, Inc.
     14 # Licensed under the Apache License, Version 2.0 (the "License")
     15 #
     16 # 09-Feb-2016   Brendan Gregg   Created this.
     17 
     18 from __future__ import print_function
     19 from bcc import BPF
     20 from ctypes import c_int
     21 from time import sleep, strftime
     22 from sys import argv
     23 
     24 def usage():
     25     print("USAGE: %s [interval [count]]" % argv[0])
     26     exit()
     27 
     28 # arguments
     29 interval = 1
     30 count = -1
     31 if len(argv) > 1:
     32     try:
     33         interval = int(argv[1])
     34         if interval == 0:
     35             raise
     36         if len(argv) > 2:
     37             count = int(argv[2])
     38     except:  # also catches -h, --help
     39         usage()
     40 
     41 # define BPF program
     42 bpf_text = """
     43 #include <uapi/linux/ptrace.h>
     44 
     45 enum stats {
     46     S_REFS = 1,
     47     S_SLOW,
     48     S_MISS,
     49     S_MAXSTAT
     50 };
     51 
     52 BPF_ARRAY(stats, u64, S_MAXSTAT);
     53 
     54 /*
     55  * How this is instrumented, and how to interpret the statistics, is very much
     56  * tied to the current kernel implementation (this was written on Linux 4.4).
     57  * This will need maintenance to keep working as the implementation changes. To
     58  * aid future adventurers, this is is what the current code does, and why.
     59  *
     60  * First problem: the current implementation takes a path and then does a
     61  * lookup of each component. So how do we count a reference? Once for the path
     62  * lookup, or once for every component lookup? I've chosen the latter
     63  * since it seems to map more closely to actual dcache lookups (via
     64  * __d_lookup_rcu()). It's counted via calls to lookup_fast().
     65  *
     66  * The implementation tries different, progressively slower, approaches to
     67  * lookup a file. At what point do we call it a dcache miss? I've chosen when
     68  * a d_lookup() (which is called during lookup_slow()) returns zero.
     69  *
     70  * I've also included a "SLOW" statistic to show how often the fast lookup
     71  * failed. Whether this exists or is interesting is an implementation detail,
     72  * and the "SLOW" statistic may be removed in future versions.
     73  */
     74 void count_fast(struct pt_regs *ctx) {
     75     int key = S_REFS;
     76     u64 *leaf = stats.lookup(&key);
     77     if (leaf) (*leaf)++;
     78 }
     79 
     80 void count_lookup(struct pt_regs *ctx) {
     81     int key = S_SLOW;
     82     u64 *leaf = stats.lookup(&key);
     83     if (leaf) (*leaf)++;
     84     if (PT_REGS_RC(ctx) == 0) {
     85         key = S_MISS;
     86         leaf = stats.lookup(&key);
     87         if (leaf) (*leaf)++;
     88     }
     89 }
     90 """
     91 
     92 # load BPF program
     93 b = BPF(text=bpf_text)
     94 b.attach_kprobe(event="lookup_fast", fn_name="count_fast")
     95 b.attach_kretprobe(event="d_lookup", fn_name="count_lookup")
     96 
     97 # stat column labels and indexes
     98 stats = {
     99     "REFS": 1,
    100     "SLOW": 2,
    101     "MISS": 3
    102 }
    103 
    104 # header
    105 print("%-8s  " % "TIME", end="")
    106 for stype, idx in sorted(stats.items(), key=lambda k_v: (k_v[1], k_v[0])):
    107     print(" %8s" % (stype + "/s"), end="")
    108 print(" %8s" % "HIT%")
    109 
    110 # output
    111 i = 0
    112 while (1):
    113     if count > 0:
    114         i += 1
    115         if i > count:
    116             exit()
    117     try:
    118         sleep(interval)
    119     except KeyboardInterrupt:
    120         pass
    121         exit()
    122 
    123     print("%-8s: " % strftime("%H:%M:%S"), end="")
    124 
    125     # print each statistic as a column
    126     for stype, idx in sorted(stats.items(), key=lambda k_v: (k_v[1], k_v[0])):
    127         try:
    128             val = b["stats"][c_int(idx)].value / interval
    129             print(" %8d" % val, end="")
    130         except:
    131             print(" %8d" % 0, end="")
    132 
    133     # print hit ratio percentage
    134     try:
    135         ref = b["stats"][c_int(stats["REFS"])].value
    136         miss = b["stats"][c_int(stats["MISS"])].value
    137         hit = ref - miss
    138         pct = float(100) * hit / ref
    139         print(" %8.2f" % pct)
    140     except:
    141         print(" %7s%%" % "-")
    142 
    143     b["stats"].clear()
    144