Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你的页面为什么慢,Performance Timeline 简介 #49

Open
MengZhaoFly opened this issue Dec 13, 2019 · 0 comments
Open

你的页面为什么慢,Performance Timeline 简介 #49

MengZhaoFly opened this issue Dec 13, 2019 · 0 comments

Comments

@MengZhaoFly
Copy link
Owner

MengZhaoFly commented Dec 13, 2019

介绍

Performance timeline ,w3c 有两个版本的规范,本文基于 第二版本 介绍。
工欲善其事,必先利其器。要想使页面更快,那么准确测量出性能数据也是很重要的。
那么我们来看一下,在一个web页面的生命周期之内,我们基于 Performance Timeline可以得到哪些性能指标。

主要分为以下三大类:

  • navigation-timing:navigation of the document
  • resource-timing:页面资源
  • user-timing:开发者自定义的一些监控, 主要是(mark 和 measure,下文会讲)

举个🌰, w3c 上的例子,我稍加改造 :

<img id="image0" src="https://www.w3.org/Icons/w3c_main.png" />
<script>
function init() {
  // see [[USER-TIMING-2]]
  performance.mark("startWork");
  setTimeout(() => {
    performance.mark("endWork");
    measurePerf();
  }, 2000);

}
function measurePerf() {
  performance
    .getEntries()
    .map(entry => JSON.stringify(entry, null, 2))
    .forEach(json => console.log(json));
}

引入了一个 外部图片,
mark 可以理解为标记了一个时间点
getEntries 得到所有的性能数据,最后输出。
具体结果可看:

{
  "name": "",
  "entryType": "navigation",
  "startTime": 0,
  "duration": 50.07500003557652,
}
 {
  "name": "https://www.w3.org/Icons/w3c_main.png",
  "entryType": "resource",
}
 {
  "name": "startWork",
  "entryType": "mark",
  "startTime": 49.990000028628856,
  "duration": 0
}
 {
  "name": "first-paint",
  "entryType": "paint",
  "startTime": 94.83499999623746,
  "duration": 0
}
 {
  "name": "first-contentful-paint",
  "entryType": "paint",
  "startTime": 94.83499999623746,
  "duration": 0
}
 {
  "name": "endWork",
  "entryType": "mark",
  "startTime": 2050.5150000099093,
  "duration": 0
}

由此我就得到了页面上,当时的所有性能指标,包括navigation, resource, FP, FCP...等一些自定义的指标。

ps:

  • FP:页面上第一个像素落点的时候
  • FCP: 页面上开始有内容绘制的时候

现在我想要过滤一下只要我自己 mark 的点,可采用:getEntriesByType(mark)
只想要页面绘制相关的 fp,fcp,采用 getEntriesByName('first-paint')

but,我们得到的就只是当时得到的性能指标,假如后面又有 图片请求了呢,又有 js 请求了呢 🤣🤣。
我们要一直 轮询我们的 measurePerf 吗?
当然有新的解决方式。

PerformanceObserver

PerformanceObserver,是浏览器内部对Performance实现的观察者模式,即: 当有性能数据产生时,主动通知你。

这解决了我们之前的问题:

  • 重复轮训
  • 轮巡时不断判断,这个数据是新产生的,还是以前的
  • 可能其他数据的消费者也需要操作数据

监测页面FP,FCP

现在可以:

// 定义一个观察者
const observer = new PerformanceObserver(list => {
    list.getEntries().forEach((entry) => {
        console.log('entry对象', entry);
    });
});
// 观察的类型
observer.observe({
    entryTypes: ['paint']
});

关于 entryTypes, 可以取如下值:

  • frame:event-loop 时的每一帧
  • navigation:导航
  • resource:资源
  • mark: 打点,得到一个时间戳
  • measure:在两个点之间测量
  • paint:绘制
  • longtask(好像只有 chrome支持):任何在浏览器中执行超过 50 ms 的任务,都是 long task

