File Coverage

blib/lib/Zabbix/Check/Disk.pm
Criterion Covered Total %
statement 28 142 19.7
branch 0 64 0.0
condition 0 30 0.0
subroutine 9 16 56.2
pod 0 3 0.0
total 37 255 14.5


line stmt bran cond sub pod time code
1             package Zabbix::Check::Disk;
2             =head1 NAME
3              
4             Zabbix::Check::Disk - Zabbix check for disk
5              
6             =head1 VERSION
7              
8             version 1.10
9              
10             =head1 SYNOPSIS
11              
12             Zabbix check for disk
13              
14             UserParameter=cpan.zabbix.check.disk.discovery,/usr/bin/perl -MZabbix::Check::Disk -e_discovery
15             UserParameter=cpan.zabbix.check.disk.bps[*],/usr/bin/perl -MZabbix::Check::Disk -e_bps -- $1 $2
16             UserParameter=cpan.zabbix.check.disk.iops[*],/usr/bin/perl -MZabbix::Check::Disk -e_iops -- $1 $2
17             UserParameter=cpan.zabbix.check.disk.ioutil[*],/usr/bin/perl -MZabbix::Check::Disk -e_ioutil -- $1
18              
19             =head3 discovery
20              
21             discovers disks
22              
23             =head3 bps $1 $2
24              
25             gets disk I/O traffic in bytes per second
26              
27             $1: I
28              
29             $2: I
30              
31             =head3 iops $1 $2
32              
33             gets disk I/O transaction speed in transactions per second
34              
35             $1: I
36              
37             $2: I
38              
39             =head3 ioutil $1 $2
40              
41             gets disk I/O utilization in percentage
42              
43             $1: I
44              
45             =cut
46 1     1   967 use strict;
  1         3  
  1         39  
47 1     1   4 use warnings;
  1         1  
  1         24  
48 1     1   3 no warnings qw(qw utf8);
  1         2  
  1         31  
49 1     1   8 use v5.14;
  1         3  
50 1     1   5 use utf8;
  1         2  
  1         4  
51 1     1   17 use File::Slurp;
  1         1  
  1         47  
52 1     1   3 use JSON;
  1         1  
  1         4  
53              
54 1     1   82 use Zabbix::Check;
  1         1  
  1         118  
