blob: 573c697b6998b30aec95853e8f98dc9a7131bc67 [file] [log] [blame]
#!/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()