bekkou68 の日記

Gogengo! や IT 技術など。

メモリ使用量・空きメモリ量・ユーザCPU時間・システムCPU時間をログで表示する【Objective-C】

はじめに

"Received memory warning"

ドキッとするエラーです。このエラーが出た場合や、予防したい場合はメモリと CPU をモニタリングするのがよいアプローチだと思います。

ゴール

5秒おきにメモリ使用量・空きメモリ量・ユーザCPU時間・システムCPU時間がログとして表示される。なお、ログは開発環境でのみ表示する。
アプリ内のView数も追加で表示しました(2013年10月11日)。

実装

システムをモニタリングするクラスを定義

SystemMonitor.h

#import <Foundation/Foundation.h>

@interface SystemMonitor : NSObject

+ (void)dump;

@end

SystemMonitor.m

#import "SystemMonitor.h"

#import <mach/mach.h>

// time_value_t型を msec に変換
#define tval2msec(tval) ((tval.seconds * 1000) + (tval.microseconds / 1000))

@interface SystemMonitor ()
@end

@implementation SystemMonitor

+ (void)dump
{
    DLog(
        @"[SystemMonitor] [Memory Use=%3u(MB) Free=%3u(MB)], [CPU User=%4.1lf(sec) System=%4.1lf(sec)], [#View=%3d]",
        [self getMemoryUse] / 1000 / 1000,
        [self getFreeMemory] / 1000 / 1000,
        [self getUserCPUTime] / 1000.0,
        [self getSystemCPUTime] / 1000.0,
        [self countViewsInApp]
    );
}

// メモリ使用量を取得(bytes)
+ (unsigned int)getMemoryUse
{
    struct task_basic_info basicInfo;
    mach_msg_type_number_t basicInfoCount = TASK_BASIC_INFO_COUNT;
    
    if (task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) != KERN_SUCCESS) {
        DLog(@"[SystemMonitor] %s", strerror(errno));
        
        return -1;
    }
	
    return basicInfo.resident_size;
}

// 空きメモリ量を取得(bytes)
+ (unsigned int)getFreeMemory
{
    mach_port_t hostPort;
    mach_msg_type_number_t hostSize;
    vm_size_t pagesize;
    
    hostPort = mach_host_self();
    hostSize = sizeof(vm_statistics_data_t) / sizeof(integer_t);
    host_page_size(hostPort, &pagesize);
    vm_statistics_data_t vmStat;
    
    if (host_statistics(hostPort, HOST_VM_INFO, (host_info_t)&vmStat, &hostSize) != KERN_SUCCESS) {
        DLog(@"[SystemMonitor] Failed to fetch vm statistics");
        
        return -1;
    }
    
    natural_t freeMemory = vmStat.free_count * pagesize;
    
    return (unsigned int)freeMemory;
}

// ユーザCPU時間を取得 (msec)
+ (unsigned int)getUserCPUTime
{
    struct task_thread_times_info threadTimesInfo;
    mach_msg_type_number_t threadTimesInfoCount = TASK_THREAD_TIMES_INFO_COUNT;
    kern_return_t status;
    
    status = task_info(current_task(), TASK_THREAD_TIMES_INFO, (task_info_t)&threadTimesInfo, &threadTimesInfoCount);
    
    if (status != KERN_SUCCESS) {
        DLog(@"[SystemMonitor] %s(): Error in task_info(): %s", __FUNCTION__, strerror(errno));
        
        return -1;
    }
    
    return tval2msec(threadTimesInfo.user_time);
}

// システムCPU時間を取得(msec)
+ (unsigned int)getSystemCPUTime
{
    struct task_thread_times_info threadTimesInfo;
    mach_msg_type_number_t threadTimesInfoCount = TASK_THREAD_TIMES_INFO_COUNT;
    kern_return_t status;
    
    status = task_info(current_task(), TASK_THREAD_TIMES_INFO, (task_info_t)&threadTimesInfo, &threadTimesInfoCount);
    
    if (status != KERN_SUCCESS) {
        DLog(@"[SystemMonitor] %s(): Error in task_info(): %s", __FUNCTION__, strerror(errno));
        
        return -1;
    }
    
    return tval2msec(threadTimesInfo.system_time);
}

