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