File Coverage

blib/lib/Daemon/Shutdown/Monitor/hdparm.pm
Criterion Covered Total %
statement 18 65 27.6
branch 0 16 0.0
condition 0 6 0.0
subroutine 6 9 66.6
pod 2 2 100.0
total 26 98 26.5


line stmt bran cond sub pod time code
1             package Daemon::Shutdown::Monitor::hdparm;
2              
3 1     1   1165 use warnings;
  1         3  
  1         39  
4 1     1   6 use strict;
  1         2  
  1         28  
5 1     1   6 use Params::Validate qw/:all/;
  1         2  
  1         232  
6 1     1   7 use IPC::Run;
  1         2  
  1         45  
7 1     1   6 use YAML::Any;
  1         2  
  1         11  
8 1     1   1375 use Log::Log4perl;
  1         2  
  1         11  
9              
10             =head1 NAME
11              
12             Daemon::Shutdown::Monitor::hdparm - a hdparm specific monitor
13              
14             =head1 SYNOPSIS
15              
16             Monitor hard disk spindown state using hdparm
17              
18             =head1 DESCRIPTION
19              
20             Tests the spin state of all the disks listed in "disks" every "loop_sleep". When all disks
21             are in spun down state, the flag "trigger_pending" is set. If a further "trigger_time" seconds
22             pass and all disks are still in a spun down state, the trigger is sent back to the parent
23             process (return 1).
24              
25             =head1 METHODS
26              
27             =head2 new
28              
29             =over 2
30              
31             =item loop_sleep
32              
33             How long to sleep between each test
34              
35             Default: 60 (1 minute)
36              
37             =item disks
38              
39             An array of disks to be tested. e.g. /dev/sda
40              
41             Default: [ '/dev/sda' ]
42              
43             =item trigger_time
44              
45             The time to wait after discovering that all disks are spun down before returning (trigger a shutdown).
46              
47             Default: 3600 (1 hour)
48              
49             =item use_sudo 1|0
50              
51             Use sudo for hdparm
52            
53             sudo hdparm -C /dev/sda
54              
55             Default: 0
56              
57             =back
58              
59             =head3 Example configuration
60            
61             monitor:
62             hdparm:
63             trigger_time: 1800
64             loop_sleep: 1
65             use_sudo: 0
66             disks:
67             - /dev/sdb
68             - /dev/sdc
69             - /dev/sdd
70              
71             =cut
72              
73             sub new {
74 0     0 1   my $class = shift;
75 0           my %params = @_;
76              
77             # Validate the config file
78             %params = validate_with(
79             params => \%params,
80             spec => {
81             loop_sleep => {
82             regex => qr/^\d*$/,
83             default => 60,
84             },
85             trigger_time => {
86             regex => qr/^\d*$/,
87             default => 3600,
88             },
89             disks => {
90             type => ARRAYREF,
91             default => ['/dev/sda'],
92             callbacks => {
93             'Disks exist' => sub {
94 0     0     my $disks_ref = shift();
95 0           foreach my $disk ( @{$disks_ref} ) {
  0            
96 0 0         return 0 if !-e $disk;
97             }
98 0           return 1;
99             },
100             },
101             },
102 0           use_sudo => {
103             default => 0,
104             regex => qr/^[1|0]$/,
105             },
106             },
107             );
108 0           my $self = {};
109 0           $self->{params} = \%params;
110              
111 0           $self->{trigger_pending} = 0;
112              
113 0           bless $self, $class;
114 0           my $logger = Log::Log4perl->get_logger();
115 0           $self->{logger} = $logger;
116 0           $logger->debug( "Monitor 'hdparm' params:\n" . Dump( \%params ) );
117              
118 0           return $self;
119             }
120              
121             =head2 run
122              
123             Run the hdparm spindown Monitor
124              
125             =cut
126              
127             sub run {
128 0     0 1   my $self = shift;
129              
130 0           my $logger = $self->{logger};
131              
132 0           $logger->info( "Monitor started running: hdparm" );
133              
134 0           my $conditions_met = 1;
135              
136             # Test each disk
137 0           foreach my $disk ( @{ $self->{params}->{disks} } ) {
  0            
138 0           $logger->debug( "Monitor hdparm testing $disk" );
139 0           my @cmd = ( qw/hdparm -C/, $disk );
140 0 0         if ( $self->{params}->{use_sudo} ) {
141 0           unshift( @cmd, 'sudo' );
142             }
143 0           $logger->debug( "Monitor hdparm CMD: " . join( ' ', @cmd ) );
144 0           my ( $in, $out, $err );
145 0 0         if ( not IPC::Run::run( \@cmd, \$in, \$out, \$err, IPC::Run::timeout( 10 ) ) ) {
146 0           $logger->warn( "Could not run '" . join( ' ', @cmd ) . "': $!" );
147             }
148 0 0         if ( $err ) {
149 0           $logger->error( "Monitor hdparm: $err" );
150 0           $conditions_met = 0;
151             }
152              
153             # If any of the disks are active, the conditions for trigger are not met
154 0 0         if ( $out =~ m/drive state is: active/s ) {
155 0           $logger->debug( "Monitor hdparm sees disk is active: $disk" );
156 0           $conditions_met = 0;
157             }
158             }
159              
160 0 0         if ( $conditions_met ) {
161              
162             # All disks are spun down! Set the trigger_pending time.
163 0   0       $self->{trigger_pending} ||= time();
164 0 0 0       if ( $self->{trigger_pending}
165             and ( time() - $self->{trigger_pending} ) >= $self->{params}->{trigger_time} )
166             {
167              
168             # ... and the trigger was set, and time has run out: time to return!
169 0           $logger->info( "Monitor hdparm trigger time reached after $self->{params}->{trigger_time}" );
170             # Reset the trigger_pending because otherwise if this was a suspend, and the computer comes
171             # up again hours/days later, it will immediately fall asleep again...
172 0           $self->{trigger_pending} = 0;
173 0           return 1;
174             }
175              
176 0           $logger->info( "Monitor hdparm found all disks spun down: trigger pending." );
177             } else {
178 0 0         if ( $self->{trigger_pending} ) {
179 0           $logger->info( "Monitor hdparm trigger time being reset because of disk activity" );
180             }
181              
182             # Conditions not met - reset the trigger incase it was previously set.
183 0           $self->{trigger_pending} = 0;
184             }
185 0           return 0;
186             }
187              
188             =head1 AUTHOR
189              
190             Robin Clarke, C
191              
192             =head1 LICENSE AND COPYRIGHT
193              
194             Copyright 2015 Robin Clarke.
195              
196             This program is free software; you can redistribute it and/or modify it
197             under the terms of either: the GNU General Public License as published
198             by the Free Software Foundation; or the Artistic License.
199              
200             See http://dev.perl.org/licenses/ for more information.
201              
202             =cut
203              
204             1; # End of Daemon::Shutdown::Monitor::hdparm