iOS MetricsKit 收集电量和性能数据

App 的耗电量和性能是用户体验的重要部分,在 iOS 13 中推出了MetricKit,它用于收集和处理电池和性能指标。Improving Battery Life and Performance 中提出了三个用于不同阶段的工具进行电量和性能数据的收集,便于发现各个阶段出现的电量和性能问题。

  • XCTest Metrics (开发和测试阶段)
    • 可以使用 blocks 测试性能
  • MetricsKit (Beta 和 Public Release 阶段)
    • 用于收集电量和性能数据
  • Xcode Metrics Organizer (Public Release 阶段)
    • Xcode 中汇总了,电池,性能和 I/O 的指标

重要的两个分类指标

  • Battery
  • Performance

耗电的体现在下面这几种情况:

  • Processing (CPU 和 GPU 耗时时间)
  • Location (累计使用时长和后台使用时长)
  • Display (平均像素亮度)
  • Networking (上传和下载字节,连接性)
  • Accessories(外围设备:蓝牙等)
  • Multimedia(多媒体)
  • Camera(相机)

性能的体现在下面这几种情况:

  • Hangs (主线程卡顿)
  • Disk (I/O)
  • Application Launch (启动时间)
  • Memory (内存)

开发和测试阶段

可以通过下方代码来测试一些操作的指标

- (void)testReSizeImagePerformance {
    __auto_type app = [XCUIApplication new];
    [self
        measureWithMetrics:@[
            XCTMemoryMetric.new,
            XCTClockMetric.new,
            XCTCPUMetric.new
        ]
        block:^{
            [app.buttons[@"reSize"] tap];
        }];
}

启动时间

- (void)testLaunchPerformance {
    if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
        [self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{
            [[[XCUIApplication alloc] init] launch];
        }];
    }
}

MetricsKit

#import "AppMetricManagerSubscriber.h"
@import MetricKit;

@interface AppMetricManagerSubscriber()
<
MXMetricManagerSubscriber
>

@end

@implementation AppMetricManagerSubscriber

- (instancetype)init {
    self = [super init];
    if (!self) { return nil; }
    // 1.添加订阅
    [MXMetricManager.sharedManager addSubscriber:self];
    return self;
}

// 2.实现指标数据回调
- (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> *)payloads {
    [payloads enumerateObjectsUsingBlock:^(MXMetricPayload * _Nonnull payload, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@", payload.DictionaryRepresentation);
    }];
}

- (void)dealloc {
    // 3.移除订阅
    [MXMetricManager.sharedManager removeSubscriber:self];
}

@end

回调时机是,当 App 累计运行了 24 小时 就进行回调

回调数据格式

