diff options
| author | Daniel Friesel <derf@finalrewind.org> | 2017-04-03 15:04:15 +0200 | 
|---|---|---|
| committer | Daniel Friesel <derf@finalrewind.org> | 2017-04-03 15:04:15 +0200 | 
| commit | 00e57331b1c7ef2b1f402f41e1223308e0d8ce61 (patch) | |
| tree | 05e9b4223072582a5a6843de6d9845213a94f341 /bin | |
initial commit
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/analyze.py | 40 | ||||
| -rwxr-xr-x | bin/cal-hist | 66 | ||||
| -rwxr-xr-x | bin/dfatool | 366 | ||||
| -rwxr-xr-x | bin/gradient | 32 | ||||
| -rwxr-xr-x | bin/merge.py | 1033 | ||||
| -rwxr-xr-x | bin/mim-vs-keysight.py | 225 | ||||
| -rwxr-xr-x | bin/mimosawatch | 24 | 
7 files changed, 1786 insertions, 0 deletions
| diff --git a/bin/analyze.py b/bin/analyze.py new file mode 100755 index 0000000..57803fe --- /dev/null +++ b/bin/analyze.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import json +import numpy as np +import os +from scipy.cluster.vq import kmeans2 +import struct +import sys +import tarfile +from dfatool import running_mean, MIMOSA + +voltage = float(sys.argv[1]) +shunt = float(sys.argv[2]) +filename = sys.argv[3] + +mim = MIMOSA(voltage, shunt) + +charges, triggers = mim.load_data(filename) +trigidx = mim.trigger_edges(triggers) +triggers = [] +cal_edges = mim.calibration_edges(running_mean(mim.currents_nocal(charges[0:trigidx[0]]), 10)) +calfunc, caldata = mim.calibration_function(charges, cal_edges) +vcalfunc = np.vectorize(calfunc, otypes=[np.float64]) + +json_out = { +    'triggers' : len(trigidx), +    'first_trig' : trigidx[0] * 10, +    'calibration' : caldata, +    'trace' : mim.analyze_states(charges, trigidx, vcalfunc) +} + +basename, _ = os.path.splitext(filename) + +# TODO also look for interesting gradients inside each state + +with open(basename + ".json", "w") as f: +    json.dump(json_out, f) +    f.close() + +#print(kmeans2(charges[:firstidx], np.array([130 * ua_step, 3.6 / 987 * 1000000, 3.6 / 99300 * 1000000]))) diff --git a/bin/cal-hist b/bin/cal-hist new file mode 100755 index 0000000..96c4469 --- /dev/null +++ b/bin/cal-hist @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import csv +import numpy as np +import os +import struct +import sys +import tarfile +import matplotlib.pyplot as plt +from dfatool import running_mean, MIMOSA + +voltage = float(sys.argv[1]) +shunt = float(sys.argv[2]) +mimfile = sys.argv[3] + +mim = MIMOSA(voltage, shunt) + +charges, triggers = mim.load_data(mimfile) +trigidx = mim.trigger_edges(triggers) +cal_edges = mim.calibration_edges(running_mean(mim.currents_nocal(charges[0:trigidx[0]]), 10)) + +#charges = charges[charges > 20000] +#charges = charges[charges < 21000] + +def show_hist(data): +	bins = np.max(data) - np.min(data) +	if bins == 0: +		bins = 1 +	if bins > 1000: +		bins = bins / 10 +	#bins = 500 +	n, bins, patches = plt.hist(data, bins, normed=0, facecolor='green', alpha=0.75) +	plt.grid(True) +	plt.show() +	print(np.histogram(data, bins=bins)) + +#show_hist(charges[cal_edges[0]:cal_edges[1]]) +#show_hist(charges[cal_edges[4]:cal_edges[5]]) +#show_hist(charges[cal_edges[2]:cal_edges[3]]) +#show_hist(charges[trigidx[7]:trigidx[8]]) +#show_hist(np.array(charges)) + +#print(charges[cal_edges[0]:cal_edges[1]]) +#print(charges[cal_edges[4]:cal_edges[5]]) +#print(charges[cal_edges[2]:cal_edges[3]]) + +plt.hist(mim.charge_to_current_nocal(charges[cal_edges[2]:cal_edges[3]]) * 1e-3, 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('mA MimosaCMD') +plt.ylabel('#') +plt.grid(True) +plt.show() +plt.hist(mim.charge_to_current_nocal(charges[cal_edges[4]:cal_edges[5]]) * 1e-3, 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('mA MimosaCMD') +plt.ylabel('#') +plt.grid(True) +plt.show() +plt.hist(mim.charge_to_current_nocal(charges[cal_edges[0]:cal_edges[1]]) * 1e-3, 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('mA MimosaCMD') +plt.ylabel('#') +plt.grid(True) +plt.show() +plt.hist(charges[cal_edges[0]:cal_edges[1]], 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('Rohwert MimosaCMD') +plt.ylabel('#') +plt.grid(True) +plt.show() diff --git a/bin/dfatool b/bin/dfatool new file mode 100755 index 0000000..00b4196 --- /dev/null +++ b/bin/dfatool @@ -0,0 +1,366 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use 5.020; + +use Getopt::Long; +use IO::Handle; +use Kratos::DFADriver; +use List::Util qw(max sum); +use Time::Progress; + +our $VERSION = '0.00'; +my %opt; + +GetOptions( +	\%opt, +	qw{ +	  exclude-states=s@ +	  ignore-nested-calls +	  logging! +	  plot=s +	  no-cache +	  no-update +	  state-duration=i +	  shunt=f +	  trace-filter=s@ +	  trace-revisit=i +	  trigger-pin=i +	  trigger-port=s +	  voltage=f +	  offset=i +	  zomg-fasta-nao +	  } +); + +if ( @ARGV < 2 ) { +	show_usage(); +} + +@{ $opt{'exclude-states'} } +  = split( qr{,}, join( q{,}, @{ $opt{'exclude-states'} // [] } ) ); + +my ( $command, $xml_file, @data_files ) = @ARGV; + +my $driver = Kratos::DFADriver->new( +	cache => $opt{'no-cache'} ? 0 : 1, +	data_file       => $data_files[0], +	excluded_states => $opt{'exclude-states'} // [], +	fast_analysis   => $opt{'zomg-fasta-nao'} // 0, +	ignore_nested   => $opt{'ignore-nested-calls'} // 0, +	logging         => $opt{logging} // 1, +	state_duration  => $opt{'state-duration'} // 1000, +	trigger_pin     => $opt{'trigger-pin'}, +	trigger_port    => $opt{'trigger-port'}, +	merge_args      => $opt{'plot'} +	? [ map { "--$_" } split( qr{,}, $opt{'plot'} ) ] +	: [], +	mimosa_offset  => $opt{offset}          // 130, +	mimosa_shunt   => $opt{shunt}           // 330, +	mimosa_voltage => $opt{voltage}         // 3.60, +	trace_filter   => $opt{'trace-filter'}  // [], +	trace_revisit  => $opt{'trace-revisit'} // 2, +	xml_file       => $xml_file, +); + +my %action = ( +	clean => sub { +		$driver->launchpad_log_clean; +	}, +	enable => sub { +		$driver->write_acc_files; +	}, +	disable => sub { +		$driver->rm_acc_files; +	}, +	'to-tikz' => sub { +		say $driver->to_tikz; +	}, +	'to-dfa' => sub { +		say join( +			"\n", +			map { +				join( ' -> ', map { "$_->[0]($_->[1],$_->[2])" } @{$_} ) +			} $driver->runs +		); +	}, +	flash => sub { +		say "Compiling Kratos and flashing Launchpad"; +		$driver->launchpad_flash; +	}, +	maketest => sub { +		$driver->write_test_files; +	}, +	rmtest => sub { +		$driver->rm_test_files; +	}, +	reset => sub { +		say "Resetting MSP430"; +		$driver->launchpad_reset; +	}, +	log => sub { +		say "Resetting MSP430"; +		$driver->launchpad_reset; +		say "Connecting to Launchpad"; +		$driver->launchpad_log_init; +		say "Starting measurement"; +		$driver->mimosa->start; +		say "Calibrating MIMOSA"; +		$driver->mimosa->calibrate; +		STDOUT->autoflush(1); +		print "Waiting for sync"; + +		while ( not $driver->launchpad_log_is_synced ) { +			$driver->launchpad_log_read; +			print q{.}; +		} +		print "\r\e[2KSynced with DriverEval app\n"; +		my ( $iter, $run, $maxrun ) = $driver->launchpad_log_status; +		my $timer = Time::Progress->new( +			min => 0, +			max => $maxrun +		); +		while ( $run < 1 ) { +			$driver->launchpad_log_read; +			( $iter, $run, $maxrun ) = $driver->launchpad_log_status; +		} +		while ( $driver->launchpad_log_is_synced ) { +			$driver->launchpad_log_read; +			( $iter, $run, $maxrun ) = $driver->launchpad_log_status; +			print $timer->report( +"\r\e[2K%40b %p (${run}/${maxrun})  %L elapsed  %E remaining  in iteration ${iter}", +				$run +			); +			if ( $run == $maxrun ) { +				printf( "\r\e[2KIteration %d done after %d seconds\n", +					$iter - 1, $timer->elapsed($run) ); +				say "Stopping measurement"; +				$driver->mimosa->stop; +				say "Archiving files"; +				$driver->archive_files; +				return; +			} + +			if ( my @errors = $driver->launchpad_get_errors ) { +				say "\r\e[2KErrors in iteration ${iter}, run ${run}"; +				say join( "\n", @errors ); +				say "Aborting measurement. Current run will not be saved."; +				$driver->mimosa->kill; +				exit 1; +			} +		} +	}, +	analyze => sub { +		$driver->analyze(@data_files); +		$driver->assess_model; +		if ( not $opt{'no-update'} ) { +			$driver->update_model; +		} +	}, +	'analyze-tex' => sub { +		$driver->analyze(@data_files); +		$driver->assess_model_tex; +	}, +	analyzesingle => sub { +		if ( $opt{'no-cache'} or not $driver->log->load_cache ) { +			say "Analyzing DriverEval iterations (this may take a while)"; +			$driver->log->load_archive; +			$driver->log->preprocess; +			$driver->log->save_cache; +		} +		say "Processing"; +		$driver->log->analyze; +		$driver->assess_model; +		if ( not $opt{'no-update'} ) { +			$driver->update_model; +		} +	}, +	validate => sub { +		$driver->validate_model(@data_files); +	}, +	crossvalidate => sub { +		printf("./dfatool crossvalidate %s %s\n", +			$xml_file, +			join(q{ }, @data_files) +		); +		$driver->crossvalidate_model(@data_files); +	}, +	ls => sub { +		for my $file (@data_files) { +			my $log = $driver->log($file); +			$log->load_archive; +			my $setup = $log->setup; +			say $file; +			printf( +				"  %.2fV @ %3dΩ, %dms per state, max revisit %d\n", +				$setup->{mimosa_voltage}, $setup->{mimosa_shunt}, +				$setup->{state_duration}, $setup->{trace_revisit} +			); +			if ( $setup->{excluded_states} and @{ $setup->{excluded_states} } ) +			{ +				printf( "  excluded states: %s\n", $setup->{excluded_states} ); +			} +			if ( $setup->{trace_filter} and @{ $setup->{trace_filter} } ) { +				printf( "  trace filter: %s\n", +					join( q{ | }, @{ $setup->{trace_filter} } ) ); +			} +		} +	}, +	list => sub { +		for my $file (@data_files) { +			my $log = $driver->log($file); +			if ( $opt{'no-cache'} or not $driver->log->load_cache ) { +				$log->load_archive; +				$log->preprocess; +				$log->save_cache; +			} +			$log->analyze; +			my $data  = $log->data; +			my $setup = $log->setup; +			say $file; +			printf( +				"  %.2fV @ %3dΩ, %dms per state, max revisit %d\n", +				$setup->{mimosa_voltage}, $setup->{mimosa_shunt}, +				$setup->{state_duration}, $setup->{trace_revisit} +			); +			if ( $setup->{excluded_states} and @{ $setup->{excluded_states} } ) +			{ +				printf( "  excluded states: %s\n", $setup->{excluded_states} ); +			} +			if ( $setup->{trace_filter} and @{ $setup->{trace_filter} } ) { +				printf( "  trace filter: %s\n", +					join( q{ | }, @{ $setup->{trace_filter} } ) ); +			} +			printf( "  MIMOSA offset: %5s %5s %5s\n", 'inf', '100k', '1k' ); +			for my $cal ( @{ $data->{calibration} } ) { +				printf( "                 %5.f %5.f %5.f µW\n", +					@{$cal}{qw{r0_err_uW r2_err_uW r1_err_uW}}, +				); +			} +			for my $state ( sort keys %{ $data->{aggregate}{state} } ) { +				if ( $state ne 'UNINITIALIZED' ) { +					my $val = $data->{aggregate}{state}{$state}; +					printf( +						"  %15s : %.f±%.f = %.f, clip %.f%% ^ %.f%%\n", +						$state,                   $val->{power}{mean}, +						$val->{power}{std_inner}, $val->{power}{median}, +						$val->{clip}{mean},       $val->{clip}{max} +					); +				} +			} +		} +	}, +	show => sub { +		for my $file (@data_files) { +			my $log = $driver->log($file); +			$log->load_archive; +			if ( $opt{'no-cache'} or not $driver->log->load_cache ) { +				$log->load_archive; +				$log->preprocess; +				$log->save_cache; +			} +			$log->analyze; +			for my $trace ( @{ $log->data->{traces} } ) { +				my ( @data, @widths ); +				printf( '%3d', $trace->{id} ); +				for my $elem ( @{ $trace->{trace} } ) { +					if (    $elem->{isa} eq 'state' +						and $elem->{name} ne 'UNINITIALIZED' ) +					{ +						push( @widths, max (length( $elem->{name} ), 9) ); +						printf(' → %9s', $elem->{name}); +						my @powers +						  = map { $_->{uW_mean} } @{ $elem->{offline} }; +						push( @data, sum(@powers) / @powers ); +					} +					elsif ( $elem->{isa} eq 'transition' ) { +						my $args = join( q{, }, @{ $elem->{args} // [qw[?]] } ); +						my $pstr = "$elem->{name}($args)"; +						push( @widths, max (length($pstr), 9) ); +						printf(' → %9s', $pstr); +						my @energies +						  = map { $_->{uW_mean_delta} * ( $_->{us} - 20 ) } +						  @{ $elem->{offline} }; +						push( @data, sum(@energies) / @energies ); +					} +				} +				print "\n   "; +				for my $i ( 0 .. $#data ) { +					printf( " → \%$widths[$i]d", $data[$i] ); +				} +				print "\n"; +			} +		} +	}, +	reset => sub { +		$driver->reset_model; +	}, +); + +$SIG{INT} = $SIG{TERM} = sub { +	say "\r\e[2KTerminating MIMOSA daemon"; +	$driver->mimosa->kill; +	say "Goodbye"; +	exit 0; +}; + +sub show_usage { +	say STDERR "Usage: $0 <action> <DFA driver XML file>"; +	say STDERR 'Supported actions: ' . join( q{ }, sort keys %action ); +	exit 1; +} + +if ( exists $action{$command} ) { +	$action{$command}(); +} +elsif ( $command eq 'loop' ) { +	$action{clean}(); +	$action{enable}(); +	$action{maketest}(); +	$action{flash}(); +	while (1) { +		$action{log}(); +	} +} +else { +	show_usage(); +} + +__END__ + +=head1 NAME + +=head1 SYNOPSIS + +=head1 VERSION + +=head1 DESCRIPTION + +=head1 OPTIONS + +=over + +=back + +=head1 EXIT STATUS + +=head1 CONFIGURATION + +None. + +=head1 DEPENDENCIES + +=over + +=back + +=head1 BUGS AND LIMITATIONS + +=head1 AUTHOR + +Copyright (C) 2016 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt> + +=head1 LICENSE + +  0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/bin/gradient b/bin/gradient new file mode 100755 index 0000000..d2f43ef --- /dev/null +++ b/bin/gradient @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import csv +import numpy as np +import os +import struct +import sys +import tarfile +import matplotlib.pyplot as plt +from dfatool import running_mean, MIMOSA + +voltage = float(sys.argv[1]) +shunt = float(sys.argv[2]) +mimfile = sys.argv[3] + +mim = MIMOSA(voltage, shunt) + +charges, triggers = mim.load_data(mimfile) +charges = charges[2000000:3000000] + +currents = running_mean(mim.charge_to_current_nocal(charges), 10) * 1e-6 +xr = np.arange(len(currents)) * 1e-5 +threshold = 3e-5 +grad = np.gradient(currents, 2) +tp = np.abs(grad) > threshold +plt.plot( xr, currents, "r-") +plt.plot( xr, grad, "y-") +plt.plot( xr[tp], grad[tp], "bo") +plt.xlabel('Zeit [s]') +plt.ylabel('Strom [A]') +plt.grid(True) +plt.show() diff --git a/bin/merge.py b/bin/merge.py new file mode 100755 index 0000000..1ff7e74 --- /dev/null +++ b/bin/merge.py @@ -0,0 +1,1033 @@ +#!/usr/bin/env python3 + +import getopt +import json +import numpy as np +import os +import re +import sys +import plotter +from copy import deepcopy +from dfatool import aggregate_measures, regression_measures, is_numeric, powerset +from matplotlib.patches import Polygon +from scipy import optimize + +opts = {} + +def load_json(filename): +    with open(filename, "r") as f: +        return json.load(f) + +def save_json(data, filename): +    with open(filename, "w") as f: +        return json.dump(data, f) + +def print_data(aggregate): +    for key in sorted(aggregate.keys()): +        data = aggregate[key] +        name, params = key +        print("%s @ %s : ~ = %.f (%.f, %.f)  µ_σ_outer = %.f  n = %d" % +            (name, params, np.median(data['means']), np.percentile(data['means'], 25), +                np.percentile(data['means'], 75), np.mean(data['stds']), len(data['means']))) + +def flatten(somelist): +    return [item for sublist in somelist for item in sublist] + +def mimosa_data(elem): +    means = [x['uW_mean'] for x in elem['offline']] +    durations = [x['us'] - 20 for x in elem['offline']] +    stds = [x['uW_std'] for x in elem['offline']] +    energies = [x['uW_mean'] * (x['us'] - 20) for x in elem['offline']] +    clips = [x['clip_rate'] for x in elem['offline']] +    substate_thresholds = [] +    substate_data = [] +    timeouts = [] +    rel_energies = [] +    if 'timeout' in elem['offline'][0]: +        timeouts = [x['timeout'] for x in elem['offline']] +    if 'uW_mean_delta' in elem['offline'][0]: +        rel_energies = [x['uW_mean_delta'] * (x['us'] - 20) for x in elem['offline']] +    for x in elem['offline']: +        if 'substates' in x: +            substate_thresholds.append(x['substates']['threshold']) +            substate_data.append(x['substates']['states']) + +    return means, stds, durations, energies, rel_energies, clips, timeouts, substate_thresholds + +def online_data(elem): +    means = [int(x['power']) for x in elem['online']] +    durations = [int(x['time']) for x in elem['online']] + +    return means, durations + +# parameters = statistic variables such as txpower, bitrate etc. +# variables = function variables/parameters set by linear regression +def str_to_param_function(function_string, parameters, variables): +    rawfunction = function_string +    dependson = [False] * len(parameters) + +    for i in range(len(parameters)): +        if rawfunction.find("global(%s)" % (parameters[i])) >= 0: +            dependson[i] = True +            rawfunction = rawfunction.replace("global(%s)" % (parameters[i]), "arg[%d]" % (i)) +    for i in range(len(variables)): +        rawfunction = rawfunction.replace("param(%d)" % (i), "param[%d]" % (i)) +    fitfunc = eval("lambda param, arg: " + rawfunction); + +    return fitfunc, dependson + +def mk_function_data(name, paramdata, parameters, dependson, datatype): +    X = [[] for i in range(len(parameters))] +    Xm = [[] for i in range(len(parameters))] +    Y = [] +    Ym = [] + +    num_valid = 0 +    num_total = 0 + +    for key, val in paramdata.items(): +        if key[0] == name and len(key[1]) == len(parameters): +            valid = True +            num_total += 1 +            for i in range(len(parameters)): +                if dependson[i] and not is_numeric(key[1][i]): +                    valid = False +            if valid: +                num_valid += 1 +                Y.extend(val[datatype]) +                Ym.append(np.median(val[datatype])) +                for i in range(len(parameters)): +                    if dependson[i] or is_numeric(key[1][i]): +                        X[i].extend([int(key[1][i])] * len(val[datatype])) +                        Xm[i].append(int(key[1][i])) +                    else: +                        X[i].extend([0] * len(val[datatype])) +                        Xm[i].append(0) + +    for i in range(len(parameters)): +        X[i] = np.array(X[i]) +        Xm[i] = np.array(Xm[i]) +    X = tuple(X) +    Xm = tuple(Xm) +    Y = np.array(Y) +    Ym = np.array(Ym) + +    return X, Y, Xm, Ym, num_valid, num_total + +def raw_num0_8(num): +    return (8 - num1(num)) + +def raw_num0_16(num): +    return (16 - num1(num)) + +def raw_num1(num): +    return bin(int(num)).count("1") + +num0_8 = np.vectorize(raw_num0_8) +num0_16 = np.vectorize(raw_num0_16) +num1 = np.vectorize(raw_num1) + +def try_fits(name, datatype, paramidx, paramdata): +    functions = { +        'linear' : lambda param, arg: param[0] + param[1] * arg, +        'logarithmic' : lambda param, arg: param[0] + param[1] * np.log(arg), +        'logarithmic1' : lambda param, arg: param[0] + param[1] * np.log(arg + 1), +        'exponential' : lambda param, arg: param[0] + param[1] * np.exp(arg), +        #'polynomial' : lambda param, arg: param[0] + param[1] * arg + param[2] * arg ** 2, +        'square' : lambda param, arg: param[0] + param[1] * arg ** 2, +        'fractional' : lambda param, arg: param[0] + param[1] / arg, +        'sqrt' : lambda param, arg: param[0] + param[1] * np.sqrt(arg), +        #'num0_8' : lambda param, arg: param[0] + param[1] * num0_8(arg), +        #'num0_16' : lambda param, arg: param[0] + param[1] * num0_16(arg), +        #'num1' : lambda param, arg: param[0] + param[1] * num1(arg), +    } +    results = dict([[key, []] for key in functions.keys()]) +    errors = {} + +    allvalues = [(*key[1][:paramidx], *key[1][paramidx+1:]) for key in paramdata.keys() if key[0] == name] +    allvalues = list(set(allvalues)) + +    for value in allvalues: +        X = [] +        Xm = [] +        Y = [] +        Ym = [] +        num_valid = 0 +        num_total = 0 +        for key, val in paramdata.items(): +            if key[0] == name and len(key[1]) > paramidx and (*key[1][:paramidx], *key[1][paramidx+1:]) == value: +                num_total += 1 +                if is_numeric(key[1][paramidx]): +                    num_valid += 1 +                    Y.extend(val[datatype]) +                    Ym.append(np.median(val[datatype])) +                    X.extend([int(key[1][paramidx])] * len(val[datatype])) +                    Xm.append(int(key[1][paramidx])) +                    if int(key[1][paramidx]) == 0: +                        functions.pop('fractional', None) +                    if int(key[1][paramidx]) <= 0: +                        functions.pop('logarithmic', None) +                    if int(key[1][paramidx]) < 0: +                        functions.pop('logarithmic1', None) +                        functions.pop('sqrt', None) +                    if int(key[1][paramidx]) > 64: +                        functions.pop('exponential', None) + +        # there should be -at least- two values when fitting +        if num_valid > 1: +            Y = np.array(Y) +            Ym = np.array(Ym) +            X = np.array(X) +            Xm = np.array(Xm) +            for kind, function in functions.items(): +                results[kind] = {} +                errfunc = lambda P, X, y: function(P, X) - y +                try: +                    res = optimize.least_squares(errfunc, [0, 1], args=(X, Y), xtol=2e-15) +                    measures = regression_measures(function(res.x, X), Y) +                    for k, v in measures.items(): +                        if not k in results[kind]: +                            results[kind][k] = [] +                        results[kind][k].append(v) +                except: +                    pass + +    for function_name, result in results.items(): +        if len(result) > 0 and function_name in functions: +            errors[function_name] = {} +            for measure in result.keys(): +                errors[function_name][measure] = np.mean(result[measure]) + +    return errors + +def fit_function(function, name, datatype, parameters, paramdata, xaxis=None, yaxis=None): + +    variables = list(map(lambda x: float(x), function['params'])) +    fitfunc, dependson = str_to_param_function(function['raw'], parameters, variables) + +    X, Y, Xm, Ym, num_valid, num_total = mk_function_data(name, paramdata, parameters, dependson, datatype) + +    if num_valid > 0: + +        if num_valid != num_total: +            num_invalid = num_total - num_valid +            print("Warning: fit(%s): %d of %d states had incomplete parameter hashes" % (name, num_invalid, len(paramdata))) + +        errfunc = lambda P, X, y: fitfunc(P, X) - y +        try: +            res = optimize.least_squares(errfunc, variables, args=(X, Y), xtol=2e-15) # loss='cauchy' +        except ValueError as err: +            function['error'] = str(err) +            return +        #x1 = optimize.curve_fit(lambda param, *arg: fitfunc(param, arg), X, Y, functionparams) +        measures = regression_measures(fitfunc(res.x, X), Y) + +        if res.status <= 0: +            function['error'] = res.message +            return + +        if 'fit' in opts: +            for i in range(len(parameters)): +                plotter.plot_param_fit(function['raw'], name, fitfunc, res.x, parameters, datatype, i, X, Y, xaxis, yaxis) + +        function['params'] = list(res.x) +        function['fit'] = measures + +    else: +        function['error'] = 'log contained no numeric parameters' + +def assess_function(function, name, datatype, parameters, paramdata): + +    variables = list(map(lambda x: float(x), function['params'])) +    fitfunc, dependson = str_to_param_function(function['raw'], parameters, variables) +    X, Y, Xm, Ym, num_valid, num_total = mk_function_data(name, paramdata, parameters, dependson, datatype) + +    if num_valid > 0: +        return regression_measures(fitfunc(variables, X), Y) +    else: +        return None + +def xv_assess_function(name, funbase, what, validation, mae, smape): +    goodness = assess_function(funbase, name, what, parameters, validation) +    if goodness != None: +        if not name in mae: +            mae[name] = [] +        if not name in smape: +            smape[name] = [] +        mae[name].append(goodness['mae']) +        smape[name].append(goodness['smape']) + +def xv2_assess_function(name, funbase, what, validation, mae, smape, rmsd): +    goodness = assess_function(funbase, name, what, parameters, validation) +    if goodness != None: +        if goodness['mae'] < 10e9: +            mae.append(goodness['mae']) +            rmsd.append(goodness['rmsd']) +            smape.append(goodness['smape']) +        else: +            print('[!] Ignoring MAE of %d (SMAPE %.f)' % (goodness['mae'], goodness['smape'])) + +# Returns the values used for each parameter in the measurement, e.g. +# { 'txpower' : [1, 2, 4, 8], 'length' : [16] } +# non-numeric values such as '' are skipped +def param_values(parameters, by_param): +    paramvalues = dict([[param, set()] for param 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 param_key(elem): +    name = elem['name'] +    paramtuple = () + +    if 'parameter' in elem: +        paramkeys = sorted(elem['parameter'].keys()) +        paramtuple = tuple([elem['parameter'][x] for x in paramkeys]) + +    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 add_data_to_aggregate(aggregate, key, isa, data): +    if not key in aggregate: +        aggregate[key] = { +            'isa' : isa, +        } +        for datakey in data.keys(): +            aggregate[key][datakey] = [] +    for datakey, dataval in data.items(): +        aggregate[key][datakey].extend(dataval) + +def fake_add_data_to_aggregate(aggregate, key, isa, database, idx): +    timeout_val = [] +    if len(database['timeouts']): +        timeout_val = [database['timeouts'][idx]] +    rel_energy_val = [] +    if len(database['rel_energies']): +        rel_energy_val = [database['rel_energies'][idx]] +    add_data_to_aggregate(aggregate, key, isa, { +        'means' : [database['means'][idx]], +        'stds' : [database['stds'][idx]], +        'durations' : [database['durations'][idx]], +        'energies' : [database['energies'][idx]], +        'rel_energies' : rel_energy_val, +        'clip_rate' : [database['clip_rate'][idx]], +        'timeouts' : timeout_val, +    }) + +def weight_by_name(aggdata): +    total = {} +    count = {} +    weight = {} +    for key in aggdata.keys(): +        if not key[0] in total: +            total[key[0]] = 0 +        total[key[0]] += len(aggdata[key]['means']) +        count[key] = len(aggdata[key]['means']) +    for key in aggdata.keys(): +        weight[key] = float(count[key]) / total[key[0]] +    return weight + +# returns the mean standard deviation of all measurements of 'what' +# (e.g. power consumption or timeout) for state/transition 'name' where +# parameter 'index' is dynamic and all other parameters are fixed. +# I.e., if parameters are a, b, c ∈ {1,2,3} and 'index' corresponds to b', 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_param(data, keys, name, what, index): +    partitions = [] +    for key in keys: +        partition = [] +        for k, v in data.items(): +            if (*k[1][:index], *k[1][index+1:]) == key and k[0] == name: +                partition.extend(v[what]) +        partitions.append(partition) +    return np.mean([np.std(partition) for partition in partitions]) + +# 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. +# this is done separately for each transition (-> returns a dictionary) +def mean_std_by_trace_part(data, transitions, name, what): +    ret = {} +    for transition in transitions: +        keys = set(map(lambda x : (x[0], x[1], tuple([y for y in x[2] if y != transition])), data.keys())) +        ret[transition] = {} +        partitions = [] +        for key in keys: +            partition = [] +            for k, v in data.items(): +                key_without_transition = (k[0], k[1], tuple([y for y in k[2] if y != transition])) +                if key[0] == name and key == key_without_transition: +                    partition.extend(v[what]) +            if len(partition): +                partitions.append(partition) +        ret[transition] = np.mean([np.std(partition) for partition in partitions]) +    return ret + + +def load_run_elem(index, element, trace, by_name, by_param, by_trace): +    means, stds, durations, energies, rel_energies, clips, timeouts, sub_thresholds = mimosa_data(element) + +    online_means = [] +    online_durations = [] +    if element['isa'] == 'state': +        online_means, online_durations = online_data(element) + +    key = param_key(element) +    pre_trace = tuple(map(lambda x : x['name'], trace[1:index:2])) +    trace_key = (*key, pre_trace) +    name = element['name'] + +    add_data_to_aggregate(by_name, name, element['isa'], { +        'means' : means, +        'stds' : stds, +        'durations' : durations, +        'energies' : energies, +        'rel_energies' : rel_energies, +        'clip_rate' : clips, +        'timeouts' : timeouts, +        'sub_thresholds' : sub_thresholds, +        'param' : [key[1]] * len(means), +        'online_means' : online_means, +        'online_durations' : online_durations, +    }) +    add_data_to_aggregate(by_param, key, element['isa'], { +        'means' : means, +        'stds' : stds, +        'durations' : durations, +        'energies' : energies, +        'rel_energies' : rel_energies, +        'clip_rate' : clips, +        'timeouts' : timeouts, +        'sub_thresholds' : sub_thresholds, +        'online_means' : online_means, +        'online_durations' : online_durations, +    }) +    add_data_to_aggregate(by_trace, trace_key, element['isa'], { +        'means' : means, +        'stds' : stds, +        'durations' : durations, +        'energies' : energies, +        'rel_energies' : rel_energies, +        'clip_rate' : clips, +        'timeouts' : timeouts, +        'sub_thresholds' : sub_thresholds, +        'online_means' : online_means, +        'online_durations' : online_durations, +    }) + +def fmap(name, funtype): +    if funtype == 'linear': +        return "global(%s)" % name +    if funtype == 'logarithmic': +        return "np.log(global(%s))" % name +    if funtype == 'logarithmic1': +        return "np.log(global(%s) + 1)" % name +    if funtype == 'exponential': +        return "np.exp(global(%s))" % name +    if funtype == 'square': +        return "global(%s)**2" % name +    if funtype == 'fractional': +        return "1 / global(%s)" % name +    if funtype == 'sqrt': +        return "np.sqrt(global(%s))" % name +    if funtype == 'num0_8': +        return "num0_8(global(%s))" % name +    if funtype == 'num0_16': +        return "num0_16(global(%s))" % name +    if funtype == 'num1': +        return "num1(global(%s))" % name +    return "ERROR" + +def fguess_to_function(name, datatype, aggdata, parameters, paramdata, yaxis): +    best_fit = {} +    fitguess = aggdata['fit_guess'] +    params = list(filter(lambda x : x in fitguess, parameters)) +    if len(params) > 0: +        for param in params: +            best_fit_val = np.inf +            for func_name, fit_val in fitguess[param].items(): +                if fit_val['rmsd'] < best_fit_val: +                    best_fit_val = fit_val['rmsd'] +                    best_fit[param] = func_name +        buf = '0' +        pidx = 0 +        for elem in powerset(best_fit.items()): +            buf += " + param(%d)" % pidx +            pidx += 1 +            for fun in elem: +                buf += " * %s" % fmap(*fun) +        aggdata['function']['estimate'] = { +            'raw' : buf, +            'params' : list(np.ones((pidx))), +            'base' : [best_fit[param] for param in params] +        } +        fit_function( +            aggdata['function']['estimate'], name, datatype, parameters, +            paramdata, yaxis=yaxis) + +def param_measures(name, paramdata, key, fun): +    mae = [] +    smape = [] +    rmsd = [] +    for pkey, pval in paramdata.items(): +        if pkey[0] == name: +            # Median ist besseres Maß für MAE / SMAPE, +            # Mean ist besseres für SSR. Da least_squares SSR optimiert +            # nutzen wir hier auch Mean. +            goodness = aggregate_measures(fun(pval[key]), pval[key]) +            mae.append(goodness['mae']) +            rmsd.append(goodness['rmsd']) +            if 'smape' in goodness: +                smape.append(goodness['smape']) +    ret = { +        'mae' : np.mean(mae), +        'rmsd' : np.mean(rmsd) +    } +    if len(smape): +        ret['smape'] = np.mean(smape) + +    return ret + +def keydata(name, val, paramdata, tracedata, key): +    ret = { +        'count' : len(val[key]), +        'median' : np.median(val[key]), +        'mean'   : np.mean(val[key]), +        'mean_goodness' : aggregate_measures(np.mean(val[key]), val[key]), +        'median_goodness' : aggregate_measures(np.median(val[key]), val[key]), +        'param_mean_goodness' : param_measures(name, paramdata, key, np.mean), +        'param_median_goodness' : param_measures(name, paramdata, key, np.median), +        'std_inner' : np.std(val[key]), +        'std_param' : np.mean([np.std(paramdata[x][key]) for x in paramdata.keys() if x[0] == name]), +        'std_trace' : np.mean([np.std(tracedata[x][key]) for x in tracedata.keys() if x[0] == name]), +        'std_by_param' : {}, +        'fit_guess' : {}, +        'function' : {}, +    } + +    return ret + +def splitidx_kfold(length, num_slices): +    pairs = [] +    indexes = np.arange(length) +    for i in range(0, num_slices): +        training = np.delete(indexes, slice(i, None, num_slices)) +        validation = indexes[i::num_slices] +        pairs.append((training, validation)) +    return pairs + +def splitidx_srs(length, num_slices): +    pairs = [] +    for i in range(0, num_slices): +        shuffled = np.random.permutation(np.arange(length)) +        border = int(length * float(2) / 3) +        training = shuffled[:border] +        validation = shuffled[border:] +        pairs.append((training, validation)) +    return pairs + +def val_run(aggdata, split_fun, count): +    mae = [] +    smape = [] +    rmsd = [] +    pairs = split_fun(len(aggdata), count) +    for i in range(0, count): +        training = aggdata[pairs[i][0]] +        validation = aggdata[pairs[i][1]] +        median = np.median(training) +        goodness = aggregate_measures(median, validation) +        mae.append(goodness['mae']) +        rmsd.append(goodness['rmsd']) +        if 'smape' in goodness: +            smape.append(goodness['smape']) + +    mae_mean = np.mean(mae) +    rmsd_mean = np.mean(rmsd) +    if len(smape): +        smape_mean = np.mean(smape) +    else: +        smape_mean = -1 + +    return mae_mean, smape_mean, rmsd_mean + +# by_trace is not part of the cross-validation process +def val_run_fun(aggdata, by_trace, name, key, funtype1, funtype2, splitfun, count): +    aggdata = aggdata[name] +    isa = aggdata['isa'] +    mae = [] +    smape = [] +    rmsd = [] +    estimates = [] +    pairs = splitfun(len(aggdata[key]), count) +    for i in range(0, count): +        bpa_training = {} +        bpa_validation = {} + +        for idx in pairs[i][0]: +            bpa_key = (name, aggdata['param'][idx]) +            fake_add_data_to_aggregate(bpa_training, bpa_key, isa, aggdata, idx) +        for idx in pairs[i][1]: +            bpa_key = (name, aggdata['param'][idx]) +            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) +        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': +                if 'base' in ares[isa][name][funtype1]['function'][funtype2]: +                    estimates.append(tuple(ares[isa][name][funtype1]['function'][funtype2]['base'])) +                else: +                    estimates.append(None) +    return mae, smape, rmsd, estimates + +# by_trace is not part of the cross-validation process +def val_run_fun_p(aggdata, by_trace, name, key, funtype1, funtype2, splitfun, count): +    aggdata = dict([[x, aggdata[x]] for x in aggdata if x[0] == name]) +    isa = aggdata[list(aggdata.keys())[0]]['isa'] +    mae = [] +    smape = [] +    rmsd = [] +    estimates = [] +    pairs = splitfun(len(aggdata.keys()), count) # pairs are by_param index arrays +    keys = sorted(aggdata.keys()) +    for i in range(0, count): +        bpa_training = dict([[keys[x], aggdata[keys[x]]] for x in pairs[i][0]]) +        bpa_validation = dict([[keys[x], aggdata[keys[x]]] for x in pairs[i][1]]) +        bna_training = {} +        for val in bpa_training.values(): +            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) +        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': +                if 'base' in ares[isa][name][funtype1]['function'][funtype2]: +                    estimates.append(tuple(ares[isa][name][funtype1]['function'][funtype2]['base'])) +                else: +                    estimates.append(None) +    return mae, smape, rmsd, estimates + +def crossvalidate(by_name, by_param, by_trace, model, parameters): +    param_mc_count = 200 +    paramv = param_values(parameters, by_param) +    for name in sorted(by_name.keys()): +        isa = by_name[name]['isa'] +        by_name[name]['means'] = np.array(by_name[name]['means']) +        by_name[name]['energies'] = np.array(by_name[name]['energies']) +        by_name[name]['rel_energies'] = np.array(by_name[name]['rel_energies']) +        by_name[name]['durations'] = np.array(by_name[name]['durations']) + +        if isa == 'state': +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['means'], splitidx_srs, 200) +            print('%16s,   static      power,             Monte Carlo: MAE %8.f µW,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['means'], splitidx_kfold, 10) +            print('%16s,   static      power,             10-fold sys: MAE %8.f µW,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +        else: +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['energies'], splitidx_srs, 200) +            print('%16s,   static     energy,             Monte Carlo: MAE %8.f pJ,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['energies'], splitidx_kfold, 10) +            print('%16s,   static     energy,             10-fold sys: MAE %8.f pJ,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['rel_energies'], splitidx_srs, 200) +            print('%16s,   static rel_energy,             Monte Carlo: MAE %8.f pJ,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['rel_energies'], splitidx_kfold, 10) +            print('%16s,   static rel_energy,             10-fold sys: MAE %8.f pJ,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['durations'], splitidx_srs, 200) +            print('%16s,   static   duration,             Monte Carlo: MAE %8.f µs,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) +            mae_mean, smape_mean, rms_mean = val_run(by_name[name]['durations'], splitidx_kfold, 10) +            print('%16s,   static   duration,             10-fold sys: MAE %8.f µs,  SMAPE %6.2f%%,  RMS %d' % (name, mae_mean, smape_mean, rms_mean)) + +        def print_estimates(estimates, total): +            histogram = {} +            buf = '    ' +            for estimate in estimates: +                if not estimate in histogram: +                    histogram[estimate] = 1 +                else: +                    histogram[estimate] += 1 +            for estimate, count in sorted(histogram.items(), key=lambda kv: kv[1], reverse=True): +                buf += '  %.f%% %s' % (count * 100 / total, estimate) +            if len(estimates): +                print(buf) + +        def val_run_funs(by_name, by_trace, name, key1, key2, key3, unit): +            mae, smape, rmsd, estimates = val_run_fun(by_name, by_trace, name, key1, key2, key3, splitidx_srs, param_mc_count) +            print('%16s, %8s %10s,             Monte Carlo: MAE %8.f %s,  SMAPE %6.2f%%,  RMS %d' % ( +                name, key3, key2, np.mean(mae), unit, np.mean(smape), np.mean(rmsd))) +            print_estimates(estimates, param_mc_count) +            mae, smape, rmsd, estimates = val_run_fun(by_name, by_trace, name, key1, key2, key3, splitidx_kfold, 10) +            print('%16s, %8s %10s,             10-fold sys: MAE %8.f %s,  SMAPE %6.2f%%,  RMS %d' % ( +                name, key3, key2, np.mean(mae), unit, np.mean(smape), np.mean(rmsd))) +            print_estimates(estimates, 10) +            mae, smape, rmsd, estimates = val_run_fun_p(by_param, by_trace, name, key1, key2, key3, splitidx_srs, param_mc_count) +            print('%16s, %8s %10s, param-aware Monte Carlo: MAE %8.f %s,  SMAPE %6.2f%%,  RMS %d' % ( +                name, key3, key2, np.mean(mae), unit, np.mean(smape), np.mean(rmsd))) +            print_estimates(estimates, param_mc_count) +            mae, smape, rmsd, estimates = val_run_fun_p(by_param, by_trace, name, key1, key2, key3, splitidx_kfold, 10) +            print('%16s, %8s %10s, param-aware 10-fold sys: MAE %8.f %s,  SMAPE %6.2f%%,  RMS %d' % ( +                name, key3, key2, np.mean(mae), unit, np.mean(smape), np.mean(rmsd))) +            print_estimates(estimates, 10) + +        if 'power' in model[isa][name] and 'function' in model[isa][name]['power']: +            if 'user' in model[isa][name]['power']['function']: +                val_run_funs(by_name, by_trace, name, 'means', 'power', 'user', 'µW') +            if 'estimate' in model[isa][name]['power']['function']: +                val_run_funs(by_name, by_trace, name, 'means', 'power', 'estimate', 'µW') +        if 'timeout' in model[isa][name] and 'function' in model[isa][name]['timeout']: +            if 'user' in model[isa][name]['timeout']['function']: +                val_run_funs(by_name, by_trace, name, 'timeouts', 'timeout', 'user', 'µs') +            if 'estimate' in model[isa][name]['timeout']['function']: +                val_run_funs(by_name, by_trace, name, 'timeouts', 'timeout', 'estimate', 'µs') +        if 'duration' in model[isa][name] and 'function' in model[isa][name]['duration']: +            if 'user' in model[isa][name]['duration']['function']: +                val_run_funs(by_name, by_trace, name, 'durations', 'duration', 'user', 'µs') +            if 'estimate' in model[isa][name]['duration']['function']: +                val_run_funs(by_name, by_trace, name, 'durations', 'duration', 'estimate', 'µs') +        if 'energy' in model[isa][name] and 'function' in model[isa][name]['energy']: +            if 'user' in model[isa][name]['energy']['function']: +                val_run_funs(by_name, by_trace, name, 'energies', 'energy', 'user', 'pJ') +            if 'estimate' in model[isa][name]['energy']['function']: +                val_run_funs(by_name, by_trace, name, 'energies', 'energy', 'estimate', 'pJ') +        if 'rel_energy' in model[isa][name] and 'function' in model[isa][name]['rel_energy']: +            if 'user' in model[isa][name]['rel_energy']['function']: +                val_run_funs(by_name, by_trace, name, 'rel_energies', 'rel_energy', 'user', 'pJ') +            if 'estimate' in model[isa][name]['rel_energy']['function']: +                val_run_funs(by_name, by_trace, name, 'rel_energies', 'rel_energy', 'estimate', 'pJ') + +    return +    for i, param in enumerate(parameters): +        user_mae = {} +        user_smape = {} +        estimate_mae = {} +        estimate_smape = {} +        for val in paramv[param]: +            bpa_training = dict([[x, by_param[x]] for x in by_param if x[1][i] != val]) +            bpa_validation = dict([[x, by_param[x]] for x in by_param if x[1][i] == val]) +            to_pop = [] +            for name in by_name.keys(): +                if not any(map(lambda x : x[0] == name, bpa_training.keys())): +                    to_pop.append(name) +            for name in to_pop: +                by_name.pop(name, None) +            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']: +                    if 'user' in state['power']['function']: +                        xv_assess_function(name, state['power']['function']['user'], 'means', bpa_validation, user_mae, user_smape) +                    if 'estimate' in state['power']['function']: +                        xv_assess_function(name, state['power']['function']['estimate'], 'means', bpa_validation, estimate_mae, estimate_smape) +            for name in sorted(ares['transition'].keys()): +                trans = ares['transition'][name] +                if 'timeout' in trans and 'function' in trans['timeout']: +                    if 'user' in trans['timeout']['function']: +                        xv_assess_function(name, trans['timeout']['function']['user'], 'timeouts', bpa_validation, user_mae, user_smape) +                    if 'estimate' in trans['timeout']['function']: +                        xv_assess_function(name, trans['timeout']['function']['estimate'], 'timeouts', bpa_validation, estimate_mae, estimate_smape) + +        for name in sorted(user_mae.keys()): +            if by_name[name]['isa'] == 'state': +                print('user function %s power by %s: MAE %.f µW,  SMAPE %.2f%%' % ( +                    name, param, np.mean(user_mae[name]), np.mean(user_smape[name]))) +            else: +                print('user function %s timeout by %s: MAE %.f µs,  SMAPE %.2f%%' % ( +                    name, param, np.mean(user_mae[name]), np.mean(user_smape[name]))) +        for name in sorted(estimate_mae.keys()): +            if by_name[name]['isa'] == 'state': +                print('estimate function %s power by %s: MAE %.f µW,  SMAPE %.2f%%' % ( +                    name, param, np.mean(estimate_mae[name]), np.mean(estimate_smape[name]))) +            else: +                print('estimate function %s timeout by %s: MAE %.f µs,  SMAPE %.2f%%' % ( +                    name, param, np.mean(estimate_mae[name]), np.mean(estimate_smape[name]))) + +def validate(by_name, by_param, parameters): +    aggdata = { +        'state' : {}, +        'transition' : {}, +    } +    for key, val in by_name.items(): +        name = key +        isa = val['isa'] +        model = data['model'][isa][name] + +        if isa == 'state': +            aggdata[isa][name] = { +                'power' : { +                    'goodness' : aggregate_measures(model['power']['static'], val['means']), +                    'median' : np.median(val['means']), +                    'mean'   : np.mean(val['means']), +                    'std_inner' : np.std(val['means']), +                    'function' : {}, +                }, +                'online_power' : { +                    'goodness' : regression_measures(np.array(val['online_means']), np.array(val['means'])), +                    'median' : np.median(val['online_means']), +                    'mean'   : np.mean(val['online_means']), +                    'std_inner' : np.std(val['online_means']), +                    'function' : {}, +                }, +                'online_duration' : { +                    'goodness' : regression_measures(np.array(val['online_durations']), np.array(val['durations'])), +                    'median' : np.median(val['online_durations']), +                    'mean'   : np.mean(val['online_durations']), +                    'std_inner' : np.std(val['online_durations']), +                    'function' : {}, +                }, +                'clip' : { +                    'mean' : np.mean(val['clip_rate']), +                    'max'  : max(val['clip_rate']), +                }, +                'timeout' : {}, +            } +            if 'function' in model['power']: +                aggdata[isa][name]['power']['function'] = { +                    'estimate' : { +                        'fit' : assess_function(model['power']['function']['estimate'], +                            name, 'means', parameters, by_param), +                    }, +                    'user': { +                        'fit' : assess_function(model['power']['function']['user'], +                            name, 'means', parameters, by_param), +                    }, +                } +        if isa == 'transition': +            aggdata[isa][name] = { +                'duration' : { +                    'goodness' : aggregate_measures(model['duration']['static'], val['durations']), +                    'median' : np.median(val['durations']), +                    'mean'   : np.mean(val['durations']), +                    'std_inner' : np.std(val['durations']), +                    'function' : {}, +                }, +                'energy' : { +                    'goodness' : aggregate_measures(model['energy']['static'], val['energies']), +                    'median' : np.median(val['energies']), +                    'mean'   : np.mean(val['energies']), +                    'std_inner' : np.std(val['energies']), +                    'function' : {}, +                }, +                'rel_energy' : { +                    'goodness' : aggregate_measures(model['rel_energy']['static'], val['rel_energies']), +                    'median' : np.median(val['rel_energies']), +                    'mean'   : np.mean(val['rel_energies']), +                    'std_inner' : np.std(val['rel_energies']), +                    'function' : {}, +                }, +                'clip' : { +                    'mean' : np.mean(val['clip_rate']), +                    'max'  : max(val['clip_rate']), +                }, +                'timeout' : {}, +            } +            if 'function' in model['timeout']: +                aggdata[isa][name]['timeout'] = { +                    'median' : np.median(val['timeouts']), +                    'mean' : np.mean(val['timeouts']), +                    'function': { +                        'estimate' : { +                            'fit' : assess_function(model['timeout']['function']['estimate'], +                                name, 'timeouts', parameters, by_param), +                        }, +                        'user': { +                            'fit' : assess_function(model['timeout']['function']['user'], +                                name, 'timeouts', parameters, by_param), +                        }, +                    }, +                } +    return aggdata + +def analyze(by_name, by_param, by_trace, parameters): +    aggdata = { +        'state' : {}, +        'transition' : {}, +    } +    transition_names = list(map(lambda x: x[0], filter(lambda x: x[1]['isa'] == 'transition', by_name.items()))) +    for name, val in by_name.items(): +        isa = val['isa'] +        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'), +            'clip' : { +                'mean' : np.mean(val['clip_rate']), +                'max'  : max(val['clip_rate']), +            }, +            'timeout' : {}, +        } + +        aggval = aggdata[isa][name] +        aggval['power']['std_outer'] = np.mean(val['stds']) + +        if isa == 'transition': +            aggval['rel_energy'] = keydata(name, val, by_param, by_trace, 'rel_energies') + +        if isa == 'transition' and 'function' in data['model']['transition'][name]['timeout']: +            aggval['timeout'] = keydata(name, val, 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] != ''])) +            allvalues = [(*key[1][:i], *key[1][i+1:]) for key in by_param.keys() if key[0] == name] +            #allvalues = list(set(allvalues)) +            if len(values) > 1: +                if isa == 'state': +                    aggval['power']['std_by_param'][param] = mean_std_by_param( +                        by_param, allvalues, name, 'means', i) +                    if aggval['power']['std_by_param'][param] > 0 and aggval['power']['std_param'] / aggval['power']['std_by_param'][param] < 0.6: +                        aggval['power']['fit_guess'][param] = try_fits(name, 'means', i, by_param) +                else: +                    aggval['duration']['std_by_param'][param] = mean_std_by_param( +                        by_param, allvalues, name, 'durations', i) +                    if aggval['duration']['std_by_param'][param] > 0 and aggval['duration']['std_param'] / aggval['duration']['std_by_param'][param] < 0.6: +                        aggval['duration']['fit_guess'][param] = try_fits(name, 'durations', i, by_param) +                    aggval['energy']['std_by_param'][param] = mean_std_by_param( +                        by_param, allvalues, name, 'energies', i) +                    if aggval['energy']['std_by_param'][param] > 0 and aggval['energy']['std_param'] / aggval['energy']['std_by_param'][param] < 0.6: +                        aggval['energy']['fit_guess'][param] = try_fits(name, 'energies', i, by_param) +                    aggval['rel_energy']['std_by_param'][param] = mean_std_by_param( +                        by_param, allvalues, name, 'rel_energies', i) +                    if aggval['rel_energy']['std_by_param'][param] > 0 and aggval['rel_energy']['std_param'] / aggval['rel_energy']['std_by_param'][param] < 0.6: +                        aggval['rel_energy']['fit_guess'][param] = try_fits(name, 'rel_energies', i, by_param) +                if isa == 'transition' and 'function' in data['model']['transition'][name]['timeout']: +                    aggval['timeout']['std_by_param'][param] = mean_std_by_param( +                        by_param, allvalues, name, 'timeouts', i) +                    if aggval['timeout']['std_by_param'][param] > 0 and aggval['timeout']['std_param'] / aggval['timeout']['std_by_param'][param] < 0.6: +                        aggval['timeout']['fit_guess'][param] = try_fits(name, 'timeouts', i, by_param) + +        if isa == 'state': +            fguess_to_function(name, 'means', aggval['power'], parameters, by_param, +                'estimated %s power [µW]' % name) +            if 'function' in model['power'] and 'user' in model['power']['function']: +                aggval['power']['function']['user'] = { +                    'raw' : model['power']['function']['user']['raw'], +                    'params' : model['power']['function']['user']['params'], +                } +                fit_function( +                    aggval['power']['function']['user'], name, 'means', parameters, by_param, +                    yaxis='%s power [µW]' % name) +            if aggval['power']['std_param'] > 0 and aggval['power']['std_trace'] / aggval['power']['std_param'] < 0.5: +                aggval['power']['std_by_trace'] = mean_std_by_trace_part(by_trace, transition_names, name, 'means') +        else: +            fguess_to_function(name, 'durations', aggval['duration'], parameters, by_param, +                'estimated %s duration [µs]' % name) +            fguess_to_function(name, 'energies', aggval['energy'], parameters, by_param, +                'estimated %s energy [pJ]' % name) +            fguess_to_function(name, 'rel_energies', aggval['rel_energy'], parameters, by_param, +                'estimated relative %s energy [pJ]' % name) +            if 'function' in model['duration'] and 'user' in model['duration']['function']: +                aggval['duration']['function']['user'] = { +                    'raw' : model['duration']['function']['user']['raw'], +                    'params' : model['duration']['function']['user']['params'], +                } +                fit_function( +                    aggval['duration']['function']['user'], name, 'durations', parameters, by_param, +                    yaxis='%s duration [µs]' % name) +            if 'function' in model['energy'] and 'user' in model['energy']['function']: +                aggval['energy']['function']['user'] = { +                    'raw' : model['energy']['function']['user']['raw'], +                    'params' : model['energy']['function']['user']['params'], +                } +                fit_function( +                    aggval['energy']['function']['user'], name, 'energies', parameters, by_param, +                    yaxis='%s energy [pJ]' % name) +            if 'function' in model['rel_energy'] and 'user' in model['rel_energy']['function']: +                aggval['rel_energy']['function']['user'] = { +                    'raw' : model['rel_energy']['function']['user']['raw'], +                    'params' : model['rel_energy']['function']['user']['params'], +                } +                fit_function( +                    aggval['rel_energy']['function']['user'], name, 'rel_energies', parameters, by_param, +                    yaxis='%s rel_energy [pJ]' % name) +            if 'function' in model['timeout'] and 'user' in model['timeout']['function']: +                fguess_to_function(name, 'timeouts', aggval['timeout'], parameters, by_param, +                    'estimated %s timeout [µs]' % name) +                aggval['timeout']['function']['user'] = { +                    'raw' : model['timeout']['function']['user']['raw'], +                    'params' : model['timeout']['function']['user']['params'], +                } +                fit_function( +                    aggval['timeout']['function']['user'], name, 'timeouts', parameters, +                    by_param, yaxis='%s timeout [µs]' % name) +                if aggval['timeout']['std_param'] > 0 and aggval['timeout']['std_trace'] / aggval['timeout']['std_param'] < 0.5: +                    aggval['timeout']['std_by_trace'] = mean_std_by_trace_part(by_trace, transition_names, name, 'timeouts') + +    return aggdata + +try: +    raw_opts, args = getopt.getopt(sys.argv[1:], "", [ +        "fit", "states", "transitions", "params", "clipping", "timing", +        "histogram", "substates", "validate", "crossvalidate", "ignore-trace-idx="]) +    for option, parameter in raw_opts: +        optname = re.sub(r'^--', '', option) +        opts[optname] = parameter +    if 'ignore-trace-idx' in opts: +        opts['ignore-trace-idx'] = int(opts['ignore-trace-idx']) +except getopt.GetoptError as err: +    print(err) +    sys.exit(2) + +data = load_json(args[0]) +by_name = {} +by_param = {} +by_trace = {} +parameters = sorted(data['model']['parameter'].keys()) + +for arg in args: +    mdata = load_json(arg) +    for runidx, run in enumerate(mdata['traces']): +        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) + +if 'states' in opts: +    if 'params' in opts: +        plotter.plot_states_param(data['model'], by_param) +    else: +        plotter.plot_states(data['model'], by_name) +    if 'timing' in opts: +        plotter.plot_states_duration(data['model'], by_name) +        plotter.plot_states_duration(data['model'], by_param) +    if 'clipping' in opts: +        plotter.plot_states_clips(data['model'], by_name) +if 'transitions' in opts: +    plotter.plot_transitions(data['model'], by_name) +    if 'timing' in opts: +        plotter.plot_transitions_duration(data['model'], by_name) +        plotter.plot_transitions_timeout(data['model'], by_param) +    if 'clipping' in opts: +        plotter.plot_transitions_clips(data['model'], by_name) +if 'histogram' in opts: +    for key in sorted(by_name.keys()): +        plotter.plot_histogram(by_name[key]['means']) +if 'substates' in opts: +    if 'params' in opts: +        plotter.plot_substate_thresholds_p(data['model'], by_param) +    else: +        plotter.plot_substate_thresholds(data['model'], by_name) + +if 'validate' in opts: +    data['aggregate'] = validate(by_name, by_param, parameters) +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) + +# 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!) + +save_json(data, args[0]) diff --git a/bin/mim-vs-keysight.py b/bin/mim-vs-keysight.py new file mode 100755 index 0000000..2cfa976 --- /dev/null +++ b/bin/mim-vs-keysight.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 + +import csv +import numpy as np +import os +import struct +import sys +import tarfile +import matplotlib.pyplot as plt +from dfatool import running_mean, MIMOSA, Keysight + +voltage = float(sys.argv[1]) +shunt = float(sys.argv[2]) + +mimfile = "../data/20161114_arb_%d.mim" % shunt +csvfile = "../data/20161114_%d_arb.csv" % shunt + +mim = MIMOSA(voltage, shunt) +ks = Keysight() + +charges, triggers = mim.load_data(mimfile) +timestamps, currents = ks.load_data(csvfile) + +def calfunc330(charge): +    if charge < 140.210488888889: +        return 0 +    if charge <= 526.507377777778: +        return float(charge) * 0.0941215500652876 + -13.196828549634 +    else: +        return float(charge) * 0.0897304193584184 + -47.2437278033012 + 36.358862 + +def calfunc82(charge): +    if charge < 126.993600: +        return 0 +    if charge <= 245.464889: +        return charge * 0.306900 + -38.974361 +    else: +        return charge * 0.356383 + -87.479495 + 36.358862 + + +def calfunc33(charge): +    if charge < 127.000000: +        return 0 +    if charge <= 127.211911: +        return charge * 171.576006 + -21790.152700 +    else: +        return charge * 0.884357 + -112.500777 + 36.358862 + +calfuncs = { +    33 : calfunc33, +    82 : calfunc82, +    330 : calfunc330, +} + +vcalfunc = np.vectorize(calfuncs[int(shunt)], otypes=[np.float64]) + +#plt.plot(np.arange(0, 1000, 0.01), vcalfunc(np.arange(0, 1000, 0.01))) +#plt.xlabel('Rohdatenwert') +#plt.ylabel('Strom [µA]') +#plt.show() +#sys.exit(0) + +mim_x = np.arange(len(charges)-199) * 1e-5 +mim_y = running_mean(mim.charge_to_current_nocal(charges), 200) * 1e-6 +cal_y = running_mean(vcalfunc(charges), 200) * 1e-6 +ks_x = timestamps[:len(timestamps)-9] +ks_y = running_mean(currents, 10) + +# look for synchronization opportunity in first 5 seconds +mim_sync_idx = 0 +ks_sync_idx = 0 +for i in range(0, 500000): +    if mim_sync_idx == 0 and mim_y[i] > 0.001: +        mim_sync_idx = i +    if ks_sync_idx == 0 and ks_y[i] > 0.001: +        ks_sync_idx = i + +mim_x = mim_x - mim_x[mim_sync_idx] +ks_x = ks_x - ks_x[ks_sync_idx] + +mim_max_start = int(len(mim_y) * 0.4) +mim_max_end = int(len(mim_y) * 0.6) +mim_start_end = int(len(mim_y) * 0.1) +mim_end_start = int(len(mim_y) * 0.9) +mim_max = np.max(mim_y[mim_max_start:mim_max_end]) +mim_min1 = np.min(mim_y[:mim_start_end]) +mim_min2 = np.min(mim_y[mim_end_start:]) +mim_center = 0 +mim_start = 0 +mim_end = 0 +for i, y in enumerate(mim_y): +    if y == mim_max and i / len(mim_y) > 0.4 and i / len(mim_y) < 0.6: +        mim_center = i +    elif y == mim_min1 and i / len(mim_y) < 0.1: +        mim_start = i +    elif y == mim_min2 and i / len(mim_y) > 0.9: +        mim_end = i + +plt.plot([mim_x[mim_center]], [mim_y[mim_center]], "yo") +plt.plot([mim_x[mim_start]], [mim_y[mim_start]], "yo") +plt.plot([mim_x[mim_end]], [mim_y[mim_end]], "yo") +# +mimhandle, = plt.plot(mim_x, mim_y, "r-", label='MIMOSA') +#calhandle, = plt.plot(mim_x, cal_y, "g-", label='MIMOSA (autocal)') +kshandle, = plt.plot(ks_x, ks_y, "b-", label='Keysight') +#plt.legend(handles=[mimhandle, calhandle, kshandle]) +plt.xlabel('Zeit [s]') +plt.ylabel('Strom [A]') +plt.grid(True) + +ks_steps_up = [] +ks_steps_down = [] +mim_steps_up = [] +mim_steps_down = [] + +skip = 0 +for i, gradient in enumerate(np.gradient(ks_y, 10000)): +    if gradient > 0.5e-9 and i - skip > 200 and ks_x[i] < mim_x[mim_center] and ks_x[i] > 5: +        plt.plot([ks_x[i]], [ks_y[i]], "go") +        ks_steps_up.append(i) +        skip = i +    elif gradient < -0.5e-9 and i - skip > 200 and ks_x[i] > mim_x[mim_center] and ks_x[i] < mim_x[mim_end]: +        plt.plot([ks_x[i]], [ks_y[i]], "g*") +        ks_steps_down.append(i) +        skip = i + +j = 0 +for i, ts in enumerate(mim_x): +    if j < len(ks_steps_up) and ts > ks_x[ks_steps_up[j]]: +        mim_steps_up.append(i) +        j += 1 + +j = 0 +for i, ts in enumerate(mim_x): +    if j < len(ks_steps_down) and ts > ks_x[ks_steps_down[j]]: +        mim_steps_down.append(i) +        j += 1 + +print(ks_steps_up) +print(mim_steps_up) + +mim_values = [] +cal_values = [] +ks_values = [] + +for i in range(1, len(ks_steps_up)): +    mim_values.append(np.mean(mim_y[mim_steps_up[i-1]:mim_steps_up[i]])) +    cal_values.append(np.mean(cal_y[mim_steps_up[i-1]:mim_steps_up[i]])) +    ks_values.append(np.mean(ks_y[ks_steps_up[i-1]:ks_steps_up[i]])) +    print("step %d avg %5.3f vs %5.3f vs %5.3f mA" % +        (i, np.mean(ks_y[ks_steps_up[i-1]:ks_steps_up[i]]) * 1e3, +            np.mean(mim_y[mim_steps_up[i-1]:mim_steps_up[i]]) * 1e3, +            np.mean(cal_y[mim_steps_up[i-1]:mim_steps_up[i]]) * 1e3)) +for i in range(1, len(ks_steps_down)): +    mim_values.append(np.mean(mim_y[mim_steps_down[i-1]:mim_steps_down[i]])) +    cal_values.append(np.mean(cal_y[mim_steps_down[i-1]:mim_steps_down[i]])) +    ks_values.append(np.mean(ks_y[ks_steps_down[i-1]:ks_steps_down[i]])) +    print("step %d avg %5.3f vs %5.3f vs %5.3f mA" % +        (i, np.mean(ks_y[ks_steps_down[i-1]:ks_steps_down[i]]) * 1e3, +            np.mean(mim_y[mim_steps_down[i-1]:mim_steps_down[i]]) * 1e3, +            np.mean(cal_y[mim_steps_down[i-1]:mim_steps_down[i]]) * 1e3)) + +mim_values = np.array(mim_values) +cal_values = np.array(cal_values) +ks_values = np.array(ks_values) + +plt.show() + +plt.hist(ks_y[ks_steps_up[48]:ks_steps_up[49]] * 1e3, 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('mA Keysight') +plt.ylabel('#') +plt.grid(True) +plt.show() +plt.hist(mim_y[mim_steps_up[48]:mim_steps_up[49]] * 1e3, 100, normed=0, facecolor='blue', alpha=0.8) +plt.xlabel('mA MimosaGUI') +plt.ylabel('#') +plt.grid(True) +plt.show() + +mimhandle, = plt.plot(ks_values * 1e3, mim_values * 1e3, "ro", label='Unkalibriert', markersize=4) +calhandle, = plt.plot(ks_values * 1e3, cal_values * 1e3, "bs", label='Kalibriert', markersize=4) +plt.legend(handles=[mimhandle, calhandle]) +plt.xlabel('mA Keysight') +plt.ylabel('mA MIMOSA') +plt.grid(True) +plt.show() + +mimhandle, = plt.plot(ks_values * 1e3, (mim_values - ks_values) * 1e3, "ro", label='Unkalibriert', markersize=4) +calhandle, = plt.plot(ks_values * 1e3, (cal_values - ks_values) * 1e3, "bs", label='Kalibriert', markersize=4) +plt.legend(handles=[mimhandle, calhandle]) +plt.xlabel('Sollstrom [mA]') +plt.ylabel('Messfehler MIMOSA [mA]') +plt.grid(True) +plt.show() + +mimhandle, = plt.plot(ks_values * 1e3, (mim_values - ks_values) * 1e3, "r--", label='Unkalibriert') +calhandle, = plt.plot(ks_values * 1e3, (cal_values - ks_values) * 1e3, "b-", label='Kalibriert') +plt.legend(handles=[mimhandle, calhandle]) +plt.xlabel('Sollstrom [mA]') +plt.ylabel('Messfehler MIMOSA [mA]') +plt.grid(True) +plt.show() + +mimhandle, = plt.plot(ks_values * 1e3, (mim_values - ks_values) / ks_values * 100, "ro", label='Unkalibriert', markersize=4) +calhandle, = plt.plot(ks_values * 1e3, (cal_values - ks_values) / ks_values * 100, "bs", label='Kalibriert', markersize=4) +plt.legend(handles=[mimhandle, calhandle]) +plt.xlabel('Sollstrom [mA]') +plt.ylabel('Messfehler MIMOSA [%]') +plt.grid(True) +plt.show() + +mimhandle, = plt.plot(ks_values * 1e3, (mim_values - ks_values) / ks_values * 100, "r--", label='Unkalibriert') +calhandle, = plt.plot(ks_values * 1e3, (cal_values - ks_values) / ks_values * 100, "b-", label='Kalibriert') +plt.legend(handles=[mimhandle, calhandle]) +plt.xlabel('Sollstrom [mA]') +plt.ylabel('Messfehler MIMOSA [%]') +plt.grid(True) +plt.show() + +#mimhandle, = plt.plot(mim_x, np.gradient(mim_y, 10000), "r-", label='MIMOSA') +#kshandle, = plt.plot(ks_x, np.gradient(ks_y, 10000), "b-", label='Keysight') +#plt.legend(handles=[mimhandle, kshandle]) +#plt.xlabel('Zeit [s]') +#plt.ylabel('Strom [A]') +#plt.show() diff --git a/bin/mimosawatch b/bin/mimosawatch new file mode 100755 index 0000000..88d34a3 --- /dev/null +++ b/bin/mimosawatch @@ -0,0 +1,24 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use 5.020; + +use File::Slurp qw(slurp); +use IO::Handle; + +our $VERSION = '0.00'; + +STDOUT->autoflush(1); + +while (sleep(1)) { +	if ( -e '/tmp/mimosa/mimosa_scale_100000.tmp' ) { +		my $raw_data = slurp('/tmp/mimosa/mimosa_scale_100000.tmp'); +		my @data = map { $_ >> 8 } unpack('L*', $raw_data); +		my @buzzer = map { $_ & 0x0f } unpack('L*', $raw_data); + +		if (@data > 8) { +			printf("\r\e[2K" . ('%8d ' x 8), @data[-8 .. -1]); +		} +	} +} | 
