Skip to content

Commit

Permalink
Merge pull request #160 from LikeTheSalad/disk-buffering-part-2
Browse files Browse the repository at this point in the history
Adding disk buffering, part 2
  • Loading branch information
LikeTheSalad authored Dec 12, 2023
2 parents 5c5766a + db49011 commit cfe22e6
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static java.util.Objects.requireNonNull;

import android.app.Application;
import android.util.Log;
import io.opentelemetry.android.config.DiskBufferingConfiguration;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.instrumentation.InstrumentedApplication;
import io.opentelemetry.android.instrumentation.activity.VisibleScreenTracker;
Expand All @@ -16,10 +18,14 @@
import io.opentelemetry.android.instrumentation.network.NetworkChangeMonitor;
import io.opentelemetry.android.instrumentation.startup.InitializationEvents;
import io.opentelemetry.android.instrumentation.startup.SdkInitializationEvents;
import io.opentelemetry.android.internal.features.persistence.DiskManager;
import io.opentelemetry.android.internal.features.persistence.SimpleTemporaryFileProvider;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.contrib.disk.buffering.SpanDiskExporter;
import io.opentelemetry.contrib.disk.buffering.internal.StorageConfiguration;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
Expand All @@ -32,6 +38,7 @@
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -339,7 +346,32 @@ private SdkTracerProvider buildTracerProvider(SessionId sessionId, Application a
private SpanExporter buildSpanExporter() {
// TODO: Default to otlp...but how can we make endpoint and auth mandatory?
SpanExporter defaultExporter = LoggingSpanExporter.create();
return spanExporterCustomizer.apply(defaultExporter);
SpanExporter spanExporter = defaultExporter;
DiskBufferingConfiguration diskBufferingConfiguration =
config.getDiskBufferingConfiguration();
if (diskBufferingConfiguration.isEnabled()) {
try {
spanExporter = createDiskExporter(defaultExporter, diskBufferingConfiguration);
} catch (IOException e) {
Log.w(RumConstants.OTEL_RUM_LOG_TAG, "Could not create span disk exporter.", e);
}
}
return spanExporterCustomizer.apply(spanExporter);
}

private static SpanExporter createDiskExporter(
SpanExporter defaultExporter, DiskBufferingConfiguration diskBufferingConfiguration)
throws IOException {
DiskManager diskManager = DiskManager.create(diskBufferingConfiguration);
StorageConfiguration storageConfiguration =
StorageConfiguration.builder()
.setMaxFileSize(diskManager.getMaxCacheFileSize())
.setMaxFolderSize(diskManager.getMaxFolderSize())
.setTemporaryFileProvider(
new SimpleTemporaryFileProvider(diskManager.getTemporaryDir()))
.build();
return SpanDiskExporter.create(
defaultExporter, diskManager.getSignalsBufferDir(), storageConfiguration);
}

private SdkMeterProvider buildMeterProvider(Application application) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,30 @@

package io.opentelemetry.android.internal.features.persistence;

import android.util.Log;
import io.opentelemetry.android.RumConstants;
import io.opentelemetry.android.config.DiskBufferingConfiguration;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.internal.services.CacheStorageService;
import io.opentelemetry.android.internal.services.PreferencesService;
import io.opentelemetry.android.internal.services.ServiceManager;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* This class is internal and not for public use. Its APIs are unstable and can change at any time.
*/
public final class DiskManager {
private static final String MAX_FOLDER_SIZE_KEY = "max_signal_folder_size";
private static final Logger logger = Logger.getLogger("DiskManager");
private final CacheStorageService cacheStorageService;
private final PreferencesService preferencesService;
private final DiskBufferingConfiguration diskBufferingConfiguration;

public static DiskManager create(OtelRumConfig config) {
public static DiskManager create(DiskBufferingConfiguration config) {
ServiceManager serviceManager = ServiceManager.get();
return new DiskManager(
serviceManager.getService(CacheStorageService.class),
serviceManager.getService(PreferencesService.class),
config.getDiskBufferingConfiguration());
config);
}

private DiskManager(
Expand Down Expand Up @@ -76,8 +74,8 @@ public File getTemporaryDir() throws IOException {
public int getMaxFolderSize() {
int storedSize = preferencesService.retrieveInt(MAX_FOLDER_SIZE_KEY, -1);
if (storedSize > 0) {
logger.log(
Level.FINER,
Log.d(
RumConstants.OTEL_RUM_LOG_TAG,
String.format("Returning max folder size from preferences: %s", storedSize));
return storedSize;
}
Expand All @@ -89,17 +87,17 @@ public int getMaxFolderSize() {
int maxCacheFileSize = getMaxCacheFileSize();
int calculatedSize = (availableCacheSize / 3) - maxCacheFileSize;
if (calculatedSize < maxCacheFileSize) {
logger.log(
Level.WARNING,
Log.w(
RumConstants.OTEL_RUM_LOG_TAG,
String.format(
"Insufficient folder cache size: %s, it must be at least: %s",
calculatedSize, maxCacheFileSize));
return 0;
}
preferencesService.store(MAX_FOLDER_SIZE_KEY, calculatedSize);

logger.log(
Level.FINER,
Log.d(
RumConstants.OTEL_RUM_LOG_TAG,
String.format(
"Requested cache size: %s, available cache size: %s, folder size: %s",
requestedSize, availableCacheSize, calculatedSize));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.internal.features.persistence;

import io.opentelemetry.contrib.disk.buffering.internal.files.TemporaryFileProvider;
import java.io.File;
import java.util.UUID;

/**
* This class is internal and not for public use. Its APIs are unstable and can change at any time.
*/
public final class SimpleTemporaryFileProvider implements TemporaryFileProvider {
private final File tempDir;

public SimpleTemporaryFileProvider(File tempDir) {
this.tempDir = tempDir;
}

/** Creates a unique file instance using the provided prefix and the current time in millis. */
@Override
public File createTemporaryFile(String prefix) {
return new File(tempDir, prefix + "_" + UUID.randomUUID() + ".tmp");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import android.content.Context;
import android.os.Build;
import android.os.storage.StorageManager;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
import io.opentelemetry.android.RumConstants;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Utility to get information about the host app.
Expand All @@ -24,7 +24,6 @@
*/
public class CacheStorageService implements Service {
private final Context appContext;
private static final Logger logger = Logger.getLogger("CacheStorageService");

public CacheStorageService(Context appContext) {
this.appContext = appContext;
Expand Down Expand Up @@ -53,8 +52,8 @@ public long ensureCacheSpaceAvailable(long maxSpaceNeeded) {

@RequiresApi(api = Build.VERSION_CODES.O)
private long getAvailableSpace(File directory, long maxSpaceNeeded) {
logger.log(
Level.FINER,
Log.d(
RumConstants.OTEL_RUM_LOG_TAG,
String.format(
"Getting available space for %s, max needed is: %s",
directory, maxSpaceNeeded));
Expand All @@ -70,14 +69,14 @@ private long getAvailableSpace(File directory, long maxSpaceNeeded) {
storageManager.allocateBytes(appSpecificInternalDirUuid, spaceToAllocate);
return spaceToAllocate;
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to get available space", e);
Log.w(RumConstants.OTEL_RUM_LOG_TAG, "Failed to get available space", e);
return getLegacyAvailableSpace(directory, maxSpaceNeeded);
}
}

private long getLegacyAvailableSpace(File directory, long maxSpaceNeeded) {
logger.log(
Level.FINER,
Log.d(
RumConstants.OTEL_RUM_LOG_TAG,
String.format(
"Getting legacy available space for %s max needed is: %s",
directory, maxSpaceNeeded));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,39 @@
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.Application;
import androidx.annotation.NonNull;
import io.opentelemetry.android.config.DiskBufferingConfiguration;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.instrumentation.ApplicationStateListener;
import io.opentelemetry.android.internal.services.CacheStorageService;
import io.opentelemetry.android.internal.services.PreferencesService;
import io.opentelemetry.android.internal.services.Service;
import io.opentelemetry.android.internal.services.ServiceManager;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.contrib.disk.buffering.SpanDiskExporter;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -165,6 +176,79 @@ void setSpanExporterCustomizer() {
.untilAsserted(() -> verify(exporter).export(anyCollection()));
}

@Test
void diskBufferingEnabled() {
PreferencesService preferences = mock();
CacheStorageService cacheStorage = mock();
doReturn(60 * 1024 * 1024L).when(cacheStorage).ensureCacheSpaceAvailable(anyLong());
setUpServiceManager(preferences, cacheStorage);
OtelRumConfig config = buildConfig();
config.setDiskBufferingConfiguration(
DiskBufferingConfiguration.builder().setEnabled(true).build());
AtomicReference<SpanExporter> capturedExporter = new AtomicReference<>();

OpenTelemetryRum.builder(application, config)
.addSpanExporterCustomizer(
spanExporter -> {
capturedExporter.set(spanExporter);
return spanExporter;
})
.build();

assertThat(capturedExporter.get()).isInstanceOf(SpanDiskExporter.class);
}

@Test
void diskBufferingEnabled_when_exception_thrown() {
PreferencesService preferences = mock();
CacheStorageService cacheStorage = mock();
doReturn(60 * 1024 * 1024L).when(cacheStorage).ensureCacheSpaceAvailable(anyLong());
doAnswer(
invocation -> {
throw new IOException();
})
.when(cacheStorage)
.getCacheDir();
setUpServiceManager(preferences, cacheStorage);
OtelRumConfig config = buildConfig();
config.setDiskBufferingConfiguration(
DiskBufferingConfiguration.builder().setEnabled(true).build());
AtomicReference<SpanExporter> capturedExporter = new AtomicReference<>();

OpenTelemetryRum.builder(application, config)
.addSpanExporterCustomizer(
spanExporter -> {
capturedExporter.set(spanExporter);
return spanExporter;
})
.build();

assertThat(capturedExporter.get()).isNotInstanceOf(SpanDiskExporter.class);
}

@Test
void diskBufferingDisabled() {
AtomicReference<SpanExporter> capturedExporter = new AtomicReference<>();

makeBuilder()
.addSpanExporterCustomizer(
spanExporter -> {
capturedExporter.set(spanExporter);
return spanExporter;
})
.build();

assertThat(capturedExporter.get()).isNotInstanceOf(SpanDiskExporter.class);
}

private static void setUpServiceManager(Service... services) {
ServiceManager serviceManager = mock();
for (Service service : services) {
doReturn(service).when(serviceManager).getService(service.getClass());
}
ServiceManager.setForTest(serviceManager);
}

@NonNull
private OpenTelemetryRumBuilder makeBuilder() {
return OpenTelemetryRum.builder(application, buildConfig());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;

import io.opentelemetry.android.config.DiskBufferingConfiguration;
import io.opentelemetry.android.config.OtelRumConfig;
import io.opentelemetry.android.internal.services.CacheStorageService;
import io.opentelemetry.android.internal.services.PreferencesService;
import io.opentelemetry.android.internal.services.ServiceManager;
Expand All @@ -41,13 +40,11 @@ class DiskManagerTest {

@BeforeEach
void setUp() {
OtelRumConfig config = mock();
ServiceManager serviceManager = mock();
doReturn(diskBufferingConfiguration).when(config).getDiskBufferingConfiguration();
doReturn(cacheStorageService).when(serviceManager).getService(CacheStorageService.class);
doReturn(preferencesService).when(serviceManager).getService(PreferencesService.class);
ServiceManager.setForTest(serviceManager);
diskManager = DiskManager.create(config);
diskManager = DiskManager.create(diskBufferingConfiguration);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.internal.features.persistence;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.File;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

class SimpleTemporaryFileProviderTest {
@TempDir File tempDir;

@Test
void createUniqueFilesBasedOnCurrentTimeAndPrefix() throws InterruptedException {
SimpleTemporaryFileProvider provider = new SimpleTemporaryFileProvider(tempDir);
File first = provider.createTemporaryFile("a");
File second = provider.createTemporaryFile("b");
Thread.sleep(1);
File third = provider.createTemporaryFile("a");

assertThat(first.getName()).startsWith("a").endsWith(".tmp");
assertThat(second.getName()).startsWith("b").endsWith(".tmp");
assertThat(third.getName()).startsWith("a").endsWith(".tmp");
assertThat(first).isNotEqualTo(third);

assertThat(first.getParentFile()).isEqualTo(tempDir);
assertThat(second.getParentFile()).isEqualTo(tempDir);
assertThat(third.getParentFile()).isEqualTo(tempDir);
}
}

0 comments on commit cfe22e6

Please sign in to comment.