Skip to content

Commit 1ffcaaf

Browse files
committed
log _experiment_as_ttid
1 parent ffd44ee commit 1ffcaaf

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.firebase.perf.config.ConfigurationConstants.CollectionDeactivated;
2525
import com.google.firebase.perf.config.ConfigurationConstants.CollectionEnabled;
2626
import com.google.firebase.perf.config.ConfigurationConstants.FragmentSamplingRate;
27+
import com.google.firebase.perf.config.ConfigurationConstants.ExperimentTTID;
2728
import com.google.firebase.perf.config.ConfigurationConstants.LogSourceName;
2829
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountBackground;
2930
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountForeground;
@@ -767,6 +768,38 @@ public float getFragmentSamplingRate() {
767768
return config.getDefault();
768769
}
769770

771+
/** Returns if _experiment_as_ttid should be captured. */
772+
public boolean getIsExperimentTTIDEnabled() {
773+
// Order of precedence is:
774+
// 1. If the value exists in Android Manifest, return this value.
775+
// 2. If the value exists through Firebase Remote Config, cache and return this value.
776+
// 3. If the value exists in device cache, return this value.
777+
// 4. Otherwise, return default value.
778+
ExperimentTTID config = ExperimentTTID.getInstance();
779+
780+
// 1. Reads value in Android Manifest (it is set by developers during build time).
781+
Optional<Boolean> metadataValue = getMetadataBoolean(config);
782+
if (metadataValue.isAvailable()) {
783+
return metadataValue.get();
784+
}
785+
786+
// 2. Reads value from Firebase Remote Config, saves this value in cache layer if valid.
787+
Optional<Boolean> rcValue = getRemoteConfigBoolean(config);
788+
if (rcValue.isAvailable()) {
789+
deviceCacheManager.setValue(config.getDeviceCacheFlag(), rcValue.get());
790+
return rcValue.get();
791+
}
792+
793+
// 3. Reads value from cache layer.
794+
Optional<Boolean> deviceCacheValue = getDeviceCacheBoolean(config);
795+
if (deviceCacheValue.isAvailable()) {
796+
return deviceCacheValue.get();
797+
}
798+
799+
// 4. Returns default value if there is no valid value from above approaches.
800+
return config.getDefault();
801+
}
802+
770803
// endregion
771804

772805
// Helper functions for interaction with Metadata layer.

firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigurationConstants.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,4 +661,39 @@ protected String getMetadataFlag() {
661661
return "fragment_sampling_percentage";
662662
}
663663
}
664+
665+
protected static final class ExperimentTTID extends ConfigurationFlag<Boolean> {
666+
private static ExperimentTTID instance;
667+
668+
private ExperimentTTID() {
669+
super();
670+
}
671+
672+
protected static synchronized ExperimentTTID getInstance() {
673+
if (instance == null) {
674+
instance = new ExperimentTTID();
675+
}
676+
return instance;
677+
}
678+
679+
@Override
680+
protected Boolean getDefault() {
681+
return false;
682+
}
683+
684+
@Override
685+
protected String getRemoteConfigFlag() {
686+
return "fpr_experiment_as_ttid";
687+
}
688+
689+
@Override
690+
protected String getDeviceCacheFlag() {
691+
return "com.google.firebase.perf.ExperimentTTID";
692+
}
693+
694+
@Override
695+
protected String getMetadataFlag() {
696+
return "experiment_as_ttid";
697+
}
698+
}
664699
}

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

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,31 @@
1818
import android.app.Application;
1919
import android.app.Application.ActivityLifecycleCallbacks;
2020
import android.content.Context;
21+
import android.os.Build;
2122
import android.os.Bundle;
23+
import android.os.Process;
24+
import android.view.View;
25+
2226
import androidx.annotation.Keep;
2327
import androidx.annotation.NonNull;
2428
import androidx.annotation.Nullable;
2529
import com.google.android.gms.common.util.VisibleForTesting;
30+
import com.google.firebase.perf.config.ConfigResolver;
2631
import com.google.firebase.perf.logging.AndroidLogger;
2732
import com.google.firebase.perf.provider.FirebasePerfProvider;
2833
import com.google.firebase.perf.session.PerfSession;
2934
import com.google.firebase.perf.session.SessionManager;
3035
import com.google.firebase.perf.transport.TransportManager;
3136
import com.google.firebase.perf.util.Clock;
3237
import com.google.firebase.perf.util.Constants;
38+
import com.google.firebase.perf.util.FirstDrawDoneListener;
3339
import com.google.firebase.perf.util.Timer;
3440
import com.google.firebase.perf.v1.ApplicationProcessState;
3541
import com.google.firebase.perf.v1.TraceMetric;
3642
import java.lang.ref.WeakReference;
3743
import java.util.ArrayList;
44+
import java.util.Arrays;
45+
import java.util.Collections;
3846
import java.util.List;
3947
import java.util.concurrent.ExecutorService;
4048
import java.util.concurrent.LinkedBlockingQueue;
@@ -89,6 +97,7 @@ public class AppStartTrace implements ActivityLifecycleCallbacks {
8997
private Timer onCreateTime = null;
9098
private Timer onStartTime = null;
9199
private Timer onResumeTime = null;
100+
private Timer firstDrawDone = null;
92101

93102
private PerfSession startSession;
94103
private boolean isStartedFromBackground = false;
@@ -139,7 +148,7 @@ static AppStartTrace getInstance(TransportManager transportManager, Clock clock)
139148
MAX_POOL_SIZE,
140149
/* keepAliveTime= */ MAX_LATENCY_BEFORE_UI_INIT + 10,
141150
TimeUnit.SECONDS,
142-
new LinkedBlockingQueue<>(1)));
151+
new LinkedBlockingQueue<>()));
143152
}
144153
}
145154
}
@@ -178,6 +187,32 @@ public synchronized void unregisterActivityLifecycleCallbacks() {
178187
isRegisteredForLifecycleCallbacks = false;
179188
}
180189

