Skip to content
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

feat(react-native): support for starting native spans on android #536

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
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package com.bugsnag.reactnative.performance;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;

import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import java.security.SecureRandom;

import com.bugsnag.android.performance.BugsnagPerformance;
import com.bugsnag.android.performance.SpanOptions;

import com.bugsnag.android.performance.internal.BugsnagClock;
import com.bugsnag.android.performance.internal.EncodingUtils;
import com.bugsnag.android.performance.internal.SpanFactory;
import com.bugsnag.android.performance.internal.SpanImpl;
import com.bugsnag.android.performance.internal.processing.ImmutableConfig;

class NativeBugsnagPerformanceImpl {

static final String MODULE_NAME = "BugsnagReactNativePerformance";

private final ReactApplicationContext reactContext;

private final SecureRandom random = new SecureRandom();
Expand All @@ -29,15 +34,11 @@ public NativeBugsnagPerformanceImpl(ReactApplicationContext reactContext) {
this.reactContext = reactContext;

try {
Class.forName("com.bugsnag.android.performance.BugsnagPerformance");
Class.forName("com.bugsnag.android.performance.internal.InstrumentedAppState");
BugsnagPerformance.INSTANCE.getInstrumentedAppState$internal().getConfig$internal();
isNativePerformanceAvailable = true;
}
catch (LinkageError e) {
// do nothing, class found but is incompatible
}
catch (ClassNotFoundException e) {
// do nothing, Android Performance SDK is not installed
// do nothing, Android Performance SDK is not installed or is incompatible
}
}

Expand Down Expand Up @@ -107,11 +108,11 @@ public WritableMap getNativeConfiguration() {

WritableMap result = Arguments.createMap();
result.putString("apiKey", nativeConfig.getApiKey());
result.putString("endpoint", nativeConfig.getEndpoint());
result.putString("endpoint", nativeConfig.getEndpoint());
result.putString("releaseStage", nativeConfig.getReleaseStage());
result.putString("serviceName", nativeConfig.getServiceName());
result.putInt("attributeCountLimit", nativeConfig.getAttributeCountLimit());
result.putInt("attriubuteStringValueLimit", nativeConfig.getAttributeStringValueLimit());
result.putInt("attributeStringValueLimit", nativeConfig.getAttributeStringValueLimit());
result.putInt("attributeArrayLengthLimit", nativeConfig.getAttributeArrayLengthLimit());

var appVersion = nativeConfig.getAppVersion();
Expand All @@ -130,9 +131,67 @@ public WritableMap getNativeConfiguration() {
}

return result;
}
}

@Nullable
public WritableMap startNativeSpan(String name, ReadableMap options) {
if (!isNativePerformanceAvailable) {
return null;
}

SpanOptions spanOptions = readableMapToSpanOptions(options);
SpanFactory spanFactory = BugsnagPerformance.INSTANCE.getInstrumentedAppState$internal().getSpanFactory();
SpanImpl nativeSpan = spanFactory.createCustomSpan(name, spanOptions);

nativeSpan.getAttributes().getEntries$internal().clear();

WritableMap span = nativeSpanToJsSpan(nativeSpan);
return span;
}

private WritableMap nativeSpanToJsSpan(SpanImpl nativeSpan) {
WritableMap span = Arguments.createMap();
span.putString("name", nativeSpan.getName());
span.putString("id", EncodingUtils.toHexString(nativeSpan.getSpanId()));
span.putString("traceId", EncodingUtils.toHexString(nativeSpan.getTraceId()));

long unixNanoStartTime = BugsnagClock.INSTANCE.elapsedNanosToUnixTime(nativeSpan.getStartTime$internal());
span.putDouble("startTime", (double)unixNanoStartTime);

long parentSpanId = nativeSpan.getParentSpanId();
if (parentSpanId != 0L) {
span.putString("parentSpanId", EncodingUtils.toHexString(parentSpanId));
}

return span;
}

