mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-29 01:21:37 +00:00
Merge lp:~percona-toolkit-dev/percona-toolkit/pt-diskstats-2.0.
This commit is contained in:
553
t/lib/Diskstats.t
Normal file
553
t/lib/Diskstats.t
Normal file
@@ -0,0 +1,553 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
BEGIN {
|
||||
die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
|
||||
unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
|
||||
unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
|
||||
};
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use Test::More tests => 107;
|
||||
|
||||
use PerconaTest;
|
||||
|
||||
use OptionParser;
|
||||
|
||||
use File::Spec;
|
||||
use File::Temp ();
|
||||
|
||||
BEGIN {
|
||||
use_ok "Diskstats";
|
||||
use_ok "DiskstatsGroupByAll";
|
||||
use_ok "DiskstatsGroupByDisk";
|
||||
use_ok "DiskstatsGroupBySample";
|
||||
}
|
||||
|
||||
my $o = new OptionParser(description => 'Diskstats');
|
||||
$o->get_specs("$trunk/bin/pt-diskstats");
|
||||
$o->get_opts();
|
||||
|
||||
{
|
||||
my $obj = new Diskstats(OptionParser => $o);
|
||||
|
||||
can_ok( $obj, qw(
|
||||
columns_regex devices_regex filename
|
||||
block_size ordered_devs clear_state clear_ordered_devs
|
||||
stats_for prev_stats_for first_stats_for
|
||||
has_stats design_print_formats parse_diskstats_line
|
||||
parse_from print_deltas
|
||||
) );
|
||||
|
||||
# ############################################################################
|
||||
# Testing the constructor.
|
||||
# ############################################################################
|
||||
for my $attr (
|
||||
[ filename => (File::Temp::tempfile($0.'diskstats.XXXXXX',
|
||||
OPEN=>0, UNLINK=>1))[1] ],
|
||||
[ columns_regex => qr/!!!/ ],
|
||||
[ devices_regex => qr/!!!/ ],
|
||||
[ block_size => 215 ],
|
||||
[ show_inactive => 1 ],
|
||||
[ sample_time => 1 ],
|
||||
[ interactive => 1 ],
|
||||
) {
|
||||
my $attribute = $attr->[0];
|
||||
my $value = $attr->[1];
|
||||
my $test_obj = Diskstats->new( @$attr, OptionParser => $o );
|
||||
|
||||
is(
|
||||
$test_obj->$attribute(),
|
||||
$value,
|
||||
"Passing an explicit [$attribute] to the constructor works",
|
||||
);
|
||||
}
|
||||
|
||||
# ############################################################################
|
||||
# parse_diskstats_line
|
||||
# ############################################################################
|
||||
for my $test (
|
||||
[
|
||||
"104 0 cciss/c0d0 2139885 162788 37361471 8034486 17999682 83425310 811400340 12711047 0 6869437 20744582",
|
||||
[
|
||||
104, 0, "cciss/c0d0", # major, minor, device
|
||||
|
||||
2139885, # reads
|
||||
162788, # reads_merged
|
||||
37361471, # read_sectors
|
||||
8034486, # ms_spent_reading
|
||||
|
||||
17999682, # writes
|
||||
83425310, # writes_merged
|
||||
811400340, # written_sectors
|
||||
12711047, # ms_spent_writing
|
||||
|
||||
0, # ios_in_progress
|
||||
6869437, # ms_spent_doing_io
|
||||
20744582, # ms_weighted
|
||||
|
||||
18680735.5, # read_kbs
|
||||
405700170, # written_kbs
|
||||
103727665, # ios_requested
|
||||
434566047232,# ios_in_bytes
|
||||
],
|
||||
"parse_diskstats_line works"
|
||||
],
|
||||
[
|
||||
" 8 33 sdc1 1572537676 2369344 3687151364 1575056414 2541895139 1708184481 3991989096 121136333 1 982122453 1798311795",
|
||||
[
|
||||
'8', '33', 'sdc1', 1572537676, '2369344', 3687151364,
|
||||
'1575056414', 2541895139, '1708184481', 3991989096,
|
||||
'121136333', '1', '982122453', '1798311795', '1843575682',
|
||||
'1995994548', 5824986640, '3931719915520'
|
||||
],
|
||||
"parse_diskstats_line works"
|
||||
],
|
||||
[
|
||||
" 8 33 sdc1 1572537676 2369344 3687151364 1575056414 2541895139 1708184481 3991989096 121136333 1 982122453 1798311795\n",
|
||||
[
|
||||
'8', '33', 'sdc1', 1572537676, '2369344', 3687151364,
|
||||
'1575056414', 2541895139, '1708184481', 3991989096,
|
||||
'121136333', '1', '982122453', '1798311795',
|
||||
'1843575682',
|
||||
'1995994548', 5824986640, '3931719915520'
|
||||
],
|
||||
"parse_diskstats_line ignores a trailing newline"
|
||||
],
|
||||
[
|
||||
" 8 33 sdc1 1572537676 2369344 3687151364 1575056414 2541895139 1708184481 3991989096 121136333 1 982122453 \n",
|
||||
undef,
|
||||
"parse_diskstats_line fails on a line without enough fields"
|
||||
],
|
||||
[
|
||||
" 8 33 sdc1 1572537676 2369344 3687151364 1575056414 2541895139 1708184481 3991989096 121136333 1 982122453 12224123 12312312",
|
||||
undef,
|
||||
"parse_diskstats_line fails on a line with too many fields"
|
||||
],
|
||||
[
|
||||
"",
|
||||
undef,
|
||||
"parse_diskstats_line returns undef on an empty string",
|
||||
],
|
||||
[
|
||||
"Malformed line",
|
||||
undef,
|
||||
"parse_diskstats_line returns undef on a malformed line",
|
||||
],
|
||||
) {
|
||||
my ($line, $expected_results, $desc) = @$test;
|
||||
my ($dev, $res) = $obj->parse_diskstats_line($line, $obj->block_size);
|
||||
is_deeply( $res, $expected_results, $desc );
|
||||
}
|
||||
|
||||
# For speed, ->parse_diskstats_line doesn't check for undef.
|
||||
# In any case, this should never happen, since it's internally
|
||||
# used within a readline() loop.
|
||||
local $EVAL_ERROR;
|
||||
eval { $obj->parse_diskstats_line(undef, $obj->block_size); };
|
||||
like(
|
||||
$EVAL_ERROR,
|
||||
qr/Use of uninitialized value/,
|
||||
"parse_diskstats_line should fail on undef",
|
||||
);
|
||||
|
||||
|
||||
# ############################################################################
|
||||
# design_print_formats
|
||||
# ############################################################################
|
||||
|
||||
my @columns_in_order = @Diskstats::columns_in_order;
|
||||
|
||||
$obj->set_columns_regex(qr/./);
|
||||
my ($header, $rows, $cols) = $obj->design_print_formats();
|
||||
is_deeply(
|
||||
$cols,
|
||||
[ map { $_->[0] } @columns_in_order ],
|
||||
"design_print_formats: returns the expected columns"
|
||||
);
|
||||
|
||||
# qr/ \A (?!.*io_s$|\s*[qs]time$) /x
|
||||
$obj->set_columns_regex(qr/cnc|rt|busy|prg|[mk]b|[dr]_s|mrg/);
|
||||
($header, $rows, $cols) = $obj->design_print_formats();
|
||||
is(
|
||||
$header,
|
||||
join(" ", q{%+*s %-6s}, grep { $_ =~ $obj->columns_regex() } map { $_->[0] } @columns_in_order),
|
||||
"design_print_formats: sanity check for defaults"
|
||||
);
|
||||
|
||||
$obj->set_columns_regex(qr/./);
|
||||
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
||||
my $all_columns_format = join(" ", q{%+*s %-10s}, map { $_->[0] } @columns_in_order);
|
||||
is(
|
||||
$header,
|
||||
$all_columns_format,
|
||||
"design_print_formats: max_device_length works"
|
||||
);
|
||||
|
||||
$obj->set_columns_regex(qr/(?!)/); # Will never match
|
||||
($header, $rows, $cols) = $obj->design_print_formats(max_device_length => 10);
|
||||
is(
|
||||
$header,
|
||||
q{%+*s %-10s },
|
||||
"design_print_formats respects columns_regex"
|
||||
);
|
||||
|
||||
$obj->set_columns_regex(qr/./);
|
||||
($header, $rows, $cols) = $obj->design_print_formats(
|
||||
max_device_length => 10,
|
||||
columns => []
|
||||
);
|
||||
is(
|
||||
$header,
|
||||
q{%+*s %-10s },
|
||||
"...unless we pass an explicit column array"
|
||||
);
|
||||
|
||||
$obj->set_columns_regex(qr/./);
|
||||
($header, $rows, $cols) = $obj->design_print_formats(
|
||||
max_device_length => 10,
|
||||
columns => [qw( busy )]
|
||||
);
|
||||
is(
|
||||
$header,
|
||||
q{%+*s %-10s busy},
|
||||
"Header"
|
||||
);
|
||||
|
||||
($header, $rows, $cols) = $obj->design_print_formats(
|
||||
max_device_length => 10,
|
||||
columns =>
|
||||
[ map { $_->[0] } @columns_in_order ],
|
||||
);
|
||||
is(
|
||||
$header,
|
||||
$all_columns_format,
|
||||
"All columns format"
|
||||
);
|
||||
|
||||
throws_ok( sub { $obj->design_print_formats( columns => {} ) },
|
||||
qr/The columns argument to design_print_formats should be an arrayref/,
|
||||
"design_print_formats dies when passed an invalid columns argument");
|
||||
|
||||
# ############################################################################
|
||||
# timestamp methods
|
||||
# ############################################################################
|
||||
for my $method ( qw( curr_ts prev_ts first_ts ) ) {
|
||||
my $setter = "set_$method";
|
||||
ok(!$obj->$method(), "Diskstats->$method is initially false");
|
||||
|
||||
$obj->$setter(10);
|
||||
is($obj->$method(), 10, "Diskstats->$setter(10) sets it to 10");
|
||||
|
||||
$obj->$setter(20);
|
||||
$obj->clear_ts();
|
||||
ok(!$obj->$method(), "Diskstats->clear_ts does as advertized");
|
||||
}
|
||||
|
||||
# ############################################################################
|
||||
# Adding, removing and listing devices.
|
||||
# ############################################################################
|
||||
is_deeply(
|
||||
[ $obj->ordered_devs() ],
|
||||
[],
|
||||
"ordered_devs starts empty"
|
||||
);
|
||||
|
||||
$obj->add_ordered_dev("sda");
|
||||
is_deeply(
|
||||
[ $obj->ordered_devs() ],
|
||||
[ qw( sda ) ],
|
||||
"We can add devices just fine,"
|
||||
);
|
||||
|
||||
$obj->add_ordered_dev("sda");
|
||||
is_deeply(
|
||||
[ $obj->ordered_devs() ],
|
||||
[ qw( sda ) ],
|
||||
"...And duplicates get detected and discarded"
|
||||
);
|
||||
|
||||
$obj->clear_ordered_devs();
|
||||
is_deeply(
|
||||
[ $obj->ordered_devs() ],
|
||||
[],
|
||||
"clear_ordered_devs does as advertized,"
|
||||
);
|
||||
$obj->add_ordered_dev("sda");
|
||||
is_deeply(
|
||||
[ $obj->ordered_devs() ],
|
||||
[ qw( sda ) ],
|
||||
"...And clears the internal duplicate-checking list"
|
||||
);
|
||||
|
||||
# ############################################################################
|
||||
# show_inactive -- Whenever it prints inactive devices.
|
||||
# ############################################################################
|
||||
##
|
||||
## show_inactive now functions inside parse_from
|
||||
##
|
||||
#$obj->set_show_inactive(0);
|
||||
#my $print_output = output(
|
||||
# sub {
|
||||
# $obj->print_rows(
|
||||
# "SHOULDN'T PRINT THIS",
|
||||
# [ qw( a b c ) ],
|
||||
# { a => 0, b => 0, c => 0, d => 10 }
|
||||
# );
|
||||
# }
|
||||
#);
|
||||
#$obj->set_show_inactive(1);
|
||||
#
|
||||
#is(
|
||||
# $print_output,
|
||||
# "",
|
||||
# "->show_inactive works"
|
||||
#);
|
||||
|
||||
# ############################################################################
|
||||
# Sane defaults and fatal errors
|
||||
# ############################################################################
|
||||
for my $method ( qw( delta_against delta_against_ts group_by ) ) {
|
||||
throws_ok(
|
||||
sub { Diskstats->$method() },
|
||||
qr/\QYou must override $method() in a subclass\E/,
|
||||
"->$method has to be overriden"
|
||||
);
|
||||
}
|
||||
|
||||
is(
|
||||
$obj->compute_line_ts( first_ts => 0 ),
|
||||
sprintf( "%5.1f", 0 ),
|
||||
"compute_line_ts has a sane default",
|
||||
);
|
||||
|
||||
$obj->set_force_header(0);
|
||||
|
||||
is(
|
||||
output( sub { $obj->print_header("asdasdas") } ),
|
||||
"",
|
||||
"force_header works"
|
||||
);
|
||||
|
||||
my $output = output(
|
||||
sub { $obj->parse_from( data => "ASMFHNASJNFASKLFLKHNSKD" ); },
|
||||
stderr => 1,
|
||||
);
|
||||
|
||||
is(
|
||||
$output,
|
||||
"",
|
||||
"Doesn't die parsing unknown line"
|
||||
);
|
||||
|
||||
# ############################################################################
|
||||
# _calc* methods
|
||||
# ############################################################################
|
||||
|
||||
$obj->clear_state();
|
||||
|
||||
my $prev = {
|
||||
TS => 1281367519,
|
||||
data => ($obj->parse_diskstats_line(
|
||||
"104 0 cciss/c0d0 2139885 162788 37361471 8034486 17999682 83425310 811400340 12711047 0 6869437 20744582", $obj->block_size))[1]
|
||||
};
|
||||
my $curr = {
|
||||
TS => 1281367521,
|
||||
data => ($obj->parse_diskstats_line(
|
||||
"104 0 cciss/c0d0 2139886 162790 37361478 8034489 17999738 83425580 811402798 12711097 3 6869449 20744632", $obj->block_size))[1]
|
||||
};
|
||||
|
||||
$obj->first_ts( $prev->{TS} );
|
||||
$obj->prev_ts( $prev->{TS} );
|
||||
$obj->curr_ts( $curr->{TS} );
|
||||
|
||||
my $deltas = $obj->_calc_delta_for($curr->{data}, $prev->{data});
|
||||
|
||||
is_deeply(
|
||||
$deltas,
|
||||
{
|
||||
ms_spent_doing_io => 12,
|
||||
ms_spent_reading => 3,
|
||||
ms_spent_writing => 50,
|
||||
ms_weighted => 50,
|
||||
read_kbs => 3.5,
|
||||
read_sectors => 7,
|
||||
reads => 1,
|
||||
reads_merged => 2,
|
||||
writes => 56,
|
||||
writes_merged => 270,
|
||||
written_kbs => 1229,
|
||||
written_sectors => 2458,
|
||||
ios_in_bytes => 1262080,
|
||||
ios_requested => 329,
|
||||
ios_in_progress => 3,
|
||||
},
|
||||
"_calc_delta_for works"
|
||||
);
|
||||
|
||||
local $EVAL_ERROR;
|
||||
eval { $obj->_calc_delta_for($curr->{data}, []) };
|
||||
ok(!$EVAL_ERROR, "_calc_delta_for guards against undefined values");
|
||||
|
||||
my %read_stats = $obj->_calc_read_stats(
|
||||
delta_for => $deltas,
|
||||
elapsed => $curr->{TS} - $prev->{TS},
|
||||
devs_in_group => 1,
|
||||
);
|
||||
|
||||
is_deeply(
|
||||
\%read_stats,
|
||||
{
|
||||
avg_read_sz => '3.5',
|
||||
mbytes_read_sec => '0.001708984375',
|
||||
read_conc => '0.0015',
|
||||
read_merge_pct => '66.6666666666667',
|
||||
read_requests => 3,
|
||||
read_rtime => 1,
|
||||
reads_sec => '0.5'
|
||||
},
|
||||
"_calc_read_stats works"
|
||||
);
|
||||
|
||||
my %write_stats = $obj->_calc_write_stats(
|
||||
delta_for => $deltas,
|
||||
elapsed => $curr->{TS} - $prev->{TS},
|
||||
devs_in_group => 1,
|
||||
);
|
||||
|
||||
is_deeply(
|
||||
\%write_stats,
|
||||
{
|
||||
avg_write_sz => '21.9464285714286',
|
||||
mbytes_written_sec => '0.60009765625',
|
||||
write_conc => '0.025',
|
||||
write_merge_pct => '82.8220858895706',
|
||||
write_requests => 326,
|
||||
write_rtime => '0.153374233128834',
|
||||
writes_sec => '28',
|
||||
},
|
||||
"_calc_write_stats works"
|
||||
);
|
||||
|
||||
my %misc_stats = $obj->_calc_misc_stats(
|
||||
delta_for => $deltas,
|
||||
elapsed => $curr->{TS} - $prev->{TS},
|
||||
devs_in_group => 1,
|
||||
stats => { %write_stats, %read_stats },
|
||||
);
|
||||
|
||||
is_deeply(
|
||||
\%misc_stats,
|
||||
{
|
||||
busy => '0.6',
|
||||
line_ts => ' 0.0',
|
||||
qtime => '0.114128245504816',
|
||||
s_spent_doing_io => '28.5',
|
||||
stime => '0.0364741641337386',
|
||||
},
|
||||
"_calc_misc_stats works"
|
||||
);
|
||||
|
||||
$obj->clear_state();
|
||||
|
||||
}
|
||||
# ############################################################################
|
||||
# The three subclasses
|
||||
# ############################################################################
|
||||
for my $test (
|
||||
{
|
||||
class => "DiskstatsGroupByAll",
|
||||
results_file_prefix => "all",
|
||||
},
|
||||
{
|
||||
class => "DiskstatsGroupByDisk",
|
||||
results_file_prefix => "disk",
|
||||
},
|
||||
{
|
||||
class => "DiskstatsGroupBySample",
|
||||
results_file_prefix => "sample",
|
||||
}) {
|
||||
my $obj = $test->{class}->new(OptionParser => $o, show_inactive => 1);
|
||||
my $prefix = $test->{results_file_prefix};
|
||||
|
||||
$obj->set_columns_regex(qr/./);
|
||||
$obj->set_show_inactive(1);
|
||||
$obj->set_show_timestamps(0);
|
||||
$obj->set_automatic_headers(0);
|
||||
$obj->set_show_line_between_samples(0);
|
||||
|
||||
for my $filename ( map "diskstats-00$_.txt", 1..5 ) {
|
||||
my $file = File::Spec->catfile( "t", "pt-diskstats", "samples", $filename );
|
||||
my $file_with_trunk = File::Spec->catfile( $trunk, $file );
|
||||
|
||||
my $expected = load_file( File::Spec->catfile( "t", "pt-diskstats", "expected", "${prefix}_$filename" ) );
|
||||
|
||||
my $got = output(
|
||||
sub {
|
||||
$obj->group_by(
|
||||
filename => $file_with_trunk,
|
||||
);
|
||||
});
|
||||
|
||||
is($got, $expected, "group_by $prefix: $filename via filename");
|
||||
|
||||
$got = output(
|
||||
sub {
|
||||
open my $fh, "<", $file_with_trunk or die $!;
|
||||
$obj->group_by(
|
||||
filehandle => $fh,
|
||||
);
|
||||
});
|
||||
|
||||
is($got, $expected, "group_by $prefix: $filename via filehandle");
|
||||
|
||||
$got = output(
|
||||
sub {
|
||||
$obj->group_by(
|
||||
data => "TS 1298130002.073935000\n" . load_file( $file ),
|
||||
);
|
||||
});
|
||||
|
||||
is($got, $expected, "group_by $prefix: $filename with an extra TS at the top");
|
||||
}
|
||||
|
||||
my $data = <<'EOF';
|
||||
TS 1297205887.156653000
|
||||
1 0 ram0 0 0 0 0 0 0 0 0 0 0 0
|
||||
TS 1297205888.161613000
|
||||
EOF
|
||||
|
||||
my $got = output( sub { $obj->group_by(data => $data) }, stderr => 1 );
|
||||
is(
|
||||
$got,
|
||||
'',
|
||||
"group_by $prefix: 1 line of data between two TS lines results in no output"
|
||||
);
|
||||
|
||||
$obj->set_curr_ts(0);
|
||||
$obj->set_prev_ts(0);
|
||||
$obj->set_first_ts(0);
|
||||
|
||||
throws_ok(
|
||||
sub { $obj->_calc_deltas() },
|
||||
qr/Time between samples should be > 0, is /,
|
||||
"$test->{class}, ->_calc_deltas fails if the time elapsed is 0"
|
||||
);
|
||||
|
||||
$obj->set_curr_ts(0);
|
||||
$obj->set_prev_ts(4);
|
||||
$obj->set_first_ts(4);
|
||||
|
||||
throws_ok(
|
||||
sub { $obj->_calc_deltas() },
|
||||
qr/Time between samples should be > 0, is /,
|
||||
"$test->{class}, ->_calc_deltas fails if the time elapsed is negative"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
# ###########################################################################
|
||||
# Done.
|
||||
# ###########################################################################
|
||||
exit;
|
Reference in New Issue
Block a user