blob: bc8573b173541c817a054b3819bafe99fa199bb2 [file] [log] [blame]
/*
*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <app/reporting/ReportSchedulerImpl.h>
namespace chip {
namespace app {
namespace reporting {
using Timeout = System::Clock::Timeout;
using Timestamp = System::Clock::Timestamp;
using Milliseconds64 = System::Clock::Milliseconds64;
using ReadHandlerNode = ReportScheduler::ReadHandlerNode;
using TimerDelegate = ReportScheduler::TimerDelegate;
/**
* @class Synchronized ReportSchedulerImpl
*
* @brief This class extends ReportSchedulerImpl and overrides it's scheduling logic.
*
* It only overrides Observers method where the scheduling logic make it necessary, the others are kept as is.
*
* It inherits from TimerContext so that it can be used as a TimerDelegate instead on relying on the nodes to schedule themselves.
*
* ## Scheduling Logic
*
* This class implements a scheduling logic that aims to make all ReadHandlers report at the same time when possible.
* The goal is to minimize the different times a device wakes up to report, and thus this aims to schedule all reports at the latest
* possible time while ensuring that all reports get sent before their max interval.
*
* The logic also aims to minimize the impact on the responsivity of the device.
*
* The scheduling logic is as follows:
* - The CalculateNextReportTimeout is called by the same ReadHandler Observer callbacks than the non-synchronized implementation:
* * OnSubscriptionEstablished,
* * OnBecameReportable,
* * OnSubscriptionReportSent
*
* - The Synchronized Scheduler keeps track of the next min and max interval timestamps. It updates in CalculateNextReportTimeout
*
* - The next max interval is calculated as the earliest max interval of all the registered ReadHandlersNodes.
*
* - The next min interval is calculated as the latest min interval of the registered ReadHandlersNodes that:
* * Have a min timestamp greater than the current time
* * Are Reportable (this prevents a ReadHandler that is not reportable to hold the report of all the others)
* TODO: Assess if we want to keep this behavior or simply let the min interval be the earliest min interval to prevent cases
* where a ReadHandler with a dirty path but a very high min interval blocks all reports
* - If no ReadHandlerNode matches min interval the criteria, the next min interval is set to current timestamp.
*
* - The next report timeout is calculated in CalculatedNextReportTimeout based on the next min and max interval timestamps, as well
* as the status of each ReadHandlerNode in the pool.
*
* @note Unlike the non-synchronized implementation, the Synchronized Scheduler will reschedule itself in the event where a timer
* fires before a reportable timestamp is reached.
*
* @note In this implementation, nodes still keep track of their own min and max interval timestamps.
*/
class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl, public TimerContext
{
public:
void OnReadHandlerDestroyed(ReadHandler * aReadHandler) override;
SynchronizedReportSchedulerImpl(TimerDelegate * aTimerDelegate) : ReportSchedulerImpl(aTimerDelegate) {}
~SynchronizedReportSchedulerImpl() override { UnregisterAllHandlers(); }
void OnTransitionToIdle() override;
bool IsReportScheduled(ReadHandler * ReadHandler) override;
/** @brief Callback called when the report timer expires to schedule an engine run regardless of the state of the ReadHandlers,
*
* It loops through all handlers and sets their CanBeSynced flag to true if the current timstamp is greater than
* their respective minimal timestamps.
*
* While looping, it checks if any handler is reportable now. If not, we recalculate the next report timeout and reschedule the
* report.
*
* If a Readhangler is reportable now, an engine run is scheduled.
*
* If the timer expires after all nodes were unregistered, no action is taken.
*/
void TimerFired() override;
protected:
/**
* @brief Schedule a report for the Scheduler.
*
* If a report is already scheduled, cancel it and schedule a new one.
*
* @param[in] timeout The timeout to schedule the report.
* @param[in] node The node associated to the ReadHandler.
* @param[in] now The current system timestamp.
*
* @return CHIP_ERROR CHIP_NO_ERROR on success, timer related error code otherwise (This can only fail on starting the timer)
*/
CHIP_ERROR ScheduleReport(System::Clock::Timeout timeout, ReadHandlerNode * node, const Timestamp & now) override;
void CancelReport();
private:
friend class chip::app::reporting::TestReportScheduler;
/**
* @brief Find the highest minimum timestamp possible that still respects the lowest max timestamp and sets it as the common
* minimum. If the max timestamp has not been updated and is in the past, or if no min timestamp is lower than the current max
* timestamp, this will set now as the common minimum timestamp, thus allowing the report to be sent immediately.
*
* @param[in] now The current system timestamp, set by the event that triggered the call of this method.
*
* @return CHIP_ERROR on success or CHIP_ERROR_INVALID_LIST_LENGTH if the list is empty
*/
CHIP_ERROR FindNextMinInterval(const Timestamp & now);
/**
* @brief Find the smallest maximum interval possible and set it as the common maximum
*
* @param[in] now The current system timestamp, set by the event that triggered the call of this method.
*
* @return CHIP_ERROR on success or CHIP_ERROR_INVALID_LIST_LENGTH if the list is empty
*/
CHIP_ERROR FindNextMaxInterval(const Timestamp & now);
/**
* @brief Calculate the next report timeout for all ReadHandlerNodes
*
* @param[out] timeout The timeout to calculate.
* @param[in] aReadHandlerNode unused, kept to preserve the signature of the base class
* @param[in] now The current system timestamp when the event leading to the call of this method happened.
*
* The next report timeout is calculated by looping through all the ReadHandlerNodes and finding if any are reportable now
* or at min.
* * If a ReadHandlerNode is reportable now, the timeout is set to 0.
* * If a ReadHandlerNode is reportable at min, the timeout is set to the difference between the Scheduler's min timestamp
* and the current time.
* * If no ReadHandlerNode is reportable, the timeout is set to the difference between the Scheduler's max timestamp and the
* current time.
*
* @note Since this method is called after the OnSubscriptionReportSent callback, to avoid an endless reporting loop, Nodes with
* the IsEngineRunScheduled flag set are ignored when finding if the Scheduler should report at min, max or now.
*
* @note If a ReadHandler's report is Chunked, the IsEngineRunScheduled is ignored since we do want to keep rescheduling the
* report to the now timestamp until it is fully sent. IsChunkedReport is used to prevent starting a chunked report and
* then waiting on the max interval after the first chunk is sent.
*/
CHIP_ERROR CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aReadHandlerNode, const Timestamp & now) override;
Timestamp mNextMaxTimestamp = Milliseconds64(0);
Timestamp mNextMinTimestamp = Milliseconds64(0);
// Timestamp of the next report to be scheduled, used by OnTransitionToIdle to determine whether we should emit a report before
// the device goes to idle mode
Timestamp mNextReportTimestamp = Milliseconds64(0);
};
} // namespace reporting
} // namespace app
} // namespace chip