Skip to content

Commit e31b93c

Browse files
frolvCQ Bot Account
authored and
CQ Bot Account
committed
pw_bloat: Simple pw bloat CLI command
This adds a command to the Pigweed CLI which can be used to run size reports on binaries without having to go through the GN build. The command initially supports single binary size reports on ELF files which are linked using pw_bloat memory region symbols. Additional data sources can be specified to be displayed hierarchically under the root memoryregions. Example usage (no child data sources): $ pw bloat out/docs/obj/pw_result/size_report/bin/ladder_and_then.elf ▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄ ▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌ ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌ ▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌ ▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀ +----------------------+---------+ | memoryregions | sizes | +======================+=========+ |FLASH |1,048,064| |RAM | 196,608| |VECTOR_TABLE | 512| +======================+=========+ |Total |1,245,184| +----------------------+---------+ Change-Id: Icc34a085cc62ce3fcf0697f04aaed50c6d559024 Requires: pigweed-internal:32580 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/112314 Reviewed-by: Armando Montanez <amontanez@google.com> Reviewed-by: Ewout van Bekkum <ewout@google.com> Commit-Queue: Alexei Frolov <frolv@google.com>
1 parent d881950 commit e31b93c

File tree

8 files changed

+149
-27
lines changed

8 files changed

+149
-27
lines changed

pw_bloat/docs.rst

+41-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,48 @@ Bloat report cards allow tracking the memory usage of a system over time as code
1212
changes are made and provide a breakdown of which parts of the code have the
1313
largest size impact.
1414

15+
``pw bloat`` CLI command
16+
========================
17+
``pw_bloat`` includes a plugin for the Pigweed command line capable of running
18+
size reports on ELF binaries.
19+
20+
.. note::
21+
22+
The bloat CLI plugin is still experimental and only supports a small subset
23+
of ``pw_bloat``'s capabilities. Notably, it currently only runs on binaries
24+
which define memory region symbols; refer to the
25+
:ref:`memoryregions documentation <module-pw_bloat-memoryregions>`
26+
for details.
27+
28+
Basic usage
29+
^^^^^^^^^^^
30+
31+
**Running a size report on a single executable**
32+
33+
.. code-block:: sh
34+
35+
$ pw bloat out/docs/obj/pw_result/size_report/bin/ladder_and_then.elf
36+
37+
▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
38+
▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌
39+
▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌
40+
▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌
41+
▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
42+
43+
+----------------------+---------+
44+
| memoryregions | sizes |
45+
+======================+=========+
46+
|FLASH |1,048,064|
47+
|RAM | 196,608|
48+
|VECTOR_TABLE | 512|
49+
+======================+=========+
50+
|Total |1,245,184|
51+
+----------------------+---------+
52+
1553
.. _bloat-howto:
1654

17-
Defining size reports
18-
=====================
55+
Defining size reports in GN
56+
===========================
1957

2058
Diff Size Reports
2159
^^^^^^^^^^^^^^^^^
@@ -364,6 +402,7 @@ Note that linker scripts are not natively supported by GN and can't be provided
364402
through ``deps``, the ``bloat_macros.ld`` must be passed in the ``includes``
365403
list.
366404

405+
.. _module-pw_bloat-memoryregions:
367406

368407
``memoryregions`` data source
369408
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

