summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/dfatool116
-rwxr-xr-xbin/merge.py98
-rw-r--r--lib/Kratos/DFADriver.pm47
3 files changed, 231 insertions, 30 deletions
diff --git a/bin/dfatool b/bin/dfatool
index 00b4196..881d04b 100755
--- a/bin/dfatool
+++ b/bin/dfatool
@@ -331,12 +331,47 @@ __END__
=head1 NAME
+dfatool manages energy-aware DFA drivers for kratos and generates and runs
+test programs to assess a device's energy usage.
+
=head1 SYNOPSIS
+B<dfatool> [I<options>] enable|disable|maketest|rmtest|log|loop I<driver.xml>
+
+B<dfatool> [I<options>] analyze|validate|crossvalidate|ls|list|show
+I<driver.xml> I<data.tar> [I<moredata.tar ...>]
+
=head1 VERSION
=head1 DESCRIPTION
+TODO WARNING: rel_energy_next is unusable for transitions which frequently are
+the last transition in a run
+
+=head1 COMMANDS
+
+=over
+
+=item B<enable> I<driver.xml>
+
+Instruments the driver noted in I<driver.xml> for energy accounting and state
+and transition logging. Unconditionally uses static model attributes and only
+relative energy values. Attributes whose power or energy values are not yet
+known are set to zero.
+
+=item B<disable> I<driver.xml>
+
+Removes accounting and logging instrumentation, thus turning the driver back
+into an energy-unaware one. By default, each state may be visited up to two
+times...
+
+=item B<maketest> I<driver.xml>
+
+Creates a kratos application containing a test program for the driver. By
+default,
+
+=back
+
=head1 OPTIONS
=over
@@ -349,18 +384,93 @@ __END__
None.
-=head1 DEPENDENCIES
+=head1 SOFTWARE DEPENDENCIES
+
+As of 2017-04, the following packages (and some more) are required on Debian
+Sid:
+
+ apt install libcarp-assert-more-perl libclass-accessor-perl libdatetime-perl
+ libdevice-serialport-perl libfile-slurp-perl libipc-run-perl libjson-perl
+ liblist-moreutils-perl libmath-cartesian-product-perl
+ libparse-recdescent-perl libtime-progress-perl libxml-libxml-perl
+ python3-matplotlib python3-numpy python3-scipy
+
+Required non-standard perl modules:
=over
+=item * Carp::Assert::More
+
+=item * DateTIme
+
+=item * Device::SerialPort
+
+=item * File::Slurp
+
+=item * IPC::Run
+
+=item * List::MoreUtils
+
+=item * Math::Cartesian::Product
+
+=item * Parse::RecDescent
+
+=item * Time::Progress
+
+=item * XML::LibXML
+
+=back
+
+For analysis, the following programs and modules are required:
+
+=over
+
+=item * Python 3.5 with matplotlib, numpy and scipy modules
+
+=item * parallel (either GNU parallel or the one provided by moreutils)
+
+=back
+
+=head1 HARDWARE DEPENDENCIES
+
+Normal driver usage and analyses of generated data requires no external
+hardware. For data collection (C<< dfatool loop >> and C<< dfatool log >>), the
+following pieces are required:
+
+=over
+
+=item * An MSP430 Launchpad with the Device Under Test attached
+
+=item * MIMOSA
+
+=item * MIMOSA-Autocal
+
=back
=head1 BUGS AND LIMITATIONS
+To be determined.
+
=head1 AUTHOR
-Copyright (C) 2016 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt>
+Copyright (C) 2016-2017 by Daniel Friesel E<lt>daniel.friesel@udo.eduE<gt>
=head1 LICENSE
- 0. You just DO WHAT THE FUCK YOU WANT TO.
+You may use this program either according to the terms of the Perl license
+(also known as the Artistic license) or under the following terms
+(also knows as the Three-clause BSD License).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
diff --git a/bin/merge.py b/bin/merge.py
index 67c9cc9..cc287e8 100755
--- a/bin/merge.py
+++ b/bin/merge.py
@@ -285,7 +285,21 @@ def param_values(parameters, by_param):
return paramvalues
-def param_key(elem):
+# Returns the values used for each function argument in the measurement, e.g.
+# { 'data': [], 'length' : [16, 31, 32] }
+# non-numeric values such as '' or 'long_test_string' are skipped
+def arg_values(name, by_arg):
+ TODO
+ argvalues = dict([[arg, set()] for arg in parameters])
+
+ for _, paramvalue in by_param.keys():
+ for i, param in enumerate(parameters):
+ if is_numeric(paramvalue[i]):
+ paramvalues[param].add(paramvalue[i])
+
+ return paramvalues
+
+def mk_param_key(elem):
name = elem['name']
paramtuple = ()
@@ -295,12 +309,14 @@ def param_key(elem):
return (name, paramtuple)
-#def param_arg_key(elem):
-# # Argumentbasierte Parametrisierung ist erstmal out of scope
-# #if 'args' in elem:
-# # argtuple = tuple(elem['args'])
-#
-# return (name, paramtuple, argtuple)
+def mk_arg_key(elem):
+ name = elem['name']
+ argtuple = ()
+
+ if 'args' in elem:
+ argtuple = tuple(elem['args'])
+
+ return (name, argtuple)
def add_data_to_aggregate(aggregate, key, isa, data):
if not key in aggregate:
@@ -363,6 +379,15 @@ def mean_std_by_param(data, keys, name, what, index):
return np.mean([np.std(partition) for partition in partitions])
# returns the mean standard deviation of all measurements of 'what'
+# (e.g. energy or duration) for transition 'name' where
+# the 'index'th argumetn is dynamic and all other arguments are fixed.
+# I.e., if arguments are a, b, c ∈ {1,2,3} and 'index' is 1, then
+# this function returns the mean of the standard deviations of (a=1, b=*, c=1),
+# (a=1, b=*, c=2), and so on
+def mean_std_by_arg(data, keys, name, what, index):
+ return mean_std_by_param(data, keys, name, what, index)
+
+# returns the mean standard deviation of all measurements of 'what'
# (e.g. power consumption or timeout) for state/transition 'name' where the
# trace of previous transitions is fixed except for a single transition,
# whose occurence or absence is silently ignored.
@@ -385,7 +410,7 @@ def mean_std_by_trace_part(data, transitions, name, what):
return ret
-def load_run_elem(index, element, trace, by_name, by_param, by_trace):
+def load_run_elem(index, element, trace, by_name, by_arg, by_param, by_trace):
means, stds, durations, energies, rel_energies_prev, rel_energies_next, clips, timeouts, sub_thresholds = mimosa_data(element)
online_means = []
@@ -393,9 +418,10 @@ def load_run_elem(index, element, trace, by_name, by_param, by_trace):
if element['isa'] == 'state':
online_means, online_durations = online_data(element)
- key = param_key(element)
+ arg_key = mk_arg_key(element)
+ param_key = mk_param_key(element)
pre_trace = tuple(map(lambda x : x['name'], trace[1:index:2]))
- trace_key = (*key, pre_trace)
+ trace_key = (*param_key, pre_trace)
name = element['name']
elem_data = {
@@ -408,12 +434,13 @@ def load_run_elem(index, element, trace, by_name, by_param, by_trace):
'clip_rate' : clips,
'timeouts' : timeouts,
'sub_thresholds' : sub_thresholds,
- 'param' : [key[1]] * len(means),
+ 'param' : [param_key[1]] * len(means),
'online_means' : online_means,
'online_durations' : online_durations,
}
add_data_to_aggregate(by_name, name, element['isa'], elem_data)
- add_data_to_aggregate(by_param, key, element['isa'], elem_data)
+ add_data_to_aggregate(by_arg, arg_key, element['isa'], elem_data)
+ add_data_to_aggregate(by_param, param_key, element['isa'], elem_data)
add_data_to_aggregate(by_trace, trace_key, element['isa'], elem_data)
def fmap(name, funtype):
@@ -487,7 +514,10 @@ def param_measures(name, paramdata, key, fun):
return ret
-def keydata(name, val, paramdata, tracedata, key):
+def arg_measures(name, argdata, key, fun):
+ return param_measures(name, argdata, key, fun)
+
+def keydata(name, val, argdata, paramdata, tracedata, key):
ret = {
'count' : len(val[key]),
'median' : np.median(val[key]),
@@ -504,6 +534,12 @@ def keydata(name, val, paramdata, tracedata, key):
'function' : {},
}
+ if val['isa'] == 'transition':
+ ret['arg_mean_goodness'] = arg_measures(name, argdata, key, np.mean)
+ ret['arg_median_goodness'] = arg_measures(name, argdata, key, np.median)
+ ret['std_arg'] = np.mean([np.std(argdata[x][key]) for x in argdata.keys() if x[0] == name])
+ ret['std_by_arg'] = {}
+
return ret
def splitidx_kfold(length, num_slices):
@@ -569,7 +605,7 @@ def val_run_fun(aggdata, by_trace, name, key, funtype1, funtype2, splitfun, coun
fake_add_data_to_aggregate(bpa_validation, bpa_key, isa, aggdata, idx)
fake_by_name = { name : aggdata }
- ares = analyze(fake_by_name, bpa_training, by_trace, parameters)
+ ares = analyze(fake_by_name, {}, bpa_training, by_trace, parameters)
if name in ares[isa] and funtype2 in ares[isa][name][funtype1]['function']:
xv2_assess_function(name, ares[isa][name][funtype1]['function'][funtype2], key, bpa_validation, mae, smape, rmsd)
if funtype2 == 'estimate':
@@ -597,7 +633,7 @@ def val_run_fun_p(aggdata, by_trace, name, key, funtype1, funtype2, splitfun, co
for idx in range(0, len(val[key])):
fake_add_data_to_aggregate(bna_training, name, isa, val, idx)
- ares = analyze(bna_training, bpa_training, by_trace, parameters)
+ ares = analyze(bna_training, {}, bpa_training, by_trace, parameters)
if name in ares[isa] and funtype2 in ares[isa][name][funtype1]['function']:
xv2_assess_function(name, ares[isa][name][funtype1]['function'][funtype2], key, bpa_validation, mae, smape, rmsd)
if funtype2 == 'estimate':
@@ -718,7 +754,7 @@ def crossvalidate(by_name, by_param, by_trace, model, parameters):
to_pop.append(name)
for name in to_pop:
by_name.pop(name, None)
- ares = analyze(by_name, bpa_training, by_trace, parameters)
+ ares = analyze(by_name, {}, bpa_training, by_trace, parameters)
for name in sorted(ares['state'].keys()):
state = ares['state'][name]
if 'function' in state['power']:
@@ -858,6 +894,12 @@ def analyze_by_param(aggval, by_param, allvalues, name, key1, key2, param, param
if aggval[key1]['std_by_param'][param] > 0 and aggval[key1]['std_param'] / aggval[key1]['std_by_param'][param] < 0.6:
aggval[key1]['fit_guess'][param] = try_fits(name, key2, param_idx, by_param)
+def analyze_by_arg(aggval, by_arg, allvalues, name, key1, key2, arg_name, arg_idx):
+ aggval[key1]['std_by_arg'][arg_name] = mean_std_by_arg(
+ by_arg, allvalues, name, key2, arg_idx)
+ if aggval[key1]['std_by_arg'][arg_name] > 0 and aggval[key1]['std_arg'] / aggval[key1]['std_by_arg'][arg_name] < 0.6:
+ pass # TODO
+
def maybe_fit_function(aggval, model, by_param, parameters, name, key1, key2, unit):
if 'function' in model[key1] and 'user' in model[key1]['function']:
aggval[key1]['function']['user'] = {
@@ -868,7 +910,7 @@ def maybe_fit_function(aggval, model, by_param, parameters, name, key1, key2, un
aggval[key1]['function']['user'], name, key2, parameters, by_param,
yaxis='%s %s [%s]' % (name, key1, unit))
-def analyze(by_name, by_param, by_trace, parameters):
+def analyze(by_name, by_arg, by_param, by_trace, parameters):
aggdata = {
'state' : {},
'transition' : {},
@@ -879,9 +921,9 @@ def analyze(by_name, by_param, by_trace, parameters):
model = data['model'][isa][name]
aggdata[isa][name] = {
- 'power' : keydata(name, val, by_param, by_trace, 'means'),
- 'duration' : keydata(name, val, by_param, by_trace, 'durations'),
- 'energy' : keydata(name, val, by_param, by_trace, 'energies'),
+ 'power' : keydata(name, val, by_arg, by_param, by_trace, 'means'),
+ 'duration' : keydata(name, val, by_arg, by_param, by_trace, 'durations'),
+ 'energy' : keydata(name, val, by_arg, by_param, by_trace, 'energies'),
'clip' : {
'mean' : np.mean(val['clip_rate']),
'max' : max(val['clip_rate']),
@@ -893,11 +935,11 @@ def analyze(by_name, by_param, by_trace, parameters):
aggval['power']['std_outer'] = np.mean(val['stds'])
if isa == 'transition':
- aggval['rel_energy_prev'] = keydata(name, val, by_param, by_trace, 'rel_energies_prev')
- aggval['rel_energy_next'] = keydata(name, val, by_param, by_trace, 'rel_energies_next')
+ aggval['rel_energy_prev'] = keydata(name, val, by_arg, by_param, by_trace, 'rel_energies_prev')
+ aggval['rel_energy_next'] = keydata(name, val, by_arg, by_param, by_trace, 'rel_energies_next')
if isa == 'transition' and 'function' in data['model']['transition'][name]['timeout']:
- aggval['timeout'] = keydata(name, val, by_param, by_trace, 'timeouts')
+ aggval['timeout'] = keydata(name, val, by_arg, by_param, by_trace, 'timeouts')
for i, param in enumerate(sorted(data['model']['parameter'].keys())):
values = list(set([key[1][i] for key in by_param.keys() if key[0] == name and key[1][i] != '']))
@@ -913,6 +955,11 @@ def analyze(by_name, by_param, by_trace, parameters):
analyze_by_param(aggval, by_param, allvalues, name, 'rel_energy_next', 'rel_energies_next', param, i)
if isa == 'transition' and 'function' in data['model']['transition'][name]['timeout']:
analyze_by_param(aggval, by_param, allvalues, name, 'timeout', 'timeouts', param, i)
+ if 'parameters' in model:
+ for i, arg in enumerate(model['parameters']):
+ values = list(set([key[1][i] for key in by_arg.keys() if key[0] == name and is_numeric(key[1][i])]))
+ allvalues = [(*key[1][:i], *key[1][i+1:]) for key in by_arg.keys() if key[0] == name]
+ analyze_by_arg(aggval, by_arg, allvalues, name, 'power', 'means', arg['name'], i)
if isa == 'state':
fguess_to_function(name, 'means', aggval['power'], parameters, by_param,
@@ -958,6 +1005,7 @@ except getopt.GetoptError as err:
data = load_json(args[0])
by_name = {}
+by_arg = {}
by_param = {}
by_trace = {}
parameters = sorted(data['model']['parameter'].keys())
@@ -968,7 +1016,7 @@ for arg in args:
if 'ignore-trace-idx' not in opts or opts['ignore-trace-idx'] != runidx:
for i, elem in enumerate(run['trace']):
if elem['name'] != 'UNINITIALIZED':
- load_run_elem(i, elem, run['trace'], by_name, by_param, by_trace)
+ load_run_elem(i, elem, run['trace'], by_name, by_arg, by_param, by_trace)
if 'states' in opts:
if 'params' in opts:
@@ -1001,7 +1049,7 @@ if 'validate' in opts:
elif 'crossvalidate' in opts:
crossvalidate(by_name, by_param, by_trace, data['model'], parameters)
else:
- data['aggregate'] = analyze(by_name, by_param, by_trace, parameters)
+ data['aggregate'] = analyze(by_name, by_arg, by_param, by_trace, parameters)
# TODO optionally also plot data points for states/transitions which do not have
# a function, but may depend on a parameter (visualization is always good!)
diff --git a/lib/Kratos/DFADriver.pm b/lib/Kratos/DFADriver.pm
index 9801d99..7804d2b 100644
--- a/lib/Kratos/DFADriver.pm
+++ b/lib/Kratos/DFADriver.pm
@@ -317,15 +317,20 @@ sub printf_parameterized {
$hash = $hash->{$key};
my $std_global = $hash->{std_inner};
+ my $std_ind_arg = $hash->{std_arg};
my $std_ind_param = $hash->{std_param};
my $std_ind_trace = $hash->{std_trace};
my $std_by_param = $hash->{std_by_param};
my $std_by_trace = $hash->{std_by_trace} // {};
+ my $arg_ratio;
my $param_ratio;
my $trace_ratio;
if ( $std_global > 0 ) {
$param_ratio = $std_ind_param / $std_global;
+ if (defined $std_ind_arg) {
+ $arg_ratio = $std_ind_arg / $std_global;
+ }
}
if ( $std_ind_param > 0) {
$trace_ratio = $std_ind_trace / $std_ind_param;
@@ -351,6 +356,26 @@ sub printf_parameterized {
$param_ratio ? $param_ratio : 0 );
}
+ if ( defined $std_ind_arg and $std_global > 10
+ and $arg_ratio < 0.5
+ and not exists $hash->{argfunction}{user} )
+ {
+ printf( " %s: depends on arguments (%.2f / %.2f = %.3f)\n",
+ $key, $std_ind_arg, $std_global, $arg_ratio );
+ }
+ if ( defined $std_ind_arg and
+ (
+ $std_global < 10
+ or $arg_ratio > 0.5
+ )
+ and exists $hash->{argfunction}{user}
+ )
+ {
+ printf( " %s: should not depend on arguments (%.2f / %.2f = %.3f)\n",
+ $key, $std_ind_arg, $std_global,
+ $arg_ratio ? $arg_ratio : 0 );
+ }
+
if ( $std_global > 10 and $trace_ratio < 0.5 ) {
printf(
" %s: model insufficient, depends on trace (%.2f / %.2f = %.3f)\n",
@@ -426,7 +451,7 @@ sub printf_fit {
}
if ( exists $hash->{param_mean_goodness} ) {
printf(
- " %s: mean/ssr-fit LUT error: %.2f%% / %.f %s / %.f\n",
+ " %s: param mean/ssr-fit LUT error: %.2f%% / %.f %s / %.f\n",
$key,
$hash->{param_mean_goodness}{smape} // -1,
$hash->{param_mean_goodness}{mae}, $unit,
@@ -435,13 +460,31 @@ sub printf_fit {
}
if ( exists $hash->{param_median_goodness} ) {
printf(
- " %s: median/static LUT error: %.2f%% / %.f %s / %.f\n",
+ " %s: param median/static LUT error: %.2f%% / %.f %s / %.f\n",
$key,
$hash->{param_median_goodness}{smape} // -1,
$hash->{param_median_goodness}{mae}, $unit,
$hash->{param_mean_goodness}{rmsd}
);
}
+ if ( exists $hash->{arg_mean_goodness} ) {
+ printf(
+ " %s: arg mean/ssr-fit LUT error: %.2f%% / %.f %s / %.f\n",
+ $key,
+ $hash->{arg_mean_goodness}{smape} // -1,
+ $hash->{arg_mean_goodness}{mae}, $unit,
+ $hash->{arg_mean_goodness}{rmsd}
+ );
+ }
+ if ( exists $hash->{arg_median_goodness} ) {
+ printf(
+ " %s: arg median/static LUT error: %.2f%% / %.f %s / %.f\n",
+ $key,
+ $hash->{arg_median_goodness}{smape} // -1,
+ $hash->{arg_median_goodness}{mae}, $unit,
+ $hash->{arg_mean_goodness}{rmsd}
+ );
+ }
}
sub assess_model {