1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include <gtest/gtest_prod.h>
19 #include <stdint.h>
20 #include <algorithm>
21 
22 namespace android {
23 namespace os {
24 namespace statsd {
25 
26 /**
27  * A simple stopwatch to time the duration of condition being true.
28  *
29  * The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition
30  * changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps
31  * should be elapsedRealTime in nano seconds.
32  *
33  * Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is
34  * responsible for thread safety.
35  */
36 class ConditionTimer {
37 public:
ConditionTimer(bool initCondition,int64_t bucketStartNs)38     explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) {
39         if (initCondition) {
40             mLastConditionChangeTimestampNs = bucketStartNs;
41         }
42     };
43 
44     // Tracks how long the condition has been stayed true in the *current* bucket.
45     // When a new bucket is created, this value will be reset to 0.
46     int64_t mTimerNs = 0;
47 
48     /** Tracks the delay prior current bucket start due to delayed bucket close. */
49     int64_t mCurrentBucketStartDelayNs = 0;
50 
51     // Last elapsed real timestamp when condition changed.
52     int64_t mLastConditionChangeTimestampNs = 0;
53 
54     bool mCondition = false;
55 
56     struct ConditionDurationInfo {
57         int64_t mDurationNs;
58         int64_t mCorrectionNs;
59 
60         inline bool operator==(const ConditionDurationInfo& that) const {
61             return mDurationNs == that.mDurationNs && mCorrectionNs == that.mCorrectionNs;
62         }
63     };
64 
65     /**
66      * Handles new bucket event processing and performs condition duration calculation
67      * In case if next bucket start timestamp differs from event timestamp, the
68      * correction calculation will be performed, due to delayed bucket close
69      * \param eventTimeNs current timestamp
70      * \param nextBucketStartNs next bucket start expected timestamp
71      * \return The condition duration and correction in nanoseconds for the previous bucket
72      */
newBucketStart(int64_t eventTimeNs,int64_t nextBucketStartNs)73     ConditionDurationInfo newBucketStart(int64_t eventTimeNs, int64_t nextBucketStartNs) {
74         // we would like to apply correction only in case
75         // - when condition was true before new bucket start (pull event often the case)
76         // - and remains true after the edge
77         // here the mCondition represents current condition, which could be updated
78         // based on onConditionChange() event
79 
80         int64_t conditionCorrectionNs = -mCurrentBucketStartDelayNs;
81         mCurrentBucketStartDelayNs = 0;
82 
83         const int64_t currentBucketEndDelayNs =
84                 std::max(eventTimeNs - nextBucketStartNs, (int64_t)0);
85 
86         if (mCondition) {
87             // Normally, the next bucket happens after the last condition
88             // change. In this case, add the time between the condition becoming
89             // true to the next bucket start time.
90             // Otherwise, the next bucket start time is before the last
91             // condition change time, this means that the condition was false at
92             // the bucket boundary before the condition became true, so the
93             // timer should not get updated and the last condition change time
94             // remains as is.
95             if (nextBucketStartNs >= mLastConditionChangeTimestampNs) {
96                 mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs);
97                 mLastConditionChangeTimestampNs = nextBucketStartNs;
98                 conditionCorrectionNs += currentBucketEndDelayNs;
99 
100                 // keep start delay correction for the next bucket - condition was true
101                 // before the edge and remains true after the edge
102                 mCurrentBucketStartDelayNs = currentBucketEndDelayNs;
103             }
104         } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) {
105             // The next bucket start time is before the last condition change
106             // time, this means that the condition was true at the bucket
107             // boundary before the condition became false, so adjust the timer
108             // to match how long the condition was true to the bucket boundary.
109             // This means remove the amount the condition stayed true in the
110             // next bucket from the current bucket.
111             mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs);
112             conditionCorrectionNs += currentBucketEndDelayNs;
113 
114             // keep start delay correction for the next bucket - condition was true
115             // before the edge and remains true after the edge up to delay
116             mCurrentBucketStartDelayNs = currentBucketEndDelayNs;
117         }
118 
119         const int64_t conditionDurationNs = mTimerNs;
120         mTimerNs = 0;
121 
122         if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) {
123             // The next bucket start time is before the last condition change
124             // time, this means that the condition was true at the bucket
125             // boundary and remained true in the next bucket up to the condition
126             // change to false, so adjust the timer to match how long the
127             // condition stayed true in the next bucket (now the current bucket).
128             mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs;
129         }
130         return {conditionDurationNs, conditionCorrectionNs};
131     }
132 
onConditionChanged(bool newCondition,int64_t timestampNs)133     void onConditionChanged(bool newCondition, int64_t timestampNs) {
134         if (newCondition == mCondition) {
135             return;
136         }
137         mCondition = newCondition;
138         if (newCondition == false) {
139             mTimerNs += (timestampNs - mLastConditionChangeTimestampNs);
140         }
141         mLastConditionChangeTimestampNs = timestampNs;
142     }
143 
144     FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False);
145     FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True);
146     FRIEND_TEST(ConditionTimerTest, TestTimer_Correction_DelayedChangeToFalse);
147     FRIEND_TEST(ConditionTimerTest, TestTimer_Correction_DelayedChangeToTrue);
148     FRIEND_TEST(ConditionTimerTest, TestTimer_Correction_DelayedWithInitialFalse);
149     FRIEND_TEST(ConditionTimerTest, TestTimer_Correction_DelayedWithInitialTrue);
150 };
151 
152 }  // namespace statsd
153 }  // namespace os
154 }  // namespace android
155