Mobile Development 26 min read

Implementing iOS Performance Monitoring: CPU, Memory, FPS, Startup Time, and Power Consumption

This article details the design and implementation of an iOS performance‑monitoring SDK that captures fundamental metrics such as CPU usage, memory consumption, frame rate, cold and hot startup times, and power draw, explains the underlying Mach APIs, provides sample Objective‑C code, and discusses practical considerations for accurate measurement and troubleshooting.

Hujiang Technology
Hujiang Technology
Hujiang Technology
Implementing iOS Performance Monitoring: CPU, Memory, FPS, Startup Time, and Power Consumption

Why This Article Was Written

As mobile applications become more performance‑sensitive, developers need reliable ways to detect issues like connection timeouts, crashes, freezes, high CPU usage, memory leaks, and poor UI responsiveness. Traditional reactive debugging based on user feedback is inefficient, so a dedicated performance‑monitoring SDK is essential for proactive optimization.

Project Name Origin

The SDK is codenamed Wedjat (华狄特), referencing the Egyptian god Horus’s eye, symbolizing justice and the ability to see all actions—an apt metaphor for a tool that watches every performance‑related event.

CPU Monitoring

CPU is the most critical resource on a mobile device; excessive usage leads to stutter and battery drain. By enumerating all threads of the current process and aggregating their thread_basic_info , the SDK can compute the overall CPU usage percentage.

struct thread_basic_info {
    time_value_t user_time;   /* user run time */
    time_value_t system_time; /* system run time */
    integer_t    cpu_usage;   /* scaled cpu usage percentage */
    policy_t     policy;      /* scheduling policy */
    integer_t    run_state;   /* run state */
    integer_t    flags;       /* various flags */
    integer_t    suspend_count; /* suspend count */
    integer_t    sleep_time;  /* seconds thread has been sleeping */
};

The implementation calls task_threads to obtain the thread list, then iterates with thread_info (THREAD_BASIC_INFO) to sum user and system times and calculate the percentage. The final step deallocates the thread list with vm_deallocate to avoid leaks.

#import
#import
float cpu_usage() {
    kern_return_t kr;
    task_info_data_t tinfo;
    mach_msg_type_number_t task_info_count = TASK_INFO_MAX;
    kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
    if (kr != KERN_SUCCESS) return -1;
    task_basic_info_t basic_info = (task_basic_info_t)tinfo;
    thread_array_t thread_list;
    mach_msg_type_number_t thread_count;
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) return -1;
    float tot_cpu = 0;
    for (int i = 0; i < (int)thread_count; i++) {
        thread_info_data_t thinfo;
        mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) return -1;
        thread_basic_info_t basic_info_th = (thread_basic_info_t)thinfo;
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            tot_cpu += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
        }
    }
    vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    return tot_cpu;
}

Memory Monitoring

Memory is a scarce resource on iOS; the mach_task_basic_info structure provides resident_size (physical memory) and virtual_size . The SDK queries this via task_info with the MACH_TASK_BASIC_INFO flavor.

struct mach_task_basic_info {
    mach_vm_size_t virtual_size;   /* virtual memory size (bytes) */
    mach_vm_size_t resident_size;  /* resident memory size (bytes) */
    mach_vm_size_t resident_size_max;
    time_value_t   user_time;
    time_value_t   system_time;
    policy_t       policy;
    integer_t      suspend_count;
};
NSUInteger getResidentMemory() {
    struct mach_task_basic_info info;
    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
    int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count);
    if (r == KERN_SUCCESS) {
        return info.resident_size;
    } else {
        return -1;
    }
}

Startup Time Measurement

Startup time is split into cold start (pre‑main loading) and hot start (post‑launch resume). The article shows two approaches: recording a timestamp at the beginning of main() and computing the delta in applicationDidFinishLaunching:withOptions: , or using mach_absolute_time() inside a +load method and listening for UIApplicationDidFinishLaunchingNotification to obtain a high‑precision measurement.

