File Coverage

lib/Schedule/Chronic.pm
Criterion Covered Total %
statement 12 160 7.5
branch 0 60 0.0
condition 0 13 0.0
subroutine 4 13 30.7
pod 0 7 0.0
total 16 253 6.3


line stmt bran cond sub pod time code
1             #
2             # Constraint-based, opportunistic scheduler.
3             # Author: Vipul Ved Prakash .
4             # $Id: Chronic.pm,v 1.14 2005/04/26 07:25:34 hackworth Exp $
5             #
6              
7             package Schedule::Chronic;
8 1     1   11678 use base qw(Schedule::Chronic::Base Schedule::Chronic::Tab);
  1         3  
  1         1048  
9 1     1   1051 use Schedule::Chronic::Timer;
  1         3  
  1         38  
10 1     1   547 use Schedule::Chronic::Logger;
  1         3  
  1         28  
11 1     1   7 use Data::Dumper;
  1         2  
  1         1775  
12              
13              
14             sub new {
15              
16 0     0 0   my ($class, %args) = @_;
17 0           my %self = (%args);
18              
19 0   0       $self{safe_sleep} ||= 1; # 1 second
20 0           $self{scheduler_wait} = new Schedule::Chronic::Timer ('down');
21 0   0       $self{var} ||= '/var/run';
22 0           $self{max_sw} = 10 * 60; # 10 minutes
23 0           $self{only_once_tw} = 10 * 365 * 24 * 3600; # 10 years
24 0           $self{logger} = new Schedule::Chronic::Logger (type => $args{logtype});
25 0           $self{nohup} = 0;
26 0           $self{pending_hup} = 0;
27              
28 0 0         unless (exists $self{debug}) {
29 0           $self{debug} = 1;
30             }
31              
32 0           return bless \%self, $class;
33              
34             }
35              
36              
37             sub load_cns_for_schedule {
38              
39 0     0 0   my ($self) = @_;
40              
41 0           for my $task (@{$$self{_schedule}}) {
  0            
42 0 0         unless (exists $task->{_sched_constraints_loaded}) {
43 0           $self->load_cns_for_task($task);
44             }
45             }
46              
47             }
48              
49              
50             sub load_cns_for_task {
51              
52 0     0 0   my ($self, $task) = @_;
53              
54 0           my $constraints = $task->{constraints};
55 0           my $n_objects = 0;
56              
57             my $prep_args = sub {
58              
59 0     0     my $topass = Dumper shift;
60 0           $topass =~ s/\$VAR1 = \[//;
61 0           $topass =~ s/];\s*//g;
62 0           return $topass;
63              
64 0           };
65              
66 0           for my $constraint (keys %$constraints) {
67              
68             # Load the module corresponding to the constraint from
69             # disk. Die with a FATAL error if the module is not
70             # loaded. This behaviour should be configurable through
71             # a data member.
72              
73 0           my $module = "Schedule::Chronic::Constraint::$constraint";
74 0           eval "require $module; use $module";
75 0 0         if ($@) {
76 0           my $error = join'', $@;
77 0 0         if ($error =~ m"Can't locate (\S+)") {
78 0           $self->fatal("Cant' locate constraint module ``$1''");
79             }
80             }
81              
82             # Call the constructor and then the init() method to
83             # pass the constraint object a copy of schedule, task
84             # and thresholds/parameters supplied by the user. Save
85             # the constraint object under the constraint key.
86              
87 0           my $constructor = "$module->new()";
88 0 0         $task->{constraints}->{$constraint}->{_object} = eval $constructor or die $!;
89 0           my $object = $task->{constraints}->{$constraint}->{_object};
90              
91 0           my $init = $object->init (
92             $$self{_schedule}, $task, $$self{logger},
93 0           @{$task->{constraints}->{$constraint}->{thresholds}}
94             );
95              
96 0 0         unless ($init) {
97 0           $self->fatal("init() failed for $module")
98             }
99              
100 0           $n_objects++;
101              
102             }
103              
104             # All's good.
105 0           $self->debug("$n_objects constraint objects created.");
106 0           $task->{_sched_constraints_loaded} = 1;
107             # print Dumper $self;
108              
109 0           return 1;
110              
111             }
112            
113              
114             sub schedule {
115              
116 0     0 0   my $self = shift;
117              
118 0           my $schedule = $$self{_schedule};
119 0           my $scheduler_wait = $$self{scheduler_wait};
120              
121             # A subroutine to compute a scheduler wait, which is the the
122             # smallest of all task waits. We call this routine after
123             # we've run through all tasks at least once. This function
124             # is closed under schedule() so it has access to variables
125             # local to schedule.
126              
127             my $recompute_scheduler_wait = sub {
128              
129 0 0   0     unless (scalar @{$schedule}) {
  0            
130              
131             # Oops, there are no tasks. We'll set wait to
132             # maximum and hope that tasks show up the next time
133             # this function is called.
134              
135 0           $self->debug("no tasks to schedule.");
136 0           $scheduler_wait->set($$self{max_sw});
137 0           $self->debug("scheduler_wait: set to " . $self->time_rd($$self{max_sw}));
138 0           return;
139              
140             }
141              
142 0           my $sw = $schedule->[0]->{_task_wait}->get();
143              
144 0           for my $task (@$schedule) {
145 0 0         if ($$task{_task_wait}->get() < $sw) {
146 0           $sw = $$task{_task_wait}->get();;
147             }
148             }
149              
150 0 0         $sw = $self->{max_sw} if $sw > $self->{max_sw};
151              
152 0 0         if ($sw > 0) {
153 0           $scheduler_wait->set($sw);
154 0           $self->debug("scheduler_wait: set to " . $self->time_rd($sw));
155             }
156              
157 0           };
158            
159 0           $self->debug("entering scheduler loop...");
160              
161 0           while (1) {
162              
163             # Check to see if scheduler_wait is positive. If so,
164             # go to sleep because all task waits are larger than
165             # scheduler_wait.
166              
167 0 0         if ($scheduler_wait->get() > 0) {
168 0           $self->debug("nothing to schedule for " .
169             $self->time_rd($scheduler_wait->get()) . ", sleeping...");
170 0           sleep($scheduler_wait->get());
171             }
172              
173             # Walk over all tasks, checks constraints and execute tasks when
174             # all constraints are met. This is section should end in
175              
176             TASK:
177 0           for my $task (@$schedule) {
178              
179             # print Dumper $task;
180              
181             # A task has four components. A set of constraints, a
182             # command to run when these constraints are met, the
183             # last_ran time and a task wait timer which is the
184             # maximum wait time returned by a constraint.
185              
186 0           my $constraints = $$task{constraints};
187 0           my $task_wait = $$task{_task_wait};
188 0           my $command = $$task{command};
189 0           my $last_ran = $$task{last_ran};
190 0           my $uid = $$task{_uid};
191 0           my $only_once = $$task{only_once};
192              
193 0 0 0       if ($last_ran > 0 and $only_once == 1) {
194              
195             # This task was supposed to run ``only_once'' and it has
196             # been run once before, so we will skip it.
197              
198 0           $task_wait->set($$self{only_once_tw});
199 0           next TASK;
200              
201             }
202              
203 0           $self->debug("* $command");
204              
205 0 0         if ($task_wait->get() > 0) {
206              
207             # Constraints have indicated that they will not be met for
208             # at least sched_wait seconds.
209              
210 0           $self->debug(" task_wait: " . $self->time_rd($task_wait->get()));
211 0           next TASK;
212              
213             };
214              
215 0           my $all_cns_met = 1;
216              
217 0           for my $constraint (keys %$constraints) {
218              
219             # A constraint has two declarative components and a few
220             # derived components. The declarative components are the
221             # name of the constraint and the thresholds that
222             # parameterize the constraint. The derived components
223             # include the corresponding constraint object and other
224             # transient data structures used by the scheduler.
225              
226 0           my $cobject = $task->{constraints}->{$constraint}->{_object};
227              
228             # Now call met() and wait()
229              
230 0           my ($met) = $cobject->met();
231 0           my ($wait) = $cobject->wait();
232              
233 0 0         if (not $met) {
234              
235             # The constraint wasn't met. We'll set all_cns_met to
236             # 0 and compare constraint wait with task_wait to see
237             # if we need to readjust task_wait.
238              
239 0           $self->debug(" ($constraint) unmet");
240 0           $all_cns_met = 0;
241              
242 0 0 0       if ($wait != 0 && $wait > $task_wait->get()) {
243              
244             # Task wait is largest of all constraint waits.
245              
246 0           $self->debug(" ($constraint) won't be met for " . $self->time_rd($wait));
247 0           $task_wait->set($wait);
248              
249             }
250              
251             } else {
252            
253             # The constraint has been met. Add a log notification.
254             # We don't need to do anything. If all constraints are
255             # met, all_cns_set will remain set to 1.
256              
257 0           $self->debug(" ($constraint) met");
258            
259             }
260              
261             } # for - iterate over constraints
262              
263 0 0         if ($all_cns_met) {
264              
265             # All constraints met: the task is ready to run.
266              
267             # Set nohup to 1. Tells the SIGNAL handler that
268             # this is not a good time for a HUP. If we
269             # receive a HUP during system(), the handler
270             # will record this in $self->{pending_hup} so we
271             # can replay the signal after system() is done.
272              
273 0           $self->{nohup} = 1;
274              
275 0           my $now = time();
276 0           $$task{_previous_run} = $now - $$task{last_ran};
277 0           $$task{last_ran} = $now;
278 0           my $rv = system($$task{command});
279 0           $$task{_last_rv} = $rv;
280              
281             # Write the chrontab with updated last_ran value for the
282             # task only if the task is not an ``only_once'' task.
283              
284 0           $self->write_chrontab($$task{_chrontab});
285            
286             # Notify the email address.
287 0 0         if ($$task{notify}) {
288 0           $self->notify($task, time() - $$task{last_ran});
289             }
290              
291 0           $self->{nohup} = 0;
292 0 0         if ($self->{pending_hup}) {
293              
294             # If there got a HUP during system();
295             # replay it now.
296              
297 0           $self->debug("replaying HUP signal sent earlier");
298 0           kill(1, $$self{pid});
299             }
300            
301             }
302            
303             } # for - iterate over tasks
304              
305             # Compute the schedular wait before going through the next
306             # cycle. Scheduler wait is set only if the largest
307             # task_wait is > 0.
308              
309 0           &$recompute_scheduler_wait();
310              
311             # We'll do a one second sleep here so we don't cycle out
312             # of control when there's a mismatch between task_wait's
313             # and the scheduler_wait.
314              
315 0           sleep ($self->{safe_sleep});
316              
317             } # while - scheduler loop
318              
319             }
320              
321              
322             sub getpid {
323              
324 0     0 0   my ($self) = @_;
325 0           $self->{pid} = $$;
326              
327             }
328              
329              
330             sub notify {
331              
332 0     0 0   my ($self, $task, $time) = @_;
333              
334             # Sometimes /usr/lib won't be in path, so we look there first before
335             # calling which()
336              
337 0 0         my $success = $$task{_last_rv} == 0 ? 1 : 0;
338              
339 0           my $sendmail_path = '/usr/lib/sendmail';
340 0 0         unless (-e $sendmail_path) {
341 0           $sendmail_path = $self->which('sendmail');
342             }
343              
344 0 0         unless ($sendmail_path) {
345 0           $self->debug("``sendmail'' not found, can't notify");
346 0           return;
347             }
348              
349 0           $self->debug(" sending notification to $$task{notify}");
350              
351 0           my $template;
352              
353             # Headers
354              
355 0           $template .= "From: chronic\@localhost\n"; # FIX. username@host
356 0           $template .= "To: $$task{notify}\n";
357 0 0         $template .= "Subject: [Chronic] Success: $$task{command}\n\n" if $success;
358 0 0         $template .= "Subject: [Chronic] Failure: $$task{command}\n\n" unless $success;
359              
360             # Body
361              
362 0 0         $template .= "Task executed successfully.\n\n" if $success;
363 0 0         $template .= "\nTask failed.\n\n" unless $success;
364 0           $template .= sprintf("%20s: %s\n", "Task", $$task{command});
365 0           $template .= sprintf("%20s: %s\n", "Executed at", scalar localtime());
366 0           $template .= sprintf("%20s: %s\n", "Run time", $self->time_rd($time) . ".");
367 0           $template .= sprintf("%20s: %s\n", "Return Value", $$task{_last_rv});
368 0           $template .= sprintf("%20s: %s\n", "UID", $$task{_uid});
369 0 0 0       $template .= sprintf("%20s: %s\n", "Previous run", $self->time_rd($$task{_previous_run}) . " ago.")
370             if exists $$task{_previous_run} and $$task{only_once} == 0;
371 0 0         $template .= "\nThis was an ``only_once'' task. It won't be rescheduled.\n" if $$task{only_once};
372 0           $template .= "\nVirtually yours,\nChronic\n";
373              
374 0           open(SENDMAIL, "| $sendmail_path $$task{notify}");
375 0           print SENDMAIL $template;
376 0           print SENDMAIL ".\n";
377            
378 0           close SENDMAIL;
379              
380 0           return $self;
381              
382             }
383              
384              
385             sub time_rd {
386              
387 0     0 0   my ($self, $seconds) = @_;
388              
389 0 0         if ($seconds > 3600) {
    0          
390 0           my $hours = $seconds / 3600;
391 0 0         if ($hours > 24) {
392 0           return sprintf("%.2f days", $hours/24);
393             } else {
394 0           return sprintf("%.2f hours", $hours);
395             }
396             } elsif ($seconds > 60) {
397 0           return sprintf("%.1f minutes", $seconds/60);
398             }
399              
400 0           return "$seconds seconds";
401              
402             }
403              
404              
405             1;
406