Skip to content

Commit 1e58174

Browse files
Move app start trace build logic into a non-main thread. (#3008)
* Move app start trace build logic into a non-main thread. * Fix style * Incorporate comments * Incorporate comments
1 parent a692f63 commit 1e58174

File tree

2 files changed

+99
-53
lines changed

2 files changed

+99
-53
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.android.gms.common.util.VisibleForTesting;
2626
import com.google.firebase.perf.logging.AndroidLogger;
2727
import com.google.firebase.perf.provider.FirebasePerfProvider;
28+
import com.google.firebase.perf.session.PerfSession;
2829
import com.google.firebase.perf.session.SessionManager;
2930
import com.google.firebase.perf.transport.TransportManager;
3031
import com.google.firebase.perf.util.Clock;
@@ -35,6 +36,9 @@
3536
import java.lang.ref.WeakReference;
3637
import java.util.ArrayList;
3738
import java.util.List;
39+
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.LinkedBlockingQueue;
41+
import java.util.concurrent.ThreadPoolExecutor;
3842
import java.util.concurrent.TimeUnit;
3943

4044
/**
@@ -55,7 +59,39 @@ public class AppStartTrace implements ActivityLifecycleCallbacks {
5559

5660
private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMicros(1);
5761

62+
// Core pool size 0 allows threads to shut down if they're idle
63+
private static final int CORE_POOL_SIZE = 0;
64+
private static final int MAX_POOL_SIZE = 1; // Only need single thread
65+
5866
private static volatile AppStartTrace instance;
67+
private static ExecutorService executorService;
68+
69+
private boolean isRegisteredForLifecycleCallbacks = false;
70+
private final TransportManager transportManager;
71+
private final Clock clock;
72+
private Context appContext;
73+
/**
74+
* The first time onCreate() of any activity is called, the activity is saved as launchActivity.
75+
*/
76+
private WeakReference<Activity> launchActivity;
77+
/**
78+
* The first time onResume() of any activity is called, the activity is saved as appStartActivity
79+
*/
80+
private WeakReference<Activity> appStartActivity;
81+
82+
/**
83+
* If the time difference between app starts and creation of any Activity is larger than
84+
* MAX_LATENCY_BEFORE_UI_INIT, set mTooLateToInitUI to true and we don't send AppStart Trace.
85+
*/
86+
private boolean isTooLateToInitUI = false;
87+
88+
private Timer appStartTime = null;
89+
private Timer onCreateTime = null;
90+
private Timer onStartTime = null;
91+
private Timer onResumeTime = null;
92+
93+
private PerfSession startSession;
94+
private boolean isStartedFromBackground = false;
5995

6096
/**
6197
* Called from onCreate() method of an activity by instrumented byte code.
@@ -94,41 +130,29 @@ static AppStartTrace getInstance(TransportManager transportManager, Clock clock)
94130
if (instance == null) {
95131
synchronized (AppStartTrace.class) {
96132
if (instance == null) {
97-
instance = new AppStartTrace(transportManager, clock);
133+
instance =
134+
new AppStartTrace(
135+
transportManager,
136+
clock,
137+
new ThreadPoolExecutor(
138+
CORE_POOL_SIZE,
139+
MAX_POOL_SIZE,
140+
/* keepAliveTime= */ MAX_LATENCY_BEFORE_UI_INIT + 10,
141+
TimeUnit.SECONDS,
142+
new LinkedBlockingQueue<>(1)));
98143
}
99144
}
100145
}
101146
return instance;
102147
}
103148

