emdbg.analyze.size

  1# Copyright (c) 2024, Alexander Lerach
  2# Copyright (c) 2024, Auterion AG
  3# SPDX-License-Identifier: BSD-3-Clause
  4
  5from dataclasses import dataclass
  6from enum import Enum
  7from pathlib import Path
  8from shutil import which
  9import argparse
 10import logging
 11import re
 12import subprocess
 13
 14import rich
 15from rich.console import Console
 16from rich.table import Table
 17
 18_LOGGER = logging.getLogger(__name__)
 19_BLOATY_CMD = "bloaty"
 20
 21
 22# -----------------------------------------------------------------------------
 23def _rel_file_name(file_path: Path) -> str:
 24    """Return the filename relative to the CWD."""
 25    try: return str(Path(file_path).relative_to(Path().cwd()))
 26    except ValueError: return str(Path(file_path))
 27
 28
 29# -----------------------------------------------------------------------------
 30def _remove_all_spaces(s: str) -> str:
 31    """Removes all spaces from the given string."""
 32    return ''.join(s.split())
 33
 34
 35# -----------------------------------------------------------------------------
 36def is_bloaty_installed() -> bool:
 37    """Checks if bloaty is installed and contained in PATH."""
 38    return which(_BLOATY_CMD) is not None
 39
 40
 41# -----------------------------------------------------------------------------
 42class SectionType(Enum):
 43    """
 44    The `SectionType` describes what kind of type a linker section is.
 45    """
 46    text = 0,
 47    rodata = 1,
 48    bss = 2,
 49    data = 3,
 50    debug = 4,
 51    unknown = 5
 52
 53
 54# -----------------------------------------------------------------------------
 55@dataclass
 56class Section:
 57    """
 58    The class `Section` represents a linker section.
 59    """
 60    name: str
 61    """name of the linker section."""
 62    section_type: SectionType
 63    """type of the linker section."""
 64    vm_size: int
 65    """VM size that the linker section uses"""
 66    file_size: int
 67    """file size that the linker section uses"""
 68
 69
 70# -----------------------------------------------------------------------------
 71@dataclass
 72class AnalysisResult:
 73    """
 74    The class `AnalysisResult` represents the result of an inline analysis performed using `SectionAnalyzer`.
 75    """
 76    file_path: Path
 77    """path of the file that was analyzed."""
 78    sections: list[Section]
 79    """all linker sections that were found in the analyzed file and passed the given filters."""
 80    total_vm_size: int
 81    """overall VM size that is used by all sections in the analyzed file."""
 82    total_file_size: int
 83    """overall file size that is used by all sections in the analyzed file."""
 84
 85    def print(self, console: Console, overall_vm_size: int, overall_file_size: int):
 86        """
 87        Prints the analyzed file with its linker sections in an easy-to-read format.
 88
 89        :param console: console to print the output to.
 90        :param overall_vm_size: overall VM size that is used by all sections of all analyzed files.
 91        :param overall_file_size: overall file size that is used by all sections of all analyzed files.
 92        """
 93        console.print((
 94             f"{_rel_file_name(self.file_path)} -- Total VM Size: {self.total_vm_size}"
 95             f" ({(100 if overall_vm_size == 0 else (self.total_vm_size / overall_vm_size * 100)):.2f}%)"
 96             f" -- Total File Size: {self.total_file_size}"
 97             f" ({(100 if overall_file_size == 0 else (self.total_file_size / overall_file_size * 100)):.2f}%)"
 98        ))
 99
