-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdebugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles.html
318 lines (276 loc) · 20.9 KB
/
debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://use.fontawesome.com/afd448ce82.js"></script>
<!-- Meta Tag -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- SEO -->
<meta name="author" content="Bruno Rocha">
<meta name="keywords" content="Software, Engineering, Blog, Posts, iOS, Xcode, Swift, Articles, Tutorials, OBJ-C, Objective-C, Apple">
<meta name="description" content="The Performance Trace profile allows you to debug performance issues without having to attach the device to Xcode. Let's see what we can use it for!">
<meta name="title" content="Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce">
<meta name="url" content="https://swiftrocks.com/debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles">
<meta name="image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4">
<meta name="copyright" content="Bruno Rocha">
<meta name="robots" content="index,follow">
<meta property="og:title" content="Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="The Performance Trace profile allows you to debug performance issues without having to attach the device to Xcode. Let's see what we can use it for!"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta name="twitter:image:alt" content="Page Thumbnail"/>
<meta name="twitter:title" content="Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce"/>
<meta name="twitter:description" content="The Performance Trace profile allows you to debug performance issues without having to attach the device to Xcode. Let's see what we can use it for!"/>
<meta name="twitter:site" content="@rockbruno_"/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="images/favicon/iconsmall2.png" sizes="32x32" />
<link rel="apple-touch-icon" href="images/favicon/iconsmall2.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
<!-- Bootstrap CSS Plugins -->
<link rel="stylesheet" type="text/css" href="css/bootstrap.css">
<!-- Prism CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/prism4.css">
<!-- Main CSS Stylesheet -->
<link rel="stylesheet" type="text/css" href="css/style48.css">
<link rel="stylesheet" type="text/css" href="css/sponsor4.css">
<!-- HTML5 shiv and Respond.js support IE8 or Older for HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://swiftrocks.com/debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2023-06-01T13:30:00+02:00",
"dateModified": "2023-06-01T13:30:00+02:00",
"author": {
"@type": "Person",
"name": "Bruno Rocha"
},
"publisher": {
"@type": "Organization",
"name": "SwiftRocks",
"logo": {
"@type": "ImageObject",
"url": "https://swiftrocks.com/images/thumbs/thumb.jpg"
}
},
"headline": "Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce",
"abstract": "The Performance Trace profile allows you to debug performance issues without having to attach the device to Xcode. Let's see what we can use it for!"
}
</script>
</head>
<body>
<div id="main">
<!-- Blog Header -->
<!-- Blog Post (Right Sidebar) Start -->
<div class="container">
<div class="col-xs-12">
<div class="page-body">
<div class="row">
<div><a href="https://swiftrocks.com">
<img id="logo" class="logo" alt="SwiftRocks" src="images/bg/logo2light.png">
</a>
<div class="menu-large">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-large">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
<div class="menu-small">
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-1">
<div class="menu-item">
<a href="blog">blog</a>
</div>
<div class="menu-item">
<a href="about">about</a>
</div>
<div class="menu-item">
<a href="talks">talks</a>
</div>
<div class="menu-item">
<a href="projects">projects</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
<div class="menu-arrow-right"></div>
<div class="menu-header menu-header-small-2">
<div class="menu-item">
<a href="software-engineering-book-recommendations">book recs</a>
</div>
<div class="menu-item">
<a href="games">game recs</a>
</div>
<div class="menu-arrow-right-2"></div>
</div>
</div>
</div>
<div class="content-page" id="WRITEIT_DYNAMIC_CONTENT">
<!--WRITEIT_POST_NAME=Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce-->
<!--WRITEIT_POST_HTML_NAME=debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles-->
<!--Add here the additional properties that you want each page to possess.-->
<!--These properties can be used to change content in the template page or in the page itself as shown here.-->
<!--Properties must start with 'WRITEIT_POST'.-->
<!--Writeit provides and injects WRITEIT_POST_NAME and WRITEIT_POST_HTML_NAME by default.-->
<!--WRITEIT_POST_SHORT_DESCRIPTION=The Performance Trace profile allows you to debug performance issues without having to attach the device to Xcode. Let's see what we can use it for!-->
<!--DateFormat example: 2021-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2023-06-01T13:30:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2023-06-01T13:30:00+02:00-->
<title>Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Performance Trace Profiles in iOS: Debugging performance issues you can't reproduce</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 01 Jun 2023</div>
</div>
<p>If you landed at this article then it's possible that you're dealing with one of the most annoying things you can face as a developer: having to investigate an issue reported by your users that <b>nobody</b> seems to be able to reproduce internally.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>The topic of investigating <i>crashes</i> you can't reproduce is something I've already talked about here at SwiftRocks in the <a href="https://swiftrocks.com/how-to-solve-any-ios-crash-ever">"How To Solve Any iOS Crash Ever"</a> article, but what I wanted to show you today refers to a different yet equally challenging category of issues: <b>performance problems</b>, like hangs/freezing UI, slow code, unnecessary threading/concurrency, CPU/memory exceptions, and so on.</p>
<p>While some of the techniques mentioned in the crashes article can also be used to investigate performance-related problems, chances are you won't have to, because this category of issues has access to a debugging weapon so powerful that you don't even need to run the app yourself to find the exact source of the problem!</p>
<h2>Performance Trace Profiles</h2>
<p><i>Debugging Profiles</i> are relatively unknown in the iOS community (possibly because the types of problems that require them are themselves quite rare to come across), but they are my current favorite way of debugging performance-related issues.</p>
<p>The standard way of debugging performance problems is by hooking your device to Xcode's Instruments, but if you can't reproduce the issue yourself, then obviously you cannot do that. However, if you know <i>anyone</i> who can reproduce it (doesn't matter if it's a developer or a random user), you can still fully debug the problem by having them run a <b>Performance Trace</b> for you.</p>
<p><b>Profiles</b> are files you open in your iOS device to temporarily unlock features that the device wouldn't normally have access to, something you might've already experienced if you ever enrolled one of your devices to the beta OS program. What you might not know though is that there are <i>many</i> types of profiles out there that you can install, and some of them are oriented towards helping you debug issues in your apps!</p>
<p>As the name implies, the <b>Performance Trace</b> profile gives your device performance-tracing capabilities. To be more specific, it allows the device that has it to run a system trace of <b>everything</b> that is currently running on the phone, generating a trace that you can later extract and open in Instruments. In other words, it's like if you were locally running a profile build of your app, except that it doesn't require the device to be plugged anywhere and works on any device and build of your product, even the App Store ones.</p>
<p><b>Be careful though, because with this great power comes also great responsibility; the trace will capture everything that is happening on the user's phone, which includes a lot of personal/sensitive information about that particular device.</b> If you're going to be asking strangers to do this process for you, make sure this is clear to them and that you're not violating any data protection laws from your country that may apply.</p>
<h2>Running a Performance Trace</h2>
<p>Follow these steps to enable performance tracing on your device:</p>
<ol>
<li>Open Apple's <a href="https://developer.apple.com/bug-reporting/profiles-and-logs/">Profiles and Logs</a> page.</li>
<li>Find the <i>Performance Trace</i> entry in the list of profiles.</li>
<li>Download the profile and install it on the device that can reproduce the issue. (You can, for example, use AirDrop to transfer the profile from a computer to the device).</li>
<li>Restart the device if needed.</li>
<li>Go to <i>Settings > Control Center > Customize Controls</i>, and then enable the <i>Performance Trace</i> control.</li>
</ol>
<p>To run a trace, follow these steps:</p>
<ol>
<li>Open Control Center and tap on the Performance Trace control to begin tracing.</li>
<li>Open your app (if you haven't done it already), and reproduce the issue. Keep in mind that traces cannot be longer than 30 seconds.</li>
<li>After reproducing the problem, either open Control Center and tap on the Performance Trace control again to stop tracing or wait for the 30 seconds time limit (after which the trace will automatically stop itself).</li>
<li>Wait a while for your phone to process the trace. You will receive a notification when this is done containing the name of the generated file (usually <code>trace_somethingsomething.tgz</code>).</li>
<li>Either tap the notification or go to <i>Settings > Privacy > Analytics & Improvements > Analytics Data</i></li>
<li>Locate the trace in the list and send it to your Mac. Trace archives are usually a couple of hundred megabytes in size.</li>
<li>(Optional) Find the profile in <i>Settings > VPN & Device Management</i> and delete it. The profile will otherwise automatically delete itself after 7 days.</li>
</ol>
<p>From your Mac, uncompress the archive. The result will be an instruments report that you can open in Xcode.</p>
<h2>Analyzing the trace</h2>
<p>Once you open the trace you may find that Xcode will highlight the <b>System Trace</b> instrument for you, and that's exactly what you should open. Keep in mind that by default no symbols will be resolved since the trace was generated outside of Xcode, so you'll need access to the dSYMs for the build that was traced in hand in order to make sense of the report. Instruments should be able to automatically symbolicate the report if the dSYMs are in your machine, but if it doesn't do that (it almost never does for me), you can manually provide the path to the dSYMs at File -> Symbols. If that also doesn't work, the <a href="https://swiftrocks.com/reverse-engineering-xcode-issue-crash-symbol">Spotlight refreshing steps mentioned in this article</a> should do the trick.</p>
<p>I find that the best way to learn how to use this particular instrument is to <a href="https://developer.apple.com/videos/play/wwdc2016/411/">watch Apple's WWDC session about it</a>, but if you've watched it and still feel lost, the example below may help you.</p>
<p>The System Trace instrument is basically the Time Profiler on steroids. While the latter (which I'm assuming you have prior experience with) allows you to analyze the CPU usage and memory footprint of an app, System Trace does that <b>in addition</b> to providing detailed information about the state of the different threads in the app, including what's causing them to not run code (if applicable). When facing performance issues, this additional threading information can usually point you to the exact source of the problem!</p>
<p>Here's an example that shows how I usually approach performance issues. Consider this intentionally slow horrible piece of code:</p>
<pre><code>final class ViewController: UIViewController {
let queue = DispatchQueue(label: "slow-queue")
let waitSemaphore = DispatchSemaphore(value: 0)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
queue.async {
self.runVerySlowCode()
self.waitSemaphore.signal()
}
waitSemaphore.wait()
runVerySlowCode()
}
func runVerySlowCode() {
var arr = Array(0...100_000)
for i in (1..<arr.count).reversed() {
for j in 0..<i where arr[j] > arr[j + 1] {
arr.swapAt(j, j + 1)
}
}
}
}</code></pre>
<p>When displayed, this view controller will run some very expensive code in a background thread, <b>block itself</b> until that finishes (because why not?), and then proceed to run the same expensive code again because we really like it. This code, which results in the main thread hanging for several seconds, is obviously terrible, but pretend that somehow this slipped into production without your knowledge, your users are now complaining, and you had someone send you a performance trace. After opening the trace, you'll see something like this:</p>
<div class="post-image margin-top-40 margin-bottom-40">
<img src="https://i.imgur.com/NbmMGSM.png" alt="">
</div>
<p>The System Trace instrument tracks the state of all threads in the process, and here you can immediately see that something's wrong: the main thread is marked as <b>Blocked</b> by something for a really long time and then running non-stop for also a really long time. Each of these actions will result in the app hanging.</p>
<h2>Tips for debugging the blocked state</h2>
<p>What I really like about this instrument is that it's capable of telling you exactly <i>why</i> a thread is marked as blocked. If you change the bottom inspector to the <b>Narrative</b> mode and move the timeline to the beginning of the blocked section, the instrument will tell you that the main thread was blocked by a semaphore, which was later released by another thread:</p>
<div class="post-image margin-top-40 margin-bottom-40">
<img src="https://i.imgur.com/fvsiPfN.png" alt="">
</div>
<p>Many of the elements in this view are clickable and will provide more information when highlighted. In this case, by clicking the row that mentions the semaphore, you'll be able to see in the bottom right exactly which line triggered the event (which in this case is the semaphore in <code>viewWillAppear</code>). Another useful thing you can do is tap the name of the thread that eventually released it, which will take you to it in the timeline and reveal to you what exactly this thread was doing that caused the main thread to be blocked for so long (the next thing we'll look at).</p>
<h2>Tips for debugging the excessive work</h2>
<p>These traces will eventually lead you to a thread doing a lot of expensive work, and the process to debug those is no different than using the standard Time Profiler instrument; by highlighting the suspicious area and changing the bottom inspector to the <b>Profile</b> view, we can see what the app was doing at the time.</p>
<p>I assume you already know how to use the Time Profiler so I'll skip explaining the many different ways in which you can read this data, but if you need a refresher, <a href="https://developer.apple.com/videos/play/wwdc2019">this WWDC session</a> should cover what you need to know. We had two such cases (the work being done by the background thread that caused the main thread to be blocked and the work the main thread did on itself) in our example, and in any of them we can see that <code>runVerySlowCode</code> is the method to blame.</p>
<div class="post-image margin-top-40 margin-bottom-40">
<img src="https://i.imgur.com/894bK1H.png" alt="">
</div>
<h2>What else can Performance Traces be used for?</h2>
<p>Performance Traces shine mostly in the very specific case we covered in the introduction: you are facing an issue that is connected to performance and for some reason you cannot reproduce it yourself. You could theoretically use it to gather data on more general logic/UI issues (assuming the data you're looking for would somehow materialize in the sampled stack traces), but for those cases, you'd probably find the steps defined in the <a href="https://swiftrocks.com/how-to-solve-any-ios-crash-ever">article about crashes</a> to be more effective.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Despite being a rare situation to come by, these traces have saved by butt every time it happened and are now one of my favorite debugging techniques for this category of issues.</p>
</div>
</div>
<div class="blog-post footer-main">
<div class="footer-logos">
<a href="https://swiftrocks.com/rss.xml"><i class="fa fa-rss"></i></a>
<a href="https://twitter.com/rockbruno_"><i class="fa fa-twitter"></i></a>
<a href="https://github.com/rockbruno"><i class="fa fa-github"></i></a>
</div>
<div class="footer-text">
© 2025 Bruno Rocha
</div>
<div class="footer-text">
<p><a href="https://swiftrocks.com">Home</a> / <a href="blog">See all posts</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blog Post (Right Sidebar) End -->
</div>
</div>
</div>
<!-- All Javascript Plugins -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="js/prism4.js"></script>
<!-- Main Javascript File -->
<script type="text/javascript" src="js/scripts30.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-H8KZTWSQ1R"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-H8KZTWSQ1R');
</script>
</body>
</html>