如何採集和分析網頁用戶的性能指標

背景:

性能優化很重要,可是咱們怎麼知道本身的網頁性能如何呢?如何測算?很多人會說,我打開很快呀,不到1秒,可是別人呢?css

要正確地分析咱們網頁應用的性能指標,這種方法比較靠譜的呢!html

橫軸表明加載時間,縱軸表明用戶數(其實還能夠加上地區等指標的篩選,It's up to you!).只有這樣咱們才能避免坐井觀天,看到咱們的應用在全部用戶中的加載狀況。react

性能指標

咱們知道,用GA能夠採集不少信息,包括頁面停留時間,DOM加載時間,以及頁面加載時間等等,可是,評判一個網頁的性能,是有優先級之分的。git

Any performance metric that values all the content the same is not a good metric.github

有哪些指標是咱們須要關注的呢?當用戶導航到網頁時,一般會尋找視覺反饋,以確信一切符合預期。web

體驗 指標 具體狀況
是否發生? 首次繪製 (FP)/首次內容繪製 (FCP) 導航是否成功啓動?服務器是否有響應?
是否有用? 首次有效繪製 (FMP)/主角元素計時 是否已渲染能夠與用戶互動的足夠內容?
是否可用? 可交互時間 (TTI) 用戶能夠與頁面交互,仍是頁面仍在忙於加載?
是否使人愉快? 耗時較長的任務(Long Task) 交互是否順暢而天然,沒有滯後和卡頓?

下列加載時間線屏幕截圖有助於您更直觀地瞭解加載指標對應的加載體驗:(lighthouse檢查結果截圖)
性能優化


從左到右能夠看到,直到第三張圖頁面纔不是空白,因此

  1. FP => 第三張圖
  2. FCP => 第四張圖
  3. FMP => 第五張圖(假設轉盤爲主角,轉盤代碼開源)
  4. TTI => 也是第五張圖(不過不必定的呢,這個可能等全部sync js加載完才知道)

咱們能夠用LighthouseWeb Page Test 等這些工具用於在功能發佈前測試其性能,但這些工具並未在用戶設備上運行,所以未反映出用戶的實際性能體驗。bash

這裏咱們用幾個新增的API,他們是PerformanceObserverPerformanceEntryDOMHighResTimeStamp 以及 performance服務器

如下是PerformanceEntry的可選項:async

跟蹤FP/FCP

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // `name` will be either 'first-paint' or 'first-contentful-paint'.
      const metricName = entry.name;
      const time = Math.round(entry.startTime + entry.duration);
      //發送數據到後臺
      request.post({
        eventCategory:'Performance Metrics',
        eventAction: metricName,
        eventValue: time,
        nonInteraction: true,
      })
    }
  });
  observer.observe({entryTypes: ['paint']});
複製代碼

能夠看到用PerformanceObserver觀察entryTypes爲paint,也就是FP/FCP的timing.每個entry是一個PerformanceEntry對象。

跟蹤FMP

肯定頁面上的主角元素以後,您能夠跟蹤爲用戶呈現這些元素的時間點。
記住!不管用什麼方法,你應該儘快讓主角元素呈現出來

<img src="hero.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
<script>
performance.clearMarks("img displayed");
performance.mark("img displayed");
</script>
複製代碼

假如咱們的主角元素是一張圖片,這時候咱們在圖片加載出來前先mark一下,而後在onload時間在mark一下,二者再相減,就能夠知道咱們主角元素出來的用時了

跟蹤TTI

這裏咱們使用tti-polyfill

import ttiPolyfill from 'tti-polyfill';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
 request.post({
   eventCategory:'Performance Metrics',
   eventAction:'TTI',
   eventValue: tti,
   nonInteraction: true,
 });
});
複製代碼

getFirstConsistentlyInteractive() 方法接受可選的 startTime 配置選項,讓您能夠指定下限值(您知道您的應用在此以前沒法進行交互)。 默認狀況下,該 polyfill 使用 DOMContentLoaded 做爲開始時間,但一般狀況下,使用主角元素呈現的時刻或您知道全部事件偵聽器都已添加的時間點這類時間會更準確。

