xref: /aosp_15_r20/external/bcc/tools/lib/uobjnew.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# uobjnew  Summarize object allocations in high-level languages.
5#          For Linux, uses BCC, eBPF.
6#
7# USAGE: uobjnew [-h] [-T TOP] [-v] {c,java,ruby,tcl} pid [interval]
8#
9# Copyright 2016 Sasha Goldshtein
10# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 25-Oct-2016   Sasha Goldshtein   Created this.
13
14from __future__ import print_function
15import argparse
16from bcc import BPF, USDT, utils
17from time import sleep
18import os
19
20# C needs to be the last language.
21languages = ["c", "java", "ruby", "tcl"]
22
23examples = """examples:
24    ./uobjnew -l java 145         # summarize Java allocations in process 145
25    ./uobjnew -l c 2020 1         # grab malloc() sizes and print every second
26    ./uobjnew -l ruby 6712 -C 10  # top 10 Ruby types by number of allocations
27    ./uobjnew -l ruby 6712 -S 10  # top 10 Ruby types by total size
28"""
29parser = argparse.ArgumentParser(
30    description="Summarize object allocations in high-level languages.",
31    formatter_class=argparse.RawDescriptionHelpFormatter,
32    epilog=examples)
33parser.add_argument("-l", "--language", choices=languages,
34    help="language to trace")
35parser.add_argument("pid", type=int, help="process id to attach to")
36parser.add_argument("interval", type=int, nargs='?',
37    help="print every specified number of seconds")
38parser.add_argument("-C", "--top-count", type=int,
39    help="number of most frequently allocated types to print")
40parser.add_argument("-S", "--top-size", type=int,
41    help="number of largest types by allocated bytes to print")
42parser.add_argument("-v", "--verbose", action="store_true",
43    help="verbose mode: print the BPF program (for debugging purposes)")
44parser.add_argument("--ebpf", action="store_true",
45    help=argparse.SUPPRESS)
46args = parser.parse_args()
47
48language = args.language
49if not language:
50    language = utils.detect_language(languages, args.pid)
51
52program = """
53#include <linux/ptrace.h>
54
55struct key_t {
56#if MALLOC_TRACING
57    u64 size;
58#else
59    char name[50];
60#endif
61};
62
63struct val_t {
64    u64 total_size;
65    u64 num_allocs;
66};
67
68BPF_HASH(allocs, struct key_t, struct val_t);
69""".replace("MALLOC_TRACING", "1" if language == "c" else "0")
70
71usdt = USDT(pid=args.pid)
72
73#
74# C
75#
76if language == "c":
77    program += """
78int alloc_entry(struct pt_regs *ctx, size_t size) {
79    struct key_t key = {};
80    struct val_t *valp, zero = {};
81    key.size = size;
82    valp = allocs.lookup_or_try_init(&key, &zero);
83    if (valp) {
84        valp->total_size += size;
85        valp->num_allocs += 1;
86    }
87    return 0;
88}
89    """
90#
91# Java
92#
93elif language == "java":
94    program += """
95int alloc_entry(struct pt_regs *ctx) {
96    struct key_t key = {};
97    struct val_t *valp, zero = {};
98    u64 classptr = 0, size = 0;
99    u32 length = 0;
100    bpf_usdt_readarg(2, ctx, &classptr);
101    bpf_usdt_readarg(3, ctx, &length);
102    bpf_usdt_readarg(4, ctx, &size);
103    bpf_probe_read_user(&key.name, min(sizeof(key.name), (size_t)length), (void *)classptr);
104    valp = allocs.lookup_or_try_init(&key, &zero);
105    if (valp) {
106        valp->total_size += size;
107        valp->num_allocs += 1;
108    }
109    return 0;
110}
111    """
112    usdt.enable_probe_or_bail("object__alloc", "alloc_entry")
113#
114# Ruby
115#
116elif language == "ruby":
117    create_template = """
118int THETHING_alloc_entry(struct pt_regs *ctx) {
119    struct key_t key = { .name = "THETHING" };
120    struct val_t *valp, zero = {};
121    u64 size = 0;
122    bpf_usdt_readarg(1, ctx, &size);
123    valp = allocs.lookup_or_try_init(&key, &zero);
124    if (valp) {
125        valp->total_size += size;
126        valp->num_allocs += 1;
127    }
128    return 0;
129}
130    """
131    program += """
132int object_alloc_entry(struct pt_regs *ctx) {
133    struct key_t key = {};
134    struct val_t *valp, zero = {};
135    u64 classptr = 0;
136    bpf_usdt_readarg(1, ctx, &classptr);
137    bpf_probe_read_user(&key.name, sizeof(key.name), (void *)classptr);
138    valp = allocs.lookup_or_try_init(&key, &zero);
139    if (valp) {
140        valp->num_allocs += 1;  // We don't know the size, unfortunately
141    }
142    return 0;
143}
144    """
145    usdt.enable_probe_or_bail("object__create", "object_alloc_entry")
146    for thing in ["string", "hash", "array"]:
147        program += create_template.replace("THETHING", thing)
148        usdt.enable_probe_or_bail("%s__create" % thing,
149                                  "%s_alloc_entry" % thing)
150#
151# Tcl
152#
153elif language == "tcl":
154    program += """
155int alloc_entry(struct pt_regs *ctx) {
156    struct key_t key = { .name = "<ALL>" };
157    struct val_t *valp, zero = {};
158    valp = allocs.lookup_or_try_init(&key, &zero);
159    if (valp) {
160        valp->num_allocs += 1;
161    }
162    return 0;
163}
164    """
165    usdt.enable_probe_or_bail("obj__create", "alloc_entry")
166else:
167    print("No language detected; use -l to trace a language.")
168    exit(1)
169
170
171if args.ebpf or args.verbose:
172    if args.verbose:
173        print(usdt.get_text())
174    print(program)
175    if args.ebpf:
176        exit()
177
178bpf = BPF(text=program, usdt_contexts=[usdt])
179if language == "c":
180    bpf.attach_uprobe(name="c", sym="malloc", fn_name="alloc_entry",
181                      pid=args.pid)
182
183exit_signaled = False
184print("Tracing allocations in process %d (language: %s)... Ctrl-C to quit." %
185      (args.pid, language or "none"))
186while True:
187    try:
188        sleep(args.interval or 99999999)
189    except KeyboardInterrupt:
190        exit_signaled = True
191    print()
192    data = bpf["allocs"]
193    if args.top_count:
194        data = sorted(data.items(), key=lambda kv: kv[1].num_allocs)
195        data = data[-args.top_count:]
196    elif args.top_size:
197        data = sorted(data.items(), key=lambda kv: kv[1].total_size)
198        data = data[-args.top_size:]
199    else:
200        data = sorted(data.items(), key=lambda kv: kv[1].total_size)
201    print("%-30s %8s %12s" % ("NAME/TYPE", "# ALLOCS", "# BYTES"))
202    for key, value in data:
203        if language == "c":
204            obj_type = "block size %d" % key.size
205        else:
206            obj_type = key.name
207        print("%-30s %8d %12d" %
208              (obj_type, value.num_allocs, value.total_size))
209    if args.interval and not exit_signaled:
210        bpf["allocs"].clear()
211    else:
212        exit()
213