#!/usr/bin/env python3 # # Copyright (c) 2021 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """ This is similar to scripts/tools/memory/report_summary.py, but generates a specific output format with a simplified interface for use in github workflows. Usage: gh_sizes.py ‹platform› ‹config› ‹target› ‹binary› [‹output›] [‹option›…] ‹platform› - Platform name, corresponding to a config file in scripts/tools/memory/platform/ ‹config› - Configuration identification string. ‹target› - Build artifact identification string. ‹binary› - Binary build artifact. ‹output› - Output name or directory. ‹option›… - Other options as for report_summary. This script also expects certain environment variables, which can be set in a github workflow as follows: - name: Set up environment for size reports if: ${{ !env.ACT }} env: GH_CONTEXT: ${{ toJson(github) }} run: gh_sizes_environment.py "${GH_CONTEXT}" Default output file is {platform}-{configname}-{buildname}-sizes.json in the binary's directory. This file has the form: { "platform": "‹platform›", "config": "‹config›", "target": "‹target›", "time": 1317645296, "input": "‹binary›", "event": "pull_request", "hash": "496620796f752063616e20726561642074686973", "parent": "20796f752061726520746f6f20636c6f73652e0a", "pr": 12345, "by": "section", "ref": "refs/pull/12345/merge" "frames": { "section": [ {"section": ".bss", "size": 260496}, {"section": ".data", "size": 1648}, {"section": ".text", "size": 740236} ], "wr": [ {"wr": 0, "size": 262144}, {"wr": 1, "size": 74023} ] } } """ import datetime import logging import os import pathlib import sys import memdf.collect import memdf.report import memdf.select import memdf.util import numpy as np # type: ignore from memdf import Config, ConfigDescription, DFs, SectionDF, SegmentDF PLATFORM_CONFIG_DIR = pathlib.Path('scripts/tools/memory/platform') CONFIG: ConfigDescription = { 'event': { 'help': 'Github workflow event name', 'metavar': 'NAME', 'default': os.environ.get('GITHUB_EVENT_NAME'), }, 'pr': { 'help': 'Github PR number', 'metavar': 'NUMBER', 'default': int(os.environ.get('GH_EVENT_PR', '0')), }, 'hash': { 'help': 'Current commit hash', 'metavar': 'HASH', 'default': os.environ.get('GH_EVENT_HASH'), }, 'parent': { 'help': 'Parent commit hash', 'metavar': 'HASH', 'default': os.environ.get('GH_EVENT_PARENT'), }, 'ref': { 'help': 'Target ref', 'metavar': 'REF', 'default': os.environ.get('GH_EVENT_REF'), }, 'timestamp': { 'help': 'Build timestamp', 'metavar': 'TIME', 'default': int(float( os.environ.get('GH_EVENT_TIMESTAMP') or datetime.datetime.now().timestamp())), }, } def main(argv): status = 0 try: _, platform, config_name, target_name, binary, *args = argv except ValueError: program = pathlib.Path(argv[0]) logging.error( """ Usage: %s platform config target binary [output] [options] This is intended for use in github workflows. For other purposes, a general program for the same operations is %s/report_summary.py """, program.name, program.parent) return 1 try: config_file = pathlib.Path(platform) if config_file.is_file(): platform = config_file.stem else: config_file = (PLATFORM_CONFIG_DIR / platform).with_suffix('.cfg') output_base = f'{platform}-{config_name}-{target_name}-sizes.json' if args and not args[0].startswith('-'): out, *args = args output = pathlib.Path(out) if out.endswith('/') and not output.exists(): output.mkdir(parents=True) if output.is_dir(): output = output / output_base else: output = pathlib.Path(binary).parent / output_base config = Config().init({ **memdf.util.config.CONFIG, **memdf.collect.CONFIG, **memdf.select.CONFIG, **memdf.report.OUTPUT_CONFIG, **CONFIG, }) config.put('output.file', output) config.put('output.format', 'json_records') if config_file.is_file(): config.read_config_file(config_file) else: logging.warning('Missing config file: %s', config_file) config.parse([argv[0]] + args) config.put('output.metadata.platform', platform) config.put('output.metadata.config', config_name) config.put('output.metadata.target', target_name) config.put('output.metadata.time', config['timestamp']) config.put('output.metadata.input', binary) config.put('output.metadata.by', 'section') for key in ['event', 'hash', 'parent', 'pr', 'ref']: if value := config[key]: config.putl(['output', 'metadata', key], value) collected: DFs = memdf.collect.collect_files(config, [binary]) # Aggregate loaded segments, by writable (flash) or not (RAM). segments = collected[SegmentDF.name] segments['segment'] = segments.index segments['wr'] = ((segments['flags'] & 2) != 0).convert_dtypes( convert_boolean=False, convert_integer=True) segment_summary = segments[segments['type'] == 'PT_LOAD'][[ 'wr', 'size' ]].groupby('wr').aggregate(np.sum).reset_index().astype( {'size': np.int64}) segment_summary.attrs['name'] = "wr" sections = collected[SectionDF.name] sections = sections.join(on='segment', how='left', other=segments, rsuffix='-segment') section_summary = sections[['section', 'size', 'wr']].sort_values(by='section') section_summary.attrs['name'] = "section" summaries = { 'section': section_summary, 'memory': segment_summary, } # Write configured (json) report to the output file. memdf.report.write_dfs(config, summaries) # Write text report to stdout. memdf.report.write_dfs(config, summaries, sys.stdout, 'simple', floatfmt='.0f') except Exception as exception: raise exception return status if __name__ == '__main__': sys.exit(main(sys.argv))