From nobody Mon Sep 28 13:17:46 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6401B3CF21D for ; Fri, 21 Aug 2026 08:53:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787302394; cv=none; b=A/iMfi0/eG7TLjQyHLJlYSfbDzfj5+58ZQWnGs6fVU8NNzgB7uzZ6kMq08RnvmoS28RPqPr/UL6ctMMlRxhp3MPxMPDody37ykwfr7h/UuKn0Aj5VGLh0T4kHSQpt84Er/fgjBXp9fahbtPLdDXN3M1IlkIJqdfswFVM/uXKWJ4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787302394; c=relaxed/simple; bh=g9uW7vvpoz4UGuYFYljPJ8v+4ecwdoXeakS2Z0Nwi+Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WpbaVjpj+jZizWEAgrKSw7sUg86hlOHykdxStHsiOkqEK6lVvetM1PM80izhNkczFBrak/plCz/S+i+KSeiVKge257gtjkDTISjnpNX39+5RQpkEOoG6o6Sjo0kM9e77qp4I0t/VtuqdQC8ITQZ4x0RBbGmvwHvBLt9xKak17xk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=i2OoAXUk; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="i2OoAXUk" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-4956869750eso4804535e9.2 for ; Fri, 21 Aug 2026 01:53:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787302390; x=1787907190; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6rVFk6e2u37aNi/Yv6Vtl1AMCTMYHsSeRuZzfxsO9ww=; b=i2OoAXUkUpV0VwpjE+vTTXWM0qD25h614dw1G27IUyST43woZMUFR2mXFHRq+CiY0l iWqBOVoKjWwCGIFLtZqXGQt2kfNU2TVsTiNMKSqA3iB/yc0vL0KU0ENsXuxiJiSqtBYH 9wULgKbSZ5/9FGLprcRq44s8oShceQvjPGMM3l5fdbt773UW63HYErt78RB109ima8mR a7gBQ8grUCDTkxouyzUKd9Hiplcp+uZYFX1vJhbm0+t4qHLJwyPFy4KKDeWv1lQo5FDW 4CZDk0EVn4JLrwkCxYEuy1WZMdEeqHoMv7VRAIQCJNVsgjMHFkeiqSbwsV42LoN2CPqR Z3zQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787302390; x=1787907190; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6rVFk6e2u37aNi/Yv6Vtl1AMCTMYHsSeRuZzfxsO9ww=; b=W4v21blr2BcXbFP1TEFT5+Tyl7Sr6oaWH+Nh7g2ugKITgmT1g2jlYWTvpDpbrHoS6J Bo/+L0ocmDxdJHjNDYDYAXzT1oyGfmnqEeVJ3TBrbabwqW6ZTju+NdZC7RkQ57t7tuFv 5DXzzQ1ZLRnh+gM81+Z/VPpW+vEJwHxykZdLC29PEAxktZVvKxtfzDEBEzOsx7cZRquj WLptaZt8cfGl/L+vTDspfIP+7Chk1W1bpsgx+nRqp4PkXiDt2jIq16DHvwA1/1wPZhqK YpTEtf0ve6NTNljUJFUD18tKvV5dhB5K74pugh4YQlc1+x/RBPhctY8CQUrf1Ohud7Sj EaEQ== X-Forwarded-Encrypted: i=1; AHgh+Rr6GIiHOv3AMw8ltLO90eHSdDME79L9zdx1TM0pYr2eGivXtfmq4jqfCuOTqJJgMztE3CmOuUSnANCgEL8=@vger.kernel.org X-Gm-Message-State: AOJu0YztzmKgAQ6fo7NuhHmQPVOIaVgBM66Pxe3CsHweC7Ju4yHnRFcQ KafGajnMUTbbI5jF2hQuFs8cKwSLtoN1ovJnaChoAxTn9ogoxgo1vhyd X-Gm-Gg: AR+sD11sHF/+Z2I8fTKzHrTWWzGW8kyY1PN1oiPudg/9fES71RSNPPtcC0zAUFh4Zc1 7+qoTkRabAr7QNCXGNZrgrldYgOSyizp28YjcXCb2LdJonXF7j+po/sns64WPgmy2R0vc+hN4mM Njv1mAG/lPPBbdOlg5YZS23tUOSPGavCTIDiS/CeNHHoruY3OdByShgJ/kK54Fa7xQAUTPD4Dgd v4vBFy1m7zkxjikKYeS8yA2kWEDMp+njxZN9z7Pv26Hu5uchEL426Bw0xaOvGGDaOBU3LCyQhMn gW991KEZln+Ye+oFPNvhL299+kl6YoW/zz6yOk7NTtxiPNAVWChIGUdYbBjQqXFrUU9Q9Vv/LGW L293KKmo58tUd0Hukbc87kXTqKKLdytCRG+JNMN6oDzm0RyLshQ7aWa/zVzSPm1ZfbMBZSDZwd1 brZhg34TRhfpkqiHNzme8zPge0IPhmwzDgscUtrz6rlue4NyKNNNWZ07TUvFvTyxiyyyRvyGzyf JR9xZbNX1mak5+m2uduqY4NfbklhRNNOg== X-Received: by 2002:a05:600d:6401:10b0:499:bdf1:7578 with SMTP id 5b1f17b1804b1-499bdf1758fmr8328125e9.3.1787302390083; Fri, 21 Aug 2026 01:53:10 -0700 (PDT) Received: from amwld-tmagires1.us.drwholdings.com ([149.14.21.6]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-482b14b7d23sm17004585f8f.22.2026.08.21.01.53.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 21 Aug 2026 01:53:09 -0700 (PDT) From: Tudor-Stefan Magirescu To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Tudor-Stefan Magirescu Subject: [RFC PATCH 1/1] perf scripts flamegraph: Add --asm option Date: Fri, 21 Aug 2026 10:50:44 +0200 Message-ID: <20260821085044.982835-2-tudor.magirescu@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260821085044.982835-1-tudor.magirescu@gmail.com> References: <20260821085044.982835-1-tudor.magirescu@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The flamegraph script reports samples at function granularity, so a wide frame shows which function is hot but not which part of it. Obtaining that requires perf annotate, which reports per-instruction counts without the calling context of a flame graph. Add an --asm option to emit more fine-grained flame graphs by including instruction-level information. By enabling this option, instructions appear as the leaves in the flame graph call stacks: main a(int) imul $0x3b9aca07,%rax,%rdx [0x13ad] Signed-off-by: Tudor-Stefan Magirescu --- tools/perf/scripts/python/flamegraph.py | 120 ++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/p= ython/flamegraph.py index ad735990c5be..424919e24aed 100755 --- a/tools/perf/scripts/python/flamegraph.py +++ b/tools/perf/scripts/python/flamegraph.py @@ -19,14 +19,23 @@ # pylint: disable=3Dmissing-function-docstring =20 import argparse +from dataclasses import dataclass import hashlib import io import json import os import subprocess import sys +import re from typing import Dict, Optional, Union import urllib.request +from perf_trace_context import perf_config_get + + +def default_objdump(): + config =3D perf_config_get("annotate.objdump") + return config if config else "objdump" + =20 MINIMAL_HTML =3D """ @@ -67,10 +76,101 @@ class Node: } =20 =20 +@dataclass +class Symbol: + start: int + size: int + + +class Instructions: + def __init__(self): + self.symbols: dict[str, dict[str, Symbol]] =3D {} + self.instructions: dict[tuple[str, str], dict[int, str]] =3D {} + self.objdump_line: re.Pattern =3D re.compile(r"^\s+([0-9a-f]+):\s+= (.*)") + self.symbol_line: re.Pattern =3D re.compile( + r"^([0-9a-f]+) (.{7})\s+\S+\s+([0-9a-f]+)\s+(.*)$") + self.objdump: str =3D default_objdump() + + def load_symbols(self, dso: str) -> dict[str, Symbol]: + result =3D subprocess.run( + [self.objdump, "--demangle", "-t", dso], + capture_output=3DTrue, text=3DTrue, check=3DFalse + ) + + if result.returncode !=3D 0: + return {} + + symbols: dict[str, Symbol] =3D {} + + for line in result.stdout.splitlines(): + match =3D self.symbol_line.match(line) + if not match: + continue + + value, flags, size_str, name =3D match.groups() + # the 7th flag column holds the symbol type, "F" for a function + if flags[6] !=3D "F": + continue + + size =3D int(size_str, 16) + if size =3D=3D 0: + continue + + # a name may be preceded by its visibility + parts =3D name.split(maxsplit=3D1) + if len(parts) =3D=3D 2 and parts[0] in (".hidden", ".protected= ", + ".internal"): + name =3D parts[1] + + symbols[name.strip()] =3D Symbol(start=3Dint(value, 16), size= =3Dsize) + + return symbols + + def load_instructions(self, dso: str, sym: Symbol) -> dict[int, str]: + result =3D subprocess.run( + [ + self.objdump, "-d", + "--no-show-raw-insn", + f"--start-address=3D0x{sym.start:x}", + f"--stop-address=3D0x{sym.start + sym.size:x}", + dso, + ], + capture_output=3DTrue, text=3DTrue, check=3DFalse + ) + + instructions: dict[int, str] =3D {} + + for line in result.stdout.splitlines(): + match =3D self.objdump_line.match(line) + if not match: + continue + addr =3D int(match.group(1), 16) + insn =3D match.group(2).strip() + instructions[addr] =3D insn + + return instructions + + def lookup_instruction(self, dso: str, func: str, + off: int) -> Optional[tuple[int, Optional[str]]= ]: + if dso not in self.symbols: + self.symbols[dso] =3D self.load_symbols(dso) + + sym =3D self.symbols[dso].get(func) + if sym is None: + return None + + if (dso, func) not in self.instructions: + self.instructions[(dso, func)] =3D self.load_instructions(dso,= sym) + + addr =3D sym.start + off + return (addr, self.instructions[(dso, func)].get(addr)) + + class FlameGraphCLI: def __init__(self, args): self.args =3D args self.stack =3D Node("all", "root") + self.instructions =3D Instructions() if args.asm else None =20 @staticmethod def get_libtype_from_dso(dso: Optional[str]) -> str: @@ -119,6 +219,22 @@ class FlameGraphCLI: name =3D event.get("symbol", "[unknown]") libtype =3D self.get_libtype_from_dso(event.get("dso")) node =3D self.find_or_create_node(node, name, libtype) + + if self.args.asm: + # use the sample IP directly rather than callchain[0], since w= ith + # precise event recording (i.e. :pp) the top callchain entry m= ay + # point to the next instruction rather than the sampled IP + sym_name =3D event.get("symbol") + sym_off =3D event.get("symoff") + dso =3D event.get("dso") + + if sym_name and sym_off is not None and dso: + found =3D self.instructions.lookup_instruction(dso, sym_na= me, sym_off) + if found is not None: + addr, insn =3D found + name =3D f"{insn} [0x{addr:x}]" if insn else f"[0x{add= r:x}]" + libtype =3D self.get_libtype_from_dso(dso) + node =3D self.find_or_create_node(node, name, libtype) node.value +=3D 1 =20 def get_report_header(self) -> str: @@ -259,6 +375,10 @@ if __name__ =3D=3D "__main__": dest=3D"event_name", type=3Dstr, help=3D"specify the event to generate flamegraph f= or") + parser.add_argument("--asm", + default=3DFalse, + action=3D"store_true", + help=3D"annotate leaf frames with instruction-leve= l nodes") =20 cli_args =3D parser.parse_args() cli =3D FlameGraphCLI(cli_args) --=20 2.43.0