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

Add metrics API #3205

Merged
merged 32 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4f5b71f
Implement different metric types, add API and aggregator
markushi Feb 14, 2024
1d9aca3
Fix typos, make executorService volatile
markushi Feb 14, 2024
98b01f0
Move from Calendar to long timestamps, add hooks for testing
markushi Feb 14, 2024
1809236
Rename to metrics, have Sentry.metrics() as entry point
markushi Feb 15, 2024
72ceeee
Add more tests, use cr32 for hashing strings
markushi Feb 15, 2024
0f79074
Merge branch 'main' into feat/metrics
markushi Feb 15, 2024
64445a6
Enrich tags, add more tests
markushi Feb 15, 2024
1a21cf5
Cleanup tests and timing API, remove uneeded threadlocal
markushi Feb 15, 2024
2730784
Update changelog
markushi Feb 15, 2024
d24e95a
Merge branch 'main' into feat/metrics
markushi Feb 16, 2024
64b6efc
Move default tag generation to IMetricsHub, improve dx
markushi Feb 16, 2024
02abee8
Cleanup
markushi Feb 16, 2024
c371cea
Cleanup
markushi Feb 16, 2024
3af988e
Fix remove duplicate metricAggregator.close call
markushi Feb 19, 2024
3e88d48
Address PR feedback
markushi Feb 21, 2024
bbb4501
Merge branch 'main' into feat/metrics
markushi Feb 21, 2024
01412ab
Move test into metrics helper
markushi Feb 21, 2024
9c67a6c
Rename getValues to serialize to match API spec
markushi Feb 22, 2024
6a3be45
Add support for force-flushing metrics when weight is too high
markushi Feb 22, 2024
43d1319
Merge branch 'main' into feat/metrics
markushi Feb 22, 2024
cb0a002
Update Changelog
markushi Feb 22, 2024
07fc4e5
Revert "Add support for force-flushing metrics when weight is too high"
markushi Feb 22, 2024
3da6982
Fix .api
markushi Feb 22, 2024
576450e
Fix tests
markushi Feb 26, 2024
43305ed
Fix remove test code
markushi Feb 26, 2024
860dc55
Merge branch 'main' into feat/metrics
markushi Feb 26, 2024
84466fd
Merge branch 'main' into feat/metrics
markushi Feb 26, 2024
4fddc57
Replace tag values with empty string
markushi Feb 28, 2024
fcff268
Address PR feedback
markushi Feb 28, 2024
86b3ffa
Format & API
markushi Feb 28, 2024
1788aaf
Fix tests
markushi Feb 28, 2024
9b1f4aa
Force flush metrics when aggregator exceeds max weight (#3220)
markushi Feb 28, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Fix hub restore point in wrappers: SentryWrapper, SentryTaskDecorator and SentryScheduleHook ([#3225](https://github.com/getsentry/sentry-java/pull/3225))
- We now reset the hub to its previous value on the thread where the `Runnable`/`Callable`/`Supplier` is executed instead of setting it to the hub that was used on the thread where the `Runnable`/`Callable`/`Supplier` was created.
- Fix add missing thread name/id to app start spans ([#3226](https://github.com/getsentry/sentry-java/pull/3226))
- Experimental: Add Metrics API ([#3205](https://github.com/getsentry/sentry-java/pull/3205))

## 7.4.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ class ActivityLifecycleIntegrationTest {
// Assert the ttfd span is running and a timeout autoCancel task has been scheduled
assertNotNull(ttfdSpan)
assertFalse(ttfdSpan.isFinished)
assertTrue(deferredExecutorService.scheduledRunnables.isNotEmpty())
assertTrue(deferredExecutorService.hasScheduledRunnables())

// Run the autoClose task and assert the ttfd span is finished with deadlineExceeded
deferredExecutorService.runAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.CheckIn
import io.sentry.Hint
import io.sentry.IMetricsAggregator
import io.sentry.IScope
import io.sentry.ISentryClient
import io.sentry.ProfilingTraceData
Expand Down Expand Up @@ -174,5 +175,9 @@ class SessionTrackingIntegrationTest {
override fun getRateLimiter(): RateLimiter? {
TODO("Not yet implemented")
}

override fun getMetricsAggregator(): IMetricsAggregator {
TODO("Not yet implemented")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.app.Application;
import android.os.StrictMode;
import io.sentry.Sentry;

/** Apps. main Application. */
public class MyApplication extends Application {
Expand All @@ -24,6 +25,8 @@ public void onCreate() {
// });
// */
// });

Sentry.metrics().increment("app.start.cold");
}

private void strictMode() {
Expand Down
2 changes: 1 addition & 1 deletion sentry-test-support/api/sentry-test-support.api
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public final class io/sentry/SkipError : java/lang/Error {
public final class io/sentry/test/DeferredExecutorService : io/sentry/ISentryExecutorService {
public fun <init> ()V
public fun close (J)V
public final fun getScheduledRunnables ()Ljava/util/ArrayList;
public final fun hasScheduledRunnables ()Z
public fun isClosed ()Z
public final fun runAll ()V
public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
Expand Down
28 changes: 22 additions & 6 deletions sentry-test-support/src/main/kotlin/io/sentry/test/Mocks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,40 @@ class ImmediateExecutorService : ISentryExecutorService {

class DeferredExecutorService : ISentryExecutorService {

private val runnables = ArrayList<Runnable>()
val scheduledRunnables = ArrayList<Runnable>()
private var runnables = ArrayList<Runnable>()
private var scheduledRunnables = ArrayList<Runnable>()

fun runAll() {
runnables.forEach { it.run() }
scheduledRunnables.forEach { it.run() }
// take a snapshot of the runnable list in case
// executing the runnable itself schedules more runnables
val currentRunnableList = runnables
val currentScheduledRunnableList = scheduledRunnables

synchronized(this) {
runnables = ArrayList()
scheduledRunnables = ArrayList()
}

currentRunnableList.forEach { it.run() }
currentScheduledRunnableList.forEach { it.run() }
}

override fun submit(runnable: Runnable): Future<*> {
runnables.add(runnable)
synchronized(this) {
runnables.add(runnable)
}
return mock()
}

override fun <T> submit(callable: Callable<T>): Future<T> = mock()
override fun schedule(runnable: Runnable, delayMillis: Long): Future<*> {
scheduledRunnables.add(runnable)
synchronized(this) {
scheduledRunnables.add(runnable)
}
return mock()
}
override fun close(timeoutMillis: Long) {}
override fun isClosed(): Boolean = false

fun hasScheduledRunnables(): Boolean = scheduledRunnables.isNotEmpty()
}
204 changes: 202 additions & 2 deletions sentry/api/sentry.api

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.sentry.clientreport.DiscardReason;
import io.sentry.hints.SessionEndHint;
import io.sentry.hints.SessionStartHint;
import io.sentry.metrics.MetricsApi;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.User;
Expand All @@ -17,14 +18,16 @@
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class Hub implements IHub {
public final class Hub implements IHub, MetricsApi.IMetricsInterface {

private volatile @NotNull SentryId lastEventId;
private final @NotNull SentryOptions options;
private volatile boolean isEnabled;
Expand All @@ -33,10 +36,10 @@ public final class Hub implements IHub {
private final @NotNull Map<Throwable, Pair<WeakReference<ISpan>, String>> throwableToSpan =
Collections.synchronizedMap(new WeakHashMap<>());
private final @NotNull TransactionPerformanceCollector transactionPerformanceCollector;
private final @NotNull MetricsApi metricsApi;

public Hub(final @NotNull SentryOptions options) {
this(options, createRootStackItem(options));

// Integrations are no longer registered on Hub ctor, but on Sentry.init
}

Expand All @@ -52,6 +55,8 @@ private Hub(final @NotNull SentryOptions options, final @NotNull Stack stack) {
// Integrations will use this Hub instance once registered.
// Make sure Hub ready to be used then.
this.isEnabled = true;

this.metricsApi = new MetricsApi(this);
}

private Hub(final @NotNull SentryOptions options, final @NotNull StackItem rootStackItem) {
Expand Down Expand Up @@ -937,4 +942,22 @@ private IScope buildLocalScope(
final StackItem item = stack.peek();
return item.getClient().getRateLimiter();
}

@Override
public @NotNull MetricsApi metrics() {
return metricsApi;
}

@Override
public @NotNull IMetricsAggregator getMetricsAggregator() {
return stack.peek().getClient().getMetricsAggregator();
}

@Override
public @NotNull Map<String, String> getDefaultTagsForMetrics() {
final Map<String, String> tags = new HashMap<>(2);
tags.put("release", options.getRelease());
tags.put("environment", options.getEnvironment());
return tags;
}
}
6 changes: 6 additions & 0 deletions sentry/src/main/java/io/sentry/HubAdapter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry;

import io.sentry.metrics.MetricsApi;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.User;
Expand Down Expand Up @@ -272,4 +273,9 @@ public void reportFullyDisplayed() {
public @Nullable RateLimiter getRateLimiter() {
return Sentry.getCurrentHub().getRateLimiter();
}

@Override
public @NotNull MetricsApi metrics() {
return Sentry.getCurrentHub().metrics();
}
}
5 changes: 5 additions & 0 deletions sentry/src/main/java/io/sentry/IHub.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry;

import io.sentry.metrics.MetricsApi;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.User;
Expand Down Expand Up @@ -582,4 +583,8 @@ TransactionContext continueTrace(
@ApiStatus.Internal
@Nullable
RateLimiter getRateLimiter();

@ApiStatus.Experimental
@NotNull
MetricsApi metrics();
}
122 changes: 122 additions & 0 deletions sentry/src/main/java/io/sentry/IMetricsAggregator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.sentry;

import java.io.Closeable;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface IMetricsAggregator extends Closeable {

/**
* Emits a Counter metric
*
* @param key A unique key identifying the metric
* @param value The value to be added
* @param unit An optional unit, see {@link MeasurementUnit}
* @param tags Optional Tags to associate with the metric
* @param timestampMs The time when the metric was emitted. Defaults to the time at which the
* metric is emitted, if no value is provided.
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void increment(
final @NotNull String key,
final double value,
final @Nullable MeasurementUnit unit,
final @Nullable Map<String, String> tags,
final long timestampMs,
final int stackLevel);

/**
* Emits a Gauge metric
*
* @param key A unique key identifying the metric
* @param value The value to be added
* @param unit An optional unit, see {@link MeasurementUnit}
* @param tags Optional Tags to associate with the metric
* @param timestampMs The time when the metric was emitted. Defaults to the time at which the
* metric is emitted, if no value is provided.
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void gauge(
final @NotNull String key,
final double value,
final @Nullable MeasurementUnit unit,
final @Nullable Map<String, String> tags,
final long timestampMs,
final int stackLevel);

/**
* Emits a Distribution metric
*
* @param key A unique key identifying the metric
* @param value The value to be added
* @param unit An optional unit, see {@link MeasurementUnit}
* @param tags Optional Tags to associate with the metric
* @param timestampMs The time when the metric was emitted. Defaults to the time at which the
* metric is emitted, if no value is provided.
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void distribution(
final @NotNull String key,
final double value,
final @Nullable MeasurementUnit unit,
final @Nullable Map<String, String> tags,
final long timestampMs,
final int stackLevel);

/**
* Emits a Set metric
*
* @param key A unique key identifying the metric
* @param value The value to be added
* @param unit An optional unit, see {@link MeasurementUnit}
* @param tags Optional Tags to associate with the metric
* @param timestampMs The time when the metric was emitted. Defaults to the time at which the
* metric is emitted, if no value is provided.
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void set(
final @NotNull String key,
final int value,
final @Nullable MeasurementUnit unit,
final @Nullable Map<String, String> tags,
final long timestampMs,
final int stackLevel);

/**
* Emits a Set metric
*
* @param key A unique key identifying the metric
* @param value The value to be added
* @param unit An optional unit, see {@link MeasurementUnit}
* @param tags Optional Tags to associate with the metric
* @param timestampMs The time when the metric was emitted. Defaults to the time at which the
* metric is emitted, if no value is provided.
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void set(
final @NotNull String key,
final @NotNull String value,
final @Nullable MeasurementUnit unit,
final @Nullable Map<String, String> tags,
final long timestampMs,
final int stackLevel);

/**
* Emits a distribution with the time it takes to run a given code block.
*
* @param key A unique key identifying the metric
* @param callback The code block to measure
* @param unit An optional unit, see {@link MeasurementUnit.Duration}, defaults to seconds
* @param tags Optional Tags to associate with the metric
* @param stackLevel Optional number of stacks levels to ignore when determining the code location
*/
void timing(
final @NotNull String key,
final @NotNull Runnable callback,
final @NotNull MeasurementUnit.Duration unit,
final @Nullable Map<String, String> tags,
final int stackLevel);

void flush(boolean force);
}
4 changes: 4 additions & 0 deletions sentry/src/main/java/io/sentry/ISentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,8 @@ SentryId captureTransaction(
default boolean isHealthy() {
return true;
}

@ApiStatus.Internal
@NotNull
IMetricsAggregator getMetricsAggregator();
}
Loading
Loading