gh_sizes.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 memdf.collect
  69. import memdf.report
  70. import memdf.select
  71. import memdf.util
  72. import numpy as np # type: ignore
  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))