Skip to content

Commit

Permalink
Merge fed5b21 into 7597ded
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanosiano committed Nov 7, 2022
2 parents 7597ded + fed5b21 commit 9385630
Show file tree
Hide file tree
Showing 17 changed files with 1,156 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .sauce/sentry-uitest-android-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ suites:

- name: "Android 10 (api 29)"
devices:
- id: OnePlus_7_Pro_real # OnePlus 7 Pro - api 29 (10)
- id: OnePlus_7T_real_us # OnePlus 7T - api 29 (10)
- id: Nokia_7_1_real_us # Nokia 7.1 - api 29 (10)

# At the time of writing (July, 4, 2022), the market share per android version is:
Expand Down
2 changes: 1 addition & 1 deletion .sauce/sentry-uitest-android-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ suites:

- name: "Android 10 Ui test (api 29)"
devices:
- id: OnePlus_7_Pro_real # OnePlus 7 Pro - api 29 (10)
- id: OnePlus_7T_real_us # OnePlus 7 Pro - api 29 (10)

# Controls what artifacts to fetch when the suite on Sauce Cloud has finished.
artifacts:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Provide hook for Jetpack Compose navigation instrumentation ([#2320](https://github.com/getsentry/sentry-java/pull/2320))
- Populate `event.modules` with dependencies metadata ([#2324](https://github.com/getsentry/sentry-java/pull/2324))
- Support Spring 6 and Spring Boot 3 ([#2289](https://github.com/getsentry/sentry-java/pull/2289))
- added FrameMetrics to Android profiling data ([#2342](https://github.com/getsentry/sentry-java/pull/2342))

### Dependencies

Expand Down
23 changes: 23 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setProfilingTracesIntervalMillis (I)V
}

public final class io/sentry/android/core/SentryFrameMetricsCollector : android/app/Application$ActivityLifecycleCallbacks {
public fun <init> (Landroid/content/Context;Lio/sentry/SentryOptions;Lio/sentry/android/core/BuildInfoProvider;)V
public fun <init> (Landroid/content/Context;Lio/sentry/SentryOptions;Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/SentryFrameMetricsCollector$WindowFrameMetricsManager;)V
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityDestroyed (Landroid/app/Activity;)V
public fun onActivityPaused (Landroid/app/Activity;)V
public fun onActivityResumed (Landroid/app/Activity;)V
public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityStarted (Landroid/app/Activity;)V
public fun onActivityStopped (Landroid/app/Activity;)V
public fun startCollection (Lio/sentry/android/core/SentryFrameMetricsCollector$FrameMetricsCollectorListener;)Ljava/lang/String;
public fun stopCollection (Ljava/lang/String;)V
}

public abstract interface class io/sentry/android/core/SentryFrameMetricsCollector$FrameMetricsCollectorListener {
public abstract fun onFrameMetricCollected (Landroid/view/FrameMetrics;F)V
}

public abstract interface class io/sentry/android/core/SentryFrameMetricsCollector$WindowFrameMetricsManager {
public fun addOnFrameMetricsAvailableListener (Landroid/view/Window;Landroid/view/Window$OnFrameMetricsAvailableListener;Landroid/os/Handler;)V
public fun removeOnFrameMetricsAvailableListener (Landroid/view/Window;Landroid/view/Window$OnFrameMetricsAvailableListener;)V
}

public final class io/sentry/android/core/SentryInitProvider : android/content/ContentProvider {
public fun <init> ()V
public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ static void init(
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));

options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider));
new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.os.Debug;
import android.os.Process;
import android.os.SystemClock;
import android.view.FrameMetrics;
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ITransaction;
Expand All @@ -21,14 +22,18 @@
import io.sentry.SentryLevel;
import io.sentry.android.core.internal.util.CpuInfoUtils;
import io.sentry.exception.SentryEnvelopeException;
import io.sentry.profilemeasurements.ProfileMeasurement;
import io.sentry.profilemeasurements.ProfileMeasurementValue;
import io.sentry.util.Objects;
import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -60,23 +65,41 @@ final class AndroidTransactionProfiler implements ITransactionProfiler {
private long profileStartCpuMillis = 0;
private boolean isInitialized = false;
private int transactionsCounter = 0;
private @Nullable String frameMetricsCollectorId;
private final @NotNull SentryFrameMetricsCollector frameMetricsCollector;
private final @NotNull Map<String, ProfilingTransactionData> transactionMap = new HashMap<>();
private final @NotNull ArrayDeque<ProfileMeasurementValue> screenFrameRateMeasurements =
new ArrayDeque<>();
private final @NotNull ArrayDeque<ProfileMeasurementValue>
adverseFrameRenderTimestampMeasurements = new ArrayDeque<>();
private final @NotNull ArrayDeque<ProfileMeasurementValue>
adverseFrozenFrameTimestampMeasurements = new ArrayDeque<>();
private final @NotNull Map<String, ProfileMeasurement> measurementsMap = new HashMap<>();

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider) {
this(context, sentryAndroidOptions, buildInfoProvider, HubAdapter.getInstance());
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector) {
this(
context,
sentryAndroidOptions,
buildInfoProvider,
frameMetricsCollector,
HubAdapter.getInstance());
}

public AndroidTransactionProfiler(
final @NotNull Context context,
final @NotNull SentryAndroidOptions sentryAndroidOptions,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull IHub hub) {
this.context = Objects.requireNonNull(context, "The application context is required");
this.options = Objects.requireNonNull(sentryAndroidOptions, "SentryAndroidOptions is required");
this.hub = Objects.requireNonNull(hub, "Hub is required");
this.frameMetricsCollector =
Objects.requireNonNull(frameMetricsCollector, "SentryFrameMetricsCollector is required");
this.buildInfoProvider =
Objects.requireNonNull(buildInfoProvider, "The BuildInfoProvider is required.");
this.packageInfo = ContextUtils.getPackageInfo(context, options.getLogger(), buildInfoProvider);
Expand Down Expand Up @@ -144,21 +167,7 @@ public synchronized void onTransactionStart(@NotNull ITransaction transaction) {
transactionsCounter--;
return;
}

// We stop profiling after a timeout to avoid huge profiles to be sent
scheduledFinish =
options
.getExecutorService()
.schedule(() -> onTransactionFinish(transaction, true), PROFILING_TIMEOUT_MILLIS);

transactionStartNanos = SystemClock.elapsedRealtimeNanos();
profileStartCpuMillis = Process.getElapsedCpuTime();

ProfilingTransactionData transactionData =
new ProfilingTransactionData(transaction, transactionStartNanos, profileStartCpuMillis);
transactionMap.put(transaction.getEventId().toString(), transactionData);

Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs);
onFirstTransactionStarted(transaction, traceFile);
} else {
ProfilingTransactionData transactionData =
new ProfilingTransactionData(
Expand All @@ -175,6 +184,64 @@ public synchronized void onTransactionStart(@NotNull ITransaction transaction) {
transactionsCounter);
}

@SuppressLint("NewApi")
private void onFirstTransactionStarted(
@NotNull ITransaction transaction, @NotNull File traceFile) {

measurementsMap.clear();
screenFrameRateMeasurements.clear();
adverseFrameRenderTimestampMeasurements.clear();
adverseFrozenFrameTimestampMeasurements.clear();

frameMetricsCollectorId =
frameMetricsCollector.startCollection(
new SentryFrameMetricsCollector.FrameMetricsCollectorListener() {
final long nanosInSecond = TimeUnit.SECONDS.toNanos(1);
final long frozenFrameThresholdNanos = TimeUnit.MILLISECONDS.toNanos(700);
float lastRefreshRate = 0;

@Override
public void onFrameMetricCollected(
@NotNull FrameMetrics frameMetrics, float refreshRate) {
long frameTimestampRelativeNanos =
SystemClock.elapsedRealtimeNanos() - transactionStartNanos;
long durationNanos = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
// Most frames take just a few nanoseconds longer than the optimal calculated
// duration.
// Therefore we subtract one, because otherwise almost all frames would be slow.
boolean isSlow = durationNanos > nanosInSecond / (refreshRate - 1);
float newRefreshRate = (int) (refreshRate * 100) / 100F;
if (durationNanos > frozenFrameThresholdNanos) {
adverseFrozenFrameTimestampMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos));
} else if (isSlow) {
adverseFrameRenderTimestampMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos));
}
if (newRefreshRate != lastRefreshRate) {
lastRefreshRate = newRefreshRate;
screenFrameRateMeasurements.addLast(
new ProfileMeasurementValue(frameTimestampRelativeNanos, newRefreshRate));
}
}
});

// We stop profiling after a timeout to avoid huge profiles to be sent
scheduledFinish =
options
.getExecutorService()
.schedule(() -> onTransactionFinish(transaction, true), PROFILING_TIMEOUT_MILLIS);

transactionStartNanos = SystemClock.elapsedRealtimeNanos();
profileStartCpuMillis = Process.getElapsedCpuTime();

ProfilingTransactionData transactionData =
new ProfilingTransactionData(transaction, transactionStartNanos, profileStartCpuMillis);
transactionMap.put(transaction.getEventId().toString(), transactionData);

Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs);
}