{
    appVersion = "1.0";
    applicationLaunchMetrics =     {
        histogrammedResumeTime =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 60;
                    bucketEnd = "210 ms";
                    bucketStart = "200 ms";
                };
                1 =                 {
                    bucketCount = 70;
                    bucketEnd = "310 ms";
                    bucketStart = "300 ms";
                };
                2 =                 {
                    bucketCount = 80;
                    bucketEnd = "510 ms";
                    bucketStart = "500 ms";
                };
            };
        };
        histogrammedTimeToFirstDrawKey =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 50;
                    bucketEnd = "1,010 ms";
                    bucketStart = "1,000 ms";
                };
                1 =                 {
                    bucketCount = 60;
                    bucketEnd = "2,010 ms";
                    bucketStart = "2,000 ms";
                };
                2 =                 {
                    bucketCount = 30;
                    bucketEnd = "3,010 ms";
                    bucketStart = "3,000 ms";
                };
            };
        };
    };
    applicationResponsivenessMetrics =     {
        histogrammedAppHangTime =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 50;
                    bucketEnd = "100 ms";
                    bucketStart = "0 ms";
                };
                1 =                 {
                    bucketCount = 60;
                    bucketEnd = "400 ms";
                    bucketStart = "100 ms";
                };
                2 =                 {
                    bucketCount = 30;
                    bucketEnd = "700 ms";
                    bucketStart = "400 ms";
                };
            };
        };
    };
    applicationTimeMetrics =     {
        cumulativeBackgroundAudioTime = "30 sec";
        cumulativeBackgroundLocationTime = "30 sec";
        cumulativeBackgroundTime = "40 sec";
        cumulativeForegroundTime = "700 sec";
    };
    cellularConditionMetrics =     {
        cellConditionTime =         {
            histogramNumBuckets = 3;
            histogramValue =             {
                0 =                 {
                    bucketCount = 20;
                    bucketEnd = "1 bars";
                    bucketStart = "1 bars";
                };
                1 =                 {
                    bucketCount = 30;
                    bucketEnd = "2 bars";
                    bucketStart = "2 bars";
                };
                2 =                 {
                    bucketCount = 50;
                    bucketEnd = "3 bars";
                    bucketStart = "3 bars";
                };
            };
        };
    };
    cpuMetrics =     {
        cumulativeCPUTime = "100 sec";
    };
    diskIOMetrics =     {
        cumulativeLogicalWrites = "1,300 kB";
    };
    displayMetrics =     {
        averagePixelLuminance =         {
            averageValue = "50 apl";
            sampleCount = 500;
            standardDeviation = 0;
        };
    };
    gpuMetrics =     {
        cumulativeGPUTime = "20 sec";
    };
    locationActivityMetrics =     {
        cumulativeBestAccuracyForNavigationTime = "20 sec";
        cumulativeBestAccuracyTime = "30 sec";
        cumulativeHundredMetersAccuracyTime = "30 sec";
        cumulativeKilometerAccuracyTime = "20 sec";
        cumulativeNearestTenMetersAccuracyTime = "30 sec";
        cumulativeThreeKilometersAccuracyTime = "20 sec";
    };
    memoryMetrics =     {
        averageSuspendedMemory =         {
            averageValue = "100,000 kB";
            sampleCount = 500;
            standardDeviation = 0;
        };
        peakMemoryUsage = "200,000 kB";
    };
    metaData =     {
        appBuildVersion = 1;
        deviceType = "iPhone11,8";
        osVersion = "iPhone OS 13.3 (17C54)";
        regionFormat = CN;
    };
    networkTransferMetrics =     {
        cumulativeCellularDownload = "80,000 kB";
        cumulativeCellularUpload = "70,000 kB";
        cumulativeWifiDownload = "60,000 kB";
        cumulativeWifiUpload = "50,000 kB";
    };
    signpostMetrics =     (
                {
            signpostCategory = TestSignpostCategory1;
            signpostIntervalData =             {
                histogrammedSignpostDurations =                 {
                    histogramNumBuckets = 3;
                    histogramValue =                     {
                        0 =                         {
                            bucketCount = 50;
                            bucketEnd = "100 ms";
                            bucketStart = "0 ms";
                        };
                        1 =                         {
                            bucketCount = 60;
                            bucketEnd = "400 ms";
                            bucketStart = "100 ms";
                        };
                        2 =                         {
                            bucketCount = 30;
                            bucketEnd = "700 ms";
                            bucketStart = "400 ms";
                        };
                    };
                };
                signpostAverageMemory = "100,000 kB";
                signpostCumulativeCPUTime = "30,000 ms";
                signpostCumulativeLogicalWrites = "600 kB";
            };
            signpostName = TestSignpostName1;
            totalSignpostCount = 30;
        },
                {
            signpostCategory = TestSignpostCategory2;
            signpostIntervalData =             {
                histogrammedSignpostDurations =                 {
                    histogramNumBuckets = 3;
                    histogramValue =                     {
                        0 =                         {
                            bucketCount = 60;
                            bucketEnd = "200 ms";
                            bucketStart = "0 ms";
                        };
                        1 =                         {
                            bucketCount = 70;
                            bucketEnd = "300 ms";
                            bucketStart = "201 ms";
                        };
                        2 =                         {
                            bucketCount = 80;
                            bucketEnd = "500 ms";
                            bucketStart = "301 ms";
                        };
                    };
                };
                signpostAverageMemory = "60,000 kB";
                signpostCumulativeCPUTime = "50,000 ms";
                signpostCumulativeLogicalWrites = "700 kB";
            };
            signpostName = TestSignpostName2;
            totalSignpostCount = 40;
        }
    );
    timeStampBegin = "2020-02-08 16:00:00 +0000";
    timeStampEnd = "2020-02-09 15:59:00 +0000";
}

自定义打点, 收集某个范围内的指标数据

__auto_type reSizeLog = [MXMetricManager makeLogHandleWithCategory:@"Image"];
os_signpost_id_t signpost_id = os_signpost_id_generate(reSizeLog);
MXSignpostIntervalBegin(reSizeLog, signpost_id, "reSizeImage");
[self reSizeImage];
MXSignpostIntervalEnd(reSizeLog, signpost_id, "reSizeImage");

Xcode Metrics Organizer

什么是 Xcode Metrics Organizer?

  • 开箱即用的电量和性能分析工具
  • 无须改动 App
  • 数据收集满足用户隐私

Xcode Metrics Organizer 如何运作

当使用 App 时候,iOS 会记录各项指标,然后发送到苹果服务端上,并自动生成相关的可视化报告。

可以通过 Window -> Organizer -> Metrics 查看,可以查看各项指标,并且和历史版本进行对比。

  • 内存提供了峰值和平均内存
  • 电量有前后台占用情况,CPU 、定位和网络的比重等。
  • 历史版本启动耗时
  • 主线程卡顿情况
  • 写磁盘数据量
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 本文由作者张迎贞授权网易云社区发布。 APP性能测试除了需要监控PCU、内存占用、流量等,还需要获取APP的电量数...
    43ce3d72fadb阅读 717评论 0 0
  • 北京欢迎你,为你开天辟地,流动中的魅力,充满着朝气。 ――《北京欢迎你》 ...
    古上心月阅读 136评论 0 0
  • 七月与安生写影评 看完七月与安生感叹世间竟有这么好的感情,这么多年,经历了那么多事,希望他们像小说中活成对方的样子...
    那年冬天小雪阅读 190评论 0 0
  • Xcode常用插件 [http://www.cnblogs.com/dsxniubility/p/5099191....
    騂跃神话阅读 291评论 0 0
  • 茫白的天,孤独的人,和他自己的热闹。 他生在潺潺水声中也活在人声鼎沸里。 他生在江南烟雨中也活在无疆大漠里。 他看...
    柳摇新绿阅读 283评论 4 11