From a49c644624d79e78c8cc95057819df244a642fa7 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Fri, 24 Feb 2023 12:31:13 -0800 Subject: [PATCH] Extract unified lifecycle instrumentation to new class (#483) * move lifecycle instrumentation install code to otel * add javadoc * move creation into lambda so we don't front-load * spotless * factor out builder. * spotless --- .../java/com/splunk/rum/RumInitializer.java | 128 +++--------------- .../AndroidLifecycleInstrumentation.java | 125 +++++++++++++++++ ...ndroidLifecycleInstrumentationBuilder.java | 49 +++++++ 3 files changed, 193 insertions(+), 109 deletions(-) create mode 100644 splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentation.java create mode 100644 splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentationBuilder.java diff --git a/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java b/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java index 862e0b733..c56c62a5b 100644 --- a/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java +++ b/splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java @@ -48,17 +48,10 @@ import io.opentelemetry.rum.internal.GlobalAttributesSpanAppender; import io.opentelemetry.rum.internal.OpenTelemetryRum; import io.opentelemetry.rum.internal.OpenTelemetryRumBuilder; -import io.opentelemetry.rum.internal.instrumentation.InstrumentedApplication; -import io.opentelemetry.rum.internal.instrumentation.activity.ActivityCallbacks; -import io.opentelemetry.rum.internal.instrumentation.activity.ActivityTracerCache; -import io.opentelemetry.rum.internal.instrumentation.activity.Pre29ActivityCallbacks; -import io.opentelemetry.rum.internal.instrumentation.activity.Pre29VisibleScreenLifecycleBinding; -import io.opentelemetry.rum.internal.instrumentation.activity.RumFragmentActivityRegisterer; -import io.opentelemetry.rum.internal.instrumentation.activity.VisibleScreenLifecycleBinding; import io.opentelemetry.rum.internal.instrumentation.activity.VisibleScreenTracker; import io.opentelemetry.rum.internal.instrumentation.anr.AnrDetector; import io.opentelemetry.rum.internal.instrumentation.crash.CrashReporter; -import io.opentelemetry.rum.internal.instrumentation.fragment.RumFragmentLifecycleCallbacks; +import io.opentelemetry.rum.internal.instrumentation.lifecycle.AndroidLifecycleInstrumentation; import io.opentelemetry.rum.internal.instrumentation.network.CurrentNetworkProvider; import io.opentelemetry.rum.internal.instrumentation.network.NetworkAttributesSpanAppender; import io.opentelemetry.rum.internal.instrumentation.network.NetworkChangeMonitor; @@ -206,13 +199,26 @@ SplunkRum initialize( private void installLifecycleInstrumentations( OpenTelemetryRumBuilder otelRumBuilder, VisibleScreenTracker visibleScreenTracker) { - installStartupTimerInstrumentation(otelRumBuilder); - installActivityLifecycleEventsInstrumentation(otelRumBuilder, visibleScreenTracker); - installFragmentLifecycleInstrumentation(otelRumBuilder, visibleScreenTracker); - installScreenTrackingInstrumentation(otelRumBuilder, visibleScreenTracker); - otelRumBuilder.addInstrumentation( instrumentedApp -> { + Function tracerCustomizer = + tracer -> + (Tracer) + spanName -> { + String component = + spanName.equals(APP_START_SPAN_NAME) + ? COMPONENT_APPSTART + : COMPONENT_UI; + return tracer.spanBuilder(spanName) + .setAttribute(COMPONENT_KEY, component); + }; + AndroidLifecycleInstrumentation instrumentation = + AndroidLifecycleInstrumentation.builder() + .setVisibleScreenTracker(visibleScreenTracker) + .setStartupTimer(startupTimer) + .setTracerCustomizer(tracerCustomizer) + .build(); + instrumentation.installOn(instrumentedApp); initializationEvents.add( new InitializationEvent( "activityLifecycleCallbacksInitialized", @@ -220,102 +226,6 @@ private void installLifecycleInstrumentations( }); } - private void installScreenTrackingInstrumentation( - OpenTelemetryRumBuilder otelRumBuilder, VisibleScreenTracker visibleScreenTracker) { - otelRumBuilder.addInstrumentation( - instrumentedApp -> { - Application.ActivityLifecycleCallbacks screenTrackingBinding = - buildScreenTrackingBinding(visibleScreenTracker); - instrumentedApp - .getApplication() - .registerActivityLifecycleCallbacks(screenTrackingBinding); - }); - } - - @NonNull - private Application.ActivityLifecycleCallbacks buildScreenTrackingBinding( - VisibleScreenTracker visibleScreenTracker) { - if (Build.VERSION.SDK_INT < 29) { - return new Pre29VisibleScreenLifecycleBinding(visibleScreenTracker); - } - return new VisibleScreenLifecycleBinding(visibleScreenTracker); - } - - private void installFragmentLifecycleInstrumentation( - OpenTelemetryRumBuilder otelRumBuilder, VisibleScreenTracker visibleScreenTracker) { - otelRumBuilder.addInstrumentation( - instrumentedApp -> { - Application.ActivityLifecycleCallbacks fragmentRegisterer = - buildFragmentRegisterer(visibleScreenTracker, instrumentedApp); - instrumentedApp - .getApplication() - .registerActivityLifecycleCallbacks(fragmentRegisterer); - }); - } - - @NonNull - private Application.ActivityLifecycleCallbacks buildFragmentRegisterer( - VisibleScreenTracker visibleScreenTracker, InstrumentedApplication instrumentedApp) { - Tracer delegateTracer = instrumentedApp.getOpenTelemetrySdk().getTracer(RUM_TRACER_NAME); - Tracer tracer = - spanName -> - delegateTracer - .spanBuilder(spanName) - .setAttribute(COMPONENT_KEY, COMPONENT_UI); - RumFragmentLifecycleCallbacks fragmentLifecycle = - new RumFragmentLifecycleCallbacks(tracer, visibleScreenTracker); - if (Build.VERSION.SDK_INT < 29) { - return RumFragmentActivityRegisterer.createPre29(fragmentLifecycle); - } - return RumFragmentActivityRegisterer.create(fragmentLifecycle); - } - - private void installStartupTimerInstrumentation(OpenTelemetryRumBuilder otelRumBuilder) { - otelRumBuilder.addInstrumentation( - instrumentedApp -> { - instrumentedApp - .getApplication() - .registerActivityLifecycleCallbacks( - startupTimer.createLifecycleCallback()); - }); - } - - private void installActivityLifecycleEventsInstrumentation( - OpenTelemetryRumBuilder otelRumBuilder, VisibleScreenTracker visibleScreenTracker) { - otelRumBuilder.addInstrumentation( - instrumentedApp -> { - Application.ActivityLifecycleCallbacks activityCallbacks = - buildActivityEventsCallback(visibleScreenTracker, instrumentedApp); - instrumentedApp - .getApplication() - .registerActivityLifecycleCallbacks(activityCallbacks); - }); - } - - @NonNull - private Application.ActivityLifecycleCallbacks buildActivityEventsCallback( - VisibleScreenTracker visibleScreenTracker, InstrumentedApplication instrumentedApp) { - Tracer delegateTracer = instrumentedApp.getOpenTelemetrySdk().getTracer(RUM_TRACER_NAME); - Tracer tracer = - spanName -> { - // override the component to be appstart when appstart - String component = - spanName.equals(APP_START_SPAN_NAME) - ? COMPONENT_APPSTART - : COMPONENT_UI; - return delegateTracer - .spanBuilder(spanName) - .setAttribute(COMPONENT_KEY, component); - }; - - ActivityTracerCache tracers = - new ActivityTracerCache(tracer, visibleScreenTracker, startupTimer); - if (Build.VERSION.SDK_INT < 29) { - return new Pre29ActivityCallbacks(tracers); - } - return new ActivityCallbacks(tracers); - } - private Resource createResource() { // applicationName can't be null at this stage String applicationName = requireNonNull(builder.applicationName); diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentation.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentation.java new file mode 100644 index 000000000..d48eabbcc --- /dev/null +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentation.java @@ -0,0 +1,125 @@ +/* + * Copyright Splunk Inc. + * + * 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 io.opentelemetry.rum.internal.instrumentation.lifecycle; + +import android.app.Application; +import android.os.Build; +import androidx.annotation.NonNull; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.rum.internal.instrumentation.InstrumentedApplication; +import io.opentelemetry.rum.internal.instrumentation.activity.ActivityCallbacks; +import io.opentelemetry.rum.internal.instrumentation.activity.ActivityTracerCache; +import io.opentelemetry.rum.internal.instrumentation.activity.Pre29ActivityCallbacks; +import io.opentelemetry.rum.internal.instrumentation.activity.Pre29VisibleScreenLifecycleBinding; +import io.opentelemetry.rum.internal.instrumentation.activity.RumFragmentActivityRegisterer; +import io.opentelemetry.rum.internal.instrumentation.activity.VisibleScreenLifecycleBinding; +import io.opentelemetry.rum.internal.instrumentation.activity.VisibleScreenTracker; +import io.opentelemetry.rum.internal.instrumentation.fragment.RumFragmentLifecycleCallbacks; +import io.opentelemetry.rum.internal.instrumentation.startup.AppStartupTimer; +import java.util.function.Function; + +/** + * This is an umbrella instrumentation that covers several things: * startup timer callback is + * registered so that UI startup time can be measured - activity lifecycle callbacks are registered + * so that lifecycle events can be generated - activity lifecycle callback listener is registered to + * that will register a FragmentLifecycleCallbacks when appropriate - activity lifecycle callback + * listener is registered to dispatch events to the VisibleScreenTracker + */ +public class AndroidLifecycleInstrumentation { + + private static final String INSTRUMENTATION_SCOPE = "io.opentelemetry.lifecycle"; + private final AppStartupTimer startupTimer; + private final VisibleScreenTracker visibleScreenTracker; + + private final Function tracerCustomizer; + + AndroidLifecycleInstrumentation(AndroidLifecycleInstrumentationBuilder builder) { + this.startupTimer = builder.startupTimer; + this.visibleScreenTracker = builder.visibleScreenTracker; + this.tracerCustomizer = builder.tracerCustomizer; + } + + public static AndroidLifecycleInstrumentationBuilder builder() { + return new AndroidLifecycleInstrumentationBuilder(); + } + + public void installOn(InstrumentedApplication app) { + installStartupTimerInstrumentation(app); + installActivityLifecycleEventsInstrumentation(app); + installFragmentLifecycleInstrumentation(app); + installScreenTrackingInstrumentation(app); + } + + private void installStartupTimerInstrumentation(InstrumentedApplication app) { + app.getApplication() + .registerActivityLifecycleCallbacks(startupTimer.createLifecycleCallback()); + } + + private void installActivityLifecycleEventsInstrumentation(InstrumentedApplication app) { + Application.ActivityLifecycleCallbacks activityCallbacks = buildActivityEventsCallback(app); + app.getApplication().registerActivityLifecycleCallbacks(activityCallbacks); + } + + @NonNull + private Application.ActivityLifecycleCallbacks buildActivityEventsCallback( + InstrumentedApplication instrumentedApp) { + Tracer delegateTracer = + instrumentedApp.getOpenTelemetrySdk().getTracer(INSTRUMENTATION_SCOPE); + Tracer tracer = tracerCustomizer.apply(delegateTracer); + + ActivityTracerCache tracers = + new ActivityTracerCache(tracer, visibleScreenTracker, startupTimer); + if (Build.VERSION.SDK_INT < 29) { + return new Pre29ActivityCallbacks(tracers); + } + return new ActivityCallbacks(tracers); + } + + private void installFragmentLifecycleInstrumentation(InstrumentedApplication app) { + Application.ActivityLifecycleCallbacks fragmentRegisterer = buildFragmentRegisterer(app); + app.getApplication().registerActivityLifecycleCallbacks(fragmentRegisterer); + } + + @NonNull + private Application.ActivityLifecycleCallbacks buildFragmentRegisterer( + InstrumentedApplication app) { + + Tracer delegateTracer = app.getOpenTelemetrySdk().getTracer(INSTRUMENTATION_SCOPE); + Tracer tracer = tracerCustomizer.apply(delegateTracer); + RumFragmentLifecycleCallbacks fragmentLifecycle = + new RumFragmentLifecycleCallbacks(tracer, visibleScreenTracker); + if (Build.VERSION.SDK_INT < 29) { + return RumFragmentActivityRegisterer.createPre29(fragmentLifecycle); + } + return RumFragmentActivityRegisterer.create(fragmentLifecycle); + } + + private void installScreenTrackingInstrumentation(InstrumentedApplication app) { + Application.ActivityLifecycleCallbacks screenTrackingBinding = + buildScreenTrackingBinding(visibleScreenTracker); + app.getApplication().registerActivityLifecycleCallbacks(screenTrackingBinding); + } + + @NonNull + private Application.ActivityLifecycleCallbacks buildScreenTrackingBinding( + VisibleScreenTracker visibleScreenTracker) { + if (Build.VERSION.SDK_INT < 29) { + return new Pre29VisibleScreenLifecycleBinding(visibleScreenTracker); + } + return new VisibleScreenLifecycleBinding(visibleScreenTracker); + } +} diff --git a/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentationBuilder.java b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentationBuilder.java new file mode 100644 index 000000000..b073e0305 --- /dev/null +++ b/splunk-otel-android/src/main/java/io/opentelemetry/rum/internal/instrumentation/lifecycle/AndroidLifecycleInstrumentationBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright Splunk Inc. + * + * 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 io.opentelemetry.rum.internal.instrumentation.lifecycle; + +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.rum.internal.instrumentation.activity.VisibleScreenTracker; +import io.opentelemetry.rum.internal.instrumentation.startup.AppStartupTimer; +import java.util.function.Function; + +public class AndroidLifecycleInstrumentationBuilder { + AppStartupTimer startupTimer; + VisibleScreenTracker visibleScreenTracker; + Function tracerCustomizer = Function.identity(); + + public AndroidLifecycleInstrumentationBuilder setStartupTimer(AppStartupTimer timer) { + this.startupTimer = timer; + return this; + } + + public AndroidLifecycleInstrumentationBuilder setVisibleScreenTracker( + VisibleScreenTracker tracker) { + this.visibleScreenTracker = tracker; + return this; + } + + public AndroidLifecycleInstrumentationBuilder setTracerCustomizer( + Function customizer) { + this.tracerCustomizer = customizer; + return this; + } + + public AndroidLifecycleInstrumentation build() { + return new AndroidLifecycleInstrumentation(this); + } +}