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 u64 *leaf = stats.lookup(&key); 77 if (leaf) (*leaf)++; 78} 79 80void 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 93b = BPF(text=bpf_text) 94b.attach_kprobe(event="lookup_fast", fn_name="count_fast") 95b.attach_kretprobe(event="d_lookup", fn_name="count_lookup") 96 97# stat column labels and indexes 98stats = { 99 "REFS": 1, 100 "SLOW": 2, 101 "MISS": 3 102} 103 104# header 105print("%-8s " % "TIME", end="") 106for stype, idx in sorted(stats.items(), key=lambda k_v: (k_v[1], k_v[0])): 107 print(" %8s" % (stype + "/s"), end="") 108print(" %8s" % "HIT%") 109 110# output 111i = 0 112while (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