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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add runtime classes for FileIOStream instrumentation #1826

Merged
merged 32 commits into from Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f050409
Add runtime classes for FileIOStream instrumentation
romtsn Dec 3, 2021
6f326ba
Add NotNull annotation to StringUtils
romtsn Dec 3, 2021
70e7948
Add NotNull and Nullable annotations
romtsn Dec 3, 2021
301fb6a
Add final modifiers
romtsn Dec 3, 2021
09cfe00
Extract repeating logic
romtsn Dec 3, 2021
49c828c
Exception -> IOException
romtsn Dec 6, 2021
6819c36
Adjust span description and remove todos
romtsn Dec 6, 2021
96d005a
Refine finishSpan
romtsn Dec 6, 2021
36a8e3e
Set throwable for a span
romtsn Dec 6, 2021
067af96
Add test for SentryFIS and adjust logic
romtsn Dec 9, 2021
2270a43
Add test for FIS
romtsn Dec 9, 2021
916a814
Remove unused ctors
romtsn Dec 9, 2021
a617617
Fix SentryFIS counting
romtsn Dec 9, 2021
d3c2fc4
Fix SentryFIS read operation
romtsn Dec 9, 2021
be5aedd
Add Tests for SentryFOS
romtsn Dec 9, 2021
e9491d8
Formatting
romtsn Dec 9, 2021
19c524f
Dump API
romtsn Dec 9, 2021
ab90c1e
Changelog
romtsn Dec 9, 2021
d9e8a74
Set throwable in-place
romtsn Dec 13, 2021
3a4bee3
Pass only isSendDefaultPii instead of IHub
romtsn Dec 13, 2021
5ca25a0
V -> T generic
romtsn Dec 13, 2021
2297464
Change doc of performIO
romtsn Dec 13, 2021
aa8ad65
Make ctors package private
romtsn Dec 13, 2021
c6144c7
SentryFIOS @Open -> final
romtsn Dec 13, 2021
faf315f
Make IHub ctors package-private
romtsn Dec 13, 2021
4b1804f
Improve one-byte read method perf
romtsn Dec 13, 2021
67e4720
Locale.US -> Locale.ROOT
romtsn Dec 13, 2021
34c3c8d
Extract SentryOptions into a variable
romtsn Dec 13, 2021
4f8919e
Split getSut into 2 methods
romtsn Dec 13, 2021
4709983
Spotless
romtsn Dec 13, 2021
f15d3af
Dump api
romtsn Dec 13, 2021
a3b6103
Make SentryFIOS public
romtsn Dec 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,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)
* Feat: Add `SentryFileInputStream` and `SentryFileOutputStream` for File I/O performance instrumentation (#1826)

## 5.4.3

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

public class io/sentry/instrumentation/file/SentryFileInputStream : java/io/FileInputStream {
public fun <init> (Ljava/io/File;)V
public fun <init> (Ljava/io/File;Lio/sentry/IHub;)V
public fun <init> (Ljava/io/FileDescriptor;)V
public fun <init> (Ljava/io/FileDescriptor;Lio/sentry/IHub;)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/File;Lio/sentry/IHub;)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 class io/sentry/instrumentation/file/SentryFileOutputStream : java/io/FileOutputStream {
public fun <init> (Ljava/io/File;)V
public fun <init> (Ljava/io/File;Lio/sentry/IHub;)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 +2054,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,110 @@
package io.sentry.instrumentation.file;

import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.SpanStatus;
import io.sentry.util.Pair;
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 @NotNull IHub hub;

private @Nullable Throwable throwable = null;
romtsn marked this conversation as resolved.
Show resolved Hide resolved
private @NotNull SpanStatus spanStatus = SpanStatus.OK;
private long byteCount;

static @Nullable ISpan startSpan(final @NotNull IHub hub, final @NotNull String op) {
romtsn marked this conversation as resolved.
Show resolved Hide resolved
final ISpan parent = hub.getSpan();
return parent != null ? parent.startChild(op) : null;
}

FileIOSpanManager(
final @Nullable ISpan currentSpan, final @Nullable File file, final @NotNull IHub hub) {
this.currentSpan = currentSpan;
this.file = file;
this.hub = hub;
}
romtsn marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
romtsn marked this conversation as resolved.
Show resolved Hide resolved
* FileOutputStream#write(int)} The operation is of a type {@link Pair}, where the first
* element is the result of the IO operation, and the second element is the number of bytes
* read/written/skipped/etc.
*/
<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;
throwable = exception;
throw exception;
}
}

void finish(final @NotNull Closeable delegate) throws IOException {
try {
delegate.close();
} catch (IOException exception) {
spanStatus = SpanStatus.INTERNAL_ERROR;
throwable = 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() || hub.getOptions().isSendDefaultPii()) {
currentSpan.setData("file.path", file.getAbsolutePath());
}
} else {
currentSpan.setDescription(byteCountToString);
}
currentSpan.setData("file.size", byteCount);
currentSpan.setThrowable(throwable);
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<V> {
romtsn marked this conversation as resolved.
Show resolved Hide resolved

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

import io.sentry.IHub;
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 @NotNull IHub hub;
marandaneto marked this conversation as resolved.
Show resolved Hide resolved

public FileInputStreamInitData(
romtsn marked this conversation as resolved.
Show resolved Hide resolved
final @Nullable File file,
final @Nullable ISpan span,
final @NotNull FileInputStream delegate,
final @NotNull IHub hub) {
romtsn marked this conversation as resolved.
Show resolved Hide resolved
this.file = file;
this.span = span;
this.delegate = delegate;
this.hub = hub;
}
}
@@ -0,0 +1,30 @@
package io.sentry.instrumentation.file;

import io.sentry.IHub;
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 {
romtsn marked this conversation as resolved.
Show resolved Hide resolved

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

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

import com.jakewharton.nopen.annotation.Open;
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.
*/
@Open
romtsn marked this conversation as resolved.
Show resolved Hide resolved
public 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());
}

public SentryFileInputStream(final @Nullable File file, final @NotNull IHub hub)
romtsn marked this conversation as resolved.
Show resolved Hide resolved
throws FileNotFoundException {
this(init(file, null, hub));
}

public 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.hub);
delegate = data.delegate;
}

private SentryFileInputStream(final @NotNull FileInputStreamInitData data)
throws FileNotFoundException {
super(data.file);
spanManager = new FileIOSpanManager(data.span, data.file, data.hub);
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);
}

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);
}

@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(
() -> {
result.set(delegate.read());
return result.get() != -1 ? 1 : 0;
romtsn marked this conversation as resolved.
Show resolved Hide resolved
});
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);
}

public static FileInputStream create(
final @NotNull FileInputStream delegate, final @Nullable File file, final @NotNull IHub hub)
throws FileNotFoundException {
return new SentryFileInputStream(init(file, delegate, hub));
}
romtsn marked this conversation as resolved.
Show resolved Hide resolved
}
}