diff options
-rwxr-xr-x | bin/analyze-archive.py | 36 | ||||
-rw-r--r-- | lib/dfatool.py | 28 |
2 files changed, 57 insertions, 7 deletions
diff --git a/bin/analyze-archive.py b/bin/analyze-archive.py index 770e5d5..f7847c5 100755 --- a/bin/analyze-archive.py +++ b/bin/analyze-archive.py @@ -21,6 +21,22 @@ Options: parameters. Also plots the corresponding measurements. If gplearn function is set, it is plotted using dashed lines. +--export-traces=<directory> + Export power traces of all states and transitions to <directory>. + Creates a JSON file for each state and transition. Each JSON file + lists all occurences of the corresponding state/transition in the + benchmark's PTA trace. Each occurence contains the corresponding PTA + parameters (if any) in 'parameter' and measurement results in 'offline'. + As measurements are typically run repeatedly, 'offline' is in turn a list + of measurements: offline[0]['uW'] is the power trace of the first + measurement of this state/transition, offline[1]['uW'] corresponds t the + second measurement, etc. Values are provided in microwatts. + For example, TX.json[0].offline[0].uW corresponds to the first measurement + of the first TX state in the benchmark, and TX.json[5].offline[2].uW + corresponds to the third measurement of the sixth TX state in the benchmark. + WARNING: Several GB of RAM and disk space are required for complex measurements. + (JSON files may grow very large -- we trade efficiency for easy handling) + --param-info Show parameter names and values @@ -220,6 +236,7 @@ if __name__ == '__main__': show_quality = [] pta = None energymodel_export_file = None + trace_export_dir = None xv_method = None xv_count = 10 @@ -227,6 +244,7 @@ if __name__ == '__main__': optspec = ( 'plot-unparam= plot-param= param-info show-models= show-quality= ' 'ignored-trace-indexes= discard-outliers= function-override= ' + 'export-traces= ' 'filter-param= ' 'cross-validate= ' 'with-safe-functions hwmodel= export-energymodel=' @@ -275,10 +293,26 @@ if __name__ == '__main__': print(err) sys.exit(2) - raw_data = RawData(args) + raw_data = RawData(args, with_traces=('export-traces' in opts)) preprocessed_data = raw_data.get_preprocessed_data() + if 'export-traces' in opts: + uw_per_sot = dict() + for trace in preprocessed_data: + for state_or_transition in trace['trace']: + name = state_or_transition['name'] + if name not in uw_per_sot: + uw_per_sot[name] = list() + for elem in state_or_transition['offline']: + elem['uW'] = list(elem['uW']) + uw_per_sot[name].append(state_or_transition) + for name, data in uw_per_sot.items(): + target = f"{opts['export-traces']}/{name}.json" + print(f'exporting {target} ...') + with open(target, 'w') as f: + json.dump(data, f) + if raw_data.preprocessing_stats['num_valid'] == 0: print('No valid data available. Abort.') sys.exit(2) diff --git a/lib/dfatool.py b/lib/dfatool.py index 177bd7b..8fb41a5 100644 --- a/lib/dfatool.py +++ b/lib/dfatool.py @@ -332,7 +332,7 @@ class CrossValidator: def _preprocess_mimosa(measurement): setup = measurement['setup'] - mim = MIMOSA(float(setup['mimosa_voltage']), int(setup['mimosa_shunt'])) + mim = MIMOSA(float(setup['mimosa_voltage']), int(setup['mimosa_shunt']), with_traces=measurement['with_traces']) try: charges, triggers = mim.load_data(measurement['content']) trigidx = mim.trigger_edges(triggers) @@ -489,14 +489,16 @@ class RawData: Expects a specific trace format and UART log output (as produced by the dfatool benchmark generator). Loads data, prunes bogus measurements, and - provides preprocessed data suitable for PTAModel. + provides preprocessed data suitable for PTAModel. Results are cached on the + file system, making subsequent loads near-instant. """ - def __init__(self, filenames): + def __init__(self, filenames, with_traces=False): """ Create a new RawData object. - Each filename element corresponds to a measurement run. It must be a tar archive with the following contents: + Each filename element corresponds to a measurement run. + It must be a tar archive with the following contents: Version 0: @@ -540,8 +542,12 @@ class RawData: `.opt.configs`: .... * EnergyTrace log files (`*.etlog`) as specified in `.opt.files` + If a cached result for a file is available, it is loaded and the file + is not preprocessed, unless `with_traces` is set. + tbd """ + self.with_traces = with_traces self.filenames = filenames.copy() self.traces_by_fileno = [] self.setup_by_fileno = [] @@ -562,7 +568,8 @@ class RawData: break self.set_cache_file() - self.load_cache() + if not with_traces: + self.load_cache() def set_cache_file(self): cache_key = hashlib.sha256('!'.join(self.filenames).encode()).hexdigest() @@ -580,6 +587,8 @@ class RawData: self.preprocessed = True def save_cache(self): + if self.with_traces: + return try: os.mkdir(self.cache_dir) except FileExistsError: @@ -920,6 +929,7 @@ class RawData: def get_preprocessed_data(self, verbose=True): """ Return a list of DFA traces annotated with energy, timing, and parameter data. + The list is cached on disk, unless the constructor was called with `with_traces` set. Each DFA trace contains the following elements: * `id`: Numeric ID, starting with 1 @@ -1000,6 +1010,7 @@ class RawData: 'fileno': i, 'info': member, 'setup': self.setup_by_fileno[i], + 'with_traces': self.with_traces, }) elif version == 1: @@ -1049,6 +1060,7 @@ class RawData: 'setup': self.setup_by_fileno[j], 'repeat_id': repeat_id, 'expected_trace': ptalog['traces'][j], + 'with_traces': self.with_traces, }) self.filenames = new_filenames @@ -2507,7 +2519,7 @@ class MIMOSA: Resulting data is a list of state/transition/state/transition/... measurements. """ - def __init__(self, voltage: float, shunt: int, verbose=True): + def __init__(self, voltage: float, shunt: int, verbose=True, with_traces=False): """ Initialize MIMOSA loader for a specific voltage and shunt setting. @@ -2518,6 +2530,7 @@ class MIMOSA: self.voltage = voltage self.shunt = shunt self.verbose = verbose + self.with_traces = with_traces self.r1 = 984 # "1k" self.r2 = 99013 # "100k" self.errors = list() @@ -2855,6 +2868,9 @@ class MIMOSA: 'us': (idx - previdx) * 10, } + if self.with_traces: + data['uW'] = range_ua * self.voltage + if 'states' in substates: data['substates'] = substates ssum = np.sum(list(map(lambda x: x['duration'], substates['states']))) |