mirror of https://github.com/m-labs/artiq
156 lines
5.5 KiB
Python
Executable File
156 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import sys
|
|
import struct
|
|
from collections import defaultdict
|
|
import subprocess
|
|
|
|
from artiq.tools import verbosity_args, init_logger
|
|
from artiq.master.databases import DeviceDB
|
|
from artiq.coredevice.comm_mgmt import CommMgmt
|
|
|
|
|
|
class Symbolizer:
|
|
def __init__(self, binary):
|
|
self._addr2line = subprocess.Popen([
|
|
"or1k-linux-addr2line", "--exe=" + binary,
|
|
"--addresses", "--demangle=rust", "--functions", "--inlines"
|
|
], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
|
|
|
|
def symbolize(self, addr):
|
|
self._addr2line.stdin.write("0x{:08x}\n0\n".format(addr))
|
|
self._addr2line.stdin.flush()
|
|
self._addr2line.stdout.readline() # 0x[addr]
|
|
|
|
result = []
|
|
while True:
|
|
function = self._addr2line.stdout.readline().rstrip()
|
|
|
|
# check for end marker
|
|
if function == "0x00000000": # 0x00000000
|
|
self._addr2line.stdout.readline() # ??
|
|
self._addr2line.stdout.readline() # ??:0
|
|
return result
|
|
|
|
file, line = self._addr2line.stdout.readline().rstrip().split(":")
|
|
|
|
result.append((function, file, line))
|
|
|
|
|
|
class CallgrindWriter:
|
|
def __init__(self, output, binary, compression=True):
|
|
self._output = output
|
|
self._binary = binary
|
|
self._current = defaultdict(lambda: None)
|
|
self._ids = defaultdict(lambda: {})
|
|
self._compression = compression
|
|
self._symbolizer = Symbolizer(binary)
|
|
|
|
def _write(self, fmt, *args, **kwargs):
|
|
self._output.write(fmt.format(*args, **kwargs))
|
|
self._output.write("\n")
|
|
|
|
def _spec(self, spec, value):
|
|
if self._current[spec] == value:
|
|
return
|
|
self._current[spec] = value
|
|
|
|
if not self._compression or value == "??":
|
|
self._write("{}={}", spec, value)
|
|
return
|
|
|
|
spec_ids = self._ids[spec]
|
|
if value in spec_ids:
|
|
self._write("{}=({})", spec, spec_ids[value])
|
|
else:
|
|
spec_ids[value] = len(spec_ids) + 1
|
|
self._write("{}=({}) {}", spec, spec_ids[value], value)
|
|
|
|
def header(self):
|
|
self._write("# callgrind format")
|
|
self._write("version: 1")
|
|
self._write("creator: ARTIQ")
|
|
self._write("positions: instr line")
|
|
self._write("events: Hits")
|
|
self._write("")
|
|
self._spec("ob", self._binary)
|
|
self._spec("cob", self._binary)
|
|
|
|
def hit(self, addr, count):
|
|
for function, file, line in self._symbolizer.symbolize(addr):
|
|
self._spec("fn", function)
|
|
self._spec("fl", file)
|
|
self._write("0x{:08x} {} {}", addr, line, count)
|
|
|
|
def edge(self, caller, callee, count):
|
|
function, file, line = next(self._symbolizer.symbolize(callee))
|
|
self._spec("cfn", function)
|
|
self._spec("cfl", file)
|
|
self._write("calls={} 0x{:08x} {}", count, callee, line)
|
|
|
|
function, file, line = next(self._symbolizer.symbolize(caller))
|
|
self._spec("fn", function)
|
|
self._spec("fl", file)
|
|
self._write("0x{:08x} {} {}", caller, line, count)
|
|
|
|
|
|
def get_argparser():
|
|
parser = argparse.ArgumentParser(description="ARTIQ core device profiling tool")
|
|
|
|
verbosity_args(parser)
|
|
parser.add_argument("--device-db", default="device_db.py",
|
|
help="device database file (default: '%(default)s')")
|
|
|
|
subparsers = parser.add_subparsers(dest="action")
|
|
subparsers.required = True
|
|
|
|
p_start = subparsers.add_parser("start",
|
|
help="start profiling")
|
|
p_start.add_argument("--interval", metavar="MICROS", type=int, default=2000,
|
|
help="sampling interval, in microseconds")
|
|
p_start.add_argument("--hits-size", metavar="ENTRIES", type=int, default=8192,
|
|
help="hit buffer size")
|
|
p_start.add_argument("--edges-size", metavar="ENTRIES", type=int, default=0,
|
|
help="edge buffer size (edge profiling not implemented)")
|
|
|
|
p_stop = subparsers.add_parser("stop",
|
|
help="stop profiling")
|
|
|
|
p_save = subparsers.add_parser("save",
|
|
help="save profile")
|
|
p_save.add_argument("output", metavar="OUTPUT", type=argparse.FileType("w"),
|
|
help="file to save profile to, in Callgrind format")
|
|
p_save.add_argument("firmware", metavar="FIRMWARE", type=str,
|
|
help="path to firmware ELF file")
|
|
p_save.add_argument("--no-compression", default=True, action='store_false',
|
|
help="disable profile compression")
|
|
|
|
return parser
|
|
|
|
|
|
def main():
|
|
args = get_argparser().parse_args()
|
|
init_logger(args)
|
|
|
|
core_addr = DeviceDB(args.device_db).get("core")["arguments"]["host"]
|
|
mgmt = CommMgmt(core_addr)
|
|
try:
|
|
if args.action == "start":
|
|
mgmt.start_profiler(args.interval, args.hits_size, args.edges_size)
|
|
elif args.action == "stop":
|
|
mgmt.stop_profiler()
|
|
elif args.action == "save":
|
|
hits, edges = mgmt.get_profile()
|
|
writer = CallgrindWriter(args.output, args.firmware, not args.no_compression)
|
|
writer.header()
|
|
for addr, count in hits.items():
|
|
writer.hit(addr, count)
|
|
for (caller, callee), count in edges.items():
|
|
writer.edge(caller, callee, count)
|
|
finally:
|
|
mgmt.close()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|