有赞移动性能监控平台(一)
2021-02-25 20:59 zouhui 阅读(500) 评论(0) 编辑 收藏 举报前言
随着移动端业务复杂度的提升,开发同学在编写业务的时候往往容易忽略性能问题,虽然有赞移动端自研了 APM ,但是 APM 采集的都是线上的数据,无法在 QA 与开发阶段提前发现问题,为了保障软件的稳定性,需要补齐线下监控能力,避免性能问题上线对商家经营过程造成影响。
一、架构设计
整体基于 APM 现有框架迭代线下监控能力,并在端上开发 AWACS 可视化工具,通过全局悬浮窗,并结合提醒能力(弹窗与 Toast 提示)实时通知测试人员进行问题查看,同时后台也会定时分析测试环境采集的性能数据,进行管理与分配。
QA 同学基于 Appium 补齐了 UI 主流程 case ,通过自动化测试最大程度确保每次应用发版的稳定。而设备自动化回归流程可以与线下监控完美的结合起来,首先每个版本可以确保运行在同一款设备上,其次每个版本运行的主流程用例基本上保持一致,这样就为应用“前后”版本性能数据分析对比提供了两个参照条件。性能问题上报后,除了微信Robot通知相关干系人解决问题外,还基于移动端 mPaaS 搭建了问题管理与分配平台,便于跟进与追踪。
二、监控指标分析
性能监控目前对阶段、流量、页面耗时、 ANR 、慢方法、 fps 等数据做了实时监控,本篇文章只会对阶段、流量、页面耗时进行归纳分析,后面“有赞移动性能监控平台系列文章“会对 ANR 、慢方法、 fps 等监控数据进行总结。
2.1 阶段数据
移动端每个业务流程都可以统称为“阶段”,比如 App 启动、商品加购、商品查询等,业务方可以对自身需要关心的业务阶段进行监控,结合“数据分析”与“告警能力”快速协助业务方排查问题。阶段分析包括“方法耗时分析”与“网络状况分析”两个部分,下面会具体介绍。
2.1.1 方法耗时分析
在 App 编译期会对每个方法进行前后打点,确保运行过程中每个阶段方法耗时都可以被自动统计出来,节省手动打点统计成本。
原理
开发 Gradle Plugin 插件,在 App 编译 Transform 阶段( .class 转换为 .dex 过程),对字节码进行操作,在方法的开始执行( methodEnter )与结束执行( methodExit )通过 ASM 工具分别插入 MethodBeat 的 i 与 o 方法,对每个方法进行首尾打点,运行时自动统计方法耗时。
分析详情
应用所有版本产生的阶段数据都会上传到后端,后端会通过定式任务对阶段数据进行分析,分析角度分为新增、新减、陡增、陡降4个维度,协助开发综合对问题进行排查。启动阶段举例:
2.1.2 网络状况分析
业务方可以定义是否需要监控阶段的网络状况,比如启动阶段,除了要监控启动方法耗时之外,网络状态也需要进行监控( App 启动时,硬件负载比较高,过多的网络 IO 请求会拖累启动速度)。
原理
有赞零售 App 网络通过 OkHttp 进行请求,通过自定义拦截器对网络进行统一拦截,统计每个阶段网络链接数量与请求耗时, intercept 方法实现如下:
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
String url = getRequestUrl(request.url());
if (!TextUtils.isEmpty(url)) {
AppSegmentCache.INSTANCE.setRequestStart(url);
long startNs = System.nanoTime();
try {
response = chain.proceed(request);
} catch (Exception e) {
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
AppSegmentCache.INSTANCE.setRequestEnd(url, tookMs);
}
return chain.proceed(request);
}
分析详情
分为“全部调用”与"重复调用"两个统计维度,“全部调用”会统计当前版本该阶段网络请求总数,且会与上个版本请求总数进行比较,计算出升降趋势(提升/下降了 x%),且列出“新增”与“新减”两个数据维度,协助对网络状况进行分析。“重复调用”会统计所有重复调用的接口状况,包括网络重复请求的总数,还会全部列出重复调用链接内容,方便业务方进行排查优化。
2.2 流量
App 运行过程中主要涉及到接口、文本、视频、图片等各种流量请求,往往在开发过程中不太会注意流量消耗这个指标,最近也经常有商家反馈 App 流量消耗比较大,但目前并不能准确的定位流量消耗主因。
2.2.1 原理
分别对 HttpUrlConnection 和 OkHttp 做 Hook ( .class -> .dex transform 流程插桩),所有的请求都得经过 Hook 层,这样就能统计每个请求的流量大小与 response 内容,便于业务方进行分析。
OkHttpHook
App 编译期往 OkHttp 中配置 GlobalNetworkInterceptor 拦截器,统计 response length(流量大小)、response content(请求返回内容,如果是 gzip 需要解压),然后将流量内容写入文件中(非线上包才会采集),便于分析。
internal object OkHttpHook {
@JvmField
public val globalNetworkInterceptor = Interceptor { chain ->
... ...
// 计算repsonse length(流量大小)
// 读取response content(如果是gzip需要解压)
// 流量内容写入文件中
val fileUrl = File(file, URLEncoder.encode(SimpleDateFormat("yyyy-MM-dd-HH:mm:ss-SSS").format(Date()) + "-" + netPackInfo.url))
fileUrl.writeText(netPackInfo.toString())
... ...
}
HttpUrlConnectHook
编译期对 HttpUrlConnection 进行 Hook ,底层网络请求其实是代理到 OkhttpClient 上,这样就能保证 HttpUrlConnection 所有网络请求也能通过 OkHttp 进行处理,这样流量拦截器( GlobalNetworkInterceptor )就可以进行复用了。
public object HttpUrlConnectHook {
@JvmStatic
fun proxy(httpUrlConnection: URLConnection): URLConnection {
try {
return hookOkHttpURLConnection(httpUrlConnection)
} catch (e: Exception) {
e.printStackTrace()
}
return urlConnection
}
}
@Throws(Exception::class)
private fun hookOkHttpURLConnection(httpUrlConnection: URLConnection): URLConnection {
val builder = OkHttpClient.Builder()
val mClient = builder
.retryOnConnectionFailure(true)
... ...
.build()
val strUrl = httpUrlConnection.url.toString()
val url = URL(strUrl)
val protocol = url.protocol.toLowerCase(Locale.ROOT)
if (protocol.startWith("http", ignoreCase = true)) {
return HttpUrlFactory.OkHttpURLConnection(url, mClient)
} else urlConnection
}
2.2.2 分析详情
通过展示网络各个统计指标数据详情,包括每个接口的请求流量大小、请求次数、请求内容,便于技术人员对流量问题进行分析。
2.3 页面耗时
有赞零售面向B端的产品,适配了很多低端收银机,在页面流畅性有严格要求,通过页面耗时监控,统计每个页面( Activity | Fragment )的耗时,当页面耗时超过阈值时,会生成问题,分配给相应的处理人进行修复。
2.3.1 原理
监控 Activity 与 Fragment onCreate() 方法开始执行作为页面绘制开始时机,页面 onDraw() 方法第一次回调时机作为页面绘制结束时机,两个时机做减法,算出页面渲染耗时。
页面监控开始时机
在 ActivityLifecycleCallbacks 全局监听 Activity 生命周期,在 onActivityCreated() 方法中调用 watchActivity() 方法,watchActivity 除了统一对 Activity 页面预埋开始时机外,还会区分 Activity 类型,对 Activity 内嵌的 Fragment 注册 FragmentLifecycleCallbacks 监听,同样在 onFragmentViewCreate() 回调中对Fragment页面开始时机进行预埋。
public void watchActivity(Activity activity) {
watchWithMonitorView(activity.getClass().getName(), activity.getWindow().getDecorView());
... ...
if (activity instanceof android.support.v4.app.FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentLifecycleCallbacks() {
public void onFragmentViewCreated(android.support.v4.app.FragmentManager fm, final android.support.v4.app.Fragment f, View v,
Bundle savedInstanceState) {
watchWithMonitorView(f.getClass().getName(), v);
}
}), true);
}
... ...
}
页面监控结束时机
监控页面根布局 onDraw() 第一次回调,定为页面绘制结束时机。
public void watchWithMonitorView(final String className, final View view) {
final long startTime = System.currentTimeMillis();
final WeakReference<View> viewWeakReference = new WeakReference<>(view);
final ViewTreeObserver.OnDrawListener onDrawListener = new ViewTreeObserver.OnDrawListener() {
Boolean first = true;
@Override
public void onDraw() {
if (startTime != 0 && first && viewWeakReference.get() != null) {
... ...
}
};
view.getViewTreeObserver().addOnDrawListener(onDrawListener);
}
2.3.2 分析详情
在设备自动化回归过程中一个页面会被多次调用,在线下监控环境中,只有一个页面3次超过耗时阈值( 200ms )才会算成有效的页面卡顿问题,防止硬件不稳定造成问题误报。
三、后台问题分析
设备自动化回归过程中产生的性能数据存在一定的波动性,后台需要对批量性能数据进行算法校验,评估出合理的有效问题,减少问题误报。
分析工作流程(阶段数据分析举例):
设备自动化回归过程采集到阶段数据后,上传到移动网关,晚上7点启动定时任务,在后台拉取当前应用版本各个阶段最近n条数据,进行数据聚合分析,计算出合理的阶段耗时平均值,再同样拉取当前应用上个版本各个阶段的最近n条数据,同样算出阶段耗时平均值,与当前版本阶段耗时平均值进行对比,算出涨跌幅度,如果超出阈值就会当成有效问题,进行分配与告警。
四、线下AWACS工具
在 QA 与开发过程中, App 上会悬浮告警 ICON ,开发者可以点击告警 ICON 打开性能监控中心进行数据查看。性能监控中心会展示阶段、 ANR 、慢方法、流量、 FPS 等性能数据,便于开发对问题进行排查。
五、问题管理与分配平台
后台对问题进行分析后,如果是有效问题会落到后台 db 中,前台在 mPaaS 搭建一套问题查看与分配 UI 看板,方便业务方对问题进行处理与状态跟进。
5.1 问题列表
APM 监控的所有性能指标都可以在性能面板中进行切换筛选, tab 选中后再结合应用、状态、环境筛选器列出问题列表。
5.2 问题详情
点击问题列表后跳转到问题详情,问题详情中包含问题发生次数、进度、详细信息、设备基本信息等,协助开发定位问题。
点击问题详情右上角“变更状态”按钮,可以变更问题状态,并可选择负责人对问题进行分配与跟进。
六、未来规划
1:监控更多维度的数据,包括 cpu 、线程、子线程更新 UI 等。
2:增加更多的自动化测试用例,针对特定的性能场景进行独立测试(现在测试用例基本上都是主流程,覆盖度还不太够)。
3:补齐自动化设备的数量与型号,通过多机型综合分析性能问题。
4:推广到公司内部使用,协助解决有赞其他应用端性能问题。
end