LCOV - code coverage report
Current view: top level - widget - SystemTimeConverter.h (source / functions) Hit Total Coverage
Test: output.info Lines: 41 47 87.2 %
Date: 2017-07-14 16:53:18 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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           1 :   SystemTimeConverter()
      32             :     : mReferenceTime(Time(0))
      33             :     , mReferenceTimeStamp() // Initializes to the null timestamp
      34             :     , mLastBackwardsSkewCheck(Time(0))
      35           1 :     , kTimeRange(std::numeric_limits<Time>::max())
      36           1 :     , kTimeHalfRange(kTimeRange / 2)
      37           3 :     , kBackwardsSkewCheckInterval(Time(2000))
      38             :   {
      39             :     static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
      40           1 :   }
      41             : 
      42             :   template <typename CurrentTimeGetter>
      43             :   mozilla::TimeStamp
      44           6 :   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           6 :     if (mReferenceTimeStamp.IsNull()) {
      49           1 :       UpdateReferenceTime(aTime, aCurrentTimeGetter);
      50             :     }
      51           6 :     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           6 :     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           6 :     if (newer) {
      96             :       // Make aTime correspond to roughlyNow
      97           0 :       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           0 :       mLastBackwardsSkewCheck = aTime;
     102             : 
     103           0 :       return roughlyNow;
     104             :     }
     105             : 
     106           6 :     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           2 :       mLastBackwardsSkewCheck = aTime;
     111           4 :     } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
     112           2 :       aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
     113           2 :       mLastBackwardsSkewCheck = aTime;
     114             :     }
     115             : 
     116             :     // Finally, calculate the timestamp
     117           6 :     return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
     118             :   }
     119             : 
     120             :   void
     121           1 :   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           1 :     if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) {
     158           0 :       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           1 :     UpdateReferenceTime(aReferenceTime, aLowerBound);
     170             :   }
     171             : 
     172             : private:
     173             :   template <typename CurrentTimeGetter>
     174             :   void
     175           1 :   UpdateReferenceTime(Time aReferenceTime,
     176             :                       const CurrentTimeGetter& aCurrentTimeGetter) {
     177           1 :     Time currentTime = aCurrentTimeGetter.GetCurrentTime();
     178           1 :     TimeStamp currentTimeStamp = TimeStamp::Now();
     179           1 :     Time timeSinceReference = currentTime - aReferenceTime;
     180             :     TimeStamp referenceTimeStamp =
     181           1 :       currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
     182           1 :     UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
     183           1 :   }
     184             : 
     185             :   void
     186           2 :   UpdateReferenceTime(Time aReferenceTime,
     187             :                       const TimeStamp& aReferenceTimeStamp) {
     188           2 :     mReferenceTime = aReferenceTime;
     189           2 :     mReferenceTimeStamp = aReferenceTimeStamp;
     190           2 :   }
     191             : 
     192             :   bool
     193           7 :   IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp, Time* aDelta)
     194             :   {
     195           7 :     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           7 :       static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds());
     205             : 
     206           7 :     Time timeToTimeStamp = timeStampDelta - timeDelta;
     207           7 :     bool isNewer = false;
     208           7 :     if (timeToTimeStamp == 0) {
     209           2 :       *aDelta = 0;
     210           5 :     } else if (timeToTimeStamp < kTimeHalfRange) {
     211           5 :       *aDelta = timeToTimeStamp;
     212             :     } else {
     213           0 :       isNewer = true;
     214           0 :       *aDelta = timeDelta - timeStampDelta;
     215             :     }
     216             : 
     217           7 :     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 */

Generated by: LCOV version 1.13