image.png

跟蹤Long tasks

Long time在 style layout, paint,script,都會出現。

image.png

image.png

image.png

咱們能夠在Dev Tools找到存在的long-task。

要在 JavaScript 中檢測耗時較長的任務,須要建立新的 PerformanceObserver,並觀察類型爲 longtask 的條目。 耗時較長的任務條目的一個優勢是包含提供方屬性,有助於您更輕鬆地追查致使出現耗時較長任務的代碼:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    request.post({
      eventCategory:'Performance Metrics',
      eventAction: 'longtask',
      eventValue:Math.round(entry.startTime + entry.duration),
      eventLabel:JSON.stringify(entry.attribution),
    });
  }
});

observer.observe({entryTypes: ['longtask']});
複製代碼

跟蹤輸入延遲

若要在代碼中檢測輸入延遲,您可將事件時間戳與當前時間做比較,若是二者相差超過 100 毫秒,您能夠並應該進行報告。

const testBtn = document.querySelector('#testBtn');

testBtn.addEventListener('click', (event) => {
  // Event listener logic goes here...

  const lag = performance.now() - event.timeStamp;
  if (lag > 100) {
    request.post({
      eventCategory:'Performance Metric'
      eventAction: 'input-latency',
      eventLabel: '#subscribe:click',
      eventValue:Math.round(lag),
      nonInteraction: true,
    });
  }
});
複製代碼

記錄丟失的用戶(統計用戶在頁面的時長)

有些時候,用戶會由於頁面加載時間過長而選擇離開,可是他到底忍受了多久呢?
咱們能夠用visibilitychange(該事件在頁面卸載或進入後臺時觸發)記錄

<script>
window.trackLeaver = () => {
  // Remove the listener so it only runs once.
  document.removeEventListener('visibilitychange', window.trackLeaver);

  // Send the data to Google Analytics via the Measurement Protocol.
  navigator.sendBeacon && navigator.sendBeacon(ANALYTICS_URL, [
    'v=1', 't=event', 'ec=Load', 'ea=abandon', 'ni=1',
    'dl=' + encodeURIComponent(location.href),
    'dt=' + encodeURIComponent(document.title),
    'tid=' + TRACKING_ID,
    'cid=' + CLIENT_ID,
    'ev=' + Math.round(performance.now()),
  ].join('&'));
};
document.addEventListener('visibilitychange', window.trackLeaver);
</script>
複製代碼

其餘指標

1.採集css/js的加載時間

直接在全部css文件後加一下performace.mark就能夠了, 不過內聯的css或者async js都不行哦。

2.採集字體的加載時間

使用 fontfaceobserver,

performance.clearMarks("img displayed");
performance.mark("img displayed");
new w.FontFaceObserver( "lato" )
	.check()
	.then( function(){ console.log( 「Loaded!」 );
	performance.clearMarks("img displayed");
performance.mark("img displayed");});
複製代碼

方法相似於採集主角元素的加載時間。

3.採集用戶Dom渲染的時間

參考 React16.9 Profiler,

function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Aggregate or log render timings...
}
複製代碼

在RenderCallback裏面上報就能夠了。

總結

咱們作優化必定是爲了有效果,若是咱們作出來最後的效果還不如之前就壞了,因此咱們要本身先測試,而後先灰度觀察,再全量。

這裏方法只是爲了有機會幫助咱們制定優化策略,要取決於咱們的人力,時間,技術等成本。

參考文章

  1. developers.google.com/web/fundame…
  2. speedcurve.com/blog/user-t…
  3. w3c.github.io/performance…
  4. www.filamentgroup.com/lab/font-ev…
  5. w3c.github.io/longtasks/
  6. www.stevesouders.com/blog/2015/0…
  7. github.com/GoogleChrom…
  8. docs.google.com/document/d/…
相關文章
相關標籤/搜索