1#!/usr/bin/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 20from __future__ import print_function 21from bcc import BPF 22from time import sleep, strftime 23import argparse 24import signal 25import re 26from sys import argv 27 28# signal handler 29def signal_ignore(signal, frame): 30 print() 31 32# Function to gather data from /proc/meminfo 33# return dictionary for quicker lookup of both values 34def get_meminfo(): 35 result = dict() 36 37 for line in open('/proc/meminfo'): 38 k = line.split(':', 3) 39 v = k[1].split() 40 result[k[0]] = int(v[0]) 41 return result 42 43# set global variables 44mpa = 0 45mbd = 0 46apcl = 0 47apd = 0 48total = 0 49misses = 0 50hits = 0 51debug = 0 52 53# arguments 54parser = argparse.ArgumentParser( 55 description="Count cache kernel function calls", 56 formatter_class=argparse.RawDescriptionHelpFormatter) 57parser.add_argument("-T", "--timestamp", action="store_true", 58 help="include timestamp on output") 59parser.add_argument("interval", nargs="?", default=1, 60 help="output interval, in seconds") 61parser.add_argument("count", nargs="?", default=-1, 62 help="number of outputs") 63parser.add_argument("--ebpf", action="store_true", 64 help=argparse.SUPPRESS) 65args = parser.parse_args() 66count = int(args.count) 67tstamp = args.timestamp 68interval = int(args.interval) 69 70# define BPF program 71bpf_text = """ 72#include <uapi/linux/ptrace.h> 73struct key_t { 74 u64 ip; 75}; 76 77BPF_HASH(counts, struct key_t); 78 79int do_count(struct pt_regs *ctx) { 80 struct key_t key = {}; 81 u64 ip; 82 83 key.ip = PT_REGS_IP(ctx); 84 counts.atomic_increment(key); // update counter 85 return 0; 86} 87 88""" 89 90if debug or args.ebpf: 91 print(bpf_text) 92 if args.ebpf: 93 exit() 94 95# load BPF program 96b = BPF(text=bpf_text) 97b.attach_kprobe(event="add_to_page_cache_lru", fn_name="do_count") 98b.attach_kprobe(event="mark_page_accessed", fn_name="do_count") 99 100# Function account_page_dirtied() is changed to folio_account_dirtied() in 5.15. 101# FIXME: Both folio_account_dirtied() and account_page_dirtied() are 102# static functions and they may be gone during compilation and this may 103# introduce some inaccuracy. 104if BPF.get_kprobe_functions(b'folio_account_dirtied'): 105 b.attach_kprobe(event="folio_account_dirtied", fn_name="do_count") 106elif BPF.get_kprobe_functions(b'account_page_dirtied'): 107 b.attach_kprobe(event="account_page_dirtied", fn_name="do_count") 108b.attach_kprobe(event="mark_buffer_dirty", fn_name="do_count") 109 110# header 111if tstamp: 112 print("%-8s " % "TIME", end="") 113print("%8s %8s %8s %8s %12s %10s" % 114 ("HITS", "MISSES", "DIRTIES", "HITRATIO", "BUFFERS_MB", "CACHED_MB")) 115 116loop = 0 117exiting = 0 118while 1: 119 if count > 0: 120 loop += 1 121 if loop > count: 122 exit() 123 124 try: 125 sleep(interval) 126 except KeyboardInterrupt: 127 exiting = 1 128 # as cleanup can take many seconds, trap Ctrl-C: 129 signal.signal(signal.SIGINT, signal_ignore) 130 131 counts = b["counts"] 132 for k, v in sorted(counts.items(), key=lambda counts: counts[1].value): 133 func = b.ksym(k.ip) 134 # partial string matches in case of .isra (necessary?) 135 if func.find(b"mark_page_accessed") == 0: 136 mpa = max(0, v.value) 137 if func.find(b"mark_buffer_dirty") == 0: 138 mbd = max(0, v.value) 139 if func.find(b"add_to_page_cache_lru") == 0: 140 apcl = max(0, v.value) 141 if func.find(b"account_page_dirtied") == 0: 142 apd = max(0, v.value) 143 144 # total = total cache accesses without counting dirties 145 # misses = total of add to lru because of read misses 146 total = mpa - mbd 147 misses = apcl - apd 148 if misses < 0: 149 misses = 0 150 if total < 0: 151 total = 0 152 hits = total - misses 153 154 # If hits are < 0, then its possible misses are overestimated 155 # due to possibly page cache read ahead adding more pages than 156 # needed. In this case just assume misses as total and reset hits. 157 if hits < 0: 158 misses = total 159 hits = 0 160 ratio = 0 161 if total > 0: 162 ratio = float(hits) / total 163 164 if debug: 165 print("%d %d %d %d %d %d %d\n" % 166 (mpa, mbd, apcl, apd, total, misses, hits)) 167 168 counts.clear() 169 170 # Get memory info 171 mem = get_meminfo() 172 cached = int(mem["Cached"]) / 1024 173 buff = int(mem["Buffers"]) / 1024 174 175 if tstamp: 176 print("%-8s " % strftime("%H:%M:%S"), end="") 177 print("%8d %8d %8d %7.2f%% %12.0f %10.0f" % 178 (hits, misses, mbd, 100 * ratio, buff, cached)) 179 180 mpa = mbd = apcl = apd = total = misses = hits = cached = buff = 0 181 182 if exiting: 183 print("Detaching...") 184 exit() 185