Skip to content

Commit

Permalink
added profile_chunk envelope create
Browse files Browse the repository at this point in the history
added IHub.captureProfileChunk and ISentryClient.captureProfileChunk
added profilerId and chunkId reset logic to AndroidContinuousProfiler
added absolute timestamps to ProfileMeasurementValue
added ProfileContext to Contexts
  • Loading branch information
stefanosiano committed Sep 17, 2024
1 parent d7d846f commit a3d904d
Show file tree
Hide file tree
Showing 41 changed files with 1,454 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
import io.sentry.IHub;
import io.sentry.ILogger;
import io.sentry.ISentryExecutorService;
import io.sentry.ProfileChunk;
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 org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand All @@ -32,6 +37,9 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {
private boolean isRunning = false;
private @Nullable IHub hub;
private @Nullable Future<?> closeFuture;
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,
Expand Down Expand Up @@ -105,8 +113,17 @@ 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();
}

closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
}

Expand Down Expand Up @@ -138,14 +155,29 @@ private synchronized void stop(final boolean restartProfiler) {
return;
}

// The hub can be null if the profiler is started before the SDK is initialized (app start
// profiling), meaning there's no hub 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 (hub != null) {
sendChunks(hub, hub.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 @@ -157,6 +189,28 @@ public synchronized void close() {
stop();
}

private void sendChunks(final @NotNull IHub hub, 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) {
hub.captureProfileChunk(payload);
}
});
} catch (Throwable e) {
options.getLogger().log(SentryLevel.DEBUG, "Failed to send profile chunks.", e);
}
}

