定格 2019-10-24
MetriKit是iOS 13中用于收集和处理电池和性能指标的新框架。这是在WWDC今年与XCTestMetrics和Xcode Metrics组织者一起,作为一项协调一致的努力的一部分,为开发人员带来关于他们的应用程序在该领域的表现的新见解。
苹果会自动从AppStore上安装的应用程序中收集度量指标。您可以在Xcode 11中通过打开组织者(? ? ? o)并选择新的Metrics选项卡。
MetriKit是Xcode组织者度量的补充,它提供了一种编程方式来接收有关应用程序在该领域中的表现的日常信息。有了这些信息,您可以自己收集、聚合和分析比通过Xcode更详细的信息。
度量可以帮助您发现您在本地测试时可能没有看到的问题,并允许您跟踪和更改不同版本的应用程序。在这个最初的版本中,苹果专注于两个对用户最重要的指标:电池使用和性能.
电池寿命取决于许多不同的因素。物理方面,如设备的年龄和充电周期的次数是决定性的,
但你的手机使用方式也很重要。
比如CPU的使用、显示器的亮度和屏幕上的颜色,以及收音机用于获取数据或获取当前位置的频率--所有这些都会产生很大的影响。但最重要的是要记住的是,用户非常关心电池的使用寿命。
除了相机有多好外,电荷之间的间隔时间也是很长的。这个这些天当有人买新手机时的决定因素。
所以当他们的新的,昂贵的手机不度过这一天,他们会很不开心的。
直到最近,苹果公司还在电池问题上承担了大部分的责任。
但自从iOS 12和它的新电池使用屏在设置中,用户现在可以判断他们最喜欢的应用程序是什么时候被指责的。
幸运的是,有了iOS 13,你现在就拥有了所有你需要的东西,以确保你的应用程序不会与合理的能源使用发生冲突。
性能是整个用户体验中的另一个关键因素。正常情况下,我们可能会看数据,如处理器时钟速度或帧速率作为业绩的衡量标准。但相反,苹果专注于不那么抽象和更具可操作性的指标:
主/UI线程被阻塞的频率有多大,以致应用程序对用户输入没有响应?
用户点击图标后,应用程序需要多长时间才能使用?
在进入后台之前,应用程序在峰值时使用了多少内存?
这个应用程序多久写一次到磁盘,如果你还不知道,那就是相对缓慢运行 (即使是iPhone上的闪存!)
从API使用者的角度来看,很难想象`MetriKit`如何更容易合并。您所需要的只是您的应用程序的某些部分作为一个度量订阅者(一个明显的选择是您的`AppDelegate`),并将其添加到共享`MXMetricManager:`
import UIKit import MetricKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { MXMetricManager.shared.add(self) return true } func applicationWillTerminate(_ application: UIApplication) { MXMetricManager.shared.remove(self) } } extension AppDelegate: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload]) { ... } }
iOS在使用应用程序时会自动收集样本,并且每天(每24小时)发送一次包含这些指标的聚合报告。
以验证您的`MXMetricManagerSubscriber`正在按预期调用其委托方法,在Xcode运行应用程序时,从Debug菜单中选择SIMPLE MetriKit有效载荷。
模拟MetriKit有效载荷菜单项要求应用程序在实际设备上运行,并为模拟器构建禁用。
除了为您收集的基线统计数据之外,还可以使用`mxSignpost`函数来收集代码中最重要部分的度量。这,这个路标支持API捕获CPU时间、内存和写入磁盘。
例如,如果应用程序的一部分确实对音频流进行了后处理,您可以用公制路标对这些区域进行注释,以确定该工作的能量和性能影响:
let audioLogHandle = MXMetricManager.makeLogHandle(category: "Audio") func processAudioStream() { mxSignpost(.begin, log: audioLogHandle, name: "ProcessAudioStream") ... mxSignpost(.end, log: audioLogHandle, name: "ProcessAudioStream") }
既然你掌握了这些信息,你会怎么处理呢?我们该怎么填` ... `实现中的占位符`didReceive(_:)?`
你,你们能把它转交给一些付费的分析或事故报告服务,但这其中的乐趣在哪里呢??让我们构建自己的Web服务来收集这些以供进一步分析:
这个 MXMetricPayload 由度量管理器订阅者接收的对象有一个方便的 jsonRepresentation() 方法生成如下内容:
[点击查看 JSON表示 ]
正如你所看到的,这个表示法有很多地方。为所有这些信息定义模式将是一项艰巨的工作,而且不能保证这种情况今后不会改变。因此,让我们采用NoSQL范式(尽管是负责任地使用波斯特格斯)通过将有效载荷存储在JSONB列:
CREATE TABLE IF NOT EXISTS metrics ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, payload JSONB NOT NULL );
我们可以使用JSON算子就像这样:
SELECT (payload -> ‘applicationTimeMetrics‘ ->> ‘cumulativeForegroundTime‘)::INTERVAL FROM metrics; -- interval -- ═══════════════════ -- @ 11 mins 40 secs -- (1 row)
度量的JSON表示将时间和内存的度量存储为带有单位的字符串(如"100 ms"和500 kB)。在Postgres中,您可以直接将时间度量转换到INTERVAL类型但是,您需要创建一个转换为字节计数的函数:
CREATE OR REPLACE FUNCTION parse_byte_count (TEXT) RETURNS BIGINT AS $$ SELECT replace(split_part($1, ‘ ‘, 1),‘,‘,‘‘)::BIGINT * CASE split_part($1, ‘ ‘, 2) WHEN ‘kB‘ THEN 1024 WHEN ‘MB‘ THEN 1024 * 1024 WHEN ‘GB‘ THEN 1024 * 1024 * 1024 END $$ LANGUAGE ‘sql‘ STRICT IMMUTABLE;
PostgreSQL中的JSON运算符使用起来可能很麻烦--特别是对于更复杂的查询。其中一种帮助方法是创建一个视图。(物化或以其他方式)要以最方便的表示方式向您投射最重要的信息:
DROP VIEW key_performance_indicators; CREATE VIEW key_performance_indicators AS SELECT id, (payload -> ‘appVersion‘) AS app_version, (payload -> ‘metaData‘ ->> ‘deviceType‘) AS device_type, (payload -> ‘metaData‘ ->> ‘regionFormat‘) AS region, (payload -> ‘applicationTimeMetrics‘ ->> ‘cumulativeForegroundTime‘ )::INTERVAL AS cumulative_foreground_time, parse_byte_count( payload -> ‘memoryMetrics‘ ->> ‘peakMemoryUsage‘ ) AS peak_memory_usage_bytes FROM metrics;
使用视图,您可以执行聚合查询在您的所有度量指标中,JSON有效负载都具有模式支持的关系数据库的方便性:
SELECT avg(cumulative_foreground_time) FROM key_performance_indicators; -- avg -- ══════════════════ -- @ 9 mins 41 secs SELECT app_version, percentile_disc(0.5) WITHIN GROUP (ORDER BY peak_memory_usage_bytes) AS median FROM key_performance_indicators GROUP BY app_version; -- app_version │ median -- ═════════════╪═══════════ -- "1.0.1" │ 192500000 -- "1.0.0" │ 204800000
PostgreSQL不能很好地处理CamelCase的表名或列名,所以在使用以下函数时要记住这一点jsonb_to_record.
在本例中,大多数繁重的工作都委托给Postgres,这使得服务器端的实现相当枯燥。为了完整起见,以下是Ruby(Sinatra)和JavaScript(Express)中的一些参考实现:
require ‘sinatra/base‘ require ‘pg‘ require ‘sequel‘ class App < Sinatra::Base configure do DB = Sequel.connect(ENV[‘DATABASE_URL‘]) end post ‘/collect‘ do DB[:metrics].insert(payload: request.body.read) status 204 end end
现在我们已经设置好了一切,最后一步就是实现所需的`MXMetricManagerSubscriber`委托方法`didReceive(_:)`要将这些信息传递给我们的web服务:
extension AppDelegate: MXMetricManagerSubscriber { func didReceive(_ payloads: [MXMetricPayload]) { for payload in payloads { let url = URL(string: "https://example.com/collect")! var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = payload.jsonRepresentation() let task = URLSession.shared.dataTask(with: request) task.priority = URLSessionTask.lowPriority task.resume() } } }
加入iOS开发交流QQ群:[1012951431],选择加入一起交流,一起学习,共享学习资料。期待你的加入!(进群可领取礼包)
转载地址 : https://nshipster.com/metrickit/