Skip to content
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

Closed
wants to merge 12 commits into from

Conversation

marandaneto
Copy link
Contributor

📜 Description

Just a POC

💡 Motivation and Context

💚 How did you test it?

📝 Checklist

  • I reviewed the submitted code
  • I added tests to verify the changes
  • I updated the docs if needed
  • No breaking changes

🔮 Next steps

@marandaneto marandaneto changed the title FeaT: App start up Feat: App start up and slow/frozen frames metrics May 18, 2021
Comment on lines +235 to +241
// 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);
// }
Copy link
Contributor Author

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

Copy link
Member

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?

Copy link
Contributor Author

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

final Map<String, @NotNull MeasurementValue> framesMetrics =
ActivityFramesState.getInstance().getMetrics();
if (framesMetrics != null) {
transaction.getMeasurements().putAll(framesMetrics);
Copy link
Contributor Author

@marandaneto marandaneto May 19, 2021

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

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Member

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.

@marandaneto
Copy link
Contributor Author

@philipphofmann and @bruno-garcia can you give some early feedback? I have left a few comments and TODO with my findings as well

@codecov-commenter
Copy link

codecov-commenter commented May 19, 2021

Codecov Report

Merging #1466 (1715c4d) into main (657706a) will decrease coverage by 0.04%.
The diff coverage is 20.00%.

Impacted file tree graph

@@             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              
Impacted Files Coverage Δ Complexity Δ
...main/java/io/sentry/protocol/MeasurementValue.java 0.00% <0.00%> (ø) 0.00 <0.00> (?)
...ain/java/io/sentry/protocol/SentryTransaction.java 86.20% <50.00%> (-2.69%) 12.00 <0.00> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 657706a...1715c4d. Read the comment docs.

Copy link
Member

@philipphofmann philipphofmann left a 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😁. No just kidding.

Suggested change
android:initOrder="200"
android:initOrder="1"

Copy link
Contributor Author

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) {
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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?

Copy link
Contributor Author

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

return appStartTime;
}

synchronized void setAppStartTime() {
Copy link
Member

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?

Comment on lines +235 to +241
// 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);
// }
Copy link
Member

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);
Copy link
Member

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.

Comment on lines 89 to 90
// this is called after the Activity is created, so we know if the App is a warm or cold
// start.
Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Member

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) {
Copy link
Member

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?

Copy link
Contributor Author

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

@marandaneto marandaneto mentioned this pull request May 25, 2021
4 tasks
<provider
android:name=".SentryPerformanceProvider"
android:authorities="${applicationId}.SentryInitProvider"
android:initOrder="200"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
android:initOrder="200"
android:initOrder="1"

😄

Copy link
Contributor Author

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
Copy link
Member

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 {
Copy link
Member

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

Copy link
Contributor Author

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

@bruno-garcia
Copy link
Member

Is this blocked? Is there anything I can do to help move this PR (particularly the App Startup bits)

@marandaneto
Copy link
Contributor Author

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
Next week I'll be focused only on that to make the discussed changes and land it.

@marandaneto marandaneto deleted the feat/app-start-up branch November 23, 2021 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants