文档章节

iOS 应用,性能监控

我爱水果
 我爱水果
发布于 2017/08/22 17:02
字数 784
阅读 10
收藏 0
iOS

# [github 源码地址](https://github.com/hongruqi/Walle.git  )

# iOS 应用,性能监控
> 背景,目前正在优化项目,首先要对项目内的性能指标进行分析,这个可以通过Instrument 进行debug 分析。这样做只适用于开发人员。性能指标作为一项衡量App的重要指标无法量化。为了每次发布前能有一个性能报告,需要开发一个组件,对性能数据进行记录,之后通过脚本生成报表。

##报表中重点关注的指标有以下几点:
- 启动时间
- 内存
- FPS(页面刷新帧率)
- CPU 
- 页面渲染时间

## debug模式
- 主线程阻塞时,输出MainThread 栈信息。

##分别介绍如何实现这些数据的采集。

###  内存
```Objc
vm_size_t usedMemory(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    return (kerr == KERN_SUCCESS) ? info.resident_size : 0; // size in bytes
}
```
### FPS
- 使用CADisplayLink 进行获取

```Objc
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(envokeDisplayLink:)];
        [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        

- (void)envokeDisplayLink:(CADisplayLink *)displayLink
{
    if (_lastTime == 0) {
        _lastTime = displayLink.timestamp;
        return;
    }
    
    _count ++;
    
    NSTimeInterval interval = displayLink.timestamp - _lastTime;
    
    if (interval < 1) {
        return;
    }
    
    _lastTime = displayLink.timestamp;
    CGFloat fps = _count / interval;
    _count = 0;
    
    NSInteger shownFPS = round(fps);
    CGFloat memory = [XYPerformanceUtility usedMemoryInMB];
    CGFloat cpu = [XYPerformanceUtility cpuUsage];
    DDLogInfo(@"FPS:%ld,MEM:%.2f,CPU:%.2f", (long)shownFPS, memory, cpu);
    
    [self.performanceView setPerformanceViewData:cpu memory:memory FPS:shownFPS];
    
}   
```

###  CPU
```Objc
float cpu_usage()
{
    kern_return_t kr;
    task_info_data_t tinfo;
    mach_msg_type_number_t task_info_count;
    
    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;
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    
    thread_basic_info_t basic_info_th;
    uint32_t stat_thread = 0; // Mach threads
    
    basic_info = (task_basic_info_t)tinfo;
    
    // get threads in the task
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    if (thread_count > 0)
        stat_thread += thread_count;
    
    long tot_sec = 0;
    long tot_usec = 0;
    float tot_cpu = 0;
    int j;
    
    for (j = 0; j < thread_count; j++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
            tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
            tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
        }
        
    } // for each thread
    
    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    
    return tot_cpu;
}
```
### 启动时间
```Objc
+ (void)load
{
    loadTime = mach_absolute_time();
    mach_timebase_info(&timebaseInfo);
    @autoreleasepool {
        __block id obs;
        obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
                                                                object:nil queue:nil
                                                            usingBlock:^(NSNotification *note) {
                                                                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                                                    applicationRespondedTime = mach_absolute_time();
                                                                   DDLogInfo(@"VivaVedio_IOS_Start_Time: %.f", MachTimeToSeconds(applicationRespondedTime - loadTime));
                                                                });
                                                                [[NSNotificationCenter defaultCenter] removeObserver:obs];
                                                            }];
    }
}
```

### 页面渲染耗时
利用runtime, 将UIViewController 的viewWillAppear, viewDidAppear 进行hook。输出调用的时间间隔。

```Objc
@interface UIViewController()

@property (nonatomic, assign) CFTimeInterval viewControllerAppearDuration;

@end

@implementation UIViewController (Performance)
+ (void)load{
    [self walle_swizzlingViewWillAppear];
    [self walle_swizzlingViewDidAppear];
}

+ (void)walle_swizzlingViewWillAppear
{
    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(walle_viewWillAppear:);
    [self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
}

+ (void)walle_swizzlingViewDidAppear
{
    SEL originalSelector = @selector(viewDidAppear:);
    SEL swizzledSelector = @selector(walle_viewDidAppear:);
    [self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
}

- (void)walle_viewWillAppear:(BOOL)animated
{
    self.viewControllerAppearDuration = CACurrentMediaTime();
    [self walle_viewWillAppear:animated];
}

- (void)walle_viewDidAppear:(BOOL)animated
{
    [self walle_viewDidAppear:animated];
    self.viewControllerAppearDuration = CACurrentMediaTime() - self.viewControllerAppearDuration;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *name = NSStringFromClass(self.class);
        DDLogInfo(@"View Controller :%@ show time : %g s", name, self.viewControllerAppearDuration);
    });
}


+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
    Class clz = cls;
    Method originalMethod = class_getInstanceMethod(clz, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(clz, swizzledSelector);
    
    BOOL didAddMethod = class_addMethod(clz, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(clz, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

- (void)setViewControllerAppearDuration:(CFTimeInterval)viewControllerAppearDuration
{
    objc_setAssociatedObject(self, @selector(viewControllerAppearDuration), @(viewControllerAppearDuration), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CFTimeInterval )viewControllerAppearDuration
{
    return [objc_getAssociatedObject(self, @selector(viewControllerAppearDuration)) doubleValue];
}

```
## debug 主线程阻塞
- 通过监控Runloop的回调进行监控

```Objc
    _observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        self->_activity = activity;
        dispatch_semaphore_t semaphore = self->_semaphore;
        dispatch_semaphore_signal(semaphore);
    });
    
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    // 创建信号
    _semaphore = dispatch_semaphore_create(0);
    // 在子线程监控时长
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (YES)
        {
            // 超时250ms 认为卡顿
            long st = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            if (st != 0)
            {
                if (_activity == kCFRunLoopBeforeSources || _activity == kCFRunLoopAfterWaiting)
                {
                    if (++_countTime < 5)
                        continue;
                    NSString *track = [BSBacktraceLogger bs_backtraceOfMainThread];
                    NSLog(@"############### Main thread is blocked ###############");
                    NSLog(@"%@", track);
                    NSLog(@"############### Main thread is blocked ###############");
                }
            }
            _countTime = 0;
        }
    });
```

© 著作权归作者所有

我爱水果
粉丝 0
博文 19
码字总数 30442
作品 0
杭州
私信 提问
应用性能接入框架 - Matrix for iOS/macOS

Matrix for iOS/macOS 是一款微信团队研发并日常使用的性能探针工具,目前集成在 iOS 和 macOS 微信的 APM(Application Performance Manage)平台中使用。Matrix for iOS/macOS 建立了一套 ...

匿名
04/04
2.6K
1
谷歌Flutter跨平台应用开发SDK迎来首个发行预览版本

谷歌Flutter跨平台应用开发SDK迎来首个发行预览版本 2018-06-22 12:26编辑: 枣泥布丁分类:程序人生来源:程序师 跨平台Google Flutter预览版本 招聘信息: 图像处理及模式识别工程师 C/C+...

枣泥布丁
2018/06/22
0
0
iOS高仿微信悬浮窗、忍者小猪游戏、音乐播放器、支付宝、今日头条布局滚动效果等源码

iOS精选源码 iOS WKWebView的使用源码(http://www.code4app.com/thread-30279-1-1.html) 模仿apple music 小播放器的交互实现(http://www.code4app.com/thread-30282-1-1.html) 高仿微信的悬......

sunnyaigd
2018/06/26
102
0
苹果发布 iOS 9.3.3 正式版和 iOS 10 beta 3 测试版

苹果今天发布了 iOS 9.3.3 正式版,iOS 9.3.3 从5月23日开始测试,一共有5个测试版。对于 iOS 9 用户来说,可以通过 OTA 或者 iTunes 完成升级。iOS 9.3.3 主要改进系统底层性能,并修复 Bu...

oschina
2016/07/19
2.7K
6
姗姗来迟的苹果安全:爱加密全球首推iOS应用加密技术

苹果iOS系统和安卓系统作为全球使用人数最多的手机操作系统,一直以来都备受关注,尤其在安全领域,安卓应用被破解、盗版的现象已经成为普遍;而iOS系统的封闭性被广大开发者一直认为是最安全...

3eera
2014/11/25
678
10

没有更多内容

加载失败,请刷新页面

加载更多

只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
今天
58
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
今天
27
0
全世界到底有多少软件开发人员?

埃文斯数据公司(Evans Data Corporation) 2019 最新的统计数据(原文)显示,2018 年全球共有 2300 万软件开发人员,预计到 2019 年底这个数字将达到 2640万,到 2023 年达到 2770万。 而来自...

红薯
今天
61
0
Go 语言基础—— 通道(channel)

通过通信来共享内存(Java是通过共享内存来通信的) 定义 func service() string {time.Sleep(time.Millisecond * 50)return "Done"}func AsyncService() chan string {retCh := mak......

刘一草
今天
57
0
Apache Flink 零基础入门(一):基础概念解析

Apache Flink 的定义、架构及原理 Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速...

Vincent-Duan
今天
50
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部