App性能数据获取(一)

一、启动时间规划

启动时间包括两部分:Launch Time = Pre-main Time + Loading Time

  • Pre-main Time 指 main 函数执行之前的加载时间,包括 dylib 动态库加载,Mach-O 文件加载,Rebase/Binding,Objective-C Runtime 加载等;

  • Loading Time 指 main 函数开始执行到 AppDelegate 的 applicationDidBecomeActive: 回调方法执行(App 被激活)的时间间隔,这个时间包含了的 App 启动时各初始化项的执行时间(一般写在 application:didFinishLaunchingWithOptions: 方法里)

1.冷启动统计方案:

t1(创建进程 - main) + t2(main - didFinishLaunching) + t3(自定义时间)

SDK的设计上,进程创建时间戳的获取:

@implementation LTLoadTimeManager
+ (NSTimeInterval)processStartTime {
    struct kinfo_proc kProcInfo;
    if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) {
        return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
    } else {
        NSAssert(NO, @"无法取得进程的信息");
        return 0;
    }
}

+ (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo {
    int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    size_t size = sizeof(*procInfo);
    return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0) == 0;
}
@end

didFinishLaunching时间戳获取可以做到业务无感,常规获取方案如下:

+ (void)load {
    NSLog(@"时间(load 执行时间)%f",[[NSDate date] timeIntervalSince1970] * 1000);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            __block id obs;
            obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
                                                                    object:nil queue:[NSOperationQueue mainQueue]
                                                                usingBlock:^(NSNotification *note) {
                                                                    
                                                                    NSTimeInterval didFinishLaunching = [[NSDate date] timeIntervalSince1970] * 1000;//毫秒
                                                                   
                                                                    NSTimeInterval app_start_time =  [LTLoadTimeManager processStartTime];
                                                                    NSLog(@"时间(创建进程 毫秒)%f",app_start_time);
                                                                    NSLog(@"时间(main start毫秒)%f",main_start_time);
                                                                    NSLog(@"时间(didFinishLaunching 毫秒):%f",didFinishLaunching);
                                                                    NSLog(@"时间间隔(创建进程->main)%f",main_start_time - app_start_time);
                                                                    NSLog(@"时间间隔(main->didFinishLaunching)%f",didFinishLaunching - main_start_time);
                                                                    NSLog(@"时间间隔(创建进程->didFinishLaunching 毫秒):%f",didFinishLaunching - app_start_time);
                                                                    ///MARK:记录冷启动时间
                                                                    k_lucky_cold_start_time_interval = didFinishLaunching - app_start_time;
                                                                    //移除观察者
                                                                    [[NSNotificationCenter defaultCenter] removeObserver:obs];
                                                                }];
            
            [self recordHotStart];///MARK:记录热启动
            
        }
    });
}

以上 k_lucky_cold_start_time_interval 即为创建进程到didFinishLaunching的时长 即t1 + t2
对于t3的定义,我们决定暴露给外部。你可以在首页加载完成时调用,也可以在首页网络请求完成刷新UI调用,这都取决于你的选择:

///MARK:自定义启动结束节点
+ (NSTimeInterval)recordCustomLuanchedTime {
    NSTimeInterval user_custom_time = [[NSDate date] timeIntervalSince1970] * 1000;//毫秒
    NSTimeInterval start_time =  [LTLoadTimeManager __processStartTime];
    k_lucky_cold_start_time_interval = user_custom_time - start_time;
    return k_lucky_cold_start_time_interval;
}

2.热启动统计方案:

applicationWillEnterForegroundapplicationDidBecomeActive之间的时间

///MARK:热启动
+ (void)recordHotStart {
    
    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        k_lucky_hot_begin_time = [[NSDate date] timeIntervalSince1970] * 1000;
    }];

    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        k_lucky_hot_end_time = [[NSDate date] timeIntervalSince1970] * 1000;
        if (k_lucky_hot_begin_time > 0) {
            k_lucky_hot_start_time_interval = k_lucky_hot_end_time - k_lucky_hot_begin_time;
            NSLog(@"热启动时长:%f 毫秒",k_lucky_hot_start_time_interval);
        }
    }];
}

以上k_lucky_hot_end_time即为热启动时长

二.页面加载时间:

页面加载时长,这里采取的方案是viewWillAppear - viewDidAppear的时长。可以在viewWillappear方法和viweDidAppear中获取对应的时间戳计算时长。也可以通过hook UIViewController做到无痕获取。方法比较简单,具体代码就不贴出来了。

三.CPU使用率:

与 Mac OS X 类似,iOS 的线程技术也是基于 Mach 线程技术实现的,在 Mach 层中 thread_basic_info 结构体提供了线程的基本信息。

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 in effect */
        integer_t       run_state;      /* run state (see below) */
        integer_t       flags;          /* various flags (see below) */
        integer_t       suspend_count;  /* suspend count for thread */
        integer_t       sleep_time;     /* number of seconds that thread
                                           has been sleeping */
};

一个 task 包含它的线程列表。内核提供了 task_threads API 调用获取指定 task 的线程列表,然后可以通过 thread_info API 调用来查询指定线程的信息,thread_info API 在 thread_act.h 中定义。

kern_return_t task_threads
(
    task_t target_task,
    thread_act_array_t *act_list,
    mach_msg_type_number_t *act_listCnt
);

task_threads 将 target_task 任务中的所有线程保存在 act_list 数组中,数组中包含 act_listCnt 个条目。

kern_return_t thread_info
(
    thread_act_t target_act,
    thread_flavor_t flavor,
    thread_info_t thread_info_out,
    mach_msg_type_number_t *thread_info_outCnt
);

thread_info 查询 flavor 指定的 thread 信息,将信息返回到长度为 thread_info_outCnt 字节的 thread_info_out 缓存区中。
具体实现如下:

+ (double)getCpuUsage {
    kern_return_t           kr;
    thread_array_t          threadList;         // 保存当前Mach task的线程列表
    mach_msg_type_number_t  threadCount;        // 保存当前Mach task的线程个数
    thread_info_data_t      threadInfo;         // 保存单个线程的信息列表
    mach_msg_type_number_t  threadInfoCount;    // 保存当前线程的信息列表大小
    thread_basic_info_t     threadBasicInfo;    // 线程的基本信息
    
    // 通过“task_threads”API调用获取指定 task 的线程列表
    //  mach_task_self_,表示获取当前的 Mach task
    kr = task_threads(mach_task_self(), &threadList, &threadCount);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    double cpuUsage = 0;
    for (int i = 0; i < threadCount; i++) {
        threadInfoCount = THREAD_INFO_MAX;
        // 通过“thread_info”API调用来查询指定线程的信息
        //  flavor参数传的是THREAD_BASIC_INFO,使用这个类型会返回线程的基本信息,
        //  定义在 thread_basic_info_t 结构体,包含了用户和系统的运行时间、运行状态和调度优先级等
        kr = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        threadBasicInfo = (thread_basic_info_t)threadInfo;
        if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) {
            cpuUsage += threadBasicInfo->cpu_usage;
        }
    }
    
    // 回收内存,防止内存泄漏
    vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t));

    return cpuUsage / (double)TH_USAGE_SCALE * 100.0;
}

其它性能数据待续...

参考定义
https://www.jianshu.com/p/baf324a2f40d

你可能感兴趣的:(App性能数据获取(一))