1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef SystemTimeConverter_h 7 #define SystemTimeConverter_h 8 9 #include <limits> 10 #include "mozilla/TimeStamp.h" 11 #include "mozilla/TypeTraits.h" 12 13 namespace mozilla { 14 15 // Utility class that converts time values represented as an unsigned integral 16 // number of milliseconds from one time source (e.g. a native event time) to 17 // corresponding mozilla::TimeStamp objects. 18 // 19 // This class handles wrapping of integer values and skew between the time 20 // source and mozilla::TimeStamp values. 21 // 22 // It does this by using an historical reference time recorded in both time 23 // scales (i.e. both as a numerical time value and as a TimeStamp). 24 // 25 // For performance reasons, this class is careful to minimize calls to the 26 // native "current time" function (e.g. gdk_x11_server_get_time) since this can 27 // be slow. 28 template <typename Time> 29 class SystemTimeConverter { 30 public: 31 SystemTimeConverter() 32 : mReferenceTime(Time(0)) 33 , mReferenceTimeStamp() // Initializes to the null timestamp 34 , mLastBackwardsSkewCheck(Time(0)) 35 , kTimeRange(std::numeric_limits<Time>::max()) 36 , kTimeHalfRange(kTimeRange / 2) 37 , kBackwardsSkewCheckInterval(Time(2000)) 38 { 39 static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned"); 40 } 41 42 template <typename CurrentTimeGetter> 43 mozilla::TimeStamp 44 GetTimeStampFromSystemTime(Time aTime, 45 CurrentTimeGetter& aCurrentTimeGetter) { 46 // If the reference time is not set, use the current time value to fill 47 // it in. 48 if (mReferenceTimeStamp.IsNull()) { 49 UpdateReferenceTime(aTime, aCurrentTimeGetter); 50 } 51 TimeStamp roughlyNow = TimeStamp::Now(); 52 53 // Check for skew between the source of Time values and TimeStamp values. 54 // We do this by comparing two durations (both in ms): 55 // 56 // i. The duration from the reference time to the passed-in time. 57 // (timeDelta in the diagram below) 58 // ii. The duration from the reference timestamp to the current time 59 // based on TimeStamp::Now. 60 // (timeStampDelta in the diagram below) 61 // 62 // Normally, we'd expect (ii) to be slightly larger than (i) to account 63 // for the time taken between generating the event and processing it. 64 // 65 // If (ii) - (i) is negative then the source of Time values is getting 66 // "ahead" of TimeStamp. We call this "forwards" skew below. 67 // 68 // For the reverse case, if (ii) - (i) is positive (and greater than some 69 // tolerance factor), then we may have "backwards" skew. This is often 70 // the case when we have a backlog of events and by the time we process 71 // them, the time given by the system is comparatively "old". 72 // 73 // We call the absolute difference between (i) and (ii), "deltaFromNow". 74 // 75 // Graphically: 76 // 77 // mReferenceTime aTime 78 // Time scale: ........+.......................*........ 79 // |--------timeDelta------| 80 // 81 // mReferenceTimeStamp roughlyNow 82 // TimeStamp scale: ........+...........................*.... 83 // |------timeStampDelta-------| 84 // 85 // |---| 86 // deltaFromNow 87 // 88 Time deltaFromNow; 89 bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow); 90 91 // Tolerance when detecting clock skew. 92 static const Time kTolerance = 30; 93 94 // Check for forwards skew 95 if (newer) { 96 // Make aTime correspond to roughlyNow 97 UpdateReferenceTime(aTime, roughlyNow); 98 99 // We didn't have backwards skew so don't bother checking for 100 // backwards skew again for a little while. 101 mLastBackwardsSkewCheck = aTime; 102 103 return roughlyNow; 104 } 105 106 if (deltaFromNow <= kTolerance) { 107 // If the time between event times and TimeStamp values is within 108 // the tolerance then assume we don't have clock skew so we can 109 // avoid checking for backwards skew for a while. 110 mLastBackwardsSkewCheck = aTime; 111 } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) { 112 aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow); 113 mLastBackwardsSkewCheck = aTime; 114 } 115 116 // Finally, calculate the timestamp 117 return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow); 118 } 119 120 void 121 CompensateForBackwardsSkew(Time aReferenceTime, 122 const TimeStamp &aLowerBound) { 123 // Check if we actually have backwards skew. Backwards skew looks like 124 // the following: 125 // 126 // mReferenceTime 127 // Time: ..+...a...b...c.......................... 128 // 129 // mReferenceTimeStamp 130 // TimeStamp: ..+.....a.....b.....c.................... 131 // 132 // Converted 133 // time: ......a'..b'..c'......................... 134 // 135 // What we need to do is bring mReferenceTime "forwards". 136 // 137 // Suppose when we get (c), we detect possible backwards skew and trigger 138 // an async request for the current time (which is passed in here as 139 // aReferenceTime). 140 // 141 // We end up with something like the following: 142 // 143 // mReferenceTime aReferenceTime 144 // Time: ..+...a...b...c...v...................... 145 // 146 // mReferenceTimeStamp 147 // TimeStamp: ..+.....a.....b.....c..........x......... 148 // ^ ^ 149 // aLowerBound TimeStamp::Now() 150 // 151 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than 152 // (aReferenceTime - mReferenceTime) then we know we have backwards skew. 153 // 154 // If that's not the case, then we probably just got caught behind 155 // temporarily. 156 Time delta; 157 if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) { 158 return; 159 } 160 161 // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies 162 // somewhere between aLowerBound (which was the TimeStamp when we triggered 163 // the async request for the current time) and TimeStamp::Now(). 164 // 165 // If aReferenceTime was waiting in the event queue for a long time, the 166 // equivalent TimeStamp might be much closer to aLowerBound than 167 // TimeStamp::Now() so for now we just set it to aLowerBound. That's 168 // guaranteed to be at least somewhat of an improvement. 169 UpdateReferenceTime(aReferenceTime, aLowerBound); 170 } 171 172 private: 173 template <typename CurrentTimeGetter> 174 void 175 UpdateReferenceTime(Time aReferenceTime, 176 const CurrentTimeGetter& aCurrentTimeGetter) { 177 Time currentTime = aCurrentTimeGetter.GetCurrentTime(); 178 TimeStamp currentTimeStamp = TimeStamp::Now(); 179 Time timeSinceReference = currentTime - aReferenceTime; 180 TimeStamp referenceTimeStamp = 181 currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference); 182 UpdateReferenceTime(aReferenceTime, referenceTimeStamp); 183 } 184 185 void 186 UpdateReferenceTime(Time aReferenceTime, 187 const TimeStamp& aReferenceTimeStamp) { 188 mReferenceTime = aReferenceTime; 189 mReferenceTimeStamp = aReferenceTimeStamp; 190 } 191 192 bool 193 IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp, Time* aDelta) 194 { 195 Time timeDelta = aTime - mReferenceTime; 196 197 // Cast the result to signed 64-bit integer first since that should be 198 // enough to hold the range of values returned by ToMilliseconds() and 199 // the result of converting from double to an integer-type when the value 200 // is outside the integer range is undefined. 201 // Then we do an implicit cast to Time (typically an unsigned 32-bit 202 // integer) which wraps times outside that range. 203 Time timeStampDelta = 204 static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds()); 205 206 Time timeToTimeStamp = timeStampDelta - timeDelta; 207 bool isNewer = false; 208 if (timeToTimeStamp == 0) { 209 *aDelta = 0; 210 } else if (timeToTimeStamp < kTimeHalfRange) { 211 *aDelta = timeToTimeStamp; 212 } else { 213 isNewer = true; 214 *aDelta = timeDelta - timeStampDelta; 215 } 216 217 return isNewer; 218 } 219 220 Time mReferenceTime; 221 TimeStamp mReferenceTimeStamp; 222 Time mLastBackwardsSkewCheck; 223 224 const Time kTimeRange; 225 const Time kTimeHalfRange; 226 const Time kBackwardsSkewCheckInterval; 227 }; 228 229 } // namespace mozilla 230 231 #endif /* SystemTimeConverter_h */ 232