File Coverage

blib/lib/App/MultiModule/Tasks/ResourceWatcher.pm
Criterion Covered Total %
statement 26 111 23.4
branch 0 36 0.0
condition 0 2 0.0
subroutine 9 18 50.0
pod 3 3 100.0
total 38 170 22.3


line stmt bran cond sub pod time code
1             package App::MultiModule::Tasks::ResourceWatcher;
2             $App::MultiModule::Tasks::ResourceWatcher::VERSION = '1.161190';
3 2     2   1042 use 5.006;
  2         8  
4 2     2   6 use strict;
  2         2  
  2         38  
5 2     2   6 use warnings FATAL => 'all';
  2         2  
  2         56  
6 2     2   6 use Data::Dumper;
  2         2  
  2         90  
7 2     2   6 use Message::Transform qw(mtransform);
  2         2  
  2         68  
8 2     2   846 use P9Y::ProcessTable;
  2         160852  
  2         54  
9 2     2   12 use Storable;
  2         2  
  2         186  
10 2     2   10 use POSIX ":sys_wait_h";
  2         4  
  2         10  
11              
12 2     2   256 use parent 'App::MultiModule::Task';
  2         4  
  2         12  
13              
14             =head1 NAME
15              
16             App::MultiModule::Tasks::ResourceWatcher - Manage process resources under App::MultiModule
17              
18             =cut
19              
20             =head2 message
21              
22             =cut
23              
24             sub message {
25 0     0 1   my $self = shift;
26 0           my $message = shift;
27 0           my %args = @_;
28             $self->debug('message', message => $message)
29 0 0         if $self->{debug} > 5;
30 0           my $state = $self->{state};
31 0           my $state_watches = $state->{watches};
32 0 0         if($message->{watches}) {
33 0           mtransform($state_watches, $message->{watches});
34             }
35             }
36              
37             sub _get_processes {
38 0     0     my $self = shift;
39 0           my $ret = {};
40 0           my $ts = time;
41 0           foreach my $process (P9Y::ProcessTable->table) {
42 0           $process->{process_uptime} = $ts - $process->{start};
43 0           $ret->{$process->{pid}} = $process;
44             }
45 0           return $ret;
46             }
47              
48             sub _fire {
49 0     0     my $self = shift;
50 0           my $level = shift;
51 0           my $watch_name = shift;
52 0           my $pid = shift;
53             my $message = {
54             resourceWatcher_level => $level->{level_number},
55 0           watch_name => $watch_name,
56             };
57 0           delete $level->{level_number};
58 0           $message->{resourceWatcher} = Storable::dclone($level);
59             mtransform($message, $level->{transform})
60 0 0         if $level->{transform};
61 0 0         if(my $actions = $level->{actions}) {
62 0 0         if($actions->{signal}) {
63             #we could look at the return value of kill, but if it's zero,
64             #that just means that the process exited beween the time we
65             #gathered all of the processes and now, which is something that
66             #will happen from time to time and isn't notable
67 0           kill $actions->{signal}, $pid;
68 0           $message->{resourceWatcher_signal_sent} = $actions->{signal};
69             } else {
70 0           $self->error("App::MultiModule::Tasks::ResourceWatcher::_fire: called action must currently have a signal attribute. \$watch_name=$watch_name \$level_number=$message->{resourceWatcher_level}");
71             }
72             }
73 0           $self->emit($message);
74             }
75              
76             sub _tick {
77 0     0     my $self = shift;
78 0           my $watches = Storable::dclone($self->{config}->{watches});
79 0           my $state_watches = $self->{state}->{watches};
80 0           mtransform($watches, $state_watches);
81 0   0       my $timeout = $self->{config}->{tick_timeout} || 1;
82 0           eval {
83 0     0     local $SIG{ALRM} = sub { die "timed out\n"; };
  0            
84 0           alarm $timeout;
85 0           my $processes = $self->_get_processes;
86              
87             WATCH:
88 0           foreach my $watch_name (keys %$watches) {
89 0           my $watch = $watches->{$watch_name};
90 0 0         $watch->{levels} = {} unless $watch->{levels};
91 0 0         if(my $pid = $watch->{resourceWatcher_PID}) {
92 0           my $process_info = $processes->{$pid};
93 0 0         if(not $process_info) { #the process is gone
94 0 0         if($watch->{no_process}) {
95 0           my $message = {
96             watch_name => $watch_name,
97             };
98             mtransform($message, $watch->{no_process}->{transform})
99 0 0         if $watch->{no_process}->{transform};
100 0           $self->emit($message);
101             }
102 0           delete $state_watches->{$watch_name};
103 0           next WATCH;
104             }
105             #sort numerically descending
106             LEVEL:
107 0           foreach my $level_number (sort { $b <=> $a } keys %{$watch->{levels}}) {
  0            
  0            
108 0           my $level = $watch->{levels}->{$level_number};
109 0           $level->{level_number} = $level_number;
110 0 0         if(my $floor = $level->{floor}) {
111 0           my $fire = 1;
112 0           foreach my $floor_field (keys %$floor) {
113 0 0         if(not defined $process_info->{$floor_field}) {
114 0           $self->error("App::MultiModule::Tasks::ResourceWatcher::_tick: referenced floor_field does not exist in process_info \$watch_name=$watch_name \$level_number=$level_number \$floor_field=$floor_field \$process_info=" . Data::Dumper::Dumper $process_info);
115 0           last;
116             }
117             #we will not fire if any field in the process
118             #is below the defined floor
119 0 0         if($process_info->{$floor_field} < $floor->{$floor_field}) {
120 0           $fire = 0;
121             }
122             }
123 0 0         if($fire) {
124 0           $self->_fire($level, $watch_name, $pid);
125 0           last LEVEL;
126             }
127             } else {
128 0           $self->error("App::MultiModule::Tasks::ResourceWatcher::_tick: we currently require each level of each watch to have a floor field \$watch_name=$watch_name \$level_number=$level_number");
129             }
130             }
131             } else {
132 0           $self->error("App::MultiModule::Tasks::ResourceWatcher::_tick: we currently require each watch to have a resourceWatcher_PID field \$watch_name=$watch_name");
133             }
134             }
135             };
136 0 0         if($@) {
137 0           $self->error("App::MultiModule::Tasks::ResourceWatcher::_tick: general exception: $@");
138             }
139 0           alarm 0;
140             }
141             =head1 cut
142             $VAR1 = [
143             bless( {
144             '_pt_obj' => bless( {}, 'P9Y::ProcessTable::Table' ),
145             'priority' => '20',
146             'uid' => 0,
147             'sess' => '1',
148             'environ' => {
149             'PATH' => '/sbin:/usr/sbin:/bin:/usr/bin',
150             'recovery' => '',
151             },
152             'majflt' => 54,
153             'cwd' => '/'
154             }, 'P9Y::ProcessTable::Process' ),
155             bless( {
156             =cut
157              
158              
159             =head2 set_config
160              
161             =cut
162             sub set_config {
163 0     0 1   my $self = shift;
164 0           my $config = shift;
165 0           $self->{config} = $config;
166 0 0         $self->{config}->{watches} = {} unless $self->{config}->{watches};
167 0 0         $self->{state} = {} unless $self->{state};
168 0 0         $self->{state}->{watches} = {} unless $self->{state}->{watches};
169             $self->named_recur(
170             recur_name => 'ResourceWatcher_reap-zombies',
171             repeat_interval => 1,
172             work => sub {
173 0     0     my $kid;
174 0           do {
175 0           $kid = waitpid(-1, WNOHANG);
176             } while $kid > 0;
177             }
178 0           );
179             $self->named_recur(
180             recur_name => 'ResourceWatcher_tick',
181             repeat_interval => 1,
182             work => sub {
183 0     0     $self->_tick;
184             }
185 0           );
186             }
187              
188             =head2 is_stateful
189              
190             =cut
191             sub is_stateful {
192 0     0 1   return 'TODO: maybe?';
193             }
194              
195             =head1 AUTHOR
196              
197             Dana M. Diederich, C<< <dana@realms.org> >>
198              
199             =head1 BUGS
200              
201             Please report any bugs or feature requests through L<https://github.com/dana/perl-App-MultiModule-Tasks-ResourceWatcher/issues>. I will be notified, and then you'll
202             automatically be notified of progress on your bug as I make changes.
203              
204             =head1 SUPPORT
205              
206             You can find documentation for this module with the perldoc command.
207              
208             perldoc App::MultiModule::Tasks::ResourceWatcher
209              
210              
211             You can also look for information at:
212              
213             =over 4
214              
215             =item * Report bugs here:
216              
217             L<https://github.com/dana/perl-App-MultiModule-Tasks-ResourceWatcher/issues>
218              
219             =item * AnnoCPAN: Annotated CPAN documentation
220              
221             L<http://annocpan.org/dist/App-MultiModule-Tasks-ResourceWatcher>
222              
223             =item * CPAN Ratings
224              
225             L<http://cpanratings.perl.org/d/App-MultiModule-Tasks-ResourceWatcher>
226              
227             =item * Search CPAN
228              
229             L<https://metacpan.org/module/App::MultiModule::Tasks::ResourceWatcher>
230              
231             =back
232              
233             =head1 ACKNOWLEDGEMENTS
234              
235             =head1 LICENSE AND COPYRIGHT
236              
237             Copyright 2016 Dana M. Diederich.
238              
239             This program is free software; you can redistribute it and/or modify it
240             under the terms of the the Artistic License (2.0). You may obtain a
241             copy of the full license at:
242              
243             L<http://www.perlfoundation.org/artistic_license_2_0>
244              
245             Any use, modification, and distribution of the Standard or Modified
246             Versions is governed by this Artistic License. By using, modifying or
247             distributing the Package, you accept this license. Do not use, modify,
248             or distribute the Package, if you do not accept this license.
249              
250             If your Modified Version has been derived from a Modified Version made
251             by someone other than you, you are nevertheless required to ensure that
252             your Modified Version complies with the requirements of this license.
253              
254             This license does not grant you the right to use any trademark, service
255             mark, tradename, or logo of the Copyright Holder.
256              
257             This license includes the non-exclusive, worldwide, free-of-charge
258             patent license to make, have made, use, offer to sell, sell, import and
259             otherwise transfer the Package with respect to any patent claims
260             licensable by the Copyright Holder that are necessarily infringed by the
261             Package. If you institute patent litigation (including a cross-claim or
262             counterclaim) against any party alleging that the Package constitutes
263             direct or contributory patent infringement, then this Artistic License
264             to you shall terminate on the date that such litigation is filed.
265              
266             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
267             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
268             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
269             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
270             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
271             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
272             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
273             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
274              
275              
276             =cut
277              
278             1; # End of App::MultiModule::Tasks::ResourceWatcher