104-
private boolean isRegisteredForLifecycleCallbacks = false;
105-
private final TransportManager transportManager;
106-
private final Clock clock;
107-
private Context appContext;
108-
/**
109-
* The first time onCreate() of any activity is called, the activity is saved as launchActivity.
110-
*/
111-
private WeakReference<Activity> launchActivity;
112-
/**
113-
* The first time onResume() of any activity is called, the activity is saved as appStartActivity
114-
*/
115-
private WeakReference<Activity> appStartActivity;
116-
117-
/**
118-
* If the time difference between app starts and creation of any Activity is larger than
119-
* MAX_LATENCY_BEFORE_UI_INIT, set mTooLateToInitUI to true and we don't send AppStart Trace.
120-
*/
121-
private boolean isTooLateToInitUI = false;
122-
123-
private Timer onCreateTime = null;
124-
private Timer onStartTime = null;
125-
private Timer onResumeTime = null;
126-
127-
private boolean isStartedFromBackground = false;
128-
129-
AppStartTrace(@NonNull TransportManager transportManager, @NonNull Clock clock) {
149+
AppStartTrace(
150+
@NonNull TransportManager transportManager,
151+
@NonNull Clock clock,
152+
@NonNull ExecutorService executorService) {
130153
this.transportManager = transportManager;
131154
this.clock = clock;
155+
this.executorService = executorService;
132156
}
133157

134158
/** Called from FirebasePerfProvider to register this callback. */
@@ -191,51 +215,57 @@ public synchronized void onActivityResumed(Activity activity) {
191215
appStartActivity = new WeakReference<Activity>(activity);
192216

193217
onResumeTime = clock.getTime();
194-
final Timer startTime = FirebasePerfProvider.getAppStartTime();
218+
this.appStartTime = FirebasePerfProvider.getAppStartTime();
219+
this.startSession = SessionManager.getInstance().perfSession();
195220
AndroidLogger.getInstance()
196221
.debug(
197222
"onResume(): "
198223
+ activity.getClass().getName()
199224
+ ": "
200-
+ startTime.getDurationMicros(onResumeTime)
225+
+ this.appStartTime.getDurationMicros(onResumeTime)
201226
+ " microseconds");
202227

228+
// Log the app start trace in a non-main thread.
229+
executorService.execute(this::logAppStartTrace);
230+
231+
if (isRegisteredForLifecycleCallbacks) {
232+
// After AppStart trace is logged, we can unregister this callback.
233+
unregisterActivityLifecycleCallbacks();
234+
}
235+
}
236+
237+
private void logAppStartTrace() {
203238
TraceMetric.Builder metric =
204239
TraceMetric.newBuilder()
205240
.setName(Constants.TraceNames.APP_START_TRACE_NAME.toString())
206-
.setClientStartTimeUs(startTime.getMicros())
207-
.setDurationUs(startTime.getDurationMicros(onResumeTime));
241+
.setClientStartTimeUs(getappStartTime().getMicros())
242+
.setDurationUs(getappStartTime().getDurationMicros(onResumeTime));
208243
List<TraceMetric> subtraces = new ArrayList<>(/* initialCapacity= */ 3);
209244

210-
TraceMetric.Builder temp =
245+
TraceMetric.Builder traceMetricBuilder =
211246
TraceMetric.newBuilder()
212247
.setName(Constants.TraceNames.ON_CREATE_TRACE_NAME.toString())
213-
.setClientStartTimeUs(startTime.getMicros())
214-
.setDurationUs(startTime.getDurationMicros(onCreateTime));
215-
subtraces.add(temp.build());
248+
.setClientStartTimeUs(getappStartTime().getMicros())
249+
.setDurationUs(getappStartTime().getDurationMicros(onCreateTime));
250+
subtraces.add(traceMetricBuilder.build());
216251

217-
temp = TraceMetric.newBuilder();
218-
temp.setName(Constants.TraceNames.ON_START_TRACE_NAME.toString())
252+
traceMetricBuilder = TraceMetric.newBuilder();
253+
traceMetricBuilder
254+
.setName(Constants.TraceNames.ON_START_TRACE_NAME.toString())
219255
.setClientStartTimeUs(onCreateTime.getMicros())
220256
.setDurationUs(onCreateTime.getDurationMicros(onStartTime));
221-
subtraces.add(temp.build());
257+
subtraces.add(traceMetricBuilder.build());
222258

223-
temp = TraceMetric.newBuilder();
224-
temp.setName(Constants.TraceNames.ON_RESUME_TRACE_NAME.toString())
259+
traceMetricBuilder = TraceMetric.newBuilder();
260+
traceMetricBuilder
261+
.setName(Constants.TraceNames.ON_RESUME_TRACE_NAME.toString())
225262
.setClientStartTimeUs(onStartTime.getMicros())
226263
.setDurationUs(onStartTime.getDurationMicros(onResumeTime));
227-
subtraces.add(temp.build());
264+
subtraces.add(traceMetricBuilder.build());
228265

229-
metric
230-
.addAllSubtraces(subtraces)
231-
.addPerfSessions(SessionManager.getInstance().perfSession().build());
266+
metric.addAllSubtraces(subtraces).addPerfSessions(this.startSession.build());
232267

233268
transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND);
234-
235-
if (isRegisteredForLifecycleCallbacks) {
236-
// After AppStart trace is logged, we can unregister this callback.
237-
unregisterActivityLifecycleCallbacks();
238-
}
239269
}
240270

241271
@Override
@@ -285,6 +315,11 @@ Activity getAppStartActivity() {
285315
return appStartActivity.get();
286316
}
287317

