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