diff --git a/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-function-evidence.png b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-function-evidence.png new file mode 100644 index 0000000000000..6c40f984c9582 Binary files /dev/null and b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-function-evidence.png differ diff --git a/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-profile.png b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-profile.png new file mode 100644 index 0000000000000..4bbfee2508d3c Binary files /dev/null and b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-profile.png differ diff --git a/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-stack-trace.png b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-stack-trace.png new file mode 100644 index 0000000000000..5dcc0a2c78207 Binary files /dev/null and b/src/docs/product/issues/issue-details/performance-issues/frame-drop/frame-drop-stack-trace.png differ diff --git a/src/docs/product/issues/issue-details/performance-issues/frame-drop/index.mdx b/src/docs/product/issues/issue-details/performance-issues/frame-drop/index.mdx new file mode 100644 index 0000000000000..36e068aee1f3a --- /dev/null +++ b/src/docs/product/issues/issue-details/performance-issues/frame-drop/index.mdx @@ -0,0 +1,111 @@ +--- +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. + +We can detect some specific causes for a main thread stall, like decoding [images](/product/issues/issue-details/performance-issues/image-decoding-main-thread/) or [JSON](/product/issues/issue-details/performance-issues/json-decoding-main-thread/), or [searching strings with regexes](/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. + +## 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 most time-consuming application function call delaying the display link's next vsync. In a run of functions calling through without any self-time, the deepest call will be chosen. + + + +The minimum supported version for Cocoa is `8.12.0`. + + + +## 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 *sortedEvenNumbers = [NSMutableOrderedSet 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 *sortedEvenNumbers = [NSMutableOrderedSet 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: + +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 *sortedEvenNumbers = [NSMutableOrderedSet 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 *sortedEvenNumbers = [NSMutableOrderedSet 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 be unable to parallelize loops whose iterations are dependent or where order is significant. +- Parallelization may be less efficient for small collections because [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). So 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.