File Coverage

blib/lib/Sys/Statistics/Linux/DiskStats.pm
Criterion Covered Total %
statement 74 114 64.9
branch 19 40 47.5
condition 5 12 41.6
subroutine 9 10 90.0
pod 4 4 100.0
total 111 180 61.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Sys::Statistics::Linux::DiskStats - Collect linux disk statistics.
4              
5             =head1 SYNOPSIS
6              
7             use Sys::Statistics::Linux::DiskStats;
8              
9             my $lxs = Sys::Statistics::Linux::DiskStats->new;
10             $lxs->init;
11             sleep 1;
12             my $stat = $lxs->get;
13              
14             Or
15              
16             my $lxs = Sys::Statistics::Linux::DiskStats->new(initfile => $file);
17             $lxs->init;
18             my $stat = $lxs->get;
19              
20             =head1 DESCRIPTION
21              
22             Sys::Statistics::Linux::DiskStats gathers disk statistics from the virtual F filesystem (procfs).
23              
24             For more information read the documentation of the front-end module L.
25              
26             =head1 DISK STATISTICS
27              
28             Generated by F or F.
29              
30             major - The mayor number of the disk
31             minor - The minor number of the disk
32             rdreq - Number of read requests that were made to physical disk per second.
33             rdbyt - Number of bytes that were read from physical disk per second.
34             wrtreq - Number of write requests that were made to physical disk per second.
35             wrtbyt - Number of bytes that were written to physical disk per second.
36             ttreq - Total number of requests were made from/to physical disk per second.
37             ttbyt - Total number of bytes transmitted from/to physical disk per second.
38              
39             =head1 METHODS
40              
41             =head2 new()
42              
43             Call C to create a new object.
44              
45             my $lxs = Sys::Statistics::Linux::DiskStats->new;
46              
47             Maybe you want to store/load the initial statistics to/from a file:
48              
49             my $lxs = Sys::Statistics::Linux::DiskStats->new(initfile => '/tmp/diskstats.yml');
50              
51             If you set C it's not necessary to call sleep before C.
52              
53             It's also possible to set the path to the proc filesystem.
54              
55             Sys::Statistics::Linux::DiskStats->new(
56             files => {
57             # This is the default
58             path => '/proc',
59             diskstats => 'diskstats',
60             partitions => 'partitions',
61             }
62             );
63              
64             =head2 init()
65              
66             Call C to initialize the statistics.
67              
68             $lxs->init;
69              
70             =head2 get()
71              
72             Call C to get the statistics. C returns the statistics as a hash reference.
73              
74             my $stat = $lxs->get;
75              
76             =head2 raw()
77              
78             Get raw values.
79              
80             =head1 EXPORTS
81              
82             No exports.
83              
84             =head1 SEE ALSO
85              
86             B
87              
88             =head1 REPORTING BUGS
89              
90             Please report all bugs to .
91              
92             =head1 AUTHOR
93              
94             Jonny Schulz .
95              
96             =head1 COPYRIGHT
97              
98             Copyright (c) 2006, 2007 by Jonny Schulz. All rights reserved.
99              
100             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
101              
102             =cut
103              
104             package Sys::Statistics::Linux::DiskStats;
105              
106 1     1   7 use strict;
  1         1  
  1         45  
107 1     1   6 use warnings;
  1         2  
  1         38  
108 1     1   6 use Carp qw(croak);
  1         2  
  1         69  
109 1     1   1209 use Time::HiRes;
  1         1999  
  1         5  
