-
-
Notifications
You must be signed in to change notification settings - Fork 444
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
Feat: App start up and slow/frozen frames metrics #1466
Conversation
// private boolean isHardwareAccelerated(Activity activity) { | ||
// // we can't observe frame rates for a non hardware accelerated view | ||
// return activity.getWindow() != null | ||
// && ((activity.getWindow().getAttributes().flags | ||
// & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) | ||
// != 0); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read that reading slow and frozen frames are only possible if FLAG_HARDWARE_ACCELERATED
is enabled, but it worked with it disabled during tests, so I'm not sure if I should keep this check or not.
FLAG_HARDWARE_ACCELERATED
is disabled by default by the way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you try it in a release build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, maybe thats the case for some OS versions only, I could not find anything though
sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java
Show resolved
Hide resolved
final Map<String, @NotNull MeasurementValue> framesMetrics = | ||
ActivityFramesState.getInstance().getMetrics(); | ||
if (framesMetrics != null) { | ||
transaction.getMeasurements().putAll(framesMetrics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slow/frozen frames are always sent, to every transaction, and represents the overall amount of the app lifecycle, and not from the start-end time of the transaction, we can't track only during the start/end of the transaction unless we implement the whole counting ourselves and this changes based on OS version and so on, that's the abstraction the class FrameMetricsAggregator
does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever we start a transaction, we could check the current count of the frames in ActivityFramesState
. When you finish a transaction, you again call ActivityFramesState
, and then you can calculate the different types of frames during the time of the transaction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that would not scale well if I have multiple transactions at the same time, the same issue as counting on iOS the frames on iOS, we can do it next maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running into a similar issue on RN as well. Considered @philipphofmann's suggestion at first but on JS users can change the start/end timestamps which makes it not work and way more difficult to implement metrics at the start/end of the transaction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we run into scaling problems? Hmm, changing it afterward might cause problems. Having it different on platforms is also not a good idea. The advantage of always sending the total number of frames during the lifetime of an application gives you more insights on how many slow frames and frozen frames you have, but the downside is that you have no clue where they are coming from. Being able to correlate them with transactions gives you way more insight. Maybe it would even make sense to send both lifetime frames and frames during the transaction.
sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java
Show resolved
Hide resolved
@philipphofmann and @bruno-garcia can you give some early feedback? I have left a few comments and TODO with my findings as well |
Codecov Report
@@ Coverage Diff @@
## main #1466 +/- ##
============================================
- Coverage 75.75% 75.71% -0.05%
Complexity 1926 1926
============================================
Files 190 191 +1
Lines 6580 6585 +5
Branches 665 665
============================================
+ Hits 4985 4986 +1
- Misses 1270 1274 +4
Partials 325 325
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job for a draft PR. Although it's a draft PR, I added many comments. I guess you would anyway fix most of them before making this PR ready for review. Still, I added them to not forget about them.
<provider | ||
android:name=".SentryPerformanceProvider" | ||
android:authorities="${applicationId}.SentryPerformanceProvider" | ||
android:initOrder="200" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😁. No just kidding.
android:initOrder="200" | |
android:initOrder="1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the higher the more important, so it'd make sense to put Long.MAX_VALUE :D
// frozen frames, threshold is 700ms | ||
frozenFrames += numFrames; | ||
} | ||
if (frameTime > 16) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m
: With 60 FPS a frame actually can take 1000 / 60 = 16.66667 ms
to render. On Cocoa, I had to change it to 1000 / 59
, because almost every frame takes a few nanoseconds longer than the optimal duration. Is there a way to retrieve the current frame rate? It could also be 120 or 90.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it changes on Android, it's written in their docs and source code in the same way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. How does it work then if you have a phone with 120 fps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik Android displays are limited to 60 fps
sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java
Show resolved
Hide resolved
return appStartTime; | ||
} | ||
|
||
synchronized void setAppStartTime() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m
: We could use Double-Checked Locking to avoid acquiring unnecessary locks. It might make sense to add a comment on why we need synchronization. I guess because someone could initialize the SDK on a background thread?
// private boolean isHardwareAccelerated(Activity activity) { | ||
// // we can't observe frame rates for a non hardware accelerated view | ||
// return activity.getWindow() != null | ||
// && ((activity.getWindow().getAttributes().flags | ||
// & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) | ||
// != 0); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you try it in a release build?
final Map<String, @NotNull MeasurementValue> framesMetrics = | ||
ActivityFramesState.getInstance().getMetrics(); | ||
if (framesMetrics != null) { | ||
transaction.getMeasurements().putAll(framesMetrics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever we start a transaction, we could check the current count of the frames in ActivityFramesState
. When you finish a transaction, you again call ActivityFramesState
, and then you can calculate the different types of frames during the time of the transaction.
// this is called after the Activity is created, so we know if the App is a warm or cold | ||
// start. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l
: Can you maybe elaborate a bit on why this is called after the Activity is created? Why don't we use onActivityCreated
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a better comment, but it's hard to explain and requires understanding deeply how the whole process start work, its a few pages that cant be easily resumed in one liner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we maybe link to resources so you can find out on your own even when you have to read some articles?
@@ -196,11 +227,29 @@ public synchronized void onActivityCreated( | |||
|
|||
@Override | |||
public synchronized void onActivityStarted(final @NonNull Activity activity) { | |||
if (performanceEnabled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l
: Why don't we do this in onActivityCreated
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because that's when the FrameMetricsAggregator
is able to start counting, before that, it can't do anything
sentry-android-core/src/main/java/io/sentry/android/core/AppStartState.java
Outdated
Show resolved
Hide resolved
<provider | ||
android:name=".SentryPerformanceProvider" | ||
android:authorities="${applicationId}.SentryInitProvider" | ||
android:initOrder="200" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
android:initOrder="200" | |
android:initOrder="1" |
😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -34,6 +37,9 @@ | |||
|
|||
private boolean isAllActivityCallbacksAvailable; | |||
|
|||
// does it need to be atomic? its only in the main thread |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it read by another thread? If no other thread uses it than we're probably fine
import org.jetbrains.annotations.ApiStatus; | ||
|
||
@ApiStatus.Internal | ||
public final class MeasurementValue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we're better off without this class and using the float
directly to avoid allocating these objects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be easily removed, its not a public API, but the idea was to be extendable like the API is, its an object with a single float property
Is this blocked? Is there anything I can do to help move this PR (particularly the App Startup bits) |
nop, this PR was the PoC, the PR for App startup is #1487 |
📜 Description
Just a POC
💡 Motivation and Context
💚 How did you test it?
📝 Checklist
🔮 Next steps