解剖华为云用户性能数据收集和前端监控

华为云通过TinyMonitor看板来对用户前端性能数据、异常等做监控。与传统拨测形式的监控方式不同,Tiny Monitor采用用户上报的方式,能够获取全量的、真实的用户数据,并提供丰富的数据看板,为业务的性能监控提供强大的支持。

1. 用户性能数据收集

在真实的用户监测场景下如何采集和计算用户数据,实际上包括以下几个方面的问题:

  1. 如何评估用户性能?
  2. 如何设定性能指标?
  3. 如何采集和计算用户数据?

1.1 如何评估用户性能?

前端性能会直接影响用户体验,如下两组对比图,我们感受到性能对页面展示的直接影响:
截图.PNG
image.png
当然前端性能也会比用户本身的条件影响,例如从3G网络到4G网络等带宽影响。既然性能与人相关联,我们如何检测所有的性能数据呢?
在模拟的环境中进行检测(实验室数据):

  • Lighthouse
  • WebPage Test
  • PageSpeed Insights

汇集实际的用户数据(真实数据):

  • Chrome User Experience Report (CrUX)
  • Analytics data (e.g. Google Analytics)
  • PageSpeed Insights

image.png

1.2 如何设定性能指标?

主要从首屏时间、全加载时间、用户设备信息等几个维度来探讨。通过 Navigation Timing API分析:
image.png
1. 确定统计时间点:

页面性能统计的起始时间点,应该是用户输入网址回车后开始等待的时间。一个是通过navigationStart获取,相当于在URL输入栏回车或者页面按F5刷新的时间点;另外一个是通过 fetchStart,相当于浏览器准备好使用 HTTP 请求获取文档的时间。从开发者实际分析使用的场景,浏览器重定向、卸载页面的耗时对页面加载分析并无太大作用,通常建议使用 fetchStart 作为统计起始点
image.png

2. 首屏时间:

有如下集中测量方式确定首屏时间:

  1. 开发者手动打点(最佳方式)
  2. window.performance.getEntriesByType(‘paint’)[0] (PerformancePaintTiming API ,MDN Web标准)
  3. window.chrome.loadTimes().firstPaintTime (老API,不推荐)
  4. responseEnd – fetchStart (保底方案)

image.png

3. DOM READY —— 页面解析完成的时间

DOM Ready,指的是页面解析完成的时间,在高级浏览器里有对应的DOM事件 – DOMContentLoaded。该事件在文档解析完成时会触发。那么文档解析到底包括哪些操作呢?虽然暂不能给出一个完全的答案,但文档的解析至少应该包括以下操作:HTML文档分析以及DOM树的创建、外链脚本的加载、外链脚本的执行以及内联脚本的执行,但是不包括图片、iframe等其它资源的加载。正因为如此,该事件触发的时机一般比window.onload要早,而且是在所有DOM元素都可以操作之时。DOM Ready指标影响的是交互功能的最早可用时间,DOM Ready时间如果过长的话,用户会发现页面已经出来了,但是很多功能却是不可用的。
image.png

4. PAGE LOAD

Page Load 时间指的就是window.onload事件触发的时间。与DOM Ready时间相比,Page Load的时间往往要更靠后一些,因为Page Load不仅仅是HTML文档解析完毕还包括了所有资源加载所需要的时间,例如图片资源的加载、iframe的加载等鉴于window.onload事件要等到所有资源加载完成后才会触发,因此资源加载的时间越长则Page Load的时间越长。如果没有任何外链资源,则Page Load时间与DOM Ready时间几乎是相等的,随着图片等资源的增加,Page Load与DOM Ready的差距也会越来越大。Page Load的时间越久,浏览器状态栏显示加载中的时间也就越久,因此会影响用户对页面整体速度的体验。
image.png

5. 用户环境信息

用户环境信息也是影响性能指标的一大因素:

  • 客户端类型(PC、手机)
  • 操作系统OS(Windows、Mac、Unix)
  • 浏览器类型(Chrome、IE)
  • 国家(国家代码)、地区(地区代码)
  • 渠道(用户来源)
  • 局点(北京一、北京四、上海一等)
  • 网络类型(3G、4G、5G、Wifi)

1.3 如何采集和计算用户数据?

华为云的数据采集架构如下图所示,然后将采集信息通过 TinyMonitor 看板来展示。TinyMonitor 工具是华为自研的性能监控平台具有如下特点:

  1. 提供基于业务分类下的性能数据上报、展示、分析等一站式功能,作为性能持续看护和定位问题平台,助力BU产品性能,提升体验。
  2. 通过在前端页面js非侵入式埋点,来上报用户访问页面的真实性能数据
  3. 数据上报到全球鹰后台进行统计分析,并提供查询接口
  4. 实现前端性能看板统一呈现各站点、各页面的性能数据,并提供趋势分析、性能数据分布、地域性能分析等查询能力
  5. 实时采集真实用户体验数据;端到端多维度数据分析展示;记录页面加载全过程,建立页面和资源的加载时序图;支持多种告警规则和告警间隙,通过应用号推送的方式通知业务负责人

