#!/usr/bin/env python3 # # Copyright (c) 2023 KNS Group LLC (YADRO) # Copyright (c) 2020 Yonatan Goldschmidt # # SPDX-License-Identifier: Apache-2.0 """ Stack compressor for FlameGraph This translate stack samples captured by perf subsystem into format used by flamegraph.pl. Translation uses .elf file to get function names from addresses Usage: ./script/perf/stackcollapse.py """ import re import sys import struct import binascii from functools import lru_cache from elftools.elf.elffile import ELFFile @lru_cache(maxsize=None) def addr_to_sym(addr, elf): symtab = elf.get_section_by_name(".symtab") for sym in symtab.iter_symbols(): if sym.entry.st_info.type == "STT_FUNC" and sym.entry.st_value <= addr < sym.entry.st_value + sym.entry.st_size: return sym.name if addr == 0: return "nullptr" return "[unknown]" def collapse(buf, elf): while buf: count, = struct.unpack_from(">Q", buf) assert count > 0 addrs = struct.unpack_from(f">{count}Q", buf, 8) func_trace = reversed(list(map(lambda a: addr_to_sym(a, elf), addrs))) prev_func = next(func_trace) line = prev_func # merge dublicate functions for func in func_trace: if prev_func != func: prev_func = func line += ";" + func print(line, 1) buf = buf[8 + 8 * count:] if __name__ == "__main__": elf = ELFFile(open(sys.argv[2], "rb")) with open(sys.argv[1], "r") as f: inp = f.read() lines = inp.splitlines() assert int(re.match(r"Perf buf length (\d+)", lines[0]).group(1)) == len(lines) - 1 buf = binascii.unhexlify("".join(lines[1:])) collapse(buf, elf)