December 29, 2019

探索performance.timing与首页性能参数上报

最近打算给WEB应用收集一些打开的相关数据,所以需要对performance的api进行熟悉。收集完数据之后,后续有什么故障或者优化的手段,就可以使用数据说话,而不是说,“加入了XX的优化,快了很多”,“服务器负载过高,影响了XX的部分的解析时间”。

接下来主要分析performance.timing对象所在属性,对于performance对象的其他属性不做详细处理。这篇文章的也没什么特别的地方,只是在面向谷歌研究的时候,发现很多关于performance.timing的描述都很模棱两可,而且比较简单,对于实际情况应用与影响因素不明确,所以做了部分测试。

performance.timing 基础介绍

performance.timing对象的数据是什么?该对象下包含页面打开的各种的时间戳,例如:DNS 耗费时间,TCP 连接时间等;这些时间不通过performance的api,暂时无法获取这些相对底层的时间。这个包含以下字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
navigationStart
unloadEventStart
unloadEventEnd
redirectStart
redirectEnd
fetchStart
domainLookupStart
domainLookupEnd
connectStart
connectEnd
secureConnectionStart
requestStart
responseStart
responseEnd
domLoading
domInteractive
domContentLoadedEventStart
domContentLoadedEventEnd
domComplete
loadEventStart
loadEventEnd

这些字段顺序触发是怎样的?参考alloyteam抄回来的这张图: image.png

所以调整以下,整体顺序是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  // unload 事件触发的开始与结束时间
  // unload 事件指的是上一个页面(跳转到当前页面)或者当前页面(刷新页面)监听的事件: window.addEventListener('unload', () => {})
  // 这个通常是这个事件的回调函数所耗费的事件,感觉不太重要?
  unloadEventStart,
  unloadEventEnd,
  // 比较好理解,重定向所花的时间,若中途包含多次重定向才能确定最终的地址,则耗费的时间随之上升
  redirectStart,
  redirectEnd,
  // alloyteam描述:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
  // 个人理解,当有 unload 事件的时候,与unloadEventStart时间一致,否则与 fetchStart 时间一致
  navigationStart,
  // 已找到目标地址,开始获取文档
  fetchStart,
  // DNS 解析时间
  domainLookupStart,
  domainLookupEnd,
  // DNS 解析到对应的ip,开始建立TCP
  connectStart,
  // 如果是加密https,则TCP加密有这一步,如果是http协议,则这个值为0
  secureConnectionStart,
  connectEnd,
  // TCP 已建立,准备发出http请求
  requestStart
  // 接收文档数据
  responseStart
  responseEnd
  // dom 解析
  domLoading
  // dom 解析完毕
  domInteractive
  // 触发 DOMConctentLoaded 事件
  domContentLoadedEventStart
  domContentLoadedEventEnd
  // dom 已准备好,对应的资源也已经ok
  domComplete
  // 触发 load 事件
  loadEventStart
  loadEventEnd
}

影响不同事件的因素

unload 相关

对应unloadEventStart,unloadEventEnd, 这个是unload时间的执行整个时间;有时候从别的页面跳转过来,控制权不在自己所在应用,通常来说意义不是很大。

影响因素:上一次unload事件处理任务

redirect 相关

对应redirectStart,redirectEnd, 这个比较明显,在达到最终域名之前,重定向所花的时间;通常重定向收集部分数据或者链接更改。

影响因素:redirect 次数

DNS 相关

对应domainLookupStart,domainLookupEnd, 这个通常是指该链接文档对应的'index.html'所在链接的DNS解析时间,不是页面所有链接的DNS所花时间

影响因素:域名供应商?[狗头]

TCP 相关

对应connectStart,connectEnd,secureConnectionStart事件,建立TCP链接所耗费时间

影响因素:服务器对请求响应速度

文档接收发出与收到时间

对应requestStart,responseStart,responseEnd事件;从requestStartresponseStart这段时间,是服务器处理生成文档的过程;而responseStartresponseEnd的时间,是客户端下载整个文档所花的时间;

影响因素:1. 确认文档:服务器处理文档的时间;2. 下载过程:客户端与服务端的网络速度;

以上几个因素对于基础网络研究不深入,所以只能点一些皮毛,比较浅显的影响因素

DOM 树解析过程

对应domLoading, domInteractive;从dom开始解析到dom解析完毕;这里的domInteractive响应,已经包含了对应的静态资源;这里的静态资源指的是index.html所在的包含的同步css,js等。不包含图片。同步js文件发出新的请求加入的js文件,这些文件加载不影响这个过程的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<head>
  <!-- 同步 css 加载时间影响 -->
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <!-- 图片加载时间不影响 -->
  <img src="/big.jpg" />

  <!-- 同步js影响 domInteractive 时间 -->
  <script src="index.js"></script>
  <!-- 异步 js 文件加载时间不影响 -->
  <script src="defer.js" defer></script>
  <script src="async.js" async></script>
