-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathfaster-performance-debugging-with-ettrace.html
284 lines (244 loc) · 18.1 KB
/
faster-performance-debugging-with-ettrace.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
<!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="Still using the Time Profiler? Better performance debugging tools are available today, and in this article, I'll show you one of them.">
<meta name="title" content="Faster iOS performance debugging with ETTrace">
<meta name="url" content="https://swiftrocks.com/faster-performance-debugging-with-ettrace">
<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="Faster iOS performance debugging with ETTrace"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="Still using the Time Profiler? Better performance debugging tools are available today, and in this article, I'll show you one of them."/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/faster-performance-debugging-with-ettrace"/>
<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="Faster iOS performance debugging with ETTrace"/>
<meta name="twitter:description" content="Still using the Time Profiler? Better performance debugging tools are available today, and in this article, I'll show you one of them."/>
<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/faster-performance-debugging-with-ettrace"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2023-09-07T14:00:00+02:00",
"dateModified": "2023-09-11T14:00: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": "Faster iOS performance debugging with ETTrace",
"abstract": "Still using the Time Profiler? Better performance debugging tools are available today, and in this article, I'll show you one of them."
}
</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=Faster iOS performance debugging with ETTrace-->
<!--WRITEIT_POST_HTML_NAME=faster-performance-debugging-with-ettrace-->
<!--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=Still using the Time Profiler? Better performance debugging tools are available today, and in this article, I'll show you one of them.-->
<!--DateFormat example: 2021-04-12T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2023-09-11T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2023-09-07T14:00:00+02:00-->
<title>Faster iOS performance debugging with ETTrace</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Faster iOS performance debugging with ETTrace</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 11 Sep 2023</div>
</div>
<p>Performance problems can be rare in this era of blazing-fast iPhones, but chances are you're still having to deal with them every once in a while. Classic performance issues such as scroll hitches are still very easy to introduce if you're not careful about them, and when it happens, you'll want to be properly equipped to get to the bottom of it.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>The "standard" way of debugging performance issues in iOS is to use Xcode's <i>Time Profiler</i> instrument, but I personally never had a good experience with it. While it contains all the information you need to understand a particular problem, that information is not exactly easy to make sense of. To make it worse, sometimes <i>even getting the information to show up</i> in the first place can be quite the challenge, <a href="https://swiftrocks.com/reverse-engineering-xcode-issue-crash-symbol">as Instruments in iOS in general have been historically broken and plagued by bad UX.</a></p>
<p>Thankfully, you don't have to go through any of that! Today much better performance debugging tools are available (and for free), and in this article, I'll show you one of them.</p>
<h2>Enter ETTrace</h2>
<p><a href="https://github.com/EmergeTools/ETTrace">ETTrace</a> is an open-source performance measurement framework for iOS developed by the folks behind <a href="https://www.emergetools.com/">Emerge</a>, and I can say that today this is my favorite tool for measuring and debugging performance problems in iOS.</p>
<p>As mentioned in the beginning, while the Time Profiler does technically provide you with all the information that you need, actually understanding this information or even getting it to show up in the first place can be a big challenge, even if you know exactly what you're doing.</p>
<p>For me, personally, there are three things that make the Time Profiler hard to use. The first one is that you need to compile a special <i>Profile</i> build for it to work, meaning you cannot run it ad-hoc on an existing build or device. The second is that the Time Profiler has a really annoying tendency to simply refuse to work every once in a while, mostly when it comes to symbolication. Finally, last but not least, when you do manage to get it to work, the way in which the data is presented to you is not very helpful when it comes to locating the source of a particular performance bottleneck. In other words, there are better ways to display this data.</p>
<p>ETTrace, on the other hand, has none of these problems. <b>It doesn't require a special build, it automatically handles symbolication for you, and it displays the data in a much more readable way.</b> It's basically the Time Profiler on steroids, and I have found it to be in most cases a complete replacement for it.</p>
<h2>Example: Using ETTrace to find and fix a bottleneck</h2>
<p>For instructions on how to install ETTrace, <a href="https://github.com/EmergeTools/ETTrace">check out the official repo.</a> As of writing, ETTrace is installed by linking a dynamic framework into your app and installing a special <code>ettrace</code> CLI tool in your Mac. You can trace any build of your app that links against this framework, which is why you don't need to compile a special <i>Profile</i> build like you would when using Xcode and the Time Profiler. In practice you could even ship this framework alongside your App Store builds in order to be able to directly debug issues found in production, but I would personally not do that and keep it restricted to debug builds.</p>
<p>To see how ETTrace can help us debug performance issues better than the standard Time Profiler, let's pretend that we have a view controller called <code>ExploreCardViewController</code>, and that we have noticed that tapping a specific collection view cell in this VC is causing the app to freeze for a while.</p>
<p>To find out exactly why this is happening, we just need to run ETTrace. After following the usage steps as described on the repo, you'd be presented with something like this:</p>
<div class="post-image">
<img src="https://i.imgur.com/83Xzhe7.png" alt="Emerge">
</div>
<p>This way of displaying information is called a <b>Flame Graph</b>, and I find it to be a very efficient way of locating performance bottlenecks in your app's code. Each "entry" that you see here is a single method call in your app, with the X axis dictating <b>when</b> it was called (and how long it took to run), and the Y axis dictating <b>where/who</b> called it. In the example above, the first 3 frames (start/main/UIApplicationMain) represent functions internal to iOS that are responsible for launching and keeping the app alive, while everything else below it is actual code from our example app.</p>
<p>To find performance bottlenecks in a flame graph, all we need to do is look for the presence of a "chunky" stack trace and then go down the Y axis until we find <i>which frame exactly</i> is the source of the chunkiness.</p>
<p>Consider how <code>ExploreCardViewController</code> is shown in the report. It's very large, which means that this method is taking a really long time to run. But what exactly is <i>causing</i> it? Is it the literal call to <code>didSelectItemAt</code>, or is it something else further down the stack trace?</p>
<p>By going down the trace we can see that at its very bottom there's a <i>very</i> expensive call to <code>usleep</code> originating from <code>ArticleViewController.viewDidLoad()</code>, which is the reason why that entire stack trace is being reported as being expensive:</p>
<div class="post-image">
<img src="https://i.imgur.com/HclPx0t.png" alt="Emerge">
</div>
<p>Oops, seems like we forgot some debug code in our class!</p>
<pre><code>func viewDidLoad() {
sleep(1) // TODO: remove this!
}</code></pre>
<p>After deleting the call, the bottleneck was gone!</p>
<p>You may find this to be a dumb example, but I find that debugging real performance issues doesn't stray too far from this. The difference is just that instead of a dumb call to <code>sleep</code>, you'd see some other expensive operation. Otherwise, the process to locate it and the different ways in which you could fix it are the same.</p>
<h2>Other useful ETTrace features</h2>
<p>The example above showed a bottleneck that originated from a single very expensive call, but that's not the only source of performance issues. Sometimes the bottleneck may originate not from one large call, but multiple small ones in rapid sequence.</p>
<p>ETTrace's <b>Invert</b> and <b>Cluster Libraries</b> allow you to quickly debug issues like this by merging all those small calls together. For clarity, this is something that the Time Profiler can also do, but again, it's just that I personally find that ETTrace's flame graphs are much easier to understand than the Time Profiler's tree structure.</p>
<div class="post-image">
<img src="https://i.imgur.com/nu9JB0Z.png" alt="Emerge">
</div>
<p>Another feature I find myself using a lot is the <b>comparison view</b>. By uploading a second trace file, ETTrace will present you the <b>difference</b> between both traces, allowing you to quickly determine which methods became faster and which methods became slower. This can be good for getting some quick information about whether or something improves or causes a bottleneck, but note that this is not a very reliable way of determining how fast/slow exactly a particular method is. If you need very accurate information, then I recommend using <a href="https://swiftrocks.com/benchmarking-swift-code-properly-with-attabench">Attabench</a>.</p>
<div class="post-image">
<img src="https://i.imgur.com/Wh1RsYZ.png" alt="Emerge">
</div>
<p>Alternatively, if your company happens to pay for Emerge's enterprise solutions, you can also use their performance analysis product, which is similar to ETTrace but with the difference that it can actually provide you with data that is statistically significant.</p>
<h2>Does it completely replace the Time Profiler?</h2>
<p>I have been using ETTrace for most of my performance debugging work, but there are still a couple of cases where you might need to use the Time Profiler.</p>
<p>The first case that comes to my mind is when you need to <b>debug something that you cannot reproduce</b>, which is something that <a href="https://swiftrocks.com/debugging-ios-performance-issues-you-cant-reproduce-with-performance-trace-profiles">I've covered previously here at SwiftRocks.</a> For cases like this you'll find Apple's performance trace profiles to be the best solution, which currently require you to use Xcode and the Time Profiler.</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>Another case you might still need the Time Profiler for is when you're looking not just for performance data, but also other types of iOS-related information such as thread state, device temperature, battery level, os_logs, signposts, hangs, and so on. Nothing currently matches Xcode's Instruments when it comes to putting all this device information into one single place, so issues that require looking at multiple types of device information are still perfectly suited for it.</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>