Skip to main content

Performance Monitoring

Performance Monitoring​

Performance monitoring helps you track, measure, and optimize your app's performance. Embrace provides tools to monitor various aspects of your app's performance and identify bottlenecks.

Performance Metrics​

You can track several types of performance metrics in your Apple app:

  • Duration metrics: How long operations take to complete
  • Resource usage: Memory and CPU consumption
  • Frequency metrics: How often operations occur
  • Custom performance metrics: Application-specific measurements

Tracking Operation Duration​

The most common performance metric is operation duration, which you can track using spans:

func loadData() {
let span = EmbraceIO.shared.createSpan(name: "data_loading")

// Perform data loading
let data = fetchDataFromCache()

span?.setAttribute(key: "data_size", value: "\(data.count)")
span?.end()
}

For more complex operations, create a hierarchy of spans:

func renderFeed() {
let renderSpan = EmbraceIO.shared.createSpan(name: "feed_rendering")

// 1. Load data
let dataSpan = EmbraceIO.shared.createSpan(name: "feed_data_loading", parentSpan: renderSpan)
let feedItems = loadFeedData()
dataSpan?.setAttribute(key: "item_count", value: "\(feedItems.count)")
dataSpan?.end()

// 2. Process images
let imageSpan = EmbraceIO.shared.createSpan(name: "feed_image_processing", parentSpan: renderSpan)
processFeedImages(feedItems)
imageSpan?.end()

// 3. Update UI
let uiSpan = EmbraceIO.shared.createSpan(name: "feed_ui_update", parentSpan: renderSpan)
updateUI(with: feedItems)
uiSpan?.end()

renderSpan?.end()
}

Custom Performance Events​

Custom events allow you to mark significant points in your code execution:

// Track when the app finishes initial loading
EmbraceIO.shared.log(
"App fully loaded",
severity: .info,
attributes: [
"initial_load_time_ms": "\(loadTimeInMilliseconds)",
"cached_resources": "\(cachedResourceCount)",
"network_resources": "\(networkResourceCount)"
]
)

Tracking Resource Usage​

You can track resource usage manually at key points:

func trackMemoryUsage(operation: String) {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4

let result: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}

if result == KERN_SUCCESS {
let usedMemory = Float(info.resident_size) / (1024 * 1024) // Convert to MB

EmbraceIO.shared.log(
"Memory usage checkpoint",
severity: .info,
attributes: [
"operation": operation,
"memory_used_mb": String(format: "%.2f", usedMemory)
]
)
}
}

// Usage at key points
trackMemoryUsage(operation: "after_image_processing")
trackMemoryUsage(operation: "after_data_loaded")

Performance Attributes​

Add performance attributes to spans to provide more context:

func loadUserProfile() {
let span = EmbraceIO.shared.createSpan(name: "user_profile_loading")

// Set attributes that impact performance
span?.setAttribute(key: "cache_state", value: isCached ? "hit" : "miss")
span?.setAttribute(key: "image_count", value: "\(profile.images.count)")
span?.setAttribute(key: "connection_type", value: networkType.rawValue)

// Load the profile
let profile = fetchUserProfile()

// Add result attributes
span?.setAttribute(key: "profile_size_kb", value: "\(profileSizeInKB)")
span?.setAttribute(key: "loading_time_ms", value: "\(loadingTimeInMS)")

span?.end()
}

Monitoring Critical User Flows​

Track performance across user flows by linking spans:

func checkoutProcess() {
// 1. Start the main checkout span
let checkoutSpan = EmbraceIO.shared.createSpan(name: "checkout_flow")

// 2. Cart validation
validateCart { [weak self] success in
if success {
// 3. Payment processing
self?.processPayment { paymentSuccess in
if paymentSuccess {
// 4. Order confirmation
self?.confirmOrder { orderSuccess in
if orderSuccess {
checkoutSpan?.setAttribute(key: "checkout_result", value: "success")
} else {
checkoutSpan?.setAttribute(key: "checkout_result", value: "failed_at_confirmation")
}
checkoutSpan?.end()
}
} else {
checkoutSpan?.setAttribute(key: "checkout_result", value: "failed_at_payment")
checkoutSpan?.end()
}
}
} else {
checkoutSpan?.setAttribute(key: "checkout_result", value: "failed_at_validation")
checkoutSpan?.end()
}
}
}

