-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdebug-cpu-exceptions-xcode-energy-reports.html
311 lines (265 loc) · 17.8 KB
/
debug-cpu-exceptions-xcode-energy-reports.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
<!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="If you don't know what a CPU Usage termination is, consider yourself lucky. Let's explore iOS' CPU Exceptions!">
<meta name="title" content="Solving CPU Usage Crashes with Xcode's Energy Organizer">
<meta name="url" content="https://swiftrocks.com/debug-cpu-exceptions-xcode-energy-reports">
<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="Solving CPU Usage Crashes with Xcode's Energy Organizer"/>
<meta property="og:image" content="https://swiftrocks.com/images/thumbs/thumb.jpg?4"/>
<meta property="og:description" content="If you don't know what a CPU Usage termination is, consider yourself lucky. Let's explore iOS' CPU Exceptions!"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://swiftrocks.com/debug-cpu-exceptions-xcode-energy-reports"/>
<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="Solving CPU Usage Crashes with Xcode's Energy Organizer"/>
<meta name="twitter:description" content="If you don't know what a CPU Usage termination is, consider yourself lucky. Let's explore iOS' CPU Exceptions!"/>
<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/debug-cpu-exceptions-xcode-energy-reports"
},
"image": [
"https://swiftrocks.com/images/thumbs/thumb.jpg"
],
"datePublished": "2021-09-28T14:00:00+02:00",
"dateModified": "2021-09-28T14: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": "Solving CPU Usage Crashes with Xcode's Energy Organizer",
"abstract": "If you don't know what a CPU Usage termination is, consider yourself lucky. Let's explore iOS' CPU Exceptions!"
}
</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=Solving CPU Usage Crashes with Xcode's Energy Organizer-->
<!--WRITEIT_POST_HTML_NAME=debug-cpu-exceptions-xcode-energy-reports-->
<!--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=If you don't know what a CPU Usage termination is, consider yourself lucky. Let's explore iOS' CPU Exceptions!-->
<!--DateFormat example: 2021-09-28T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE_LAST_MOD=2021-09-28T14:00:00+02:00-->
<!--WRITEIT_POST_SITEMAP_DATE=2021-09-28T14:00:00+02:00-->
<title>Solving CPU Usage Crashes with Xcode's Energy Organizer</title>
<div class="blog-post">
<div class="post-title-index">
<h1>Solving CPU Usage Crashes with Xcode's Energy Organizer</h1>
</div>
<div class="post-info">
<div class="post-info-text">Published on 28 Sep 2021</div>
</div>
<p>If you don't know what a CPU Usage termination is, consider yourself lucky. Not only this is one of the nastiest crashes you can get in iOS, but it's also not even considered as a crash by the system. These terminations are not reported to crash providers like Firebase, so if your app has a severe high CPU usage crash, you won't even know about it until your users start reporting it to you. Really nasty stuff!</p>
<div class="sponsor-article-ad-auto hidden"></div>
<p>If you found this article by googling "how the hell do I solve this CPU usage thing?", you're in good hands. I experienced some of these crashes recently, and after suffering due to the lack of proper documentation from Apple explaining this issue, I was able to aggregate several pieces of scattered information about this on the web. Let's explore iOS' CPU Exceptions!</p>
<h2>What are CPU Exceptions in iOS?</h2>
<p>According to Apple, your app is free to destroy the phone's CPU -- as long as it's running in the foreground and not doing it for a very long time. If your app is running in the <i>background</i> however, you need to cut back on CPU usage. It's not necessarily a problem to have an expensive task in the background once, but if you're consistently using over 80% CPU over a period of one minute, iOS will kill your app without warning. This is called a <b>CPU Exception</b>, or a "high CPU usage termination".</p>
<p>As far as I know, this situation is only possible if your app is using a background mode (like an audio app) as regular apps can't run in the background for large periods of time. In any case, when iOS kills your app, you'll get a report file that looks roughly like this:</p>
<pre><code>Event: cpu usage
Action taken: Process killed
CPU: 48 seconds cpu time over 52 seconds (93% cpu average), exceeding limit of 80% cpu over 60 seconds
CPU limit: 48s
Limit duration: 60s
CPU used: 48s
CPU duration: 52s
Duration: 51.59s
Duration Sampled: 47.54s
Steps: 44
Heaviest stack for the target process:
(trace)</code></pre>
<p>If you receive one of these logs as part of a bug report, my first advice is to ignore it completely. The problem here is that the trace is completely useless -- although it tells you roughly which part of the app is hosting the problematic code, it gives you no information about what the actual problem is, and to make it worse, every trace is going to be different from each other. Remember: The issue is not that one specific piece of code is expensive, it's that you've been doing expensive things consistently for over a minute.</p>
<p>To properly debug this problem, you'll need the reports of multiple users. By having the "expensive stack trace" of multiple instances of the termination, the idea is that we can overlay them to see what they have in common, which should hopefully pinpoint the exact line of code that is bootstrapping the entire cascade that is leading to this issue.</p>
<h2>Enter Xcode's Energy Organizer</h2>
<p>As it turns out, Apple already does this for you. If you open your Xcode's Organizer and click on the <b>Energy</b>, you'll see that Apple reports to you all instances of problematic CPU/memory usage -- including cases where it decided to terminate the app! (as long as the user has agreed to share metrics with the developer)</p>
<div class="post-image">
<img src="https://i.imgur.com/77jLQee.png" alt="Energy Organizer">
</div>
<h2>How to Symbolicate Xcode's Energy Logs</h2>
<p>If your crashes are symbolicated, you can skip to the next section. If your crashes are <b>not</b> symbolicated then it means that the app's archive wasn't created from your machine, so we need to download its dSYMs and provide them to Xcode.</p>
<p>Unfortunately, this is one of these moments where you start to rue the fact that Apple has a monopoly on iOS developer tools, but symbolicating these logs can be a pain in the ass.</p>
<ul>
<li>Make sure you have the dSYM file for the version you're trying to symbolicate. If you're archiving the app from a CI pipeline, you should make sure your pipeline is storing the dSYMs somewhere you can retrieve them later on for cases like this. Don't trust the "Download debug symbols" button in the Archive window, because that button simply doesn't work and they never bothered to fix it.</li>
<li>Now, place the dSYM zip somewhere. It doesn't matter where, because Xcode will attempt to find it through Spotlight.</li>
<li>With everything set, open the energy report, right click the stack trace and click <code>Symbolicate</code>. If it fails, it means that your computer's Spotlight failed to index the dSYM. Fortunately that's easy to fix, <a href="https://swiftrocks.com/reverse-engineering-xcode-issue-crash-symbol">and you can find the steps at the end of my article about the symbolication process.</a></li>
</ul>
<h2>Analyzing Xcode's Energy Logs</h2>
<p>I was surprised to see how detailed the Energy Organizer is, because I didn't even know it existed before I went deep into trying to find out how to solve CPU usage issues. The organizer reports several different kinds of issues, with symbols on the log indicating the issue being reported. These symbols changed across different Xcode versions, but in my Xcode 12.4, a minus sign indicates that the app crashed.</p>
<p>For <i>High CPU Usage</i> reports and terminations, an energy log contains a sampling of CPU usage taken over a period of time. But instead of showing individual traces like in a crash, the Energy Organizer instead groups similar samplings together and show you what they have in common:</p>
<div class="post-image">
<img src="https://i.imgur.com/QiQJHpU.png" alt="Energy Organizer">
</div>
<p>The percentage represents how many times that frame appeared in all the samples, with a massive increase of it likely indicating what line of code is causing the high CPU usage. It might not necessarily indicate that this is the root of the issue, but since everyone is congregating into it, it's at the very least indicating one of the consequences of whatever is causing the issue.</p>
<p><b>If you're trying to reproduce the termination itself, you should probably grab one of your app's Release builds and run it in a real device without a debugger attached.</b> I think it's possible to get these terminations in the simulator, but in general it's always a good idea to debug this sort of problem in a production build.</p>
<h2>How to prevent and detect high CPU usage warnings?</h2>
<p>According to Apple, the problem is not that you can't use CPU in the background, it's that you need to use the proper APIs for it. If you absolutely need to do an expensive task in the background, you should be using the proper <b>background task</b> APIs. More specifically for this issue, the <a href="https://developer.apple.com/videos/play/wwdc2020/10078/#:~:text=If%20the%20foreground%20app%20grows,for%20less%20than%2050%20megabytes.">Why is my app getting killed?</a> WWDC session from 2020 recommends using the <code>BGProcessingTask</code> API, which represents an expensive task that will only be executed when the device is in an idle state (for example, charging during the night).</p>
<p>At the beginning of this article I mentioned that the system won't treat these issues as "crashes", but there's actually you can detect them. Although they are not crashes, <code>MetricKit</code> has a special diagnostic payload specifically for this problem: <code>MXCPUExceptionDiagnostic</code>.</p>
<p>If you haven't used <code>MetricKit</code> before, it works by subscribing an object to a diagnostics reporter singleton. When that is done, the system will send a report as an array of <code>MXMetricPayload</code> objects at most once per day, containing metrics from the past 24 hours and any previously undelivered daily reports. There are many interesting metrics you can extract, but in this article we're only interested in CPU Exception ones. Here's how you can implement an object that reports CPU Exceptions from a device:</p>
<pre><code>import MetricKit
final class CPUExceptionsReporter: NSObject, MXMetricManagerSubscriber {
override init() {
super.init()
MXMetricManager.shared.add(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
payload.cpuExceptionDiagnostics?.forEach(handleCPUException)
}
}
func handleCPUException(_ diagnostic: MXCPUExceptionDiagnostic) {
// Write a report to your crash reporter of choice
}
deinit {
MXMetricManager.shared.remove(self)
}
}</code></pre>
<div class="sponsor-article-ad-auto hidden"></div>
<p>You should be aware that <code>MetricKit</code> only reports this data once a day, so the data represents a warning/termination that happened <b>in the past</b>, very much likely in a different session entirely.</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>