diff options
Diffstat (limited to 'lib/MIMOSA/Log.pm')
-rw-r--r-- | lib/MIMOSA/Log.pm | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/lib/MIMOSA/Log.pm b/lib/MIMOSA/Log.pm new file mode 100644 index 0000000..4f7c6a2 --- /dev/null +++ b/lib/MIMOSA/Log.pm @@ -0,0 +1,388 @@ +package MIMOSA::Log; + +use strict; +use warnings; +use 5.020; + +use Archive::Tar; +use Carp; +use File::Slurp qw(read_file read_dir write_file); +use JSON; +use List::Util qw(sum); + +#use Statistics::Basic::Mean; +#use Statistics::Basic::StdDev; + +our $VERSION = '0.00'; +my $CACHE_VERSION = 5; + +sub new { + my ( $class, %opt ) = @_; + + my $self = \%opt; + + $self->{tmpdir} = "/tmp/kratos-dfa-mim-$$"; + + if ( $opt{tmpsuffix} ) { + $self->{tmpdir} .= "-$opt{tmpsuffix}"; + } + + bless( $self, $class ); + + return $self; +} + +sub tar { + my ($self) = @_; + + $self->{tar} //= Archive::Tar->new( $self->{data_file} ); + + return $self->{tar}; +} + +sub setup { + my ($self) = @_; + + return $self->{setup}; +} + +sub load { + return new(@_); +} + +sub DESTROY { + my ($self) = @_; + + if ( -d $self->{tmpdir} ) { + for my $file ( read_dir( $self->{tmpdir} ) ) { + unlink("$self->{tmpdir}/$file"); + } + rmdir( $self->{tmpdir} ); + } +} + +sub load_archive { + my ($self) = @_; + + my $tmpdir = $self->{tmpdir}; + + my @filelist = $self->tar->list_files; + my @mim_files = sort grep { m{ \. mim $ }x } @filelist; + my @json_files = map { ( split( qr{[.]}, $_ ) )[0] . '.json' } @mim_files; + + if ( $self->{fast_analysis} ) { + splice( @mim_files, 4 ); + splice( @json_files, 4 ); + } + + $self->{filelist} = [@filelist]; + $self->{mim_files} = [@mim_files]; + $self->{mim_results} = [@json_files]; + + $self->{log}{traces} = JSON->new->decode( + $self->tar->get_content('src/apps/DriverEval/DriverLog.json') ); + $self->{setup} = JSON->new->decode( $self->tar->get_content('setup.json') ); + + mkdir($tmpdir); + + for my $file (@mim_files) { + $self->tar->extract_file( $file, "${tmpdir}/${file}" ); + } +} + +sub load_cache { + my ($self) = @_; + my $tmpdir = $self->{tmpdir}; + my ( $dirname, $basename ) + = ( $self->{data_file} =~ m{ ^ (.*) / ([^/]+) . tar $ }x ); + my $cachefile = "${dirname}/cache/${basename}.json"; + + if ( -e $cachefile ) { + mkdir($tmpdir); + write_file( $self->json_name, read_file($cachefile) ); + my $json = JSON->new->decode( read_file($cachefile) ); + if ( $json->{version} != $CACHE_VERSION ) { + return 0; + } + $self->{setup} = $json->{setup}; + return 1; + } + return 0; +} + +sub save_cache { + my ($self) = @_; + my $tmpdir = $self->{tmpdir}; + my ( $dirname, $basename ) + = ( $self->{data_file} =~ m{ ^ (.*) / ([^/]+) . tar $ }x ); + my $cachefile = "${dirname}/cache/${basename}.json"; + + if ( not -d "${dirname}/cache" ) { + mkdir("${dirname}/cache"); + } + + write_file( $cachefile, read_file( $self->json_name ) ); +} + +sub num_iterations { + my ($self) = @_; + + return scalar @{ $self->{mim_files} }; +} + +sub sched_trigger_count { + my ($self) = @_; + + if ( not $self->{sched_trigger_count} ) { + $self->{sched_trigger_count} = 0; + for my $run ( @{ $self->{log}{traces} } ) { + $self->{sched_trigger_count} += @{ $run->{trace} }; + } + } + + return $self->{sched_trigger_count}; +} + +sub merge { + my ( $self, $file ) = @_; + + if ( not -e $file ) { + return "Does not exist"; + } + + my $data = JSON->new->decode( read_file($file) ); + my $trig_count = $data->{triggers}; + if ( $self->sched_trigger_count != $trig_count ) { + return sprintf( 'Expected %d trigger edges, got %d', + $self->sched_trigger_count, $trig_count ); + } + + #printf("calibration check at: %.f±%.f %.f±%.f %.f±%.f\n", + # $data->{calibration}{r0_mean}, + # $data->{calibration}{r0_std}, + # $data->{calibration}{r2_mean}, + # $data->{calibration}{r2_std}, + # $data->{calibration}{r1_mean}, + # $data->{calibration}{r1_std}, + #); + + # verify that state duration really is < 1.5 * setup{state_duration} and > + # 0.5 * setup{state_duration}. otherwise we may have missed a trigger, + # which wasn't detected earlier because of duplicate triggers elsewhere. + my $data_idx = 0; + for my $run ( @{ $self->{log}{traces} } ) { + my $prev_elem = {name => q{}}; + for my $trace_elem ( @{ $run->{trace} } ) { + my $log_elem = $data->{trace}[$data_idx]; + if ($log_elem->{isa} eq 'state' + and $trace_elem->{name} ne 'UNINITIALIZED' + and $log_elem->{us} > $self->{setup}{state_duration} * 1500 + and $prev_elem->{name} ne 'txDone' + and $prev_elem->{name} ne 'epilogue') { + return sprintf('State %s (trigger index %d) took %.1f ms longer than expected', + $trace_elem->{name}, $data_idx, + ($log_elem->{us} / 1000) - $self->{setup}{state_duration} + ); + } + if ($log_elem->{isa} eq 'state' + and $trace_elem->{name} ne 'UNINITIALIZED' + and $trace_elem->{name} ne 'TX' + and $log_elem->{us} < $self->{setup}{state_duration} * 500 ) { + return sprintf('State %s (trigger index %d) was %.1f ms shorter than expected', + $trace_elem->{name}, $data_idx, + $self->{setup}{state_duration} - ($log_elem->{us} / 1000) + ); + } + $prev_elem = $trace_elem; + $data_idx++; + } + } + + $data_idx = 0; + for my $run ( @{ $self->{log}{traces} } ) { + for my $trace_elem ( @{ $run->{trace} } ) { + if ( $data->{trace}[$data_idx]{isa} ne $trace_elem->{isa} ) { + croak(); + } + delete $data->{trace}[$data_idx]{isa}; + push( @{ $trace_elem->{offline} }, $data->{trace}[$data_idx] ); + $data_idx++; + } + } + + push( @{ $self->{log}{calibration} }, $data->{calibration} ); + + return; +} + +sub preprocess { + my ($self) = @_; + my $tmpdir = $self->{tmpdir}; + my @files = @{ $self->{mim_files} }; + my $shunt = $self->{setup}{mimosa_shunt}; + my $voltage = $self->{setup}{mimosa_voltage}; + my @errmap; + + @files = map { "${tmpdir}/$_" } @files; + + if ( qx{parallel --version 2> /dev/null} =~ m{GNU parallel} ) { + system( qw(parallel ../dfatool/bin/analyze.py), + $voltage, $shunt, ':::', @files ); + } + else { + system( qw(parallel ../dfatool/bin/analyze.py), + $voltage, $shunt, '--', @files ); + } + + for my $i ( 0 .. $#{ $self->{mim_results} } ) { + my $file = $self->{mim_results}[$i]; + my $error = $self->merge("${tmpdir}/${file}"); + + if ($error) { + say "${file}: ${error}"; + push(@errmap, $i); + } + } + + if ( @errmap == @files ) { + die("All MIMOSA measurements were erroneous. Aborting.\n"); + } + + $self->{log}{model} = $self->{model}; + $self->{log}{errmap} = \@errmap; + $self->{log}{setup} = $self->{setup}; + $self->{log}{version} = $CACHE_VERSION; + write_file( $self->json_name, + JSON->new->convert_blessed->encode( $self->{log} ) ); +} + +sub analyze { + my ( $self, @extra_files ) = @_; + my $tmpdir = $self->{tmpdir}; + + @extra_files = grep { $_ ne $self->json_name } @extra_files; + + for my $file ( $self->json_name, @extra_files ) { + my $json = JSON->new->decode( read_file($file) ); + $json->{model} = $self->{model}; + +# fix for incomplete json files: transitions can also depend on global parameters + for my $run ( @{ $json->{traces} } ) { + for my $i ( 0 .. $#{ $run->{trace} } ) { + $run->{trace}[$i]{parameter} + //= $run->{trace}[ $i - 1 ]{parameter}; + } + } + + write_file( $file, JSON->new->convert_blessed->encode($json) ); + } + + system( '../dfatool/bin/merge.py', @{ $self->{merge_args} // [] }, + $self->json_name, @extra_files ); + + my $json = JSON->new->decode( read_file( $self->json_name ) ); + + $self->{aggregate} = $json->{aggregate}; + + # debug + write_file( "/tmp/DriverLog.json", JSON->new->pretty->encode($json) ); +} + +sub validate { + my ( $self, @extra_files ) = @_; + my $tmpdir = $self->{tmpdir}; + + @extra_files = grep { $_ ne $self->json_name } @extra_files; + + for my $file ( $self->json_name, @extra_files ) { + my $json = JSON->new->decode( read_file($file) ); + $json->{model} = $self->{model}; + my @errmap = @{ $json->{errmap} // [] }; + +# fix for incomplete json files: transitions can also depend on global parameters + for my $run ( @{ $json->{traces} } ) { + for my $i ( 0 .. $#{ $run->{trace} } ) { + $run->{trace}[$i]{parameter} + //= $run->{trace}[ $i - 1 ]{parameter}; + } + } + # online durations count current state + next transition, but we + # only want to analyze current state -> substract next transition. + # Note that we can only do this on online data which has + # corresponding offline data, i.e. where the offline data was not + # erroneous + for my $run ( @{ $json->{traces} } ) { + if (exists $run->{total_energy}) { + # splice changes the array (and thus the indices). so we need to + # start removing elements at the end + for my $erridx (reverse @errmap) { + splice(@{$run->{total_energy}}, $erridx, 1); + } + } + for my $i ( 0 .. $#{ $run->{trace} } ) { + for my $erridx (reverse @errmap) { + splice(@{$run->{trace}[$i]{online}}, $erridx, 1); + } + if ($run->{trace}[$i]{isa} eq 'state') { + for my $j (0 .. $#{ $run->{trace}[$i]{online} } ) { + $run->{trace}[$i]{online}[$j]{time} -= + $run->{trace}[$i+1]{offline}[$j]{us}; + } + } + } + } + + write_file( $file, JSON->new->convert_blessed->encode($json) ); + } + + system( '../dfatool/bin/merge.py', @{ $self->{merge_args} // [] }, + '--validate', $self->json_name, @extra_files ); + + my $json = JSON->new->decode( read_file( $self->json_name ) ); + + $self->{aggregate} = $json->{aggregate}; + + # debug + write_file( "/tmp/DriverLog.json", JSON->new->pretty->encode($json) ); +} + +sub crossvalidate { + my ( $self, @extra_files ) = @_; + my $tmpdir = $self->{tmpdir}; + + @extra_files = grep { $_ ne $self->json_name } @extra_files; + + for my $file ( $self->json_name, @extra_files ) { + my $json = JSON->new->decode( read_file($file) ); + $json->{model} = $self->{model}; + +# fix for incomplete json files: transitions can also depend on global parameters + for my $run ( @{ $json->{traces} } ) { + for my $i ( 0 .. $#{ $run->{trace} } ) { + $run->{trace}[$i]{parameter} + //= $run->{trace}[ $i - 1 ]{parameter}; + } + } + + write_file( $file, JSON->new->convert_blessed->encode($json) ); + } + + system( '../dfatool/bin/merge.py', @{ $self->{merge_args} // [] }, + '--crossvalidate', $self->json_name, @extra_files ); +} + +sub data { + my ($self) = @_; + my $tmpdir = $self->{tmpdir}; + my $json = JSON->new->decode( read_file( $self->json_name ) ); + return $json; +} + +sub json_name { + my ($self) = @_; + my $tmpdir = $self->{tmpdir}; + + return "${tmpdir}/DriverLog.json"; +} + +1; |