#!/usr/bin/python # # cachestat Count cache kernel function calls. # For Linux, uses BCC, eBPF. See .c file. # # USAGE: cachestat # Taken from funccount by Brendan Gregg # This is a rewrite of cachestat from perf to bcc # https://github.com/brendangregg/perf-tools/blob/master/fs/cachestat # # Copyright (c) 2016 Allan McAleavy. # Copyright (c) 2015 Brendan Gregg. # Licensed under the Apache License, Version 2.0 (the "License") # # 09-Sep-2015 Brendan Gregg Created this. # 06-Nov-2015 Allan McAleavy # 13-Jan-2016 Allan McAleavy run pep8 against program # 02-Feb-2019 Brendan Gregg Column shuffle, bring back %ratio from __future__ import print_function from bcc import BPF from time import sleep, strftime import argparse import signal import re from sys import argv # signal handler def signal_ignore(signal, frame): print() # Function to gather data from /proc/meminfo # return dictionary for quicker lookup of both values def get_meminfo(): result = dict() for line in open('/proc/meminfo'): k = line.split(':', 3) v = k[1].split() result[k[0]] = int(v[0]) return result # set global variables mpa = 0 mbd = 0 apcl = 0 apd = 0 total = 0 misses = 0 hits = 0 debug = 0 # arguments parser = argparse.ArgumentParser( description="Count cache kernel function calls", formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") parser.add_argument("interval", nargs="?", default=1, help="output interval, in seconds") parser.add_argument("count", nargs="?", default=-1, help="number of outputs") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) args = parser.parse_args() count = int(args.count) tstamp = args.timestamp interval = int(args.interval) # define BPF program bpf_text = """ #include struct key_t { u64 ip; }; BPF_HASH(counts, struct key_t); int do_count(struct pt_regs *ctx) { struct key_t key = {}; u64 ip; key.ip = PT_REGS_IP(ctx); counts.atomic_increment(key); // update counter return 0; } """ if debug or args.ebpf: print(bpf_text) if args.ebpf: exit() # load BPF program b = BPF(text=bpf_text) b.attach_kprobe(event="add_to_page_cache_lru", fn_name="do_count") b.attach_kprobe(event="mark_page_accessed", fn_name="do_count") b.attach_kprobe(event="account_page_dirtied", fn_name="do_count") b.attach_kprobe(event="mark_buffer_dirty", fn_name="do_count") # header if tstamp: print("%-8s " % "TIME", end="") print("%8s %8s %8s %8s %12s %10s" % ("HITS", "MISSES", "DIRTIES", "HITRATIO", "BUFFERS_MB", "CACHED_MB")) loop = 0 exiting = 0 while 1: if count > 0: loop += 1 if loop > count: exit() try: sleep(interval) except KeyboardInterrupt: exiting = 1 # as cleanup can take many seconds, trap Ctrl-C: signal.signal(signal.SIGINT, signal_ignore) counts = b["counts"] for k, v in sorted(counts.items(), key=lambda counts: counts[1].value): func = b.ksym(k.ip) # partial string matches in case of .isra (necessary?) if func.find(b"mark_page_accessed") == 0: mpa = max(0, v.value) if func.find(b"mark_buffer_dirty") == 0: mbd = max(0, v.value) if func.find(b"add_to_page_cache_lru") == 0: apcl = max(0, v.value) if func.find(b"account_page_dirtied") == 0: apd = max(0, v.value) # total = total cache accesses without counting dirties # misses = total of add to lru because of read misses total = mpa - mbd misses = apcl - apd if misses < 0: misses = 0 if total < 0: total = 0 hits = total - misses # If hits are < 0, then its possible misses are overestimated # due to possibly page cache read ahead adding more pages than # needed. In this case just assume misses as total and reset hits. if hits < 0: misses = total hits = 0 ratio = 0 if total > 0: ratio = float(hits) / total if debug: print("%d %d %d %d %d %d %d\n" % (mpa, mbd, apcl, apd, total, misses, hits)) counts.clear() # Get memory info mem = get_meminfo() cached = int(mem["Cached"]) / 1024 buff = int(mem["Buffers"]) / 1024 if tstamp: print("%-8s " % strftime("%H:%M:%S"), end="") print("%8d %8d %8d %7.2f%% %12.0f %10.0f" % (hits, misses, mbd, 100 * ratio, buff, cached)) mpa = mbd = apcl = apd = total = misses = hits = cached = buff = 0 if exiting: print("Detaching...") exit()