// アプリ内の View数
+ (int)countViewsInApp
{
    int num = 0;
    
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
        for (UIView *aView in [window subviews]) {
            num++; // parent
            num += [self countSubviews:aView];
        }
    }
    
    return num;
}

// View の subview数
+ (int)countSubviews:(UIView *)view
{
    int num = 0;
    
    for (UIView *subview in [view subviews]) {
        num++;
        if ([[subview subviews] count] > 0) {
            num += [self countSubviews:subview];
        }
    }
    
    return num;
}

@end
クラスの使い方

AppDelegate.m

#import "SystemMonitor.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // ... (snip) ...
#ifdef DEBUG
    [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(dumpSystemStatus:) userInfo:nil repeats:YES];
#endif
    // ... (snip) ...
}

- (void)dumpSystemStatus:(NSTimer*)timer
{
    [SystemMonitor dump];
}

// ... (snip) ...

@end

実行結果

こんな感じで出力されます。
最初はメモリ使用量は少なく、CPU時間は短いです。途中で負荷のかかる処理をしました。メモリ使用量が増えたり、メモリが確保されて空きメモリ量が増えたり、ユーザCPU時間が増えたりする様子がわかります。

2013-06-07 01:12:44.106 myapp[17790:707] [SystemMonitor] [Memory Use= 10(MB) Free=  6(MB)], [CPU User= 0.7(sec) System= 0.0(sec)], [#View= 61]
2013-06-07 01:12:49.106 myapp[17790:707] [SystemMonitor] [Memory Use=  9(MB) Free=  7(MB)], [CPU User= 0.5(sec) System= 0.0(sec)], [#View= 61]
2013-06-07 01:12:54.105 myapp[17790:707] [SystemMonitor] [Memory Use= 14(MB) Free= 11(MB)], [CPU User= 0.9(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:12:59.106 myapp[17790:707] [SystemMonitor] [Memory Use= 14(MB) Free= 12(MB)], [CPU User= 1.3(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:04.105 myapp[17790:707] [SystemMonitor] [Memory Use= 12(MB) Free= 10(MB)], [CPU User= 1.6(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:13.752 myapp[17790:707] [SystemMonitor] [Memory Use= 30(MB) Free=  7(MB)], [CPU User= 6.6(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:14.162 myapp[17790:707] [SystemMonitor] [Memory Use= 30(MB) Free= 32(MB)], [CPU User= 6.8(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:19.241 myapp[17790:707] [SystemMonitor] [Memory Use= 35(MB) Free= 17(MB)], [CPU User= 9.9(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:24.105 myapp[17790:707] [SystemMonitor] [Memory Use= 42(MB) Free= 18(MB)], [CPU User=13.2(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:29.358 myapp[17790:707] [SystemMonitor] [Memory Use= 49(MB) Free= 23(MB)], [CPU User=15.1(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:35.105 myapp[17790:707] [SystemMonitor] [Memory Use= 59(MB) Free= 15(MB)], [CPU User=19.7(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:39.125 myapp[17790:707] [SystemMonitor] [Memory Use= 57(MB) Free= 20(MB)], [CPU User=22.3(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:44.613 myapp[17790:707] [SystemMonitor] [Memory Use= 59(MB) Free= 13(MB)], [CPU User=26.7(sec) System= 0.0(sec)], [#View= 68]
2013-06-07 01:13:49.106 myapp[17790:707] [SystemMonitor] [Memory Use= 61(MB) Free= 35(MB)], [CPU User=28.8(sec) System= 0.0(sec)], [#View= 68]

5秒おきにしておけば、メモリ使用量の肥大化は十分見つけることができると思います。タイミングが際どいときは間隔を短くするとやりやすいと思います。

関連エントリ

ちなみに普段は NSLog は使わず開発環境だけで表示する DLog を定義してつかっています。