110              
111             our $VERSION = '0.24';
112              
113             sub new {
114 1     1 1 1 my $class = shift;
115 1 50       8 my $opts = ref($_[0]) ? shift : {@_};
116              
117 1         5 my %self = (
118             files => {
119             path => '/proc',
120             diskstats => 'diskstats',
121             partitions => 'partitions',
122             },
123             # --------------------------------------------------------------
124             # The sectors are equivalent with blocks and have a size of 512
125             # bytes since 2.4 kernels. This value is needed to calculate the
126             # amount of disk i/o's in bytes.
127             # --------------------------------------------------------------
128             blocksize => 512,
129             );
130              
131 1 50       5 if (defined $opts->{initfile}) {
132 0         0 require YAML::Syck;
133 0         0 $self{initfile} = $opts->{initfile};
134             }
135              
136 1         2 foreach my $file (keys %{ $opts->{files} }) {
  1         5  
137 0         0 $self{files}{$file} = $opts->{files}->{$file};
138             }
139              
140 1 50       4 if ($opts->{blocksize}) {
141 0         0 $self{blocksize} = $opts->{blocksize};
142             }
143              
144 1         8 return bless \%self, $class;
145             }
146              
147             sub init {
148 2     2 1 3 my $self = shift;
149              
150 2 50 33     14 if ($self->{initfile} && -r $self->{initfile}) {
151 0         0 $self->{init} = YAML::Syck::LoadFile($self->{initfile});
152 0         0 $self->{time} = delete $self->{init}->{time};
153             } else {
154 2         12 $self->{time} = Time::HiRes::gettimeofday();
155 2         5 $self->{init} = $self->_load;
156             }
157             }
158              
159             sub get {
160 1     1 1 4 my $self = shift;
161 1         4 my $class = ref $self;
162              
163 1 50       9 if (!exists $self->{init}) {
164 0         0 croak "$class: there are no initial statistics defined";
165             }
166              
167 1         7 $self->{stats} = $self->_load;
168 1         9 $self->_deltas;
169              
170 1 50       9 if ($self->{initfile}) {
171 0         0 $self->{init}->{time} = $self->{time};
172 0         0 YAML::Syck::DumpFile($self->{initfile}, $self->{init});
173             }
174              
175 1         9 return $self->{stats};
176             }
177              
178             sub raw {
179 0     0 1 0 my $self = shift;
180 0         0 my $raw = $self->_load;
181              
182 0         0 return $raw;
183             }
184              
185             #
186             # private stuff
187             #
188              
189             sub _load {
190 3     3   6 my $self = shift;
191 3         8 my $class = ref $self;
192 3         7 my $file = $self->{files};
193 3         7 my $bksz = $self->{blocksize};
194 3         3 my (%stats, $fh);
195              
196             # -----------------------------------------------------------------------------
197             # one of the both must be opened for the disk statistics!
198             # if diskstats (2.6) doesn't exists then let's try to read
199             # the partitions (2.4)
200             #
201             # /usr/src/linux/Documentation/iostat.txt shortcut
202             #
203             # ... the statistics fields are those after the device name.
204             #
205             # Field 1 -- # of reads issued
206             # This is the total number of reads completed successfully.
207             # Field 2 -- # of reads merged, field 6 -- # of writes merged
208             # Reads and writes which are adjacent to each other may be merged for
209             # efficiency. Thus two 4K reads may become one 8K read before it is
210             # ultimately handed to the disk, and so it will be counted (and queued)
211             # as only one I/O. This field lets you know how often this was done.
212             # Field 3 -- # of sectors read
213             # This is the total number of sectors read successfully.
214             # Field 4 -- # of milliseconds spent reading
215             # This is the total number of milliseconds spent by all reads (as
216             # measured from __make_request() to end_that_request_last()).
217             # Field 5 -- # of writes completed
218             # This is the total number of writes completed successfully.
219             # Field 7 -- # of sectors written
220             # This is the total number of sectors written successfully.
221             # Field 8 -- # of milliseconds spent writing
222             # This is the total number of milliseconds spent by all writes (as
223             # measured from __make_request() to end_that_request_last()).
224             # Field 9 -- # of I/Os currently in progress
225             # The only field that should go to zero. Incremented as requests are
226             # given to appropriate request_queue_t and decremented as they finish.
227             # Field 10 -- # of milliseconds spent doing I/Os
228             # This field is increases so long as field 9 is nonzero.
229             # Field 11 -- weighted # of milliseconds spent doing I/Os
230             # This field is incremented at each I/O start, I/O completion, I/O
231             # merge, or read of these stats by the number of I/Os in progress
232             # (field 9) times the number of milliseconds spent doing I/O since the
233             # last update of this field. This can provide an easy measure of both
234             # I/O completion time and the backlog that may be accumulating.
235             # -----------------------------------------------------------------------------
236              
237 3 50       15 my $file_diskstats = $file->{path} ? "$file->{path}/$file->{diskstats}" : $file->{diskstats};
238 3 50       11 my $file_partitions = $file->{path} ? "$file->{path}/$file->{partitions}" : $file->{partitions};
239              
240 3 50       173 if (open $fh, '<', $file_diskstats) {
    0          
241 3         1075 while (my $line = <$fh>) {
242             # -- -- -- F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11
243             # $1 $2 $3 $4 -- $5 -- $6 -- $7 -- -- -- --
244 93 50       718 if ($line =~ /^\s+(\d+)\s+(\d+)\s+(.+?)\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+$/) {
    0          
245 93         250 for my $x ($stats{$3}) { # $3 -> the device name
246 93         246 $x->{major} = $1;
247 93         177 $x->{minor} = $2;
248 93         187 $x->{rdreq} = $4; # Field 1
249 93         195 $x->{rdbyt} = $5 * $bksz; # Field 3
250 93         172 $x->{wrtreq} = $6; # Field 5
251 93         171 $x->{wrtbyt} = $7 * $bksz; # Field 7
252 93         192 $x->{ttreq} += $x->{rdreq} + $x->{wrtreq};
253 93         617 $x->{ttbyt} += $x->{rdbyt} + $x->{wrtbyt};
254             }
255             }
256              
257             # -----------------------------------------------------------------------------
258             # Field 1 -- # of reads issued
259             # This is the total number of reads issued to this partition.
260             # Field 2 -- # of sectors read
261             # This is the total number of sectors requested to be read from this
262             # partition.
263             # Field 3 -- # of writes issued
264             # This is the total number of writes issued to this partition.
265             # Field 4 -- # of sectors written
266             # This is the total number of sectors requested to be written to
267             # this partition.
268             # -----------------------------------------------------------------------------
269             # -- -- -- F1 F2 F3 F4
270             # $1 $2 $3 $4 $5 $6 $7
271             elsif ($line =~ /^\s+(\d+)\s+(\d+)\s+(.+?)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/) {
272 0         0 for my $x ($stats{$3}) { # $3 -> the device name
273 0         0 $x->{major} = $1;
274 0         0 $x->{minor} = $2;
275 0         0 $x->{rdreq} = $4; # Field 1
276 0         0 $x->{rdbyt} = $5 * $bksz; # Field 2
277 0         0 $x->{wrtreq} = $6; # Field 3
278 0         0 $x->{wrtbyt} = $7 * $bksz; # Field 4
279 0         0 $x->{ttreq} += $x->{rdreq} + $x->{wrtreq};
280 0         0 $x->{ttbyt} += $x->{rdbyt} + $x->{wrtbyt};
281             }
282             }
283             }
284 3         66 close($fh);
285             } elsif (open $fh, '<', $file_partitions) {
286 0         0 while (my $line = <$fh>) {
287             # -- -- -- -- F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11
288             # $1 $2 -- $3 $4 -- $5 -- $6 -- $7 -- -- -- --
289 0 0       0 next unless $line =~ /^\s+(\d+)\s+(\d+)\s+\d+\s+(.+?)\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+$/;
290 0         0 for my $x ($stats{$3}) { # $3 -> the device name
291 0         0 $x->{major} = $1;
292 0         0 $x->{minor} = $2;
293 0         0 $x->{rdreq} = $4; # Field 1
294 0         0 $x->{rdbyt} = $5 * $bksz; # Field 3
295 0         0 $x->{wrtreq} = $6; # Field 5
296 0         0 $x->{wrtbyt} = $7 * $bksz; # Field 7
297 0         0 $x->{ttreq} += $x->{rdreq} + $x->{wrtreq};
298 0         0 $x->{ttbyt} += $x->{rdbyt} + $x->{wrtbyt};
299             }
300             }
301 0         0 close($fh);
302             } else {
303 0         0 croak "$class: unable to open $file_diskstats or $file_partitions ($!)";
304             }
305              
306 3 50 33     79 if (!-e $file_diskstats || !scalar %stats) {
307 0         0 croak "$class: no diskstats found! your system seems not to be compiled with CONFIG_BLK_STATS=y";
308             }
309              
310 3         63 return \%stats;
311             }
312              
313             sub _deltas {
314 1     1   3 my $self = shift;
315 1         2 my $class = ref $self;
316 1         3 my $istat = $self->{init};
317 1         3 my $lstat = $self->{stats};
318 1         7 my $time = Time::HiRes::gettimeofday();
319 1         20 my $delta = sprintf('%.2f', $time - $self->{time});
320 1         4 $self->{time} = $time;
321              
322 1         2 foreach my $dev (keys %{$lstat}) {
  1         10  
323 31 50       71 if (!exists $istat->{$dev}) {
324 0         0 delete $lstat->{$dev};
325 0         0 next;
326             }
327              
328 31         46 my $idev = $istat->{$dev};
329 31         42 my $ldev = $lstat->{$dev};
330              
331 31         34 while (my ($k, $v) = each %{$ldev}) {
  279         1678  
332 248 100       789 next if $k =~ /^major\z|^minor\z/;
333              
334 186 50       377 if (!defined $idev->{$k}) {
335 0         0 croak "$class: not defined key found '$k'";
336             }
337              
338 186 50 33     1034 if ($v !~ /^\d+\z/ || $ldev->{$k} !~ /^\d+\z/) {
339 0         0 croak "$class: invalid value for key '$k'";
340             }
341              
342 186 100 66     515 if ($ldev->{$k} == $idev->{$k} || $idev->{$k} > $ldev->{$k}) {
    50          
343 170         301 $ldev->{$k} = sprintf('%.2f', 0);
344             } elsif ($delta > 0) {
345 16         90 $ldev->{$k} = sprintf('%.2f', ($ldev->{$k} - $idev->{$k}) / $delta);
346             } else {
347 0         0 $ldev->{$k} = sprintf('%.2f', $ldev->{$k} - $idev->{$k});
348             }
349              
350 186         404 $idev->{$k} = $v;
351             }
352             }
353             }
354              
355             1;