CFAbsoluteTime StartTime;
int main(int argc, char *argv[]) {
    @autoreleasepool {
        StartTime = CFAbsoluteTimeGetCurrent();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
// In applicationDidFinishLaunching:
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});

FPS Monitoring

Frames‑per‑second is measured using a CADisplayLink that fires each screen refresh. The demo counts frames over a one‑second interval and computes FPS. This method reflects the run‑loop rate rather than the true Core Animation performance.

@implementation YYFPSLabel {
    CADisplayLink *_link;
    NSUInteger _count;
    NSTimeInterval _lastTime;
}
- (id)init {
    self = [super init];
    if (self) {
        _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
        [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
    return self;
}
- (void)tick:(CADisplayLink *)link {
    if (_lastTime == 0) { _lastTime = link.timestamp; return; }
    _count++;
    NSTimeInterval delta = link.timestamp - _lastTime;
    if (delta < 1) return;
    float fps = _count / delta;
    _count = 0;
    _lastTime = link.timestamp;
    // display fps …
}
@end

Freezing / Lag Detection

Lag occurs when CPU or GPU cannot finish work before the next V‑Sync (≈16.67 ms). Two common detection strategies are FPS monitoring and run‑loop stall detection. The article presents a run‑loop observer that records kCFRunLoopBeforeSources and kCFRunLoopAfterWaiting intervals; if the interval exceeds a threshold repeatedly, a lag event is reported.

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    MyClass *object = (__bridge MyClass *)info;
    object->activity = activity;
    dispatch_semaphore_signal(object->semaphore);
}
- (void)registerObserver {
    CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    self->semaphore = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0,0), ^{
        while (YES) {
            long st = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
            if (st != 0 && (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)) {
                if (++self->timeoutCount >= 5) {
                    // report lag
                }
            } else {
                self->timeoutCount = 0;
            }
        }
    });
}

Power‑Consumption Monitoring

Three approaches are compared: the public UIDevice API (simple but coarse), the private IOKit power‑source API (more precise, 1 % granularity), and a jailbreak‑only method using iOSDiagnosticsSupport to read hourly energy logs. Sample IOKit code extracts current and maximum capacity from the power‑source dictionary and computes a percentage.

- (double)getBatteryLevel {
    CFTypeRef blob = IOPSCopyPowerSourcesInfo();
    CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
    int num = CFArrayGetCount(sources);
    if (num == 0) return -1.0;
    for (int i = 0; i < num; i++) {
        CFDictionaryRef pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, i));
        if (!pSource) return -1.0;
        int cur, max;
        CFNumberGetValue(CFDictionaryGetValue(pSource, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberSInt32Type, &cur);
        CFNumberGetValue(CFDictionaryGetValue(pSource, CFSTR(kIOPSMaxCapacityKey)), kCFNumberSInt32Type, &max);
        double percent = (double)cur / (double)max * 100.0;
        NSLog(@"curCapacity : %d / maxCapacity: %d , percentage: %.1f ", cur, max, percent);
        return percent;
    }
    return -1.0;
}

Wrap Up

The first part of the series explains how to collect core iOS performance metrics—CPU usage, memory footprint, FPS, cold/hot launch times, and power consumption—using low‑level Mach APIs and public frameworks. The next article will focus on network‑request monitoring, and the author notes that power‑consumption measurement still lacks a perfect solution, leaving room for further research.

References

Mac OS X and iOS Internals: To the Apple’s Core

OS X and iOS Kernel Programming

Handling low memory conditions in iOS and Mavericks

iOS-System-Services

GT

Optimizing Facebook for iOS start time

Apple Documentation: Strategies for Handling App State Transitions

iOS time handling articles

StartupMeasurer

Frame rate (Wikipedia)

YYText

Mobile performance monitoring solution Hertz

iOS smooth UI techniques

WeChat Reader iOS performance summary

PLCrashReporter

iOS‑Diagnostics

Performance visualization practice

iOSperformance monitoringStartup TimeFPSPower ConsumptionMemory usageCPU usage
Hujiang Technology
Written by

Hujiang Technology

We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.