diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp index 6e14dfefbd336014d386b89a585950aad993731c..ba7c009b288c8e9fbcba420de672942548849993 100644 --- a/src/shared/scheduler/TaskScheduler.cpp +++ b/src/shared/scheduler/TaskScheduler.cpp @@ -28,18 +28,11 @@ using namespace std; using namespace miosix; +using namespace Boardcore::Constants; namespace Boardcore { -namespace Constants -{ -static constexpr unsigned int TICKS_PER_MS = - miosix::TICK_FREQ / 1000; // Number of ticks in a millisecond -static constexpr unsigned int MS_PER_TICK = - 1000 / miosix::TICK_FREQ; // Number of milliseconds in a tick -} // namespace Constants - TaskScheduler::EventQueue TaskScheduler::makeAgenda() { std::vector<Event> agendaStorage{}; @@ -60,7 +53,7 @@ TaskScheduler::TaskScheduler(miosix::Priority priority) } size_t TaskScheduler::addTask(function_t function, uint32_t period, - Policy policy, int64_t startTick) + Policy policy, int64_t startTime) { // In the case of early returns, using RAII mutex wrappers to unlock the // mutex would cause it to be locked and unlocked one more time before @@ -77,20 +70,22 @@ size_t TaskScheduler::addTask(function_t function, uint32_t period, return 0; } + auto periodNs = period * NS_IN_MS; + if (policy == Policy::ONE_SHOT) { - startTick += period; + startTime += periodNs; } // Insert a new task with the given parameters - tasks.emplace_back(function, period, policy, startTick); + tasks.emplace_back(function, periodNs, policy, startTime); size_t id = tasks.size() - 1; // Only add the task to the agenda if the scheduler is running // Otherwise, the agenda will be populated when the scheduler is started if (isRunning()) { - agenda.emplace(id, startTick); + agenda.emplace(id, startTime); } condvar.broadcast(); // Signals the run thread @@ -122,7 +117,7 @@ void TaskScheduler::enableTask(size_t id) } task.enabled = true; - agenda.emplace(id, getTick() + task.period * Constants::TICKS_PER_MS); + agenda.emplace(id, getTime() + task.period); mutex.unlock(); } @@ -186,21 +181,23 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats() void TaskScheduler::populateAgenda() { - int64_t currentTick = getTick(); + int64_t currentTime = getTime(); for (size_t id = 1; id < tasks.size(); id++) { Task& task = tasks[id]; - int64_t nextTick = task.startTick; - // Normalize the tasks start time if they precede the current tick - if (nextTick < currentTick) + int64_t nextTime = task.startTime; + // Normalize the tasks start time if they precede the current time + if (nextTime < currentTime) { - nextTick += - ((currentTick - nextTick) / task.period + 1) * task.period; + int64_t timeSinceStart = currentTime - nextTime; + int64_t periodsMissed = timeSinceStart / task.period; + int64_t periodsToSkip = periodsMissed + 1; + nextTime += periodsToSkip * task.period; } - agenda.emplace(id, nextTick); + agenda.emplace(id, nextTime); } } @@ -221,18 +218,18 @@ void TaskScheduler::run() return; } - int64_t startTick = getTick(); + int64_t startTime = getTime(); Event nextEvent = agenda.top(); Task& nextTask = tasks[nextEvent.taskId]; // If the task has the SKIP policy and its execution was missed, we need // to move it forward to match the period - if (nextEvent.nextTick < startTick && nextTask.policy == Policy::SKIP) + if (nextEvent.nextTime < startTime && nextTask.policy == Policy::SKIP) { agenda.pop(); - enqueue(nextEvent, startTick); + enqueue(nextEvent, startTime); } - else if (nextEvent.nextTick <= startTick) + else if (nextEvent.nextTime <= startTime) { agenda.pop(); @@ -254,42 +251,42 @@ void TaskScheduler::run() } // Enqueue only on a valid task - updateStats(nextEvent, startTick, getTick()); - enqueue(nextEvent, startTick); + updateStats(nextEvent, startTime, getTime()); + enqueue(nextEvent, startTime); } } else { Unlock<FastMutex> unlock(lock); - Thread::sleepUntil(nextEvent.nextTick); + Thread::nanoSleepUntil(nextEvent.nextTime); } } } -void TaskScheduler::updateStats(const Event& event, int64_t startTick, - int64_t endTick) +void TaskScheduler::updateStats(const Event& event, int64_t startTime, + int64_t endTime) { Task& task = tasks[event.taskId]; // Activation stats - float activationError = startTick - event.nextTick; - task.activationStats.add(activationError * Constants::MS_PER_TICK); + float activationError = startTime - event.nextTime; + task.activationStats.add(activationError / NS_IN_MS); // Period stats int64_t lastCall = task.lastCall; if (lastCall >= 0) - task.periodStats.add((startTick - lastCall) * Constants::MS_PER_TICK); + task.periodStats.add((startTime - lastCall) / NS_IN_MS); - // Update the last call tick to the current start tick for the next + // Update the last call time to the current start time for the next // iteration - task.lastCall = startTick; + task.lastCall = startTime; // Workload stats - task.workloadStats.add(endTick - startTick); + task.workloadStats.add((endTime - startTime) / NS_IN_MS); } -void TaskScheduler::enqueue(Event event, int64_t startTick) +void TaskScheduler::enqueue(Event event, int64_t startTime) { Task& task = tasks[event.taskId]; switch (task.policy) @@ -300,21 +297,26 @@ void TaskScheduler::enqueue(Event event, int64_t startTick) task.enabled = false; return; case Policy::SKIP: + { + // Compute the number of missed periods since the last execution + int64_t timeSinceLastExec = startTime - event.nextTime; + int64_t periodsMissed = timeSinceLastExec / task.period; + + // Schedule the task executon to the next aligned period, by + // skipping over the missed ones + // E.g. 3 periods have passed since last execution, the next viable + // schedule time is after 4 periods + int64_t periodsToSkip = periodsMissed + 1; + // Update the task to run at the next viable timeslot, while still + // being aligned to the original one + event.nextTime += periodsToSkip * task.period; + // Updated the missed events count - task.missedEvents += (startTick - event.nextTick) / task.period; - - // Compute the number of periods between the tick the event should - // have been run and the tick it actually run. Than adds 1 and - // multiply the period to get the next execution tick still aligned - // to the original one. - // E.g. If a task has to run once every 2 ticks and start at tick 0 - // but for whatever reason the first execution is at tick 3, then - // the next execution will be at tick 4. - event.nextTick += - ((startTick - event.nextTick) / task.period + 1) * task.period; + task.missedEvents += static_cast<uint32_t>(periodsMissed); break; + } case Policy::RECOVER: - event.nextTick += task.period * Constants::TICKS_PER_MS; + event.nextTime += task.period; break; } @@ -324,15 +326,15 @@ void TaskScheduler::enqueue(Event event, int64_t startTick) } TaskScheduler::Task::Task() - : function(nullptr), period(0), startTick(0), enabled(false), + : function(nullptr), period(0), startTime(0), enabled(false), policy(Policy::SKIP), lastCall(-1), activationStats(), periodStats(), workloadStats(), missedEvents(0), failedEvents(0) { } -TaskScheduler::Task::Task(function_t function, uint32_t period, Policy policy, - int64_t startTick) - : function(function), period(period), startTick(startTick), enabled(true), +TaskScheduler::Task::Task(function_t function, int64_t period, Policy policy, + int64_t startTime) + : function(function), period(period), startTime(startTime), enabled(true), policy(policy), lastCall(-1), activationStats(), periodStats(), workloadStats(), missedEvents(0), failedEvents(0) { diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h index 6496eb9bf57c065ac76dcd00505da13495ae5b48..80fb22c63608cceec014f0b16239a249406e10ee 100644 --- a/src/shared/scheduler/TaskScheduler.h +++ b/src/shared/scheduler/TaskScheduler.h @@ -78,7 +78,7 @@ public: * it is supposed to (e.g. another thread occupies the CPU), the scheduler * doesn't recover the missed executions but instead skips those and * continues normally. This ensures that all the events are aligned with - * the original start tick. In other words, the period and the start tick of + * the original start time. In other words, the period and the start time of * a task specifies the time slots the task has to be executed. If one of * this time slots can't be used, that specific execution won't be * recovered. @@ -91,7 +91,7 @@ public: { ONE_SHOT, ///< Run the task one single timer. SKIP, // Skips lost executions and stays aligned with the original - // start tick. + // start time. RECOVER ///< Prioritize the number of executions over the period. }; @@ -109,12 +109,13 @@ public: * @param function Function to be called periodically. * @param period Inter call period [ms]. * @param policy Task policy, default is SKIP. - * @param startTick First activation time, useful for synchronizing tasks. + * @param startTime Absolute system time of the first activation time, + * useful for synchronizing tasks [ns] * @return The ID of the task if it was added successfully, 0 otherwise. */ size_t addTask(function_t function, uint32_t period, Policy policy = Policy::RECOVER, - int64_t startTick = miosix::getTick()); + int64_t startTime = miosix::getTime()); /** * @brief Enables the task with the given id. @@ -136,13 +137,13 @@ private: struct Task { function_t function; - uint32_t period; // [ms] - int64_t startTick; ///< First activation time, useful for synchronizing + int64_t period; ///< [ns] + int64_t startTime; ///< First activation time, useful for synchronizing ///< tasks. bool enabled; ///< Whether the task should be executed. Policy policy; - int64_t lastCall; ///< Last activation tick for statistics computation. - Stats activationStats; ///< Stats about activation tick error. + int64_t lastCall; ///< Last activation time for statistics computation. + Stats activationStats; ///< Stats about activation time error. Stats periodStats; ///< Stats about period error. Stats workloadStats; ///< Stats about time the task takes to compute. uint32_t missedEvents; ///< Number of events that could not be run. @@ -157,12 +158,12 @@ private: * @brief Creates a task with the given parameters * * @param function The std::function to be called - * @param period The Period in [ms] + * @param period The Period in [ns] * @param policy The task policy in case of a miss - * @param startTick The first activation time + * @param startTime The first activation time */ - explicit Task(function_t function, uint32_t period, Policy policy, - int64_t startTick); + explicit Task(function_t function, int64_t period, Policy policy, + int64_t startTime); // Delete copy constructor and copy assignment operator to avoid copying // and force moving @@ -183,27 +184,27 @@ private: struct Event { size_t taskId; ///< The task to execute. - int64_t nextTick; ///< Tick of next activation. + int64_t nextTime; ///< Absolute time of next activation. - Event(size_t taskId, int64_t nextTick) - : taskId(taskId), nextTick(nextTick) + Event(size_t taskId, int64_t nextTime) + : taskId(taskId), nextTime(nextTime) { } /** - * @brief Compare two events based on the next tick. - * @note This is used to have the event with the lowest tick first in + * @brief Compare two events based on the next time. + * @note This is used to have the event with the lowest time first in * the agenda. Newly pushed events are moved up in the queue (see - * heap bubble-up) until the other tick is lower. + * heap bubble-up) until the other time is lower. */ bool operator>(const Event& other) const { - return this->nextTick > other.nextTick; + return this->nextTime > other.nextTime; } }; // Use `std::greater` as the comparator to have elements with the lowest - // tick first. Requires operator `>` to be defined for Event. + // time first. Requires operator `>` to be defined for Event. using EventQueue = std::priority_queue<Event, std::vector<Event>, std::greater<Event>>; @@ -227,13 +228,13 @@ private: /** * @brief Update task statistics (Intended for when the task is executed). * - * This function changes the task last call tick to the startTick. + * This function changes the task last call time to the startTime. * * \param event Current event. - * \param startTick Start of execution tick. - * \param endTick End of execution tick. + * \param startTime Start of execution time. + * \param endTime End of execution time. */ - void updateStats(const Event& event, int64_t startTick, int64_t endTick); + void updateStats(const Event& event, int64_t startTime, int64_t endTime); /** * @brief (Re)Enqueue an event into the agenda based on the scheduling @@ -242,22 +243,23 @@ private: * Requires the mutex to be locked! * * \param event Event to be scheduled. Note: this parameter is modified, the - * nextTick field is updated in order to respect the task interval. - * \param startTick Activation tick, needed to update the nextTick value of + * nextTime field is updated in order to respect the task interval. + * \param startTime Activation time, needed to update the nextTime value of * the event. */ - void enqueue(Event event, int64_t startTick); + void enqueue(Event event, int64_t startTime); static TaskStatsResult fromTaskIdPairToStatsResult(const Task& task, size_t id) { - return TaskStatsResult{id, - task.period, - task.activationStats.getStats(), - task.periodStats.getStats(), - task.workloadStats.getStats(), - task.missedEvents, - task.failedEvents}; + return TaskStatsResult{ + id, + static_cast<uint32_t>(task.period / Constants::NS_IN_MS), + task.activationStats.getStats(), + task.periodStats.getStats(), + task.workloadStats.getStats(), + task.missedEvents, + task.failedEvents}; } miosix::FastMutex mutex; ///< Mutex to protect tasks and agenda. diff --git a/src/shared/utils/Constants.h b/src/shared/utils/Constants.h index 87481cb6a6df7fa7a73cbbdf89a22167fa6d01e3..c6b492a0b4d8664c0bcc9cf2ebd6fcfa002dd79a 100644 --- a/src/shared/utils/Constants.h +++ b/src/shared/utils/Constants.h @@ -51,6 +51,9 @@ static constexpr float MSL_TEMPERATURE = 288.15f; // [Kelvin] static constexpr float B21_LATITUDE = 45.501141; static constexpr float B21_LONGITUDE = 9.156281; + +static constexpr long long NS_IN_MS = 1000000ll; // Nanoseconds in 1 ms +static constexpr long long NS_IN_S = 1000000000ll; // Nanoseconds in 1 s } // namespace Constants } // namespace Boardcore