Skip to content

Commit 3cfbc50

Browse files
authored
Disk caching for java (signalfx#59)
1 parent ed53a9c commit 3cfbc50

31 files changed

Lines changed: 1766 additions & 46 deletions

android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
SplunkOtelReactNative_kotlinVersion=1.7.0
2-
SplunkOtelReactNative_minSdkVersion=21
2+
SplunkOtelReactNative_minSdkVersion=26
33
SplunkOtelReactNative_targetSdkVersion=31
44
SplunkOtelReactNative_compileSdkVersion=31
55
SplunkOtelReactNative_ndkversion=21.4.7075529

android/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="com.splunkotelreactnative">
3+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4+
35
<application>
46
<provider
57
android:name=".SplunkPerfProvider"

android/src/main/java/com/splunkotelreactnative/ConfigMapReader.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.facebook.react.bridge.ReadableMap;
2121

2222
public class ConfigMapReader extends MapReader {
23+
private static final boolean DEFAULT_ENABLE_DISK_BUFFERING = false;
24+
private static final int DEFAULT_MAX_STORAGE_USE_MB = 25;
2325
private final ReadableMap map;
2426

2527
public ConfigMapReader(ReadableMap map) {
@@ -34,13 +36,26 @@ public String getRumAccessToken() {
3436
return Keys.RUM_ACCESS_TOKEN.get(map);
3537
}
3638

39+
public boolean getEnableDiskBuffering() {
40+
Boolean value = Keys.ENABLE_DISK_BUFFERING.get(map);
41+
return value != null ? value : DEFAULT_ENABLE_DISK_BUFFERING;
42+
}
43+
44+
public int getMaxStorageUseMb() {
45+
Long value = Keys.MAX_STORAGE_USE_MB.getLong(map);
46+
return value != null ? value.intValue() : DEFAULT_MAX_STORAGE_USE_MB;
47+
}
48+
3749
public ReadableMap getGlobalAttributes() {
3850
return ConfigMapReader.Keys.GLOBAL_ATTRIBUTES.getMap(map);
3951
}
4052

4153
private interface Keys {
4254
StringKey BEACON_ENDPOINT = new StringKey("beaconEndpoint");
4355
StringKey RUM_ACCESS_TOKEN = new StringKey("rumAccessToken");
56+
BooleanKey ENABLE_DISK_BUFFERING = new BooleanKey("enableDiskBuffering");
57+
58+
NumberKey MAX_STORAGE_USE_MB = new NumberKey("limitDiskUsageMegabytes");
4459
MapKey GLOBAL_ATTRIBUTES = new MapKey("globalAttributes");
4560
}
4661
}

android/src/main/java/com/splunkotelreactnative/CustomZipkinEncoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
* <p>SplunkSpanDataModifier#SPLUNK_OPERATION_KEY}) with the span name properly cased, then
3737
* correcting the span name here at encoding time.
3838
*/
39-
class CustomZipkinEncoder implements BytesEncoder<Span> {
39+
public class CustomZipkinEncoder implements BytesEncoder<Span> {
4040
static final AttributeKey<String> SPLUNK_OPERATION_KEY = stringKey("_splunk_operation");
4141
private final WriteBuffer.Writer<Span> writer = new V2SpanWriter();
4242

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.splunkotelreactnative;
2+
3+
public class LogConstants {
4+
public static final String LOG_TAG = "SplunkRNRum";
5+
}

android/src/main/java/com/splunkotelreactnative/MapReader.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@ public String get(ReadableMap map) {
3737
}
3838
}
3939

40+
protected static class BooleanKey {
41+
private final String key;
42+
43+
public BooleanKey(String key) {
44+
this.key = key;
45+
}
46+
47+
public Boolean get(ReadableMap map) {
48+
if (map.hasKey(key) && map.getType(key) == ReadableType.Boolean) {
49+
return map.getBoolean(key);
50+
} else {
51+
return null;
52+
}
53+
}
54+
}
55+
4056
protected static class NumberKey {
4157
private final String key;
4258

android/src/main/java/com/splunkotelreactnative/SplunkOtelReactNativeModule.java

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.splunkotelreactnative;
1919

20+
import android.content.ContextWrapper;
2021
import android.util.Log;
2122

2223
import androidx.annotation.NonNull;
@@ -27,14 +28,18 @@
2728
import com.facebook.react.bridge.ReactApplicationContext;
2829
import com.facebook.react.bridge.ReactContextBaseJavaModule;
2930
import com.facebook.react.bridge.ReactMethod;
31+
import com.facebook.react.bridge.ReadableArray;
3032
import com.facebook.react.bridge.ReadableMap;
3133
import com.facebook.react.bridge.WritableMap;
3234
import com.facebook.react.module.annotations.ReactModule;
3335
import com.splunkotelreactnative.crash.CrashEventAttributeExtractor;
3436
import com.splunkotelreactnative.crash.CrashReporter;
37+
import com.splunkotelreactnative.exporter.disk.DiskBufferingExporterFactory;
3538

39+
import java.util.ArrayList;
3640
import java.util.Collections;
3741
import java.util.Iterator;
42+
import java.util.List;
3843
import java.util.Map;
3944

4045
import io.opentelemetry.api.common.Attributes;
@@ -44,6 +49,7 @@
4449
import io.opentelemetry.api.trace.TraceFlags;
4550
import io.opentelemetry.api.trace.TraceState;
4651
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder;
52+
import io.opentelemetry.sdk.trace.data.SpanData;
4753
import io.opentelemetry.sdk.trace.data.StatusData;
4854
import io.opentelemetry.sdk.trace.export.SpanExporter;
4955

@@ -79,10 +85,8 @@ public void initialize(ReadableMap configMap, Promise promise) {
7985

8086
String endpointWithAuthentication = beaconEndpoint + "?auth=" + accessToken;
8187

82-
exporter = new CrashEventAttributeExtractor(new ZipkinSpanExporterBuilder()
83-
.setEndpoint(endpointWithAuthentication)
84-
.setEncoder(new CustomZipkinEncoder())
85-
.build());
88+
exporter = createExporter(endpointWithAuthentication, getReactApplicationContext(),
89+
mapReader.getEnableDiskBuffering(), mapReader.getMaxStorageUseMb());
8690

8791
crashReporter = new CrashReporter(exporter,
8892
attributesFromMap(mapReader.getGlobalAttributes()), getReactApplicationContext());
@@ -110,39 +114,47 @@ public void nativeCrash() {
110114
}
111115

112116
@ReactMethod
113-
public void export(ReadableMap spanMap, Promise promise) {
117+
public void export(ReadableArray spanMaps, Promise promise) {
114118
SpanExporter currentExporter = exporter;
115-
SpanMapReader mapReader = new SpanMapReader(spanMap);
116119

117120
if (currentExporter == null) {
118121
reportFailure(promise, "Export: exporter not initialized");
119122
return;
120123
}
121124

122-
SpanContext context = contextFromMap(mapReader);
125+
List<SpanData> spanDataList = new ArrayList<>();
123126

124-
if (!context.isValid()) {
125-
reportFailure(promise, "Export: trace or span ID not provided");
126-
return;
127-
}
127+
for (int i = 0; i < spanMaps.size(); i++) {
128+
ReadableMap spanMap = spanMaps.getMap(i);
129+
SpanMapReader mapReader = new SpanMapReader(spanMap);
128130

129-
SpanContext parentContext = parentContextFromMap(mapReader, context);
130-
ReactSpanProperties spanProperties = propertiesFromMap(mapReader);
131+
SpanContext context = contextFromMap(mapReader);
132+
if (!context.isValid()) {
133+
reportFailure(promise, "Export: trace or span ID not provided");
134+
return;
135+
}
131136

132-
if (spanProperties == null) {
133-
reportFailure(promise, "Export: missing name, start or end time");
134-
return;
135-
}
137+
SpanContext parentContext = parentContextFromMap(mapReader, context);
138+
ReactSpanProperties spanProperties = propertiesFromMap(mapReader);
139+
140+
if (spanProperties == null) {
141+
reportFailure(promise, "Export: missing name, start or end time");
142+
return;
143+
}
136144

137-
Attributes attributes = attributesFromMap(mapReader.getAttributes());
145+
Attributes attributes = attributesFromMap(mapReader.getAttributes());
146+
ReactSpanData spanData = new ReactSpanData(spanProperties, attributes, context, parentContext,
147+
Collections.emptyList());
138148

139-
ReactSpanData spanData = new ReactSpanData(spanProperties, attributes, context, parentContext,
140-
Collections.emptyList());
141-
currentExporter.export(Collections.singleton(spanData));
149+
spanDataList.add(spanData);
150+
}
151+
152+
currentExporter.export(spanDataList);
142153

143154
promise.resolve(null);
144155
}
145156

157+
146158
@ReactMethod
147159
public void setSessionId(String sessionId) {
148160
CrashReporter currentCrashReporter = crashReporter;
@@ -177,6 +189,18 @@ private SpanContext contextFromMap(SpanMapReader mapReader) {
177189
return SpanContext.create(traceId, spanId, traceFlags, TraceState.getDefault());
178190
}
179191

192+
@NonNull
193+
private SpanExporter createExporter(String endpoint, ContextWrapper application,
194+
boolean enableDiskBuffering, int maxStorageUseMb) {
195+
if (!enableDiskBuffering) {
196+
return new CrashEventAttributeExtractor(new ZipkinSpanExporterBuilder()
197+
.setEndpoint(endpoint)
198+
.setEncoder(new CustomZipkinEncoder())
199+
.build());
200+
}
201+
return DiskBufferingExporterFactory.setupDiskBuffering(endpoint, application, maxStorageUseMb);
202+
}
203+
180204
@NonNull
181205
private SpanContext parentContextFromMap(SpanMapReader mapReader, SpanContext childContext) {
182206
String parentSpanId = mapReader.getParentSpanId();
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunkotelreactnative.exporter.disk;
18+
19+
import java.time.Clock;
20+
import java.util.ArrayDeque;
21+
import java.util.List;
22+
23+
/**
24+
* Utility class to track how much bandwidth is being used by span data. It tracks raw, uncompressed
25+
* spans bytes being input to a sender and is not intended to represent actual
26+
* gzipped/compressed/tls bytes on the network.
27+
*/
28+
class BandwidthTracker {
29+
private static final int DATAPOINTS_TO_TRACK = 6;
30+
private final Clock clock;
31+
private final ArrayDeque<Long> times = new ArrayDeque<>();
32+
private final ArrayDeque<Long> sizes = new ArrayDeque<>();
33+
34+
BandwidthTracker() {
35+
this(Clock.systemUTC());
36+
}
37+
38+
// Exists for testing
39+
BandwidthTracker(Clock clock) {
40+
this.clock = clock;
41+
}
42+
43+
/** Call this method with encoded zipkin span data to have it tracked. */
44+
void tick(List<byte[]> zipkinSpanData) {
45+
if (times.size() > DATAPOINTS_TO_TRACK) {
46+
times.removeFirst();
47+
}
48+
times.add(clock.millis());
49+
50+
if (sizes.size() > DATAPOINTS_TO_TRACK) {
51+
sizes.removeFirst();
52+
}
53+
long currentSize =
54+
zipkinSpanData.stream()
55+
.map(bytes -> bytes.length)
56+
.reduce(0, Integer::sum, Integer::sum);
57+
sizes.add(currentSize);
58+
}
59+
60+
/**
61+
* Calculates the current average sustained throughput.
62+
*
63+
* @return - The currently tracked bandwidth, in bytes per second.
64+
*/
65+
double totalSustainedRate() {
66+
if (sizes.size() < 2) return 0;
67+
68+
// Don't count the first ingest payload
69+
double total = sizes.stream().skip(1).reduce(0L, Long::sum, Long::sum);
70+
71+
double timeDeltaInSeconds = (times.getLast() - times.getFirst()) / 1000.0;
72+
return total / timeDeltaInSeconds;
73+
}
74+
}

0 commit comments

Comments
 (0)