7 #include "greedyscheduler.h"
9 #include <ekos_scheduler_debug.h>
12 #include "scheduler.h"
13 #include "ekos/ekos.h"
14 #include "ui_scheduler.h"
17 constexpr
int SCHEDULE_RESOLUTION_MINUTES = 2;
22 GreedyScheduler::GreedyScheduler()
26 void GreedyScheduler::setParams(
bool restartImmediately,
bool restartQueue,
27 bool rescheduleErrors,
int abortDelay,
28 int errorHandlingDelay)
30 setRescheduleAbortsImmediate(restartImmediately);
31 setRescheduleAbortsQueue(restartQueue);
32 setRescheduleErrors(rescheduleErrors);
33 setAbortDelaySeconds(abortDelay);
34 setErrorDelaySeconds(errorHandlingDelay);
45 SchedulerJob::enableGraphicsUpdates(
false);
49 scheduledJob =
nullptr;
53 prepareJobsForEvaluation(jobs, now, capturedFramesCount, scheduler);
55 scheduledJob = selectNextJob(sortedJobs, now,
nullptr,
true, &when,
nullptr,
nullptr, &capturedFramesCount);
56 auto schedule = getSchedule();
57 if (!schedule.empty())
62 for (
int i = schedule.size() - 1; i >= 0; i--)
63 scheduler->appendLogText(GreedyScheduler::jobScheduleString(schedule[i]));
64 scheduler->appendLogText(
QString(
"Greedy Scheduler plan for the next 48 hours starting %1 (%2)s:")
67 else scheduler->appendLogText(
QString(
"Greedy Scheduler: empty plan (%1s)").arg(timer.
elapsed() / 1000.0));
68 if (scheduledJob !=
nullptr)
70 qCDebug(KSTARS_EKOS_SCHEDULER)
71 <<
QString(
"Greedy Scheduler scheduling next job %1 at %2")
72 .
arg(scheduledJob->getName(), when.
toString(
"hh:mm"));
73 scheduledJob->setState(SchedulerJob::JOB_SCHEDULED);
74 scheduledJob->setStartupTime(when);
75 foreach (
auto job, sortedJobs)
76 job->updateJobCells();
81 SchedulerJob::enableGraphicsUpdates(
true);
82 for (
auto job : sortedJobs)
84 job->updateJobCells();
93 SchedulerJob *currentJob)
96 SchedulerJob *
next = selectNextJob(jobs, now, currentJob,
false, &startTime);
97 if (next == currentJob && now.
secsTo(startTime) <= 1)
104 qCDebug(KSTARS_EKOS_SCHEDULER)
105 <<
QString(
"Greedy Scheduler bumping current job %1 for %2 at %3")
106 .
arg(currentJob->getName(), next ?
next->getName() :
"---", now.
toString(
"hh:mm"));
117 foreach (SchedulerJob *job, sortedJobs)
119 switch (job->getCompletionCondition())
121 case SchedulerJob::FINISH_AT:
123 if (job->getCompletionTime().isValid() && job->getCompletionTime() < now)
125 job->setState(SchedulerJob::JOB_COMPLETE);
130 case SchedulerJob::FINISH_REPEAT:
133 if (job->getRepeatsRemaining() == 0)
135 if (scheduler !=
nullptr) scheduler->appendLogText(
i18n(
"Job '%1' has no more batches remaining.", job->getName()));
136 job->setState(SchedulerJob::JOB_COMPLETE);
137 job->setEstimatedTime(0);
148 foreach (SchedulerJob *job, sortedJobs)
150 switch (job->getState())
152 case SchedulerJob::JOB_INVALID:
153 case SchedulerJob::JOB_COMPLETE:
157 case SchedulerJob::JOB_ERROR:
158 case SchedulerJob::JOB_ABORTED:
161 case SchedulerJob::JOB_IDLE:
162 case SchedulerJob::JOB_BUSY:
163 case SchedulerJob::JOB_SCHEDULED:
164 case SchedulerJob::JOB_EVALUATION:
166 job->setState(SchedulerJob::JOB_EVALUATION);
172 foreach (SchedulerJob *job, sortedJobs)
174 if (job->getState() == SchedulerJob::JOB_INVALID || job->getState() == SchedulerJob::JOB_COMPLETE)
180 if (reestimateJobTimes)
182 job->setEstimatedTime(-1);
185 job->setState(SchedulerJob::JOB_INVALID);
189 if (job->getEstimatedTime() == 0)
191 job->setRepeatsRemaining(0);
192 job->setState(SchedulerJob::JOB_COMPLETE);
205 bool allowJob(SchedulerJob *job,
bool rescheduleAbortsImmediate,
bool rescheduleAbortsQueue,
bool rescheduleErrors)
207 if (job->getState() == SchedulerJob::JOB_INVALID || job->getState() == SchedulerJob::JOB_COMPLETE)
209 if (job->getState() == SchedulerJob::JOB_ABORTED && !rescheduleAbortsImmediate && !rescheduleAbortsQueue)
211 if (job->getState() == SchedulerJob::JOB_ERROR && !rescheduleErrors)
220 bool rescheduleAbortsQueue,
int abortDelaySeconds,
221 bool rescheduleErrors,
int errorDelaySeconds)
224 const QDateTime &abortTime = job->getLastAbortTime();
225 const QDateTime &errorTime = job->getLastErrorTime();
227 if (abortTime.
isValid() && rescheduleAbortsQueue)
229 auto abortStartTime = abortTime.
addSecs(abortDelaySeconds);
230 if (abortStartTime > now)
231 possibleStart = abortStartTime;
235 if (errorTime.
isValid() && rescheduleErrors)
237 auto errorStartTime = errorTime.
addSecs(errorDelaySeconds);
238 if (errorStartTime > now)
239 possibleStart = errorStartTime;
242 if (!possibleStart.
isValid() || possibleStart < now)
244 return possibleStart;
259 SchedulerJob *currentJob,
bool fullSchedule,
QDateTime *when,
264 constexpr
int MIN_RUN_SECS = 10 * 60;
267 constexpr
int MAX_INTERRUPT_SECS = 30;
270 bool currentJobIsStartAt = (currentJob && currentJob->getFileStartupCondition() == SchedulerJob::START_AT &&
271 currentJob->getFileStartupTime().isValid());
273 SchedulerJob *nextJob =
nullptr;
276 for (
int i = 0; i < jobs.
size(); ++i)
278 SchedulerJob *job = jobs[i];
279 const bool evaluatingCurrentJob = (currentJob && (job == currentJob));
281 if (!allowJob(job, rescheduleAbortsImmediate, rescheduleAbortsQueue, rescheduleErrors))
285 QDateTime startSearchingtAt = firstPossibleStart(
286 job, now, rescheduleAbortsQueue, abortDelaySeconds, rescheduleErrors, errorDelaySeconds);
291 const QDateTime startTime = job->getNextPossibleStartTime(startSearchingtAt, SCHEDULE_RESOLUTION_MINUTES,
292 evaluatingCurrentJob);
295 if (nextJob ==
nullptr)
298 nextStart = startTime;
300 if (nextInterruption) *nextInterruption =
QDateTime();
307 const int runSecs = evaluatingCurrentJob ? MAX_INTERRUPT_SECS : MIN_RUN_SECS;
310 if (evaluatingCurrentJob && currentJobIsStartAt)
312 if (nextInterruption) *nextInterruption =
QDateTime();
313 nextStart = startTime;
317 else if (startTime.
secsTo(nextStart) > runSecs)
321 if (nextInterruption) *nextInterruption = nextStart;
322 interruptStr =
QString(
"interrupted by %1").
arg(nextJob->getName());
323 nextStart = startTime;
329 if (!currentJob && nextStart.
isValid() && now.
secsTo(nextStart) < MIN_RUN_SECS)
332 else if (evaluatingCurrentJob)
340 if (evaluatingCurrentJob)
break;
342 if (nextJob !=
nullptr)
348 for (
int i = 0; i < jobs.
size(); ++i)
350 SchedulerJob *atJob = jobs[i];
351 const QDateTime atTime = atJob->getFileStartupTime();
352 if (atJob->getFileStartupCondition() == SchedulerJob::START_AT && atTime.
isValid())
354 if (!allowJob(atJob, rescheduleAbortsImmediate, rescheduleAbortsQueue, rescheduleErrors))
357 QDateTime startSearchingtAt = firstPossibleStart(
358 atJob, now, rescheduleAbortsQueue, abortDelaySeconds, rescheduleErrors,
362 const QDateTime atJobStartTime = atJob->getNextPossibleStartTime(startSearchingtAt, SCHEDULE_RESOLUTION_MINUTES, currentJob
363 && (atJob == currentJob));
367 const double startDelta = atJobStartTime.
secsTo(atTime);
368 if (fabs(startDelta) < 10 * 60)
374 const int gap = currentJob ==
nullptr ? MIN_RUN_SECS : 30;
375 if (nextStart.
secsTo(atJobStartTime) <= gap)
378 nextStart = atJobStartTime;
379 if (nextInterruption) *nextInterruption =
QDateTime();
381 else if (nextInterruption)
385 if (!nextInterruption->
isValid() ||
386 atJobStartTime.
secsTo(*nextInterruption) < 0)
388 *nextInterruption = atJobStartTime;
389 interruptStr =
QString(
"interrupted by %1").
arg(atJob->getName());
397 if (when !=
nullptr) *when = nextStart;
398 if (interruptReason !=
nullptr) *interruptReason = interruptStr;
405 unsetEvaluation(jobs);
407 constexpr
int twoDays = 48 * 3600;
408 if (fullSchedule && nextJob !=
nullptr)
409 simulate(jobs, now, now.
addSecs(twoDays), capturedFramesCount);
423 foreach (SchedulerJob *job, jobs)
425 SchedulerJob *newJob =
new SchedulerJob();
429 newJob->setStatusCell(
nullptr);
430 newJob->setStartupCell(
nullptr);
431 copiedJobs.
append(newJob);
432 job->setGreedyCompletionTime(
QDateTime());
437 int numStartupCandidates = 0, numStartups = 0;
439 foreach (SchedulerJob *job, copiedJobs)
442 const auto state = job->getState();
443 if (state == SchedulerJob::JOB_SCHEDULED || state == SchedulerJob::JOB_EVALUATION ||
444 state == SchedulerJob::JOB_BUSY || state == SchedulerJob::JOB_IDLE)
445 numStartupCandidates++;
449 if (capturedFramesCount !=
nullptr)
450 capturedFramesCopy = *capturedFramesCount;
452 prepareJobsForEvaluation(copiedJobs, time, capturedFramesCopy,
nullptr,
false);
457 for(
int i = 0; i < simJobs.
size(); ++i)
458 workDone[simJobs[i]] = 0.0;
468 SchedulerJob *selectedJob = selectNextJob(
469 simJobs, simTime,
nullptr,
false, &jobStartTime, &jobInterruptTime, &interruptReason);
470 if (selectedJob ==
nullptr)
474 if (endTime.
isValid() && jobStartTime.
secsTo(endTime) < 0)
break;
478 QDateTime jobConstraintTime = selectedJob->getNextEndTime(jobStartTime, SCHEDULE_RESOLUTION_MINUTES, &constraintReason,
481 if (selectedJob->getEstimatedTime() > 0)
484 const int timeLeft = selectedJob->getEstimatedTime() - workDone[selectedJob];
485 jobCompletionTime = jobStartTime.
addSecs(timeLeft);
489 QDateTime jobStopTime = jobInterruptTime;
490 QString stopReason = jobStopTime.
isValid() ? interruptReason :
"";
491 if (jobConstraintTime.
isValid() && (!jobStopTime.
isValid() || jobStopTime.
secsTo(jobConstraintTime) < 0))
493 stopReason = constraintReason;
494 jobStopTime = jobConstraintTime;
496 if (jobCompletionTime.
isValid() && (!jobStopTime.
isValid() || jobStopTime.
secsTo(jobCompletionTime) < 0))
498 stopReason =
"job completion";
499 jobStopTime = jobCompletionTime;
503 workDone[selectedJob] += jobStartTime.
secsTo(jobStopTime);
507 if (!selectedJob->getStartupTime().isValid())
510 selectedJob->setStartupTime(jobStartTime);
511 selectedJob->setGreedyCompletionTime(jobStopTime);
512 selectedJob->setStopReason(stopReason);
513 selectedJob->setState(SchedulerJob::JOB_SCHEDULED);
514 scheduledJobs.
append(selectedJob);
518 if (selectedJob->getEstimatedTime() >= 0 &&
519 workDone[selectedJob] >= selectedJob->getEstimatedTime())
520 selectedJob->setState(SchedulerJob::JOB_COMPLETE);
522 schedule.append(JobSchedule(jobs[copiedJobs.
indexOf(selectedJob)], jobStartTime, jobStopTime, stopReason));
523 simTime = jobStopTime.
addSecs(60);
529 if (++iterations > 20)
break;
535 for (
int i = 0; i < jobs.
size(); ++i)
537 if (scheduledJobs.
indexOf(copiedJobs[i]) >= 0)
539 jobs[i]->setState(SchedulerJob::JOB_SCHEDULED);
540 jobs[i]->setStartupTime(copiedJobs[i]->getStartupTime());
542 jobs[i]->setGreedyCompletionTime(copiedJobs[i]->getGreedyCompletionTime());
543 jobs[i]->setStopReason(copiedJobs[i]->getStopReason());
548 unsetEvaluation(jobs);
555 for (
int i = 0; i < jobs.
size(); ++i)
557 if (jobs[i]->getState() == SchedulerJob::JOB_EVALUATION)
558 jobs[i]->setState(SchedulerJob::JOB_IDLE);
562 QString GreedyScheduler::jobScheduleString(
const JobSchedule &jobSchedule)
564 return QString(
"%1\t%2 --> %3 \t%4")
565 .
arg(jobSchedule.job->getName(), -10)
566 .
arg(jobSchedule.startTime.toString(
"MM/dd hh:mm"),
567 jobSchedule.stopTime.toString(
"hh:mm"), jobSchedule.stopReason);
572 foreach (
auto &line, schedule)
574 fprintf(stderr,
"%s\n",
QString(
"%1 %2 --> %3 (%4)")
575 .arg(jobScheduleString(line)).toLatin1().data());