Skip to content

Commit

Permalink
Initial implementation of OpenTelemetryRum (#378)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Rzeszutek authored Oct 30, 2022
1 parent 73725ce commit 3d91ee1
Show file tree
Hide file tree
Showing 30 changed files with 926 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.rum.internal.instrumentation.ApplicationStateListener;
import java.util.concurrent.atomic.AtomicBoolean;

class NetworkMonitor implements AppStateListener {
class NetworkMonitor implements ApplicationStateListener {
static final AttributeKey<String> NETWORK_STATUS_KEY = stringKey("network.status");

private final ConnectionUtil connectionUtil;
Expand All @@ -40,12 +41,12 @@ void addConnectivityListener(Tracer tracer) {
}

@Override
public void appForegrounded() {
public void onApplicationForegrounded() {
shouldEmitChangeEvents.set(true);
}

@Override
public void appBackgrounded() {
public void onApplicationBackgrounded() {
shouldEmitChangeEvents.set(false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.rum.internal.OpenTelemetryRum;
import java.util.function.Consumer;
import okhttp3.Call;
import okhttp3.OkHttpClient;
Expand All @@ -31,7 +32,7 @@ class NoOpSplunkRum extends SplunkRum {
// passing null values here is fine, they'll never get used anyway
@SuppressWarnings("NullAway")
private NoOpSplunkRum() {
super(null, null, null);
super(OpenTelemetryRum.noop(), null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,16 @@ class RumAttributeAppender implements SpanProcessor {

static final AttributeKey<String> SESSION_ID_KEY = stringKey("splunk.rumSessionId");

private final SessionId sessionId;
private final VisibleScreenTracker visibleScreenTracker;
private final ConnectionUtil connectionUtil;

RumAttributeAppender(
SessionId sessionId,
VisibleScreenTracker visibleScreenTracker,
ConnectionUtil connectionUtil) {
this.sessionId = sessionId;
RumAttributeAppender(VisibleScreenTracker visibleScreenTracker, ConnectionUtil connectionUtil) {
this.visibleScreenTracker = visibleScreenTracker;
this.connectionUtil = connectionUtil;
}

@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
span.setAttribute(SESSION_ID_KEY, sessionId.getSessionId());

String currentScreen = visibleScreenTracker.getCurrentlyVisibleScreen();
span.setAttribute(SplunkRum.SCREEN_NAME_KEY, currentScreen);

Expand Down
277 changes: 141 additions & 136 deletions splunk-otel-android/src/main/java/com/splunk/rum/RumInitializer.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,21 @@
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;
import java.util.function.Supplier;

/**
* Session ID ratio based sampler. Uses {@link Sampler#traceIdRatioBased(double)} sampler
* internally, but passes sessionId instead of traceId to the underlying sampler in order to use the
* same ratio logic but on sessionId instead. This is valid as {@link SessionId} uses {@link
* same ratio logic but on sessionId instead. This is valid as sessionId uses {@link
* io.opentelemetry.api.trace.TraceId#fromLongs(long, long)} internally to generate random session
* IDs.
*/
class SessionIdRatioBasedSampler implements Sampler {
private final SessionId sessionId;
private final Sampler ratioBasedSampler;
private final Supplier<SplunkRum> splunkRumSupplier;

SessionIdRatioBasedSampler(double ratio, SessionId sessionId) {
this.sessionId = sessionId;
SessionIdRatioBasedSampler(double ratio, Supplier<SplunkRum> splunkRumSupplier) {
this.splunkRumSupplier = splunkRumSupplier;
// SessionId uses the same format as TraceId, so we can reuse trace ID ratio sampler.
this.ratioBasedSampler = Sampler.traceIdRatioBased(ratio);
}
Expand All @@ -51,7 +52,12 @@ public SamplingResult shouldSample(
List<LinkData> parentLinks) {
// Replace traceId with sessionId
return ratioBasedSampler.shouldSample(
parentContext, sessionId.getSessionId(), name, spanKind, attributes, parentLinks);
parentContext,
splunkRumSupplier.get().getRumSessionId(),
name,
spanKind,
attributes,
parentLinks);
}

@Override
Expand Down
31 changes: 17 additions & 14 deletions splunk-otel-android/src/main/java/com/splunk/rum/SplunkRum.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTelemetry;
import io.opentelemetry.rum.internal.GlobalAttributesSpanAppender;
import io.opentelemetry.rum.internal.OpenTelemetryRum;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -79,21 +80,16 @@ public class SplunkRum {

@Nullable private static SplunkRum INSTANCE;

private final SessionId sessionId;
private final OpenTelemetrySdk openTelemetrySdk;
private final OpenTelemetryRum openTelemetryRum;
private final GlobalAttributesSpanAppender globalAttributes;

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

SplunkRum(
OpenTelemetrySdk openTelemetrySdk,
SessionId sessionId,
GlobalAttributesSpanAppender globalAttributes) {
this.openTelemetrySdk = openTelemetrySdk;
this.sessionId = sessionId;
SplunkRum(OpenTelemetryRum openTelemetryRum, GlobalAttributesSpanAppender globalAttributes) {
this.openTelemetryRum = openTelemetryRum;
this.globalAttributes = globalAttributes;
}

Expand All @@ -119,7 +115,8 @@ static SplunkRum initialize(
if (builder.debugEnabled) {
Log.i(
LOG_TAG,
"Splunk RUM monitoring initialized with session ID: " + INSTANCE.sessionId);
"Splunk RUM monitoring initialized with session ID: "
+ INSTANCE.getRumSessionId());
}

return INSTANCE;
Expand Down Expand Up @@ -163,7 +160,7 @@ public Call.Factory createRumOkHttpCallFactory(OkHttpClient client) {
}

private OkHttpTelemetry createOkHttpTracing() {
return OkHttpTelemetry.builder(openTelemetrySdk)
return OkHttpTelemetry.builder(getOpenTelemetry())
.addAttributesExtractor(
new RumResponseAttributesExtractor(new ServerTimingHeaderParser()))
.build();
Expand All @@ -174,7 +171,7 @@ private OkHttpTelemetry createOkHttpTracing() {
* instrumentation.
*/
public OpenTelemetry getOpenTelemetry() {
return openTelemetrySdk;
return openTelemetryRum.getOpenTelemetry();
}

/**
Expand All @@ -183,7 +180,7 @@ public OpenTelemetry getOpenTelemetry() {
* recommended that you do not cache this value, but always retrieve it from here when needed.
*/
public String getRumSessionId() {
return sessionId.getSessionId();
return openTelemetryRum.getRumSessionId();
}

/**
Expand Down Expand Up @@ -247,7 +244,7 @@ public void addRumException(Throwable throwable, Attributes attributes) {
}

Tracer getTracer() {
return openTelemetrySdk.getTracer(RUM_TRACER_NAME);
return getOpenTelemetry().getTracer(RUM_TRACER_NAME);
}

void recordAnr(StackTraceElement[] stackTrace) {
Expand Down Expand Up @@ -307,7 +304,13 @@ static void resetSingletonForTest() {

// (currently) for testing only
void flushSpans() {
openTelemetrySdk.getSdkTracerProvider().forceFlush().join(1, TimeUnit.SECONDS);
OpenTelemetry openTelemetry = getOpenTelemetry();
if (openTelemetry instanceof OpenTelemetrySdk) {
((OpenTelemetrySdk) openTelemetry)
.getSdkTracerProvider()
.forceFlush()
.join(1, TimeUnit.SECONDS);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,26 @@
* limitations under the License.
*/

package com.splunk.rum;
package io.opentelemetry.rum.internal;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.opentelemetry.rum.internal.instrumentation.ApplicationStateListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class AppStateWatcher implements Application.ActivityLifecycleCallbacks {
final class ApplicationStateWatcher implements Application.ActivityLifecycleCallbacks {

private final List<AppStateListener> appStateListeners;
private final List<ApplicationStateListener> applicationStateListeners =
new CopyOnWriteArrayList<>();
// we count the number of activities that have been "started" and not yet "stopped" here to
// figure out when the app goes into the background.
private int numberOfOpenActivities = 0;

AppStateWatcher(List<AppStateListener> appStateListeners) {
this.appStateListeners = appStateListeners;
}
public ApplicationStateWatcher() {}

@Override
public void onActivityCreated(
Expand All @@ -41,8 +42,8 @@ public void onActivityCreated(
@Override
public void onActivityStarted(@NonNull Activity activity) {
if (numberOfOpenActivities == 0) {
for (AppStateListener appListener : appStateListeners) {
appListener.appForegrounded();
for (ApplicationStateListener listener : applicationStateListeners) {
listener.onApplicationForegrounded();
}
}
numberOfOpenActivities++;
Expand All @@ -57,8 +58,8 @@ public void onActivityPaused(@NonNull Activity activity) {}
@Override
public void onActivityStopped(@NonNull Activity activity) {
if (--numberOfOpenActivities == 0) {
for (AppStateListener appListener : appStateListeners) {
appListener.appBackgrounded();
for (ApplicationStateListener listener : applicationStateListeners) {
listener.onApplicationBackgrounded();
}
}
}
Expand All @@ -68,4 +69,8 @@ public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bun

@Override
public void onActivityDestroyed(@NonNull Activity activity) {}

void registerListener(ApplicationStateListener listener) {
applicationStateListeners.add(listener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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;

import android.app.Application;
import io.opentelemetry.rum.internal.instrumentation.ApplicationStateListener;
import io.opentelemetry.rum.internal.instrumentation.InstrumentedApplication;
import io.opentelemetry.sdk.OpenTelemetrySdk;

final class InstrumentedApplicationImpl implements InstrumentedApplication {

private final Application application;
private final OpenTelemetrySdk openTelemetrySdk;
private final ApplicationStateWatcher applicationStateWatcher;

InstrumentedApplicationImpl(
Application application,
OpenTelemetrySdk openTelemetrySdk,
ApplicationStateWatcher applicationStateWatcher) {
this.application = application;
this.openTelemetrySdk = openTelemetrySdk;
this.applicationStateWatcher = applicationStateWatcher;
}

@Override
public Application getApplication() {
return application;
}

@Override
public OpenTelemetrySdk getOpenTelemetrySdk() {
return openTelemetrySdk;
}

@Override
public void registerApplicationStateListener(ApplicationStateListener listener) {
applicationStateWatcher.registerListener(listener);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,22 @@
* limitations under the License.
*/

package com.splunk.rum;
package io.opentelemetry.rum.internal;

interface AppStateListener {
void appForegrounded();
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.TraceId;

void appBackgrounded();
enum NoopOpenTelemetryRum implements OpenTelemetryRum {
INSTANCE;

@Override
public OpenTelemetry getOpenTelemetry() {
return OpenTelemetry.noop();
}

@Override
public String getRumSessionId() {
// RUM sessionId has the same format as traceId
return TraceId.getInvalid();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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;

import io.opentelemetry.api.OpenTelemetry;

/**
* Entrypoint for the OpenTelemetry Real User Monitoring library for Android.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public interface OpenTelemetryRum {

/** Returns a new {@link OpenTelemetryRumBuilder} for {@link OpenTelemetryRum}. */
static OpenTelemetryRumBuilder builder() {
return new OpenTelemetryRumBuilder();
}

/** Returns a no-op implementation of {@link OpenTelemetryRum}. */
static OpenTelemetryRum noop() {
return NoopOpenTelemetryRum.INSTANCE;
}

/**
* Get a handle to the instance of the {@linkplain OpenTelemetry OpenTelemetry API} that this
* instance is using for instrumentation.
*/
OpenTelemetry getOpenTelemetry();

/**
* Get the client session ID associated with this instance of the RUM instrumentation library.
* Note: this value will change throughout the lifetime of an application instance, so it is
* recommended that you do not cache this value, but always retrieve it from here when needed.
*/
String getRumSessionId();
}
Loading

0 comments on commit 3d91ee1

Please sign in to comment.