Home | History | Annotate | Download | only in lib
      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