Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2023 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #pragma once
20 :
21 : #include <app/reporting/ReportSchedulerImpl.h>
22 :
23 : namespace chip {
24 : namespace app {
25 : namespace reporting {
26 :
27 : using Timeout = System::Clock::Timeout;
28 : using Timestamp = System::Clock::Timestamp;
29 : using Milliseconds64 = System::Clock::Milliseconds64;
30 : using ReadHandlerNode = ReportScheduler::ReadHandlerNode;
31 : using TimerDelegate = ReportScheduler::TimerDelegate;
32 :
33 : /**
34 : * @class Synchronized ReportSchedulerImpl
35 : *
36 : * @brief This class extends ReportSchedulerImpl and overrides its scheduling logic.
37 : *
38 : * It overrides the OnTransitionToIdle methods from ReadHandler::Observer.
39 : *
40 : * It inherits from TimerContext so that it can be used as a TimerDelegate instead of relying on the nodes to schedule themselves.
41 : *
42 : * ## Scheduling Logic
43 : *
44 : * This class implements a scheduling logic that aims to make all ReadHandlers report at the same time when possible.
45 : * The goal is to minimize the number of times a device wakes up to report, and thus this aims to schedule all reports at the latest
46 : * possible time while ensuring that all reports get sent before their max interval.
47 : *
48 : * The logic also aims to minimize the impact on the responsiveness of the device.
49 : *
50 : * The scheduling logic is as follows:
51 : * - The CalculateNextReportTimeout is called by any ReadHandler methods that affect when/whether a report should be sent. These
52 : * are:
53 : * * OnSubscriptionEstablished,
54 : * * OnBecameReportable,
55 : * * OnSubscriptionReportSent
56 : *
57 : * - The Synchronized Scheduler keeps track of the next min and max interval timestamps and updates them in
58 : * CalculateNextReportTimeout
59 : *
60 : * - The next max interval is calculated as the earliest max interval of all the registered ReadHandlersNodes.
61 : *
62 : * - The next min interval is calculated as the latest min interval of the registered ReadHandlersNodes that:
63 : * * Have a min timestamp greater than the current time
64 : * * Are Reportable (this prevents a ReadHandler that is not reportable from blocking the reporting of other ReadHandlers)
65 : * TODO: Assess if we want to keep this behavior or simply let the min interval be the earliest min interval to prevent cases
66 : * where a ReadHandler with a dirty path but a very high min interval blocks all reports
67 : * - If no ReadHandlerNode matches the min interval criteria, the next min interval is set to the current timestamp.
68 : *
69 : * - The next report timeout is calculated in CalculatedNextReportTimeout based on the next min and max interval timestamps, as well
70 : * as the status of each ReadHandlerNode in the pool.
71 : *
72 : * @note Unlike the non-synchronized implementation, the Synchronized Scheduler will reschedule itself in the event that a timer
73 : * fires before a reportable timestamp is reached.
74 : *
75 : * @note In this implementation, nodes still keep track of their own min and max interval timestamps.
76 : */
77 : class SynchronizedReportSchedulerImpl : public ReportSchedulerImpl, public TimerContext
78 : {
79 : public:
80 : void OnReadHandlerDestroyed(ReadHandler * aReadHandler) override;
81 :
82 : SynchronizedReportSchedulerImpl(TimerDelegate * aTimerDelegate) : ReportSchedulerImpl(aTimerDelegate) {}
83 0 : ~SynchronizedReportSchedulerImpl() override { UnregisterAllHandlers(); }
84 :
85 : void OnTransitionToIdle() override;
86 :
87 : bool IsReportScheduled(ReadHandler * ReadHandler) override;
88 :
89 : /** @brief Callback called when the report timer expires to schedule an engine run regardless of the state of the ReadHandlers,
90 : *
91 : * It loops through all handlers and sets their CanBeSynced flag to true if the current timestamp is greater than
92 : * their respective minimal timestamps.
93 : *
94 : * While looping, it checks if any handler is reportable now. If not, we recalculate the next report timeout and reschedule the
95 : * report.
96 : *
97 : * If a Readhandler is reportable now, an engine run is scheduled.
98 : *
99 : * If the timer expires after all nodes are unregistered, no action is taken.
100 : */
101 : void TimerFired() override;
102 :
103 : protected:
104 : /**
105 : * @brief Schedule a report for the Scheduler.
106 : *
107 : * If a report is already scheduled, cancel it and schedule a new one.
108 : *
109 : * @param[in] timeout The delay before the report will happen.
110 : * @param[in] node The node associated with the ReadHandler.
111 : * @param[in] now The current system timestamp.
112 : *
113 : * @return CHIP_ERROR CHIP_NO_ERROR on success, timer-related error code otherwise (This can only fail on starting the timer)
114 : */
115 : CHIP_ERROR ScheduleReport(System::Clock::Timeout timeout, ReadHandlerNode * node, const Timestamp & now) override;
116 : void CancelReport();
117 :
118 : private:
119 : friend class chip::app::reporting::TestReportScheduler;
120 :
121 : /**
122 : * @brief Find the highest minimum timestamp possible that still respects the lowest max timestamp and sets it as the common
123 : * 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
124 : * timestamp, this will set the "now" parameter as the common minimum timestamp, thus allowing the report to be sent
125 : * immediately.
126 : *
127 : * @param[in] now The current system timestamp, set by the event that triggered the call of this method.
128 : *
129 : * @return CHIP_ERROR on success or CHIP_ERROR_INVALID_LIST_LENGTH if the list is empty
130 : */
131 : CHIP_ERROR FindNextMinInterval(const Timestamp & now);
132 :
133 : /**
134 : * @brief Find the smallest maximum interval possible and set it as the common maximum
135 : *
136 : * @param[in] now The current system timestamp, set by the event that triggered the call of this method.
137 : *
138 : * @return CHIP_ERROR on success or CHIP_ERROR_INVALID_LIST_LENGTH if the list is empty
139 : */
140 : CHIP_ERROR FindNextMaxInterval(const Timestamp & now);
141 :
142 : /**
143 : * @brief Calculate the next report timeout for all ReadHandlerNodes
144 : *
145 : * @param[out] timeout The timeout to calculate.
146 : * @param[in] aReadHandlerNode unused, kept to preserve the signature of the base class
147 : * @param[in] now The current system timestamp when the event leading to the call of this method happened.
148 : *
149 : * The next report timeout is calculated by looping through all the ReadHandlerNodes and finding if any are reportable now
150 : * or at min.
151 : * * If a ReadHandlerNode is reportable now, the timeout is set to 0.
152 : * * If a ReadHandlerNode is reportable at min, the timeout is set to the difference between the Scheduler's min timestamp
153 : * and the current time.
154 : * * If no ReadHandlerNode is reportable, the timeout is set to the difference between the Scheduler's max timestamp and the
155 : * current time.
156 : @note When looping through the ReadHandlerNodes, the IsEngineRunScheduled flag is used to prevent calling ScheduleRun on a
157 : ReadHandler that already has an engine run scheduled, which would cause an endless report loop in some cases. The only
158 : reason why we would want to call ScheduleRun on a node that already has an engine run scheduled is if the ongoing report
159 : is chunked, which means that the report is not fully sent yet and that the EngineRun should be scheduled again until
160 : there are no chunks left.
161 :
162 : The Endless Reporting Loop Scenario would be:
163 : 1. At least two ReadHandlers are registered to the Scheduler
164 : 2. ScheduleRun() is called with 2 reportable ReadHandlers (meaning they both return true to IsReportableNow())
165 : 3. The Scheduler sends the first report and calls OnSubscriptionReportSent on the ReadHandler
166 : 4. OnSubscriptionReportSent calls CalculateNextReportTimeout, which loops through all ReadHandlers and finds that a least
167 : one ReadHandler is reportable now, and thus sets the timeout to 0.
168 : 5. OnSubscriptionReportSent then calls ScheduleReport with a timeout of 0, which calls TimerFired on the Scheduler
169 : 6. If the MinInterval of the ReadHandler is 0, the Scheduler will set the CanBeSynced flag to true, and the
170 : IsReportableNow Will return true since (now >= MinTimestamp || CanBeSynced()) will be true.
171 : 7. ScheduleRun() will be called on the ReadHandler with 2 reportable ReadHandlers, and the loop will start again.
172 : *
173 : */
174 : CHIP_ERROR CalculateNextReportTimeout(Timeout & timeout, ReadHandlerNode * aReadHandlerNode, const Timestamp & now) override;
175 :
176 : Timestamp mNextMaxTimestamp = Milliseconds64(0);
177 : Timestamp mNextMinTimestamp = Milliseconds64(0);
178 :
179 : // Timestamp of the next report to be scheduled, used by OnTransitionToIdle to determine whether we should emit a report before
180 : // the device goes to idle mode
181 : Timestamp mNextReportTimestamp = Milliseconds64(0);
182 : };
183 :
184 : } // namespace reporting
185 : } // namespace app
186 : } // namespace chip
|