14
14
15
15
package com .google .firebase .perf .util ;
16
16
17
+ import static java .util .concurrent .TimeUnit .MILLISECONDS ;
18
+ import static java .util .concurrent .TimeUnit .NANOSECONDS ;
19
+
20
+ import android .os .Build ;
17
21
import android .os .Parcel ;
18
22
import android .os .Parcelable ;
23
+ import android .os .SystemClock ;
19
24
import androidx .annotation .NonNull ;
20
25
import com .google .android .gms .common .util .VisibleForTesting ;
21
- import java .util .concurrent .TimeUnit ;
22
26
23
- /** A Timer class provides both wall-clock (epoch) time and high resolution time (nano time). */
27
+ /**
28
+ * A Timer class provides both wall-clock (epoch) time and monotonic time (elapsedRealtime).
29
+ * Timestamps are captured with millisecond-precision, because that's the time unit most widely
30
+ * available in Android APIs. However, private fields and public method returns are in microseconds
31
+ * due to Fireperf proto requirements.
32
+ */
24
33
public class Timer implements Parcelable {
25
34
26
- /** Wall-clock time or epoch time in microseconds, */
27
- private long timeInMicros ;
28
35
/**
29
- * High resolution time in nanoseconds. High resolution time should only be used to calculate
30
- * duration or latency. It is not wall-clock time.
36
+ * Wall-clock time or epoch time in microseconds. Do NOT use for duration because wall-clock is
37
+ * not guaranteed to be monotonic: it can be set by the user or the phone network, thus it may
38
+ * jump forwards or backwards unpredictably. {@see SystemClock}
39
+ */
40
+ private long wallClockMicros ;
41
+ /**
42
+ * Monotonic time measured in the {@link SystemClock#elapsedRealtime()} timebase. Only used to
43
+ * compute duration between 2 timestamps in the same timebase. It is NOT wall-clock time.
44
+ */
45
+ private long elapsedRealtimeMicros ;
46
+
47
+ /**
48
+ * Returns a new Timer object as if it was stamped at the given elapsedRealtime. Uses current
49
+ * wall-clock as a reference to extrapolate the wall-clock at the given elapsedRealtime.
50
+ *
51
+ * @param elapsedRealtimeMillis timestamp in the {@link SystemClock#elapsedRealtime()} timebase
52
+ */
53
+ public static Timer ofElapsedRealtime (final long elapsedRealtimeMillis ) {
54
+ long elapsedRealtimeMicros = MILLISECONDS .toMicros (elapsedRealtimeMillis );
55
+ long wallClockMicros = wallClockMicros () + (elapsedRealtimeMicros - elapsedRealtimeMicros ());
56
+ return new Timer (wallClockMicros , elapsedRealtimeMicros );
57
+ }
58
+
59
+ /**
60
+ * Helper to get current wall-clock time from system API.
61
+ *
62
+ * @return wall-clock time in microseconds.
31
63
*/
32
- private long highResTime ;
64
+ private static long wallClockMicros () {
65
+ return MILLISECONDS .toMicros (System .currentTimeMillis ());
66
+ }
33
67
34
68
/**
35
- * Construct Timer object using System clock. Make it package visible to be only accessible from
36
- * com.google.firebase.perf.util.Clock.
69
+ * Helper to get current {@link SystemClock#elapsedRealtime()} from system API.
70
+ *
71
+ * @return wall-clock time in microseconds.
37
72
*/
73
+ private static long elapsedRealtimeMicros () {
74
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN_MR1 ) {
75
+ return NANOSECONDS .toMicros (SystemClock .elapsedRealtimeNanos ());
76
+ }
77
+ return MILLISECONDS .toMicros (SystemClock .elapsedRealtime ());
78
+ }
79
+
80
+ // TODO: make all constructors private, use public static factory methods, per Effective Java
81
+ /** Construct Timer object using System clock. */
38
82
public Timer () {
39
- timeInMicros = TimeUnit .MILLISECONDS .toMicros (System .currentTimeMillis ());
40
- highResTime = System .nanoTime ();
83
+ this (wallClockMicros (), elapsedRealtimeMicros ());
41
84
}
42
85
43
86
/**
44
- * Construct a Timer object with input wall-clock time, assume high resolution time is same as
45
- * wall-clock time.
87
+ * Construct a Timer object with input wall-clock time and elapsedRealtime.
46
88
*
47
- * @param time wall-clock time in microseconds
89
+ * @param epochMicros wall-clock time in milliseconds since epoch
90
+ * @param elapsedRealtimeMicros monotonic time in microseconds in the {@link
91
+ * SystemClock#elapsedRealtime()} timebase
48
92
*/
49
93
@ VisibleForTesting
50
- public Timer (long time ) {
51
- this .timeInMicros = time ;
52
- highResTime = TimeUnit . MICROSECONDS . toNanos ( time ) ;
94
+ Timer (long epochMicros , long elapsedRealtimeMicros ) {
95
+ this .wallClockMicros = epochMicros ;
96
+ this . elapsedRealtimeMicros = elapsedRealtimeMicros ;
53
97
}
54
98
55
99
/**
56
- * Construct a Timer object with input wall-clock time and high resolution time.
100
+ * TEST-ONLY constructor that sets both wall-clock time and elapsedRealtime to the same input
101
+ * value. Do NOT use this for any real logic because this is mixing 2 different time-bases.
57
102
*
58
- * @param time wall-clock time in microseconds
59
- * @param highResTime high resolution time in nanoseconds
103
+ * @param testTime value to set both wall-clock and elapsedRealtime to for testing purposes
60
104
*/
61
105
@ VisibleForTesting
62
- public Timer (long time , long highResTime ) {
63
- this .timeInMicros = time ;
64
- this .highResTime = highResTime ;
106
+ public Timer (long testTime ) {
107
+ this (testTime , testTime );
65
108
}
66
109
67
110
private Timer (Parcel in ) {
68
- timeInMicros = in .readLong ();
69
- highResTime = in .readLong ();
111
+ this (in .readLong (), in .readLong ());
70
112
}
71
113
72
114
/** resets the start time */
73
115
public void reset () {
74
- timeInMicros = TimeUnit .MILLISECONDS .toMicros (System .currentTimeMillis ());
75
- highResTime = System .nanoTime ();
116
+ // TODO: consider removing this method and make Timer immutable thus fully thread-safe
117
+ wallClockMicros = wallClockMicros ();
118
+ elapsedRealtimeMicros = elapsedRealtimeMicros ();
76
119
}
77
120
78
121
/** Return wall-clock time in microseconds. */
79
122
public long getMicros () {
80
- return timeInMicros ;
123
+ return wallClockMicros ;
81
124
}
82
125
83
126
/**
84
- * Calculate duration in microseconds using the the high resolution time .
127
+ * Calculate duration in microseconds using elapsedRealtime .
85
128
*
86
129
* <p>The start time is this Timer object, end time is current time.
87
130
*
88
131
* @return duration in microseconds.
89
132
*/
90
133
public long getDurationMicros () {
91
- return TimeUnit . NANOSECONDS . toMicros ( System . nanoTime () - this . highResTime );
134
+ return getDurationMicros ( new Timer () );
92
135
}
93
136
94
137
/**
95
- * Calculate duration in microseconds using the the high resolution time. The start time is this
96
- * Timer object.
138
+ * Calculate duration in microseconds using elapsedRealtime. The start time is this Timer object.
97
139
*
98
140
* @param end end Timer object
99
141
* @return duration in microseconds.
100
142
*/
101
143
public long getDurationMicros (@ NonNull final Timer end ) {
102
- return TimeUnit . NANOSECONDS . toMicros ( end .highResTime - this .highResTime ) ;
144
+ return end .elapsedRealtimeMicros - this .elapsedRealtimeMicros ;
103
145
}
104
146
105
147
/**
@@ -111,17 +153,7 @@ public long getDurationMicros(@NonNull final Timer end) {
111
153
* was created.
112
154
*/
113
155
public long getCurrentTimestampMicros () {
114
- return timeInMicros + getDurationMicros ();
115
- }
116
-
117
- /**
118
- * Return high resolution time in microseconds, only useful for testing..
119
- *
120
- * @return high resolution time in microseconds.
121
- */
122
- @ VisibleForTesting
123
- public long getHighResTime () {
124
- return TimeUnit .NANOSECONDS .toMicros (highResTime );
156
+ return wallClockMicros + getDurationMicros ();
125
157
}
126
158
127
159
/**
@@ -132,8 +164,8 @@ public long getHighResTime() {
132
164
* @param flags always will be the value 0.
133
165
*/
134
166
public void writeToParcel (Parcel out , int flags ) {
135
- out .writeLong (timeInMicros );
136
- out .writeLong (highResTime );
167
+ out .writeLong (wallClockMicros );
168
+ out .writeLong (elapsedRealtimeMicros );
137
169
}
138
170
139
171
/**
0 commit comments