190+
/**
191+
* Gets the timetamp that marks the beginning of app start, currently defined as the beginning of
192+
* BIND_APPLICATION. Fallback to class-load time of {@link FirebasePerfProvider} when API < 24.
193+
*
194+
* @return {@link Timer} at the beginning of app start by Fireperf definition.
195+
*/
196+
private static Timer getStartTimer() {
197+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
198+
return Timer.ofElapsedRealtime(Process.getStartElapsedRealtime());
199+
}
200+
return FirebasePerfProvider.getAppStartTime();
201+
}
202+
203+
private void recordFirstDrawDone() {
204+
if (firstDrawDone != null) {
205+
return;
206+
}
207+
this.firstDrawDone = clock.getTime();
208+
executorService.execute(() -> this.logColdStart(getStartTimer(), this.firstDrawDone, this.startSession));
209+
210+
if (isRegisteredForLifecycleCallbacks) {
211+
// After AppStart trace is queued to be logged, we can unregister this callback.
212+
unregisterActivityLifecycleCallbacks();
213+
}
214+
}
215+
181216
@Override
182217
public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) {
183218
if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
@@ -207,11 +242,20 @@ public synchronized void onActivityStarted(Activity activity) {
207242
@Override
208243
public synchronized void onActivityResumed(Activity activity) {
209244
if (isStartedFromBackground
210-
|| onResumeTime != null // An activity already called onResume()
211245
|| isTooLateToInitUI) {
212246
return;
213247
}
214248

249+
// Shadow-launch experiment of new app start time
250+
if (ConfigResolver.getInstance().getIsExperimentTTIDEnabled()) {
251+
View rootView = activity.findViewById(android.R.id.content);
252+
FirstDrawDoneListener.registerForNextDraw(rootView, this::recordFirstDrawDone);
253+
}
254+
255+
if (onResumeTime != null) { // An activity already called onResume()
256+
return;
257+
}
258+
215259
appStartActivity = new WeakReference<Activity>(activity);
216260

217261
onResumeTime = clock.getTime();
@@ -227,11 +271,24 @@ public synchronized void onActivityResumed(Activity activity) {
227271

228272
// Log the app start trace in a non-main thread.
229273
executorService.execute(this::logAppStartTrace);
274+
}
230275

231-
if (isRegisteredForLifecycleCallbacks) {
232-
// After AppStart trace is logged, we can unregister this callback.
233-
unregisterActivityLifecycleCallbacks();
234-
}
276+
private void logColdStart(Timer start, Timer end, PerfSession session) {
277+
TraceMetric.Builder metric =
278+
TraceMetric.newBuilder()
279+
.setName("_experiment_as_ttid")
280+
.setClientStartTimeUs(start.getMicros())
281+
.setDurationUs(start.getDurationMicros(end));
282+
283+
TraceMetric.Builder subtrace =
284+
TraceMetric.newBuilder()
285+
.setName("_experiment_classLoadTime")
286+
.setClientStartTimeUs(FirebasePerfProvider.getAppStartTime().getMicros())
287+
.setDurationUs(FirebasePerfProvider.getAppStartTime().getDurationMicros(end));
288+
289+
metric.addSubtraces(subtrace).addPerfSessions(this.startSession.build());
290+
291+
transportManager.log(metric.build(), ApplicationProcessState.FOREGROUND_BACKGROUND);
235292
}
236293

237294
private void logAppStartTrace() {

0 commit comments

Comments
 (0)