blob: 81ed0896d8a64bcb926f6d071a4fb49384c59194 [file] [log] [blame]
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -07001#!/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] {java,ruby,c} 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
17from time import sleep
18
19examples = """examples:
Sasha Goldshteind4a42482016-12-19 09:54:31 +000020 ./uobjnew java 145 # summarize Java allocations in process 145
21 ./uobjnew c 2020 1 # grab malloc() sizes and print every second
22 ./uobjnew ruby 6712 -C 10 # top 10 Ruby types by number of allocations
23 ./uobjnew ruby 6712 -S 10 # top 10 Ruby types by total size
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070024"""
25parser = argparse.ArgumentParser(
26 description="Summarize object allocations in high-level languages.",
27 formatter_class=argparse.RawDescriptionHelpFormatter,
28 epilog=examples)
29parser.add_argument("language", choices=["java", "ruby", "c"],
30 help="language to trace")
31parser.add_argument("pid", type=int, help="process id to attach to")
32parser.add_argument("interval", type=int, nargs='?',
33 help="print every specified number of seconds")
34parser.add_argument("-C", "--top-count", type=int,
35 help="number of most frequently allocated types to print")
36parser.add_argument("-S", "--top-size", type=int,
37 help="number of largest types by allocated bytes to print")
38parser.add_argument("-v", "--verbose", action="store_true",
39 help="verbose mode: print the BPF program (for debugging purposes)")
40args = parser.parse_args()
41
42program = """
43#include <linux/ptrace.h>
44
45struct key_t {
46#if MALLOC_TRACING
47 u64 size;
48#else
49 char name[50];
50#endif
51};
52
53struct val_t {
54 u64 total_size;
55 u64 num_allocs;
56};
57
58BPF_HASH(allocs, struct key_t, struct val_t);
59""".replace("MALLOC_TRACING", "1" if args.language == "c" else "0")
60
61usdt = USDT(pid=args.pid)
62
Sasha Goldshteinbee71b22016-10-26 06:34:06 -070063#
64# Java
65#
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070066if args.language == "java":
67 program += """
68int alloc_entry(struct pt_regs *ctx) {
69 struct key_t key = {};
70 struct val_t *valp, zero = {};
71 u64 classptr = 0, size = 0;
72 bpf_usdt_readarg(2, ctx, &classptr);
73 bpf_usdt_readarg(4, ctx, &size);
74 bpf_probe_read(&key.name, sizeof(key.name), (void *)classptr);
75 valp = allocs.lookup_or_init(&key, &zero);
76 valp->total_size += size;
77 valp->num_allocs += 1;
78 return 0;
79}
80 """
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -050081 usdt.enable_probe_or_bail("object__alloc", "alloc_entry")
Sasha Goldshteinbee71b22016-10-26 06:34:06 -070082#
83# Ruby
84#
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070085elif args.language == "ruby":
86 create_template = """
87int THETHING_alloc_entry(struct pt_regs *ctx) {
88 struct key_t key = { .name = "THETHING" };
89 struct val_t *valp, zero = {};
90 u64 size = 0;
91 bpf_usdt_readarg(1, ctx, &size);
92 valp = allocs.lookup_or_init(&key, &zero);
93 valp->total_size += size;
94 valp->num_allocs += 1;
95 return 0;
96}
97 """
98 program += """
99int object_alloc_entry(struct pt_regs *ctx) {
100 struct key_t key = {};
101 struct val_t *valp, zero = {};
102 u64 classptr = 0;
103 bpf_usdt_readarg(1, ctx, &classptr);
104 bpf_probe_read(&key.name, sizeof(key.name), (void *)classptr);
105 valp = allocs.lookup_or_init(&key, &zero);
106 valp->num_allocs += 1; // We don't know the size, unfortunately
107 return 0;
108}
109 """
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500110 usdt.enable_probe_or_bail("object__create", "object_alloc_entry")
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700111 for thing in ["string", "hash", "array"]:
112 program += create_template.replace("THETHING", thing)
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500113 usdt.enable_probe_or_bail("%s__create" % thing, "%s_alloc_entry" % thing)
Sasha Goldshteinbee71b22016-10-26 06:34:06 -0700114#
115# C
116#
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700117elif args.language == "c":
118 program += """
119int alloc_entry(struct pt_regs *ctx, size_t size) {
120 struct key_t key = {};
121 struct val_t *valp, zero = {};
122 key.size = size;
123 valp = allocs.lookup_or_init(&key, &zero);
124 valp->total_size += size;
125 valp->num_allocs += 1;
126 return 0;
127}
128 """
129
130if args.verbose:
131 print(usdt.get_text())
132 print(program)
133
134bpf = BPF(text=program, usdt_contexts=[usdt])
135if args.language == "c":
Sasha Goldshtein39ace6f2016-12-19 09:52:34 +0000136 bpf.attach_uprobe(name="c", sym="malloc", fn_name="alloc_entry",
137 pid=args.pid)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700138
139exit_signaled = False
140print("Tracing allocations in process %d (language: %s)... Ctrl-C to quit." %
141 (args.pid, args.language or "none"))
142while True:
143 try:
144 sleep(args.interval or 99999999)
145 except KeyboardInterrupt:
146 exit_signaled = True
147 print()
148 data = bpf["allocs"]
149 if args.top_count:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100150 data = sorted(data.items(), key=lambda kv: kv[1].num_allocs)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700151 data = data[-args.top_count:]
152 elif args.top_size:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100153 data = sorted(data.items(), key=lambda kv: kv[1].total_size)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700154 data = data[-args.top_size:]
155 else:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100156 data = sorted(data.items(), key=lambda kv: kv[1].total_size)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700157 print("%-30s %8s %12s" % ("TYPE", "# ALLOCS", "# BYTES"))
158 for key, value in data:
159 if args.language == "c":
160 obj_type = "block size %d" % key.size
161 else:
162 obj_type = key.name
163 print("%-30s %8d %12d" %
164 (obj_type, value.num_allocs, value.total_size))
165 if args.interval and not exit_signaled:
166 bpf["allocs"].clear()
167 else:
168 exit()