gh_sizes.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2021 Project CHIP Authors
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """
  18. This is similar to scripts/tools/memory/report_summary.py, but generates
  19. a specific output format with a simplified interface for use in github
  20. workflows.
  21. Usage: gh_sizes.py ‹platform› ‹config› ‹target› ‹binary› [‹output›] [‹option›…]
  22. ‹platform› - Platform name, corresponding to a config file
  23. in scripts/tools/memory/platform/
  24. ‹config› - Configuration identification string.
  25. ‹target› - Build artifact identification string.
  26. ‹binary› - Binary build artifact.
  27. ‹output› - Output name or directory.
  28. ‹option›… - Other options as for report_summary.
  29. This script also expects certain environment variables, which can be set in a
  30. github workflow as follows:
  31. - name: Set up environment for size reports
  32. if: ${{ !env.ACT }}
  33. env:
  34. GH_CONTEXT: ${{ toJson(github) }}
  35. run: gh_sizes_environment.py "${GH_CONTEXT}"
  36. Default output file is {platform}-{configname}-{buildname}-sizes.json in the
  37. binary's directory. This file has the form:
  38. {
  39. "platform": "‹platform›",
  40. "config": "‹config›",
  41. "target": "‹target›",
  42. "time": 1317645296,
  43. "input": "‹binary›",
  44. "event": "pull_request",
  45. "hash": "496620796f752063616e20726561642074686973",
  46. "parent": "20796f752061726520746f6f20636c6f73652e0a",
  47. "pr": 12345,
  48. "by": "section",
  49. "ref": "refs/pull/12345/merge"
  50. "frames": {
  51. "section": [
  52. {"section": ".bss", "size": 260496},
  53. {"section": ".data", "size": 1648},
  54. {"section": ".text", "size": 740236}
  55. ],
  56. "wr": [
  57. {"wr": 0, "size": 262144},
  58. {"wr": 1, "size": 74023}
  59. ]
  60. }
  61. }
  62. """
  63. import datetime
  64. import logging
  65. import os
  66. import pathlib
  67. import sys
  68. import numpy as np # type: ignore
  69. import memdf.collect
  70. import memdf.report
  71. import memdf.select
  72. import memdf.util
  73. from memdf import Config, ConfigDescription, DFs, SectionDF, SegmentDF
  74. PLATFORM_CONFIG_DIR = pathlib.Path('scripts/tools/memory/platform')
  75. CONFIG: ConfigDescription = {
  76. 'event': {
  77. 'help': 'Github workflow event name',
  78. 'metavar': 'NAME',
  79. 'default': os.environ.get('GITHUB_EVENT_NAME'),
  80. },
  81. 'pr': {
  82. 'help': 'Github PR number',
  83. 'metavar': 'NUMBER',
  84. 'default': int(os.environ.get('GH_EVENT_PR', '0')),
  85. },
  86. 'hash': {
  87. 'help': 'Current commit hash',
  88. 'metavar': 'HASH',
  89. 'default': os.environ.get('GH_EVENT_HASH'),
  90. },
  91. 'parent': {
  92. 'help': 'Parent commit hash',
  93. 'metavar': 'HASH',
  94. 'default': os.environ.get('GH_EVENT_PARENT'),
  95. },
  96. 'ref': {
  97. 'help': 'Target ref',
  98. 'metavar': 'REF',
  99. 'default': os.environ.get('GH_EVENT_REF'),
  100. },
  101. 'timestamp': {
  102. 'help': 'Build timestamp',
  103. 'metavar': 'TIME',
  104. 'default': int(float(
  105. os.environ.get('GH_EVENT_TIMESTAMP')
  106. or datetime.datetime.now().timestamp())),
  107. },
  108. }
  109. def main(argv):
  110. status = 0
  111. try:
  112. _, platform, config_name, target_name, binary, *args = argv
  113. except ValueError:
  114. program = pathlib.Path(argv[0])
  115. logging.error(
  116. """
  117. Usage: %s platform config target binary [output] [options]
  118. This is intended for use in github workflows.
  119. For other purposes, a general program for the same operations is
  120. %s/report_summary.py
  121. """, program.name, program.parent)
  122. return 1
  123. try:
  124. config_file = pathlib.Path(platform)
  125. if config_file.is_file():
  126. platform = config_file.stem
  127. else:
  128. config_file = (PLATFORM_CONFIG_DIR / platform).with_suffix('.cfg')
  129. output_base = f'{platform}-{config_name}-{target_name}-sizes.json'
  130. if args and not args[0].startswith('-'):
  131. out, *args = args
  132. output = pathlib.Path(out)
  133. if out.endswith('/') and not output.exists():
  134. output.mkdir(parents=True)
  135. if output.is_dir():
  136. output = output / output_base
  137. else:
  138. output = pathlib.Path(binary).parent / output_base
  139. config = Config().init({
  140. **memdf.util.config.CONFIG,
  141. **memdf.collect.CONFIG,
  142. **memdf.select.CONFIG,
  143. **memdf.report.OUTPUT_CONFIG,
  144. **CONFIG,
  145. })
  146. config.put('output.file', output)
  147. config.put('output.format', 'json_records')
  148. if config_file.is_file():
  149. config.read_config_file(config_file)
  150. else:
  151. logging.warning('Missing config file: %s', config_file)
  152. config.parse([argv[0]] + args)
  153. config.put('output.metadata.platform', platform)
  154. config.put('output.metadata.config', config_name)
  155. config.put('output.metadata.target', target_name)
  156. config.put('output.metadata.time', config['timestamp'])
  157. config.put('output.metadata.input', binary)
  158. config.put('output.metadata.by', 'section')
  159. for key in ['event', 'hash', 'parent', 'pr', 'ref']:
  160. if value := config[key]:
  161. config.putl(['output', 'metadata', key], value)
  162. collected: DFs = memdf.collect.collect_files(config, [binary])
  163. # Aggregate loaded segments, by writable (flash) or not (RAM).
  164. segments = collected[SegmentDF.name]
  165. segments['segment'] = segments.index
  166. segments['wr'] = ((segments['flags'] & 2) != 0).convert_dtypes(
  167. convert_boolean=False, convert_integer=True)
  168. segment_summary = segments[segments['type'] == 'PT_LOAD'][[
  169. 'wr', 'size'
  170. ]].groupby('wr').aggregate(np.sum).reset_index().astype(
  171. {'size': np.int64})
  172. segment_summary.attrs['name'] = "wr"
  173. sections = collected[SectionDF.name]
  174. sections = sections.join(on='segment',
  175. how='left',
  176. other=segments,
  177. rsuffix='-segment')
  178. section_summary = sections[['section', 'size',
  179. 'wr']].sort_values(by='section')
  180. section_summary.attrs['name'] = "section"
  181. summaries = {
  182. 'section': section_summary,
  183. 'memory': segment_summary,
  184. }
  185. # Write configured (json) report to the output file.
  186. memdf.report.write_dfs(config, summaries)
  187. # Write text report to stdout.
  188. memdf.report.write_dfs(config,
  189. summaries,
  190. sys.stdout,
  191. 'simple',
  192. floatfmt='.0f')
  193. except Exception as exception:
  194. raise exception
  195. return status
  196. if __name__ == '__main__':
  197. sys.exit(main(sys.argv))