pw_bloat/py/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pw_python_package("py") {
2424
]
2525
sources = [
2626
"pw_bloat/__init__.py",
27+
"pw_bloat/__main__.py",
2728
"pw_bloat/bloat.py",
2829
"pw_bloat/bloaty_config.py",
2930
"pw_bloat/label.py",

pw_bloat/py/label_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import os
1919
import logging
2020
import sys
21-
from pw_bloat.label import from_bloaty_tsv, DataSourceMap, Label
21+
from pw_bloat.label import DataSourceMap, Label
2222

2323
LIST_LABELS = [
2424
Label(name='main()', size=30, parents=tuple(['FLASH', '.code'])),
@@ -35,7 +35,7 @@ def get_test_map():
3535
pw_root = os.environ.get("PW ROOT")
3636
filename = f"{pw_root}/pigweed/pw_bloat/test_label.csv"
3737
with open(filename, 'r') as csvfile:
38-
ds_map = from_bloaty_tsv(csvfile)
38+
ds_map = DataSourceMap.from_bloaty_tsv(csvfile)
3939
capacity_patterns = [("^__TEXT$", 459), ("^_", 920834)]
4040
for cap_pattern, cap_size in capacity_patterns:
4141
ds_map.add_capacity(cap_pattern, cap_size)

pw_bloat/py/pw_bloat/__main__.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2022 The Pigweed Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4+
# use this file except in compliance with the License. You may obtain a copy of
5+
# the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations under
13+
# the License.
14+
"""Size reporting utilities."""
15+
16+
import argparse
17+
import logging
18+
from pathlib import Path
19+
import sys
20+
21+
from pw_bloat import bloat
22+
from pw_bloat.label import DataSourceMap
23+
from pw_bloat.label_output import BloatTableOutput
24+
import pw_cli.log
25+
26+
_LOG = logging.getLogger(__name__)
27+
28+
29+
def _parse_args() -> argparse.Namespace:
30+
parser = argparse.ArgumentParser(description=__doc__)
31+
32+
parser.add_argument('binary',
33+
help='Path to the ELF file to analyze',
34+
type=Path)
35+
parser.add_argument(
36+
'-d',
37+
'--data-sources',
38+
help='Comma-separated list of additional Bloaty data sources to report',
39+
type=lambda s: s.split(','),
40+
default=())
41+
parser.add_argument('-v',
42+
'--verbose',
43+
help=('Print all log messages '
44+
'(only errors are printed by default)'),
45+
action='store_true')
46+
47+
return parser.parse_args()
48+
49+
50+
def main() -> int:
51+
"""Run binary size reports."""
52+
53+
args = _parse_args()
54+
55+
if not args.verbose:
56+
pw_cli.log.set_all_loggers_minimum_level(logging.ERROR)
57+
58+
try:
59+
bloaty_tsv = bloat.memory_regions_size_report(
60+
args.binary,
61+
additional_data_sources=args.data_sources,
62+
extra_args=('--tsv', ))
63+
except bloat.NoMemoryRegions:
64+
_LOG.error('Executable %s does not define any bloat memory regions',
65+
args.binary)
66+
_LOG.error(
67+
'Refer to https://pigweed.dev/pw_bloat/#memoryregions-data-source')
68+
_LOG.error('for information on how to configure them.')
69+
return 1
70+
71+
data_source_map = DataSourceMap.from_bloaty_tsv(bloaty_tsv)
72+
73+
print(BloatTableOutput(data_source_map).create_table())
74+
return 0
75+
76+
77+
if __name__ == '__main__':
78+
sys.exit(main())

pw_bloat/py/pw_bloat/bloat.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import pw_cli.log
2929

3030
from pw_bloat.bloaty_config import generate_bloaty_config
31-
from pw_bloat.label import from_bloaty_tsv
31+
from pw_bloat.label import DataSourceMap
3232
from pw_bloat.label_output import (BloatTableOutput, LineCharset, RstOutput,
3333
AsciiCharset)
3434

@@ -106,7 +106,7 @@ def memory_regions_size_report(
106106
elf: Path,
107107
additional_data_sources: Iterable[str] = (),
108108
extra_args: Iterable[str] = (),
109-
) -> str:
109+
) -> Iterable[str]:
110110
"""Runs a size report on an ELF file using pw_bloat memory region symbols.
111111
112112
Arguments:
@@ -137,7 +137,7 @@ def memory_regions_size_report(
137137
bloaty_config.name,
138138
data_sources=('memoryregions', *additional_data_sources),
139139
extra_args=extra_args,
140-
).decode('utf-8')
140+
).decode('utf-8').splitlines()
141141

142142

143143
def write_file(filename: str, contents: str, out_dir_file: str) -> None:
@@ -162,11 +162,12 @@ def single_target_output(target: str, bloaty_config: str, target_out_file: str,
162162
return 1
163163

164164
single_tsv = single_output.decode().splitlines()
165-
single_report = BloatTableOutput(from_bloaty_tsv(single_tsv),
165+
single_report = BloatTableOutput(DataSourceMap.from_bloaty_tsv(single_tsv),
166166
MAX_COL_WIDTH, LineCharset)
167167

168-
rst_single_report = BloatTableOutput(from_bloaty_tsv(single_tsv),
169-
MAX_COL_WIDTH, AsciiCharset, True)
168+
rst_single_report = BloatTableOutput(
169+
DataSourceMap.from_bloaty_tsv(single_tsv), MAX_COL_WIDTH, AsciiCharset,
170+
True)
170171

171172
single_report_table = single_report.create_table()
172173

@@ -242,8 +243,9 @@ def main() -> int:
242243
if not single_output_target or not single_output_base:
243244
continue
244245

245-
base_dsm = from_bloaty_tsv(single_output_base.decode().splitlines())
246-
target_dsm = from_bloaty_tsv(
246+
base_dsm = DataSourceMap.from_bloaty_tsv(
247+
single_output_base.decode().splitlines())
248+
target_dsm = DataSourceMap.from_bloaty_tsv(
247249
single_output_target.decode().splitlines())
248250
diff_dsm = target_dsm.diff(base_dsm)
249251

pw_bloat/py/pw_bloat/label.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ class DataSourceMap:
113113
"""
114114
_BASE_TOTAL_LABEL = 'total'
115115

116+
@classmethod
117+
def from_bloaty_tsv(cls, raw_tsv: Iterable[str]) -> 'DataSourceMap':
118+
"""Read in Bloaty TSV output and store in DataSourceMap."""
119+
reader = csv.reader(raw_tsv, delimiter='\t')
120+
top_row = next(reader)
121+
vmsize_index = top_row.index('vmsize')
122+
ds_map_tsv = cls(top_row[:vmsize_index])
123+
for row in reader:
124+
ds_map_tsv.insert_label_hierachy(row[:vmsize_index],
125+
int(row[vmsize_index]))
126+
return ds_map_tsv
127+
116128
def __init__(self, data_sources_names: Iterable[str]):
117129
self._data_sources = list(
118130
_DataSource(name) for name in ['base', *data_sources_names])
@@ -246,15 +258,3 @@ def has_diff_sublabels(self, top_ds_label: str) -> bool:
246258
== top_ds_label):
247259
return True
248260
return False
249-
250-
251-
def from_bloaty_tsv(raw_tsv: Iterable[str]) -> DataSourceMap:
252-
"""Read in Bloaty TSV output and store in DataSourceMap."""
253-
reader = csv.reader(raw_tsv, delimiter='\t')
254-
top_row = next(reader)
255-
vmsize_index = top_row.index('vmsize')
256-
ds_map_tsv = DataSourceMap(top_row[:vmsize_index])
257-
for row in reader:
258-
ds_map_tsv.insert_label_hierachy(row[:vmsize_index],
259-
int(row[vmsize_index]))
260-
return ds_map_tsv

