| #!/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 |
| |
| from __future__ import print_function |
| from bcc import BPF |
| from time import sleep, strftime |
| 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 |
| |
| # args |
| def usage(): |
| print("USAGE: %s [-T] [ interval [count] ]" % argv[0]) |
| exit() |
| |
| # arguments |
| interval = 5 |
| count = -1 |
| tstamp = 0 |
| |
| if len(argv) > 1: |
| if str(argv[1]) == '-T': |
| tstamp = 1 |
| |
| if len(argv) > 1 and tstamp == 0: |
| try: |
| if int(argv[1]) > 0: |
| interval = int(argv[1]) |
| if len(argv) > 2: |
| if int(argv[2]) > 0: |
| count = int(argv[2]) |
| except: |
| usage() |
| |
| elif len(argv) > 2 and tstamp == 1: |
| try: |
| if int(argv[2]) > 0: |
| interval = int(argv[2]) |
| if len(argv) >= 4: |
| if int(argv[3]) > 0: |
| count = int(argv[3]) |
| except: |
| usage() |
| |
| # load BPF program |
| bpf_text = """ |
| |
| #include <uapi/linux/ptrace.h> |
| 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.increment(key); // update counter |
| return 0; |
| } |
| |
| """ |
| 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" % |
| ("TOTAL", "MISSES", "HITS", "DIRTIES", "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.get_table("counts") |
| for k, v in sorted(counts.items(), key=lambda counts: counts[1].value): |
| |
| if re.match(b'mark_page_accessed', b.ksym(k.ip)) is not None: |
| mpa = max(0, v.value) |
| |
| if re.match(b'mark_buffer_dirty', b.ksym(k.ip)) is not None: |
| mbd = max(0, v.value) |
| |
| if re.match(b'add_to_page_cache_lru', b.ksym(k.ip)) is not None: |
| apcl = max(0, v.value) |
| |
| if re.match(b'account_page_dirtied', b.ksym(k.ip)) is not None: |
| 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 total < 0: |
| total = 0 |
| |
| if misses < 0: |
| misses = 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 |
| |
| 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 == 1: |
| print("%-8s " % strftime("%H:%M:%S"), end="") |
| print("%8d %8d %8d %8d %12.0f %10.0f" % |
| (total, misses, hits, mbd, buff, cached)) |
| |
| mpa = 0 |
| mbd = 0 |
| apcl = 0 |
| apd = 0 |
| total = 0 |
| misses = 0 |
| hits = 0 |
| cached = 0 |
| buff = 0 |
| |
| if exiting: |
| print("Detaching...") |
| exit() |