318+
@VisibleForTesting
319+
Timer getappStartTime() {
320+
return appStartTime;
321+
}
322+
288323
@VisibleForTesting
289324
Timer getOnCreateTime() {
290325
return onCreateTime;

firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.firebase.perf.util.Timer;
3434
import com.google.firebase.perf.v1.ApplicationProcessState;
3535
import com.google.firebase.perf.v1.TraceMetric;
36+
import com.google.testing.timing.FakeScheduledExecutorService;
3637
import java.util.concurrent.TimeUnit;
3738
import org.junit.Assert;
3839
import org.junit.Before;
@@ -87,15 +88,18 @@ public Timer answer(InvocationOnMock invocationOnMock) throws Throwable {
8788
/** Test activity sequentially goes through onCreate()->onStart()->onResume() state change. */
8889
@Test
8990
public void testLaunchActivity() {
90-
AppStartTrace trace = new AppStartTrace(transportManager, clock);
91+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
92+
AppStartTrace trace = new AppStartTrace(transportManager, clock, fakeExecutorService);
9193
// first activity goes through onCreate()->onStart()->onResume() state change.
9294
currentTime = 1;
9395
trace.onActivityCreated(activity1, bundle);
9496
currentTime = 2;
9597
trace.onActivityStarted(activity1);
9698
currentTime = 3;
9799
trace.onActivityResumed(activity1);
100+
fakeExecutorService.runAll();
98101
verifyFinalState(activity1, trace, 1, 2, 3);
102+
99103
// same activity goes through onCreate()->onStart()->onResume() state change again.
100104
// should have no effect on AppStartTrace.
101105
currentTime = 4;
@@ -104,6 +108,7 @@ public void testLaunchActivity() {
104108
trace.onActivityStarted(activity1);
105109
currentTime = 6;
106110
trace.onActivityResumed(activity1);
111+
fakeExecutorService.runAll();
107112
verifyFinalState(activity1, trace, 1, 2, 3);
108113

109114
// a different activity goes through onCreate()->onStart()->onResume() state change.
@@ -114,6 +119,7 @@ public void testLaunchActivity() {
114119
trace.onActivityStarted(activity2);
115120
currentTime = 9;
116121
trace.onActivityResumed(activity2);
122+
fakeExecutorService.runAll();
117123
verifyFinalState(activity1, trace, 1, 2, 3);
118124
}
119125

@@ -158,7 +164,8 @@ private void verifyFinalState(
158164
*/
159165
@Test
160166
public void testInterleavedActivity() {
161-
AppStartTrace trace = new AppStartTrace(transportManager, clock);
167+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
168+
AppStartTrace trace = new AppStartTrace(transportManager, clock, fakeExecutorService);
162169
// first activity onCreate()
163170
currentTime = 1;
164171
trace.onActivityCreated(activity1, bundle);
@@ -178,19 +185,22 @@ public void testInterleavedActivity() {
178185
trace.onActivityResumed(activity2);
179186
Assert.assertEquals(activity1, trace.getLaunchActivity());
180187
Assert.assertEquals(activity2, trace.getAppStartActivity());
188+
fakeExecutorService.runAll();
181189
verifyFinalState(activity2, trace, 1, 3, 4);
182190

183191
// first activity continues.
184192
currentTime = 5;
185193
trace.onActivityStarted(activity1);
186194
currentTime = 6;
187195
trace.onActivityResumed(activity1);
196+
fakeExecutorService.runAll();
188197
verifyFinalState(activity2, trace, 1, 3, 4);
189198
}
190199

191200
@Test
192201
public void testDelayedAppStart() {
193-
AppStartTrace trace = new AppStartTrace(transportManager, clock);
202+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
203+
AppStartTrace trace = new AppStartTrace(transportManager, clock, fakeExecutorService);
194204
// Delays activity creation after 1 minute from app start time.
195205
currentTime = appStartTime + TimeUnit.MINUTES.toMicros(1) + 1;
196206
trace.onActivityCreated(activity1, bundle);
@@ -210,7 +220,8 @@ public void testDelayedAppStart() {
210220

211221
@Test
212222
public void testStartFromBackground() {
213-
AppStartTrace trace = new AppStartTrace(transportManager, clock);
223+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
224+
AppStartTrace trace = new AppStartTrace(transportManager, clock, fakeExecutorService);
214225
trace.setIsStartFromBackground();
215226
trace.onActivityCreated(activity1, bundle);
216227
Assert.assertNull(trace.getOnCreateTime());

0 commit comments

Comments
 (0)