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 /lib/MIMOSA | |
initial commit
Diffstat (limited to 'lib/MIMOSA')
| -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; | 
