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