blob: 119fd9cf65969742f8ee2bfbd2834c25e5a1abbc [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
unixtest57abe5b2016-01-31 10:47:03 +00002#
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
Brendan Gregg6af7b842019-02-02 12:45:23 -080018# 02-Feb-2019 Brendan Gregg Column shuffle, bring back %ratio
unixtest57abe5b2016-01-31 10:47:03 +000019
20from __future__ import print_function
21from bcc import BPF
22from time import sleep, strftime
Marko Myllynen27e7aea2018-09-26 20:09:07 +030023import argparse
unixtest57abe5b2016-01-31 10:47:03 +000024import 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
unixtest57abe5b2016-01-31 10:47:03 +000044mpa = 0
45mbd = 0
46apcl = 0
47apd = 0
Joel Fernandes2b348702018-02-27 20:38:42 -080048total = 0
unixtest57abe5b2016-01-31 10:47:03 +000049misses = 0
Joel Fernandes2b348702018-02-27 20:38:42 -080050hits = 0
unixtest57abe5b2016-01-31 10:47:03 +000051debug = 0
52
unixtest57abe5b2016-01-31 10:47:03 +000053# arguments
Marko Myllynen27e7aea2018-09-26 20:09:07 +030054parser = 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")
Brendan Gregg6af7b842019-02-02 12:45:23 -080059parser.add_argument("interval", nargs="?", default=1,
Marko Myllynen27e7aea2018-09-26 20:09:07 +030060 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)
unixtest57abe5b2016-01-31 10:47:03 +000069
Marko Myllynen27e7aea2018-09-26 20:09:07 +030070# define BPF program
unixtest57abe5b2016-01-31 10:47:03 +000071bpf_text = """
unixtest57abe5b2016-01-31 10:47:03 +000072#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 = {};
unixtest57abe5b2016-01-31 10:47:03 +000081 u64 ip;
82
Naveen N. Rao4afa96a2016-05-03 14:54:21 +053083 key.ip = PT_REGS_IP(ctx);
Javier Honduvilla Coto64bf9652018-08-01 06:50:19 +020084 counts.increment(key); // update counter
unixtest57abe5b2016-01-31 10:47:03 +000085 return 0;
86}
87
88"""
Marko Myllynen27e7aea2018-09-26 20:09:07 +030089
90if debug or args.ebpf:
91 print(bpf_text)
92 if args.ebpf:
93 exit()
94
95# load BPF program
unixtest57abe5b2016-01-31 10:47:03 +000096b = 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")
99b.attach_kprobe(event="account_page_dirtied", fn_name="do_count")
100b.attach_kprobe(event="mark_buffer_dirty", fn_name="do_count")
101
102# header
103if tstamp:
104 print("%-8s " % "TIME", end="")
Joel Fernandes2b348702018-02-27 20:38:42 -0800105print("%8s %8s %8s %8s %12s %10s" %
Brendan Gregg6af7b842019-02-02 12:45:23 -0800106 ("HITS", "MISSES", "DIRTIES", "HITRATIO", "BUFFERS_MB", "CACHED_MB"))
unixtest57abe5b2016-01-31 10:47:03 +0000107
108loop = 0
109exiting = 0
110while 1:
111 if count > 0:
112 loop += 1
113 if loop > count:
114 exit()
115
116 try:
117 sleep(interval)
118 except KeyboardInterrupt:
119 exiting = 1
120 # as cleanup can take many seconds, trap Ctrl-C:
121 signal.signal(signal.SIGINT, signal_ignore)
122
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300123 counts = b["counts"]
unixtest57abe5b2016-01-31 10:47:03 +0000124 for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
Brendan Gregg6af7b842019-02-02 12:45:23 -0800125 func = b.ksym(k.ip)
126 # partial string matches in case of .isra (necessary?)
127 if func.find("mark_page_accessed") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700128 mpa = max(0, v.value)
Brendan Gregg6af7b842019-02-02 12:45:23 -0800129 if func.find("mark_buffer_dirty") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700130 mbd = max(0, v.value)
Brendan Gregg6af7b842019-02-02 12:45:23 -0800131 if func.find("add_to_page_cache_lru") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700132 apcl = max(0, v.value)
Brendan Gregg6af7b842019-02-02 12:45:23 -0800133 if func.find("account_page_dirtied") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700134 apd = max(0, v.value)
unixtest57abe5b2016-01-31 10:47:03 +0000135
Brendan Gregg6af7b842019-02-02 12:45:23 -0800136 # total = total cache accesses without counting dirties
137 # misses = total of add to lru because of read misses
138 total = mpa - mbd
139 misses = apcl - apd
140 if misses < 0:
141 misses = 0
142 if total < 0:
143 total = 0
144 hits = total - misses
unixtest57abe5b2016-01-31 10:47:03 +0000145
Brendan Gregg6af7b842019-02-02 12:45:23 -0800146 # If hits are < 0, then its possible misses are overestimated
147 # due to possibly page cache read ahead adding more pages than
148 # needed. In this case just assume misses as total and reset hits.
149 if hits < 0:
150 misses = total
151 hits = 0
152 ratio = 0
153 if total > 0:
154 ratio = float(hits) / total
unixtest57abe5b2016-01-31 10:47:03 +0000155
156 if debug:
Joel Fernandes2b348702018-02-27 20:38:42 -0800157 print("%d %d %d %d %d %d %d\n" %
158 (mpa, mbd, apcl, apd, total, misses, hits))
unixtest57abe5b2016-01-31 10:47:03 +0000159
160 counts.clear()
161
162 # Get memory info
163 mem = get_meminfo()
164 cached = int(mem["Cached"]) / 1024
165 buff = int(mem["Buffers"]) / 1024
166
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300167 if tstamp:
unixtest57abe5b2016-01-31 10:47:03 +0000168 print("%-8s " % strftime("%H:%M:%S"), end="")
Brendan Gregg6af7b842019-02-02 12:45:23 -0800169 print("%8d %8d %8d %7.2f%% %12.0f %10.0f" %
170 (hits, misses, mbd, 100 * ratio, buff, cached))
unixtest57abe5b2016-01-31 10:47:03 +0000171
Brendan Gregg6af7b842019-02-02 12:45:23 -0800172 mpa = mbd = apcl = apd = total = misses = hits = cached = buff = 0
unixtest57abe5b2016-01-31 10:47:03 +0000173
174 if exiting:
175 print("Detaching...")
176 exit()