1 # Copyright (C) 2002 Stanislav Sinyagin
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17 # $Id: Scheduler.pm,v 1.1 2010-12-27 00:03:39 ivan Exp $
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
22 # Task object MUST implement two methods:
23 # run() -- the running cycle
24 # whenNext() -- returns the next time it must be run.
25 # See below the Torrus::Scheduler::PeriodicTask class definition
29 # -ProcessName => process name and commandline options
30 # -RunOnce => 1 -- this prevents from infinite loop.
33 package Torrus::Scheduler;
36 use Torrus::SchedulerInfo;
46 %{$self->{'options'}} = %options;
47 %{$self->{'data'}} = ();
49 if( not defined( $options{'-Tree'} ) or
50 not defined( $options{'-ProcessName'} ) )
55 $self->{'stats'} = new Torrus::SchedulerInfo( -Tree => $options{'-Tree'},
64 delete $self->{'stats'};
70 return $self->{'options'}{'-Tree'};
77 $0 = $self->{'options'}{'-ProcessName'} . ' [' . $text . ']';
86 if( not defined $when )
88 # If not specified, run immediately
91 $self->storeTask( $task, $when );
92 $self->{'stats'}->clearStats( $task->id() );
102 if( not defined( $self->{'tasks'}{$when} ) )
104 $self->{'tasks'}{$when} = [];
106 push( @{$self->{'tasks'}{$when}}, $task );
114 if( defined( $self->{'tasks'} ) )
116 foreach my $when ( keys %{$self->{'tasks'}} )
118 foreach my $task ( @{$self->{'tasks'}{$when}} )
120 $self->{'stats'}->clearStats( $task->id() );
123 undef $self->{'tasks'};
136 $self->setProcessStatus('initializing scheduler');
137 while( not $self->beforeRun() )
139 &Torrus::DB::checkInterrupted();
141 Error('Scheduler initialization error. Sleeping ' .
142 $Torrus::Scheduler::failedInitSleep . ' seconds');
144 &Torrus::DB::setUnsafeSignalHandlers();
145 sleep($Torrus::Scheduler::failedInitSleep);
146 &Torrus::DB::setSafeSignalHandlers();
148 $self->setProcessStatus('');
149 my $nextRun = time() + 3600;
150 foreach my $when ( keys %{$self->{'tasks'}} )
152 # We have 1-second rounding error
153 if( $when <= time() + 1 )
155 foreach my $task ( @{$self->{'tasks'}{$when}} )
157 &Torrus::DB::checkInterrupted();
159 my $startTime = time();
161 $self->beforeTaskRun( $task, $startTime, $when );
162 $task->beforeRun( $self->{'stats'} );
164 $self->setProcessStatus('running');
166 my $whenNext = $task->whenNext();
168 $task->afterRun( $self->{'stats'}, $startTime );
169 $self->afterTaskRun( $task, $startTime );
173 if( $whenNext == $when )
175 Error("Incorrect time returned by task");
177 $self->storeTask( $task, $whenNext );
178 if( $nextRun > $whenNext )
180 $nextRun = $whenNext;
184 delete $self->{'tasks'}{$when};
186 elsif( $nextRun > $when )
192 if( $self->{'options'}{'-RunOnce'} or
193 ( scalar( keys %{$self->{'tasks'}} ) == 0 and
194 not $self->{'options'}{'-RunAlways'} ) )
196 $self->setProcessStatus('');
201 if( scalar( keys %{$self->{'tasks'}} ) == 0 )
203 Info('Tasks list is empty. Will sleep until ' .
204 scalar(localtime($nextRun)));
207 $self->setProcessStatus('sleeping');
208 &Torrus::DB::setUnsafeSignalHandlers();
209 Debug('We will sleep until ' . scalar(localtime($nextRun)));
211 if( $Torrus::Scheduler::maxSleepTime > 0 )
213 Debug('This is a VmWare-like clock. We devide the sleep ' .
214 'interval into small pieces');
215 while( time() < $nextRun )
217 my $sleep = $nextRun - time();
218 if( $sleep > $Torrus::Scheduler::maxSleepTime )
220 $sleep = $Torrus::Scheduler::maxSleepTime;
222 Debug('Sleeping ' . $sleep . ' seconds');
228 my $sleep = $nextRun - time();
235 &Torrus::DB::setSafeSignalHandlers();
241 # A method to override by ancestors. Executed every time before the
242 # running cycle. Must return true value when finishes.
246 Debug('Torrus::Scheduler::beforeRun() - doing nothing');
255 my $startTime = shift;
256 my $plannedStartTime = shift;
258 if( not $task->didNotRun() and $startTime > $plannedStartTime + 1 )
260 my $late = $startTime - $plannedStartTime;
261 Verbose(sprintf('Task delayed %d seconds', $late));
262 $self->{'stats'}->setStatsValues( $task->id(), 'LateStart', $late );
271 my $startTime = shift;
273 my $len = time() - $startTime;
274 Verbose(sprintf('%s task finished in %d seconds', $task->name(), $len));
276 $self->{'stats'}->setStatsValues( $task->id(), 'RunningTime', $len );
280 # User data can be stored here
284 return $self->{'data'};
288 # Periodic task base class
290 # -Period => seconds -- cycle period
291 # -Offset => seconds -- time offset from even period moments
292 # -Name => "string" -- Symbolic name for log messages
293 # -Instance => N -- instance number
295 package Torrus::Scheduler::PeriodicTask;
307 if( not defined( $options{'-Instance'} ) )
309 $options{'-Instance'} = 0;
312 %{$self->{'options'}} = %options;
314 $self->{'options'}{'-Period'} = 0 unless
315 defined( $self->{'options'}{'-Period'} );
317 $self->{'options'}{'-Offset'} = 0 unless
318 defined( $self->{'options'}{'-Offset'} );
320 $self->{'options'}{'-Name'} = "PeriodicTask" unless
321 defined( $self->{'options'}{'-Name'} );
323 $self->{'missedPeriods'} = 0;
325 $self->{'options'}{'-Started'} = time();
327 # Array of (Name, Value) pairs for any kind of stats
328 $self->{'statValues'} = [];
330 Debug("New Periodic Task created: period=" .
331 $self->{'options'}{'-Period'} .
332 " offset=" . $self->{'options'}{'-Offset'});
342 if( $self->period() > 0 )
345 my $period = $self->period();
346 my $offset = $self->offset();
349 if( defined $self->{'previousSchedule'} )
351 if( $now - $self->{'previousSchedule'} <= $period )
353 $previous = $self->{'previousSchedule'};
355 elsif( not $Torrus::Scheduler::ignoreClockSkew )
357 Error('Last run of ' . $self->{'options'}{'-Name'} .
358 ' was more than ' . $period . ' seconds ago');
359 $self->{'missedPeriods'} =
360 int( ($now - $self->{'previousSchedule'}) / $period );
363 if( not defined( $previous ) )
365 $previous = $now - ($now % $period) + $offset;
368 my $whenNext = $previous + $period;
369 $self->{'previousSchedule'} = $whenNext;
371 Debug("Task ". $self->{'options'}{'-Name'}.
372 " wants to run next time at " . scalar(localtime($whenNext)));
387 Verbose(sprintf('%s periodic task started. Period: %d:%.2d; ' .
390 int( $self->period() / 60 ), $self->period() % 60,
391 int( $self->offset() / 60 ), $self->offset() % 60));
399 my $startTime = shift;
401 my $len = time() - $startTime;
402 if( $len > $self->period() )
404 Warn(sprintf('%s task execution (%d) longer than period (%d)',
405 $self->name(), $len, $self->period()));
407 $stats->setStatsValues( $self->id(), 'TooLong', $len );
408 $stats->incStatsCounter( $self->id(), 'OverrunPeriods',
409 int( $len > $self->period() ) );
412 if( $self->{'missedPeriods'} > 0 )
414 $stats->incStatsCounter( $self->id(), 'MissedPeriods',
415 $self->{'missedPeriods'} );
416 $self->{'missedPeriods'} = 0;
419 foreach my $pair( @{$self->{'statValues'}} )
421 $stats->setStatsValues( $self->id(), @{$pair} );
423 @{$self->{'statValues'}} = [];
430 Error("Dummy class Torrus::Scheduler::PeriodicTask was run");
437 return $self->{'options'}->{'-Period'};
444 return $self->{'options'}->{'-Offset'};
451 return( not defined( $self->{'previousSchedule'} ) );
458 return $self->{'options'}->{'-Name'};
464 return $self->{'options'}->{'-Instance'};
471 return $self->{'options'}->{'-Started'};
478 return join(':', 'P', $self->name(), $self->instance(),
479 $self->period(), $self->offset());
488 push( @{$self->{'statValues'}}, [$name, $value] );
496 # indent-tabs-mode: nil
497 # perl-indent-level: 4