Skip to content

Commit

Permalink
feat: Add runtime classes for FileIOStream instrumentation (#1826)
Browse files Browse the repository at this point in the history
  • Loading branch information
romtsn authored and maciejwalkowiak committed Jan 5, 2022
1 parent dac1c38 commit 72c2195
Show file tree
Hide file tree
Showing 11 changed files with 859 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -6,7 +6,7 @@
* Ref: Rename Fragment span operation from `ui.fragment.load` to `ui.load` (#1824)
* Feat: Add locale to device context and deprecate language (#1832)
* Ref: change `java.util.Random` to `java.security.SecureRandom` for possible security reasons (#1831)
* Fix: Sending errors in Spring WebFlux integration (#1819)
* Feat: Add `SentryFileInputStream` and `SentryFileOutputStream` for File I/O performance instrumentation (#1826)

## 5.4.3

Expand Down
40 changes: 40 additions & 0 deletions sentry/api/sentry.api
Expand Up @@ -1432,6 +1432,45 @@ public abstract interface class io/sentry/hints/SubmissionResult {
public abstract fun setResult (Z)V
}

public final class io/sentry/instrumentation/file/SentryFileInputStream : java/io/FileInputStream {
public fun <init> (Ljava/io/File;)V
public fun <init> (Ljava/io/FileDescriptor;)V
public fun <init> (Ljava/lang/String;)V
public fun close ()V
public fun read ()I
public fun read ([B)I
public fun read ([BII)I
public fun skip (J)J
}

public final class io/sentry/instrumentation/file/SentryFileInputStream$Factory {
public fun <init> ()V
public static fun create (Ljava/io/FileInputStream;Ljava/io/File;)Ljava/io/FileInputStream;
public static fun create (Ljava/io/FileInputStream;Ljava/io/FileDescriptor;)Ljava/io/FileInputStream;
public static fun create (Ljava/io/FileInputStream;Ljava/lang/String;)Ljava/io/FileInputStream;
}

public final class io/sentry/instrumentation/file/SentryFileOutputStream : java/io/FileOutputStream {
public fun <init> (Ljava/io/File;)V
public fun <init> (Ljava/io/File;Z)V
public fun <init> (Ljava/io/FileDescriptor;)V
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Z)V
public fun close ()V
public fun write (I)V
public fun write ([B)V
public fun write ([BII)V
}

public final class io/sentry/instrumentation/file/SentryFileOutputStream$Factory {
public fun <init> ()V
public static fun create (Ljava/io/FileOutputStream;Ljava/io/File;)Ljava/io/FileOutputStream;
public static fun create (Ljava/io/FileOutputStream;Ljava/io/File;Z)Ljava/io/FileOutputStream;
public static fun create (Ljava/io/FileOutputStream;Ljava/io/FileDescriptor;)Ljava/io/FileOutputStream;
public static fun create (Ljava/io/FileOutputStream;Ljava/lang/String;)Ljava/io/FileOutputStream;
public static fun create (Ljava/io/FileOutputStream;Ljava/lang/String;Z)Ljava/io/FileOutputStream;
}

public final class io/sentry/protocol/App : io/sentry/IUnknownPropertiesConsumer {
public static final field TYPE Ljava/lang/String;
public fun <init> ()V
Expand Down Expand Up @@ -2011,6 +2050,7 @@ public final class io/sentry/util/Platform {
}

public final class io/sentry/util/StringUtils {
public static fun byteCountToString (J)Ljava/lang/String;
public static fun capitalize (Ljava/lang/String;)Ljava/lang/String;
public static fun getStringAfterDot (Ljava/lang/String;)Ljava/lang/String;
public static fun removeSurrounding (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
Expand Down
@@ -0,0 +1,112 @@
package io.sentry.instrumentation.file;

import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.SpanStatus;
import io.sentry.util.Platform;
import io.sentry.util.StringUtils;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class FileIOSpanManager {

private final @Nullable ISpan currentSpan;
private final @Nullable File file;
private final boolean isSendDefaultPii;

private @NotNull SpanStatus spanStatus = SpanStatus.OK;
private long byteCount;

static @Nullable ISpan startSpan(final @NotNull IHub hub, final @NotNull String op) {
final ISpan parent = hub.getSpan();
return parent != null ? parent.startChild(op) : null;
}

FileIOSpanManager(
final @Nullable ISpan currentSpan,
final @Nullable File file,
final boolean isSendDefaultPii) {
this.currentSpan = currentSpan;
this.file = file;
this.isSendDefaultPii = isSendDefaultPii;
}

/**
* Performs file IO, counts the read/written bytes and handles exceptions in case of occurence
*
* @param operation An IO operation to execute (e.g. {@link FileInputStream#read()} or {@link
* FileOutputStream#write(int)} The operation is of a type {@link Integer} or {@link Long},
* where the output is the result of the IO operation (byte count read/written)
*/
<T> T performIO(final @NotNull FileIOCallable<T> operation) throws IOException {
try {
final T result = operation.call();
if (result instanceof Integer) {
final int resUnboxed = (int) result;
if (resUnboxed != -1) {
byteCount += resUnboxed;
}
} else if (result instanceof Long) {
final long resUnboxed = (long) result;
if (resUnboxed != -1L) {
byteCount += resUnboxed;
}
}
return result;
} catch (IOException exception) {
spanStatus = SpanStatus.INTERNAL_ERROR;
if (currentSpan != null) {
currentSpan.setThrowable(exception);
}
throw exception;
}
}

void finish(final @NotNull Closeable delegate) throws IOException {
try {
delegate.close();
} catch (IOException exception) {
spanStatus = SpanStatus.INTERNAL_ERROR;
if (currentSpan != null) {
currentSpan.setThrowable(exception);
}
throw exception;
} finally {
finishSpan();
}
}

private void finishSpan() {
if (currentSpan != null) {
final String byteCountToString = StringUtils.byteCountToString(byteCount);
if (file != null) {
final String description = file.getName() + " " + "(" + byteCountToString + ")";
currentSpan.setDescription(description);
if (Platform.isAndroid() || isSendDefaultPii) {
currentSpan.setData("file.path", file.getAbsolutePath());
}
} else {
currentSpan.setDescription(byteCountToString);
}
currentSpan.setData("file.size", byteCount);
currentSpan.finish(spanStatus);
}
}

/**
* A task that returns a result and may throw an IOException. Implementors define a single method
* with no arguments called {@code call}.
*
* <p>Derived from {@link java.util.concurrent.Callable}
*/
@FunctionalInterface
interface FileIOCallable<T> {

T call() throws IOException;
}
}
@@ -0,0 +1,26 @@
package io.sentry.instrumentation.file;

import io.sentry.ISpan;
import java.io.File;
import java.io.FileInputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class FileInputStreamInitData {

final @Nullable File file;
final @Nullable ISpan span;
final @NotNull FileInputStream delegate;
final boolean isSendDefaultPii;

FileInputStreamInitData(
final @Nullable File file,
final @Nullable ISpan span,
final @NotNull FileInputStream delegate,
final boolean isSendDefaultPii) {
this.file = file;
this.span = span;
this.delegate = delegate;
this.isSendDefaultPii = isSendDefaultPii;
}
}
@@ -0,0 +1,29 @@
package io.sentry.instrumentation.file;

import io.sentry.ISpan;
import java.io.File;
import java.io.FileOutputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class FileOutputStreamInitData {

final @Nullable File file;
final @Nullable ISpan span;
final boolean append;
final @NotNull FileOutputStream delegate;
final boolean isSendDefaultPii;

FileOutputStreamInitData(
final @Nullable File file,
final boolean append,
final @Nullable ISpan span,
final @NotNull FileOutputStream delegate,
final boolean isSendDefaultPii) {
this.file = file;
this.append = append;
this.span = span;
this.delegate = delegate;
this.isSendDefaultPii = isSendDefaultPii;
}
}
@@ -0,0 +1,143 @@
package io.sentry.instrumentation.file;

import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ISpan;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* An implementation of {@link java.io.FileInputStream} that creates a {@link io.sentry.ISpan} for
* reading operation with filename and byte count set as description
*
* <p>Note, that span is started when this InputStream is instantiated via constructor and finishes
* when the {@link java.io.FileInputStream#close()} is called.
*/
public final class SentryFileInputStream extends FileInputStream {

private final @NotNull FileInputStream delegate;
private final @NotNull FileIOSpanManager spanManager;

public SentryFileInputStream(final @Nullable String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, HubAdapter.getInstance());
}

public SentryFileInputStream(final @Nullable File file) throws FileNotFoundException {
this(file, HubAdapter.getInstance());
}

public SentryFileInputStream(final @NotNull FileDescriptor fdObj) {
this(fdObj, HubAdapter.getInstance());
}

SentryFileInputStream(final @Nullable File file, final @NotNull IHub hub)
throws FileNotFoundException {
this(init(file, null, hub));
}

SentryFileInputStream(final @NotNull FileDescriptor fdObj, final @NotNull IHub hub) {
this(init(fdObj, null, hub), fdObj);
}

private SentryFileInputStream(
final @NotNull FileInputStreamInitData data, final @NotNull FileDescriptor fd) {
super(fd);
spanManager = new FileIOSpanManager(data.span, data.file, data.isSendDefaultPii);
delegate = data.delegate;
}

private SentryFileInputStream(final @NotNull FileInputStreamInitData data)
throws FileNotFoundException {
super(data.file);
spanManager = new FileIOSpanManager(data.span, data.file, data.isSendDefaultPii);
delegate = data.delegate;
}

private static FileInputStreamInitData init(
final @Nullable File file, @Nullable FileInputStream delegate, final @NotNull IHub hub)
throws FileNotFoundException {
final ISpan span = FileIOSpanManager.startSpan(hub, "file.read");
if (delegate == null) {
delegate = new FileInputStream(file);
}
return new FileInputStreamInitData(file, span, delegate, hub.getOptions().isSendDefaultPii());
}

private static FileInputStreamInitData init(
final @NotNull FileDescriptor fd,
@Nullable FileInputStream delegate,
final @NotNull IHub hub) {
final ISpan span = FileIOSpanManager.startSpan(hub, "file.read");
if (delegate == null) {
delegate = new FileInputStream(fd);
}
return new FileInputStreamInitData(null, span, delegate, hub.getOptions().isSendDefaultPii());
}

@Override
public int read() throws IOException {
// this is the only case, when the read() operation returns the byte value, and not the count
// hence we need this special handling
AtomicInteger result = new AtomicInteger(0);
spanManager.performIO(
() -> {
final int res = delegate.read();
result.set(res);
return res != -1 ? 1 : 0;
});
return result.get();
}

@Override
public int read(final byte @NotNull [] b) throws IOException {
return spanManager.performIO(() -> delegate.read(b));
}

@Override
public int read(final byte @NotNull [] b, final int off, final int len) throws IOException {
return spanManager.performIO(() -> delegate.read(b, off, len));
}

@Override
public long skip(final long n) throws IOException {
return spanManager.performIO(() -> delegate.skip(n));
}

@Override
public void close() throws IOException {
spanManager.finish(delegate);
}

public static final class Factory {
public static FileInputStream create(
final @NotNull FileInputStream delegate, final @Nullable String name)
throws FileNotFoundException {
return new SentryFileInputStream(
init(name != null ? new File(name) : null, delegate, HubAdapter.getInstance()));
}

public static FileInputStream create(
final @NotNull FileInputStream delegate, final @Nullable File file)
throws FileNotFoundException {
return new SentryFileInputStream(init(file, delegate, HubAdapter.getInstance()));
}

public static FileInputStream create(
final @NotNull FileInputStream delegate, final @NotNull FileDescriptor descriptor) {
return new SentryFileInputStream(
init(descriptor, delegate, HubAdapter.getInstance()), descriptor);
}

static FileInputStream create(
final @NotNull FileInputStream delegate, final @Nullable File file, final @NotNull IHub hub)
throws FileNotFoundException {
return new SentryFileInputStream(init(file, delegate, hub));
}
}
}

0 comments on commit 72c2195

Please sign in to comment.