1#!/usr/bin/env python 2# @lint-avoid-python-3-compatibility-imports 3# 4# slabratetop Summarize kmem_cache_alloc() calls. 5# For Linux, uses BCC, eBPF. 6# 7# USAGE: slabratetop [-h] [-C] [-r MAXROWS] [interval] [count] 8# 9# This uses in-kernel BPF maps to store cache summaries for efficiency. 10# 11# SEE ALSO: slabtop(1), which shows the cache volumes. 12# 13# Copyright 2016 Netflix, Inc. 14# Licensed under the Apache License, Version 2.0 (the "License") 15# 16# 15-Oct-2016 Brendan Gregg Created this. 17# 23-Jan-2023 Rong Tao Introduce kernel internal data structure and 18# functions to temporarily solve problem for 19# >=5.16(TODO: fix this workaround) 20 21from __future__ import print_function 22from bcc import BPF 23from bcc.utils import printb 24from time import sleep, strftime 25import argparse 26from subprocess import call 27 28# arguments 29examples = """examples: 30 ./slabratetop # kmem_cache_alloc() top, 1 second refresh 31 ./slabratetop -C # don't clear the screen 32 ./slabratetop 5 # 5 second summaries 33 ./slabratetop 5 10 # 5 second summaries, 10 times only 34""" 35parser = argparse.ArgumentParser( 36 description="Kernel SLAB/SLUB memory cache allocation rate top", 37 formatter_class=argparse.RawDescriptionHelpFormatter, 38 epilog=examples) 39parser.add_argument("-C", "--noclear", action="store_true", 40 help="don't clear the screen") 41parser.add_argument("-r", "--maxrows", default=20, 42 help="maximum rows to print, default 20") 43parser.add_argument("interval", nargs="?", default=1, 44 help="output interval, in seconds") 45parser.add_argument("count", nargs="?", default=99999999, 46 help="number of outputs") 47parser.add_argument("--ebpf", action="store_true", 48 help=argparse.SUPPRESS) 49args = parser.parse_args() 50interval = int(args.interval) 51countdown = int(args.count) 52maxrows = int(args.maxrows) 53clear = not int(args.noclear) 54debug = 0 55 56# linux stats 57loadavg = "/proc/loadavg" 58 59# define BPF program 60bpf_text = """ 61#include <uapi/linux/ptrace.h> 62#include <linux/mm.h> 63#include <linux/kasan.h> 64 65// memcg_cache_params is a part of kmem_cache, but is not publicly exposed in 66// kernel versions 5.4 to 5.8. Define an empty struct for it here to allow the 67// bpf program to compile. It has been completely removed in kernel version 68// 5.9, but it does not hurt to have it here for versions 5.4 to 5.8. 69struct memcg_cache_params {}; 70 71// introduce kernel interval slab structure and slab_address() function, solved 72// 'undefined' error for >=5.16. TODO: we should fix this workaround if BCC 73// framework support BTF/CO-RE. 74struct slab { 75 unsigned long __page_flags; 76 77#if defined(CONFIG_SLAB) 78 79 struct kmem_cache *slab_cache; 80 union { 81 struct { 82 struct list_head slab_list; 83 void *freelist; /* array of free object indexes */ 84 void *s_mem; /* first object */ 85 }; 86 struct rcu_head rcu_head; 87 }; 88 unsigned int active; 89 90#elif defined(CONFIG_SLUB) 91 92 struct kmem_cache *slab_cache; 93 union { 94 struct { 95 union { 96 struct list_head slab_list; 97#ifdef CONFIG_SLUB_CPU_PARTIAL 98 struct { 99 struct slab *next; 100 int slabs; /* Nr of slabs left */ 101 }; 102#endif 103 }; 104 /* Double-word boundary */ 105 void *freelist; /* first free object */ 106 union { 107 unsigned long counters; 108 struct { 109 unsigned inuse:16; 110 unsigned objects:15; 111 unsigned frozen:1; 112 }; 113 }; 114 }; 115 struct rcu_head rcu_head; 116 }; 117 unsigned int __unused; 118 119#elif defined(CONFIG_SLOB) 120 121 struct list_head slab_list; 122 void *__unused_1; 123 void *freelist; /* first free block */ 124 long units; 125 unsigned int __unused_2; 126 127#else 128#error "Unexpected slab allocator configured" 129#endif 130 131 atomic_t __page_refcount; 132#ifdef CONFIG_MEMCG 133 unsigned long memcg_data; 134#endif 135}; 136 137// slab_address() will not be used, and NULL will be returned directly, which 138// can avoid adaptation of different kernel versions 139static inline void *slab_address(const struct slab *slab) 140{ 141 return NULL; 142} 143 144#ifdef CONFIG_64BIT 145typedef __uint128_t freelist_full_t; 146#else 147typedef u64 freelist_full_t; 148#endif 149 150typedef union { 151 struct { 152 void *freelist; 153 unsigned long counter; 154 }; 155 freelist_full_t full; 156} freelist_aba_t; 157 158#ifdef CONFIG_SLUB 159#include <linux/slub_def.h> 160#else 161#include <linux/slab_def.h> 162#endif 163 164#define CACHE_NAME_SIZE 32 165 166// the key for the output summary 167struct info_t { 168 char name[CACHE_NAME_SIZE]; 169}; 170 171// the value of the output summary 172struct val_t { 173 u64 count; 174 u64 size; 175}; 176 177BPF_HASH(counts, struct info_t, struct val_t); 178 179int kprobe__kmem_cache_alloc(struct pt_regs *ctx, struct kmem_cache *cachep) 180{ 181 struct info_t info = {}; 182 const char *name = cachep->name; 183 bpf_probe_read_kernel(&info.name, sizeof(info.name), name); 184 185 struct val_t *valp, zero = {}; 186 valp = counts.lookup_or_try_init(&info, &zero); 187 if (valp) { 188 valp->count++; 189 valp->size += cachep->size; 190 } 191 192 return 0; 193} 194""" 195if debug or args.ebpf: 196 print(bpf_text) 197 if args.ebpf: 198 exit() 199 200# initialize BPF 201b = BPF(text=bpf_text) 202 203print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval) 204 205# output 206exiting = 0 207while 1: 208 try: 209 sleep(interval) 210 except KeyboardInterrupt: 211 exiting = 1 212 213 # header 214 if clear: 215 call("clear") 216 else: 217 print() 218 with open(loadavg) as stats: 219 print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read())) 220 print("%-32s %6s %10s" % ("CACHE", "ALLOCS", "BYTES")) 221 222 # by-TID output 223 counts = b.get_table("counts") 224 line = 0 225 for k, v in reversed(sorted(counts.items(), 226 key=lambda counts: counts[1].size)): 227 printb(b"%-32s %6d %10d" % (k.name, v.count, v.size)) 228 229 line += 1 230 if line >= maxrows: 231 break 232 counts.clear() 233 234 countdown -= 1 235 if exiting or countdown == 0: 236 print("Detaching...") 237 exit() 238