@Override
public synchronized void onTransactionFinish(@NotNull ITransaction transaction) {
onTransactionFinish(transaction, false);
Expand Down Expand Up @@ -226,8 +293,14 @@ private synchronized void onTransactionFinish(
}
return;
}
onLastTransactionFinished(transaction, isTimeout);
}

@SuppressLint("NewApi")
private void onLastTransactionFinished(ITransaction transaction, boolean isTimeout) {
Debug.stopMethodTracing();
frameMetricsCollector.stopCollection(frameMetricsCollectorId);

long transactionEndNanos = SystemClock.elapsedRealtimeNanos();
long transactionEndCpuMillis = Process.getElapsedCpuTime();
long transactionDurationNanos = transactionEndNanos - transactionStartNanos;
Expand Down Expand Up @@ -270,6 +343,18 @@ private synchronized void onTransactionFinish(
profileStartCpuMillis);
}

measurementsMap.put(
ProfileMeasurement.ID_SLOW_FRAME_RENDERS,
new ProfileMeasurement(
ProfileMeasurement.UNIT_NANOSECONDS, adverseFrameRenderTimestampMeasurements));
measurementsMap.put(
ProfileMeasurement.ID_FROZEN_FRAME_RENDERS,
new ProfileMeasurement(
ProfileMeasurement.UNIT_NANOSECONDS, adverseFrozenFrameTimestampMeasurements));
measurementsMap.put(
ProfileMeasurement.ID_SCREEN_FRAME_RATES,
new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements));

// cpu max frequencies are read with a lambda because reading files is involved, so it will be
// done in the background when the trace file is read
ProfilingTraceData profilingTraceData =
Expand All @@ -292,7 +377,8 @@ private synchronized void onTransactionFinish(
options.getEnvironment(),
isTimeout
? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT
: ProfilingTraceData.TRUNCATION_REASON_NORMAL);
: ProfilingTraceData.TRUNCATION_REASON_NORMAL,
measurementsMap);

SentryEnvelope envelope;
try {
Expand Down
Loading

0 comments on commit 9385630

Please sign in to comment.