</body>
1
2
3
4
5
// index.js
var script = document.createElement('script')
// 后续加入的js文件加载时间不影响这个过程
script.src = '/append-by-index.js'
document.head.appendChild(script)

影响因素:

  1. dom 树的节点数量
  2. 文档包含的同步css, js文件的大小与加载时间;

DOM 解析完毕到触发 DOMContentLoaded 事件

对应domInteractive, domContentLoadedEventStart;这个时候dom已经解析完毕,按道理来说应该马上进入domContentLoadedEventStart事件,还有其他影响吗?也是我们上面提到的,标识为deferscript标签如果在domInteractive触发后还没加载好,那么这个时候就会影响到。

这是因为defer的特性,defer的js文件会在DOMContentLoaded事件触发前执行,执行完毕再触发DOMContentLoaded事件。

defer类似的async异步scriptasync的js文件没有这个限制,而是在下载完毕后,马上执行,与DOMContentLoaded事件没有关系。

影响因素:

  1. 设置defer的js文件加载速度

DOMContentLoaded 事件执行时间

对应domContentLoadedEventStartdomContentLoadedEventEnd,这个比较简单,就是DOMContentLoaded回调事件函数执行的时长。

影响因素:

  1. DOMContentLoaded事件回调函数执行时间

DOM 最终完成

对应domComplete事件;这里有点疑惑,dom在上面不是已经完成了,这里怎么还有一个domComplete事件。因为这个domComplete事件是应用中所有的资源,包括图片等都已经加载完了,才会触发domComplete事件

影响因素:

  1. 页面所有资源的加载速度

loaded执行时间

这个与DOMContentLoaded类似,是load事件的执行时间:

1
window.addEventListener('load', () => {})

影响因素:

  1. load回调函数的执行时长

收集指标

通过上面可以知道哪些因素影响哪一部分的效果,通常来说,收集的指标有以下几种:

1
2
// 后续的performance.timing都是使用这个变量代替
var timing = performance.timing
  1. DNS 解析时间:timing.domainLookupEnd - timing.domainLookupStart
  2. TCP 建立时间:timing.connectEnd - timing.connectStart
  3. redirect 时间: timing.redirectEnd - timing.redirectStart
  4. ttfb: timing.responseStart - timing.requestStart 有些统计会把DNS解析或者TCP连接都归类到ttfb的时间,但是从chrome开发者工具看到,ttfb只包含从请求发起到收到的时间,所以这里也是选用了这种 ttfb wiki
  5. 文档下载时间:timing.responseEnd - timing.responseStart ,反映该请求文档的下载时间,服务器是否处理过长
  6. DOM 解析与基础静态文件下载时间:timing.domInteractive - timing.domLoading;若时间过长,则考虑 DOM 节点的问题与基础静态文件体积大小,是否对入口文件进行拆分,使用懒加载功能;是否对静态文件加入到CDN,是否改成http2等
  7. 所有资源加载速度:timing.domComplete - timing.domContentLoadedEventEnd;图片资源是否过大,非必要进页面展现的图片使用懒加载占位等,图片资源是否也加入cdn,http2等.....

思考

domLoading事件之前的指标都是只与html文档有关系。对于SPA应用来说,实际上DOM元素没多少个,大部分元素都是后面进行渲染,所以是否需要对后续渲染的过程再做一层处理,收集app启动的时间?收集的启动时间没有对应在timing对象,需要通过performance.measure来处理,下面以vue与react为例子进行一个简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// vue main.js
import xx from 'xx'
// ...
performance.mark('appBootStart')

new Vue({
  el: '#app',
  mounted() {
    // 使用 setTimeout 是使用宏任务保证可交互状态
    setTimeout(() => {
      performance.mark('appBootEnd')

      performance.measure('bootDuration', 'appBootStart', 'appBootEnd')

      const { duration } = performance.getEntriesByName('bootDuration')[0]
      // upload duration time
    })
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// react main.js
import xx from 'xx'
// ...
performance.mark('appBootStart')

const app = () => {
  // componentDidMounted hook
  useEffect(() => {
    setTimeout(() => {
      performance.mark('appBootEnd')

      performance.measure('bootDuration', 'appBootStart', 'appBootEnd')

      const { duration } = performance.getEntriesByName('bootDuration')[0]
      // upload duration time
    })
  }, [])
}

如果根节点的mount事件并不能充分展现应用的实际情况,那么需要更改到其他组件下进行处理,才能记录真实应用的情况;反馈有内容的第一次可交互时间。

参考文章: