美文网首页
App性能数据获取(一)

App性能数据获取(一)

作者: Boothlee | 来源:发表于2019-05-20 18:29 被阅读0次

    一、启动时间规划

    启动时间包括两部分: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<NSObject> 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性能数据获取(一)

          本文链接:https://www.haomeiwen.com/subject/jldlzqtx.html