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