Skip to content

Commit

Permalink
Enable manual creation of screen view spans
Browse files Browse the repository at this point in the history
  • Loading branch information
jtmalinowski committed Jun 10, 2024
1 parent 1bc3c4c commit 053e0ec
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
NavHostFragment.findNavController(FirstFragment.this)
.navigate(R.id.action_FirstFragment_to_SecondFragment));

binding.buttonScreenName.setOnClickListener(v -> {
SplunkRum.getInstance().setScreenName("custom-screen");
});

binding.crash.setOnClickListener(v -> multiThreadCrashing());

binding.httpMe.setOnClickListener(
Expand Down Expand Up @@ -184,12 +188,22 @@ public LiveData<String> getSessionId() {
public void onDestroyView() {
super.onDestroyView();
binding = null;

SplunkRum.getInstance().setScreenName(null);
}

@Override
public void onResume() {
super.onResume();
sessionId.postValue(splunkRum.getRumSessionId());

SplunkRum.getInstance().setScreenName("custom-resumed-name", "Resumed");
}

@Override
public void onPause() {
super.onPause();
SplunkRum.getInstance().setScreenName(null);
}

@SuppressLint("AllowAllHostnameVerifier")
Expand Down
6 changes: 6 additions & 0 deletions sample-app/src/main/res/layout/fragment_first.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
android:layout_height="wrap_content"
android:text="@string/next" />

<Button
android:id="@+id/button_screen_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/screen_name" />

<Button
android:id="@+id/crash"
android:layout_width="wrap_content"
Expand Down
1 change: 1 addition & 0 deletions sample-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="screen_name">Custom screen name</string>

<string name="hello_first_fragment">Hello first fragment</string>
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.splunk.rum;

import androidx.annotation.Nullable;

import java.util.concurrent.atomic.AtomicReference;

import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;

public class ExplicitVisibleScreenNameTracker extends VisibleScreenTracker {
private final AtomicReference<String> lastScreenName = new AtomicReference<>();
private final AtomicReference<String> previouslyLastScreenName = new AtomicReference<>();

public void setExplicitScreenName(String screenName) {
this.previouslyLastScreenName.set(this.lastScreenName.get());
this.lastScreenName.set(screenName);
}

@Nullable
@Override
public String getPreviouslyVisibleScreen() {
String screenName = previouslyLastScreenName.get();
if (screenName != null) {
return screenName;
}

return super.getPreviouslyVisibleScreen();
}

@Override
public String getCurrentlyVisibleScreen() {
String screenName = lastScreenName.get();
if (screenName != null) {
return screenName;
}

return super.getCurrentlyVisibleScreen();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class NoOpSplunkRum extends SplunkRum {
// passing null values here is fine, they'll never get used anyway
@SuppressWarnings("NullAway")
private NoOpSplunkRum() {
super(OpenTelemetryRum.noop(), null);
super(OpenTelemetryRum.noop(), null, null);
}

@Override
Expand Down Expand Up @@ -79,4 +79,9 @@ public void integrateWithBrowserRum(WebView webView) {
void flushSpans() {
// no-op
}

@Override
public void setScreenName(String screenName) {
// no-op
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.opentelemetry.android.OpenTelemetryRum;
import io.opentelemetry.android.OpenTelemetryRumBuilder;
import io.opentelemetry.android.RuntimeDetailsExtractor;
import io.opentelemetry.android.ScreenAttributesSpanProcessor;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
import io.opentelemetry.android.instrumentation.anr.AnrDetector;
Expand All @@ -52,6 +53,7 @@
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.sdk.trace.SpanLimits;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
Expand Down Expand Up @@ -86,7 +88,7 @@ class RumInitializer {
}

SplunkRum initialize(Looper mainLooper) {
VisibleScreenTracker visibleScreenTracker = new VisibleScreenTracker();
ExplicitVisibleScreenNameTracker visibleScreenTracker = new ExplicitVisibleScreenNameTracker();

initializationEvents.begin();

Expand All @@ -98,6 +100,7 @@ SplunkRum initialize(Looper mainLooper) {
config.disableNetworkChangeMonitoring();
}

config.disableScreenAttributes();
OpenTelemetryRumBuilder otelRumBuilder = OpenTelemetryRum.builder(application, config);

otelRumBuilder.mergeResource(createSplunkResource());
Expand Down Expand Up @@ -193,6 +196,13 @@ SplunkRum initialize(Looper mainLooper) {
installCrashReporter(otelRumBuilder);
}

otelRumBuilder.addTracerProviderCustomizer(
(tracerProviderBuilder, app) -> {
SpanProcessor screenAttributesAppender =
new ScreenAttributesSpanProcessor(visibleScreenTracker);
return tracerProviderBuilder.addSpanProcessor(screenAttributesAppender);
});

// Lifecycle events instrumentation are always installed.
installLifecycleInstrumentations(otelRumBuilder, visibleScreenTracker);

Expand All @@ -204,7 +214,7 @@ SplunkRum initialize(Looper mainLooper) {
builder.getConfigFlags(),
openTelemetryRum.getOpenTelemetry().getTracer(RUM_TRACER_NAME));

return new SplunkRum(openTelemetryRum, globalAttributeSupplier);
return new SplunkRum(openTelemetryRum, globalAttributeSupplier, visibleScreenTracker);
}

@NonNull
Expand Down
25 changes: 24 additions & 1 deletion splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.splunk.rum;

import static io.opentelemetry.android.RumConstants.LAST_SCREEN_NAME_KEY;
import static io.opentelemetry.android.RumConstants.SCREEN_NAME_KEY;
import static io.opentelemetry.api.common.AttributeKey.doubleKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;

Expand All @@ -28,6 +30,7 @@
import androidx.annotation.Nullable;
import com.splunk.rum.internal.GlobalAttributesSupplier;
import io.opentelemetry.android.OpenTelemetryRum;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
import io.opentelemetry.android.instrumentation.startup.AppStartupTimer;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
Expand Down Expand Up @@ -72,15 +75,17 @@ public class SplunkRum {

private final OpenTelemetryRum openTelemetryRum;
private final GlobalAttributesSupplier globalAttributes;
private final ExplicitVisibleScreenNameTracker visibleScreenTracker;

static {
Handler handler = new Handler(Looper.getMainLooper());
startupTimer.detectBackgroundStart(handler);
}

SplunkRum(OpenTelemetryRum openTelemetryRum, GlobalAttributesSupplier globalAttributes) {
SplunkRum(OpenTelemetryRum openTelemetryRum, GlobalAttributesSupplier globalAttributes, ExplicitVisibleScreenNameTracker visibleScreenTracker) {
this.openTelemetryRum = openTelemetryRum;
this.globalAttributes = globalAttributes;
this.visibleScreenTracker = visibleScreenTracker;
}

/** Creates a new {@link SplunkRumBuilder}, used to set up a {@link SplunkRum} instance. */
Expand Down Expand Up @@ -113,6 +118,24 @@ static SplunkRum initialize(SplunkRumBuilder builder, Application application) {
return INSTANCE;
}

public void setScreenName(String screenName, String spanType) {
visibleScreenTracker.setExplicitScreenName(screenName);

if (screenName != null) {
Span span = getTracer()
.spanBuilder(spanType)
.setAttribute(COMPONENT_KEY, "ui")
.startSpan();
span.setAttribute(SCREEN_NAME_KEY, screenName);
span.setAttribute(LAST_SCREEN_NAME_KEY, visibleScreenTracker.getPreviouslyVisibleScreen());
span.end();
}
}

public void setScreenName(String screenName) {
setScreenName(screenName, "Created");
}

/** Returns {@code true} if the Splunk RUM library has been successfully initialized. */
public static boolean isInitialized() {
return INSTANCE != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ void doesNotThrow() {

Location location = mock(Location.class);
instance.updateLocation(location);

instance.setScreenName("explicit-screen-name");
instance.flushSpans();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package com.splunk.rum;

import static com.splunk.rum.SplunkRum.COMPONENT_KEY;
import static io.opentelemetry.android.RumConstants.LAST_SCREEN_NAME_KEY;
import static io.opentelemetry.android.RumConstants.SCREEN_NAME_KEY;
import static io.opentelemetry.api.common.AttributeKey.longKey;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -70,8 +73,12 @@ public class SplunkRumTest {
@Mock private OpenTelemetryRum openTelemetryRum;
@Mock private GlobalAttributesSupplier globalAttributes;

private ExplicitVisibleScreenNameTracker screenNameTracker;

@BeforeEach
public void setup() {
screenNameTracker = new ExplicitVisibleScreenNameTracker();

tracer = otelTesting.getOpenTelemetry().getTracer("testTracer");
SplunkRum.resetSingletonForTest();
}
Expand Down Expand Up @@ -148,7 +155,7 @@ void nonNullMethods() {
void addEvent() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Attributes attributes = Attributes.of(stringKey("one"), "1", longKey("two"), 2L);
splunkRum.addRumEvent("foo", attributes);
Expand All @@ -166,7 +173,7 @@ void addException() {

when(openTelemetryRum.getOpenTelemetry()).thenReturn(testSdk);

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

NullPointerException exception = new NullPointerException("oopsie");
Attributes attributes = Attributes.of(stringKey("one"), "1", longKey("two"), 2L);
Expand Down Expand Up @@ -197,7 +204,7 @@ private OpenTelemetrySdk buildTestSdk(InMemorySpanExporter testExporter) {
void createAndEnd() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Span span = splunkRum.startWorkflow("workflow");
Span inner = tracer.spanBuilder("foo").startSpan();
Expand Down Expand Up @@ -255,7 +262,7 @@ void updateLocation() {
.when(globalAttributes)
.update(isA(Consumer.class));

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes);
SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);

Location location = mock(Location.class);
when(location.getLatitude()).thenReturn(42d);
Expand All @@ -274,4 +281,37 @@ void updateLocation() {

assertTrue(updatedAttributes.get().isEmpty());
}

@Test
void canSetScreenName() {
when(openTelemetryRum.getOpenTelemetry()).thenReturn(otelTesting.getOpenTelemetry());

SplunkRum splunkRum = new SplunkRum(openTelemetryRum, globalAttributes, screenNameTracker);
splunkRum.setScreenName("screen-1");
splunkRum.setScreenName("screen-2");

// pause and resume
splunkRum.setScreenName(null);
splunkRum.setScreenName("screen-2", "Resumed");

// exit the view with explicit screen names
// both last screen name and second last screen name have to be cleared, hence the doubled call
splunkRum.setScreenName(null);
splunkRum.setScreenName(null);

List<SpanData> spans = otelTesting.getSpans();
assertEquals(3, spans.size());

assertEquals("Created", spans.get(0).getName());
assertEquals("screen-1", spans.get(0).getAttributes().get(SCREEN_NAME_KEY));
assertNull(spans.get(0).getAttributes().get(LAST_SCREEN_NAME_KEY));

assertEquals("Created", spans.get(1).getName());
assertEquals("screen-2", spans.get(1).getAttributes().get(SCREEN_NAME_KEY));
assertEquals("screen-1", spans.get(1).getAttributes().get(LAST_SCREEN_NAME_KEY));

assertEquals("Resumed", spans.get(2).getName());
assertEquals("screen-2", spans.get(2).getAttributes().get(SCREEN_NAME_KEY));
assertNull(spans.get(2).getAttributes().get(LAST_SCREEN_NAME_KEY));
}
}

0 comments on commit 053e0ec

Please sign in to comment.