@Override
public boolean isRunning() {
return isRunning;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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 @@ -85,7 +86,9 @@ public void collect(final @NotNull PerformanceCollectionData performanceCollecti

CpuCollectionData cpuData =
new CpuCollectionData(
System.currentTimeMillis(), (cpuUsagePercentage / (double) numCores) * 100.0);
System.currentTimeMillis(),
(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 @@ -18,7 +19,8 @@ public void collect(final @NotNull PerformanceCollectionData performanceCollecti
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(now, usedMemory, usedNativeMemory, new SentryNanotimeDate());
performanceCollectionData.addMemoryData(memoryData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import io.sentry.ISentryExecutorService;
import io.sentry.MemoryCollectionData;
import io.sentry.PerformanceCollectionData;
import io.sentry.SentryDate;
import io.sentry.SentryLevel;
import io.sentry.SentryNanotimeDate;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.profilemeasurements.ProfileMeasurement;
import io.sentry.profilemeasurements.ProfileMeasurementValue;
Expand Down Expand Up @@ -158,6 +160,7 @@ public void onFrameMetricCollected(
// profileStartNanos is calculated through SystemClock.elapsedRealtimeNanos(),
// but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp
// relative to profileStartNanos
final SentryDate timestamp = new SentryNanotimeDate();
final long frameTimestampRelativeNanos =
frameEndNanos
- System.nanoTime()
Expand All @@ -171,15 +174,18 @@ public void onFrameMetricCollected(
}
if (isFrozen) {
frozenFrameRenderMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos));
new ProfileMeasurementValue(
frameTimestampRelativeNanos, durationNanos, timestamp));
} else if (isSlow) {
slowFrameRenderMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos));
new ProfileMeasurementValue(
frameTimestampRelativeNanos, durationNanos, timestamp));
}
if (refreshRate != lastRefreshRate) {
lastRefreshRate = refreshRate;
screenFrameRateMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, refreshRate));
new ProfileMeasurementValue(
frameTimestampRelativeNanos, refreshRate, timestamp));
}
}
});
Expand Down Expand Up @@ -326,19 +332,22 @@ private void putPerformanceCollectionDataInMeasurements(
cpuUsageMeasurements.add(
new ProfileMeasurementValue(
TimeUnit.MILLISECONDS.toNanos(cpuData.getTimestampMillis()) + timestampDiff,
cpuData.getCpuUsagePercentage()));
cpuData.getCpuUsagePercentage(),
cpuData.getTimestamp()));
}
if (memoryData != null && memoryData.getUsedHeapMemory() > -1) {
memoryUsageMeasurements.add(
new ProfileMeasurementValue(
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis()) + timestampDiff,
memoryData.getUsedHeapMemory()));
memoryData.getUsedHeapMemory(),
memoryData.getTimestamp()));
}
if (memoryData != null && memoryData.getUsedNativeMemory() > -1) {
nativeMemoryUsageMeasurements.add(
new ProfileMeasurementValue(
TimeUnit.MILLISECONDS.toNanos(memoryData.getTimestampMillis()) + timestampDiff,
memoryData.getUsedNativeMemory()));
memoryData.getUsedNativeMemory(),
memoryData.getTimestamp()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class AndroidCpuCollectorTest {
assertNotNull(cpuData)
assertNotEquals(0.0, cpuData.cpuUsagePercentage)
assertNotEquals(0, cpuData.timestampMillis)
assertNotEquals(0, cpuData.timestamp.nanoTimestamp())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ class AndroidMemoryCollectorTest {
assertEquals(usedNativeMemory, memoryData.usedNativeMemory)
assertEquals(usedMemory, memoryData.usedHeapMemory)
assertNotEquals(0, memoryData.timestampMillis)
assertNotEquals(0, memoryData.timestamp.nanoTimestamp())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.sentry.ILogger
import io.sentry.ISentryExecutorService
import io.sentry.MemoryCollectionData
import io.sentry.PerformanceCollectionData
import io.sentry.SentryDate
import io.sentry.SentryExecutorService
import io.sentry.SentryLevel
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector
Expand Down Expand Up @@ -278,12 +279,14 @@ class AndroidProfilerTest {
val profiler = fixture.getSut()
val performanceCollectionData = ArrayList<PerformanceCollectionData>()
var singleData = PerformanceCollectionData()
singleData.addMemoryData(MemoryCollectionData(1, 2, 3))
singleData.addCpuData(CpuCollectionData(1, 1.4))
val t1 = mock<SentryDate>()
val t2 = mock<SentryDate>()
singleData.addMemoryData(MemoryCollectionData(1, 2, 3, t1))
singleData.addCpuData(CpuCollectionData(1, 1.4, t1))
performanceCollectionData.add(singleData)

singleData = PerformanceCollectionData()
singleData.addMemoryData(MemoryCollectionData(2, 3, 4))
singleData.addMemoryData(MemoryCollectionData(2, 3, 4, t2))
performanceCollectionData.add(singleData)

profiler.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,12 @@ class AndroidTransactionProfilerTest {
val profiler = fixture.getSut(context)
val performanceCollectionData = ArrayList<PerformanceCollectionData>()
var singleData = PerformanceCollectionData()
singleData.addMemoryData(MemoryCollectionData(1, 2, 3))
singleData.addCpuData(CpuCollectionData(1, 1.4))
singleData.addMemoryData(MemoryCollectionData(1, 2, 3, mock()))
singleData.addCpuData(CpuCollectionData(1, 1.4, mock()))
performanceCollectionData.add(singleData)

singleData = PerformanceCollectionData()
singleData.addMemoryData(MemoryCollectionData(2, 3, 4))
singleData.addMemoryData(MemoryCollectionData(2, 3, 4, mock()))
performanceCollectionData.add(singleData)

profiler.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.sentry.Hint
import io.sentry.IMetricsAggregator
import io.sentry.IScope
import io.sentry.ISentryClient
import io.sentry.ProfileChunk
import io.sentry.ProfilingTraceData
import io.sentry.Sentry
import io.sentry.SentryEnvelope
Expand Down Expand Up @@ -177,6 +178,10 @@ class SessionTrackingIntegrationTest {
TODO("Not yet implemented")
}

override fun captureProfileChunk(profileChunk: ProfileChunk, scope: IScope?): SentryId {
TODO("Not yet implemented")
}

override fun captureCheckIn(checkIn: CheckIn, scope: IScope?, hint: Hint?): SentryId {
TODO("Not yet implemented")
}
Expand Down
Loading

0 comments on commit a3d904d

Please sign in to comment.