关于 entry
每个事件类型的 entry 对象都不一样。

navigation entry 对象里能拿到相关的数据有

image

这里完整地描述了一个 页面 呈现的完整流程。
拿到每个时间点可以进行分析每个区间的时间耗费。

let t = entry
console.log('DNS查询耗时 :' + (t.domainLookupEnd - t.domainLookupStart).toFixed(0))
console.log('TCP链接耗时 :' + (t.connectEnd - t.connectStart).toFixed(0))
console.log('request请求耗时 :' + (t.responseEnd - t.responseStart).toFixed(0))

console.log('解析dom树耗时 :' + (t.domComplete - t.domInteractive).toFixed(0))
console.log('白屏时间 :' + (t.responseStart - t.startTime).toFixed(0))
console.log('domready时间 :' + (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0))
console.log('onload时间 :' + (t.loadEventEnd - t.navigationStart).toFixed(0))

ps
关于 dom 几个重要的时间点:

  • domInteractive : 网页DOM结构结束解析、开始加载内嵌资源时,即Document.readyState属性变为“interactive”。
  • domComplete:当前文档解析完成,即Document.readyState 变为 'complete'。
  • domContentLoadedEventStart: 触发当前 文档的 domContentLoadedEvent 的时候
  • domContentLoadedEventEnd: 在 当前文档的 domContentLoadedEvent 完成之后发生
  • document.redayState: 的状态,
    • loading / 正在加载 document 仍在加载。
    • interactive / 可交互 文档已被解析,"正在加载"状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载。
    • complete / 完成 文档和所有子资源已完成加载。表示 load 状态的事件即将被触发
      其他:

window.onload: default, it is fired when the entire page loads , including its content (images, CSS, scripts, etc.) In some browsers it now takes over the role of document.onload and fires when the DOM is ready as well.

resource entry 对象里能拿到相关的数据有

image

这里道理和上面的 navigation 一样。

mark

这里打了四个点,用来标识两个任务的开始时间和结束时间,两个任务分别采用 Promise 推迟执行。doTask 模拟推迟行为。

const doTask = (ms) => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, ms);
})
async function run() {
  performance.mark("startTask1");
  await doTask(1000); // Some developer code
  performance.mark("endTask1");

  performance.mark("startTask2");
  await doTask(2000); // Some developer code
  performance.mark("endTask2");

  // Log them out
  const entries = performance.getEntriesByType("mark");
  for (const entry of entries) {
    console.table(entry.toJSON());
  }
}
run();

分别得到四个点的时间。

image

measure

用于在两个 **点 (即上文提到的打点) ** 之间测量时间
举个 例子:需要测量一个 for 循环花费的时间。

const observer = new PerformanceObserver(list => {
  list.getEntries().forEach((entry) => {
    console.table(entry);
  });
});

observer.observe({
  entryTypes: ['measure']
});
performance.mark('cal-start');
// 模拟耗时任务
for(let i = 0; i < 10000; i ++) {

}
performance.mark('cal-end');
// 该 measure 的名字:my-cal
// measure 开始的时间点:cal-start
// measure 结束的时间点:cal-end
performance.measure('my-cal', 'cal-start', 'cal-end')

image

longtask

这个 支持度 好像不高,chrome 亲测可以。
可以检测 应用里 任何在浏览器中执行超过 50 ms 的任务。

原因来源还比较多:

  • 浏览器的render
  • 自身的js执行

比如上面得 实例, 我们仅需加入一个需要的检测 的 entryType 既可。

observer.observe({
  entryTypes: ['measure', 'longtask']
});

可以看到,Performance timeline 第二版本,相对以前还是有很大改进的,我们也有了更多的手段得到想要监控的数据。
EOF。

最后

如果喜欢本篇文章,可以关注的微信公众号,如果不嫌烦,还可以把它添加到桌面😀。

search

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant