From f39c602d41ffb3043302bb9278aafe2404907f46 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 10:56:22 -0500 Subject: [PATCH 01/15] Start implementing a diff for symbols --- scripts/tools/file_size_from_nm.py | 87 ++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 74912bc185effb..9cc0058eb111fc 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -44,10 +44,10 @@ import logging import re import subprocess -from dataclasses import dataclass +from dataclasses import dataclass, replace from enum import Enum, auto from pathlib import Path -from typing import Optional +from typing import Optional, Tuple import click import coloredlogs @@ -82,8 +82,8 @@ class FetchStyle(Enum): __FETCH_STYLES__ = { - "nm": ChartStyle.TREE_MAP, - "objdump": ChartStyle.SUNBURST, + "nm": FetchStyle.NM, + "objdump": FetchStyle.OBJDUMP, } @@ -660,6 +660,65 @@ def symbols_from_nm(elf_file: str) -> list[Symbol]: return symbols +def fetch_symbols(elf_file: Path, fetch: FetchStyle) -> Tuple[list[Symbol], str]: + """Returns the sumbol list and the separator used to split symbols + """ + if fetch == FetchStyle.NM: + return symbols_from_nm(elf_file.absolute().as_posix()), "::" + else: + return symbols_from_objdump(elf_file.absolute().as_posix()), '/' + +def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: + """ + Generates a NEW set of symbols for the difference between original and base. + + Two symbols with the same name are assumed different IF AND ONLY IF they have a different size + between original and base. + + Symbols are the same if their "name" if the have the same tree path. + """ + orig_items = dict([(v.tree_path, v) for v in orig]) + base_items = dict([(v.tree_path, v) for v in base]) + + unique_paths = set(orig_items.keys()).union(set(base_items.keys())) + + result = [] + + for path in unique_paths: + orig_symbol = orig_items.get(path, None) + base_symbol = base_items.get(path, None) + + if not orig_symbol: + if not base_symbol: + raise AssertionError("Internal logic error: paths should be valid somewhere") + + result.append(replace(base_symbol, + name=f"REMOVED: {base_symbol.name}", + tree_path = ["REMOVED"] + base_symbol.tree_path, + )) + continue + + if not base_symbol: + result.append(replace(orig_symbol, + name=f"ADDED: {orig_symbol.name}", + tree_path = ["ADDED"] + orig_symbol.tree_path, + )) + continue + + if orig_symbol.size == base_symbol.size: + # symbols are identical + continue + + result.append(replace(orig_symbol, + name=f"CHANGED: {orig_symbol.name}", + tree_path = ["CHANGED"] + orig_symbol.tree_path, + )) + + return result + + + + @click.command() @click.option( "--log-level", @@ -699,7 +758,13 @@ def symbols_from_nm(elf_file: str) -> list[Symbol]: default=None, help="Strip out a tree subset (e.g. ::C)", ) -@click.argument("elf-file", type=Path) +@click.option( + "--diff", + default=None, + type=click.Path(file_okay=True, dir_okay=False, exists=True), + help="Diff against the given file (changes symbols to increase/decrease)", +) +@click.argument("elf-file", type=click.Path(file_okay=True, dir_okay=False, exists=True)) def main( log_level, elf_file: Path, @@ -708,16 +773,16 @@ def main( max_depth: int, zoom: Optional[str], strip: Optional[str], + diff: Optional[Path], ): log_fmt = "%(asctime)s %(levelname)-7s %(message)s" coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt=log_fmt) - if __FETCH_STYLES__[fetch_via] == FetchStyle.NM: - symbols = symbols_from_nm(elf_file.absolute().as_posix()) - separator = "::" - else: - symbols = symbols_from_objdump(elf_file.absolute().as_posix()) - separator = "/" + symbols, separator = fetch_symbols(elf_file, __FETCH_STYLES__[fetch_via]) + + if diff: + diff_symbols, _ = fetch_symbols(diff, __FETCH_STYLES__[fetch_via]) + symbols = compute_symbol_diff(symbols, diff_symbols) build_treemap( elf_file.name, symbols, separator, __CHART_STYLES__[display_type], max_depth, zoom, strip From 2534b81ba30489dba1dccdc882a2f681156757e0 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 10:58:32 -0500 Subject: [PATCH 02/15] Fix up size computations --- scripts/tools/file_size_from_nm.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 9cc0058eb111fc..999cae3e683766 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -694,14 +694,14 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: result.append(replace(base_symbol, name=f"REMOVED: {base_symbol.name}", - tree_path = ["REMOVED"] + base_symbol.tree_path, + tree_path = ["DECREASE"] + base_symbol.tree_path, )) continue if not base_symbol: result.append(replace(orig_symbol, name=f"ADDED: {orig_symbol.name}", - tree_path = ["ADDED"] + orig_symbol.tree_path, + tree_path = ["INCREASE"] + orig_symbol.tree_path, )) continue @@ -709,10 +709,20 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: # symbols are identical continue - result.append(replace(orig_symbol, - name=f"CHANGED: {orig_symbol.name}", - tree_path = ["CHANGED"] + orig_symbol.tree_path, - )) + size_delta = orig_symbol.size - base_symbol.size + + if size_delta > 0: + result.append(replace(orig_symbol, + name=f"CHANGED: {orig_symbol.name}", + tree_path = ["INCREASE"] + orig_symbol.tree_path, + size=size_delta, + )) + else: + result.append(replace(orig_symbol, + name=f"CHANGED: {orig_symbol.name}", + tree_path = ["DECREASE"] + orig_symbol.tree_path, + size=-size_delta, + )) return result From 5975bca60ad250c47e91ae9524e7737c043e886d Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 11:17:08 -0500 Subject: [PATCH 03/15] make things run --- scripts/tools/file_size_from_nm.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 999cae3e683766..58b58d60cce6c0 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -46,7 +46,6 @@ import subprocess from dataclasses import dataclass, replace from enum import Enum, auto -from pathlib import Path from typing import Optional, Tuple import click @@ -87,7 +86,7 @@ class FetchStyle(Enum): } -@dataclass +@dataclass(frozen=True) class Symbol: name: str symbol_type: str @@ -660,13 +659,17 @@ def symbols_from_nm(elf_file: str) -> list[Symbol]: return symbols -def fetch_symbols(elf_file: Path, fetch: FetchStyle) -> Tuple[list[Symbol], str]: +def fetch_symbols(elf_file: str, fetch: FetchStyle) -> Tuple[list[Symbol], str]: """Returns the sumbol list and the separator used to split symbols """ if fetch == FetchStyle.NM: - return symbols_from_nm(elf_file.absolute().as_posix()), "::" + return symbols_from_nm(elf_file), "::" else: - return symbols_from_objdump(elf_file.absolute().as_posix()), '/' + return symbols_from_objdump(elf_file), '/' + +def list_id(l: list[str]) -> str: + return "->".join(l) + def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: """ @@ -677,8 +680,8 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: Symbols are the same if their "name" if the have the same tree path. """ - orig_items = dict([(v.tree_path, v) for v in orig]) - base_items = dict([(v.tree_path, v) for v in base]) + orig_items = dict([(list_id(v.tree_path), v) for v in orig]) + base_items = dict([(list_id(v.tree_path), v) for v in base]) unique_paths = set(orig_items.keys()).union(set(base_items.keys())) @@ -777,25 +780,27 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: @click.argument("elf-file", type=click.Path(file_okay=True, dir_okay=False, exists=True)) def main( log_level, - elf_file: Path, + elf_file: str, display_type: str, fetch_via: str, max_depth: int, zoom: Optional[str], strip: Optional[str], - diff: Optional[Path], + diff: Optional[str], ): log_fmt = "%(asctime)s %(levelname)-7s %(message)s" coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt=log_fmt) symbols, separator = fetch_symbols(elf_file, __FETCH_STYLES__[fetch_via]) + title = elf_file if diff: diff_symbols, _ = fetch_symbols(diff, __FETCH_STYLES__[fetch_via]) symbols = compute_symbol_diff(symbols, diff_symbols) + title = f"{elf_file} COMPARED TO {diff}" build_treemap( - elf_file.name, symbols, separator, __CHART_STYLES__[display_type], max_depth, zoom, strip + title, symbols, separator, __CHART_STYLES__[display_type], max_depth, zoom, strip ) From 5519c298144df7142bff3f411793c479952c39ca Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 11:33:33 -0500 Subject: [PATCH 04/15] Added icicles graph --- scripts/tools/file_size_from_nm.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 58b58d60cce6c0..19c9dc654104a3 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -67,11 +67,13 @@ class ChartStyle(Enum): TREE_MAP = auto() SUNBURST = auto() + ICICLE = auto() __CHART_STYLES__ = { "treemap": ChartStyle.TREE_MAP, "sunburst": ChartStyle.SUNBURST, + "icicle": ChartStyle.ICICLE, } @@ -440,7 +442,7 @@ def build_treemap( maxdepth=max_depth, ) ) - else: + elif style == ChartStyle.SUNBURST: fig = px.sunburst( data, names="name", @@ -448,6 +450,15 @@ def build_treemap( values="size", maxdepth=max_depth, ) + else: + assert(style == ChartStyle.ICICLE) + fig = px.icicle( + data, + names="name", + parents="parent", + values="size", + maxdepth=max_depth, + ) fig.update_traces(root_color="lightgray") fig.show() From 7770289c93f7b7b91f3d9da9901cc2f227f6a3ff Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 11:37:22 -0500 Subject: [PATCH 05/15] Also add percent root to individual items --- scripts/tools/file_size_from_nm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 19c9dc654104a3..306eeffcecfb80 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -437,7 +437,7 @@ def build_treemap( labels=data["name"], parents=data["parent"], values=data["size"], - textinfo="label+value+percent parent", + textinfo="label+value+percent parent+percent root", hovertext=data["hover"], maxdepth=max_depth, ) From 8147e4e5229ed3e10c6a853bad8ad9b146277919 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 11:51:58 -0500 Subject: [PATCH 06/15] Show name sizes --- scripts/tools/file_size_from_nm.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 306eeffcecfb80..0a2404f012e689 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -378,7 +378,7 @@ def build_treemap( root = f"FILE: {name}" if zoom: root = root + f" (FILTER: {zoom})" - data: dict[str, list] = dict(name=[root], parent=[""], size=[0], hover=[""]) + data: dict[str, list] = dict(name=[root], parent=[""], size=[0], hover=[""], name_with_size=[""]) known_parents: set[str] = set() total_sizes: dict = {} @@ -418,6 +418,7 @@ def build_treemap( data["parent"].append(partial if partial else root) data["size"].append(0) data["hover"].append(next_value) + data["name_with_size"].append("") total_sizes[next_value] = total_sizes.get(next_value, 0) + symbol.size partial = next_value @@ -426,15 +427,19 @@ def build_treemap( data["parent"].append(partial if partial else root) data["size"].append(symbol.size) data["hover"].append(f"{symbol.name} of type {symbol.symbol_type}") + data["name_with_size"].append("") for idx, label in enumerate(data["name"]): if data["size"][idx] == 0: - data["hover"][idx] = f"{label}: {total_sizes.get(label, 0)}" + total_size = total_sizes.get(label, 0) + data["hover"][idx] = f"{label}: {total_size}" + data["name_with_size"][idx] = f"{label}: {total_size}" if style == ChartStyle.TREE_MAP: fig = go.Figure( go.Treemap( - labels=data["name"], + labels=data["name_with_size"], + ids=data["name"], parents=data["parent"], values=data["size"], textinfo="label+value+percent parent+percent root", @@ -445,7 +450,8 @@ def build_treemap( elif style == ChartStyle.SUNBURST: fig = px.sunburst( data, - names="name", + names="name_with_size", + ids="name", parents="parent", values="size", maxdepth=max_depth, @@ -454,7 +460,8 @@ def build_treemap( assert(style == ChartStyle.ICICLE) fig = px.icicle( data, - names="name", + names="name_with_size", + ids="name", parents="parent", values="size", maxdepth=max_depth, From 1efe421de543ace83ca991106bfedc236a8838c9 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 12:12:43 -0500 Subject: [PATCH 07/15] Better name formatting --- scripts/tools/file_size_from_nm.py | 35 ++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 0a2404f012e689..c07bdee04eef7b 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -378,7 +378,7 @@ def build_treemap( root = f"FILE: {name}" if zoom: root = root + f" (FILTER: {zoom})" - data: dict[str, list] = dict(name=[root], parent=[""], size=[0], hover=[""], name_with_size=[""]) + data: dict[str, list] = dict(name=[root], parent=[""], size=[0], hover=[""], name_with_size=[""], short_name=[""]) known_parents: set[str] = set() total_sizes: dict = {} @@ -419,6 +419,7 @@ def build_treemap( data["size"].append(0) data["hover"].append(next_value) data["name_with_size"].append("") + data["short_name"].append(name) total_sizes[next_value] = total_sizes.get(next_value, 0) + symbol.size partial = next_value @@ -428,12 +429,42 @@ def build_treemap( data["size"].append(symbol.size) data["hover"].append(f"{symbol.name} of type {symbol.symbol_type}") data["name_with_size"].append("") + data["short_name"].append(tree_name[-1]) for idx, label in enumerate(data["name"]): if data["size"][idx] == 0: total_size = total_sizes.get(label, 0) data["hover"][idx] = f"{label}: {total_size}" - data["name_with_size"][idx] = f"{label}: {total_size}" + if idx == 0: + data["name_with_size"][idx] = f"{label}: {total_size}" + else: + # The "full name" is generally quite long, so shorten it... + data["name_with_size"][idx] = f"{data["short_name"][idx]}: {total_size}" + else: + # When using object files, the paths hare are the full "foo::bar::....::method" + # so clean them up a bit + short_name = data["short_name"][idx] + + # remove namespaces, but keep template parts + # This tries to convert: + # foo::bar::baz(int, double) -> baz(int, double) + # foo::bar::baz(int, double) -> baz(int, double) + # foo::bar::baz(some::ns:bit, double) -> baz(some::ns::bit, double) + # foo::bar::baz(some::ns:bit, double) -> baz(some::ns::bit, double) + # + # Remove all before '::', however '::' found before the first of < or ( + # + limit1 = short_name.find('<') + limit2 = short_name.find('(') + if limit1 >= 0 and limit1 < limit2: + limit = limit1 + else: + limit = limit2 + separate_idx = short_name.rfind('::', 0, limit) + if separate_idx: + short_name = short_name[separate_idx+2:] + + data["name_with_size"][idx] = f"{short_name}: {data["size"][idx]}" if style == ChartStyle.TREE_MAP: fig = go.Figure( From 79a6eabbe55eb1e701cd6153bf93cc015e28914f Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 25 Feb 2025 17:16:03 +0000 Subject: [PATCH 08/15] Restyled by autopep8 --- scripts/tools/file_size_from_nm.py | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index c07bdee04eef7b..43973c05eca01e 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -488,7 +488,7 @@ def build_treemap( maxdepth=max_depth, ) else: - assert(style == ChartStyle.ICICLE) + assert (style == ChartStyle.ICICLE) fig = px.icicle( data, names="name_with_size", @@ -716,6 +716,7 @@ def fetch_symbols(elf_file: str, fetch: FetchStyle) -> Tuple[list[Symbol], str]: else: return symbols_from_objdump(elf_file), '/' + def list_id(l: list[str]) -> str: return "->".join(l) @@ -745,16 +746,16 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: raise AssertionError("Internal logic error: paths should be valid somewhere") result.append(replace(base_symbol, - name=f"REMOVED: {base_symbol.name}", - tree_path = ["DECREASE"] + base_symbol.tree_path, - )) + name=f"REMOVED: {base_symbol.name}", + tree_path=["DECREASE"] + base_symbol.tree_path, + )) continue if not base_symbol: result.append(replace(orig_symbol, - name=f"ADDED: {orig_symbol.name}", - tree_path = ["INCREASE"] + orig_symbol.tree_path, - )) + name=f"ADDED: {orig_symbol.name}", + tree_path=["INCREASE"] + orig_symbol.tree_path, + )) continue if orig_symbol.size == base_symbol.size: @@ -765,22 +766,20 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: if size_delta > 0: result.append(replace(orig_symbol, - name=f"CHANGED: {orig_symbol.name}", - tree_path = ["INCREASE"] + orig_symbol.tree_path, - size=size_delta, - )) + name=f"CHANGED: {orig_symbol.name}", + tree_path=["INCREASE"] + orig_symbol.tree_path, + size=size_delta, + )) else: result.append(replace(orig_symbol, - name=f"CHANGED: {orig_symbol.name}", - tree_path = ["DECREASE"] + orig_symbol.tree_path, - size=-size_delta, - )) + name=f"CHANGED: {orig_symbol.name}", + tree_path=["DECREASE"] + orig_symbol.tree_path, + size=-size_delta, + )) return result - - @click.command() @click.option( "--log-level", From 091cd0997412a8cd5f4e1647109f40b7d8d90e67 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 12:27:29 -0500 Subject: [PATCH 09/15] Make linter happy --- scripts/tools/file_size_from_nm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 43973c05eca01e..730a22eaaba8ed 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -717,8 +717,9 @@ def fetch_symbols(elf_file: str, fetch: FetchStyle) -> Tuple[list[Symbol], str]: return symbols_from_objdump(elf_file), '/' -def list_id(l: list[str]) -> str: - return "->".join(l) +def list_id(tree_path: list[str]) -> str: + """Converts a tree path in to a single string (so that it is hashable)""" + return "->".join(tree_path) def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: From 388cf545ddb9e4b94d151f0e3d554facca7a88e0 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 15:51:10 -0500 Subject: [PATCH 10/15] add assert for fetch values, clean up asserts a bit --- scripts/tools/file_size_from_nm.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index 730a22eaaba8ed..ffa4db9f744d0d 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -488,7 +488,7 @@ def build_treemap( maxdepth=max_depth, ) else: - assert (style == ChartStyle.ICICLE) + assert style == ChartStyle.ICICLE fig = px.icicle( data, names="name_with_size", @@ -713,8 +713,9 @@ def fetch_symbols(elf_file: str, fetch: FetchStyle) -> Tuple[list[Symbol], str]: """ if fetch == FetchStyle.NM: return symbols_from_nm(elf_file), "::" - else: - return symbols_from_objdump(elf_file), '/' + + assert fetch == FetchStyle.OBJDUMP + return symbols_from_objdump(elf_file), '/' def list_id(tree_path: list[str]) -> str: From 59d7669c50ad4b3d5821c17e41c7092b6714f099 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 15:55:43 -0500 Subject: [PATCH 11/15] Match statements seem easier to read and provable branches with static analysis --- scripts/tools/file_size_from_nm.py | 68 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index ffa4db9f744d0d..c50eb5a9e1ba65 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -466,37 +466,37 @@ def build_treemap( data["name_with_size"][idx] = f"{short_name}: {data["size"][idx]}" - if style == ChartStyle.TREE_MAP: - fig = go.Figure( - go.Treemap( - labels=data["name_with_size"], - ids=data["name"], - parents=data["parent"], - values=data["size"], - textinfo="label+value+percent parent+percent root", - hovertext=data["hover"], + match style: + case ChartStyle.TREE_MAP: + fig = go.Figure( + go.Treemap( + labels=data["name_with_size"], + ids=data["name"], + parents=data["parent"], + values=data["size"], + textinfo="label+value+percent parent+percent root", + hovertext=data["hover"], + maxdepth=max_depth, + ) + ) + case ChartStyle.SUNBURST: + fig = px.sunburst( + data, + names="name_with_size", + ids="name", + parents="parent", + values="size", + maxdepth=max_depth, + ) + case ChartStyle.ICICLE: + fig = px.icicle( + data, + names="name_with_size", + ids="name", + parents="parent", + values="size", maxdepth=max_depth, ) - ) - elif style == ChartStyle.SUNBURST: - fig = px.sunburst( - data, - names="name_with_size", - ids="name", - parents="parent", - values="size", - maxdepth=max_depth, - ) - else: - assert style == ChartStyle.ICICLE - fig = px.icicle( - data, - names="name_with_size", - ids="name", - parents="parent", - values="size", - maxdepth=max_depth, - ) fig.update_traces(root_color="lightgray") fig.show() @@ -711,11 +711,11 @@ def symbols_from_nm(elf_file: str) -> list[Symbol]: def fetch_symbols(elf_file: str, fetch: FetchStyle) -> Tuple[list[Symbol], str]: """Returns the sumbol list and the separator used to split symbols """ - if fetch == FetchStyle.NM: - return symbols_from_nm(elf_file), "::" - - assert fetch == FetchStyle.OBJDUMP - return symbols_from_objdump(elf_file), '/' + match fetch: + case FetchStyle.NM: + return symbols_from_nm(elf_file), "::" + case FetchStyle.OBJDUMP: + return symbols_from_objdump(elf_file), '/' def list_id(tree_path: list[str]) -> str: From ff1e246d696dc0a3f1dec76e90d9b48043744e2c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 16:10:04 -0500 Subject: [PATCH 12/15] Cleaner code by using express everywhere --- scripts/tools/file_size_from_nm.py | 35 ++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index c50eb5a9e1ba65..c79fbcc79efdfe 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -52,7 +52,6 @@ import coloredlogs import cxxfilt import plotly.express as px -import plotly.graph_objects as go # Supported log levels, mapping string values required for argument # parsing into logging constants @@ -466,39 +465,29 @@ def build_treemap( data["name_with_size"][idx] = f"{short_name}: {data["size"][idx]}" + match style: case ChartStyle.TREE_MAP: - fig = go.Figure( - go.Treemap( - labels=data["name_with_size"], - ids=data["name"], - parents=data["parent"], - values=data["size"], - textinfo="label+value+percent parent+percent root", - hovertext=data["hover"], - maxdepth=max_depth, - ) - ) + figure_generator = px.treemap case ChartStyle.SUNBURST: - fig = px.sunburst( - data, - names="name_with_size", - ids="name", - parents="parent", - values="size", - maxdepth=max_depth, - ) + figure_generator = px.sunburst case ChartStyle.ICICLE: - fig = px.icicle( + figure_generator = px.icicle + + fig = figure_generator( data, names="name_with_size", ids="name", parents="parent", values="size", maxdepth=max_depth, - ) + ) - fig.update_traces(root_color="lightgray") + fig.update_traces( + root_color="lightgray", + textinfo="label+value+percent parent+percent root", + hovertext="hover", + ) fig.show() From f10f12a687b06be711d9335bc1e74115289891f9 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 16:13:28 -0500 Subject: [PATCH 13/15] Shorter code --- scripts/tools/file_size_from_nm.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index c79fbcc79efdfe..c90d2bd2289a60 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -46,7 +46,7 @@ import subprocess from dataclasses import dataclass, replace from enum import Enum, auto -from typing import Optional, Tuple +from typing import Callable, Optional, Tuple import click import coloredlogs @@ -63,16 +63,10 @@ } -class ChartStyle(Enum): - TREE_MAP = auto() - SUNBURST = auto() - ICICLE = auto() - - __CHART_STYLES__ = { - "treemap": ChartStyle.TREE_MAP, - "sunburst": ChartStyle.SUNBURST, - "icicle": ChartStyle.ICICLE, + "treemap": px.treemap, + "sunburst": px.sunburst, + "icicle": px.icicle, } @@ -363,7 +357,7 @@ def build_treemap( name: str, symbols: list[Symbol], separator: str, - style: ChartStyle, + figure_generator: Callable, max_depth: int, zoom: Optional[str], strip: Optional[str], @@ -465,15 +459,6 @@ def build_treemap( data["name_with_size"][idx] = f"{short_name}: {data["size"][idx]}" - - match style: - case ChartStyle.TREE_MAP: - figure_generator = px.treemap - case ChartStyle.SUNBURST: - figure_generator = px.sunburst - case ChartStyle.ICICLE: - figure_generator = px.icicle - fig = figure_generator( data, names="name_with_size", From dd2617dca391885a4255f4ef65634f4cfe41bd72 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 25 Feb 2025 16:31:21 -0500 Subject: [PATCH 14/15] Allow some color control too --- scripts/tools/file_size_from_nm.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index c90d2bd2289a60..cbbe59d37a9e74 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -70,6 +70,15 @@ } +# Scales from https://plotly.com/python/builtin-colorscales/ +__COLOR_SCALES__ = { + "none": None, + "tempo": px.colors.sequential.tempo, + "blues": px.colors.sequential.Blues, + "plasma": px.colors.sequential.Plasma_r, +} + + class FetchStyle(Enum): NM = auto() OBJDUMP = auto() @@ -358,6 +367,7 @@ def build_treemap( symbols: list[Symbol], separator: str, figure_generator: Callable, + color: Optional[list[str]], max_depth: int, zoom: Optional[str], strip: Optional[str], @@ -459,6 +469,11 @@ def build_treemap( data["name_with_size"][idx] = f"{short_name}: {data["size"][idx]}" + extra_args = {} + if color is not None: + extra_args['color_continuous_scale'] = color + extra_args['color'] = "size" + fig = figure_generator( data, names="name_with_size", @@ -466,6 +481,7 @@ def build_treemap( parents="parent", values="size", maxdepth=max_depth, + **extra_args, ) fig.update_traces( @@ -771,6 +787,13 @@ def compute_symbol_diff(orig: list[Symbol], base: list[Symbol]) -> list[Symbol]: type=click.Choice(list(__CHART_STYLES__.keys()), case_sensitive=False), help="Style of the chart", ) +@click.option( + "--color", + default="None", + show_default=True, + type=click.Choice(list(__COLOR_SCALES__.keys()), case_sensitive=False), + help="Color display (if any)", +) @click.option( "--fetch-via", default="nm", @@ -806,6 +829,7 @@ def main( log_level, elf_file: str, display_type: str, + color: str, fetch_via: str, max_depth: int, zoom: Optional[str], @@ -824,7 +848,7 @@ def main( title = f"{elf_file} COMPARED TO {diff}" build_treemap( - title, symbols, separator, __CHART_STYLES__[display_type], max_depth, zoom, strip + title, symbols, separator, __CHART_STYLES__[display_type], __COLOR_SCALES__[color], max_depth, zoom, strip ) From 06228b1b90c4d11c6a29e737f6be62698c9a548d Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 25 Feb 2025 21:32:03 +0000 Subject: [PATCH 15/15] Restyled by autopep8 --- scripts/tools/file_size_from_nm.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/tools/file_size_from_nm.py b/scripts/tools/file_size_from_nm.py index cbbe59d37a9e74..dc882e1dc2ab12 100755 --- a/scripts/tools/file_size_from_nm.py +++ b/scripts/tools/file_size_from_nm.py @@ -475,13 +475,13 @@ def build_treemap( extra_args['color'] = "size" fig = figure_generator( - data, - names="name_with_size", - ids="name", - parents="parent", - values="size", - maxdepth=max_depth, - **extra_args, + data, + names="name_with_size", + ids="name", + parents="parent", + values="size", + maxdepth=max_depth, + **extra_args, ) fig.update_traces(