ETISS 0.11.2
ExtendableTranslatingInstructionSetSimulator(version0.11.2)
Loading...
Searching...
No Matches
get_metrics.py
Go to the documentation of this file.
1# SPDX-License-Identifier: BSD-3-Clause
2#
3# This file is part of ETISS. It is licensed under the BSD 3-Clause License; you may not use this file except in
4# compliance with the License. You should have received a copy of the license along with this project. If not, see the
5# LICENSE file.
6
7#!/usr/bin/env python
8
9import os
10import csv
11import argparse
12import configparser
13import humanize
14import elftools.elf.elffile as elffile
15from elftools.elf.constants import SH_FLAGS
16from elftools.elf.sections import SymbolTableSection
17
18
19"""
20Script to gather metrics on ROM,RAM,Stack and Heap usage.
21
22To produce the memory trace:
23- Enable tracing:
24 - Via INI file:
25 ```ini
26 [BoolConfigurations]
27 simple_mem_system.print_dbus_access=true
28 simple_mem_system.print_to_file=true
29 ```
30 - Via run_helper.sh script: `run_helper.sh ... trace`
31 - Via command line: `bare_etiss_processor ... --simple_mem_system.print_dbus_access=true --simple_mem_system.print_to_file=true`
32- A file "dBusAccess.csv" should have been created
33
34Then run this script:
35> ./get_metrics.py ../bin/TARGET_ELF_FILE [-i memsegs.ini]
36"""
37
38# Feel free to overwrite these defaults for your needs
39DEFAULT_RAM_START = 0x80000
40DEFAULT_RAM_SIZE = 0x80000
41DEFAULT_STACK_SIZE = 0x4000
42
43
45 def __init__(self, name, min, max):
46 self.name = name
47 self.min = min
48 self.max = max
49 assert self.min <= self.max, "Invalid MemRange"
50 self.num_reads = 0
51 self.num_writes = 0
52 self.read_bytes = 0
54 self.low = 0xFFFFFFFF
55 self.high = 0
56
57 def contains(self, adr):
58 return adr >= self.min and adr < self.max
59
60 def trace(self, adr, mode, pc, sz):
61 self.low = min(adr, self.low)
62 self.high = max(adr, self.high)
63 if mode == "r":
64 self.num_reads += 1
65 self.read_bytes += sz
66 elif mode == "w":
67 self.num_writes += 1
68 self.written_bytes += sz
69 else:
70 raise ValueError(f"Invalid mode: {mode}")
71
72 @property
73 def count(self):
74 return self.num_reads + self.num_writes
75
76 def usage(self):
77 if self.low > self.high:
78 return 0
79 return self.high - self.low
80
81 def stats(self):
82 if self.low > self.high:
83 return self.name + "\t[not accessed]"
84 return self.name + f"\t[0x{self.low:x}-0x{self.high:x}] \t({self.count} times, reads: {self.num_reads} <{self.read_bytes}B>, writes: {self.num_writes} <{self.written_bytes}B>)"
85
86
87def parseElf(inFile):
88 m = {}
89 m["rom_rodata"] = 0
90 m["rom_code"] = 0
91 m["rom_misc"] = 0
92 m["ram_data"] = 0
93 m["ram_zdata"] = 0
94 heapStart = None
95
96 ignoreSections = ["", ".stack", ".comment", ".riscv.attributes", ".strtab", ".shstrtab"]
97
98 with open(inFile, "rb") as f:
99 e = elffile.ELFFile(f)
100
101 for s in e.iter_sections():
102 if s.name.startswith(".text"):
103 m["rom_code"] += s.data_size
104 elif s.name.startswith(".srodata"):
105 m["rom_rodata"] += s.data_size
106 elif s.name.startswith(".sdata"):
107 m["ram_data"] += s.data_size
108 elif s.name == ".rodata":
109 m["rom_rodata"] += s.data_size
110 elif s.name == ".vectors" or s.name == ".init_array":
111 m["rom_misc"] += s.data_size
112 elif s.name == ".data":
113 m["ram_data"] += s.data_size
114 elif s.name == ".bss" or s.name == ".sbss" or s.name == ".shbss":
115 m["ram_zdata"] += s.data_size
116 elif s.name.startswith(".gcc_except"):
117 pass
118 elif s.name.startswith(".sdata2"):
119 pass
120 elif s.name.startswith(".debug_"):
121 pass
122 elif s.name in ignoreSections:
123 pass
124 elif isinstance(s, SymbolTableSection):
125 for sym in s.iter_symbols():
126 if sym.name == "_heap_start":
127 heapStart = sym["st_value"]
128 else:
129 print("warning: ignored: " + s.name + " / size: " + str(s.data_size))
130
131 return m, heapStart
132
133
134def printSz(sz, unknown_msg=""):
135 if sz is None:
136 return f"unknown [{unknown_msg}]" if unknown_msg else "unknown"
137 return humanize.naturalsize(sz) + " (" + hex(sz) + ")"
138
139
140if __name__ == "__main__":
141 parser = argparse.ArgumentParser()
142 parser.add_argument("elf", metavar="ELF", type=str, nargs=1, help="The target ELF file")
143 parser.add_argument(
144 "--trace",
145 "-t",
146 default="dBusAccess.csv",
147 type=str,
148 help="Path to CSV trace file of memory accesses (default: %(default)s)",
149 )
150 parser.add_argument(
151 "--ini", "-i", default="", type=str, help="Path to INI file containing simple_mem_system layout (optional)"
152 )
153 parser.add_argument("--out", "-o", metavar="FILE", type=str, default="", help="""Output CSV file (default: -)""")
154 args = parser.parse_args()
155
156 elfFile = args.elf[0]
157 traceFile = args.trace
158 memIni = args.ini
159 csvFile = args.out
160
161 ramStart = DEFAULT_RAM_START
162 ramSize = DEFAULT_RAM_SIZE
163 stackSize = DEFAULT_STACK_SIZE
164
165 if len(memIni) > 0:
166 # Overwrite the default memory layout by parsing the specified INI file
167 #
168 # Example configuration file `memsegs.ini`:
169 #
170 # [IntConfigurations]
171 # simple_mem_system.memseg_origin_00=0x0
172 # simple_mem_system.memseg_length_00=0x100000
173 # simple_mem_system.memseg_origin_01=0x100000
174 # simple_mem_system.memseg_length_01=0x5000000
175 #
176 config = configparser.ConfigParser()
177 config.read(memIni)
178
179 if "IntConfigurations" not in config:
180 raise RuntimeError("Section [IntConfigurations] does not exist in config file " + memCfg)
181
182 cfg = config["IntConfigurations"]
183
184 # ROM Start/Size is currently not used
185 # romStart = cfg["simple_mem_system.memseg_origin_00"]
186 # romSize = cfg["simple_mem_system.memseg_length_00"]
187 ramStart = int(cfg["simple_mem_system.memseg_origin_01"], 0)
188 ramSize = int(cfg["simple_mem_system.memseg_length_01"], 0)
189
190 staticSizes, heapStart = parseElf(elfFile)
191 if not heapStart:
192 raise RuntimeError("did not find heap start")
193
194 print("heap starts at: " + hex(heapStart))
195
196 d = MemRange("Data", ramStart, heapStart)
197 h = MemRange("Heap", heapStart, ramStart + ramSize - stackSize)
198 s = MemRange("Stack", ramStart + ramSize - stackSize, ramStart + ramSize)
199 mems = [d, h, s]
200
201 trace_available = False
202 if os.path.exists(traceFile):
203 trace_available = True
204 with open(traceFile) as f:
205 reader = csv.reader(f, skipinitialspace=True, delimiter=";")
206 for r in reader:
207 # ts = int(r[0])
208 pc = int(r[1], 16)
209 mode = r[2] # r/w
210 adr = int(r[3], 16)
211 sz = int(r[4], 16)
212 for mem in mems:
213 if mem.contains(adr):
214 mem.trace(adr, mode, pc, sz)
215
216 for mem in mems:
217 print(mem.stats())
218
219 romSize = sum([staticSizes[k] for k in staticSizes if k.startswith("rom_")])
220 ramSize = sum([staticSizes[k] for k in staticSizes if k.startswith("ram_")])
221
222 results = {
223 "rom": romSize,
224 "rom_rodata": staticSizes["rom_rodata"],
225 "rom_code": staticSizes["rom_code"],
226 "rom_misc": staticSizes["rom_misc"],
227 "ram": (ramSize + s.usage() + h.usage()) if trace_available else ramSize,
228 "ram_data": staticSizes["ram_data"],
229 "ram_zdata": staticSizes["ram_zdata"],
230 "ram_stack": s.usage() if trace_available else None,
231 "ram_heap": h.usage() if trace_available else None,
232 }
233
234 print("=== Results ===")
235 print("ROM usage: " + printSz(results["rom"]))
236 print(" read-only data: " + printSz(results["rom_rodata"]))
237 print(" code: " + printSz(results["rom_code"]))
238 print(" other required: " + printSz(results["rom_misc"]))
239 print(
240 "RAM usage: "
241 + printSz(results["ram"])
242 + ("" if trace_available else " [stack and heap usage not included]")
243 )
244 print(" data: " + printSz(results["ram_data"]))
245 print(" zero-init data: " + printSz(results["ram_zdata"]))
246 print(" stack: " + printSz(results["ram_stack"], unknown_msg="missing trace file"))
247 print(" heap: " + printSz(results["ram_heap"], unknown_msg="missing trace file"))
248
249 # Write metrics to file
250 if csvFile:
251 with open(csvFile, "w") as f:
252 writer = csv.DictWriter(f, fieldnames=results.keys())
253 writer.writeheader()
254 writer.writerow(results)
trace(self, adr, mode, pc, sz)
__init__(self, name, min, max)
parseElf(inFile)
printSz(sz, unknown_msg="")