100        table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD)
101        table.add_column("Section")
102        table.add_column("Type")
103        table.add_column("VM size")
104        table.add_column("File size")
105        table.add_column("% (VM size)")
106        table.add_column("% (File size)")
107
108        for section in self.sections:
109            table.add_row(section.name, str(section.section_type.name), str(section.vm_size), str(section.file_size),
110                          "{:.2f}".format(100 if self.total_vm_size == 0 else section.vm_size / self.total_vm_size * 100),
111                          "{:.2f}".format(100 if self.total_file_size == 0 else section.file_size / self.total_file_size * 100))
112
113        console.print(table)
114
115    def to_csv_lines(self) -> str:
116        """
117        Prints the analyzed file with its linker sections into the CSV format. This allows for analysis in another tool.
118        """
119        out = ""
120
121        for section in self.sections:
122            out = out + f"{_rel_file_name(self.file_path)},{section.name},{section.section_type.name},{section.vm_size},{section.file_size}\n"
123        return out
124
125
126# -----------------------------------------------------------------------------
127class SectionAnalyzer:
128    """
129    The `SectionAnalyzer` extracts information about linker sections using bloaty.
130
131    :param map_file_content: The contents of the map file. If `None` is passed, the output may contain sections that are removed by the linker.
132    :param all_vm: Tells whether to include sections with a VM size of 0.
133    :param section_filter: Regex that will be used as a filter on section names. `None` in case of no filter.
134    :param type_filter: List of section types that shall be considered. Empty in case of no filters.
135    """
136    def __init__(self, map_file_content: str | None, all_vm: bool, section_filter: str | None, type_filter: list[str]):
137        self._map_file_content = map_file_content
138        self._all_vm = all_vm
139        self._section_filter = section_filter
140        self._type_filter = type_filter
141
142    def get_sections(self, file_path: Path) -> AnalysisResult | None:
143        """
144        Analyzes the given file using bloaty and returns all identified `Section`. In case of any errors, `None` will be returned.
145        The sections will only be added to the output, if they pass the given filters.
146        If a map file is provided all sections that are removed by the linker will be removed from the output.
147
148        :param file_path: the file to analyze.
149
150        :return: on success, all identified `Section`. Otherwise, `None` will be returned.
151        """
152        sections = []
153        total_vm_size = 0
154        total_file_size = 0
155        bloaty_output = self.get_bloaty_output(file_path)
156
157        if bloaty_output:
158            for line in bloaty_output.splitlines()[1:]:
159                line_split = line.split(",")
160
161                if len(line_split) == 3 and line_split[1].isdigit() and line_split[2].isdigit():
162                    section_type = self.get_section_type(line_split[0])
163                    section = Section(line_split[0], section_type, int(line_split[1]), int(line_split[2]))
164
165                    if section.vm_size > 0 or self._all_vm:
166                        if self.check_section_map_file(section) and self.check_section_filter(section) and self.check_section_type_filter(section):
167                            sections.append(section)
168                            total_vm_size = total_vm_size + section.vm_size
169                            total_file_size = total_file_size + section.file_size
170                else:
171                    _LOGGER.error(f"bloaty output contains invalid line: {line}")
172                    return None
173            return AnalysisResult(file_path, sections, total_vm_size, total_file_size)
174        else:
175            return None
176
177    def get_bloaty_output(self, file_path: Path) -> str | None:
178        """
179        Runs bloaty on the given file and collects its stdout if bloaty returns successfully. If it reports an error, `None` will be returned.
180        The following args are passed to bloaty:
181        - `-s vm`: Sort the output by VM size.
182        - `-n 0`: Output all sections.
183        - `--csv`: Output as CSV to stdout, which is easier to parse than the normal output.
184
185        :param file_path: the file to analyze using bloaty.
186
187        :return: on success, the bloaty stdout is returned. Otherwise, `None` will be returned.
188        """
189        res = subprocess.run([_BLOATY_CMD, "-s", "vm", "-n", "0", "--csv", f"{file_path.as_posix()}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
190
191        if res.returncode == 0:
192            return res.stdout.decode()
193        else:
194            _LOGGER.error(f"bloaty returned with: {res.returncode}. stdout: {res.stdout.decode()}. stderr: {res.stderr.decode()}")
195            return None
196
197    def get_section_type(self, section_name: str) -> SectionType:
198        """Gets the type of the section based on its name. If this is not possible the type will be set to `unkown`."""
199        for type in SectionType:
200            if section_name.startswith(f".{type.name}"):
201                return type
202        return SectionType.unknown
203
204    def check_section_map_file(self, section: Section) -> bool:
205        """Check if the section is contained in the map file (and thus was not removed by the linker)."""
206        return self._map_file_content is None or section.name in self._map_file_content
207
208    def check_section_filter(self, section: Section) -> bool:
209        """Check if the section name matches the user given regex. If no regex is given, always returns True."""
210        return self._section_filter is None or re.compile(self._section_filter).match(section.name)
211
212    def check_section_type_filter(self, section: Section) -> bool:
213        """Check if the section type matches the user given type filter. If no filter is given, always returns True."""
214        return len(self._type_filter) == 0 or section.section_type.name in self._type_filter
215
216
217# -----------------------------------------------------------------------------
218if __name__ == "__main__":
219    parser = argparse.ArgumentParser(description="Section size analyzer.")
220    parser.add_argument(
221        "--build-dir",
222        help="Path to the build directory. If not given, the current working directory will be used instead."
223    )
224    parser.add_argument(
225        "--map-file",
226        help="Path to the map file. It is used to remove sections that got removed by the linker from the analysis results."
227    )
228    parser.add_argument(
229        "--type-filter",
230        help="Comma separated list of section types to filter for. For example '-t rodata,text' would only show rodata and text sections."
231    )
232    parser.add_argument(
233        "--section-filter",
234        help="Regex that will be applied to section names. Only the ones that match the regex will be considered."
235    )
236    parser.add_argument(
237        "--csv",
238        action="store_true",
239        default=False,
240        help="Write output to a CSV file instead of stdout."
241    )
242    parser.add_argument(
243        "--all-vm",
244        action="store_true",
245        default=False,
246        help="Also include sections with a VM size of 0."
247    )
248
249    args = parser.parse_args()
250
251    build_dir = Path().cwd()
252    map_file_content = None
253    type_filter = []
254
255    if not is_bloaty_installed():
256        _LOGGER.error("bloaty is not installed. Please install it to use this tool.")
257        exit(1)
258
259    if args.build_dir:
260        build_dir = Path(args.build_dir)
261
262        if not build_dir.exists():
263            _LOGGER.error(f"Given build directory: {build_dir} does not exist.")
264            exit(1)
265
266    if args.map_file:
267        map_file_path = Path(args.map_file)
268
269        if not map_file_path.exists():
270            _LOGGER.error(f"Given map file: {map_file_path} does not exist.")
271            exit(1)
272        with open(args.map_file, "r") as f:
273            map_file_content = f.read()
274
275    if args.type_filter:
276        type_filter = _remove_all_spaces(args.type_filter).split(",")
277
278    if args.section_filter:
279        try:
280            re.compile(args.section_filter)
281        except re.error:
282            _LOGGER.error(f"Given regex: {args.section_filter} is not valid.")
283            exit(1)
284
285    console = Console()
286    section_analyzer = SectionAnalyzer(map_file_content, args.all_vm, args.section_filter, type_filter)
287
288    res = []
289    overall_vm_size = 0
290    overall_file_size = 0
291
292    for file_path in build_dir.rglob("*.a"):
293        analysis_result = section_analyzer.get_sections(file_path)
294
295        if analysis_result:
296            res.append(analysis_result)
297            overall_vm_size = overall_vm_size + analysis_result.total_vm_size
298            overall_file_size = overall_file_size + analysis_result.total_file_size
299
300    res.sort(key=lambda x: x.total_vm_size, reverse=True)
301
302    if args.csv:
303        with open("output.csv", "w") as f:
304            f.write("file_path,section,type,vm_size,file_size\n")
305
306            for analysis_result in res:
307                if analysis_result.total_vm_size > 0 or args.all_vm:
308                    f.write(analysis_result.to_csv_lines())
309    else:
310        for analysis_result in res:
311            if analysis_result.total_vm_size > 0 or args.all_vm:
312                analysis_result.print(console, overall_vm_size, overall_file_size)
313                console.print("")
def is_bloaty_installed() -> bool:
37def is_bloaty_installed() -> bool:
38    """Checks if bloaty is installed and contained in PATH."""
39    return which(_BLOATY_CMD) is not None

Checks if bloaty is installed and contained in PATH.

class SectionType(enum.Enum):
43class SectionType(Enum):
44    """
45    The `SectionType` describes what kind of type a linker section is.
46    """
47    text = 0,
48    rodata = 1,
49    bss = 2,
50    data = 3,
51    debug = 4,
52    unknown = 5

The SectionType describes what kind of type a linker section is.

text = <SectionType.text: (0,)>
rodata = <SectionType.rodata: (1,)>
bss = <SectionType.bss: (2,)>
data = <SectionType.data: (3,)>
debug = <SectionType.debug: (4,)>
unknown = <SectionType.unknown: 5>
@dataclass
class Section:
56@dataclass
57class Section:
58    """
59    The class `Section` represents a linker section.
60    """
61    name: str
62    """name of the linker section."""
63    section_type: SectionType
64    """type of the linker section."""
65    vm_size: int
66    """VM size that the linker section uses"""
67    file_size: int
68    """file size that the linker section uses"""

The class Section represents a linker section.

Section( name: str, section_type: SectionType, vm_size: int, file_size: int)
name: str

name of the linker section.

section_type: SectionType

type of the linker section.

vm_size: int

VM size that the linker section uses

file_size: int

file size that the linker section uses

@dataclass
class AnalysisResult:
 72@dataclass
 73class AnalysisResult:
 74    """
 75    The class `AnalysisResult` represents the result of an inline analysis performed using `SectionAnalyzer`.
 76    """
 77    file_path: Path
 78    """path of the file that was analyzed."""
 79    sections: list[Section]
 80    """all linker sections that were found in the analyzed file and passed the given filters."""
 81    total_vm_size: int
 82    """overall VM size that is used by all sections in the analyzed file."""
 83    total_file_size: int
 84    """overall file size that is used by all sections in the analyzed file."""
 85
 86    def print(self, console: Console, overall_vm_size: int, overall_file_size: int):
 87        """
 88        Prints the analyzed file with its linker sections in an easy-to-read format.
 89
 90        :param console: console to print the output to.
 91        :param overall_vm_size: overall VM size that is used by all sections of all analyzed files.
 92        :param overall_file_size: overall file size that is used by all sections of all analyzed files.
 93        """
 94        console.print((
 95             f"{_rel_file_name(self.file_path)} -- Total VM Size: {self.total_vm_size}"
 96             f" ({(100 if overall_vm_size == 0 else (self.total_vm_size / overall_vm_size * 100)):.2f}%)"
 97             f" -- Total File Size: {self.total_file_size}"
 98             f" ({(100 if overall_file_size == 0 else (self.total_file_size / overall_file_size * 100)):.2f}%)"
 99        ))
100
101        table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD)
102        table.add_column("Section")
103        table.add_column("Type")
104        table.add_column("VM size")
105        table.add_column("File size")
106        table.add_column("% (VM size)")
107        table.add_column("% (File size)")
108
109        for section in self.sections:
110            table.add_row(section.name, str(section.section_type.name), str(section.vm_size), str(section.file_size),
111                          "{:.2f}".format(100 if self.total_vm_size == 0 else section.vm_size / self.total_vm_size * 100),
112                          "{:.2f}".format(100 if self.total_file_size == 0 else section.file_size / self.total_file_size * 100))
113
114        console.print(table)
115
116    def to_csv_lines(self) -> str:
117        """
118        Prints the analyzed file with its linker sections into the CSV format. This allows for analysis in another tool.
119        """
120        out = ""
121
122        for section in self.sections:
123            out = out + f"{_rel_file_name(self.file_path)},{section.name},{section.section_type.name},{section.vm_size},{section.file_size}\n"
124        return out

The class AnalysisResult represents the result of an inline analysis performed using SectionAnalyzer.

AnalysisResult( file_path: pathlib.Path, sections: list[Section], total_vm_size: int, total_file_size: int)
file_path: pathlib.Path

path of the file that was analyzed.

sections: list[Section]

all linker sections that were found in the analyzed file and passed the given filters.

total_vm_size: int

overall VM size that is used by all sections in the analyzed file.

total_file_size: int

overall file size that is used by all sections in the analyzed file.

def print( self, console: rich.console.Console, overall_vm_size: int, overall_file_size: int):
 86    def print(self, console: Console, overall_vm_size: int, overall_file_size: int):
 87        """
 88        Prints the analyzed file with its linker sections in an easy-to-read format.
 89
 90        :param console: console to print the output to.
 91        :param overall_vm_size: overall VM size that is used by all sections of all analyzed files.
 92        :param overall_file_size: overall file size that is used by all sections of all analyzed files.
 93        """
 94        console.print((
 95             f"{_rel_file_name(self.file_path)} -- Total VM Size: {self.total_vm_size}"
 96             f" ({(100 if overall_vm_size == 0 else (self.total_vm_size / overall_vm_size * 100)):.2f}%)"
 97             f" -- Total File Size: {self.total_file_size}"
 98             f" ({(100 if overall_file_size == 0 else (self.total_file_size / overall_file_size * 100)):.2f}%)"
 99        ))
100
101        table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD)
102        table.add_column("Section")
103        table.add_column("Type")
104        table.add_column("VM size")
105        table.add_column("File size")
106        table.add_column("% (VM size)")
107        table.add_column("% (File size)")
108
109        for section in self.sections:
110            table.add_row(section.name, str(section.section_type.name), str(section.vm_size), str(section.file_size),
111                          "{:.2f}".format(100 if self.total_vm_size == 0 else section.vm_size / self.total_vm_size * 100),
112                          "{:.2f}".format(100 if self.total_file_size == 0 else section.file_size / self.total_file_size * 100))
113
114        console.print(table)

