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

docs: add page for long tasks causing frame drops #7625

Merged
merged 24 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d2f33af
docs: add page for long tasks causing frame drops
armcknight Aug 11, 2023
0fafa61
add examples
armcknight Aug 11, 2023
7f89cd1
style(lint): Auto commit lint changes
getsantry[bot] Aug 11, 2023
b0768f2
fix examples
armcknight Aug 11, 2023
a88b482
style(lint): Auto commit lint changes
getsantry[bot] Aug 11, 2023
9ed5ace
Update src/docs/product/issues/issue-details/performance-issues/long-…
armcknight Aug 15, 2023
77f4fe2
Update src/docs/product/issues/issue-details/performance-issues/long-…
armcknight Aug 15, 2023
45eb143
Update src/docs/product/issues/issue-details/performance-issues/long-…
armcknight Aug 15, 2023
c4b992b
remove todo for now
armcknight Aug 15, 2023
cc9a5b1
add screenshots and rename; fix a copypasta
armcknight Aug 15, 2023
5f28add
move path to new name
armcknight Aug 15, 2023
5a9c420
add links
armcknight Aug 15, 2023
aa7472a
fix another copypasta
armcknight Aug 15, 2023
5309491
don't link to any docs on frozen ui frames yet
armcknight Aug 15, 2023
16b0362
add profile screenshot
armcknight Aug 15, 2023
d368216
some feedback and reword explanation of search algorithm
armcknight Aug 16, 2023
2a42b4d
improvements
armcknight Aug 16, 2023
89635b8
remove header until/unless we add more solution ideas for swift concu…
armcknight Aug 16, 2023
940bb3c
add more caveats
armcknight Aug 18, 2023
1ab7ec5
style(lint): Auto commit lint changes
getsantry[bot] Aug 18, 2023
cf6279d
Apply suggestions from code review
armcknight Aug 24, 2023
8e9a529
style(lint): Auto commit lint changes
getsantry[bot] Aug 24, 2023
2a84ed6
updated profile screenshot
armcknight Sep 18, 2023
784bc79
add min supported cocoa sdk version
armcknight Sep 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "Frame Drop"
sidebar_order: 30
redirect_from:
- /product/issues/performance-issues/frame-drop/
description: "Learn more about how we detect Frame Drop issues and what you can do to help fix them."
---

The main (or UI) thread in a mobile app is responsible for handling all user interaction and needs to be able to respond to gestures and taps in real time. If a long-running operation blocks the main thread, the app becomes unresponsive, impacting the quality of the user experience.
armcknight marked this conversation as resolved.
Show resolved Hide resolved

We can detect some specific causes for a main thread stall, like decoding [images](https://docs.sentry.io/product/issues/issue-details/performance-issues/image-decoding-main-thread/) or [JSON](https://docs.sentry.io/product/issues/issue-details/performance-issues/json-decoding-main-thread/), or [searching strings with regexes](https://docs.sentry.io/product/issues/issue-details/performance-issues/regex-main-thread/), but there are many other things that could cause a stall. If a main thread stall causes your app to drop UI frames, but doesn't match one of our specific detectors, Sentry reports it under the generic Frame Drop issue type.
armcknight marked this conversation as resolved.
Show resolved Hide resolved

## Detection Criteria

[Profiling](/product/profiling/) must be enabled for Sentry to detect Frame Drop issues. Once set up, Sentry will look for profiles that record a frozen UI frame and then search for the application function call occupying the majority of time that the display link waited to draw the frozen frame. If multiple functions simply call through to the next, the deepest call is chosen.
armcknight marked this conversation as resolved.
Show resolved Hide resolved

## Function Evidence

To find additional information about your Frame Drop problem, go to its **Issue Details** page and scroll down to the "Function Evidence" section, which shows the following:

- **Transaction Name:** The name of the transaction where the issue was detected.
- **Suspect Function:** The function that triggered the issue detection.
- **Duration:** How long the function took to execute and the number of consecutive samples collected by the profiler that contained the function.

![Frame Drop Function Evidence](frame-drop-function-evidence.png)

To view the entire profile associated with the issue, click the “View Profile” button.

The profile will indicate where the suspect function was called from, along with other functions being called _by_ the suspect function:

![Frame Drop Profile](frame-drop-profile.png)

## Stack Trace

The “Stack Trace” section shows a full stack trace view, highlighting the long-running function frame:

![Frame Drop Stack Trace](frame-drop-stack-trace.png)

## Example

### iOS

The following code executes a long-running `while` loop on the main thread:

```objective-c
// given an array of number strings...

NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet];
[numbers enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if (obj.integerValue % 2 == 0) {
[sortedEvenNumbers addObject:obj];
}
}];
```

Performance could be improved by moving the long computation off of the main thread. The simplest approach is dispatching it to a lower Quality of Service (QoS) queue (or `NSOperationQueue`):

```objective-c {tabTitle:Dispatch to Background Queue}
// given an array of number strings...

NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
[numbers enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if (obj.integerValue % 2 == 0) {
[sortedEvenNumbers addObject:obj];
}
}];
});
```

Another avenue to consider for loops is to parallelize iterations. There are several options for doing this:
armcknight marked this conversation as resolved.
Show resolved Hide resolved

1. If you're iterating through a Foundation collection, you may already be using `enumerateObjects` or `enumerateKeysAndObjects`. Change this to `-[NSArray|NSSet|NSOrderedSet enumerateObjectsWithOptions:usingBlock:]` or `-[NSDictionary enumerateKeysAndObjectsWithOptions:usingBlock:]`, with option `NSEnumerationConcurrent`. Note that modifying the collection from inside the block will result in an exception being thrown. This is roughly equivalent to dispatching each iteration of the loop body to a concurrent GCD queue.

1. Use `dispatch_apply` to perform iterations of a general loop on a concurrent queue.

```objective-c {tabTitle:Foundation Collection Concurrent Enumeration}
// given an array of number strings...

NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet];
[numbers enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(NSString * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if (obj.integerValue % 2 == 0) {
[sortedEvenNumbers addObject:obj];
}
}];
```

```objective-c {tabTitle:dispatch_apply}
// given an array of number strings...

NSMutableOrderedSet<NSString *> *sortedEvenNumbers = [NSMutableOrderedSet<NSString *> orderedSet];
dispatch_apply(numberOfNumbers, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(size_t iteration) {
NSString *number = numbers[iteration];
if (number.integerValue % 2 == 0) {
[sortedEvenNumbers addObject:number];
}
});
```

There are several things to Keep in mind when introducing concurrency: - you may need to `@synchronize` critical sections, use semaphores, or dispatch back to a serial queue (or the main queue for UI work) - you may not be able to parallelize loops whose iterations are dependent or where order is significant - parallelization may not be more efficient for small collections, as [thread spawning has its own costs](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html#//apple_ref/doc/uid/10000057i-CH6-SW20); always measure first! - both `enumerateObjects...` and `dispatch_apply` are synchronous and won't return until all iterations have completed. Dispatch their invocation asynchronously off the main queue to avoid waiting.
armcknight marked this conversation as resolved.
Show resolved Hide resolved
Loading