Keke Yezi

Better Late Than Never

我是叶斌 (Kekeyezi) 就职国泰君安.


Coding & Game & 初级奶爸

iOS App Optimization series 4 耗时统计

对于耗时统计作用最有价值的莫过于以下几点

关于启动时间优化之前有文章分析过,如果没有心情细看可以直接使用最终方案。

起点时间

+ (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;
}

+ (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;
    }
}

终点时间

+ (void)load {
    @autoreleasepool {
        __block id<NSObject> obs;
        obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification
                                                                object:nil queue:nil
                                                            usingBlock:^(NSNotification *note) {
                                                                [self startUp];
                                                                [[NSNotificationCenter defaultCenter] removeObserver:obs];
                                                            }];
    }
}

两边的时间差就是一定意义上的启动时间,可以用作统计分析。ps:release模式下才有意义

  • 监控各个业务是否给用户带来卡顿的感觉

​ 其实就是用户点开页面的响应时间了。从我点击要进行跳转到用户见到他想要的界面并且能够操作 这段时间如果越快就给人一种越流畅的感觉。那么这么时间应该要怎么进行统计呢?很自然的想到 vc的init -> viewDidApper。利用Swizzle 可以hook对应的系统方案达到自动统计的作用。这种方案应该是比较成熟也相对来说门槛不高。但是如果要求更高一些,我需要线上监控某个我开发的需求里面最占用性能的函数是什么,又该如何处理呢?

推荐一个最近发现处理的比较好的库MTHawkeye

虽然star数不多,但是基本实现了我一期想要做到的效果。还没有体验的小伙伴可以下载试试他们的功能。

里面做了2块有意义的事情。

  1. Hook vc的生命周期做AOP打点监控
  2. 利用fishhook+ ARM 64 汇编达到函数级别的监控

直接上代码吧

他将启动流程定义为这几个流程

image-20190519154419217

定义了一下几个时间点进行Hook 统计

typedef NS_ENUM(NSInteger, MTHViewControllerLifeCycleStep) {
    MTHViewControllerLifeCycleStepInitExit = 0,
    MTHViewControllerLifeCycleStepLoadViewEnter,
    MTHViewControllerLifeCycleStepLoadViewExit,
    MTHViewControllerLifeCycleStepViewDidLoadEnter,
    MTHViewControllerLifeCycleStepViewDidLoadExit,
    MTHViewControllerLifeCycleStepViewWillAppearEnter,
    MTHViewControllerLifeCycleStepViewWillAppearExit,
    MTHViewControllerLifeCycleStepViewDidAppearEnter,
    MTHViewControllerLifeCycleStepViewDidAppearExit,
    MTHViewControllerLifeCycleStepUnknown,
};

下面两段代码应该见的很多了 而且写法都一样