Prints the analyzed file with its linker sections in an easy-to-read format.

Parameters
  • console: console to print the output to.
  • overall_vm_size: overall VM size that is used by all sections of all analyzed files.
  • overall_file_size: overall file size that is used by all sections of all analyzed files.
def to_csv_lines(self) -> str:
116    def to_csv_lines(self) -> str:
117        """
118        Prints the analyzed file with its linker sections into the CSV format. This allows for analysis in another tool.
119        """
120        out = ""
121
122        for section in self.sections:
123            out = out + f"{_rel_file_name(self.file_path)},{section.name},{section.section_type.name},{section.vm_size},{section.file_size}\n"
124        return out

Prints the analyzed file with its linker sections into the CSV format. This allows for analysis in another tool.

class SectionAnalyzer:
128class SectionAnalyzer:
129    """
130    The `SectionAnalyzer` extracts information about linker sections using bloaty.
131
132    :param map_file_content: The contents of the map file. If `None` is passed, the output may contain sections that are removed by the linker.
133    :param all_vm: Tells whether to include sections with a VM size of 0.
134    :param section_filter: Regex that will be used as a filter on section names. `None` in case of no filter.
135    :param type_filter: List of section types that shall be considered. Empty in case of no filters.
136    """
137    def __init__(self, map_file_content: str | None, all_vm: bool, section_filter: str | None, type_filter: list[str]):
138        self._map_file_content = map_file_content
139        self._all_vm = all_vm
140        self._section_filter = section_filter
141        self._type_filter = type_filter
142
143    def get_sections(self, file_path: Path) -> AnalysisResult | None:
144        """
145        Analyzes the given file using bloaty and returns all identified `Section`. In case of any errors, `None` will be returned.
146        The sections will only be added to the output, if they pass the given filters.
147        If a map file is provided all sections that are removed by the linker will be removed from the output.
148
149        :param file_path: the file to analyze.
150
151        :return: on success, all identified `Section`. Otherwise, `None` will be returned.
152        """
153        sections = []
154        total_vm_size = 0
155        total_file_size = 0
156        bloaty_output = self.get_bloaty_output(file_path)
157
158        if bloaty_output:
159            for line in bloaty_output.splitlines()[1:]:
160                line_split = line.split(",")
161
162                if len(line_split) == 3 and line_split[1].isdigit() and line_split[2].isdigit():
163                    section_type = self.get_section_type(line_split[0])
164                    section = Section(line_split[0], section_type, int(line_split[1]), int(line_split[2]))
165
166                    if section.vm_size > 0 or self._all_vm:
167                        if self.check_section_map_file(section) and self.check_section_filter(section) and self.check_section_type_filter(section):
168                            sections.append(section)
169                            total_vm_size = total_vm_size + section.vm_size
170                            total_file_size = total_file_size + section.file_size
171                else:
172                    _LOGGER.error(f"bloaty output contains invalid line: {line}")
173                    return None
174            return AnalysisResult(file_path, sections, total_vm_size, total_file_size)
175        else:
176            return None
177
178    def get_bloaty_output(self, file_path: Path) -> str | None:
179        """
180        Runs bloaty on the given file and collects its stdout if bloaty returns successfully. If it reports an error, `None` will be returned.
181        The following args are passed to bloaty:
182        - `-s vm`: Sort the output by VM size.
183        - `-n 0`: Output all sections.
184        - `--csv`: Output as CSV to stdout, which is easier to parse than the normal output.
185
186        :param file_path: the file to analyze using bloaty.
187
188        :return: on success, the bloaty stdout is returned. Otherwise, `None` will be returned.
189        """
190        res = subprocess.run([_BLOATY_CMD, "-s", "vm", "-n", "0", "--csv", f"{file_path.as_posix()}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
191
192        if res.returncode == 0:
193            return res.stdout.decode()
194        else:
195            _LOGGER.error(f"bloaty returned with: {res.returncode}. stdout: {res.stdout.decode()}. stderr: {res.stderr.decode()}")
196            return None
197
198    def get_section_type(self, section_name: str) -> SectionType:
199        """Gets the type of the section based on its name. If this is not possible the type will be set to `unkown`."""
200        for type in SectionType:
201            if section_name.startswith(f".{type.name}"):
202                return type
203        return SectionType.unknown
204
205    def check_section_map_file(self, section: Section) -> bool:
206        """Check if the section is contained in the map file (and thus was not removed by the linker)."""
207        return self._map_file_content is None or section.name in self._map_file_content
208
209    def check_section_filter(self, section: Section) -> bool:
210        """Check if the section name matches the user given regex. If no regex is given, always returns True."""
211        return self._section_filter is None or re.compile(self._section_filter).match(section.name)
212
213    def check_section_type_filter(self, section: Section) -> bool:
214        """Check if the section type matches the user given type filter. If no filter is given, always returns True."""
215        return len(self._type_filter) == 0 or section.section_type.name in self._type_filter

The SectionAnalyzer extracts information about linker sections using bloaty.

Parameters
  • map_file_content: The contents of the map file. If None is passed, the output may contain sections that are removed by the linker.
  • all_vm: Tells whether to include sections with a VM size of 0.
  • section_filter: Regex that will be used as a filter on section names. None in case of no filter.
  • type_filter: List of section types that shall be considered. Empty in case of no filters.
SectionAnalyzer( map_file_content: str | None, all_vm: bool, section_filter: str | None, type_filter: list[str])
137    def __init__(self, map_file_content: str | None, all_vm: bool, section_filter: str | None, type_filter: list[str]):
138        self._map_file_content = map_file_content
139        self._all_vm = all_vm
140        self._section_filter = section_filter
141        self._type_filter = type_filter
def get_sections( self, file_path: pathlib.Path) -> AnalysisResult | None:
143    def get_sections(self, file_path: Path) -> AnalysisResult | None:
144        """
145        Analyzes the given file using bloaty and returns all identified `Section`. In case of any errors, `None` will be returned.
146        The sections will only be added to the output, if they pass the given filters.
147        If a map file is provided all sections that are removed by the linker will be removed from the output.
148
149        :param file_path: the file to analyze.
150
151        :return: on success, all identified `Section`. Otherwise, `None` will be returned.
152        """
153        sections = []
154        total_vm_size = 0
155        total_file_size = 0
156        bloaty_output = self.get_bloaty_output(file_path)
157
158        if bloaty_output:
159            for line in bloaty_output.splitlines()[1:]:
160                line_split = line.split(",")
161
162                if len(line_split) == 3 and line_split[1].isdigit() and line_split[2].isdigit():
163                    section_type = self.get_section_type(line_split[0])
164                    section = Section(line_split[0], section_type, int(line_split[1]), int(line_split[2]))
165
166                    if section.vm_size > 0 or self._all_vm:
167                        if self.check_section_map_file(section) and self.check_section_filter(section) and self.check_section_type_filter(section):
168                            sections.append(section)
169                            total_vm_size = total_vm_size + section.vm_size
170                            total_file_size = total_file_size + section.file_size
171                else:
172                    _LOGGER.error(f"bloaty output contains invalid line: {line}")
173                    return None
174            return AnalysisResult(file_path, sections, total_vm_size, total_file_size)
175        else:
176            return None

Analyzes the given file using bloaty and returns all identified Section. In case of any errors, None will be returned. The sections will only be added to the output, if they pass the given filters. If a map file is provided all sections that are removed by the linker will be removed from the output.

Parameters
  • file_path: the file to analyze.
Returns

on success, all identified Section. Otherwise, None will be returned.

def get_bloaty_output(self, file_path: pathlib.Path) -> str | None:
178    def get_bloaty_output(self, file_path: Path) -> str | None:
179        """
180        Runs bloaty on the given file and collects its stdout if bloaty returns successfully. If it reports an error, `None` will be returned.
181        The following args are passed to bloaty:
182        - `-s vm`: Sort the output by VM size.
183        - `-n 0`: Output all sections.
184        - `--csv`: Output as CSV to stdout, which is easier to parse than the normal output.
185
186        :param file_path: the file to analyze using bloaty.
187
188        :return: on success, the bloaty stdout is returned. Otherwise, `None` will be returned.
189        """
190        res = subprocess.run([_BLOATY_CMD, "-s", "vm", "-n", "0", "--csv", f"{file_path.as_posix()}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
191
192        if res.returncode == 0:
193            return res.stdout.decode()
194        else:
195            _LOGGER.error(f"bloaty returned with: {res.returncode}. stdout: {res.stdout.decode()}. stderr: {res.stderr.decode()}")
196            return None

Runs bloaty on the given file and collects its stdout if bloaty returns successfully. If it reports an error, None will be returned. The following args are passed to bloaty:

  • -s vm: Sort the output by VM size.
  • -n 0: Output all sections.
  • --csv: Output as CSV to stdout, which is easier to parse than the normal output.
Parameters
  • file_path: the file to analyze using bloaty.
Returns

on success, the bloaty stdout is returned. Otherwise, None will be returned.

def get_section_type(self, section_name: str) -> SectionType:
198    def get_section_type(self, section_name: str) -> SectionType:
199        """Gets the type of the section based on its name. If this is not possible the type will be set to `unkown`."""
200        for type in SectionType:
201            if section_name.startswith(f".{type.name}"):
202                return type
203        return SectionType.unknown

Gets the type of the section based on its name. If this is not possible the type will be set to unkown.

def check_section_map_file(self, section: Section) -> bool:
205    def check_section_map_file(self, section: Section) -> bool:
206        """Check if the section is contained in the map file (and thus was not removed by the linker)."""
207        return self._map_file_content is None or section.name in self._map_file_content

Check if the section is contained in the map file (and thus was not removed by the linker).

def check_section_filter(self, section: Section) -> bool:
209    def check_section_filter(self, section: Section) -> bool:
210        """Check if the section name matches the user given regex. If no regex is given, always returns True."""
211        return self._section_filter is None or re.compile(self._section_filter).match(section.name)

Check if the section name matches the user given regex. If no regex is given, always returns True.

def check_section_type_filter(self, section: Section) -> bool:
213    def check_section_type_filter(self, section: Section) -> bool:
214        """Check if the section type matches the user given type filter. If no filter is given, always returns True."""
215        return len(self._type_filter) == 0 or section.section_type.name in self._type_filter

Check if the section type matches the user given type filter. If no filter is given, always returns True.