Production Performance Monitoring for Mobile Apps
You can't run Xcode Instruments in CI, and users won't send allocation profiles. Production performance monitoring is a separate task with tools that work on the device, don't interfere with users, and send aggregated metrics to the server.
What We Measure and With What Tools
Firebase Performance Monitoring — zero barrier to entry. The SDK automatically measures: cold start time, HTTP requests (latency, payload size, success rate), screen render time. Add custom traces for any code:
// iOS — custom trace for critical operation
let trace = Performance.startTrace(name: "catalog_load")
trace.start()
catalogService.load { [weak self] result in
trace.stop() // metric goes to Firebase
self?.handleResult(result)
}
// Android
val trace = Firebase.performance.newTrace("catalog_load")
trace.start()
catalogRepository.load { result ->
trace.stop()
handleResult(result)
}
Sentry Performance — if Sentry is already used for crashes, Performance Monitoring enables without extra SDK. Provides distributed tracing: see not only operation time on client, but breakdown by API calls it generates.
Datadog RUM — for teams with existing Datadog infrastructure. Automatically tracks Session Replay (records user interactions), Frame Rate, Network requests with full stack trace.
Metrics That Actually Impact Retention
FPS in scrolling — one of the key perceived performance indicators. UITableView jerks on fast scroll due to synchronous JPEG decoding on main thread — classic problem. Measure via CADisplayLink and send p5 (percentage of frames below 60fps) to analytics:
// Simple FPS monitor
class FPSMonitor {
private var displayLink: CADisplayLink?
private var lastTimestamp: CFTimeInterval = 0
private var frameCount = 0
func start() {
displayLink = CADisplayLink(target: self, selector: #selector(tick))
displayLink?.add(to: .main, forMode: .common)
}
@objc private func tick(_ link: CADisplayLink) {
frameCount += 1
if link.timestamp - lastTimestamp >= 1.0 {
let fps = Double(frameCount) / (link.timestamp - lastTimestamp)
MetricsCollector.record("screen_fps", value: fps, screen: currentScreen)
frameCount = 0
lastTimestamp = link.timestamp
}
}
}
On Android — FrameMetricsAggregator from androidx.core gives detailed breakdown by rendering phases.
Memory warnings — iOS sends didReceiveMemoryWarning before killing the app. Log this event with current screen and allocated memory via task_info:
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil, queue: .main
) { _ in
Analytics.logEvent("memory_warning", parameters: [
"current_screen": AppRouter.currentScreen,
"memory_mb": memoryUsageMB()
])
}
Alerts and Thresholds
Set alerts in Firebase Performance or Datadog on:
- Cold start time > 3s (p75) — Apple recommends < 400ms to first frame
- HTTP error rate > 2%
- Screen render time > 500ms (p95)
- ANR rate (Android) > 0.1%
- App not responding (iOS watchdog kills) — via Crashlytics crash rate
Main thing: don't set alerts on too-low thresholds — alert fatigue kills response to real problems.
Scope of Work
- Firebase Performance / Sentry Performance / Datadog RUM integration
- Custom traces on key operations (loading, navigation, business processes)
- FPS monitoring and memory monitoring
- Alert setup with Slack/Telegram notifications
- Dashboard with key performance metrics
Timeframe
SDK integration + custom traces + alerts: 1–3 days. Cost calculated individually.