private SpanOptions readableMapToSpanOptions(ReadableMap jsOptions) {
SpanOptions spanOptions = SpanOptions.DEFAULTS
.setFirstClass(true)
.makeCurrentContext(false)
.within(null);
lemnik marked this conversation as resolved.
Show resolved Hide resolved

if (jsOptions.hasKey("startTime")) {
double startTime = jsOptions.getDouble("startTime");
long nativeStartTime = BugsnagClock.INSTANCE.unixNanoTimeToElapsedRealtime((long)startTime);
spanOptions = spanOptions.startTime(nativeStartTime);
}

ReadableMap parentContext = null;
if (jsOptions.hasKey("parentContext") && (parentContext = jsOptions.getMap("parentContext")) != null) {
ReactNativeSpanContext nativeParentContext = new ReactNativeSpanContext(
parentContext.getString("id"),
parentContext.getString("traceId")
);

spanOptions = spanOptions.within(nativeParentContext);
}

return spanOptions;
}

@Nullable
private String abiToArchitecture(@Nullable String abi) {
if (abi == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.bugsnag.reactnative.performance;

import androidx.annotation.NonNull;
import com.bugsnag.android.performance.SpanContext;
import java.util.UUID;
import java.util.concurrent.Callable;

class ReactNativeSpanContext implements SpanContext {
private static final int HEX_RADIX = 16;
private static final int TRACE_ID_MIDPOINT = 16;

private final long nativeSpanId;
private final UUID nativeTraceId;

ReactNativeSpanContext(String spanId, String traceId) {
nativeSpanId = Long.parseUnsignedLong(spanId, HEX_RADIX);
nativeTraceId = new UUID(
Long.parseUnsignedLong(traceId.substring(0, TRACE_ID_MIDPOINT), HEX_RADIX),
Long.parseUnsignedLong(traceId.substring(TRACE_ID_MIDPOINT), HEX_RADIX)
);
}

@Override
public long getSpanId() {
return nativeSpanId;
}

@NonNull
@Override
public UUID getTraceId() {
return nativeTraceId;
}

@NonNull
@Override
public Runnable wrap(@NonNull Runnable runnable) {
return SpanContext.DefaultImpls.wrap(this, runnable);
}

@NonNull
@Override
public <T> Callable<T> wrap(@NonNull Callable<T> callable) {
return SpanContext.DefaultImpls.wrap(this, callable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.bugsnag.reactnative.performance.NativeBugsnagPerformanceSpec;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

public class BugsnagReactNativePerformance extends NativeBugsnagPerformanceSpec {
Expand Down Expand Up @@ -44,5 +45,9 @@ public boolean isNativePerformanceAvailable() {
public WritableMap getNativeConfiguration() {
return impl.getNativeConfiguration();
}

@Override public WritableMap startNativeSpan(String name, ReadableMap options) {
return impl.startNativeSpan(name, options);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

import java.util.Map;
Expand Down Expand Up @@ -46,4 +47,9 @@ public boolean isNativePerformanceAvailable() {
public WritableMap getNativeConfiguration() {
return impl.getNativeConfiguration();
}

@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap startNativeSpan(String name, ReadableMap options) {
return impl.startNativeSpan(name, options);
}
}
28 changes: 23 additions & 5 deletions packages/platforms/react-native/lib/NativeBugsnagPerformance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'
import { TurboModuleRegistry } from 'react-native'

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type DeviceInfo = {
arch: string | undefined
model: string | undefined
Expand All @@ -10,26 +10,44 @@ export type DeviceInfo = {
bundleIdentifier: string | undefined
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type NativeConfiguration = {
apiKey: string
endpoint: string
samplingProbability?: number
appVersion?: string
samplingProbability: number | undefined
appVersion: string | undefined
releaseStage: string
enabledReleaseStages?: string[]
enabledReleaseStages: string[] | undefined
serviceName: string
attributeCountLimit: number
attributeStringValueLimit: number
attributeArrayLengthLimit: number
}

export type ParentContext = {
id: string
traceId: string
}

export type NativeSpanOptions = {
startTime: number | undefined
parentContext: ParentContext | null
}

export type NativeSpan = {
name: string
id: string
traceId: string
startTime: number
parentSpanId: string
}

export interface Spec extends TurboModule {
getDeviceInfo: () => DeviceInfo
requestEntropy: () => string
requestEntropyAsync: () => Promise<string>
isNativePerformanceAvailable: () => boolean
getNativeConfiguration: () => NativeConfiguration | null
startNativeSpan: (name: string, options: NativeSpanOptions) => NativeSpan
}

export default TurboModuleRegistry.get<Spec>(
Expand Down