File Coverage

blib/lib/App/MultiModule/Tasks/ResourceWatcher.pm
Criterion Covered Total %
statement 23 107 21.5
branch 0 36 0.0
condition 0 2 0.0
subroutine 8 16 50.0
pod 3 3 100.0
total 34 164 20.7


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