summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Friesel <daniel.friesel@uos.de>2019-07-22 16:58:00 +0200
committerDaniel Friesel <daniel.friesel@uos.de>2019-07-22 16:58:00 +0200
commit825ee24544c18be6c98c5869d89d7f59290b38ac (patch)
tree2fbe83e619981b23ebdc0e73b1027038a7a9d643 /lib
parentac49f53902d5b5229d691165762a8f419e07e687 (diff)
implement autogeneration and data aggregation of timing benchmarks
Diffstat (limited to 'lib')
-rw-r--r--lib/harness.py81
-rw-r--r--lib/runner.py81
2 files changed, 141 insertions, 21 deletions
diff --git a/lib/harness.py b/lib/harness.py
index 529a77e..1390e03 100644
--- a/lib/harness.py
+++ b/lib/harness.py
@@ -11,18 +11,19 @@ import re
# generated otherwise and it should also work with AnalyticModel (which does
# not have states)
class TransitionHarness:
- def __init__(self, gpio_pin = None):
+ def __init__(self, gpio_pin = None, pta = None):
self.gpio_pin = gpio_pin
+ self.pta = pta
+ self.reset()
+
+ def reset(self):
self.traces = []
self.trace_id = 1
- pass
+ self.synced = False
def start_benchmark(self):
pass
- def stop_benchmark(self):
- pass
-
def global_code(self):
ret = ''
if self.gpio_pin != None:
@@ -34,8 +35,8 @@ class TransitionHarness:
ret += 'PTALog ptalog;\n'
return ret
- def start_benchmark(self):
- return 'ptalog.startBenchmark(0);\n'
+ def start_benchmark(self, benchmark_id = 0):
+ return 'ptalog.startBenchmark({:d});\n'.format(benchmark_id)
def start_trace(self):
self.traces.append({
@@ -62,15 +63,15 @@ class TransitionHarness:
def start_run(self):
return 'ptalog.reset();\n'
- def pass_transition(self, transition_id, transition_code, transition: object = None, parameter: dict = dict()):
+ def pass_transition(self, transition_id, transition_code, transition: object = None):
ret = 'ptalog.passTransition({:d});\n'.format(transition_id)
ret += 'ptalog.startTransition();\n'
ret += '{}\n'.format(transition_code)
ret += 'ptalog.stopTransition();\n'
return ret
- def stop_run(self, trace_id = 0):
- return 'ptalog.dump({:d});\n'.format(trace_id)
+ def stop_run(self, num_traces = 0):
+ return 'ptalog.dump({:d});\n'.format(num_traces)
def stop_benchmark(self):
return ''
@@ -96,8 +97,11 @@ class TransitionHarness:
pass
class OnboardTimerHarness(TransitionHarness):
- def __init__(self, gpio_pin = None):
- super().__init__(gpio_pin = gpio_pin)
+ def __init__(self, counter_limits, **kwargs):
+ super().__init__(**kwargs)
+ self.trace_id = 0
+ self.trace_length = 0
+ self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow = counter_limits
def global_code(self):
ret = '#include "driver/counter.h"\n'
@@ -105,18 +109,63 @@ class OnboardTimerHarness(TransitionHarness):
ret += super().global_code()
return ret
- def start_benchmark(self):
+ def start_benchmark(self, benchmark_id = 0):
ret = 'counter.start();\n'
ret += 'counter.stop();\n'
ret += 'ptalog.passNop(counter);\n'
- ret += super().start_benchmark()
+ ret += super().start_benchmark(benchmark_id)
return ret
- def pass_transition(self, transition_id, transition_code, transition: object = None, parameter: dict = dict()):
+ def pass_transition(self, transition_id, transition_code, transition: object = None):
ret = 'ptalog.passTransition({:d});\n'.format(transition_id)
ret += 'ptalog.startTransition();\n'
ret += 'counter.start();\n'
ret += '{}\n'.format(transition_code)
ret += 'counter.stop();\n'
ret += 'ptalog.stopTransition(counter);\n'
- return ret \ No newline at end of file
+ return ret
+
+ def parser_cb(self, line):
+ #print('[HARNESS] got line {}'.format(line))
+ if re.match(r'\[PTA\] benchmark start, id=(.*)', line):
+ self.synced = True
+ print('[HARNESS] synced')
+ if self.synced:
+ res = re.match(r'\[PTA\] trace=(.*) count=(.*)', line)
+ if res:
+ self.trace_id = int(res.group(1))
+ self.trace_length = int(res.group(2))
+ self.current_transition_in_trace = 0
+ #print('[HARNESS] trace {:d} contains {:d} transitions. Expecting {:d} transitions.'.format(self.trace_id, self.trace_length, len(self.traces[self.trace_id]['trace']) // 2))
+ res = re.match(r'\[PTA\] transition=(.*) cycles=(.*)/(.*)', line)
+ if res:
+ transition_id = int(res.group(1))
+ # TODO Handle Overflows (requires knowledge of arch-specific max cycle value)
+ cycles = int(res.group(2))
+ overflow = int(res.group(3))
+ if overflow >= self.counter_max_overflow:
+ raise RuntimeError('Counter overflow ({:d}/{:d}) in benchmark id={:d} trace={:d}: transition #{:d} (ID {:d})'.format(cycles, overflow, 0, self.trace_id, self.current_transition_in_trace, transition_id))
+ duration_us = cycles * self.one_cycle_in_us + overflow * self.one_overflow_in_us
+ # self.traces contains transitions and states, UART output only contains trnasitions -> use index * 2
+ try:
+ log_data_target = self.traces[self.trace_id]['trace'][self.current_transition_in_trace * 2]
+ except IndexError:
+ transition_name = None
+ if self.pta:
+ transition_name = self.pta.transitions[transition_id].name
+ print('[HARNESS] benchmark id={:d} trace={:d}: transition #{:d} (ID {:d}, name {}) is out of bounds'.format(0, self.trace_id, self.current_transition_in_trace, transition_id, transition_name))
+ print(' Offending line: {}'.format(line))
+ return
+ if log_data_target['isa'] != 'transition':
+ raise RuntimeError('Log mismatch: Expected transition, got {:s}'.format(log_data_target['isa']))
+ if self.pta:
+ transition = self.pta.transitions[transition_id]
+ if transition.name != log_data_target['name']:
+ raise RuntimeError('Log mismatch: Expected transition {:s}, got transition {:s}'.format(log_data_target['name'], transition.name))
+ #print('[HARNESS] Logging data for transition {}'.format(log_data_target['name']))
+ if 'offline_aggregates' not in log_data_target:
+ log_data_target['offline_aggregates'] = {
+ 'duration' : list()
+ }
+ log_data_target['offline_aggregates']['duration'].append(duration_us)
+ self.current_transition_in_trace += 1 \ No newline at end of file
diff --git a/lib/runner.py b/lib/runner.py
index baf4d16..fe76459 100644
--- a/lib/runner.py
+++ b/lib/runner.py
@@ -10,6 +10,7 @@ Functions:
get_counter_limits -- return arch-specific multipass counter limits (max value, max overflow)
"""
+import os
import re
import serial
import serial.threaded
@@ -39,12 +40,16 @@ class SerialReader(serial.threaded.Protocol):
str_data = data.decode('UTF-8')
self.recv_buf += str_data
- lines = list(map(str.strip, self.recv_buf.split('\n')))
+ # We may get anything between \r\n, \n\r and simple \n newlines.
+ # We assume that \n is always present and use str.strip to remove leading/trailing \r symbols
+ # Note: Do not call str.strip on lines[-1]! Otherwise, lines may be mangled
+ lines = self.recv_buf.split('\n')
if len(lines) > 1:
- self.lines.extend(lines[:-1])
+ self.lines.extend(map(str.strip, lines[:-1]))
self.recv_buf = lines[-1]
if self.callback:
- self.callback(lines[:-1])
+ for line in lines[:-1]:
+ self.callback(str.strip(line))
except UnicodeDecodeError:
pass
@@ -117,6 +122,51 @@ class SerialMonitor:
self.worker.stop()
self.ser.close()
+
+class MIMOSAMonitor(SerialMonitor):
+ def __init__(self, port: str, baud: int, callback = None, offset = 130, shunt = 330, voltage = 3.3):
+ super().__init__(port = port, baud = baud, callback = callback)
+ self._offset = offset
+ self._shunt = shunt
+ self._voltage = voltage
+ self._start_mimosa()
+
+ def _mimosacmd(self, opts):
+ cmd = ['MimosaCMD']
+ cmd.extend(opts)
+ res = subprocess.run(cmd)
+ if res.returncode != 0:
+ raise RuntimeError('MimosaCMD returned ' + res.returncode)
+
+ def _start_mimosa(self):
+ self._mimosacmd(['--start'])
+ self._mimosacmd(['--parameter', 'offset', str(self._offset)])
+ self._mimosacmd(['--parameter', 'shunt', str(self._shunt)])
+ self._mimosacmd(['--parameter', 'voltage', str(self._voltage)])
+ self._mimosacmd(['--mimosa-start'])
+
+ def _stop_mimosa(self):
+ self._mimosacmd(['--mimosa-stop'])
+ mtime_changed = True
+ mim_file = None
+ time.sleep(1)
+ for filename in os.listdir():
+ if re.search(r'[.]mim$', filename):
+ mim_file = filename
+ break
+ while mtime_changed:
+ mtime_changed = False
+ if time.time() - os.stat(mim_file).st_mtime < 3:
+ mtime_changed = True
+ time.sleep(1)
+ self._mimosacmd(['--stop'])
+ return mim_file
+
+ def close(self):
+ super().close()
+ mim_file = self._stop_mimosa()
+ os.remove(mim_file)
+
class ShellMonitor:
"""SerialMonitor runs a program and captures its output for a specific amount of time."""
def __init__(self, script: str, callback = None):
@@ -140,7 +190,8 @@ class ShellMonitor:
stdout = subprocess.PIPE, stderr = subprocess.PIPE,
universal_newlines = True)
if self.callback:
- self.callback(res.stdout.split('\n'))
+ for line in res.stdout.split('\n'):
+ self.callback(line)
return res.stdout.split('\n')
def monitor(self):
@@ -193,12 +244,15 @@ def get_info(arch, opts: list = []) -> list:
return res.stdout.split('\n')
def get_monitor(arch: str, **kwargs) -> object:
- """Return a SerialMonitor or ShellMonitor."""
+ """Return a SerialMonitor or ShellMonitor, depending on "make info" output of arch."""
for line in get_info(arch):
if 'Monitor:' in line:
_, port, arg = line.split(' ')
if port == 'run':
return ShellMonitor(arg, **kwargs)
+ elif 'mimosa' in kwargs:
+ mimosa_kwargs = kwargs.pop('mimosa')
+ return MIMOSAMonitor(port, arg, **mimosa_kwargs, **kwargs)
else:
return SerialMonitor(port, arg, **kwargs)
raise RuntimeError('Monitor failure')
@@ -212,3 +266,20 @@ def get_counter_limits(arch: str) -> tuple:
max_overflow = int(match.group(2))
return overflow_value, max_overflow
raise RuntimeError('Did not find Counter Overflow limits')
+
+def get_counter_limits_us(arch: str) -> tuple:
+ """Return duration of one counter step and one counter overflow in us."""
+ cpu_freq = 0
+ overflow_value = 0
+ max_overflow = 0
+ for line in get_info(arch):
+ match = re.match(r'CPU\s+Freq:\s+(.*)\s+Hz', line)
+ if match:
+ cpu_freq = int(match.group(1))
+ match = re.match(r'Counter Overflow:\s+([^/]*)/(.*)', line)
+ if match:
+ overflow_value = int(match.group(1))
+ max_overflow = int(match.group(2))
+ if cpu_freq and overflow_value:
+ return 1000000 / cpu_freq, overflow_value * 1000000 / cpu_freq, max_overflow
+ raise RuntimeError('Did not find Counter Overflow limits')