Skip to content

Commit

Permalink
Change payload for Continuous Profiling v8 (p2) (#3711)
Browse files Browse the repository at this point in the history
* added profile_chunk envelope create
* added IHub.captureProfileChunk and ISentryClient.captureProfileChunk
* added profilerId and chunkId reset logic to AndroidContinuousProfiler
* added absolute timestamps to ProfileMeasurementValue
* added ProfileContext to Contexts
* removed timestampMillis from MemoryCollectionData and CpuCollectionData, now it uses timestamp.nanotime() to achieve same result
* continuous profiler doesn't stop anymore when an error occurs, but continue scheduling restart

Instantiate continuous profiling v8 (p3) (#3725)
* added profile context to SentryTracer
* removed isProfilingEnabled from AndroidContinuousProfiler, as it's useless
* added continuous profiler to SentryOptions
* added DefaultTransactionPerformanceCollector to AndroidContinuousProfiler
* updated DefaultTransactionPerformanceCollector to work with string ids other than transactions
* fixed ProfileChunk measurements being modifiable from other code
* added thread id and name to SpanContext.data
* added profiler_id to span data
* close continuous profiler on scopes close
* renamed TransactionPerformanceCollector to CompositePerformanceCollector
* added SpanContext.data ser/deser

Handle App Start Continuous Profiling v8 (p4) (#3730)
* create app start continuous profiler instead of transaction profiler, based on config
* updated SentryAppStartProfilingOptions with isContinuousProfilingEnabled flag
* updated SentryOptions with isContinuousProfilingEnabled() method
* cut profiler setup out in a specific function to improve readability of AndroidOptionsInitializer

Add new APIs for Continuous Profiling v8 (p5) (#3844)
* AndroidContinuousProfiler now retrieve the scopes on start()
* removed profilesSampleRate from sample app to enable continuous profiling
* added Sentry.startProfiler and Sentry.stopProfiler APIs
  • Loading branch information
stefanosiano authored Nov 14, 2024
1 parent da77271 commit a9235ae
Show file tree
Hide file tree
Showing 100 changed files with 2,722 additions and 357 deletions.
6 changes: 4 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
}

public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler {
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ZILio/sentry/ISentryExecutorService;)V
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/ISentryExecutorService;)V
public fun close ()V
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
public fun isRunning ()Z
public fun setScopes (Lio/sentry/IScopes;)V
public fun start ()V
public fun stop ()V
}
Expand Down Expand Up @@ -447,6 +447,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
public fun clear ()V
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler;
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
Expand All @@ -465,6 +466,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
public fun registerApplicationForegroundCheck (Landroid/app/Application;)V
public fun setAppLaunchedInForeground (Z)V
public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

import android.annotation.SuppressLint;
import android.os.Build;
import io.sentry.CompositePerformanceCollector;
import io.sentry.IContinuousProfiler;
import io.sentry.ILogger;
import io.sentry.IScopes;
import io.sentry.ISentryExecutorService;
import io.sentry.NoOpScopes;
import io.sentry.PerformanceCollectionData;
import io.sentry.ProfileChunk;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.protocol.SentryId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import org.jetbrains.annotations.ApiStatus;
Expand All @@ -23,7 +32,6 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {

private final @NotNull ILogger logger;
private final @Nullable String profilingTracesDirPath;
private final boolean isProfilingEnabled;
private final int profilingTracesHz;
private final @NotNull ISentryExecutorService executorService;
private final @NotNull BuildInfoProvider buildInfoProvider;
Expand All @@ -32,21 +40,23 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {
private @Nullable AndroidProfiler profiler = null;
private boolean isRunning = false;
private @Nullable IScopes scopes;
private @Nullable Future<?> closeFuture;
private @Nullable Future<?> stopFuture;
private @Nullable CompositePerformanceCollector performanceCollector;
private final @NotNull List<ProfileChunk.Builder> payloadBuilders = new ArrayList<>();
private @NotNull SentryId profilerId = SentryId.EMPTY_ID;
private @NotNull SentryId chunkId = SentryId.EMPTY_ID;

public AndroidContinuousProfiler(
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull ILogger logger,
final @Nullable String profilingTracesDirPath,
final boolean isProfilingEnabled,
final int profilingTracesHz,
final @NotNull ISentryExecutorService executorService) {
this.logger = logger;
this.frameMetricsCollector = frameMetricsCollector;
this.buildInfoProvider = buildInfoProvider;
this.profilingTracesDirPath = profilingTracesDirPath;
this.isProfilingEnabled = isProfilingEnabled;
this.profilingTracesHz = profilingTracesHz;
this.executorService = executorService;
}
Expand All @@ -57,10 +67,6 @@ private void init() {
return;
}
isInitialized = true;
if (!isProfilingEnabled) {
logger.log(SentryLevel.INFO, "Profiling is disabled in options.");
return;
}
if (profilingTracesDirPath == null) {
logger.log(
SentryLevel.WARNING,
Expand All @@ -81,15 +87,17 @@ private void init() {
(int) SECONDS.toMicros(1) / profilingTracesHz,
frameMetricsCollector,
null,
logger,
buildInfoProvider);
}

public synchronized void setScopes(final @NotNull IScopes scopes) {
this.scopes = scopes;
logger);
}

public synchronized void start() {
if ((scopes == null || scopes != NoOpScopes.getInstance())
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
this.scopes = Sentry.getCurrentScopes();
this.performanceCollector =
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
}

// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
Expand All @@ -106,10 +114,23 @@ public synchronized void start() {
if (startData == null) {
return;
}

isRunning = true;

if (profilerId == SentryId.EMPTY_ID) {
profilerId = new SentryId();
}

if (chunkId == SentryId.EMPTY_ID) {
chunkId = new SentryId();
}

if (performanceCollector != null) {
performanceCollector.start(chunkId.toString());
}

try {
closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
} catch (RejectedExecutionException e) {
logger.log(
SentryLevel.ERROR,
Expand All @@ -124,8 +145,8 @@ public synchronized void stop() {

@SuppressLint("NewApi")
private synchronized void stop(final boolean restartProfiler) {
if (closeFuture != null) {
closeFuture.cancel(true);
if (stopFuture != null) {
stopFuture.cancel(true);
}
// check if profiler was created and it's running
if (profiler == null || !isRunning) {
Expand All @@ -138,22 +159,44 @@ private synchronized void stop(final boolean restartProfiler) {
return;
}

// todo add PerformanceCollectionData
final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(false, null);
List<PerformanceCollectionData> performanceCollectionData = null;
if (performanceCollector != null) {
performanceCollectionData = performanceCollector.stop(chunkId.toString());
}

final AndroidProfiler.ProfileEndData endData =
profiler.endAndCollect(false, performanceCollectionData);

// check if profiler end successfully
if (endData == null) {
return;
logger.log(
SentryLevel.ERROR,
"An error occurred while collecting a profile chunk, and it won't be sent.");
} else {
// The scopes can be null if the profiler is started before the SDK is initialized (app start
// profiling), meaning there's no scopes to send the chunks. In that case, we store the data
// in a list and send it when the next chunk is finished.
synchronized (payloadBuilders) {
payloadBuilders.add(
new ProfileChunk.Builder(
profilerId, chunkId, endData.measurementsMap, endData.traceFile));
}
}

isRunning = false;
// A chunk is finished. Next chunk will have a different id.
chunkId = SentryId.EMPTY_ID;

// todo schedule capture profile chunk envelope
if (scopes != null) {
sendChunks(scopes, scopes.getOptions());
}

if (restartProfiler) {
logger.log(SentryLevel.DEBUG, "Profile chunk finished. Starting a new one.");
start();
} else {
// When the profiler is stopped manually, we have to reset its id
profilerId = SentryId.EMPTY_ID;
logger.log(SentryLevel.DEBUG, "Profile chunk finished.");
}
}
Expand All @@ -162,14 +205,41 @@ public synchronized void close() {
stop();
}

@Override
public @NotNull SentryId getProfilerId() {
return profilerId;
}

private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
try {
options
.getExecutorService()
.submit(
() -> {
final ArrayList<ProfileChunk> payloads = new ArrayList<>(payloadBuilders.size());
synchronized (payloadBuilders) {
for (ProfileChunk.Builder builder : payloadBuilders) {
payloads.add(builder.build(options));
}
payloadBuilders.clear();
}
for (ProfileChunk payload : payloads) {
scopes.captureProfileChunk(payload);
}
});
} catch (Throwable e) {
options.getLogger().log(SentryLevel.DEBUG, "Failed to send profile chunks.", e);
}
}

@Override
public boolean isRunning() {
return isRunning;
}

@VisibleForTesting
@Nullable
Future<?> getCloseFuture() {
return closeFuture;
Future<?> getStopFuture() {
return stopFuture;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.sentry.IPerformanceSnapshotCollector;
import io.sentry.PerformanceCollectionData;
import io.sentry.SentryLevel;
import io.sentry.SentryNanotimeDate;
import io.sentry.util.FileUtils;
import io.sentry.util.Objects;
import java.io.File;
Expand Down Expand Up @@ -73,7 +74,7 @@ public void collect(final @NotNull PerformanceCollectionData performanceCollecti

CpuCollectionData cpuData =
new CpuCollectionData(
System.currentTimeMillis(), (cpuUsagePercentage / (double) numCores) * 100.0);
(cpuUsagePercentage / (double) numCores) * 100.0, new SentryNanotimeDate());

performanceCollectionData.addCpuData(cpuData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.sentry.IPerformanceSnapshotCollector;
import io.sentry.MemoryCollectionData;
import io.sentry.PerformanceCollectionData;
import io.sentry.SentryNanotimeDate;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

Expand All @@ -15,10 +16,10 @@ public void setup() {}

@Override
public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) {
long now = System.currentTimeMillis();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize();
MemoryCollectionData memoryData = new MemoryCollectionData(now, usedMemory, usedNativeMemory);
MemoryCollectionData memoryData =
new MemoryCollectionData(usedMemory, usedNativeMemory, new SentryNanotimeDate());
performanceCollectionData.addMemoryData(memoryData);
}
}
Loading

0 comments on commit a9235ae

Please sign in to comment.