blob: 633f970bd6cbefc5e663f075b48db4bcb3d65eea [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);
zcy80242fb2021-07-02 00:12:32 +080084 counts.atomic_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")
Yonghong Song61087b92021-11-08 11:12:37 -080099
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")
unixtest57abe5b2016-01-31 10:47:03 +0000108b.attach_kprobe(event="mark_buffer_dirty", fn_name="do_count")
109
110# header
111if tstamp:
112 print("%-8s " % "TIME", end="")
Joel Fernandes2b348702018-02-27 20:38:42 -0800113print("%8s %8s %8s %8s %12s %10s" %
Brendan Gregg6af7b842019-02-02 12:45:23 -0800114 ("HITS", "MISSES", "DIRTIES", "HITRATIO", "BUFFERS_MB", "CACHED_MB"))
unixtest57abe5b2016-01-31 10:47:03 +0000115
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
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300131 counts = b["counts"]
unixtest57abe5b2016-01-31 10:47:03 +0000132 for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
Brendan Gregg6af7b842019-02-02 12:45:23 -0800133 func = b.ksym(k.ip)
134 # partial string matches in case of .isra (necessary?)
10ne10267b482019-03-26 03:07:40 +0200135 if func.find(b"mark_page_accessed") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700136 mpa = max(0, v.value)
10ne10267b482019-03-26 03:07:40 +0200137 if func.find(b"mark_buffer_dirty") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700138 mbd = max(0, v.value)
10ne10267b482019-03-26 03:07:40 +0200139 if func.find(b"add_to_page_cache_lru") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700140 apcl = max(0, v.value)
10ne10267b482019-03-26 03:07:40 +0200141 if func.find(b"account_page_dirtied") == 0:
chantraa2d669c2016-07-29 14:10:15 -0700142 apd = max(0, v.value)
unixtest57abe5b2016-01-31 10:47:03 +0000143
Brendan Gregg6af7b842019-02-02 12:45:23 -0800144 # 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
unixtest57abe5b2016-01-31 10:47:03 +0000153
Brendan Gregg6af7b842019-02-02 12:45:23 -0800154 # 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
unixtest57abe5b2016-01-31 10:47:03 +0000163
164 if debug:
Joel Fernandes2b348702018-02-27 20:38:42 -0800165 print("%d %d %d %d %d %d %d\n" %
166 (mpa, mbd, apcl, apd, total, misses, hits))
unixtest57abe5b2016-01-31 10:47:03 +0000167
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
Marko Myllynen27e7aea2018-09-26 20:09:07 +0300175 if tstamp:
unixtest57abe5b2016-01-31 10:47:03 +0000176 print("%-8s " % strftime("%H:%M:%S"), end="")
Brendan Gregg6af7b842019-02-02 12:45:23 -0800177 print("%8d %8d %8d %7.2f%% %12.0f %10.0f" %
178 (hits, misses, mbd, 100 * ratio, buff, cached))
unixtest57abe5b2016-01-31 10:47:03 +0000179
Brendan Gregg6af7b842019-02-02 12:45:23 -0800180 mpa = mbd = apcl = apd = total = misses = hits = cached = buff = 0
unixtest57abe5b2016-01-31 10:47:03 +0000181
182 if exiting:
183 print("Detaching...")
184 exit()