55              
56              
57             BEGIN
58             {
59 1     1   5 require Exporter;
60             # set the version for version checking
61 1         1 our $VERSION = '1.10';
62             # Inherit from Exporter to export functions and variables
63 1         8 our @ISA = qw(Exporter);
64             # Functions and variables which are exported by default
65 1         3 our @EXPORT = qw(_discovery _bps _iops _ioutil);
66             # Functions and variables which can be optionally exported
67 1         1549 our @EXPORT_OK = qw();
68             }
69              
70              
71             sub disks
72             {
73 0     0 0   my $result = {};
74 0           for my $blockpath (glob("/sys/dev/block/*"))
75             {
76 0 0         next unless -f "$blockpath/uevent";
77 0           my $uevent = read_file("$blockpath/uevent", { err_mode => "quiet" });
78 0           my ($major) = $uevent =~ /^\QMAJOR=\E(.*)/m;
79 0           my ($minor) = $uevent =~ /^\QMINOR=\E(.*)/m;
80 0           my ($devname) = $uevent =~ /^\QDEVNAME=\E(.*)/m;
81 0           my ($devtype) = $uevent =~ /^\QDEVTYPE=\E(.*)/m;
82 0           my $devpath = "/dev/$devname";
83 0 0 0       my $disk = {
    0 0        
    0 0        
84             blockpath => $blockpath,
85             devname => $devname,
86             devtype => $devtype,
87             devpath => $devpath,
88             major => $major,
89             minor => $minor,
90             size => (-f "$blockpath/size" and $_ = read_file("$blockpath/size", { err_mode => "quiet" }))? int(s/^\s+|\s+$//gr)*512: undef,
91             removable => (-f "$blockpath/removable" and $_ = read_file("$blockpath/removable", { err_mode => "quiet" }))? s/^\s+|\s+$//gr: undef,
92             partition => (-f "$blockpath/partition" and $_ = read_file("$blockpath/partition", { err_mode => "quiet" }))? s/^\s+|\s+$//gr: undef,
93             dmname => undef,
94             dmpath => undef,
95             };
96 0 0 0       if (-f "$blockpath/dm/name" and my $dmname = read_file("$blockpath/dm/name", { err_mode => "quiet" }))
97             {
98 0           chomp $dmname;
99 0           $disk->{dmname} = $dmname;
100 0           $disk->{dmpath} = "/dev/mapper/$dmname";
101             }
102 0 0         my $dmpath = $disk->{dmpath}? $disk->{dmpath}: "";
103 0 0         for my $mount (grep(/^(\Q$disk->{devpath}\E|\Q$dmpath\E)\s+/, (-f "/proc/mounts")? read_file("/proc/mounts", { err_mode => "quiet" }): ()))
104             {
105 0           chomp $mount;
106 0           my ($devpath, $mountpoint, $fstype) = $mount =~ /^(\S+)\s+(\S+)\s+(\S+)\s+/;
107 0           $disk->{fstype} = $fstype;
108             }
109 0           $result->{$devname} = $disk;
110             }
111 0           return $result;
112             }
113              
114             sub stats
115             {
116 0     0 0   my $result = {};
117 0           my $disks = disks();
118 0           for my $devname (keys %$disks)
119             {
120 0           my $disk = $disks->{$devname};
121 0 0         next unless -f "$disk->{blockpath}/stat";
122 0           my $statLine = read_file("$disk->{blockpath}/stat", { err_mode => "quiet" });
123 0 0         next unless $statLine;
124 0           chomp $statLine;
125 0           my $stat = { 'epoch' => time() };
126             (
127             $stat->{readIOs},
128             $stat->{readsMerges},
129             $stat->{readSectors},
130             $stat->{readWaits},
131             $stat->{writeIOs},
132             $stat->{writesMerges},
133             $stat->{writeSectors},
134             $stat->{writeWaits},
135             $stat->{inFlight},
136             $stat->{IOTicks},
137             $stat->{totalWaits},
138 0           ) = $statLine =~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/;
139 0           $result->{$devname} = $stat;
140             }
141 0           return $result;
142             }
143              
144             sub analyzeStats
145             {
146 0     0 0   my $now = time();
147 0           my $stats;
148             my $oldStats;
149 0           my $tmpPrefix = "/tmp/".(caller(0))[3] =~ s/\Q::\E/-/gr.",stats,";
150 0           for my $tmpPath (sort {$b cmp $a} glob("$tmpPrefix*"))
  0            
151             {
152 0 0         if (my ($epoch, $pid) = $tmpPath =~ /^\Q$tmpPrefix\E(\d*)\.(\d*)/)
153             {
154 0 0         if ($now-$epoch < 1*60)
155             {
156 0 0         if (not $stats)
157             {
158 0           my $tmp = read_file($tmpPath, { err_mode => "quiet" });
159 0 0         eval { $stats = from_json($tmp) } if $tmp;
  0            
160             }
161 0           next;
162             }
163 0 0         if (not $oldStats)
164             {
165 0           my $tmp = read_file($tmpPath, { err_mode => "quiet" });
166 0 0         eval { $oldStats = from_json($tmp) } if $tmp;
  0            
167 0 0 0       next unless not $tmp or $@;
168             }
169 0 0         next unless $now-$epoch > 2*60;
170             }
171 0           unlink($tmpPath);
172             }
173 0 0         unless ($stats)
174             {
175 0           $stats = stats();
176 0           my $tmp;
177 0           eval { $tmp = to_json($stats, {pretty => 1}) };
  0            
178 0 0         write_file("$tmpPrefix$now.$$", { err_mode => "quiet" }, $tmp) if $tmp;
179             }
180 0 0         return unless $oldStats;
181 0           my $result = {};
182 0           for my $devname (keys %$stats)
183             {
184 0           my $stat = $stats->{$devname};
185 0           my $oldStat = $oldStats->{$devname};
186 0 0         next unless defined $oldStat;
187 0           my $diff = $stat->{epoch} - $oldStat->{epoch};
188 0 0         next unless $diff;
189 0           $result->{$devname} = {};
190              
191 0           my $sector;
192             my $io;
193              
194 0           $sector = $stat->{readSectors} - $oldStat->{readSectors};
195 0           $io = $stat->{readIOs} - $oldStat->{readIOs};
196 0           $result->{$devname}->{bps_read} = 512*$sector/$diff;
197 0           $result->{$devname}->{iops_read} = $io/$diff;
198              
199 0           $sector = $stat->{writeSectors} - $oldStat->{writeSectors};
200 0           $io = $stat->{writeIOs} - $oldStat->{writeIOs};
201 0           $result->{$devname}->{bps_write} = 512*$sector/$diff;
202 0           $result->{$devname}->{iops_write} = $io/$diff;
203              
204 0           $sector = $stat->{readSectors} - $oldStat->{readSectors} + $stat->{writeSectors} - $oldStat->{writeSectors};
205 0           $io = $stat->{readIOs} - $oldStat->{readIOs} + $stat->{writeIOs} - $oldStat->{writeIOs};
206 0           $result->{$devname}->{bps_total} = 512*$sector/$diff;
207 0           $result->{$devname}->{iops_total} = $io/$diff;
208              
209 0           $result->{$devname}->{ioutil} = 100*($stat->{IOTicks} - $oldStat->{IOTicks})/(1000*$diff);
210             }
211 0           return $result;
212             }
213              
214             sub _discovery
215             {
216 0     0     my ($removable) = @_;
217 0           my @items;
218 0           my $disks = disks();
219 0           for my $devname (keys %$disks)
220             {
221 0           my $disk = $disks->{$devname};
222 0 0 0       next if not $removable and $disk->{removable};
223 0           push @items, $disk;
224             }
225 0           return printDiscovery(@items);
226             }
227              
228             sub _bps
229             {
230 0     0     my ($devname, $type) = map(zbxDecode($_), @ARGV);
231 0 0 0       return unless $devname and $type and $type =~ /^read|write|total$/;
      0        
232 0           my $result = 0;
233 0           my $analyzed = analyzeStats();
234 0 0         my $status = $analyzed->{$devname} if $analyzed;
235 0 0         $result = sprintf("%.2f", $status->{"bps_$type"}) if $status;
236 0           print $result;
237 0           return $result;
238             }
239              
240             sub _iops
241             {
242 0     0     my ($devname, $type) = map(zbxDecode($_), @ARGV);
243 0 0 0       return unless $devname and $type and $type =~ /^read|write|total$/;
      0        
244 0           my $result = 0;
245 0           my $analyzed = analyzeStats();
246 0 0         my $status = $analyzed->{$devname} if $analyzed;
247 0 0         $result = sprintf("%.2f", $status->{"iops_$type"}) if $status;
248 0           print $result;
249 0           return $result;
250             }
251              
252             sub _ioutil
253             {
254 0     0     my ($devname) = map(zbxDecode($_), @ARGV);
255 0 0         return unless $devname;
256 0           my $result = 0;
257 0           my $analyzed = analyzeStats();
258 0 0         my $status = $analyzed->{$devname} if $analyzed;
259 0 0         $result = sprintf("%.2f", $status->{"ioutil"}) if $status;
260 0           print $result;
261 0           return $result;
262             }
263              
264              
265             1;
266             __END__