Skip to content

Commit aebf0e1

Browse files
authored
OpenTelemetryRum.shutdown() and instrumentation uninstall (#1109)
* allow instrumentation to be uninstalled # Conflicts: # core/src/main/java/io/opentelemetry/android/OpenTelemetryRumImpl.java * allow instrumentation to be uninstalled * allow slow rendering instrumentation to be shut down * also shut down the sdk * shut down the export scheduler as well. * spotless
1 parent 8e63e8e commit aebf0e1

11 files changed

Lines changed: 119 additions & 19 deletions

File tree

core/src/main/java/io/opentelemetry/android/NoopOpenTelemetryRum.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ public String getRumSessionId() {
2525

2626
@Override
2727
public void emitEvent(String eventName, String body, Attributes attributes) {}
28+
29+
@Override
30+
public void shutdown() {
31+
// nop
32+
}
2833
}

core/src/main/java/io/opentelemetry/android/OpenTelemetryRum.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@
1515
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
1616
import io.opentelemetry.sdk.trace.SdkTracerProvider;
1717

18-
/**
19-
* Entrypoint for the OpenTelemetry Real User Monitoring library for Android.
20-
*
21-
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
22-
* at any time.
23-
*/
18+
/** Entrypoint for the OpenTelemetry Real User Monitoring library for Android. */
2419
public interface OpenTelemetryRum {
2520

2621
/**
@@ -137,4 +132,10 @@ default void emitEvent(String eventName, Attributes attributes) {
137132
* @param attributes The attributes associated with the event, providing metadata.
138133
*/
139134
void emitEvent(String eventName, String body, Attributes attributes);
135+
136+
/**
137+
* Initiates orderly shutdown of this OpenTelemetryRum instance. After this method completes,
138+
* the instance should be considered invalid and no longer used.
139+
*/
140+
void shutdown();
140141
}

core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,13 @@ public OpenTelemetryRum build() {
331331
otelSdkReadyListeners.forEach(listener -> listener.accept(sdk));
332332

333333
SdkPreconfiguredRumBuilder delegate =
334-
new SdkPreconfiguredRumBuilder(application, sdk, sessionProvider, config);
334+
new SdkPreconfiguredRumBuilder(application, sdk, sessionProvider, config)
335+
.setShutdownHook(
336+
() -> {
337+
if (exportScheduleHandler != null) {
338+
exportScheduleHandler.disable();
339+
}
340+
});
335341

336342
// AsyncTask is deprecated but the thread pool is still used all over the Android SDK
337343
// and it provides a way to get a background thread without having to create a new one.

core/src/main/java/io/opentelemetry/android/OpenTelemetryRumImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk
1414
internal class OpenTelemetryRumImpl(
1515
private val openTelemetrySdk: OpenTelemetrySdk,
1616
private val sessionProvider: SessionProvider,
17+
private val onShutdown: Runnable,
1718
) : OpenTelemetryRum {
1819
private val logger: ExtendedLogger =
1920
openTelemetrySdk.logsBridge
@@ -36,4 +37,8 @@ internal class OpenTelemetryRumImpl(
3637
.setAllAttributes(attributes)
3738
.emit()
3839
}
40+
41+
override fun shutdown() {
42+
onShutdown.run()
43+
}
3944
}

core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class SdkPreconfiguredRumBuilder internal constructor(
1919
private val sessionProvider: SessionProvider,
2020
private val config: OtelRumConfig,
2121
) {
22+
private var onShutdown: Runnable = Runnable {} // nop
2223
private val instrumentations = mutableListOf<AndroidInstrumentation>()
2324

2425
/**
@@ -31,6 +32,15 @@ class SdkPreconfiguredRumBuilder internal constructor(
3132
return this
3233
}
3334

35+
/**
36+
* Call this to provide a shutdown hook that will be called when the OpenTelemetryRum
37+
* instance is shut down.
38+
*/
39+
fun setShutdownHook(onShutdown: Runnable): SdkPreconfiguredRumBuilder {
40+
this.onShutdown = onShutdown
41+
return this
42+
}
43+
3444
/**
3545
* Creates a new instance of [OpenTelemetryRum] with the settings of this [ ].
3646
*
@@ -41,11 +51,19 @@ class SdkPreconfiguredRumBuilder internal constructor(
4151
* @return A new [OpenTelemetryRum] instance.
4252
*/
4353
fun build(): OpenTelemetryRum {
44-
val openTelemetryRum = OpenTelemetryRumImpl(sdk, sessionProvider)
54+
val ctx = InstallationContext(application, sdk, sessionProvider)
55+
val enabledInstrumentations = getEnabledInstrumentations()
56+
val onShutdown: () -> Unit = {
57+
for (instrumentation in enabledInstrumentations) {
58+
instrumentation.uninstall(ctx)
59+
}
60+
sdk.shutdown()
61+
onShutdown.run()
62+
}
63+
val openTelemetryRum = OpenTelemetryRumImpl(sdk, sessionProvider, onShutdown)
4564

4665
// Install instrumentations
47-
val ctx = InstallationContext(application, openTelemetryRum.openTelemetry, sessionProvider)
48-
for (instrumentation in getEnabledInstrumentations()) {
66+
for (instrumentation in enabledInstrumentations) {
4967
instrumentation.install(ctx)
5068
}
5169

core/src/main/java/io/opentelemetry/android/features/diskbuffering/scheduler/DefaultExportScheduleHandler.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,9 @@ class DefaultExportScheduleHandler(
2020
periodicWorkService.enqueue(exportScheduler)
2121
}
2222
}
23+
24+
override fun disable() {
25+
super.disable()
26+
exportScheduler.shutdown()
27+
}
2328
}

core/src/main/java/io/opentelemetry/android/features/diskbuffering/scheduler/DefaultExportScheduler.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import java.util.concurrent.TimeUnit
1616
class DefaultExportScheduler(
1717
periodicWorkProvider: () -> PeriodicWork,
1818
) : PeriodicRunnable(periodicWorkProvider) {
19+
@Volatile
20+
private var isShutDown: Boolean = false
21+
1922
companion object {
2023
private val DELAY_BEFORE_NEXT_EXPORT_IN_MILLIS = TimeUnit.SECONDS.toMillis(10)
2124
}
@@ -32,7 +35,11 @@ class DefaultExportScheduler(
3235
}
3336
}
3437

35-
override fun shouldStopRunning(): Boolean = SignalFromDiskExporter.get() == null
38+
fun shutdown() {
39+
isShutDown = true
40+
}
41+
42+
override fun shouldStopRunning(): Boolean = isShutDown || (SignalFromDiskExporter.get() == null)
3643

3744
override fun minimumDelayUntilNextRunInMillis(): Long = DELAY_BEFORE_NEXT_EXPORT_IN_MILLIS
3845
}

instrumentation/android-instrumentation/src/main/java/io/opentelemetry/android/instrumentation/AndroidInstrumentation.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ interface AndroidInstrumentation {
3030
*/
3131
fun install(ctx: InstallationContext)
3232

33+
/**
34+
* This method can be called to uninstall the instrumentation. Implementations should remove all
35+
* used resources and shut down cleanly.
36+
*
37+
* @param ctx The InstallationContext under which the instrumentation had been removed.
38+
*/
39+
fun uninstall(ctx: InstallationContext) {
40+
// NOP default implementation
41+
}
42+
3343
/**
3444
* The canonical short name for this instrumentation.
3545
*/

instrumentation/slowrendering/src/main/java/io/opentelemetry/android/instrumentation/slowrendering/SlowRenderListener.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.opentelemetry.api.trace.Tracer;
2727
import java.time.Duration;
2828
import java.time.Instant;
29+
import java.util.Map;
2930
import java.util.concurrent.ConcurrentHashMap;
3031
import java.util.concurrent.ConcurrentMap;
3132
import java.util.concurrent.Executors;
@@ -92,8 +93,21 @@ void start() {
9293
TimeUnit.MILLISECONDS);
9394
}
9495

96+
public void shutdown() {
97+
executorService.shutdownNow();
98+
for (Map.Entry<Activity, PerActivityListener> entry : activities.entrySet()) {
99+
Activity activity = entry.getKey();
100+
PerActivityListener listener = entry.getValue();
101+
activity.getWindow().removeOnFrameMetricsAvailableListener(listener);
102+
}
103+
activities.clear();
104+
}
105+
95106
@Override
96107
public void onActivityResumed(@NonNull Activity activity) {
108+
if (executorService.isShutdown()) {
109+
return;
110+
}
97111
PerActivityListener listener = new PerActivityListener(activity);
98112
PerActivityListener existing = activities.putIfAbsent(activity, listener);
99113
if (existing == null) {
@@ -103,6 +117,9 @@ public void onActivityResumed(@NonNull Activity activity) {
103117

104118
@Override
105119
public void onActivityPaused(@NonNull Activity activity) {
120+
if (executorService.isShutdown()) {
121+
return;
122+
}
106123
PerActivityListener listener = activities.remove(activity);
107124
if (listener != null) {
108125
activity.getWindow().removeOnFrameMetricsAvailableListener(listener);

instrumentation/slowrendering/src/main/java/io/opentelemetry/android/instrumentation/slowrendering/SlowRenderingInstrumentation.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@
55

66
package io.opentelemetry.android.instrumentation.slowrendering;
77

8+
import static io.opentelemetry.android.common.RumConstants.OTEL_RUM_LOG_TAG;
9+
810
import android.os.Build;
911
import android.util.Log;
1012
import androidx.annotation.NonNull;
13+
import androidx.annotation.Nullable;
1114
import com.google.auto.service.AutoService;
12-
import io.opentelemetry.android.common.RumConstants;
1315
import io.opentelemetry.android.instrumentation.AndroidInstrumentation;
1416
import io.opentelemetry.android.instrumentation.InstallationContext;
1517
import java.time.Duration;
18+
import org.jetbrains.annotations.NotNull;
1619

1720
/** Entrypoint for installing the slow rendering detection instrumentation. */
1821
@AutoService(AndroidInstrumentation.class)
1922
public final class SlowRenderingInstrumentation implements AndroidInstrumentation {
2023

2124
private static final String INSTRUMENTATION_NAME = "slowrendering";
2225
Duration slowRenderingDetectionPollInterval = Duration.ofSeconds(1);
26+
@Nullable private volatile SlowRenderListener detector = null;
2327

2428
/**
2529
* Configures the rate at which frame render durations are polled.
@@ -30,7 +34,7 @@ public final class SlowRenderingInstrumentation implements AndroidInstrumentatio
3034
public SlowRenderingInstrumentation setSlowRenderingDetectionPollInterval(Duration interval) {
3135
if (interval.toMillis() <= 0) {
3236
Log.e(
33-
RumConstants.OTEL_RUM_LOG_TAG,
37+
OTEL_RUM_LOG_TAG,
3438
"Invalid slowRenderingDetectionPollInterval: "
3539
+ interval
3640
+ "; must be positive");
@@ -44,12 +48,18 @@ public SlowRenderingInstrumentation setSlowRenderingDetectionPollInterval(Durati
4448
public void install(@NonNull InstallationContext ctx) {
4549
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
4650
Log.w(
47-
RumConstants.OTEL_RUM_LOG_TAG,
51+
OTEL_RUM_LOG_TAG,
4852
"Slow/frozen rendering detection is not supported on platforms older than Android N (SDK version 24).");
4953
return;
5054
}
55+
if (detector != null) {
56+
Log.w(
57+
OTEL_RUM_LOG_TAG,
58+
"SlowRenderingInstrumentation skipping installation (detector already installed)");
59+
return;
60+
}
5161

52-
SlowRenderListener detector =
62+
detector =
5363
new SlowRenderListener(
5464
ctx.getOpenTelemetry().getTracer("io.opentelemetry.slow-rendering"),
5565
slowRenderingDetectionPollInterval);
@@ -58,6 +68,19 @@ public void install(@NonNull InstallationContext ctx) {
5868
detector.start();
5969
}
6070

71+
@Override
72+
public void uninstall(@NotNull InstallationContext ctx) {
73+
if (detector == null) {
74+
Log.w(
75+
OTEL_RUM_LOG_TAG,
76+
"SlowRenderingInstrumentation skipping uninstall (detector is null)");
77+
return;
78+
}
79+
ctx.getApplication().unregisterActivityLifecycleCallbacks(detector);
80+
detector.shutdown();
81+
detector = null;
82+
}
83+
6184
@NonNull
6285
@Override
6386
public String getName() {

0 commit comments

Comments
 (0)