1#!/usr/bin/env python 2# 3# cachestat Count cache kernel function calls. 4# For Linux, uses BCC, eBPF. See .c file. 5# 6# USAGE: cachestat 7# Taken from funccount by Brendan Gregg 8# This is a rewrite of cachestat from perf to bcc 9# https://github.com/brendangregg/perf-tools/blob/master/fs/cachestat 10# 11# Copyright (c) 2016 Allan McAleavy. 12# Copyright (c) 2015 Brendan Gregg. 13# Licensed under the Apache License, Version 2.0 (the "License") 14# 15# 09-Sep-2015 Brendan Gregg Created this. 16# 06-Nov-2015 Allan McAleavy 17# 13-Jan-2016 Allan McAleavy run pep8 against program 18# 02-Feb-2019 Brendan Gregg Column shuffle, bring back %ratio 19# 15-Feb-2023 Rong Tao Add writeback_dirty_{folio,page} tracepoints 20 21from __future__ import print_function 22from bcc import BPF 23from time import sleep, strftime 24import argparse 25import signal 26import re 27from sys import argv 28 29# signal handler 30def signal_ignore(signal, frame): 31 print() 32 33# Function to gather data from /proc/meminfo 34# return dictionary for quicker lookup of both values 35def get_meminfo(): 36 result = dict() 37 38 for line in open('/proc/meminfo'): 39 k = line.split(':', 3) 40 v = k[1].split() 41 result[k[0]] = int(v[0]) 42 return result 43 44# set global variables 45mpa = 0 46mbd = 0 47apcl = 0 48apd = 0 49total = 0 50misses = 0 51hits = 0 52debug = 0 53 54# arguments 55parser = argparse.ArgumentParser( 56 description="Count cache kernel function calls", 57 formatter_class=argparse.RawDescriptionHelpFormatter) 58parser.add_argument("-T", "--timestamp", action="store_true", 59 help="include timestamp on output") 60parser.add_argument("interval", nargs="?", default=1, 61 help="output interval, in seconds") 62parser.add_argument("count", nargs="?", default=-1, 63 help="number of outputs") 64parser.add_argument("--ebpf", action="store_true", 65 help=argparse.SUPPRESS) 66args = parser.parse_args() 67count = int(args.count) 68tstamp = args.timestamp 69interval = int(args.interval) 70 71# define BPF program 72bpf_text = """ 73#include <uapi/linux/ptrace.h> 74struct key_t { 75 // NF_{APCL,MPA,MBD,APD} 76 u32 nf; 77}; 78 79enum { 80 NF_APCL, 81 NF_MPA, 82 NF_MBD, 83 NF_APD, 84}; 85 86BPF_HASH(counts, struct key_t); 87 88static int __do_count(void *ctx, u32 nf) { 89 struct key_t key = {}; 90 u64 ip; 91 92 key.nf = nf; 93 counts.atomic_increment(key); // update counter 94 return 0; 95} 96 97int do_count_apcl(struct pt_regs *ctx) { 98 return __do_count(ctx, NF_APCL); 99} 100int do_count_mpa(struct pt_regs *ctx) { 101 return __do_count(ctx, NF_MPA); 102} 103int do_count_mbd(struct pt_regs *ctx) { 104 return __do_count(ctx, NF_MBD); 105} 106int do_count_apd(struct pt_regs *ctx) { 107 return __do_count(ctx, NF_APD); 108} 109int do_count_apd_tp(void *ctx) { 110 return __do_count(ctx, NF_APD); 111} 112""" 113 114if debug or args.ebpf: 115 print(bpf_text) 116 if args.ebpf: 117 exit() 118 119# load BPF program 120b = BPF(text=bpf_text) 121b.attach_kprobe(event="add_to_page_cache_lru", fn_name="do_count_apcl") 122b.attach_kprobe(event="mark_page_accessed", fn_name="do_count_mpa") 123 124# Function account_page_dirtied() is changed to folio_account_dirtied() in 5.15. 125# Both folio_account_dirtied() and account_page_dirtied() are 126# static functions and they may be gone during compilation and this may 127# introduce some inaccuracy, use tracepoint writeback_dirty_{page,folio}, 128# instead when attaching kprobe fails, and report the running 129# error in time. 130if BPF.get_kprobe_functions(b'folio_account_dirtied'): 131 b.attach_kprobe(event="folio_account_dirtied", fn_name="do_count_apd") 132elif BPF.get_kprobe_functions(b'account_page_dirtied'): 133 b.attach_kprobe(event="account_page_dirtied", fn_name="do_count_apd") 134elif BPF.tracepoint_exists("writeback", "writeback_dirty_folio"): 135 b.attach_tracepoint(tp="writeback:writeback_dirty_folio", fn_name="do_count_apd_tp") 136elif BPF.tracepoint_exists("writeback", "writeback_dirty_page"): 137 b.attach_tracepoint(tp="writeback:writeback_dirty_page", fn_name="do_count_apd_tp") 138else: 139 raise Exception("Failed to attach kprobe %s or %s or any tracepoint" % 140 ("folio_account_dirtied", "account_page_dirtied")) 141b.attach_kprobe(event="mark_buffer_dirty", fn_name="do_count_mbd") 142 143# header 144if tstamp: 145 print("%-8s " % "TIME", end="") 146print("%8s %8s %8s %8s %12s %10s" % 147 ("HITS", "MISSES", "DIRTIES", "HITRATIO", "BUFFERS_MB", "CACHED_MB")) 148 149loop = 0 150exiting = 0 151while 1: 152 if count > 0: 153 loop += 1 154 if loop > count: 155 exit() 156 157 try: 158 sleep(interval) 159 except KeyboardInterrupt: 160 exiting = 1 161 # as cleanup can take many seconds, trap Ctrl-C: 162 signal.signal(signal.SIGINT, signal_ignore) 163 164 counts = b["counts"] 165 for k, v in sorted(counts.items(), key=lambda counts: counts[1].value): 166 # partial string matches in case of .isra (necessary?) 167 if k.nf == 0: # NF_APCL 168 apcl = max(0, v.value) 169 if k.nf == 1: # NF_MPA 170 mpa = max(0, v.value) 171 if k.nf == 2: # NF_MBD 172 mbd = max(0, v.value) 173 if k.nf == 3: # NF_APD 174 apd = max(0, v.value) 175 176 # total = total cache accesses without counting dirties 177 # misses = total of add to lru because of read misses 178 total = mpa - mbd 179 misses = apcl - apd 180 if misses < 0: 181 misses = 0 182 if total < 0: 183 total = 0 184 hits = total - misses 185 186 # If hits are < 0, then its possible misses are overestimated 187 # due to possibly page cache read ahead adding more pages than 188 # needed. In this case just assume misses as total and reset hits. 189 if hits < 0: 190 misses = total 191 hits = 0 192 ratio = 0 193 if total > 0: 194 ratio = float(hits) / total 195 196 if debug: 197 print("%d %d %d %d %d %d %d\n" % 198 (mpa, mbd, apcl, apd, total, misses, hits)) 199 200 counts.clear() 201 202 # Get memory info 203 mem = get_meminfo() 204 cached = int(mem["Cached"]) / 1024 205 buff = int(mem["Buffers"]) / 1024 206 207 if tstamp: 208 print("%-8s " % strftime("%H:%M:%S"), end="") 209 print("%8d %8d %8d %7.2f%% %12.0f %10.0f" % 210 (hits, misses, mbd, 100 * ratio, buff, cached)) 211 212 mpa = mbd = apcl = apd = total = misses = hits = cached = buff = 0 213 214 if exiting: 215 print("Detaching...") 216 exit() 217