xref: /aosp_15_r20/external/bcc/tools/dcstat.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env 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
18from __future__ import print_function
19from bcc import BPF
20from ctypes import c_int
21from time import sleep, strftime
22from sys import argv
23
24def usage():
25    print("USAGE: %s [interval [count]]" % argv[0])
26    exit()
27
28# arguments
29interval = 1
30count = -1
31if 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
42bpf_text = """
43#include <uapi/linux/ptrace.h>
44
45enum stats {
46    S_REFS = 1,
47    S_SLOW,
48    S_MISS,
49    S_MAXSTAT
50};
51
52BPF_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 */
74void count_fast(struct pt_regs *ctx) {
75    int key = S_REFS;
76    stats.atomic_increment(key);
77}
78
79void count_lookup(struct pt_regs *ctx) {
80    int key = S_SLOW;
81    stats.atomic_increment(key);
82    if (PT_REGS_RC(ctx) == 0) {
83        key = S_MISS;
84        stats.atomic_increment(key);
85    }
86}
87"""
88
89# load BPF program
90b = BPF(text=bpf_text)
91b.attach_kprobe(event_re="^lookup_fast$|^lookup_fast.constprop.*.\d$", fn_name="count_fast")
92b.attach_kretprobe(event="d_lookup", fn_name="count_lookup")
93
94# stat column labels and indexes
95stats = {
96    "REFS": 1,
97    "SLOW": 2,
98    "MISS": 3
99}
100
101# header
102print("%-8s  " % "TIME", end="")
103for stype, idx in sorted(stats.items(), key=lambda k_v: (k_v[1], k_v[0])):
104    print(" %8s" % (stype + "/s"), end="")
105print(" %8s" % "HIT%")
106
107# output
108i = 0
109while (1):
110    if count > 0:
111        i += 1
112        if i > count:
113            exit()
114    try:
115        sleep(interval)
116    except KeyboardInterrupt:
117        exit()
118
119    print("%-8s: " % strftime("%H:%M:%S"), end="")
120
121    # print each statistic as a column
122    for stype, idx in sorted(stats.items(), key=lambda k_v: (k_v[1], k_v[0])):
123        try:
124            val = b["stats"][c_int(idx)].value / interval
125            print(" %8d" % val, end="")
126        except:
127            print(" %8d" % 0, end="")
128
129    # print hit ratio percentage
130    try:
131        ref = b["stats"][c_int(stats["REFS"])].value
132        miss = b["stats"][c_int(stats["MISS"])].value
133        hit = ref - miss
134        pct = float(100) * hit / ref
135        print(" %8.2f" % pct)
136    except:
137        print(" %7s%%" % "-")
138
139    b["stats"].clear()
140