1#!/usr/bin/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
18from __future__ import print_function
19from bcc import BPF
20from bcc.utils import printb
21from time import sleep, strftime
22import argparse
23import signal
24from subprocess import call
25
26# arguments
27examples = """examples:
28    ./slabratetop            # kmem_cache_alloc() top, 1 second refresh
29    ./slabratetop -C         # don't clear the screen
30    ./slabratetop 5          # 5 second summaries
31    ./slabratetop 5 10       # 5 second summaries, 10 times only
32"""
33parser = argparse.ArgumentParser(
34    description="Kernel SLAB/SLUB memory cache allocation rate top",
35    formatter_class=argparse.RawDescriptionHelpFormatter,
36    epilog=examples)
37parser.add_argument("-C", "--noclear", action="store_true",
38    help="don't clear the screen")
39parser.add_argument("-r", "--maxrows", default=20,
40    help="maximum rows to print, default 20")
41parser.add_argument("interval", nargs="?", default=1,
42    help="output interval, in seconds")
43parser.add_argument("count", nargs="?", default=99999999,
44    help="number of outputs")
45parser.add_argument("--ebpf", action="store_true",
46    help=argparse.SUPPRESS)
47args = parser.parse_args()
48interval = int(args.interval)
49countdown = int(args.count)
50maxrows = int(args.maxrows)
51clear = not int(args.noclear)
52debug = 0
53
54# linux stats
55loadavg = "/proc/loadavg"
56
57# signal handler
58def signal_ignore(signal, frame):
59    print()
60
61# define BPF program
62bpf_text = """
63#include <uapi/linux/ptrace.h>
64#include <linux/mm.h>
65#include <linux/slab.h>
66#ifdef CONFIG_SLUB
67#include <linux/slub_def.h>
68#else
69#include <linux/slab_def.h>
70#endif
71
72#define CACHE_NAME_SIZE 32
73
74// the key for the output summary
75struct info_t {
76    char name[CACHE_NAME_SIZE];
77};
78
79// the value of the output summary
80struct val_t {
81    u64 count;
82    u64 size;
83};
84
85BPF_HASH(counts, struct info_t, struct val_t);
86
87int kprobe__kmem_cache_alloc(struct pt_regs *ctx, struct kmem_cache *cachep)
88{
89    struct info_t info = {};
90    const char *name = cachep->name;
91    bpf_probe_read(&info.name, sizeof(info.name), name);
92
93    struct val_t *valp, zero = {};
94    valp = counts.lookup_or_init(&info, &zero);
95    valp->count++;
96    valp->size += cachep->size;
97
98    return 0;
99}
100"""
101if debug or args.ebpf:
102    print(bpf_text)
103    if args.ebpf:
104        exit()
105
106# initialize BPF
107b = BPF(text=bpf_text)
108
109print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
110
111# output
112exiting = 0
113while 1:
114    try:
115        sleep(interval)
116    except KeyboardInterrupt:
117        exiting = 1
118
119    # header
120    if clear:
121        call("clear")
122    else:
123        print()
124    with open(loadavg) as stats:
125        print("%-8s loadavg: %s" % (strftime("%H:%M:%S"), stats.read()))
126    print("%-32s %6s %10s" % ("CACHE", "ALLOCS", "BYTES"))
127
128    # by-TID output
129    counts = b.get_table("counts")
130    line = 0
131    for k, v in reversed(sorted(counts.items(),
132                                key=lambda counts: counts[1].size)):
133        printb(b"%-32s %6d %10d" % (k.name, v.count, v.size))
134
135        line += 1
136        if line >= maxrows:
137            break
138    counts.clear()
139
140    countdown -= 1
141    if exiting or countdown == 0:
142        print("Detaching...")
143        exit()
144