blob: bcd69b433f596e7293ccd755eab5a160c05566f1 [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
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020016from bcc import BPF, USDT, utils
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070017from time import sleep
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020018import os
19
20# C needs to be the last language.
21languages = ["java", "ruby", "c"]
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070022
23examples = """examples:
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020024 ./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
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070028"""
29parser = argparse.ArgumentParser(
30 description="Summarize object allocations in high-level languages.",
31 formatter_class=argparse.RawDescriptionHelpFormatter,
32 epilog=examples)
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020033parser.add_argument("-l", "--language", choices=languages,
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070034 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)")
44args = parser.parse_args()
45
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020046language = args.language
47if not language:
48 language = utils.detect_language(languages, args.pid)
49
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070050program = """
51#include <linux/ptrace.h>
52
53struct key_t {
54#if MALLOC_TRACING
55 u64 size;
56#else
57 char name[50];
58#endif
59};
60
61struct val_t {
62 u64 total_size;
63 u64 num_allocs;
64};
65
66BPF_HASH(allocs, struct key_t, struct val_t);
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020067""".replace("MALLOC_TRACING", "1" if language == "c" else "0")
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070068
69usdt = USDT(pid=args.pid)
70
Sasha Goldshteinbee71b22016-10-26 06:34:06 -070071#
72# Java
73#
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020074if language == "java":
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070075 program += """
76int alloc_entry(struct pt_regs *ctx) {
77 struct key_t key = {};
78 struct val_t *valp, zero = {};
79 u64 classptr = 0, size = 0;
80 bpf_usdt_readarg(2, ctx, &classptr);
81 bpf_usdt_readarg(4, ctx, &size);
82 bpf_probe_read(&key.name, sizeof(key.name), (void *)classptr);
83 valp = allocs.lookup_or_init(&key, &zero);
84 valp->total_size += size;
85 valp->num_allocs += 1;
86 return 0;
87}
88 """
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -050089 usdt.enable_probe_or_bail("object__alloc", "alloc_entry")
Sasha Goldshteinbee71b22016-10-26 06:34:06 -070090#
91# Ruby
92#
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +020093elif language == "ruby":
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -070094 create_template = """
95int THETHING_alloc_entry(struct pt_regs *ctx) {
96 struct key_t key = { .name = "THETHING" };
97 struct val_t *valp, zero = {};
98 u64 size = 0;
99 bpf_usdt_readarg(1, ctx, &size);
100 valp = allocs.lookup_or_init(&key, &zero);
101 valp->total_size += size;
102 valp->num_allocs += 1;
103 return 0;
104}
105 """
106 program += """
107int object_alloc_entry(struct pt_regs *ctx) {
108 struct key_t key = {};
109 struct val_t *valp, zero = {};
110 u64 classptr = 0;
111 bpf_usdt_readarg(1, ctx, &classptr);
112 bpf_probe_read(&key.name, sizeof(key.name), (void *)classptr);
113 valp = allocs.lookup_or_init(&key, &zero);
114 valp->num_allocs += 1; // We don't know the size, unfortunately
115 return 0;
116}
117 """
Sasha Goldshteindc3a57c2017-02-08 16:02:11 -0500118 usdt.enable_probe_or_bail("object__create", "object_alloc_entry")
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700119 for thing in ["string", "hash", "array"]:
120 program += create_template.replace("THETHING", thing)
Paul Chaignon956ca1c2017-03-04 20:07:56 +0100121 usdt.enable_probe_or_bail("%s__create" % thing,
122 "%s_alloc_entry" % thing)
Sasha Goldshteinbee71b22016-10-26 06:34:06 -0700123#
124# C
125#
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200126elif language == "c":
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700127 program += """
128int alloc_entry(struct pt_regs *ctx, size_t size) {
129 struct key_t key = {};
130 struct val_t *valp, zero = {};
131 key.size = size;
132 valp = allocs.lookup_or_init(&key, &zero);
133 valp->total_size += size;
134 valp->num_allocs += 1;
135 return 0;
136}
137 """
138
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200139else:
140 print("No language detected; use -l to trace a language.")
141 exit(1)
142
143
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700144if args.verbose:
145 print(usdt.get_text())
146 print(program)
147
148bpf = BPF(text=program, usdt_contexts=[usdt])
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200149if language == "c":
Sasha Goldshtein39ace6f2016-12-19 09:52:34 +0000150 bpf.attach_uprobe(name="c", sym="malloc", fn_name="alloc_entry",
151 pid=args.pid)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700152
153exit_signaled = False
154print("Tracing allocations in process %d (language: %s)... Ctrl-C to quit." %
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200155 (args.pid, language or "none"))
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700156while True:
157 try:
158 sleep(args.interval or 99999999)
159 except KeyboardInterrupt:
160 exit_signaled = True
161 print()
162 data = bpf["allocs"]
163 if args.top_count:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100164 data = sorted(data.items(), key=lambda kv: kv[1].num_allocs)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700165 data = data[-args.top_count:]
166 elif args.top_size:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100167 data = sorted(data.items(), key=lambda kv: kv[1].total_size)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700168 data = data[-args.top_size:]
169 else:
Rafael Fonsecac465a242017-02-13 16:04:33 +0100170 data = sorted(data.items(), key=lambda kv: kv[1].total_size)
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700171 print("%-30s %8s %12s" % ("TYPE", "# ALLOCS", "# BYTES"))
172 for key, value in data:
Paul Chaignon4bb6d7f2017-03-30 19:05:40 +0200173 if language == "c":
Sasha Goldshtein2e7e2402016-10-25 05:28:29 -0700174 obj_type = "block size %d" % key.size
175 else:
176 obj_type = key.name
177 print("%-30s %8d %12d" %
178 (obj_type, value.num_allocs, value.total_size))
179 if args.interval and not exit_signaled:
180 bpf["allocs"].clear()
181 else:
182 exit()