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