pw_bloat/py/pw_bloat/label_output.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class BloatTableOutput:
7777
"""ASCII Table generator from DataSourceMap."""
7878

7979
_RST_PADDING_WIDTH = 6
80+
_DEFAULT_MAX_WIDTH = 80
8081

8182
class _LabelContent(NamedTuple):
8283
name: str
@@ -85,7 +86,7 @@ class _LabelContent(NamedTuple):
8586

8687
def __init__(self,
8788
ds_map: Union[DiffDataSourceMap, DataSourceMap],
88-
col_max_width: int,
89+
col_max_width: int = _DEFAULT_MAX_WIDTH,
8990
charset: Union[Type[AsciiCharset],
9091
Type[LineCharset]] = AsciiCharset,
9192
rst_output: bool = False,

pw_cli/py/pw_cli/pw_command_plugins.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ def _register_builtin_plugins(registry: plugins.Registry) -> None:
2929
"""Registers the commands that are included with pw by default."""
3030

3131
# Register these by name to avoid circular dependencies.
32+
registry.register_by_name('bloat', 'pw_bloat.__main__', 'main')
3233
registry.register_by_name('doctor', 'pw_doctor.doctor', 'main')
33-
registry.register_by_name('python-packages',
34-
'pw_env_setup.python_packages', 'main')
3534
registry.register_by_name('format', 'pw_presubmit.format_code', 'main')
3635
registry.register_by_name('keep-sorted', 'pw_presubmit.keep_sorted',
3736
'main')
3837
registry.register_by_name('logdemo', 'pw_cli.log', 'main')
3938
registry.register_by_name('module', 'pw_module.__main__', 'main')
39+
registry.register_by_name('python-packages',
40+
'pw_env_setup.python_packages', 'main')
4041
registry.register_by_name('test', 'pw_unit_test.test_runner', 'main')
4142
registry.register_by_name('watch', 'pw_watch.watch', 'main')
4243

0 commit comments

Comments
 (0)