// MARK: replacement objc_msgSend (arm64)
//replacement objc_msgSend (arm64)
// https://blog.nelhage.com/2010/10/amd64-and-va_arg/
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
// https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
#define call(b, value)                            \
    __asm volatile("stp x8, x9, [sp, #-16]!\n");  \
    __asm volatile("mov x12, %0\n" ::"r"(value)); \
    __asm volatile("ldp x8, x9, [sp], #16\n");    \
    __asm volatile(#b " x12\n");

#define save()                      \
    __asm volatile(                 \
        "stp x8, x9, [sp, #-16]!\n" \
        "stp x6, x7, [sp, #-16]!\n" \
        "stp x4, x5, [sp, #-16]!\n" \
        "stp x2, x3, [sp, #-16]!\n" \
        "stp x0, x1, [sp, #-16]!\n" \
                                    \
        "stp q8, q9, [sp, #-32]!\n" \
        "stp q6, q7, [sp, #-32]!\n" \
        "stp q4, q5, [sp, #-32]!\n" \
        "stp q2, q3, [sp, #-32]!\n" \
        "stp q0, q1, [sp, #-32]!\n");

#define load()                    \
    __asm volatile(               \
        "ldp q0, q1, [sp], #32\n" \
        "ldp q2, q3, [sp], #32\n" \
        "ldp q4, q5, [sp], #32\n" \
        "ldp q6, q7, [sp], #32\n" \
        "ldp q8, q9, [sp], #32\n" \
                                  \
        "ldp x0, x1, [sp], #16\n" \
        "ldp x2, x3, [sp], #16\n" \
        "ldp x4, x5, [sp], #16\n" \
        "ldp x6, x7, [sp], #16\n" \
        "ldp x8, x9, [sp], #16\n");

#define link(b, value)                           \
    __asm volatile("stp x8, lr, [sp, #-16]!\n"); \
    __asm volatile("sub sp, sp, #16\n");         \
    call(b, value);                              \
    __asm volatile("add sp, sp, #16\n");         \
    __asm volatile("ldp x8, lr, [sp], #16\n");

#define ret() __asm volatile("ret\n");

__attribute__((__naked__)) static void hook_Objc_msgSend() {
    // Save parameters.
    save()

        __asm volatile("mov x2, lr\n");
    __asm volatile("mov x3, x4\n");

    // Call our before_objc_msgSend.
    call(blr, &before_objc_msgSend)

        // Load parameters.
        load()

        // Call through to the original objc_msgSend.
        call(blr, orig_objc_msgSend)

        // Save original objc_msgSend return value.
        save()

        // Call our after_objc_msgSend.
        call(blr, &after_objc_msgSend)

        // restore lr
        __asm volatile("mov lr, x0\n");

    // Load original objc_msgSend return value.
    load()

        // return
        ret()
}

其实这些代码都出自 戴铭老师之手。从这里可以看到他的开源项目只不过关于统计的代码他和其他功能耦合在一块并没有单独提出来。但是MTHawkeye 进行了分离,所以我们能直观的看到和Instruments的time profile一样的功能。

  • 手动打点

还有很多并不是通用的逻辑 我们也希望能在线上进行监控统计,那其实就和埋点一样 虽然有无痕埋点的策略,但是还是有少部分需求需要定制化,特殊化处理。比如我们需要监控某个请求链(多个接口返回合并算业务成功)这个时候手动埋点可能就更方便。

  • 网络耗时

资料待整理,这块也是利用系统给的回调函数做AOP分析

总结一下:

关于时间耗时统计介绍了3个常用场景。虽然统计和记录了数据,但是怎么上传,怎么分析,怎么通知开发解决问题 每个环节可能都会遇到这样那样的问题。走一步看一步吧~

写在最后:

Better Late Than Never ,愿我们都能跨出自己的第一步。

站在巨人的肩膀上,感谢前辈们的文章。

参考链接:

https://github.com/ming1016/GCDFetchFeed

最近的文章

APM设想

起因​ 为什么会考虑到想自研APM呢?对于我们软件的开发流程中我发现大家都聚焦于代码的质量,需求的准确性和稳定性,很容易忽视关于性能方便的要求。当然在硬件条件越来越好的时代,我们还有必要关注性能嘛?答案是当然的:有必要 而且很有必要。现在一个App的业务繁多,一个超级App可能大大小小可以供用户使用的需求能达到上千个,常用的页面涉及到几十个。如果每个需求哪怕只有一点的性能浪费积累起来就会给用户产生一定的影响。代码质量我们可以CodeReview、 静态检测,需求的准确性和稳定性我们可以通过...…

优化继续阅读
更早的文章

iOS 跨平台方案看法

随着移动开发的发展,以及在中国有特色的环境下.近阶段对于跨平台的方案有一种越来越流行的趋势,因为不论从人力成本还是更新频率上线速度来看 跨平台方案都有一定的优势。 现在主流的跨平台方案分别是 H5 ReactNative Flutter。那么我就从这三个主流方案说一说自己的理解和看法。H5H5作为存在时间最久的跨平台方案目前也是各大公司作为跨平台方案的首选。但是由于其有一定的性能问题以及和原生体验上的差异让各大公司都在另寻出路。但是作为最为稳定的跨平台方案绝对当仁不让。毕竟时间久积累的各种...…

跨平台继续阅读