From 49e282ca086fe50db530656c5c9506c0a64c1f45 Mon Sep 17 00:00:00 2001 From: Mark Santaniello Date: Mon, 31 Aug 2015 15:42:44 -0700 Subject: [PATCH] Sync to latest internal. --- StorScore.cmd | 104 ++++--- bin/write_forever.cmd | 5 + bin/write_slowly.cmd | 3 - lib/CommandLine.pm | 42 ++- lib/PreconditionRunner.pm | 128 +++----- lib/Recipe.pm | 460 ++++++++++++++++++++++------ lib/Target.pm | 320 +++++++++++++++---- parse_results.cmd | 40 +++ recipes/12hr_stress.rcp | 2 +- recipes/24hr_stress.rcp | 2 +- recipes/48hr_stress.rcp | 2 +- recipes/corners.rcp | 2 +- recipes/flush_check.rcp | 5 +- recipes/rw_mix.rcp | 2 +- recipes/smart_check.rcp | 10 +- recipes/thread_bottleneck_check.rcp | 2 +- recipes/turkey_test_part1a.rcp | 2 +- recipes/turkey_test_part1b.rcp | 2 +- recipes/turkey_test_part2a.rcp | 2 +- recipes/turkey_test_part2b.rcp | 2 +- recipes/write_impact_check.rcp | 80 +++-- test/regr.cmd | 75 ++++- 22 files changed, 957 insertions(+), 335 deletions(-) create mode 100644 bin/write_forever.cmd delete mode 100644 bin/write_slowly.cmd diff --git a/StorScore.cmd b/StorScore.cmd index aba445b..e954cd0 100644 --- a/StorScore.cmd +++ b/StorScore.cmd @@ -14,8 +14,9 @@ if %ERRORLEVEL% NEQ 0 ( for /F "tokens=4" %%I IN ('powercfg -getactivescheme') DO set ORIG_SCHEME=%%I powercfg -setactive SCHEME_MIN start /B /WAIT /HIGH perl "%~f0" %* +set PERL_ERROR_LEVEL=%ERRORLEVEL% powercfg -setactive %ORIG_SCHEME% -exit /B %ERRORLEVEL% +exit /B %PERL_ERROR_LEVEL% ); # StorScore @@ -112,27 +113,9 @@ else $output_dir .= "$prefix-" . strftime( "%Y-%m-%d_%H-%M-%S", localtime ); } - + print "Targeting " . uc( $target->type ) . ": " . $target->model . "\n"; -if( $target->is_ssd ) -{ - if( $target->supports_smart and - $target->is_sata and - !$target->is_6Gbps_sata ) - { - my $msg; - - $msg .= "\n\tWarning!\n"; - $msg .= "\tSSD target is not 6Gb/s SATA III.\n"; - $msg .= "\tThroughput will be limited.\n"; - - warn $msg; - } -} - -print "\n"; - my $recipe = Recipe->new( file_name => $recipe_file, output_dir => $output_dir, @@ -152,7 +135,23 @@ $recipe->warn_expected_run_time(); detect_scep_and_warn(); -if( $target->must_clean_disk ) +if( $target->is_ssd ) +{ + if( $target->supports_smart and + $target->is_sata and + !$target->is_6Gbps_sata ) + { + my $msg; + + $msg .= "\tWarning!\n"; + $msg .= "\tSSD target is not 6Gb/s SATA III.\n"; + $msg .= "\tThroughput will be limited.\n\n"; + + warn $msg; + } +} + +if( $target->do_purge ) { my $msg; @@ -162,21 +161,47 @@ if( $target->must_clean_disk ) warn $msg; } -elsif( defined $target->file_name and - ( $cmd_line->initialize or $recipe->contains_writes ) ) +else { - unless( -w $target->file_name or $pretend ) + if( $target->is_existing_file_or_volume ) { - die "Target is not writable\n" + my $msg; + + $msg .= "\tWarning!\n"; + $msg .= "\tTarget is an existing file/volume.\n"; + $msg .= "\tCan not purge without destroying target.\n"; + $msg .= "\tPurge steps will be skipped.\n\n"; + + warn $msg; + } + + if( $target->is_ssd ) + { + my $msg; + + $msg .= "\tWarning!\n"; + $msg .= "\tCan not eliminate SSD history effect without purge.\n"; + $msg .= "\tFor best results target a physical drive and purge.\n\n"; + + warn $msg; } - my $msg; + if( defined $target->file_name and + ( $target->do_initialize or $recipe->contains_writes ) ) + { + unless( -w $target->file_name or $pretend ) + { + die "Target is not writable\n" + } - $msg .= "\tWarning!\n"; - $msg .= "\tThis will destroy "; - $msg .= $target->file_name . "\n\n"; + my $msg; - warn $msg; + $msg .= "\tWarning!\n"; + $msg .= "\tThis will destroy "; + $msg .= $target->file_name . "\n\n"; + + warn $msg; + } } exit 0 unless should_proceed(); @@ -217,13 +242,20 @@ if( $cmd_line->collect_power ) } } -# TODO: SECURE ERASE -# -# In the future when we support SECURE ERASE, the line below -# should change to be conditional, like this: -# $target->prepare() if $target->is_hdd(); +if( defined $cmd_line->purge and not $cmd_line->purge ) +{ + print "Will skip purging as requested.\n"; +} -$target->prepare(); +if( defined $cmd_line->initialize and not $cmd_line->initialize ) +{ + print "Will skip initialization as requested.\n"; +} + +if( defined $cmd_line->precondition and not $cmd_line->precondition ) +{ + print "Will skip preconditioning as requested.\n"; +} my $wmic_runner = WmicRunner->new( target => $target, diff --git a/bin/write_forever.cmd b/bin/write_forever.cmd new file mode 100644 index 0000000..60e65d2 --- /dev/null +++ b/bin/write_forever.cmd @@ -0,0 +1,5 @@ +@echo off +REM Poor man's "run forever": pass a giant duration +set DISKSPD_CMD=DiskSpd.exe -W10 -w100 -h -d2000000000 %* +echo %DISKSPD_CMD% +%DISKSPD_CMD% diff --git a/bin/write_slowly.cmd b/bin/write_slowly.cmd deleted file mode 100644 index 1a2788e..0000000 --- a/bin/write_slowly.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -REM low bandwidth (100 KB/sec) sequential 64K writes -DiskSpd.exe -W10 -w100 -b64K -h -g100 -d2000000000 %* diff --git a/lib/CommandLine.pm b/lib/CommandLine.pm index 0215c30..f2df997 100644 --- a/lib/CommandLine.pm +++ b/lib/CommandLine.pm @@ -65,13 +65,23 @@ has 'recipe' => ( writer => '_recipe' ); +# In most cases you really want $target->do_purge +has 'purge' => ( + is => 'ro', + isa => 'Maybe[Bool]', + default => undef, + writer => '_purge' +); + +# In most cases you really want $target->do_initialize has 'initialize' => ( is => 'ro', - isa => 'Bool', - default => 1, + isa => 'Maybe[Bool]', + default => undef, writer => '_initialize' ); +# In most cases you really want $target->do_precondition has 'precondition' => ( is => 'ro', isa => 'Maybe[Bool]', @@ -234,7 +244,12 @@ sub attr my $name = shift; my $val = shift; - my $retval = eval( qq(\$self->_$name( '$val' );) ); + # The command line argument may contain backslashes, + # for example, in --target. We must escape them, + # before we eval, by converting "\" to "\\" here. + $val =~ s|\\|\\\\|g; + + my $retval = eval( qq(\$self->_$name( q($val) );) ); die $EVAL_ERROR unless defined $retval; } @@ -253,6 +268,7 @@ sub BUILD "recipe=s" => sub { $self->attr(@_) }, "verbose!" => \$verbose, "pretend!" => \$pretend, + "purge!" => sub { $self->attr(@_) }, "initialize!" => sub { $self->attr(@_) }, "precondition!" => sub { $self->attr(@_) }, "prompt!" => \$prompt, @@ -408,16 +424,18 @@ USAGE $script_name [options] OPTIONS - --target Indicates the drive or file to test (required) - --initialize Write the whole drive before testing. Defaults to true. - --precondition Performs workload-dependent preconditioning. Defaults to true. - --recipe=A.rcp Use the test list defined in "A.rcp" - --collect_smart Collect drive's SMART metadata. Defaults to true. - --collect_logman Collect performance counters from logman. Defaults to true. - --collect_power Collect system power usage statistics. Defaults to true. + --target Indicates the drive or file to test (required). + --purge Erase target before test. Default off for existing volumes. + --initialize Write whole target before testing. Defaults on for SSD. + --precondition Drive to steady-state before test. Defaults on for SSD. + --recipe=A.rcp Use the test list defined in "A.rcp". + --collect_smart Collect drive's SMART metadata. Defaults on. + --collect_logman Collect performance counters from logman. Defaults on. + --collect_power Collect system power usage statistics. Defaults on. --keep_logman_raw Prevent StorScore from deleting the logman data files. - --target_type Force target type to "ssd" or "hdd". Defaults to "auto". - --start_on_step=n Begin testing on step n of the recipe. + --target_type Force target type to "ssd" or "hdd". Defaults on. + --start_on_step=n Start testing on step n of the recipe. + --stop_on_step=n Stop testing on step n of the recipe. --pretend For testing, run without touching the target at all. EXAMPLES diff --git a/lib/PreconditionRunner.pm b/lib/PreconditionRunner.pm index 5642695..cc51c88 100644 --- a/lib/PreconditionRunner.pm +++ b/lib/PreconditionRunner.pm @@ -31,39 +31,16 @@ use warnings; use Moose; use Util; -has 'raw_disk' => ( - is => 'ro', - isa => 'Bool', +has 'target' => ( + is => 'ro', + isa => 'Target', required => 1 ); -has 'pdnum' => ( - is => 'ro', - isa => 'Str' -); - -has 'volume' => ( +has 'cmd_line' => ( is => 'ro', - isa => 'Maybe[Str]', - default => undef -); - -has 'target_file' => ( - is => 'ro', - isa => 'Maybe[Str]', - default => undef -); - -has 'demo_mode' => ( - is => 'ro', - isa => 'Maybe[Bool]', - default => 0 -); - -has 'is_target_ssd' => ( - is => 'ro', - isa => 'Bool', - default => undef + isa => 'CommandLine', + required => 1 ); use constant NORMAL_GATHER_SECONDS => 540; @@ -78,59 +55,37 @@ use constant QUICK_TEST_MIN_RUN_SECONDS => use constant QUICK_TEST_SLOPE_TOLERANCE => 0.5; -sub initialize() +# Future work: take an arbitrary workload here +# but override the write-percentage to 100% +sub write_num_passes { my $self = shift; - my $target = $self->raw_disk ? $self->pdnum : $self->target_file; - - my $num_passes = 1; + my %args = @_; + + my $msg_prefix = $args{'msg_prefix'}; + my $num_passes = $args{'num_passes'}; - if( $self->is_target_ssd and not $self->demo_mode ) - { - if ( $self->raw_disk ) - { - $num_passes = 2; - } - else - { - my $file_size; - - if( $pretend ) - { - # Ficticious 42GB test file for pretend mode - $file_size = 42 * BYTES_PER_GB_BASE2; - } - else - { - $file_size = -s $self->target_file; - } - - $file_size > 0 or die "Target file has zero size?"; - - my $vol_size = get_volume_size( $self->volume ); - - # We want to dirty all of the NAND, including the OP - # to avoid measuring the fresh-out-of-the-box condition. - # - # Writing the drive 2x is overkill, but we do it only once. - # - # Note that in cases where the file is much smaller than - # the drive, we will need to write the file many times in - # order to write the drive once. - $num_passes = int( 2 * ( $vol_size / $file_size ) ); - } - } - # Fake progress message for pretend mode - print "Initializing target: 100% [xx.x MB/s]\n" if $pretend; + if( $pretend ) + { + print $msg_prefix . "100% [xx.x MB/s]\n"; + } my $cmd = "precondition.exe "; $cmd .= "-n$num_passes "; - $cmd .= q(-p"Initializing target: " ); + $cmd .= qq(-p"$msg_prefix" ); $cmd .= "-Y "; - $cmd .= $target; + + if( $self->cmd_line->raw_disk ) + { + $cmd .= $self->target->physical_drive; + } + else + { + $cmd .= $self->target->file_name; + } my $failed = execute_task( $cmd ); @@ -147,12 +102,11 @@ sub run_to_steady_state($) my $output_dir = $args{'output_dir'}; my $test_ref = $args{'test_ref'}; - my $write_percentage = $test_ref->{'write_percentage'}; - my $access_pattern = $test_ref->{'access_pattern'}; - my $block_size = $test_ref->{'block_size'}; - my $queue_depth = $test_ref->{'queue_depth'}; - - my $target = $self->raw_disk ? $self->pdnum : $self->target_file; + my $write_percentage = $test_ref->{'write_percentage'} // die; + my $access_pattern = $test_ref->{'access_pattern'} // die; + my $block_size = $test_ref->{'block_size'} // die; + my $queue_depth = $test_ref->{'queue_depth'} // die; + my $name_string = $test_ref->{'name_string'} // die; my $block_size_kB = human_to_kilobytes( $block_size ); @@ -165,7 +119,7 @@ sub run_to_steady_state($) $cmd .= "-w$write_percentage "; $cmd .= "-ss "; - if( $self->demo_mode ) + if( $self->cmd_line->demo_mode ) { $cmd .= "-g" . QUICK_TEST_GATHER_SECONDS . " "; $cmd .= "-d" . QUICK_TEST_DWELL_SECONDS . " "; @@ -174,10 +128,17 @@ sub run_to_steady_state($) $cmd .= qq(-p"$msg_prefix" ); - $cmd .= $target; + if( $self->cmd_line->raw_disk ) + { + $cmd .= $self->target->physical_drive; + } + else + { + $cmd .= $self->target->file_name; + } my $out_file = - "$output_dir\\precondition-$test_ref->{'name_string'}.txt"; + "$output_dir\\precondition-$name_string.txt"; open( my $OUT, ">$out_file" ) or die "could not open $out_file: $!"; @@ -186,7 +147,10 @@ sub run_to_steady_state($) print $OUT "$cmd\n"; # Fake progress message for pretend mode - print "Preconditioning, achieved steady-state after 0 sec\n" if $pretend; + if( $pretend ) + { + print $msg_prefix . "achieved steady-state after 0 sec\n"; + } # Don't capture stderr so progress message goes to console my ( $errorlevel, $stdout ) = diff --git a/lib/Recipe.pm b/lib/Recipe.pm index a389441..871a85a 100644 --- a/lib/Recipe.pm +++ b/lib/Recipe.pm @@ -33,8 +33,12 @@ use English; use feature 'state'; use POSIX 'strftime'; use Symbol 'delete_package'; +use Text::Balanced 'extract_bracketed'; use Cwd; use Util; +use PreconditionRunner; +use DiskSpdParser; +use SqlioParser; no if $PERL_VERSION >= 5.017011, warnings => 'experimental::smartmatch'; @@ -69,13 +73,6 @@ has 'recipe_string' => ( writer => '_recipe_string' ); -has 'do_precondition' => ( - is => 'ro', - isa => 'Bool', - default => 1, - writer => '_do_precondition' -); - has 'steps' => ( is => 'ro', isa => 'ArrayRef', @@ -120,7 +117,7 @@ has 'current_step' => ( }, ); -has 'bg_pids' => ( +has 'bg_processes' => ( is => 'ro', isa => 'ArrayRef', default => sub { [] }, @@ -193,16 +190,17 @@ sub canonicalize_step my $step_ref = shift; my $kind = $step_ref->{'kind'}; - - return unless $kind eq 'test'; + my $number = $step_ref->{'number'}; + + return unless $kind ~~ [qw( test precondition )]; # create read_percentage from write_percentage $step_ref->{'read_percentage'} = 100 - $step_ref->{'write_percentage'}; - # ensure name_string can be used as a filename + # ensure name_string can be used as a unique filename $step_ref->{'name_string'} = - make_legal_filename( $step_ref->{'name_string'} ); + make_legal_filename( $step_ref->{'name_string'} . "-step-$number" ); } sub apply_overrides @@ -229,21 +227,24 @@ sub apply_overrides sub make_step { my $kind = shift; + my $number = shift; my @args = @_; - - # test uses named-parameter idiom - return ( kind => $kind, @args ) if $kind eq 'test'; + + my %step_args; + + # Every step records its kind and number + $step_args{'kind'} = $kind; + $step_args{'number'} = $number; + + # These kinds use the named-parameter idiom + %step_args = ( %step_args, @args ) + if $kind ~~ [qw( test initialize precondition bg_exec )]; - # bg_exec has a single unnamed argument, the command - return ( kind => $kind, command => shift @args ) - if $kind eq 'bg_exec'; - # idle has a single unnamed argument, the time - return ( kind => $kind, run_time => shift @args ) + %step_args = ( %step_args, run_time => shift @args ) if $kind eq 'idle'; - # default: no arguments, just store the kind - return ( kind => $kind ); + return %step_args; } sub handle_step @@ -254,27 +255,46 @@ sub handle_step my $do_warnings = shift; my $kind = shift; my @step_args = @_; - - my %step = make_step( $kind, @step_args ); + + my $step_number = $self->current_step; + + my %step = make_step( $kind, $step_number, @step_args ); canonicalize_step( \%step ); $self->apply_overrides( \%step ); $self->warn_illegal_args( \%step ) if $do_warnings; - + + # Keep track of the context in which we were called + my $context; + $context = 'list' if wantarray; + $context = 'void' unless defined wantarray; + $context = 'scalar' unless wantarray; + + my $scalar_retval; + my @list_retval; + my $is_legal = $self->try_legalize_arguments( \%step ); if( $is_legal ) { - $callback->( %step ); + # Carry forward our caller's context + $callback->( %step ) if $context eq 'void'; + $scalar_retval = $callback->( %step ) if $context eq 'scalar'; + @list_retval = $callback->( %step ) if $context eq 'list'; + $self->advance_current_step(); } + + return $scalar_retval if $context eq 'scalar'; + return @list_retval if $context eq 'list'; } -sub generate_header($$$) +sub generate_header($$$$) { my $file_name = shift; my $package = shift; my $be_permissive = shift; + my $be_quiet = shift; my $header = <<"HEADER"; package $package; @@ -297,12 +317,34 @@ HEADER $header .= "no warnings 'prototype';\n"; } + if( $pretend ) + { + # Hide warning "Use of unitialized value" + $header .= "no warnings 'uninitialized';\n"; + } + + if( $be_quiet ) + { + # Replace STDOUT and STDERR with NUL + $header .= "local *STDOUT; open STDOUT, '>NUL';\n"; + $header .= "local *STDERR; open STDERR, '>NUL';\n"; + } + # Improve the quality of diagnostic messages $header .= qq(\n# line 1 "$file_name"\n); return $header; } +sub generate_footer +{ + my $footer = <<"FOOTER"; + 1; # Return true if everything goes well +FOOTER + + return $footer; +} + sub execute_recipe { my $self = shift; @@ -313,13 +355,15 @@ sub execute_recipe my $recipe_warnings = $args{'recipe_warnings'} // 0; my $permissive_perl = $args{'permissive_perl'} // 0; + my $quiet_stdio = $args{'quiet_stdio'} // 0; state $eval_count = 0; # Throw-away package to "insulate" us from the eval code my $package = "RecipeEval" . $eval_count++; - my @step_kinds = qw(test bg_exec bg_killall idle); + my @step_kinds = + qw(test purge initialize precondition bg_exec bg_killall idle); { no strict 'refs'; @@ -331,7 +375,7 @@ sub execute_recipe *$sym = sub { - $self->handle_step( + return $self->handle_step( $callback, $recipe_warnings, $kind, @@ -347,10 +391,36 @@ sub execute_recipe { my $file_name = shift; - my $eval_string = - generate_header( $file_name, $package, $permissive_perl ); + # Emulate the behavior of Perl's require statement + foreach my $prefix (@INC) + { + if( -e "$prefix\\$file_name" ) + { + $file_name = "$prefix\\$file_name"; + last; + } + } - $eval_string .= slurp_file( $file_name ); + my $recipe_str = slurp_file( $file_name ); + + # For SSD: expand test macros to follow proper methodology + # We must do this every time we include a new file. + $recipe_str = $self->expand_test_macro( $recipe_str ) + if $self->target->is_ssd(); + + my $eval_string; + + $eval_string .= + generate_header( + $file_name, + $package, + $permissive_perl, + $quiet_stdio + ); + + $eval_string .= $recipe_str; + + $eval_string .= generate_footer(); my $retval = eval( $eval_string ); @@ -368,12 +438,20 @@ sub execute_recipe my $previous_cwd = getcwd(); chdir( $recipes_dir ); + my $eval_string; + # Prefix our header to recipe string - my $eval_string = - generate_header( $self->file_name, $package, $permissive_perl ); + $eval_string = + generate_header( + $self->file_name, + $package, + $permissive_perl, + $quiet_stdio + ); $eval_string .= $self->recipe_string; - + $eval_string .= generate_footer(); + # Eval recipe code with our hooks installed my $retval = eval( $eval_string ); die $EVAL_ERROR unless defined $retval; @@ -428,7 +506,7 @@ sub estimate_run_time(;$) $total += $self->calculate_step_run_time( $step_ref ); } - if( $self->do_precondition ) + if( $self->target->do_precondition ) { foreach my $step_ref ( @{$self->steps} ) { @@ -456,17 +534,84 @@ sub contains_writes() return 0; } +sub prefix_target_init +{ + my $self = shift; + my $input = shift; + + my $str = ""; + + # It's important that we don't add extra newlines otherwise + # error messages may reference the wrong recipe line number. + $str .= "purge();" if $self->target->do_purge; + $str .= "initialize();" if $self->target->do_initialize; + + return $str . $input; +} + +sub get_test_macro_string +{ + my $self = shift; + my $test_args = shift; + + my $str; + + # It's important that we don't add extra newlines otherwise + # error messages may reference the wrong recipe line number. + $str .= q( do { ); + $str .= q( my %args = ) . $test_args . ';'; + + $str .= q( purge() if $args{'purge'} // 1; ) + if $self->target->do_purge; + + $str .= q( initialize() if $args{'initialize'} // 1; ) + if $self->target->do_initialize; + + $str .= q( precondition( %args ) if $args{'precondition'} // 1; ) + if $self->target->do_precondition; + + $str .= q( test( %args ); ); + $str .= q( } ); + + return $str; +} + +sub expand_test_macro +{ + my $self = shift; + my $input = shift; + my $output; + + while( $input =~ s/(.*?)\btest(\(.*)/$2/s ) + { + $output .= $1; + + my $test_args = extract_bracketed( $input, '()' ); + die $EVAL_ERROR unless defined $test_args; + + $output .= $self->get_test_macro_string( $test_args ); + } + + $output .= $input; # Handle remainder + + return $output; +} + sub BUILD { my $self = shift; - $self->_recipe_string( slurp_file( $self->file_name ) ); + my $recipe_str = slurp_file( $self->file_name ); - # Default behavior is to precondition to steady-state when targeting - # an SSD. Allow the command line to override this with --noprecondition. - $self->_do_precondition( - $self->cmd_line->precondition // $self->target->is_ssd - ); + # For HDD: do this exactly once before running the recipe + $recipe_str = $self->prefix_target_init( $recipe_str ) + if $self->target->is_hdd(); + + # For SSD: expand test macros to follow proper methodology + $recipe_str = $self->expand_test_macro( $recipe_str ) + if $self->target->is_ssd(); + + $self->_recipe_string( $recipe_str ); # Phase 1: Parse the recipe to estimate runtime, etc. # @@ -477,7 +622,8 @@ sub BUILD $self->execute_recipe( recipe_warnings => 1, permissive_perl => 1, - callback => sub { push @{$self->steps}, {@_}; } + quiet_stdio => 1, + callback => sub { push @{$self->steps}, {@_}; } ); } @@ -504,9 +650,21 @@ sub get_time_message return $msg; } +sub get_announcement_style +{ + my $step_ref = shift; + + my $kind = $step_ref->{'kind'}; + + # These step "kinds" announce their own progress + return 'internal' + if $kind ~~ [qw( purge precondition initialize )]; + + return 'external'; +} + sub get_announcement_message { - my $self = shift; my $step_ref = shift; my $msg; @@ -526,78 +684,130 @@ sub get_announcement_message } elsif( $step_ref->{'kind'} eq 'idle' ) { - $msg = "Idling..."; + $msg = "Idling"; } elsif( $step_ref->{'kind'} eq 'bg_exec' ) { my $command = $step_ref->{'command'}; - $msg = qq{Executing "$command" in the background...}; + $msg = qq{Executing "$command" in the background}; } elsif( $step_ref->{'kind'} eq 'bg_killall' ) { - $msg = "Killing all background processes..."; + $msg = "Killing all background processes"; } return $msg; } +sub bg_killall +{ + my $self = shift; + + foreach my $proc ( @{$self->bg_processes} ) + { + my $pid = $proc->{'pid'}; + kill_task( $pid ); + } + + @{$self->bg_processes} = (); +} + sub run_step { my $self = shift; my $step_ref = shift; - + my $kind = $step_ref->{'kind'}; - # TODO: SECURE ERASE - # - # In the future when we support SECURE ERASE, here we will add: - # $target->prepare() if $target->is_ssd(); + # Keep track of the context in which we were called + my $context; + $context = 'list' if wantarray; + $context = 'void' unless defined wantarray; + $context = 'scalar' unless wantarray; - if( $kind eq 'test' and $self->do_precondition ) - { - my $pc = PreconditionRunner->new( - raw_disk => $self->cmd_line->raw_disk, - pdnum => $self->target->physical_drive, - volume => $self->target->volume, - target_file => $self->target->file_name, - demo_mode => $self->cmd_line->demo_mode, - is_target_ssd => $self->target->is_ssd - ); + my $scalar_retval; + my @list_retval; - $pc->run_to_steady_state( - msg_prefix => "Preconditioning, ", - output_dir => $self->output_dir, - test_ref => $step_ref - ); - } + my $num_step_digits = length( $self->get_num_steps ); my $progress = sprintf( - "[%3d/%3d] ", $self->current_step, $self->get_num_steps ); + "%${num_step_digits}d/%${num_step_digits}d: ", + $self->current_step, $self->get_num_steps + ); - my $announce = $self->get_announcement_message( $step_ref ); - - my $time = $self->get_time_message( $step_ref ); + my $announcement_style = get_announcement_style( $step_ref ); - my $cols_remaining = 80; - - $cols_remaining -= + if( $announcement_style eq 'external' ) + { + my $announce = get_announcement_message( $step_ref ); + + my $time = $self->get_time_message( $step_ref ); + + my $cols_remaining = 80; + + $cols_remaining -= length( $progress ) + length( $announce ) + length( $time ) + 1; - my $pad = ' ' x $cols_remaining; + my $pad = ' ' x $cols_remaining; - print $progress . $announce . $pad . $time; + print $progress . $announce . $pad . $time; + } - if( $kind eq 'test' ) + if( $kind eq 'purge' ) { + $self->target->purge( + msg_prefix => $progress, + ); + } + elsif( $kind eq 'initialize' ) + { + $self->target->initialize( + msg_prefix => $progress, + ); + } + elsif( $kind eq 'precondition' ) + { + $self->target->precondition( + msg_prefix => $progress, + output_dir => $self->output_dir, + test_ref => $step_ref + ); + } + elsif( $kind eq 'test' ) + { + $self->target->prepare() + unless $self->target->is_prepared(); + + unless( $pretend ) + { + die "Attempt to test non-existent target file?\n" + unless -e $self->target->file_name; + } + + my $ns = $step_ref->{'name_string'}; + + # Record background activity, if any, during this test + if( scalar @{$self->bg_processes} > 0 ) + { + open my $FH, '>', $self->output_dir . "\\background-$ns.txt"; + + foreach my $proc ( @{$self->bg_processes} ) + { + print $FH $proc->{'description'} . "\n"; + print $FH $proc->{'command'} . "\n"; + print $FH $proc->{'pid'} . "\n\n"; + } + + close $FH; + } + if( $step_ref->{'warmup_time'} > 0 ) { $self->io_generator->run( $step_ref, 'warmup' ); } - my $ns = $step_ref->{'name_string'}; - $self->smartctl_runner->collect( file_name => "smart-before-$ns.txt", output_dir => $self->output_dir, @@ -626,6 +836,38 @@ sub run_step $self->logman_runner->stop() if defined $self->logman_runner; $self->power->stop() if defined $self->power; + + # If test() called in list context, parse and return results + if( $context eq 'list' ) + { + my $iogen_parser; + + $iogen_parser = DiskSpdParser->new() + if $self->cmd_line->io_generator eq 'diskspd'; + + $iogen_parser = SqlioParser->new() + if $self->cmd_line->io_generator eq 'sqlio'; + + # ISSUE-REVIEW: is this guaranteed to be correct? + my $iogen_outfile = $self->output_dir . "\\test-$ns.txt"; + + open my $IOGEN_OUT, "<$iogen_outfile" + or die "Error opening $iogen_outfile"; + + my %stats; + + $iogen_parser->parse( $IOGEN_OUT, \%stats ); + + @list_retval = %stats; + + close $IOGEN_OUT; + } + + # Allow test to specify that output files be discarded + my $discard_results = $step_ref->{'discard_results'} // 0; + + unlink( glob( $self->output_dir . "\\*$ns*" ) ) + if $discard_results; } elsif( $kind eq 'idle' ) { @@ -635,6 +877,23 @@ sub run_step } elsif( $kind eq 'bg_exec' ) { + # ISSUE-REVIEW: + # + # Ensure that the target file exists to support stuff like + # purge(); + # bg_exec( do something to target file here ); + # + # Does this make sense in a general purpose bg_exec? + + $self->target->prepare() + unless $self->target->is_prepared(); + + unless( $pretend ) + { + die "Attempt to test non-existent target file?\n" + unless -e $self->target->file_name; + } + my $command = $step_ref->{'command'}; my $pid = execute_task( @@ -642,16 +901,25 @@ sub run_step background => 1, new_window => 1 ); + + my $description = $step_ref->{'description'}; + + push @{$self->bg_processes}, { + pid => $pid, + command => $command, + description => $description + }; - push @{$self->bg_pids}, $pid; } elsif( $kind eq "bg_killall" ) { - kill_task( $_ ) foreach @{$self->bg_pids}; - @{$self->bg_pids} = (); + $self->bg_killall(); } - print "\n"; + print "\n" if $announcement_style eq 'external'; + + return $scalar_retval if $context eq 'scalar'; + return @list_retval if $context eq 'list'; } sub run @@ -678,13 +946,17 @@ sub run unless( ($self->current_step < $self->cmd_line->start_on_step) or ($self->current_step > $self->cmd_line->stop_on_step) ) { - $self->run_step( {@_} ); + return $self->run_step( {@_} ); } + + # Maintain proper context on skipped steps. + # Avoids "Odd number of elements in hash assignment" warning. + return () if wantarray; } ); # kill any leftover background processes - kill_task( $_ ) foreach @{$self->bg_pids}; + $self->bg_killall(); } sub warn_expected_run_time @@ -692,7 +964,7 @@ sub warn_expected_run_time my $self = shift; my $num_pc = - $self->do_precondition ? $self->get_num_test_steps() : 0; + $self->target->do_precondition ? $self->get_num_test_steps() : 0; my $est_pc_time = 0; @@ -715,25 +987,21 @@ sub warn_expected_run_time $self->estimate_run_time( $est_pc_time ) ); - print "\tRun time will be >= $time_string"; + print "\tRun time will be >= $time_string.\n"; - if( $self->cmd_line->initialize ) + if( $self->target->do_initialize ) { - print " after target init"; + print "\tThis does not include target init "; if( $self->target->is_ssd ) { - print " (2 overwrites).\n"; + print "(2 overwrites per-test).\n"; } else { - print " (1 overwrite).\n"; + print "(1 overwrite).\n"; } } - else - { - print ".\n"; - } if( $num_pc > 0 ) { diff --git a/lib/Target.pm b/lib/Target.pm index f703659..d478ae7 100644 --- a/lib/Target.pm +++ b/lib/Target.pm @@ -36,6 +36,7 @@ no if $PERL_VERSION >= 5.017011, use Util; use SmartCtlRunner; +use PreconditionRunner; has 'cmd_line' => ( is => 'ro', @@ -92,25 +93,67 @@ has 'model' => ( writer => '_model' ); -has 'must_clean_disk' => ( +has 'do_create_new_filesystem' => ( is => 'ro', isa => 'Bool', default => 0, - writer => '_must_clean_disk' + writer => '_do_create_new_filesystem' ); -has 'must_create_new_filesystem' => ( +has 'do_create_new_file' => ( is => 'ro', isa => 'Bool', default => 0, - writer => '_must_create_new_filesystem' + writer => '_do_create_new_file' ); -has 'must_create_new_file' => ( +has 'do_initialize' => ( + is => 'ro', + isa => 'Maybe[Bool]', + default => undef, + writer => '_do_initialize' +); + +has 'do_purge' => ( + is => 'ro', + isa => 'Maybe[Bool]', + default => undef, + writer => '_do_purge' +); + +has 'do_precondition' => ( + is => 'ro', + isa => 'Maybe[Bool]', + default => undef, + writer => '_do_precondition' +); + +has 'precondition_runner' => ( + is => 'ro', + isa => 'Maybe[PreconditionRunner]', + default => undef, + writer => '_precondition_runner' +); + +has 'is_purged' => ( is => 'ro', isa => 'Bool', default => 0, - writer => '_must_create_new_file' + writer => '_is_purged' +); + +has 'is_prepared' => ( + is => 'ro', + isa => 'Bool', + default => 0, + writer => '_is_prepared' +); + +has 'is_existing_file_or_volume' => ( + is => 'ro', + isa => 'Bool', + default => 1, + writer => '_is_existing_file_or_volume' ); sub is_ssd() @@ -167,8 +210,9 @@ sub BUILD { my $self = shift; - my $target_str = $self->cmd_line->target; - my $raw_disk = $self->cmd_line->raw_disk; + my $cmd_line = $self->cmd_line; + my $target_str = $cmd_line->target; + my $raw_disk = $cmd_line->raw_disk; if( $target_str =~ /(\\\\\.\\PHYSICALDRIVE)?(\d+)$/ ) { @@ -177,28 +221,26 @@ sub BUILD die qq(Error: target "$target_str" does not exist.\n) unless physical_drive_exists( $self->physical_drive ); - # We *must* ensure the disk is free of any partitions. - # Otherwise, writes can silently fail and appear extremely fast. - $self->_must_clean_disk( 1 ); + $self->_is_existing_file_or_volume( 0 ); unless( $raw_disk ) { - $self->_must_create_new_filesystem( 1 ); - $self->_must_create_new_file( 1 ); + $self->_do_create_new_filesystem( 1 ); + $self->_do_create_new_file( 1 ); } } - elsif( uc( $target_str ) =~ /^([A-Z]{1}\:)$/ ) + elsif( uc( $target_str ) =~ /^([A-Z]{1}\:)\\?$/ ) { die "Error: --raw_disk unsupported with existing volumes.\n" if $raw_disk; $self->_volume( $1 ); - + my $pdname = volume_to_physical_drive( $self->volume ); $pdname =~ /(\d+$)/; $self->_physical_drive( $1 ); - $self->_must_create_new_file( 1 ); + $self->_do_create_new_file( 1 ); } elsif( -r $target_str or $pretend ) { @@ -231,55 +273,108 @@ sub BUILD $self->_rotation_rate( $smartctl->rotation_rate ); $self->_sata_version( $smartctl->sata_version ); } + + if( $self->is_existing_file_or_volume ) + { + # Targeting an existing file/volume, not a whole physical drive. + # We cannot purge without destroying the existing file/volume. + $self->_do_purge( 0 ); + } + else + { + # Targeting a whole physical drive, not an existing volume. + # Purge by default, unless the command line specified otherwise. + $self->_do_purge( $cmd_line->purge // 1 ); + } + + # Default policy (can be overridden by command line): + # SSD: both precondition and initialize + # HDD: do not precondition or initialize + $self->_do_initialize( $cmd_line->initialize // $self->is_ssd ); + $self->_do_precondition( $cmd_line->precondition // $self->is_ssd ); + + $self->_precondition_runner( + PreconditionRunner->new( + cmd_line => $cmd_line, + target => $self + ) + ); } +sub purge +{ + my $self = shift; + my %args = @_; + + my $msg_prefix = $args{'msg_prefix'} // die; + + print $msg_prefix; + + if( $self->is_purged ) + { + print "Skipping purge of already-purged target\n"; + return; + } + + if( $self->is_existing_file_or_volume ) + { + print "Skipping purge of existing file/volume\n"; + return; + } + + unless( $self->do_purge ) + { + my $skip_requested = + ( defined $self->cmd_line->purge and + ( $self->cmd_line->purge == 0 ) ); + + if( $skip_requested ) + { + print "Skipping purge as requested\n"; + } + else + { + print "Skipping purge\n"; + } + + return; + } + + print "Purging\n"; + + # TODO: SECURE ERASE + # + # When the target is an SSD, we should SECURE ERASE here instead of + # the "diskpart clean": + + clean_disk( $self->physical_drive ); + + $self->_is_purged( 1 ); + $self->_is_prepared( 0 ); +} + sub prepare { my $self = shift; - if( $self->must_clean_disk ) + return if $self->is_prepared; + + if( $self->do_create_new_filesystem ) { - # TODO: SECURE ERASE - # - # When the target is an SSD, we should SECURE ERASE here instead of - # the "diskpart clean": - # - # if( $self->is_ssd ) - # { - # print "Secure erasing disk...\n"; - # secure_erase( $self->physical_drive ); - # } - # else - # { - # print "Cleaning disk...\n"; - # clean_disk( $self->physical_drive ); - # } - - print "Cleaning disk...\n"; - - clean_disk( $self->physical_drive ); - } - - if( $self->must_create_new_filesystem ) - { - print "Creating new filesystem...\n"; - create_filesystem( $self->physical_drive, $self->cmd_line->partition_bytes ); - + $self->_volume( physical_drive_to_volume( $self->physical_drive ) ); } - if( $self->must_create_new_file ) + if( $self->do_create_new_file ) { - print "Creating test file...\n"; - my $free_bytes = get_volume_free_space( $self->volume ); die "Couldn't determine free space" - unless defined $free_bytes; + unless defined $free_bytes; # Reserve 1GB right off the top. # When we tried to use the whole drive, we saw odd errors. @@ -308,28 +403,131 @@ sub prepare if( defined $self->volume ) { - print "Syncing target volume...\n"; - execute_task( "sync.cmd " . $self->volume, quiet => 1 ); } - if( $self->cmd_line->initialize ) - { - my $pc = PreconditionRunner->new( - raw_disk => $self->cmd_line->raw_disk, - pdnum => $self->physical_drive, - volume => $self->volume, - target_file => $self->file_name, - demo_mode => $self->cmd_line->demo_mode, - is_target_ssd => $self->is_ssd - ); + $self->_is_purged( 0 ); + $self->_is_prepared( 1 ); +} - $pc->initialize(); +sub calculate_num_init_passes +{ + my $self = shift; + + # Go fast in demo mode + return 1 if $self->cmd_line->demo_mode; + + # For HDD: if we choose to initialize, one pass is sufficient + return 1 if $self->is_hdd; + + # For SSD: we want to dirty all of the NAND, including the OP + # to avoid measuring the fresh-out-of-the-box condition. + # + # Writing the drive 2x is overkill, but we do it only once. + # + # Note that in cases where the file is much smaller than + # the drive, we will need to write the file many times in + # order to write the drive once. + + return 2 if $self->cmd_line->raw_disk; + + my $file_size; + + if( $pretend ) + { + # Ficticious 42GB test file for pretend mode + $file_size = 42 * BYTES_PER_GB_BASE2; } else { - print "Skipping initialization as requested.\n"; + $file_size = -s $self->file_name; } + + $file_size > 0 or die "Target file has zero size?"; + + my $vol_size = get_volume_size( $self->volume ); + + return( int( 2 * ( $vol_size / $file_size ) ) ); +} + +# Similar to SNIA "workload independent preconditioning" +sub initialize +{ + my $self = shift; + + my %args = @_; + + my $msg_prefix = $args{'msg_prefix'} // die; + my $test_ref = $args{'test_ref'}; + + if( defined $test_ref ) + { + # Future work: allow for custom init pattern + ... + } + + unless( $self->do_initialize ) + { + my $skip_requested = + ( defined $self->cmd_line->initialize and + ( $self->cmd_line->initialize == 0 ) ); + + if( $skip_requested ) + { + print $msg_prefix . "Skipping initialization as requested\n"; + } + else + { + print $msg_prefix . "Skipping initialization\n"; + } + + return; + } + + $self->prepare() unless $self->is_prepared(); + + $self->precondition_runner->write_num_passes( + msg_prefix => $msg_prefix . "Initializing: ", + num_passes => $self->calculate_num_init_passes(), + ); +} + +# Similar to SNIA "workload dependent preconditioning" +sub precondition +{ + my $self = shift; + + my %args = @_; + + my $msg_prefix = $args{'msg_prefix'} // die; + my $output_dir = $args{'output_dir'} // die; + my $test_ref = $args{'test_ref'} // die; + + unless( $self->do_precondition ) + { + my $skip_requested = + ( defined $self->cmd_line->precondition and + ( $self->cmd_line->precondition == 0 ) ); + + if( $skip_requested ) + { + print $msg_prefix . "Skipping preconditioning as requested\n"; + } + else + { + print $msg_prefix . "Skipping preconditioning\n"; + } + + return; + } + + $self->prepare() unless $self->is_prepared(); + + $self->precondition_runner->run_to_steady_state( + msg_prefix => $msg_prefix . "Preconditioning: ", + output_dir => $output_dir, + test_ref => $test_ref + ); } no Moose; diff --git a/parse_results.cmd b/parse_results.cmd index 29cb67e..38d42b1 100644 --- a/parse_results.cmd +++ b/parse_results.cmd @@ -146,6 +146,40 @@ sub parse_warmup_file($$) } } +sub parse_background_file($$) +{ + my $file_name = shift; + my $stats_ref = shift; + + unless( -e $file_name ) + { + $stats_ref->{'Background Processes'} = "None"; + return; + } + + return 0 unless -e $file_name; + + open my $LOG, "<$file_name" + or die "Error opening $file_name"; + + while( my $line = <$LOG> ) + { + my $description = $line; + <$LOG>; # Command line is currently ignored + <$LOG>; # PID is currently ignored + <$LOG>; # Blank line expected here + + if( defined $stats_ref->{'Background Processes'} ) + { + $stats_ref->{'Background Processes'} .= ", "; + } + + $stats_ref->{'Background Processes'} .= $description; + } + + close $LOG; +} + sub parse_test_file($$) { my $file_name = shift; @@ -348,6 +382,7 @@ my @cols = name => "Warmup MB/sec Total", format => '#,##0.00', }, + { name => 'Background Processes' }, ); sub generate_cols_section($) @@ -611,6 +646,11 @@ sub parse_directories(@) \%file_stats ); + parse_background_file( + "background-$base_name.txt", + \%file_stats + ); + parse_test_file( $test_file_name, \%file_stats diff --git a/recipes/12hr_stress.rcp b/recipes/12hr_stress.rcp index 5104085..b146f3d 100644 --- a/recipes/12hr_stress.rcp +++ b/recipes/12hr_stress.rcp @@ -26,5 +26,5 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'stress.rpm'; +include 'stress.rpm'; do_stress_tests( 12 ); diff --git a/recipes/24hr_stress.rcp b/recipes/24hr_stress.rcp index 4bb30d6..b97d37b 100644 --- a/recipes/24hr_stress.rcp +++ b/recipes/24hr_stress.rcp @@ -26,5 +26,5 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'stress.rpm'; +include 'stress.rpm'; do_stress_tests( 24 ); diff --git a/recipes/48hr_stress.rcp b/recipes/48hr_stress.rcp index 366ec69..3c08a0f 100644 --- a/recipes/48hr_stress.rcp +++ b/recipes/48hr_stress.rcp @@ -26,5 +26,5 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'stress.rpm'; +include 'stress.rpm'; do_stress_tests( 48 ); diff --git a/recipes/corners.rcp b/recipes/corners.rcp index 34e4ca6..9823055 100644 --- a/recipes/corners.rcp +++ b/recipes/corners.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; my @block_sizes; diff --git a/recipes/flush_check.rcp b/recipes/flush_check.rcp index 8eacafa..39e2eb4 100644 --- a/recipes/flush_check.rcp +++ b/recipes/flush_check.rcp @@ -48,7 +48,10 @@ unless( $cmd_line->raw_disk ) my $drive_letter = $target->volume; $drive_letter =~ s/://; - bg_exec( "sync_loop.cmd $drive_letter" ); + bg_exec( + description => "Sync Loop", + command => "sync_loop.cmd $drive_letter" + ); do_workload( "Targeted Test Background Flush" ); bg_killall(); } diff --git a/recipes/rw_mix.rcp b/recipes/rw_mix.rcp index dbd8ccc..1011e19 100644 --- a/recipes/rw_mix.rcp +++ b/recipes/rw_mix.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [qw( sequential random )], write_percentages => [qw( 30 )], diff --git a/recipes/smart_check.rcp b/recipes/smart_check.rcp index 63be1ee..f5f99b4 100644 --- a/recipes/smart_check.rcp +++ b/recipes/smart_check.rcp @@ -45,11 +45,17 @@ if( $target->supports_smart ) { do_workload( "Targeted Test Read Baseline" ); - bg_exec( "wmi_loop.cmd" ); + bg_exec( + description => "WMI Loop", + command => "wmi_loop.cmd", + ); do_workload( "Targeted Test SMART Return Status" ); bg_killall(); - bg_exec( "smart_loop.cmd " . $target->physical_drive ); + bg_exec( + description => "SMART Loop", + command => "smart_loop.cmd " . $target->physical_drive, + ); do_workload( "Targeted Test SMART Read Data " ); bg_killall(); } diff --git a/recipes/thread_bottleneck_check.rcp b/recipes/thread_bottleneck_check.rcp index d2ea08d..b0be109 100644 --- a/recipes/thread_bottleneck_check.rcp +++ b/recipes/thread_bottleneck_check.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [ 'sequential' ], write_percentages => [ 0 ], diff --git a/recipes/turkey_test_part1a.rcp b/recipes/turkey_test_part1a.rcp index c1d1b11..819ab2f 100644 --- a/recipes/turkey_test_part1a.rcp +++ b/recipes/turkey_test_part1a.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [qw( sequential )], diff --git a/recipes/turkey_test_part1b.rcp b/recipes/turkey_test_part1b.rcp index 6bc2761..b515528 100644 --- a/recipes/turkey_test_part1b.rcp +++ b/recipes/turkey_test_part1b.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [qw( sequential )], diff --git a/recipes/turkey_test_part2a.rcp b/recipes/turkey_test_part2a.rcp index f8725a0..5c73021 100644 --- a/recipes/turkey_test_part2a.rcp +++ b/recipes/turkey_test_part2a.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [qw( random )], diff --git a/recipes/turkey_test_part2b.rcp b/recipes/turkey_test_part2b.rcp index 4276ede..d5e8cf0 100644 --- a/recipes/turkey_test_part2b.rcp +++ b/recipes/turkey_test_part2b.rcp @@ -26,7 +26,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -require 'matrix.rpm'; +include 'matrix.rpm'; do_matrix( access_patterns => [qw( random )], diff --git a/recipes/write_impact_check.rcp b/recipes/write_impact_check.rcp index 3c5dee8..8996968 100644 --- a/recipes/write_impact_check.rcp +++ b/recipes/write_impact_check.rcp @@ -26,27 +26,69 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -sub do_workload($) -{ - my $name_string = shift; - - test( - name_string => $name_string, - write_percentage => 0, - access_pattern => 'random', - block_size => '8K', - queue_depth => 4, - warmup_time => 60, - run_time => 3600 - ); -} - # ISSUE-REVIEW: This could be made to work w/raw_disk if its ever important unless( $cmd_line->raw_disk ) { - do_workload( "Targeted Test Write Impact Baseline" ); + my $ns_prefix = "Targeted Test Write Impact"; - bg_exec( "write_slowly.cmd " . $target->file_name ); - do_workload( "Targeted Test Write Impact" ); - bg_killall(); + foreach my $block_size ( qw( 4K 8K 64K ) ) + { + foreach my $access_pattern ( qw( Random Sequential ) ) + { + my %workload = ( + write_percentage => 0, + access_pattern => lc( $access_pattern ), + block_size => $block_size, + queue_depth => 4, + warmup_time => 60, + run_time => 3600 + ); + + my $ns = "$ns_prefix $block_size $access_pattern"; + + # Run the workload once unmolested as a baseline. + my %baseline = + test( %workload, name_string => "$ns Baseline" ); + + my $read_tput_KBps = $baseline{'MB/sec Total'} * 1000; + + # Run again, this time with an aggressor process injecting + # writes in the background. The aggressor is rate-limited + # to a fraction of the baseline read throughput. + foreach my $write_pct ( qw( 5 10 ) ) + { + my $write_tput_KBps = + int ( ( $write_pct / 100 ) * $read_tput_KBps ); + + my $bg_ns = "$write_pct% Injected Writes"; + + my $cmd = "write_forever.cmd "; + + $cmd .= "-b$block_size "; + $cmd .= "-r " if $access_pattern =~ /random/i; + $cmd .= "-g$write_tput_KBps "; # Rate limit + $cmd .= $target->file_name; + + # We must do our own purge/initialize, so we can "sneak" + # the bg_exec in between these steps and the actual test. + # If we didn't do this, the purge would occur after the + # bg_exec, effectively destroying the target volume and + # causing the backgrounded process to die with an error. + + purge(); + initialize(); + bg_exec( + description => $bg_ns, + command => $cmd, + ); + test( + %workload, + name_string => "$ns $bg_ns", + purge => 0, + initialize => 0, + ); + bg_killall(); + } + } + } } diff --git a/test/regr.cmd b/test/regr.cmd index 754510d..3813645 100644 --- a/test/regr.cmd +++ b/test/regr.cmd @@ -41,6 +41,11 @@ use File::Basename; use Digest::MD5 'md5_hex'; use English; +use FindBin; +use lib "$FindBin::Bin\\..\\lib"; + +use Util; + my $script_name = basename( $PROGRAM_NAME ); my $script_dir = dirname( $PROGRAM_NAME ); @@ -52,6 +57,9 @@ unless( scalar @ARGV == 1 ) my $outdir = "$script_dir\\$ARGV[0]"; +my $total_tests = 0; +my $num_nonzero_exits = 0; + die "Directory $outdir exists\n" if -d $outdir; mkdir( $outdir ); @@ -65,9 +73,16 @@ sub my_exec my $tmp_filename = "$base_filename.tmp"; my $out_filename = "$base_filename.txt"; - system( "echo $cmd > $tmp_filename" ); - system( "$cmd >> $tmp_filename 2>&1" ); - + execute_task( "echo $cmd > $tmp_filename" ); + my $errorlevel = execute_task( "$cmd >> $tmp_filename 2>&1" ); + execute_task( "echo ERRORLEVEL=$errorlevel >> $tmp_filename" ); + + if( $errorlevel != 0 ) + { + warn qq(Errorlevel $errorlevel while running "$cmd"\n); + $num_nonzero_exits++; + } + # Post process raw output file to remove noise open( my $in, "<$tmp_filename" ); open( my $out, ">$out_filename" ); @@ -110,22 +125,45 @@ sub run_one $cmd .= "--verbose "; $cmd .= "--noprompt "; + system( "rmdir /S /Q results >NUL 2>&1" ); + my_exec( "$cmd $args" ); + + $total_tests++; } sub run_matrix { my $base_args = shift; - foreach my $target ( undef, 'P:', 'P:\\fake' ) + my @targets = ( undef ); + + unless( $base_args =~ /--target=/ ) { - foreach my $target_type ( qw( auto ssd hdd ) ) + push @targets, 'P:'; + push @targets, 'P:\\fake'; + } + + my @target_types; + + if( $base_args =~ /--target_type=/ ) + { + @target_types = ( undef ); + } + else + { + @target_types = ( qw( auto ssd hdd ) ); + } + + foreach my $target ( @targets ) + { + foreach my $target_type ( @target_types ) { foreach my $recipe ( undef, 'recipes\\corners.rcp' ) { - my $matrix_args; + my $matrix_args = ""; - $matrix_args .= " --target_type=$target_type"; + $matrix_args .= " --target_type=$target_type" if defined $target_type; $matrix_args .= " --target=$target" if defined $target; $matrix_args .= " --recipe=$recipe" if defined $recipe; @@ -135,6 +173,8 @@ sub run_matrix } } +my $overall_start = time(); + chdir( ".." ); # Preserve existing results directory @@ -148,10 +188,10 @@ run_one( "--active_range=0" ); run_one( "--active_range=110" ); run_one( "--partition_bytes=1000000000 --raw_disk" ); run_one( "--compressibility=110" ); +run_one( "--raw_disk --target=P:" ); +run_one( "--raw_disk --target=P:\\fake" ); # These are the defaults anyway, so just run them once -run_one( "--initialize" ); -run_one( "--precondition" ); run_one( "--active_range=100" ); run_one( "--collect_smart" ); run_one( "--collect_logman" ); @@ -160,9 +200,11 @@ run_one( "--io_generator=diskspd" ); # Run the full matrix on these run_matrix( "" ); -run_matrix( "--noinitialize" ); -run_matrix( "--noprecondition" ); -run_matrix( "--raw_disk" ); +run_matrix( "--initialize --target_type=hdd" ); +run_matrix( "--precondition --target_type=hdd" ); +run_matrix( "--noinitialize --target_type=ssd" ); +run_matrix( "--noprecondition --target_type=ssd" ); +run_matrix( "--raw_disk --target=1234" ); run_matrix( "--active_range=1" ); run_matrix( "--active_range=50" ); run_matrix( "--partition_bytes=1000000000" ); @@ -181,9 +223,16 @@ run_matrix( "--compressibility=1" ); run_matrix( "--compressibility=20" ); run_matrix( "--results_share=\\\\share\\dir" ); run_matrix( "--io_generator=sqlio" ); +run_matrix( "--nopurge --target=1234" ); +run_matrix( "--purge --target=P:" ); +run_matrix( "--purge --target=P:\\fake" ); # Restore original results directory system( "rmdir /S /Q results >NUL 2>&1" ); rename( "results.orig", "results" ); -print "Done! Diff $outdir directory against another run.\n"; +my $dstr = seconds_to_human( time() - $overall_start ); +print "Done (took $dstr)\n"; +print "Ran $total_tests tests\n"; +print "Number of non-zero exits: $num_nonzero_exits\n"; +print "Diff $outdir directory against another run.\n";