image.png

2. 前端异常监控

2.1 异常监控的背景

  • 目前现状 —— 华为云工程师在处理线上问题的时候基本上都来自于工单,工程师只是被动地接收客户发现的问题。
  • 目前痛点 —— 兼容性问题自测难覆盖、接口报错难复现、问题发现不及时、客户反馈太紧急。
  • 实现目标 —— 做一个异常监控平台,化被动为主动,主动优化和修复线上问题,并且建立报警机制,提高工程师线上服务意识。

2.2 异常监控的意义

image.png

2.3 采集内容

在进行异常采集过程中,我们需要注意如下几个方面问题:1. 性能和信息全面性之间做出取舍; 2. 获取异常的自动化、不遗漏任何一处报错;3. 需要高效、准确和全面的捕获异常;4. 正确选择上报的时机、收集的异常日志等;基于此,我们要采集的内容有哪些呢?归纳有如下几点:

  • 用户信息 —— 当前用户状态、权限、用户信息等。
  • 行为信息 —— 用户所在的界面路径、执行了什么操作、操作时运用或者产生的相关数据等。
  • 异常信息 —— 用户操作的DOM元素节点、异常级别、异常类型、代码stack信息等。
  • 环境信息 —— 网络环境、设备型号、标志码、操作系统版本、客户端版本、API接口版本等。

2.4 异常捕获

  • try-catch 异常处理

try-catch 在我们的代码中经常见到,当代码块发生出错时 catch 将能捕捉到错误的信息,页面也将可以继续执行。但是 try-catch 处理异常的能力有限,只能捕获捉到运行时非异步错误,对于 语法错误异步错误 就显得无能为力。如下图所示,只有左图可以捕捉异常:
image.png

  • window.onerror 异常处理

window.onerror 捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,onerror 都能捕获到运行时错误。window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使是知道异常的发生控制台还是会显示。关于window.onerror 还有两点需要值得注意:

  1. 对于 onerror 这种全局捕获,最好写在所有 JS 脚本的前面,因为你无法保证你写的代码是否出错,如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。
  2. 另外 onerror 是无法捕获到网络异常的错误和语法错误。

image.png

  • Promise 异常处理 —— window.addEventListener(“unhandledrejection”)

通过 Promise 可以帮助我们解决异步回调地狱的问题,但是一旦 Promise 实例抛出异常而你没有用 catch 去捕获的话,onerror 或 try-catch 也无能为力,无法捕捉到错误。当你用到很多的 Promise 实例的话,特别是你在一些基于 promise 的异步库比如 axios 等一定要小心,因为你不知道什么时候这些异步请求会抛出异常而你并没有处理它,所以你最好添加一个 Promise 全局异常捕获事件 unhandledrejection。
image.png

  • 监听错误事件 —— window.addEventListener(“error”)

资源(img 或 script)加载失败时,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror() 处理函数。但这些 error 事件不会向上冒泡到 window,但能被window.addEventListener(‘error’) 捕获。也就是说,面对资源加载失败的错误,只能用window.addEventListerner(‘error’),而用 window.onerror 是无效的。

image.png

  • iframe 异常

image.png

  • 卡顿和页面崩溃 (load和beforeload, service worker)
  • 前端框架的异常捕获 —— Vue.config.errorHandler

在 React v16以前,可以使用 unstable_handleError 来处理捕获的错误。React v16以后,使用componentDidCatch 来处理捕获的错误。若需全局捕获错误,可以在最外层包裹一层组件,在 componentDidCatch 中捕获错误信息。

Vue 的源码中,在关键函数(比如钩子函数等)执行的时候,都加上try{} catch(){},在cacth中处理捕获到的错误。看下面的源码。

...
// vue源码片段
function callHook (vm, hook) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget();
  var handlers = vm.$options[hook];
  if (handlers) {
    for (var i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm);
      } catch (e) {
        handleError(e, vm, (hook + " hook"));
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook);
  }
  popTarget();
}
...
function globalHandleError (err, vm, info) {
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      logError(e, null, 'config.errorHandler');
    }
  }
  logError(err, vm, info);
}

function logError (err, vm, info) {
  {
    warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm);
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err);
  } else {
    throw err
  }
}
复制代码

Vue 中提供了Vue.config.errorHandler来处理捕获到的错误。

// err: 捕获到的错误对象。
// vm: 出错的VueComponent.
// info: Vue 特定的错误信息,比如错误所在的生命周期钩子
Vue.config.errorHandler = function (err, vm, info) {
  //
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享