Skip to content

Migrate fireperf content provider to component #4242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions firebase-common/src/main/java/com/google/firebase/FirebaseApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import com.google.firebase.heartbeatinfo.DefaultHeartBeatController;
import com.google.firebase.inject.Provider;
import com.google.firebase.internal.DataCollectionConfigStorage;
import com.google.firebase.provider.FirebaseInitProvider;
import com.google.firebase.tracing.ComponentMonitor;
import com.google.firebase.tracing.FirebaseTrace;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -416,6 +417,7 @@ protected FirebaseApp(Context applicationContext, String name, FirebaseOptions o
this.applicationContext = Preconditions.checkNotNull(applicationContext);
this.name = Preconditions.checkNotEmpty(name);
this.options = Preconditions.checkNotNull(options);
StartupTime startupTime = FirebaseInitProvider.startupTime;

FirebaseTrace.pushTrace("Firebase");

Expand All @@ -426,15 +428,22 @@ protected FirebaseApp(Context applicationContext, String name, FirebaseOptions o
FirebaseTrace.popTrace(); // ComponentDiscovery

FirebaseTrace.pushTrace("Runtime");
componentRuntime =
ComponentRuntime.Builder builder =
ComponentRuntime.builder(UI_EXECUTOR)
.addLazyComponentRegistrars(registrars)
.addComponentRegistrar(new FirebaseCommonRegistrar())
.addComponent(Component.of(applicationContext, Context.class))
.addComponent(Component.of(this, FirebaseApp.class))
.addComponent(Component.of(options, FirebaseOptions.class))
.setProcessor(new ComponentMonitor())
.build();
.setProcessor(new ComponentMonitor());

// Don't provide StartupTime in direct boot mode or if Firebase was manually started
if (UserManagerCompat.isUserUnlocked(applicationContext)
&& FirebaseInitProvider.currentlyInitializing.get()) {
builder.addComponent(Component.of(startupTime, StartupTime.class));
}

componentRuntime = builder.build();
FirebaseTrace.popTrace(); // Runtime

dataCollectionConfigStorage =
Expand Down
47 changes: 47 additions & 0 deletions firebase-common/src/main/java/com/google/firebase/StartupTime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase;

import android.os.SystemClock;
import com.google.auto.value.AutoValue;

/**
* Represents the time at which Firebase began initialization, both in unix time/epoch milliseconds
* and in milliseconds since boot. The absence of a StartupTime indicates an unreliable or
* misleading time, such as a launch in direct boot mode. Because of this, StartupTime cannot be
* guaranteed to be present, and instead should be optionally depended on, and its absence handled.
*/
@AutoValue
public abstract class StartupTime {

/** @return The epoch time that Firebase began initializing, in milliseconds */
public abstract long getEpochMillis();

/** @return The number of milliseconds from boot to when Firebase began initializing */
public abstract long getElapsedRealtime();

/**
* @param epochMillis Time in milliseconds since epoch
* @param elapsedRealtime Time in milliseconds since boot
*/
public static StartupTime create(long epochMillis, long elapsedRealtime) {
return new AutoValue_StartupTime(epochMillis, elapsedRealtime);
}

/** @return A StartupTime represented by the current epoch time and JVM nano time */
public static StartupTime now() {
return create(System.currentTimeMillis(), SystemClock.elapsedRealtime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.common.internal.Preconditions;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import java.util.concurrent.atomic.AtomicBoolean;

/** Initializes Firebase APIs at app startup time. */
public class FirebaseInitProvider extends ContentProvider {

private static final String TAG = "FirebaseInitProvider";
public static StartupTime startupTime = StartupTime.now();
public static AtomicBoolean currentlyInitializing = new AtomicBoolean(false);

/** Should match the {@link FirebaseInitProvider} authority if $androidId is empty. */
@VisibleForTesting
Expand All @@ -48,12 +51,17 @@ public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
/** Called before {@link Application#onCreate()}. */
@Override
public boolean onCreate() {
if (FirebaseApp.initializeApp(getContext()) == null) {
Log.i(TAG, "FirebaseApp initialization unsuccessful");
} else {
Log.i(TAG, "FirebaseApp initialization successful");
try {
currentlyInitializing.set(true);
if (FirebaseApp.initializeApp(getContext()) == null) {
Log.i(TAG, "FirebaseApp initialization unsuccessful");
} else {
Log.i(TAG, "FirebaseApp initialization successful");
}
return false;
} finally {
currentlyInitializing.set(false);
}
return false;
}

/**
Expand Down
5 changes: 0 additions & 5 deletions firebase-perf/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
<uses-permission android:name="android.permission.INTERNET"/>

<application>
<provider
android:authorities="${applicationId}.firebaseperfprovider"
android:exported="false"
android:initOrder="101"
android:name="com.google.firebase.perf.provider.FirebasePerfProvider"/>

<service
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.perf;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import com.google.firebase.perf.application.AppStateMonitor;
import com.google.firebase.perf.config.ConfigResolver;
import com.google.firebase.perf.metrics.AppStartTrace;
import com.google.firebase.perf.session.SessionManager;

/**
* The Firebase Performance early initialization.
*
* <p>Responsible for initializing the AppStartTrace, and early initialization of ConfigResolver
*/
public class FirebasePerfEarly {
private final Handler mainHandler = new Handler(Looper.getMainLooper());

public FirebasePerfEarly(FirebaseApp app) {
Context context = app.getApplicationContext();

// Initialize ConfigResolver early for accessing device caching layer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vkryachko I wonder if we could merge the 2-part initialization (FirebasePerfEarly + FirebasePerfRegistrar) to simplify it further in the future. The reason we needed FirebasePerfProvider was to capture app-start time, but now that's no longer needed, and if Fireperf is completely started by component framework, if we get rid of FirebasePerfEarly then we also don't need ConfigResolver anymore. That's because ConfigResolver and its related classes were created when the old FirebasePerfProvider early initializes part of Fireperf that needed FRC, so we had to build our own disk cache for RC. If Fireperf starts after FRC as it should, we can just use FRC. However I'm not sure if this will be okay for the SDK-Observability-project, would it be fine for Fireperf's dependencies to start first?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have full context on why the 2-part logic was in place, but my understanding that part of it was to speed up the SDK and delay a lot of work as much as possible(i.e. see #2518).

But I trust your judgement to evolve this in a way that makes sense, after this initial migration. Happy to discuss it more.

ConfigResolver configResolver = ConfigResolver.getInstance();
configResolver.setApplicationContext(context);

AppStateMonitor appStateMonitor = AppStateMonitor.getInstance();
appStateMonitor.registerActivityLifecycleCallbacks(context);
appStateMonitor.registerForAppColdStart(new FirebasePerformanceInitializer());

if (app.get(StartupTime.class) != null) {
AppStartTrace appStartTrace = AppStartTrace.getInstance();
appStartTrace.registerActivityLifecycleCallbacks(context);
mainHandler.post(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace));
}

// In the case of cold start, we create a session and start collecting gauges as early as
// possible.
// There is code in SessionManager that prevents us from resetting the session twice in case
// of app cold start.
SessionManager.getInstance().initializeGaugeCollection();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import androidx.annotation.Keep;
import com.google.android.datatransport.TransportFactory;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import com.google.firebase.components.Component;
import com.google.firebase.components.ComponentContainer;
import com.google.firebase.components.ComponentRegistrar;
Expand Down Expand Up @@ -54,6 +55,13 @@ public List<Component<?>> getComponents() {
.add(Dependency.requiredProvider(TransportFactory.class))
.factory(FirebasePerfRegistrar::providesFirebasePerformance)
.build(),
Component.builder(FirebasePerfEarly.class)
.name(LIBRARY_NAME)
.add(Dependency.required(FirebaseApp.class))
.add(Dependency.optionalProvider(StartupTime.class))
.eagerInDefaultApp()
.factory(FirebasePerfRegistrar::providesFirebasePerformanceEarly)
.build(),
/**
* Fireperf SDK is lazily by {@link FirebasePerformanceInitializer} during {@link
* com.google.firebase.perf.application.AppStateMonitor#onActivityResumed(Activity)}. we use
Expand All @@ -77,4 +85,8 @@ private static FirebasePerformance providesFirebasePerformance(ComponentContaine

return component.getFirebasePerformance();
}

private static FirebasePerfEarly providesFirebasePerformanceEarly(ComponentContainer container) {
return new FirebasePerfEarly(container.get(FirebaseApp.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import com.google.android.gms.common.util.VisibleForTesting;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import com.google.firebase.inject.Provider;
import com.google.firebase.perf.logging.AndroidLogger;
import com.google.firebase.perf.provider.FirebasePerfProvider;
import com.google.firebase.perf.util.Optional;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
Expand Down Expand Up @@ -77,24 +78,35 @@ private RemoteConfigManager() {
new LinkedBlockingQueue<Runnable>()),
/* firebaseRemoteConfig= */ null, // set once FirebaseRemoteConfig is initialized
MIN_APP_START_CONFIG_FETCH_DELAY_MS
+ new Random().nextInt(RANDOM_APP_START_CONFIG_FETCH_DELAY_MS));
+ new Random().nextInt(RANDOM_APP_START_CONFIG_FETCH_DELAY_MS),
getInitialStartupMillis());
}

@VisibleForTesting
static long getInitialStartupMillis() {
StartupTime startupTime = FirebaseApp.getInstance().get(StartupTime.class);
if (startupTime != null) {
return startupTime.getEpochMillis();
} else {
return System.currentTimeMillis();
}
}

@VisibleForTesting
RemoteConfigManager(
DeviceCacheManager cache,
Executor executor,
FirebaseRemoteConfig firebaseRemoteConfig,
long appStartConfigFetchDelayInMs) {
long appStartConfigFetchDelayInMs,
long appStartTimeInMs) {
this.cache = cache;
this.executor = executor;
this.firebaseRemoteConfig = firebaseRemoteConfig;
this.allRcConfigMap =
firebaseRemoteConfig == null
? new ConcurrentHashMap<>()
: new ConcurrentHashMap<>(firebaseRemoteConfig.getAll());
this.appStartTimeInMs =
TimeUnit.MICROSECONDS.toMillis(FirebasePerfProvider.getAppStartTime().getMicros());
this.appStartTimeInMs = appStartTimeInMs;
this.appStartConfigFetchDelayInMs = appStartConfigFetchDelayInMs;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.gms.common.util.VisibleForTesting;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import com.google.firebase.perf.config.ConfigResolver;
import com.google.firebase.perf.logging.AndroidLogger;
import com.google.firebase.perf.provider.FirebasePerfProvider;
import com.google.firebase.perf.session.PerfSession;
import com.google.firebase.perf.session.SessionManager;
import com.google.firebase.perf.transport.TransportManager;
Expand Down Expand Up @@ -91,6 +92,8 @@ public class AppStartTrace implements ActivityLifecycleCallbacks {
*/
private boolean isTooLateToInitUI = false;

private static Timer firebaseStartupTime = null;

private Timer appStartTime = null;
private Timer onCreateTime = null;
private Timer onStartTime = null;
Expand Down Expand Up @@ -163,9 +166,15 @@ static AppStartTrace getInstance(TransportManager transportManager, Clock clock)
this.clock = clock;
this.configResolver = configResolver;
this.executorService = executorService;

StartupTime startupTime = FirebaseApp.getInstance().get(StartupTime.class);
if (startupTime == null) {
firebaseStartupTime = new Timer();
}
firebaseStartupTime = Timer.ofElapsedRealtime(startupTime.getElapsedRealtime());
}

/** Called from FirebasePerfProvider to register this callback. */
/** Called from FirebasePerfEarly to register this callback. */
public synchronized void registerActivityLifecycleCallbacks(@NonNull Context context) {
// Make sure the callback is registered only once.
if (isRegisteredForLifecycleCallbacks) {
Expand All @@ -190,15 +199,15 @@ public synchronized void unregisterActivityLifecycleCallbacks() {

/**
* Gets the timetamp that marks the beginning of app start, currently defined as the beginning of
* BIND_APPLICATION. Fallback to class-load time of {@link FirebasePerfProvider} when API < 24.
* BIND_APPLICATION. Fallback to class-load time of {@link StartupTime} when API < 24.
*
* @return {@link Timer} at the beginning of app start by Fireperf definition.
*/
private static Timer getStartTimer() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Timer.ofElapsedRealtime(Process.getStartElapsedRealtime());
}
return FirebasePerfProvider.getAppStartTime();
return firebaseStartupTime;
}

private void recordFirstDrawDone() {
Expand All @@ -225,8 +234,7 @@ public synchronized void onActivityCreated(Activity activity, Bundle savedInstan
launchActivity = new WeakReference<Activity>(activity);
onCreateTime = clock.getTime();

if (FirebasePerfProvider.getAppStartTime().getDurationMicros(onCreateTime)
> MAX_LATENCY_BEFORE_UI_INIT) {
if (firebaseStartupTime.getDurationMicros(onCreateTime) > MAX_LATENCY_BEFORE_UI_INIT) {
isTooLateToInitUI = true;
}
}
Expand Down Expand Up @@ -261,7 +269,7 @@ public synchronized void onActivityResumed(Activity activity) {
appStartActivity = new WeakReference<Activity>(activity);

onResumeTime = clock.getTime();
this.appStartTime = FirebasePerfProvider.getAppStartTime();
this.appStartTime = firebaseStartupTime;
this.startSession = SessionManager.getInstance().perfSession();
AndroidLogger.getInstance()
.debug(
Expand Down Expand Up @@ -290,8 +298,8 @@ private void logColdStart(Timer start, Timer end, PerfSession session) {
TraceMetric.Builder subtrace =
TraceMetric.newBuilder()
.setName("_experiment_classLoadTime")
.setClientStartTimeUs(FirebasePerfProvider.getAppStartTime().getMicros())
.setDurationUs(FirebasePerfProvider.getAppStartTime().getDurationMicros(end));
.setClientStartTimeUs(firebaseStartupTime.getMicros())
.setDurationUs(firebaseStartupTime.getDurationMicros(end));

metric.addSubtraces(subtrace).addPerfSessions(this.startSession.build());

Expand Down Expand Up @@ -347,8 +355,8 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
/**
* We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
* If app is started from background, we do not generate AppStart trace. This runnable is posted
* to main UI thread from FirebasePerfProvider. If app is started from background, this runnable
* will be executed before any activity's onCreate() method. If app is started from foreground,
* to main UI thread from FirebasePerfEarly. If app is started from background, this runnable will
* be executed before any activity's onCreate() method. If app is started from foreground,
* activity's onCreate() method is executed before this runnable.
*/
public static class StartFromBackgroundRunnable implements Runnable {
Expand Down
Loading