summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorhathach <[email protected]>2025-12-02 12:50:31 +0700
committerhathach <[email protected]>2025-12-02 13:01:49 +0700
commitc859744784cc396ae0993a16a1935b10fbd9b797 (patch)
tree6f7c3487dacad035922f1f3c18e5411dd39003da /tools
parenta337a6d337c0cdd50981ba2040aee99966ae3152 (diff)
adding metrics for computing average compiled size
Diffstat (limited to 'tools')
-rwxr-xr-xtools/build.py4
-rwxr-xr-xtools/get_deps.py2
-rw-r--r--tools/metrics.py134
3 files changed, 137 insertions, 3 deletions
diff --git a/tools/build.py b/tools/build.py
index 5328a987f..692853297 100755
--- a/tools/build.py
+++ b/tools/build.py
@@ -113,8 +113,8 @@ def cmake_board(board, build_args, build_flags_on):
else:
rcmd = run_cmd([
'cmake', 'examples', '-B', build_dir, '-GNinja',
- f'-DBOARD={board}', '-DCMAKE_BUILD_TYPE=MinSizeRel',
- '-DLINKERMAP_OPTION=-q -f tinyusb/src', *build_args, *build_flags
+ f'-DBOARD={board}', '-DCMAKE_BUILD_TYPE=MinSizeRel', '-DLINKERMAP_OPTION=-q -f tinyusb/src',
+ *build_args, *build_flags
])
if rcmd.returncode == 0:
cmd = [
diff --git a/tools/get_deps.py b/tools/get_deps.py
index 47cc5c7dd..5fb7e022c 100755
--- a/tools/get_deps.py
+++ b/tools/get_deps.py
@@ -15,7 +15,7 @@ deps_mandatory = {
'159e31b689577dbf69cf0683bbaffbd71fa5ee10',
'all'],
'tools/linkermap': ['https://github.com/hathach/linkermap.git',
- '1f47651142646398c7746e109ae0481732aeb564',
+ 'ac1228d5bbde1e54cb2e17e928662094ae19c51d',
'all'],
'tools/uf2': ['https://github.com/microsoft/uf2.git',
'c594542b2faa01cc33a2b97c9fbebc38549df80a',
diff --git a/tools/metrics.py b/tools/metrics.py
new file mode 100644
index 000000000..d972d3681
--- /dev/null
+++ b/tools/metrics.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+"""Calculate average size from multiple linker map files."""
+
+import argparse
+import sys
+import os
+
+# Add linkermap module to path
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'linkermap'))
+import linkermap
+
+
+def combine_maps(map_files, filters=None):
+ """Combine multiple map files into a list of json_data.
+
+ Args:
+ map_files: List of paths to linker map files or JSON files
+ filters: List of path substrings to filter object files (default: [])
+
+ Returns:
+ all_json_data: Dictionary with mapfiles list and data from each map file
+ """
+ import json
+
+ filters = filters or []
+ all_json_data = {"mapfiles": [], "data": []}
+
+ for map_file in map_files:
+ if not os.path.exists(map_file):
+ print(f"Warning: {map_file} not found, skipping", file=sys.stderr)
+ continue
+
+ try:
+ if map_file.endswith('.json'):
+ with open(map_file, 'r', encoding='utf-8') as f:
+ json_data = json.load(f)
+ # Apply path filters to JSON data
+ if filters:
+ filtered_files = [
+ f for f in json_data["files"]
+ if f.get("path") and any(filt in f["path"] for filt in filters)
+ ]
+ json_data["files"] = filtered_files
+ else:
+ json_data = linkermap.analyze_map(map_file, filters=filters)
+ all_json_data["mapfiles"].append(map_file)
+ all_json_data["data"].append(json_data)
+ except Exception as e:
+ print(f"Warning: Failed to analyze {map_file}: {e}", file=sys.stderr)
+ continue
+
+ return all_json_data
+
+
+def compute_avg(all_json_data):
+ """Compute average sizes from combined json_data.
+
+ Args:
+ all_json_data: Dictionary with mapfiles and data from combine_maps()
+
+ Returns:
+ json_average: Dictionary with averaged size data
+ """
+ if not all_json_data["data"]:
+ return None
+
+ # Collect all sections preserving order
+ all_sections = []
+ for json_data in all_json_data["data"]:
+ for s in json_data["sections"]:
+ if s not in all_sections:
+ all_sections.append(s)
+
+ # Merge files with the same 'file' value and compute averages
+ file_accumulator = {} # key: file name, value: {"sections": {section: [sizes]}, "totals": [totals]}
+
+ for json_data in all_json_data["data"]:
+ for f in json_data["files"]:
+ fname = f["file"]
+ if fname not in file_accumulator:
+ file_accumulator[fname] = {"sections": {}, "totals": [], "path": f.get("path")}
+ file_accumulator[fname]["totals"].append(f["total"])
+ for section, size in f["sections"].items():
+ if section in file_accumulator[fname]["sections"]:
+ file_accumulator[fname]["sections"][section].append(size)
+ else:
+ file_accumulator[fname]["sections"][section] = [size]
+
+ # Build json_average with averaged values
+ files_average = []
+ for fname, data in file_accumulator.items():
+ avg_total = round(sum(data["totals"]) / len(data["totals"]))
+ avg_sections = {}
+ for section, sizes in data["sections"].items():
+ avg_sections[section] = round(sum(sizes) / len(sizes))
+ files_average.append({
+ "file": fname,
+ "path": data["path"],
+ "sections": avg_sections,
+ "total": avg_total
+ })
+
+ json_average = {
+ "mapfiles": all_json_data["mapfiles"],
+ "sections": all_sections,
+ "files": files_average
+ }
+
+ return json_average
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Calculate average size from linker map files')
+ parser.add_argument('files', nargs='+', help='Path to map file(s)')
+ parser.add_argument('-f', '--filter', dest='filters', action='append', default=[],
+ help='Only include object files whose path contains this substring (can be repeated)')
+ parser.add_argument('-o', '--out', dest='out', default='metrics',
+ help='Output path basename for JSON and Markdown files (default: metrics)')
+ args = parser.parse_args()
+
+ all_json_data = combine_maps(args.files, args.filters)
+ json_average = compute_avg(all_json_data)
+
+ if json_average is None:
+ print("No valid map files found", file=sys.stderr)
+ sys.exit(1)
+
+ linkermap.print_summary(json_average, False)
+ linkermap.write_json(json_average, args.out + '.json')
+ linkermap.write_markdown(json_average, args.out + '.md')
+
+
+if __name__ == '__main__':
+ main()