File Coverage

blib/lib/Test/Parser/iozone.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Test::Parser::iozone;
2              
3             =head1 NAME
4              
5             Test::Parser::iozone - Perl module to parse output from iozone.
6              
7             =head1 SYNOPSIS
8              
9             use Test::Parser::iozone;
10              
11             my $parser = new Test::Parser::iozone;
12             $parser->parse($text);
13              
14             =head1 DESCRIPTION
15              
16             This module transforms iozone output into a hash that can be used to generate
17             XML.
18              
19             =head1 FUNCTIONS
20              
21             Also see L for functions available from the base class.
22              
23             =cut
24              
25 1     1   39207 use strict;
  1         3  
  1         49  
26 1     1   6 use warnings;
  1         2  
  1         34  
27 1     1   737 use Test::Parser;
  1         16  
  1         60  
28 1     1   1885 use XML::Simple;
  0            
  0            
29             #use Chart::Graph::Gnuplot qw(gnuplot);
30              
31             @Test::Parser::iozone::ISA = qw(Test::Parser);
32             use base 'Test::Parser';
33              
34             use fields qw(
35             device
36             data
37             info
38             rundate
39             commandline
40             _mode
41             iteration
42             );
43              
44             use vars qw( %FIELDS $AUTOLOAD $VERSION );
45             our $VERSION = '1.7';
46              
47             use constant IOZONE_HEADERS =>
48             ( 'KB', 'reclen', 'write', 'rewrite', 'read',
49             'reread', 'random read', 'random write', 'bkwd read',
50             'record rewrite', 'stride read', 'fwrite', 'frewrite',
51             'fread', 'freread');
52              
53             =head2 new()
54              
55             Creates a new Test::Parser::iozone instance.
56             Also calls the Test::Parser base class' new() routine.
57             Takes no arguments.
58              
59             =cut
60              
61             sub new {
62             my $class = shift;
63             my Test::Parser::iozone $self = fields::new($class);
64             $self->SUPER::new();
65              
66             $self->testname('iozone');
67             $self->type('stress');
68             $self->description('FIXME');
69             $self->summary('FIXME');
70             $self->license('FIXME');
71             $self->vendor('FIXME');
72             $self->release('FIXME');
73             $self->url('FIXME');
74             $self->platform('FIXME');
75              
76             #
77             # iozone data in an array and other supporting information.
78             #
79             $self->{data} = [];
80             $self->{info} = '';
81             $self->{rundate} = '';
82             $self->{version} = '';
83             $self->{commandline} = '';
84             $self->{_mode} = '';
85             $self->{iteration} = 0;
86              
87             #
88             # Used for plotting.
89             #
90             $self->{format} = 'png';
91             $self->{outdir} = '.';
92             $self->{units} = 'Kbytes/sec';
93              
94             return $self;
95             }
96              
97             =head3 data()
98              
99             Returns a hash representation of the iozone data.
100              
101             =cut
102             sub data {
103             my $self = shift;
104             if (@_) {
105             $self->{data} = @_;
106             }
107              
108             return $self->{test};
109             }
110              
111             =head3
112              
113             Override of Test::Parser's default parse_line() routine to make it able
114             to parse iozone output.
115              
116             =cut
117             sub parse_line {
118             my $self = shift;
119             my $line = shift;
120              
121             #
122             # Initial info section
123             #
124             if ( $line =~ m/^\t(.*)/ ) {
125             if ($self->{_mode} eq 'data') {
126             # Looks like maybe there's multiple iozone runs in this file
127             # In any case, we're done with this one...
128             $self->{_mode} = '';
129             return Test::Parser::END_OF_RECORD;
130             }
131             $self->{_mode} = 'info';
132             $self->{info} .= $line;
133              
134             if ($line =~ m/Version.*(\d+\.\d+)\b/) {
135             $self->version($1);
136             }
137              
138             if ($line =~ m/Command line used\:\s+(.*)$/) {
139             $self->{commandline} = $1;
140             }
141              
142             if ($line =~ m/Run began\:\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)?\s*(.*)$/) {
143             $self->{rundate} = $2;
144              
145             # Hack to make gnuplot read the data correctly...
146             $self->{rundate} =~ s/\s+/:/g;
147             }
148             }
149              
150             # data
151             elsif ($line =~ /^(\s*)\d+/) {
152             $self->{_mode} = 'data';
153            
154             my @h = IOZONE_HEADERS;
155              
156             while (@h) {
157             if ($self->{iteration} eq 0 ) {
158             $self->add_column(shift @h);
159             }
160             else {
161             shift @h;
162             }
163             }
164            
165             $self->{iteration} = 1;
166            
167             # Strip leading and trailing space
168             $line =~ s/^\s+//;
169             $line =~ s/\s+$//;
170              
171             if ( $line =~ m/(\d*)\s*(\d*)\s*(\d*)\s*(\d*)\s*(\d*)\s*(\d*)\s*/ ) {
172             # warn ("$1 $2 $3 $4 $5 $6\n");
173            
174             $self->add_data($1, 1);
175             $self->add_data($2, 2);
176             $self->add_data($3, 3);
177             $self->add_data($4, 4);
178             $self->add_data($5, 5);
179             $self->add_data($6, 6);
180            
181             $self->inc_datum();
182             }
183             }
184             return 1;
185             }
186              
187             =head3 plot_2d()
188              
189             Plot the data using Gnuplot.
190              
191             =cut
192              
193              
194             =head3 commented_out
195              
196             FIXME: This will eventually be supported through the Test::Presenter method
197             to_plot(). When this method has been written, most of this can be thrown out
198              
199              
200             sub plot_2d {
201             my $self = shift;
202              
203             my %gopts;
204             $gopts{'defaults'} = {
205             'title' => 'iozone Performance',
206             # 'yrange' => '[0:]',
207             'x-axis label' => 'Record size (kb)',
208             'y-axis label' => 'File size (kb)',
209             # 'extra_opts' => 'set grid xtics ytics',
210             # 'output type' => "$self->{format}",
211             'output file' => "$self->{outdir}/iozone-",
212             };
213              
214             my %data_opts = ( 'title' => '',
215             'style' => 'lines',
216             'type' => 'columns' ,
217             );
218              
219             # TODO: We're just taking a snapshot at 32 byte record lengths
220             # We should either take this as an input, or provide a
221             # 3D plotting capability
222             my $reclen = 32;
223             my @x_columns;
224             my %y_columns;
225             foreach my $d (@{$self->{data}}) {
226             next unless ($d->{'reclen'} == $reclen);
227             push @x_columns, $d->{'KB'};
228             foreach my $key (keys %{$d}) {
229             next unless (defined $d->{$key});
230             push @{$y_columns{$key}}, $d->{$key};
231             }
232             }
233              
234             print "Number of X points (should be about 10-20):",
235             scalar @x_columns, "\n";
236              
237             #
238             # Generate charts.
239             #
240             foreach my $h (IOZONE_HEADERS) {
241             # Skip x-columns
242             next if ($h =~ /^kb$/i
243             or $h =~ /^reclen$/i);
244              
245             %{$gopts{$h}} = %{$gopts{'defaults'}};
246             $gopts{$h}->{'title'} .= " - $h";
247             $gopts{$h}->{'output file'} .= "$h.$self->{format}";
248              
249             if (defined $y_columns{$h} ) {
250             print "plotting $h\n";
251             gnuplot( $gopts{$h}, [\%data_opts, \@x_columns, $y_columns{$h}] );
252             }
253             }
254             }
255             =cut
256              
257             sub plot_3d {
258             # TODO
259             }
260              
261             # Retrieves a specific data point from the recordset
262             sub datum {
263             my $self = shift;
264             my $file_size = shift || return undef;
265             my $reclen = shift || return undef;
266              
267             foreach my $d (@{$self->{data}}) {
268             if ( $d->{'KB'} == $file_size &&
269             $d->{'reclen'} == $reclen) {
270             return $d;
271             }
272             }
273             my %null_data;
274             foreach my $h (IOZONE_HEADERS) {
275             $null_data{$h} = 0;
276             }
277             return \%null_data;
278             }
279              
280             sub _runs_to_data {
281             my $runs = shift || return undef;
282             my $file_sizes = shift || return undef;
283             my $reclens = shift || return undef;
284             my %data;
285              
286             print "Reading data from runs";
287              
288             if (@{$file_sizes} < 1) {
289             warn "Error: No file sizes specified\n";
290             return undef;
291             }
292             if (@{$reclens} < 1) {
293             warn "Error: No record lengths specified\n";
294             return undef;
295             }
296            
297             foreach my $run (@{$runs}) {
298             print '.';
299              
300             # TODO: Text X values not supported...?
301             # my $x = $run->name() || $run->{rundate};
302             my $x = $run->{rundate};
303              
304             foreach my $file_size (@{$file_sizes}) {
305             my $key = undef;
306              
307             # If multiple file sizes, add to data title
308             if (@{$file_sizes}>1) {
309             $key = "$file_size kb files";
310             }
311              
312             foreach my $reclen (@{$reclens}) {
313             # If multiple reclens, join to data title
314             if (@{$reclens}>1) {
315             if ($key) {
316             $key = join(', ', $key, "$reclen kb rec");
317             } else {
318             $key = "$reclen kb rec";
319             }
320             }
321              
322             # Add x,y to data sets
323             my $d = $run->datum($file_size, $reclen);
324             foreach my $h (IOZONE_HEADERS) {
325             if (defined $d->{$h}) {
326             push @{$data{$h}->{$file_size}->{$reclen}}, [$x, $d->{$h}];
327             }
328             }
329             }
330             }
331             }
332              
333             print "\n";
334              
335             return %data;
336             }
337              
338              
339             =head3 commented_out
340              
341             FIXME: This will eventually be supported through the Test::Presenter method
342             to_plot(). When this method has been written, most of this can be thrown out
343              
344             # This is a static function for plotting multiple runs
345             # with a date or software version as the X-Axis
346             sub historical_plot {
347             my $runs = shift || return undef;
348             my $file_sizes = shift || return undef;
349             my $reclens = shift || return undef;
350              
351             my $format = $runs->[0]->{format};
352             my $outdir = $runs->[0]->{outdir};
353              
354             # Graph options
355             my %gopts_defaults =
356             (
357             'title' => 'Historical iozone Performance',
358             'x-axis label' => 'Time',
359             'y-axis label' => 'KB/sec',
360             'yrange' => '[0:]',
361             'xdata' => 'time',
362             'timefmt' => '%b:%d:%H:%M:%S:%Y',
363             'format' => ['y', '%.0f'],
364              
365             'output file' => "$outdir/iozone-",
366             # 'extra_opts' => 'set grid',
367             );
368              
369             # Data-set default options
370             my %data_opts =
371             (
372             'title' => '',
373             'style' => 'lines',
374             'type' => 'matrix',
375             );
376              
377             if (@{$runs} < 1) {
378             warn "No data to graph\n";
379             return undef;
380             }
381            
382             if (@{$file_sizes} == 1) {
383             # Put file_size into title
384             $gopts_defaults{'title'} =
385             join(" - ", $gopts_defaults{'title'}, "$file_sizes->[0] kb files");
386             }
387              
388             if (@{$reclens} == 1) {
389             # Put reclen into title
390             $gopts_defaults{'title'} =
391             join(" - ", $gopts_defaults{'title'}, "$reclens->[0] kb records");
392             }
393              
394             # Transform the list of runs into data matrices indexed by column name
395             my %data = _runs_to_data($runs, $file_sizes, $reclens);
396             if (values %data < 1) {
397             warn "Error: Could not transform data\n";
398             return undef;
399             }
400              
401             # Create a plot for each of the iozone fields with data defined
402             foreach my $h (IOZONE_HEADERS) {
403             # Skip x-columns
404             next if ($h =~ /^kb$/i
405             or $h =~ /^reclen$/i);
406              
407             my %gopts = %gopts_defaults;
408             $gopts{'output file'} .= "$h.$format";
409              
410             if ( $data{$h} ) {
411             my @data_sets;
412             foreach my $file_size (@{$file_sizes}) {
413             foreach my $reclen (@{$reclens}) {
414             my %opts = %data_opts;
415             if (@{$file_sizes} > 1) {
416             $opts{'title'} .= " - $file_size kb files";
417             }
418             if (@{$reclens} > 1) {
419             $opts{'title'} .= " - $reclen kb records";
420             }
421             push @data_sets, [\%opts, $data{$h}->{$file_size}->{$reclen}];
422             }
423             }
424             print "plotting $h\n";
425             gnuplot(\%gopts, @data_sets );
426             }
427             }
428              
429             }
430              
431             # This is a static function to compare several runs
432             sub comparison_plot {
433             my $runs = shift || return undef;
434             my $names = shift || return undef;
435              
436             my $num_runs = @{$runs};
437             my $num_names = @{$names};
438              
439             if ($num_runs != $num_names) {
440             warn "$num_runs runs and $num_names provided.\n";
441             warn "Error: Must specify a name for each run\n";
442             return undef;
443             }
444              
445             if ($num_runs < 2) {
446             warn "Error: Need at least 2 runs to do comparison plot\n";
447             return undef;
448             }
449              
450             my $format = $runs->[0]->{format};
451             my $outdir = $runs->[0]->{outdir};
452              
453             # Graph options
454             my %gopts =
455             (
456             'title' => 'iozone Performance Comparison',
457             'x-axis label' => 'Record size (kb)',
458             'y-axis label' => 'File size (kb)',
459             'output file' => "$outdir/iozone-",
460             );
461              
462             # Transform the list of runs into data matrixes indexed by column name
463             my %data;
464             my $reclen = 32;
465             foreach my $run (@{$runs}) {
466             my $name = shift @{$names};
467             my %data_opts = (
468             'title' => $name,
469             'style' => 'lines',
470             'type' => 'columns',
471             );
472              
473             # Extract the data out of hashes and put into columns
474             my @x_column;
475             my %y_columns;
476             foreach my $d ($run->{data}) {
477             next unless ($d->{'reclen'} == $reclen);
478             push @x_column, $d->{'KB'};
479              
480             foreach my $key (keys %{$d}) {
481             push @{$y_columns{$key}}, $d->{$key};
482             }
483             }
484              
485             # Put the columns
486             foreach my $key (keys %y_columns) {
487             push @{$data{$key}}, [\%data_opts, \@x_column, $y_columns{$key}];
488             }
489             }
490              
491             # Create a plot for each of the iozone fields with data defined
492             foreach my $h (IOZONE_HEADERS) {
493             # Set the global options
494             %{$gopts{$h}} = %{$gopts{'defaults'}};
495             $gopts{$h}->{'title'} .= " - $h";
496             $gopts{$h}->{'output file'} .= "$h.$format";
497              
498             if (defined $gopts{$h} && defined $data{$h}) {
499             print "plotting $h\n";
500             gnuplot($gopts{$h}, @{$data{$h}});
501             }
502             }
503              
504             }
505              
506             =cut
507              
508              
509             sub summary_report {
510             my $runs = shift;
511              
512             # TODO
513             return '';
514             }
515              
516              
517             1;
518             __END__