зеркало из https://github.com/microsoft/StorScore.git
Sync to latest internal.
This commit is contained in:
Родитель
0e55770223
Коммит
49e282ca08
102
StorScore.cmd
102
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
|
||||
|
@ -115,24 +116,6 @@ else
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
my $msg;
|
||||
if( $target->is_ssd )
|
||||
{
|
||||
my $msg;
|
||||
|
||||
$msg .= "\tWarning!\n";
|
||||
$msg .= "\tThis will destroy ";
|
||||
$msg .= $target->file_name . "\n\n";
|
||||
$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;
|
||||
warn $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"
|
||||
}
|
||||
|
||||
my $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,
|
||||
|
|
|
@ -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%
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
REM low bandwidth (100 KB/sec) sequential 64K writes
|
||||
DiskSpd.exe -W10 -w100 -b64K -h -g100 -d2000000000 %*
|
|
@ -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
|
||||
|
|
|
@ -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 %args = @_;
|
||||
|
||||
my $num_passes = 1;
|
||||
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
my $msg_prefix = $args{'msg_prefix'};
|
||||
my $num_passes = $args{'num_passes'};
|
||||
|
||||
# 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 ) =
|
||||
|
|
442
lib/Recipe.pm
442
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'};
|
||||
my $number = $step_ref->{'number'};
|
||||
|
||||
return unless $kind eq 'test';
|
||||
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;
|
||||
|
||||
# bg_exec has a single unnamed argument, the command
|
||||
return ( kind => $kind, command => shift @args )
|
||||
if $kind eq 'bg_exec';
|
||||
# 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 )];
|
||||
|
||||
# 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
|
||||
|
@ -255,26 +256,45 @@ sub handle_step
|
|||
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,11 +438,19 @@ 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 );
|
||||
|
@ -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,21 +684,34 @@ 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;
|
||||
|
@ -548,56 +719,95 @@ sub run_step
|
|||
|
||||
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 $announcement_style = get_announcement_style( $step_ref );
|
||||
|
||||
my $time = $self->get_time_message( $step_ref );
|
||||
if( $announcement_style eq 'external' )
|
||||
{
|
||||
my $announce = get_announcement_message( $step_ref );
|
||||
|
||||
my $cols_remaining = 80;
|
||||
my $time = $self->get_time_message( $step_ref );
|
||||
|
||||
$cols_remaining -=
|
||||
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(
|
||||
|
@ -643,15 +902,24 @@ sub run_step
|
|||
new_window => 1
|
||||
);
|
||||
|
||||
push @{$self->bg_pids}, $pid;
|
||||
my $description = $step_ref->{'description'};
|
||||
|
||||
push @{$self->bg_processes}, {
|
||||
pid => $pid,
|
||||
command => $command,
|
||||
description => $description
|
||||
};
|
||||
|
||||
}
|
||||
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 )
|
||||
{
|
||||
|
|
316
lib/Target.pm
316
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,17 +221,15 @@ 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;
|
||||
|
@ -198,7 +240,7 @@ sub BUILD
|
|||
$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,39 +273,94 @@ 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
|
||||
|
@ -272,14 +369,12 @@ sub prepare
|
|||
$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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 )],
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 ],
|
||||
|
|
|
@ -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 )],
|
||||
|
|
|
@ -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 )],
|
||||
|
|
|
@ -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 )],
|
||||
|
|
|
@ -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 )],
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,8 +73,15 @@ 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" );
|
||||
|
@ -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";
|
||||
|
|
Загрузка…
Ссылка в новой задаче