Kstars

greedyscheduler.cpp
1 /* Ekos Scheduler Greedy Algorithm
2  SPDX-FileCopyrightText: Hy Murveit <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "greedyscheduler.h"
8 
9 #include <ekos_scheduler_debug.h>
10 
11 #include "Options.h"
12 #include "scheduler.h"
13 #include "ekos/ekos.h"
14 #include "ui_scheduler.h"
15 
16 // Can make the scheduling a bit faster by sampling every other minute instead of every minute.
17 constexpr int SCHEDULE_RESOLUTION_MINUTES = 2;
18 
19 namespace Ekos
20 {
21 
22 GreedyScheduler::GreedyScheduler()
23 {
24 }
25 
26 void GreedyScheduler::setParams(bool restartImmediately, bool restartQueue,
27  bool rescheduleErrors, int abortDelay,
28  int errorHandlingDelay)
29 {
30  setRescheduleAbortsImmediate(restartImmediately);
31  setRescheduleAbortsQueue(restartQueue);
32  setRescheduleErrors(rescheduleErrors);
33  setAbortDelaySeconds(abortDelay);
34  setErrorDelaySeconds(errorHandlingDelay);
35 }
36 
37 QList<SchedulerJob *> GreedyScheduler::scheduleJobs(const QList<SchedulerJob *> &jobs,
38  const QDateTime &now,
39  const QMap<QString, uint16_t> &capturedFramesCount,
40  Scheduler *scheduler)
41 {
42  for (auto job : jobs)
43  job->clearCache();
44 
45  SchedulerJob::enableGraphicsUpdates(false);
46  QDateTime when;
47  QElapsedTimer timer;
48  timer.start();
49  scheduledJob = nullptr;
50  schedule.clear();
51 
52  QList<SchedulerJob *> sortedJobs =
53  prepareJobsForEvaluation(jobs, now, capturedFramesCount, scheduler);
54 
55  scheduledJob = selectNextJob(sortedJobs, now, nullptr, true, &when, nullptr, nullptr, &capturedFramesCount);
56  auto schedule = getSchedule();
57  if (!schedule.empty())
58  {
59  // Print in reverse order ?! The log window at the bottom of the screen
60  // prints "upside down" -- most recent on top -- and I believe that view
61  // is more important than the log file (where we can invert when debugging).
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:")
65  .arg(now.toString()).arg(timer.elapsed() / 1000.0));
66  }
67  else scheduler->appendLogText(QString("Greedy Scheduler: empty plan (%1s)").arg(timer.elapsed() / 1000.0));
68  if (scheduledJob != nullptr)
69  {
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();
77  }
78  // The graphics would get updated many times during scheduling, which can
79  // cause significant cpu usage. No need for that, so we turn off updates
80  // at the start of this method, and then update all jobs once here.
81  SchedulerJob::enableGraphicsUpdates(true);
82  for (auto job : sortedJobs)
83  {
84  job->updateJobCells();
85  job->clearCache();
86  }
87 
88  return sortedJobs;
89 }
90 
91 bool GreedyScheduler::checkJob(const QList<SchedulerJob *> &jobs,
92  const QDateTime &now,
93  SchedulerJob *currentJob)
94 {
95  QDateTime startTime;
96  SchedulerJob *next = selectNextJob(jobs, now, currentJob, false, &startTime);
97  if (next == currentJob && now.secsTo(startTime) <= 1)
98  {
99  return true;
100  }
101  else
102  {
103  // We need to interrupt the current job. There's a higher-priority one to run.
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"));
107  return false;
108  }
109 }
110 
111 QList<SchedulerJob *> GreedyScheduler::prepareJobsForEvaluation(
112  const QList<SchedulerJob *> &jobs, const QDateTime &now,
113  const QMap<QString, uint16_t> &capturedFramesCount, Scheduler *scheduler, bool reestimateJobTimes)
114 {
115  QList<SchedulerJob *> sortedJobs = jobs;
116  // Remove some finished jobs from eval.
117  foreach (SchedulerJob *job, sortedJobs)
118  {
119  switch (job->getCompletionCondition())
120  {
121  case SchedulerJob::FINISH_AT:
122  /* If planned finishing time has passed, the job is set to IDLE waiting for a next chance to run */
123  if (job->getCompletionTime().isValid() && job->getCompletionTime() < now)
124  {
125  job->setState(SchedulerJob::JOB_COMPLETE);
126  continue;
127  }
128  break;
129 
130  case SchedulerJob::FINISH_REPEAT:
131  // In case of a repeating jobs, let's make sure we have more runs left to go
132  // If we don't, re-estimate imaging time for the scheduler job before concluding
133  if (job->getRepeatsRemaining() == 0)
134  {
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);
138  continue;
139  }
140  break;
141 
142  default:
143  break;
144  }
145  }
146 
147  // Change the state to eval or ERROR/ABORTED for all jobs that will be evaluated.
148  foreach (SchedulerJob *job, sortedJobs)
149  {
150  switch (job->getState())
151  {
152  case SchedulerJob::JOB_INVALID:
153  case SchedulerJob::JOB_COMPLETE:
154  // If job is invalid or complete, bypass evaluation.
155  break;
156 
157  case SchedulerJob::JOB_ERROR:
158  case SchedulerJob::JOB_ABORTED:
159  // These will be evaluated, but we'll have a delay to start.
160  break;
161  case SchedulerJob::JOB_IDLE:
162  case SchedulerJob::JOB_BUSY:
163  case SchedulerJob::JOB_SCHEDULED:
164  case SchedulerJob::JOB_EVALUATION:
165  default:
166  job->setState(SchedulerJob::JOB_EVALUATION);
167  break;
168  }
169  }
170 
171  // Estimate the job times
172  foreach (SchedulerJob *job, sortedJobs)
173  {
174  if (job->getState() == SchedulerJob::JOB_INVALID || job->getState() == SchedulerJob::JOB_COMPLETE)
175  continue;
176 
177  // -1 = Job is not estimated yet
178  // -2 = Job is estimated but time is unknown
179  // > 0 Job is estimated and time is known
180  if (reestimateJobTimes)
181  {
182  job->setEstimatedTime(-1);
183  if (Scheduler::estimateJobTime(job, capturedFramesCount, scheduler) == false)
184  {
185  job->setState(SchedulerJob::JOB_INVALID);
186  continue;
187  }
188  }
189  if (job->getEstimatedTime() == 0)
190  {
191  job->setRepeatsRemaining(0);
192  job->setState(SchedulerJob::JOB_COMPLETE);
193  continue;
194  }
195  }
196 
197  // If option says so, reorder by altitude and priority before sequencing.
198  // This was inheritied from the previous scheduler.
199  // FIXME: Consider removing priority and and sort-by-altitude.
200  qCInfo(KSTARS_EKOS_SCHEDULER) << "Option to sort jobs based on priority and altitude is" << Options::sortSchedulerJobs();
201  if (Options::sortSchedulerJobs())
202  {
203  using namespace std::placeholders;
204  std::stable_sort(sortedJobs.begin(), sortedJobs.end(),
205  std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, now));
206  std::stable_sort(sortedJobs.begin(), sortedJobs.end(), SchedulerJob::increasingPriorityOrder);
207  }
208  return sortedJobs;
209 }
210 
211 namespace
212 {
213 // Don't Allow INVALID or COMPLETE jobs to be scheduled.
214 // Allow ABORTED if one of the rescheduleAbort... options are true.
215 // Allow ERROR if rescheduleErrors is true.
216 bool allowJob(SchedulerJob *job, bool rescheduleAbortsImmediate, bool rescheduleAbortsQueue, bool rescheduleErrors)
217 {
218  if (job->getState() == SchedulerJob::JOB_INVALID || job->getState() == SchedulerJob::JOB_COMPLETE)
219  return false;
220  if (job->getState() == SchedulerJob::JOB_ABORTED && !rescheduleAbortsImmediate && !rescheduleAbortsQueue)
221  return false;
222  if (job->getState() == SchedulerJob::JOB_ERROR && !rescheduleErrors)
223  return false;
224  return true;
225 }
226 
227 // Returns the first possible time a job may be scheduled. That is, it doesn't
228 // evaluate the job, but rather just computes the needed delay (for ABORT and ERROR jobs)
229 // or returns now for other jobs.
230 QDateTime firstPossibleStart(SchedulerJob *job, const QDateTime &now,
231  bool rescheduleAbortsQueue, int abortDelaySeconds,
232  bool rescheduleErrors, int errorDelaySeconds)
233 {
234  QDateTime possibleStart = now;
235  const QDateTime &abortTime = job->getLastAbortTime();
236  const QDateTime &errorTime = job->getLastErrorTime();
237 
238  if (abortTime.isValid() && rescheduleAbortsQueue)
239  {
240  auto abortStartTime = abortTime.addSecs(abortDelaySeconds);
241  if (abortStartTime > now)
242  possibleStart = abortStartTime;
243  }
244 
245 
246  if (errorTime.isValid() && rescheduleErrors)
247  {
248  auto errorStartTime = errorTime.addSecs(errorDelaySeconds);
249  if (errorStartTime > now)
250  possibleStart = errorStartTime;
251  }
252 
253  if (!possibleStart.isValid() || possibleStart < now)
254  possibleStart = now;
255  return possibleStart;
256 }
257 } // namespace
258 
259 // Consider all jobs marked as JOB_EVALUATION/ABORT/ERROR. Assume ordered by highest priority first.
260 // - Find the job with the earliest start time (given constraints like altitude, twilight, ...)
261 // that can run for at least 10 minutes before a higher priority job.
262 // - START_AT jobs are given the highest priority, whereever on the list they may be,
263 // as long as they can start near their designated start times.
264 // - Compute a schedule for the next 2 days, if fullSchedule is true, otherwise
265 // just look for the next job.
266 // - If currentJob is not nullptr, this method is really evaluating whether
267 // that job can continue to be run, or if can't meet constraints, or if it
268 // should be preempted for another job.
269 SchedulerJob *GreedyScheduler::selectNextJob(const QList<SchedulerJob *> &jobs, const QDateTime &now,
270  SchedulerJob *currentJob, bool fullSchedule, QDateTime *when,
271  QDateTime *nextInterruption, QString *interruptReason,
272  const QMap<QString, uint16_t> *capturedFramesCount)
273 {
274  // Don't schedule a job that will be preempted in less than MIN_RUN_SECS.
275  constexpr int MIN_RUN_SECS = 10 * 60;
276 
277  // Don't preempt a job for another job that is more than MAX_INTERRUPT_SECS in the future.
278  constexpr int MAX_INTERRUPT_SECS = 30;
279 
280  // Don't interrupt START_AT jobs unless they can no longer run, or they're interrupted by another START_AT.
281  bool currentJobIsStartAt = (currentJob && currentJob->getFileStartupCondition() == SchedulerJob::START_AT &&
282  currentJob->getFileStartupTime().isValid());
283  QDateTime nextStart;
284  SchedulerJob *nextJob = nullptr;
285  QString interruptStr;
286 
287  for (int i = 0; i < jobs.size(); ++i)
288  {
289  SchedulerJob *job = jobs[i];
290  const bool evaluatingCurrentJob = (currentJob && (job == currentJob));
291 
292  if (!allowJob(job, rescheduleAbortsImmediate, rescheduleAbortsQueue, rescheduleErrors))
293  continue;
294 
295  // If the job state is abort or error, might have to delay the first possible start time.
296  QDateTime startSearchingtAt = firstPossibleStart(
297  job, now, rescheduleAbortsQueue, abortDelaySeconds, rescheduleErrors, errorDelaySeconds);
298 
299  // Find the first time this job can meet all its constraints.
300  // I found that passing in an "until" 4th argument actually hurt performance, as it reduces
301  // the effectiveness of the cache that getNextPossibleStartTime uses.
302  const QDateTime startTime = job->getNextPossibleStartTime(startSearchingtAt, SCHEDULE_RESOLUTION_MINUTES,
303  evaluatingCurrentJob);
304  if (startTime.isValid())
305  {
306  if (nextJob == nullptr)
307  {
308  // We have no other solutions--this is our best solution so far.
309  nextStart = startTime;
310  nextJob = job;
311  if (nextInterruption) *nextInterruption = QDateTime();
312  interruptStr = "";
313  }
314  else
315  {
316  // Allow this job to be scheduled if it can run this many seconds
317  // before running into a higher priority job.
318  const int runSecs = evaluatingCurrentJob ? MAX_INTERRUPT_SECS : MIN_RUN_SECS;
319 
320  // Don't interrupt a START_AT for higher priority job
321  if (evaluatingCurrentJob && currentJobIsStartAt)
322  {
323  if (nextInterruption) *nextInterruption = QDateTime();
324  nextStart = startTime;
325  nextJob = job;
326  interruptStr = "";
327  }
328  else if (startTime.secsTo(nextStart) > runSecs)
329  {
330  // We can start a lower priority job if it can run for at least runSecs
331  // before getting bumped by the previous higher priority job.
332  if (nextInterruption) *nextInterruption = nextStart;
333  interruptStr = QString("interrupted by %1").arg(nextJob->getName());
334  nextStart = startTime;
335  nextJob = job;
336  }
337  }
338  // If scheduling, and we have a solution close enough to now, none of the lower priority
339  // jobs can possibly be scheduled.
340  if (!currentJob && nextStart.isValid() && now.secsTo(nextStart) < MIN_RUN_SECS)
341  break;
342  }
343  else if (evaluatingCurrentJob)
344  {
345  // No need to keep searching past the current job if we're evaluating it
346  // and it had no startTime. It needs to be stopped.
347  *when = QDateTime();
348  return nullptr;
349  }
350 
351  if (evaluatingCurrentJob) break;
352  }
353  if (nextJob != nullptr)
354  {
355  // The exception to the simple scheduling rules above are START_AT jobs, which
356  // are given highest priority, irrespective of order. If nextJob starts less than
357  // MIN_RUN_SECS before an on-time START_AT job, then give the START_AT job priority.
358  // However, in order for the START_AT job to interrupt a current job, it must start now.
359  for (int i = 0; i < jobs.size(); ++i)
360  {
361  SchedulerJob *atJob = jobs[i];
362  const QDateTime atTime = atJob->getFileStartupTime();
363  if (atJob->getFileStartupCondition() == SchedulerJob::START_AT && atTime.isValid())
364  {
365  if (!allowJob(atJob, rescheduleAbortsImmediate, rescheduleAbortsQueue, rescheduleErrors))
366  continue;
367  // If the job state is abort or error, might have to delay the first possible start time.
368  QDateTime startSearchingtAt = firstPossibleStart(
369  atJob, now, rescheduleAbortsQueue, abortDelaySeconds, rescheduleErrors,
370  errorDelaySeconds);
371  // atTime above is the user-specified start time. atJobStartTime is the time it can
372  // actually start, given all the constraints (altitude, twilight, etc).
373  const QDateTime atJobStartTime = atJob->getNextPossibleStartTime(startSearchingtAt, SCHEDULE_RESOLUTION_MINUTES, currentJob
374  && (atJob == currentJob));
375  if (atJobStartTime.isValid())
376  {
377  // This difference between the user-specified start time, and the time it can really start.
378  const double startDelta = atJobStartTime.secsTo(atTime);
379  if (fabs(startDelta) < 10 * 60)
380  {
381  // If we're looking for a new job to start, then give the START_AT priority
382  // if it's within 10 minutes of its user-specified time.
383  // However, if we're evaluating the current job (called from checkJob() above)
384  // then only interrupt it if the START_AT job can start very soon.
385  const int gap = currentJob == nullptr ? MIN_RUN_SECS : 30;
386  if (nextStart.secsTo(atJobStartTime) <= gap)
387  {
388  nextJob = atJob;
389  nextStart = atJobStartTime;
390  if (nextInterruption) *nextInterruption = QDateTime(); // Not interrupting atJob
391  }
392  else if (nextInterruption)
393  {
394  // The START_AT job was not chosen to start now, but it's still possible
395  // that this atJob will be an interrupter.
396  if (!nextInterruption->isValid() ||
397  atJobStartTime.secsTo(*nextInterruption) < 0)
398  {
399  *nextInterruption = atJobStartTime;
400  interruptStr = QString("interrupted by %1").arg(atJob->getName());
401  }
402  }
403  }
404  }
405  }
406  }
407  }
408  if (when != nullptr) *when = nextStart;
409  if (interruptReason != nullptr) *interruptReason = interruptStr;
410 
411  // Needed so display says "Idle" for unscheduled jobs.
412  // This will also happen in simulate, but that isn't called if nextJob is null.
413  // Must test for !nextJob. setState() inside unsetEvaluation has a nasty side effect
414  // of clearing the estimated time.
415  if (!nextJob)
416  unsetEvaluation(jobs);
417 
418  constexpr int twoDays = 48 * 3600;
419  if (fullSchedule && nextJob != nullptr)
420  simulate(jobs, now, now.addSecs(twoDays), capturedFramesCount);
421 
422  return nextJob;
423 }
424 
425 void GreedyScheduler::simulate(const QList<SchedulerJob *> &jobs, const QDateTime &time, const QDateTime &endTime,
426  const QMap<QString, uint16_t> *capturedFramesCount)
427 {
428  schedule.clear();
429 
430  // Make a deep copy of jobs
431  QList<SchedulerJob *> copiedJobs;
432  QList<SchedulerJob *> scheduledJobs;
433 
434  foreach (SchedulerJob *job, jobs)
435  {
436  SchedulerJob *newJob = new SchedulerJob();
437  // Make sure the copied class pointers aren't affected!
438  *newJob = *job;
439  // Don't want to affect the UI
440  newJob->setStatusCell(nullptr);
441  newJob->setStartupCell(nullptr);
442  copiedJobs.append(newJob);
443  job->setGreedyCompletionTime(QDateTime());
444  }
445 
446  // The number of jobs we have that can be scheduled,
447  // and the number of them where a simulated start has been scheduled.
448  int numStartupCandidates = 0, numStartups = 0;
449  // Reset the start times.
450  foreach (SchedulerJob *job, copiedJobs)
451  {
452  job->setStartupTime(QDateTime());
453  const auto state = job->getState();
454  if (state == SchedulerJob::JOB_SCHEDULED || state == SchedulerJob::JOB_EVALUATION ||
455  state == SchedulerJob::JOB_BUSY || state == SchedulerJob::JOB_IDLE)
456  numStartupCandidates++;
457  }
458 
459  QMap<QString, uint16_t> capturedFramesCopy;
460  if (capturedFramesCount != nullptr)
461  capturedFramesCopy = *capturedFramesCount;
462  QList<SchedulerJob *>simJobs =
463  prepareJobsForEvaluation(copiedJobs, time, capturedFramesCopy, nullptr, false);
464 
465  QDateTime simTime = time;
466  int iterations = 0;
467  QHash<SchedulerJob*, int> workDone;
468  for(int i = 0; i < simJobs.size(); ++i)
469  workDone[simJobs[i]] = 0.0;
470 
471  while (true)
472  {
473  QDateTime jobStartTime;
474  QDateTime jobInterruptTime;
475  QString interruptReason;
476  // Find the next job to be scheduled, when it starts, and when a higher priority
477  // job might preempt it, why it would be preempted.
478  // Note: 4th arg, fullSchedule, must be false or we'd loop forever.
479  SchedulerJob *selectedJob = selectNextJob(
480  simJobs, simTime, nullptr, false, &jobStartTime, &jobInterruptTime, &interruptReason);
481  if (selectedJob == nullptr)
482  break;
483 
484  // Are we past the end time?
485  if (endTime.isValid() && jobStartTime.secsTo(endTime) < 0) break;
486 
487  QString constraintReason;
488  // Get the time that this next job would fail its constraints, and a human-readable explanation.
489  QDateTime jobConstraintTime = selectedJob->getNextEndTime(jobStartTime, SCHEDULE_RESOLUTION_MINUTES, &constraintReason,
490  jobInterruptTime);
491  QDateTime jobCompletionTime;
492  if (selectedJob->getEstimatedTime() > 0)
493  {
494  // Estimate when the job might complete, if it was allowed to run without interruption.
495  const int timeLeft = selectedJob->getEstimatedTime() - workDone[selectedJob];
496  jobCompletionTime = jobStartTime.addSecs(timeLeft);
497  }
498  // Consider the 3 stopping times computed above (preemption, constraints missed, and completion),
499  // see which comes soonest, and set the jobStopTime and jobStopReason.
500  QDateTime jobStopTime = jobInterruptTime;
501  QString stopReason = jobStopTime.isValid() ? interruptReason : "";
502  if (jobConstraintTime.isValid() && (!jobStopTime.isValid() || jobStopTime.secsTo(jobConstraintTime) < 0))
503  {
504  stopReason = constraintReason;
505  jobStopTime = jobConstraintTime;
506  }
507  if (jobCompletionTime.isValid() && (!jobStopTime.isValid() || jobStopTime.secsTo(jobCompletionTime) < 0))
508  {
509  stopReason = "job completion";
510  jobStopTime = jobCompletionTime;
511  }
512  // Increment the work done, for the next time this job might be scheduled in this simulation.
513  if (jobStopTime.isValid())
514  workDone[selectedJob] += jobStartTime.secsTo(jobStopTime);
515 
516  // Set the job's startupTime, but only for the first time the job will be scheduled.
517  // This will be used by the scheduler's UI when displaying the job schedules.
518  if (!selectedJob->getStartupTime().isValid())
519  {
520  numStartups++;
521  selectedJob->setStartupTime(jobStartTime);
522  selectedJob->setGreedyCompletionTime(jobStopTime);
523  selectedJob->setStopReason(stopReason);
524  selectedJob->setState(SchedulerJob::JOB_SCHEDULED);
525  scheduledJobs.append(selectedJob);
526  }
527 
528  // Compute if the simulated job should be considered complete because of work done.
529  if (selectedJob->getEstimatedTime() >= 0 &&
530  workDone[selectedJob] >= selectedJob->getEstimatedTime())
531  selectedJob->setState(SchedulerJob::JOB_COMPLETE);
532 
533  schedule.append(JobSchedule(jobs[copiedJobs.indexOf(selectedJob)], jobStartTime, jobStopTime, stopReason));
534  simTime = jobStopTime.addSecs(60);
535 
536  // End the simulation if we've crossed endTime, or no further jobs could be started,
537  // or if we've simply run too long.
538  if (!simTime.isValid()) break;
539  if (endTime.isValid() && simTime.secsTo(endTime) < 0) break;
540  if (++iterations > 20) break;
541  }
542 
543  // This simulation has been run using a deep-copy of the jobs list, so as not to interfere with
544  // some of their stored data. However, we do wish to update several fields of the "real" scheduleJobs.
545  // Note that the original jobs list and "copiedJobs" should be in the same order..
546  for (int i = 0; i < jobs.size(); ++i)
547  {
548  if (scheduledJobs.indexOf(copiedJobs[i]) >= 0)
549  {
550  jobs[i]->setState(SchedulerJob::JOB_SCHEDULED);
551  jobs[i]->setStartupTime(copiedJobs[i]->getStartupTime());
552  // Can't set the standard completionTime as it affects getEstimatedTime()
553  jobs[i]->setGreedyCompletionTime(copiedJobs[i]->getGreedyCompletionTime());
554  jobs[i]->setStopReason(copiedJobs[i]->getStopReason());
555  }
556  }
557  // This should go after above loop. unsetEvaluation calls setState() which clears
558  // certain fields from the state for IDLE states.
559  unsetEvaluation(jobs);
560 
561  return;
562 }
563 
564 void GreedyScheduler::unsetEvaluation(const QList<SchedulerJob *> &jobs)
565 {
566  for (int i = 0; i < jobs.size(); ++i)
567  {
568  if (jobs[i]->getState() == SchedulerJob::JOB_EVALUATION)
569  jobs[i]->setState(SchedulerJob::JOB_IDLE);
570  }
571 }
572 
573 QString GreedyScheduler::jobScheduleString(const JobSchedule &jobSchedule)
574 {
575  return QString("%1\t%2 --> %3 \t%4")
576  .arg(jobSchedule.job->getName(), -10)
577  .arg(jobSchedule.startTime.toString("MM/dd hh:mm"),
578  jobSchedule.stopTime.toString("hh:mm"), jobSchedule.stopReason);
579 }
580 
581 void GreedyScheduler::printSchedule(const QList<JobSchedule> &schedule)
582 {
583  foreach (auto &line, schedule)
584  {
585  fprintf(stderr, "%s\n", QString("%1 %2 --> %3 (%4)")
586  .arg(jobScheduleString(line)).toLatin1().data());
587  }
588 }
589 
590 } // namespace Ekos
void append(const T &value)
QDateTime addSecs(qint64 s) const const
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:70
int size() const const
QString i18n(const char *text, const TYPE &arg...)
qint64 secsTo(const QDateTime &other) const const
int indexOf(const T &value, int from) const const
qint64 elapsed() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
static bool estimateJobTime(SchedulerJob *schedJob, const QMap< QString, uint16_t > &capturedFramesCount, Scheduler *scheduler)
estimateJobTime Estimates the time the job takes to complete based on the sequence file and what modu...
Definition: scheduler.cpp:5861
bool isValid() const const
QList::iterator begin()
QString toString(Qt::DateFormat format) const const
QList::iterator end()
QAction * next(const QObject *recvr, const char *slot, QObject *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:54 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.