From 6dea5e30f9023b7748d9249f80aaeeded6011bc1 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Fri, 13 Dec 2019 14:19:16 +0100 Subject: Add Parser and Lexer for timed sequences (words with loops); use them in workload --- bin/workload.py | 62 ++++++++++++---------- lib/automata.py | 26 +++++++++- lib/lex.py | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 206 insertions(+), 38 deletions(-) diff --git a/bin/workload.py b/bin/workload.py index e294b95..d2e5b34 100755 --- a/bin/workload.py +++ b/bin/workload.py @@ -1,37 +1,43 @@ #!/usr/bin/env python3 from automata import PTA -import re import sys -from utils import soft_cast_int, human_readable +from utils import human_readable +from lex import TimedSequence, TimedWord, Workload ptafile, raw_word = sys.argv[1:] +# TODO loops im raw_word: +# init(); repeat { foo(); sleep(5m); bar(); ... } o.ä. +# - Zeitangaben mit Einheit in sleep +# - Ausgabe in Gesamt, Init und Schleifeninhalt aufdröseln + pta = PTA.from_file(ptafile) +timedword = TimedSequence(raw_word) + +print('Input: {}\n'.format(timedword)) + +prev_state = 'UNINITIALIZED' +prev_param = None +for trace_part in timedword: + print('Trace Part {}'.format(trace_part)) + if type(trace_part) is TimedWord: + result = pta.simulate(trace_part, orig_state=prev_state) + elif type(trace_part) is Workload: + result = pta.simulate(trace_part.word, orig_state=prev_state) + if prev_state != result.end_state: + print('Warning: loop starts in state {}, but terminates in {}'.format(prev_state, result.end_state.name)) + if prev_param != result.parameters: + print('Warning: loop starts with parameters {}, but terminates with {}'.format(prev_param, result.parameters)) + + print(' Duration: ' + human_readable(result.duration, 's')) + if result.duration_mae: + print(u' ± {} / {:.0f}%'.format(human_readable(result.duration_mae, 's'), result.duration_mape)) + print(' Energy: ' + human_readable(result.energy, 'J')) + if result.energy_mae: + print(u' ± {} / {:.0f}%'.format(human_readable(result.energy_mae, 'J'), result.energy_mape)) + print(' Mean Power: ' + human_readable(result.mean_power, 'W')) + print('') -trace = list() -for raw_symbol in raw_word.split(';'): - match = re.fullmatch(r' *([^(]+)\((.*)\) *', raw_symbol) - if match: - function_name = match.group(1).strip() - if match.group(2) == '': - raw_args = list() - else: - raw_args = match.group(2).split(',') - if function_name == 'sleep': - function_name = None - word = [function_name] - for raw_arg in raw_args: - word.append(soft_cast_int(raw_arg.strip())) - trace.append(word) - -print(trace) -result = pta.simulate(trace) - -print('Duration: ' + human_readable(result.duration, 's')) -if result.duration_mae: - print(u' ± {} / {:.0f}%'.format(human_readable(result.duration_mae, 's'), result.duration_mape)) -print('Energy: ' + human_readable(result.energy, 'J')) -if result.energy_mae: - print(u' ± {} / {:.0f}%'.format(human_readable(result.energy_mae, 'J'), result.energy_mape)) -print('Mean Power: ' + human_readable(result.mean_power, 'W')) + prev_state = result.end_state + prev_param = result.parameters diff --git a/lib/automata.py b/lib/automata.py index 39ac5df..02dbfb6 100755 --- a/lib/automata.py +++ b/lib/automata.py @@ -14,7 +14,31 @@ def _dict_to_list(input_dict: dict) -> list: class SimulationResult: + """ + Duration, Energy, and state/parameter results from PTA.simulate on a single run. + + :param duration: run duration in s + :param duration_mae: Mean Absolute Error of duration, assuming cycle-perfect delay/sleep calls + :param duration_mape: Mean Absolute Percentage Error of duration, assuming cycle-perfect delay/sleep caals + :param energy: run energy in J + :param energy_mae: Mean Absolute Error of energy + :param energy_mape: Mean Absolute Percentage Error of energy + :param end_state: Final `State` of run + :param parameters: Final parameters of run + :param mean_power: mean power during run in W + """ + def __init__(self, duration: float, energy: float, end_state, parameters, duration_mae: float = None, energy_mae: float = None): + u""" + Create a new SimulationResult. + + :param duration: run duration in µs + :param duration_mae: Mean Absolute Error of duration in µs, default None + :param energy: run energy in pJ + :param energy_mae: Mean Absolute Error of energy in pJ, default None + :param end_state: Final `State` after simulation run + :param parameters: Parameter values after simulation run + """ self.duration = duration * 1e-6 self.duration_mae = duration_mae * 1e-6 self.duration_mape = self.duration_mae * 100 / self.duration @@ -968,7 +992,7 @@ class PTA: else: function_name = function[0] function_args = function[1:] - if function_name is None: + if function_name is None or function_name == '_': duration = function_args[0] total_energy += state.get_energy(duration, param_dict) if state.power.value_error is not None: diff --git a/lib/lex.py b/lib/lex.py index c0323fa..4388162 100644 --- a/lib/lex.py +++ b/lib/lex.py @@ -13,6 +13,28 @@ class TimedWordLexer(Lexer): FUNCTIONSEP = r';' +class TimedSequenceLexer(Lexer): + tokens = {LPAREN, RPAREN, LBRACE, RBRACE, CYCLE, IDENTIFIER, NUMBER, ARGSEP, FUNCTIONSEP} + ignore = ' \t' + + LPAREN = r'\(' + RPAREN = r'\)' + LBRACE = r'\{' + RBRACE = r'\}' + CYCLE = r'cycle' + IDENTIFIER = r'[a-zA-Z_][a-zA-Z0-9_]*' + NUMBER = r'[0-9e.]+' + ARGSEP = r',' + FUNCTIONSEP = r';' + + def error(self, t): + print("Illegal character '%s'" % t.value[0]) + if t.value[0] == '{' and t.value.find('}'): + self.index += 1 + t.value.find('}') + else: + self.index += 1 + + class TimedWordParser(Parser): tokens = TimedWordLexer.tokens @@ -46,17 +68,133 @@ class TimedWordParser(Parser): @_('NUMBER') def arg(self, p): - return [float(p.NUMBER)] + return float(p.NUMBER) @_('IDENTIFIER') def arg(self, p): - return [p.IDENTIFIER] + return p.IDENTIFIER + + +class TimedSequenceParser(Parser): + tokens = TimedSequenceLexer.tokens + + @_('timedSequenceL', 'timedSequenceW') + def timedSequence(self, p): + return p[0] + + @_('loop') + def timedSequenceL(self, p): + return [p.loop] + + @_('loop timedSequenceW') + def timedSequenceL(self, p): + ret = [p.loop] + ret.extend(p.timedSequenceW) + return ret + + @_('timedWord') + def timedSequenceW(self, p): + return [p.timedWord] + + @_('timedWord timedSequenceL') + def timedSequenceW(self, p): + ret = [p.timedWord] + ret.extend(p.timedSequenceL) + return ret + + @_('timedSymbol FUNCTIONSEP timedWord') + def timedWord(self, p): + p.timedWord.word.insert(0, p.timedSymbol) + return p.timedWord + + @_('timedSymbol FUNCTIONSEP') + def timedWord(self, p): + return TimedWord(word=[p.timedSymbol]) + + @_('CYCLE LPAREN IDENTIFIER RPAREN LBRACE timedWord RBRACE') + def loop(self, p): + return Workload(p.IDENTIFIER, p.timedWord) + + @_('IDENTIFIER', 'IDENTIFIER LPAREN RPAREN') + def timedSymbol(self, p): + return (p.IDENTIFIER,) + + @_('IDENTIFIER LPAREN args RPAREN') + def timedSymbol(self, p): + return (p.IDENTIFIER, *p.args) + + @_('arg ARGSEP args') + def args(self, p): + ret = [p.arg] + ret.extend(p.args) + return ret + + @_('arg') + def args(self, p): + return [p.arg] + + @_('NUMBER') + def arg(self, p): + return float(p.NUMBER) + + @_('IDENTIFIER') + def arg(self, p): + return p.IDENTIFIER + + def error(self, p): + if p: + print("Syntax error at token", p.type) + # Just discard the token and tell the parser it's okay. + self.errok() + else: + print("Syntax error at EOF") + + +class TimedWord: + def __init__(self, word_string=None, word=list()): + if word_string is not None: + lexer = TimedWordLexer() + parser = TimedWordParser() + self.word = parser.parse(lexer.tokenize(word_string)) + else: + self.word = word + + def __getitem__(self, item): + return self.word[item] + + def __repr__(self): + ret = list() + for symbol in self.word: + ret.append('{}({})'.format(symbol[0], ', '.join(map(str, symbol[1:])))) + return 'TimedWord<"{}">'.format('; '.join(ret)) + + +class Workload: + def __init__(self, name, word): + self.name = name + self.word = word + + def __getitem__(self, item): + return self.word[item] + + def __repr__(self): + return 'Workload("{}", {})'.format(self.name, self.word) + + +class TimedSequence: + def __init__(self, seq_string=None, seq=list()): + if seq_string is not None: + lexer = TimedSequenceLexer() + parser = TimedSequenceParser() + self.seq = parser.parse(lexer.tokenize(seq_string)) + else: + self.seq = seq + def __getitem__(self, item): + return self.seq[item] -if __name__ == '__main__': - data = 'init(); sleep(12345); foo(_, 7);' - lexer = TimedWordLexer() - parser = TimedWordParser() - for tok in lexer.tokenize(data): - print('type={}, value={}'.format(tok.type, tok.value)) - print(parser.parse(lexer.tokenize(data))) + def __repr__(self): + ret = list() + for symbol in self.seq: + ret.append('{}'.format(symbol)) + return 'TimedSequence(seq=[{}])'.format(', '.join(ret)) -- cgit v1.2.3