// Helper methods with their own spans
func validateCart(completion: @escaping (Bool) -> Void) {
let span = EmbraceIO.shared.createSpan(name: "cart_validation")

// Validation logic
let isValid = performCartValidation()

span?.setAttribute(key: "cart_valid", value: isValid ? "true" : "false")
span?.end()

completion(isValid)
}

func processPayment(completion: @escaping (Bool) -> Void) {
let span = EmbraceIO.shared.createSpan(name: "payment_processing")

// Payment logic
performPayment { success in
span?.setAttribute(key: "payment_success", value: success ? "true" : "false")
span?.end()

completion(success)
}
}

func confirmOrder(completion: @escaping (Bool) -> Void) {
let span = EmbraceIO.shared.createSpan(name: "order_confirmation")

// Order confirmation logic
confirmOrderOnServer { success in
span?.setAttribute(key: "confirmation_success", value: success ? "true" : "false")
span?.end()

completion(success)
}
}

Tracking UI Performance​

Monitor UI rendering and user interaction performance:

class ProductListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
EmbraceIO.shared.log("ProductList viewDidLoad started", severity: .info)

// Setup UI
setupUI()

EmbraceIO.shared.log("ProductList viewDidLoad completed", severity: .info)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

let span = EmbraceIO.shared.createSpan(name: "product_list_loading")

// Load data
loadProducts { [weak self, weak span] products in
self?.updateUI(with: products)

span?.setAttribute(key: "product_count", value: "\(products.count)")
span?.end()
}
}

private func loadProducts(completion: @escaping ([Product]) -> Void) {
// Loading logic
}

private func updateUI(with products: [Product]) {
let span = EmbraceIO.shared.createSpan(name: "product_list_ui_update")

// UI update logic
collectionView.reloadData()

span?.end()
}
}

Custom Attributes for Performance Context​

Add custom attributes to provide context for performance analysis:

// Track device-specific attributes
EmbraceIO.shared.setProperty(
key: "device_performance_tier",
value: determineDevicePerformanceTier(), // e.g., "high", "medium", "low"
lifespan: .userSession
)

// Track user-specific attributes that might affect performance
if let userSubscriptionTier = userManager.currentUser?.subscriptionTier {
EmbraceIO.shared.setProperty(key: "subscription_tier", value: userSubscriptionTier, lifespan: .userSession)
}

// Track app configuration that impacts performance
EmbraceIO.shared.setProperty(key: "image_quality", value: appSettings.imageQuality.rawValue, lifespan: .userSession)
EmbraceIO.shared.setProperty(key: "animations_enabled", value: appSettings.animationsEnabled ? "true" : "false", lifespan: .userSession)

Performance Optimization Best Practices​

Identify Performance Regressions​

Use custom events to track performance metrics over time:

func trackStartupPerformance() {
let metrics = collectStartupMetrics()

EmbraceIO.shared.log(
"App startup completed",
severity: .info,
attributes: [
"time_to_interactive_ms": "\(metrics.timeToInteractive)",
"initialization_time_ms": "\(metrics.initializationTime)",
"resource_loading_time_ms": "\(metrics.resourceLoadingTime)",
"first_frame_time_ms": "\(metrics.firstFrameTime)"
]
)
}

Correlate Performance with User Experience​

Track how performance impacts user experience:

func trackScrollPerformance() {
let scrollSpan = EmbraceIO.shared.createSpan(name: "feed_scrolling")

var dropFrameCount = 0
let fpsCalculator = FPSCalculator()

// Start monitoring scroll performance
fpsCalculator.start()

// When scrolling ends
scrollDidEnd { [weak scrollSpan] in
let performanceMetrics = fpsCalculator.stop()

scrollSpan?.setAttribute(key: "avg_fps", value: String(format: "%.2f", performanceMetrics.averageFPS))
scrollSpan?.setAttribute(key: "min_fps", value: String(format: "%.2f", performanceMetrics.minFPS))
scrollSpan?.setAttribute(key: "max_fps", value: String(format: "%.2f", performanceMetrics.maxFPS))
scrollSpan?.setAttribute(key: "dropped_frames", value: "\(performanceMetrics.droppedFrames)")

scrollSpan?.end()
}
}