Skip to content

Commit a9235ae

Browse files
authored
Change payload for Continuous Profiling v8 (p2) (#3711)
* 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
1 parent da77271 commit a9235ae

File tree

100 files changed

+2722
-357
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+2722
-357
lines changed

sentry-android-core/api/sentry-android-core.api

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
3737
}
3838

3939
public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler {
40-
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
40+
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
4141
public fun close ()V
42+
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
4243
public fun isRunning ()Z
43-
public fun setScopes (Lio/sentry/IScopes;)V
4444
public fun start ()V
4545
public fun stop ()V
4646
}
@@ -447,6 +447,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
447447
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
448448
public fun clear ()V
449449
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
450+
public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler;
450451
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
451452
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
452453
public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
@@ -465,6 +466,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
465466
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
466467
public fun registerApplicationForegroundCheck (Landroid/app/Application;)V
467468
public fun setAppLaunchedInForeground (Z)V
469+
public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
468470
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
469471
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
470472
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V

sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java

Lines changed: 93 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@
44

55
import android.annotation.SuppressLint;
66
import android.os.Build;
7+
import io.sentry.CompositePerformanceCollector;
78
import io.sentry.IContinuousProfiler;
89
import io.sentry.ILogger;
910
import io.sentry.IScopes;
1011
import io.sentry.ISentryExecutorService;
12+
import io.sentry.NoOpScopes;
13+
import io.sentry.PerformanceCollectionData;
14+
import io.sentry.ProfileChunk;
15+
import io.sentry.Sentry;
1116
import io.sentry.SentryLevel;
17+
import io.sentry.SentryOptions;
1218
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
19+
import io.sentry.protocol.SentryId;
20+
import java.util.ArrayList;
21+
import java.util.List;
1322
import java.util.concurrent.Future;
1423
import java.util.concurrent.RejectedExecutionException;
1524
import org.jetbrains.annotations.ApiStatus;
@@ -23,7 +32,6 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {
2332

2433
private final @NotNull ILogger logger;
2534
private final @Nullable String profilingTracesDirPath;
26-
private final boolean isProfilingEnabled;
2735
private final int profilingTracesHz;
2836
private final @NotNull ISentryExecutorService executorService;
2937
private final @NotNull BuildInfoProvider buildInfoProvider;
@@ -32,21 +40,23 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {
3240
private @Nullable AndroidProfiler profiler = null;
3341
private boolean isRunning = false;
3442
private @Nullable IScopes scopes;
35-
private @Nullable Future<?> closeFuture;
43+
private @Nullable Future<?> stopFuture;
44+
private @Nullable CompositePerformanceCollector performanceCollector;
45+
private final @NotNull List<ProfileChunk.Builder> payloadBuilders = new ArrayList<>();
46+
private @NotNull SentryId profilerId = SentryId.EMPTY_ID;
47+
private @NotNull SentryId chunkId = SentryId.EMPTY_ID;
3648

3749
public AndroidContinuousProfiler(
3850
final @NotNull BuildInfoProvider buildInfoProvider,
3951
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
4052
final @NotNull ILogger logger,
4153
final @Nullable String profilingTracesDirPath,
42-
final boolean isProfilingEnabled,
4354
final int profilingTracesHz,
4455
final @NotNull ISentryExecutorService executorService) {
4556
this.logger = logger;
4657
this.frameMetricsCollector = frameMetricsCollector;
4758
this.buildInfoProvider = buildInfoProvider;
4859
this.profilingTracesDirPath = profilingTracesDirPath;
49-
this.isProfilingEnabled = isProfilingEnabled;
5060
this.profilingTracesHz = profilingTracesHz;
5161
this.executorService = executorService;
5262
}
@@ -57,10 +67,6 @@ private void init() {
5767
return;
5868
}
5969
isInitialized = true;
60-
if (!isProfilingEnabled) {
61-
logger.log(SentryLevel.INFO, "Profiling is disabled in options.");
62-
return;
63-
}
6470
if (profilingTracesDirPath == null) {
6571
logger.log(
6672
SentryLevel.WARNING,
@@ -81,15 +87,17 @@ private void init() {
8187
(int) SECONDS.toMicros(1) / profilingTracesHz,
8288
frameMetricsCollector,
8389
null,
84-
logger,
85-
buildInfoProvider);
86-
}
87-
88-
public synchronized void setScopes(final @NotNull IScopes scopes) {
89-
this.scopes = scopes;
90+
logger);
9091
}
9192

9293
public synchronized void start() {
94+
if ((scopes == null || scopes != NoOpScopes.getInstance())
95+
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
96+
this.scopes = Sentry.getCurrentScopes();
97+
this.performanceCollector =
98+
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
99+
}
100+
93101
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
94102
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
95103
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
@@ -106,10 +114,23 @@ public synchronized void start() {
106114
if (startData == null) {
107115
return;
108116
}
117+
109118
isRunning = true;
110119

120+
if (profilerId == SentryId.EMPTY_ID) {
121+
profilerId = new SentryId();
122+
}
123+
124+
if (chunkId == SentryId.EMPTY_ID) {
125+
chunkId = new SentryId();
126+
}
127+
128+
if (performanceCollector != null) {
129+
performanceCollector.start(chunkId.toString());
130+
}
131+
111132
try {
112-
closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
133+
stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
113134
} catch (RejectedExecutionException e) {
114135
logger.log(
115136
SentryLevel.ERROR,
@@ -124,8 +145,8 @@ public synchronized void stop() {
124145

125146
@SuppressLint("NewApi")
126147
private synchronized void stop(final boolean restartProfiler) {
127-
if (closeFuture != null) {
128-
closeFuture.cancel(true);
148+
if (stopFuture != null) {
149+
stopFuture.cancel(true);
129150
}
130151
// check if profiler was created and it's running
131152
if (profiler == null || !isRunning) {
@@ -138,22 +159,44 @@ private synchronized void stop(final boolean restartProfiler) {
138159
return;
139160
}
140161

141-
// todo add PerformanceCollectionData
142-
final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(false, null);
162+
List<PerformanceCollectionData> performanceCollectionData = null;
163+
if (performanceCollector != null) {
164+
performanceCollectionData = performanceCollector.stop(chunkId.toString());
165+
}
166+
167+
final AndroidProfiler.ProfileEndData endData =
168+
profiler.endAndCollect(false, performanceCollectionData);
143169

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

149186
isRunning = false;
187+
// A chunk is finished. Next chunk will have a different id.
188+
chunkId = SentryId.EMPTY_ID;
150189

151-
// todo schedule capture profile chunk envelope
190+
if (scopes != null) {
191+
sendChunks(scopes, scopes.getOptions());
192+
}
152193

153194
if (restartProfiler) {
154195
logger.log(SentryLevel.DEBUG, "Profile chunk finished. Starting a new one.");
155196
start();
156197
} else {
198+
// When the profiler is stopped manually, we have to reset its id
199+
profilerId = SentryId.EMPTY_ID;
157200
logger.log(SentryLevel.DEBUG, "Profile chunk finished.");
158201
}
159202
}
@@ -162,14 +205,41 @@ public synchronized void close() {
162205
stop();
163206
}
164207

208+
@Override
209+
public @NotNull SentryId getProfilerId() {
210+
return profilerId;
211+
}
212+
213+
private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
214+
try {
215+
options
216+
.getExecutorService()
217+
.submit(
218+
() -> {
219+
final ArrayList<ProfileChunk> payloads = new ArrayList<>(payloadBuilders.size());
220+
synchronized (payloadBuilders) {
221+
for (ProfileChunk.Builder builder : payloadBuilders) {
222+
payloads.add(builder.build(options));
223+
}
224+
payloadBuilders.clear();
225+
}
226+
for (ProfileChunk payload : payloads) {
227+
scopes.captureProfileChunk(payload);
228+
}
229+
});
230+
} catch (Throwable e) {
231+
options.getLogger().log(SentryLevel.DEBUG, "Failed to send profile chunks.", e);
232+
}
233+
}
234+
165235
@Override
166236
public boolean isRunning() {
167237
return isRunning;
168238
}
169239

170240
@VisibleForTesting
171241
@Nullable
172-
Future<?> getCloseFuture() {
173-
return closeFuture;
242+
Future<?> getStopFuture() {
243+
return stopFuture;
174244
}
175245
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidCpuCollector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.sentry.IPerformanceSnapshotCollector;
99
import io.sentry.PerformanceCollectionData;
1010
import io.sentry.SentryLevel;
11+
import io.sentry.SentryNanotimeDate;
1112
import io.sentry.util.FileUtils;
1213
import io.sentry.util.Objects;
1314
import java.io.File;
@@ -73,7 +74,7 @@ public void collect(final @NotNull PerformanceCollectionData performanceCollecti
7374

7475
CpuCollectionData cpuData =
7576
new CpuCollectionData(
76-
System.currentTimeMillis(), (cpuUsagePercentage / (double) numCores) * 100.0);
77+
(cpuUsagePercentage / (double) numCores) * 100.0, new SentryNanotimeDate());
7778

7879
performanceCollectionData.addCpuData(cpuData);
7980
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidMemoryCollector.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.sentry.IPerformanceSnapshotCollector;
55
import io.sentry.MemoryCollectionData;
66
import io.sentry.PerformanceCollectionData;
7+
import io.sentry.SentryNanotimeDate;
78
import org.jetbrains.annotations.ApiStatus;
89
import org.jetbrains.annotations.NotNull;
910

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

1617
@Override
1718
public void collect(final @NotNull PerformanceCollectionData performanceCollectionData) {
18-
long now = System.currentTimeMillis();
1919
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
2020
long usedNativeMemory = Debug.getNativeHeapSize() - Debug.getNativeHeapFreeSize();
21-
MemoryCollectionData memoryData = new MemoryCollectionData(now, usedMemory, usedNativeMemory);
21+
MemoryCollectionData memoryData =
22+
new MemoryCollectionData(usedMemory, usedNativeMemory, new SentryNanotimeDate());
2223
performanceCollectionData.addMemoryData(memoryData);
2324
}
2425
}

0 commit comments

Comments
 (0)