Introduction
Eight key metrics to track for application performance:
Battery Usage
Launch Time
Hang Rate
Memory
Disk Writes
Scrolling
Terminations
MXSignposts
Toolset:
Xcode Organizer
MetricKit
Instruments
XCTest
App Store Connect API
Metrics and resolutions
Battery life
Subsystems to pay attention to improve battery life:
CPU
Networking
Location
audio
Bluetooth
GPU
During development I can monitor battery impact via the energy gauge in the Debug navigator.
High CPU utilizationis when CPU use is greater than 20%CPU Wake Overheadis regions where the CPU wakes from an idle state, and there’s an incurred energy cost
After shipping the app, we can monitor our app performance via MetricKit.
class AppMetrics: MXMetricManagerSubscriber {
init() {
let shared = MXMetricManager.shared
shared.add(self)
}
deinit {
let shared = MXMetricManager.shared
shared.remove(self)
}
// Receive daily metrics
func didReceive(_ payloads: [MXMetricPayload]) {
// Process metrics
}
// Receive diagnostics
func didReceive(_ payloads: [MXDiagnosticPayload]) {
// Process metrics
}
}This data is also automatically collected (from devices that have given consent) and is available in the Xcode Organizer.
New in Xcode 13, the Organizer has Regression pane:
isolates all the metrics that have increased significantly in the most recent version of our app
Alternatively we can use the AppStore API.
Hang Rate and Scrolling
A hang is when the app is unresponsive to user input or actions for at least
250 millisecondsStuttering scrolls occur when new content isn’t ready for the next screen refresh
I can use Instruments to detect the cause of my hangs by using the
Thread StateorSystem Call Traces:the
Thread State Traceinstrument shows a timeline of the thread’s state and when the OS has scheduled the thread to runI can see how long a thread was blocked for in the details section
The
System Call Traceshows a narrative that details the system calls entered and how long they took
write performance tests with XCTest that launch and scrolls through the app
func testScrollingAnimationPerformance() throws {
app.launch()
app.staticTexts["Meal Planner"].tap()
let foodCollection = app.collectionViews.firstMatch
let measureOptions = XCTMeasureOptions()
measureOptions.invocationOptions = [.manuallyStop]
measure(
metrics: [XCTOSSignpostMetric.scrollDecelerationMetric],
options: measureOptions
) {
foodCollection.swipeUp(velocity: .fast)
stopMeasuring()
foodCollection.swipeDown(velocity: .fast)
}
}New in iOS 15 and macOS 12, MetricKit will deliver all diagnostics, including hangs, in my app immediately after an issue occurs
In the case of scroll hitches, iOS 15 introduces a new API within MetricKit to tag custom animations using MXSignpost
MXSignpost is a wrapper API shipped with MetricKit that allows to mark critical code sections for telemetry
func startAnimating() {
// Mark the beginning of animations
mxSignpostAnimationIntervalBegin(
log: MXMetricManager.makeLogHandle(category: "animation_telemetry"),
name: "custom_animation”)
}
func animationDidComplete() {
// Mark the end of the animation to receive the collected hitch rate telemetry
mxSignpost(OSSignpostType.end,
log: MXMetricManager.makeLogHandle(category: "animation_telemetry"),
name: "custom_animation")
}Disk Writes
profile your app using the File Activity template in Instruments
this records file system use in the form of system calls, to easily identify places in the app’s code where you’re accessing the file system.
Best practices:
batching your write operations
use Core Data for frequently-changing data
avoiding rapid file creation and deletion
write performance tests with XCTest to measure the disk usage
// Example performance XCTest
/// The test measures the amount of data written to disk by the code in the block and shows the result within Xcode itself
/// You can set a baseline of the amount of data expected to be written to disk so that the test fails if the code in the block exceeds that.
func testSaveMeal() {
let app = XCUIApplication()
let options = XCTMeasureOptions()
options.invocationOptions = [.manuallyStart]
measure(metrics: [XCTStorageMetric(application: app)], options: options) {
app.launch()
startMeasuring()
let firstCell = app.cells.firstMatch
firstCell.buttons["Save meal"].firstMatch.tap()
let savedButton = firstCell.buttons["Saved"].firstMatch
XCTAssertTrue(savedButton.waitForExistence(timeout: 2))
}
}We can look for the sources of these writes by taking a look at Xcode Organizer
Disk WritesReportsthese are collection of reports that are generated when your app writes more than 1 GB in a 24-hour period
Launch time and Termination
Launch time is the amount of time between when the user taps your app icon and when the first frame gets rendered in your app
Process exits can happen for many different reasons, like hitting and exceeding the system memory limit or timing out on launch
we can profile app launch time by using the
App Launch templatein Instrumentsthis template runs the app for five seconds, during which it gathers a time profile and Thread State Trace of what was going on while the app was launching
we can also measure launch times in a performance XCTest by using the
XCTApplicationsLaunchMetricin a measure block.
Memory
profile the memory use by using the Leaks, Allocations, and VM Tracker templates in Instruments
Leaks will examine my process’s heap and check for leaked memory
Allocations will analyze the memory life cycle of my app
VM Tracker will show the virtual memory space of the app over time
