Step-by-step instructions for adding performance monitoring to your iOS projects.
The BugSnag Performance iOS integration automatically instruments app starts, UIKit screen loads and network requests. SwiftUI view loads and arbitrary operations can also be manually instrumented in your code.
New to BugSnag? Create an account
Looking for error monitoring? See our integration guide
Add the BugsnagPerformance
pod to your Podfile
:
pod 'BugsnagPerformance'
Run pod install
after updating your Podfile
.
Open your Xcode project and select File → Add Packages…
Search for https://github.com/bugsnag/bugsnag-cocoa-performance
as the package URL
Set Dependency Rule exact version
to v1.10.2
, then click Add Package.
Clone the BugSnag Performance GitHub repository into a local directory providing the version tag
after --branch
:
git clone --branch v1.10.2 https://github.com/bugsnag/bugsnag-cocoa-performance
Drag BugsnagPerformance.xcodeproj
into your Xcode workspace.
Select your project in the Project Navigator and in the project editor that appears, select your app’s target.
Under the General tab, click the Frameworks, Libraries and Embedded Content section’s +
button and select BugsnagPerformance.framework
(from BugsnagPerformance
).
The latest available version of bugsnag-cocoa-performance
is v1.10.2
.
Configure your API key by adding a bugsnag
Dictionary to your Info.plist
file:
Or in XML:
<key>bugsnag</key>
<dict>
<key>apiKey</key>
<string>YOUR-API-KEY</string>
</dict>
This is the same API key and the same plist key used by the BugSnag Error Monitoring library.
You can find your API key in Project Settings from your BugSnag dashboard.
If your app implements an app delegate, import the BugsnagPerformance
module and initialize BugsnagPerformance in the application:didFinishLaunchingWithOptions:
method:
#import <BugsnagPerformance/BugsnagPerformance.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[BugsnagPerformance start];
return YES;
}
import BugsnagPerformance
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BugsnagPerformance.start()
return true
}
}
Like us, some other monitoring vendor SDKs make use of the Activity Tracing API. This API does not play well with multiple consumers. Attempting to use BugSnag Performance alongside one of these other SDKs may have unwanted impact on the data collected.
BugSnag will automatically detect and report app starts, measuring them from the process start time for the app until the UIApplicationDidBecomeActiveNotification
is received. These timings are shown under the “App starts” tab in the BugSnag Performance dashboard.
The instrumentation determines whether the app is being launched for the first time since installation or reboot and so is a full “cold” start or is otherwise a “warm” start. These two app start types are displayed separately on your BugSnag dashboard.
To disable automatic instrumentation, set the autoInstrumentAppStarts
configuration option:
BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentAppStarts = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentAppStarts = false
BugsnagPerformance.start(configuration: config)
BugSnag will automatically detect and report UIViewController
loads, measuring the time taken by any controllers in your app between loadView
and viewDidAppear
being called. These timings are shown under the “Screen loads” tab in the BugSnag Performance dashboard.
See our SwiftUI guide for instructions on setting up instrumentation for SwiftUI based apps.
Automatic capture of view loads is enabled by default and can be disabled using the autoInstrumentViewControllers
configuration option:
BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentViewControllers = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentViewControllers = false
BugsnagPerformance.start(configuration: config)
This instrumentation relies on Objective-C method “swizzling” and is injected early in your app’s startup code. See our guide on Method swizzling usage for full details on which methods are affected and how it can be fully disabled if required.
If you want control over which views are automatically instrumented, you can implement a viewControllerInstrumentationCallback
:
BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.viewControllerInstrumentationCallback = ^(UIViewController *viewController){
return ![viewController isKindOfClass:[IgnoredViewController class]];
};
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.viewControllerInstrumentationCallback = { viewController in
!(viewController is IgnoredViewController)
}
BugsnagPerformance.start(configuration: config)
When the callback returns false
, the view will not be recorded. This also allows you to manually instrument the view in order to control the start and end of the measurement yourself.
Our automatic instrumentation does not include “container” view controllers (e.g. UITabViewController
) as these don’t present any actual view themselves and so any performance impact of these views is out of a developer’s control. If you have custom container view controllers, these will be picked up in the automatic instrumentation and we recommend you exclude these using the above callback.
In some cases, such as for screens in a UITabViewController
, views will be “pre-loaded” by the OS before a user has navigated to them and made them visible. This results in a faster render if the view is later loaded, but presents some challenges for presenting and understanding measurements taken as they can occur a long time before the view load completes. Our instrumentation attempts to detect that a pre-load has occurred and adjusts the timings, marking the resulting span on your dashboard with “(pre-loaded)” to indicate that the span duration is quicker than it would otherwise have been.
If your app uses a UIViewController
that is a Swift generic class, it is not possible to intercept the events necessary to measure the load period. Such view controllers can be instrumented manually using the bugsnagTraced
API in the BugsnagPerformanceSwift
package.
For example, with the following generic declaration:
class ViewControllerFromGeneric<T>: UIViewController {
override func viewDidLoad() {
// ...
}
}
class SubclassedViewControllerFromGeneric: ViewControllerFromGeneric<AppConcreteClass> {
// ...
}
The bugsnagTraced
method should be used on each instance of the generic that you want to measure:
import BugsnagPerformanceSwift
@IBAction func showView(_ sender: Any) {
let vc = ViewControllerFromGeneric<AppConcreteClass>().bugsnagTraced("an optional name")
// or:
// let vc = SubclassedViewControllerFromGeneric().bugsnagTraced("an optional name")
show(vc, sender: sender)
}
If your app uses SwiftUI, view load measurements can be easily captured on your BugSnag dashboard using our SwiftUI package. See our SwiftUI guide for installation and usage instructions.
To instrument view loads manually, call startViewLoadSpan
with a view name and type:
- (void)viewDidLoad {
self.span = [BugsnagPerformance
startViewLoadSpanWithName:@"MyViewController"
viewType:BugsnagPerformanceViewTypeUIKit];
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.span end];
}
override func viewDidLoad() {
span = BugsnagPerformance.startViewLoadSpan(
name: "MyViewController", viewType: .uiKit)
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated);
span?.end()
span = nil
}
BugSnag will automatically detect and report network requests in your app made through URLSession
. These timings are shown under the “Network requests” tab in the BugSnag Performance dashboard.
Automatic capture of network requests is enabled by default and can be disabled using the autoInstrumentNetworkRequests
configuration option:
BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
config.autoInstrumentNetworkRequests = NO;
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.autoInstrumentNetworkRequests = false
BugsnagPerformance.start(configuration: config)
This instrumentation relies on Objective-C method “swizzling” and is injected early in your app’s startup code. See our guide on Method swizzling usage for full details on which methods are affected and how it can be fully disabled if required.
To instrument network requests manually, call reportNetworkRequestSpan
from your
URLSessionTaskDelegate
URLSession:task:didFinishCollectingMetrics
method:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
[BugsnagPerformance reportNetworkRequestSpanWithTask:task metrics:metrics];
}
func urlSession(_ session: URLSession, task: URLSessionTask,
didFinishCollecting metrics: URLSessionTaskMetrics) {
BugsnagPerformance.reportNetworkRequestSpan(task: task, metrics: metrics)
}
The networkRequestCallback
configuration option allows you to control the data sent in these network request spans using a callback.
To send custom spans to BugSnag for any other operations you wish to measure, use BugsnagPerformance.startSpan
to start a span, and call the span’s end
method to end the measurement:
BugsnagPerformanceSpan *span = [BugsnagPerformance startSpanWithName:@"login"];
[api login:^(BOOL success){
[span end];
}];
let span = BugsnagPerformance.startSpan(name: "login")
api.login { _ in
span.end()
}
The options
parameter allows you to customize some elements of the span’s behavior:
If a custom span is “first class”, its performance characteristics will be aggregated and shown in the “Custom” tab of the BugSnag dashboard. If the custom span is useful only for adding insight into the performance of its parent (through the waterfall diagram on the span instance page), you should set the isFirstClass
span option to false
:
BugsnagPerformanceSpanOptions *options = [BugsnagPerformanceSpanOptions new];
options.firstClass = BSGFirstClassNo;
[BugsnagPerformance startSpanWithName:@"always-nested" options:options];
BugsnagPerformance.startSpan(
name: "always-nested",
options: BugsnagPerformanceSpanOptions.setFirstClass(BSGFirstClass.no))
When viewing a single instance of a span in the BugSnag dashboard, a waterfall diagram will show you that instance’s children and their children, and so on. This allows you to see in more detail where the time was spent, and aid diagnosing and fixing any problems. Usually this will be automatically managed by BugSnag, however when creating your own spans, you can use the options
parameter to control their parent-child relationships to produce a meaningfully representative hierarchy in your dashboard. See Maintaining span context for more information.
By default, a span will use the current timestamp as its start time. However, you can use the startTime
span option to report spans that have already started by providing your own timestamp. You can also end a span with your own timestamp to provide a retrospective end time:
NSDate *taskStartTime = [NSDate date];
// When the task has completed:
NSDate *taskEndTime = [NSDate date];
BugsnagPerformanceSpanOptions *options = [BugsnagPerformanceSpanOptions new];
options.startTime = taskStartTime;
BugsnagPerformanceSpan *span1 = [BugsnagPerformance startSpanWithName:@"retrospective-span"
options:options];
[span1 endWithEndTime:taskEndTime];
let taskStartTime = Date()
// When the task has completed:
let taskEndTime = Date()
BugsnagPerformance.startSpan(
name: "retrospective-span",
options: BugsnagPerformanceSpanOptions.setStartTime(taskStartTime))
.end(endTime: taskEndTime)
Additional data can be added to spans to help diagnose performance issues. These are sent as span attributes and will be displayed on your Performance dashboard alongside the automatic span data captured by the SDK.
Attributes are added via a span object and are name value pairs where the type can be either a string, integer, double, boolean or an array of one of these types:
BugsnagPerformanceSpan *span = [BugsnagPerformance startSpanWithName:@"span-with-data"];
[span setAttribute:@"api.protocol" withValue:@"gql"];
[span setAttribute:@"api.version" withValue:@"v2"];
let span = BugsnagPerformance.startSpan(name: "span-with-data")
span.setAttribute("api.protocol", withValue: "gql")
span.setAttribute("api.version", withValue: "v2")
You can use a callback to set attributes when they are ending. This allows you to access all spans being generated, including those from automatic instrumentation:
BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
[config addOnSpanEndCallback:^BOOL(BugsnagPerformanceSpan * _Nonnull span) {
[span setAttribute:@"device.locale" withValue: @"en-US"];
return true;
}];
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.add { span in
span.setAttribute("device.locale", withValue: "en-US");
return true;
}
BugsnagPerformance.start(configuration: config)
The number and size of attributes added to spans are limited by the SDK to avoid oversized span payloads that would be rejected by the BugSnag API. See the Custom attribute limit configuration options to see how this can be customized if more data is required.
These callbacks can be executed with a high frequency, so care should be taken not to perform complex operations that will have a detrimental effect on performance. For more information, see the span callbacks configuration option.