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 all 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 @@ -50,6 +50,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 @@ -408,6 +409,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.getStartupTime();

FirebaseTrace.pushTrace("Firebase");

Expand All @@ -418,16 +420,23 @@ protected FirebaseApp(Context applicationContext, String name, FirebaseOptions o
FirebaseTrace.popTrace(); // ComponentDiscovery

FirebaseTrace.pushTrace("Runtime");
componentRuntime =
ComponentRuntime.Builder builder =
ComponentRuntime.builder(com.google.firebase.concurrent.UiExecutor.INSTANCE)
.addLazyComponentRegistrars(registrars)
.addComponentRegistrar(new FirebaseCommonRegistrar())
.addComponentRegistrar(new ExecutorsRegistrar())
.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.isCurrentlyInitializing()) {
builder.addComponent(Component.of(startupTime, StartupTime.class));
}

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

dataCollectionConfigStorage =
Expand Down
56 changes: 56 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,56 @@
// 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 androidx.annotation.NonNull;
import com.google.auto.value.AutoValue;

/**
* Represents Firebase's startup time in several timing methods. Represented in unix time/epoch
* milliseconds milliseconds since boot, and uptime milliseconds. 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.
*
* @hide
*/
@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();

/** @return The number of milliseconds of uptime measured by SystemClock.uptimeMillis() */
public abstract long getUptimeMillis();

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

/** @return A StartupTime represented by the current epoch time and JVM nano time */
public static @NonNull StartupTime now() {
return create(
System.currentTimeMillis(), SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,24 @@
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";
@Nullable private static StartupTime startupTime = StartupTime.now();
@NonNull private static AtomicBoolean currentlyInitializing = new AtomicBoolean(false);

/** @hide */
public static @Nullable StartupTime getStartupTime() {
return startupTime;
}

/** @hide */
public static boolean isCurrentlyInitializing() {
return currentlyInitializing.get();
}
Comment on lines +45 to +47
Copy link
Member

Choose a reason for hiding this comment

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

can we make this @hide as well?


/** Should match the {@link FirebaseInitProvider} authority if $androidId is empty. */
@VisibleForTesting
Expand All @@ -48,12 +61,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,60 @@
// 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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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 {
@NonNull private final Handler mainHandler = new Handler(Looper.getMainLooper());

public FirebasePerfEarly(@NonNull FirebaseApp app, @Nullable StartupTime startupTime) {
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 (startupTime != 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 All @@ -41,6 +42,7 @@
@Keep
public class FirebasePerfRegistrar implements ComponentRegistrar {
private static final String LIBRARY_NAME = "fire-perf";
private static final String EARLY_LIBRARY_NAME = "fire-perf-early";

@Override
@Keep
Expand All @@ -52,8 +54,16 @@ public List<Component<?>> getComponents() {
.add(Dependency.requiredProvider(RemoteConfigComponent.class))
.add(Dependency.required(FirebaseInstallationsApi.class))
.add(Dependency.requiredProvider(TransportFactory.class))
.add(Dependency.required(FirebasePerfEarly.class))
.factory(FirebasePerfRegistrar::providesFirebasePerformance)
.build(),
Component.builder(FirebasePerfEarly.class)
.name(EARLY_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 @@ -65,6 +75,8 @@ public List<Component<?>> getComponents() {
}

private static FirebasePerformance providesFirebasePerformance(ComponentContainer container) {
// Ensure FirebasePerfEarly was initialized
container.get(FirebasePerfEarly.class);
FirebasePerformanceComponent component =
DaggerFirebasePerformanceComponent.builder()
.firebasePerformanceModule(
Expand All @@ -77,4 +89,9 @@ private static FirebasePerformance providesFirebasePerformance(ComponentContaine

return component.getFirebasePerformance();
}

private static FirebasePerfEarly providesFirebasePerformanceEarly(ComponentContainer container) {
return new FirebasePerfEarly(
container.get(FirebaseApp.class), container.getProvider(StartupTime.class).get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,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 @@ -80,24 +81,36 @@ 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
@SuppressWarnings("FirebaseUseExplicitDependencies")
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
Loading