Đo lường các chỉ số hiệu suất quan trọng bằng Google Analytics

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng Google AnalyticsUser Timings API để đo lường hiệu suất thực tế của trang web hoặc ứng dụng và tối ưu hoá hiệu suất đó nhằm cải thiện trải nghiệm cho người dùng.

Các công cụ như WebPagetest.org là một khởi đầu tuyệt vời để tối ưu hoá hiệu suất, nhưng bài kiểm tra thực sự về hiệu suất của trang web sẽ luôn là dữ liệu thực tế từ người dùng thực.

Nếu điều hành một trang web, rất có thể bạn đã sử dụng Google Analytics để đo lường lưu lượng truy cập cũng như những yếu tố như mức sử dụng thiết bị và trình duyệt. Chỉ cần thêm một chút mã bổ sung, bạn có thể thêm các chỉ số hiệu suất vào hỗn hợp này.

Kiến thức bạn sẽ học được

  • Cách đo lường các chỉ số hiệu suất một cách chính xác và hiệu quả bằng User Timings API
  • Cách gửi dữ liệu đó đến Google Analytics để dữ liệu có thể được đưa vào báo cáo của bạn

Bạn cần có

  • Một trình duyệt có bảng điều khiển dành cho nhà phát triển
  • Web Server for Chrome hoặc sử dụng máy chủ web mà bạn chọn
  • Mã mẫu
  • Trình chỉnh sửa văn bản
  • (Không bắt buộc) tài khoản Google Analytics

Bạn sẽ sử dụng hướng dẫn này như thế nào?

Chỉ đọc Đọc và hoàn thành bài tập

Bạn đánh giá thế nào về trải nghiệm của mình khi xây dựng trang web hoặc ứng dụng?

Người mới bắt đầu Trung cấp Thành thạo

Bạn có thể tải tất cả mã mẫu xuống máy tính...

Tải tệp Zip xuống

...hoặc sao chép kho lưu trữ GitHub từ dòng lệnh.

git clone https://github.com/googlecodelabs/performance-analytics.git

Mã mẫu được chia thành các thư mục con tương ứng với từng bước được đánh số trong lớp học lập trình này. Bạn có thể sử dụng dữ liệu này để dễ dàng chuyển qua lại trong lớp học lập trình hoặc xác minh rằng bạn đã triển khai đúng cách.

Nếu có quyền truy cập vào một chương trình so sánh, bạn có thể dùng chương trình đó để xem chính xác những thay đổi từ bước này sang bước khác.

Trong lớp học lập trình này, bạn sẽ sử dụng một tệp HTML duy nhất tải các thành phần sau:

  • Phông chữ trên web
  • Biểu định kiểu
  • Hình ảnh
  • JavaScript

Và bạn sẽ viết mã mới để đo lường các chỉ số hiệu suất chính cho từng loại thành phần này.

Những điều cần cân nhắc về hiệu suất của thành phần

Nếu đã từng đọc bất kỳ thông tin nào về việc tối ưu hoá hiệu suất, có lẽ bạn đã biết rằng mỗi loại thành phần này đều có những đặc điểm riêng và có thể ảnh hưởng đến hiệu suất tổng thể theo nhiều cách.

CSS

Ví dụ: biểu định kiểu chặn quá trình hiển thị tất cả các phần tử trong DOM xuất hiện sau biểu định kiểu. Điều này có nghĩa là trình duyệt phải đưa ra yêu cầu về biểu định kiểu, tải biểu định kiểu xuống và phân tích cú pháp biểu định kiểu trước khi có thể hiển thị bất kỳ nội dung nào trong DOM xuất hiện sau biểu định kiểu. Vì lý do này, tốt nhất là bạn nên đặt biểu định kiểu trong <head> của tài liệu. Và do bản chất chặn của CSS, bạn cũng nên chỉ đặt CSS quan trọng trong <head> và tải CSS không quan trọng không đồng bộ sau đó.

JavaScript

Mặt khác, JavaScript không chặn quá trình hiển thị, nhưng chặn quá trình phân tích cú pháp và tạo DOM. Điều này là cần thiết vì JavaScript có thể sửa đổi DOM, tức là bất cứ khi nào trình duyệt nhìn thấy thẻ <script> (ngoại trừ các tập lệnh không đồng bộ), trình duyệt phải thực thi mã trước khi tiếp tục đến thẻ tiếp theo. Nếu thẻ <script> tham chiếu đến một tệp JavaScript bên ngoài, thì thẻ đó phải tải xuống và thực thi mã trước khi chuyển sang bước tiếp theo.

Vì lý do này, bạn nên tải JavaScript ngay trước thẻ đóng </body>, để phần lớn DOM có sẵn nhanh nhất có thể.

Phông chữ trên web

Nếu tải phông chữ trên web, bạn cũng có thể chọn chặn việc hiển thị tài liệu cho đến khi có thể sử dụng phông chữ. Trong trường hợp này, bạn cần phải biết thời gian thực tế mà người dùng cần để thực hiện thao tác này. Phông chữ trên web có thể tải nhanh đối với bạn nhưng tải rất chậm đối với phần lớn những người truy cập vào trang web của bạn. Đây là lý do khiến việc đo lường và đưa ra quyết định dựa trên dữ liệu thực tế trở nên quan trọng.

Hình ảnh

Cuối cùng, hình ảnh có thể giúp trang web trở nên sống động, nhưng cũng thường mất nhiều thời gian nhất để tải. Việc hiểu rõ ý nghĩa thực sự của chỉ số này và có thể phát hiện mọi mối tương quan giữa các mẫu sử dụng và thời gian tải trang là rất quan trọng để hiểu cách tối ưu hoá.

Bước đầu tiên trong lớp học lập trình này là xem trang minh hoạ trông như thế nào trước khi thêm bất kỳ mã đo lường hiệu suất nào.

Để xem bản minh hoạ, hãy tạo một thư mục mới và thêm một tệp có tên là index.html vào thư mục đó. Sau đó, hãy sao chép và dán mã bên dưới vào tệp index.html.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Performance Analytics Demo</title>

  <!-- Start fonts -->
  <link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
  <!-- End fonts -->

  <!-- Start CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
  <style>
    body { font-family: Roboto, sans-serif; margin: 1em; }
    img { float: left; height: auto; width: 33.33%; }
    .gallery { overflow: hidden; }
  </style>
  <!-- End CSS -->

</head>
<body>

  <div class="container">

    <!-- Start images -->
    <div class="gallery">
      <img src="http://lorempixel.com/380/200/animals/1/">
      <img src="http://lorempixel.com/380/200/animals/2/">
      <img src="http://lorempixel.com/380/200/animals/3/">
    </div>
    <!-- End images -->

    <h1>Performance Analytics Demo</h1>
    <p>Real performance data from real users.</p>

  </div>

  <!-- Start JavaScript -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  <!-- End JavaScript -->

</body>
</html>

Tiếp theo, hãy mở Web Server for Chrome rồi khởi động một máy chủ cục bộ trong thư mục mà bạn vừa tạo. Đảm bảo bạn đã đánh dấu vào ô "automatically show index.html" (tự động hiện index.html).

Screen Shot 2016-05-11 at 1.03.43 PM.png

Giờ đây, bạn có thể chuyển đến http://127.0.0.1:8887/ trong trình duyệt và xem tệp minh hoạ. Hàm này có dạng như sau:

Screen Shot 2016-05-11 at 10.59.03 AM.png

Sau khi chạy trang minh hoạ, hãy dành chút thời gian xem mã và xem tất cả các loại tài sản đang được tải. Trong vài bước tiếp theo, bạn sẽ thêm mã để đo lường thời điểm các thành phần này được tải và người dùng có thể tương tác với chúng.

Như đã đề cập trong phần cân nhắc về hiệu suất của thành phần trước đó, CSS chặn quá trình kết xuất các phần tử DOM cũng như việc thực thi các tập lệnh xuất hiện sau đó trong DOM.

Tệp minh hoạ mà bạn vừa tạo chứa CSS sau đây, tham chiếu đến Bootstrap và một số kiểu nội tuyến.

<!-- Start CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
  body { font-family: Roboto, sans-serif; margin: 1em; }
  img { float: left; height: auto; width: 33.33%; }
  .gallery { overflow: hidden; }
</style>
<!-- End CSS -->

Vì CSS chặn cả quá trình hiển thị các phần tử DOM và thực thi tập lệnh, nên bạn có thể xác định thời điểm CSS hoàn tất việc chặn bằng cách thêm thẻ <script> ngay sau CSS lưu trữ thời gian hiện tại.

Bạn có thể thực hiện việc đó bằng cách tạo một biến và chỉ định new Date() cho biến đó, nhưng nhờ User Timings API, có một cách dễ dàng hơn nhiều: phương thức performance.mark.

Để đánh dấu thời điểm CSS hoàn tất việc chặn cả quá trình hiển thị và thực thi tập lệnh, hãy thêm dòng mã sau ngay trước chú thích <!-- End CSS --> đóng.

<script>performance.mark('css:unblock');</script>

Phương thức performance.mark sẽ tạo dấu thời gian có độ phân giải cao tại thời điểm chính xác này và liên kết dấu thời gian đó với bất kỳ tên nào được truyền đến phương thức. Trong trường hợp này, bạn đã đặt tên cho dấu là "css:unblock".

Phương thức performance.mark đi đôi với phương thức performance.measure. Phương thức này được dùng để tính toán chênh lệch thời gian giữa hai dấu (ngoài các dấu bạn tạo, bạn cũng có thể dùng các dấu mà trình duyệt tự động tạo cho nhiều điểm trong Navigation Timing API).

Hàm tiện ích sau đây đo lường và trả về khoảng thời gian giữa một dấu mà bạn đã thêm và dấu responseEnd do Navigation Timing API tạo.

function measureDuration(mark, opt_reference) {
  var reference = opt_reference || 'responseEnd';
  var name = reference + ':' + mark;

  // Clears any existing measurements with the same name.
  performance.clearMeasures(name);

  // Creates a new measurement from the reference point to the specified mark.
  // If more than one mark with this name exists, the most recent one is used.
  performance.measure(name, reference, mark);

  // Gets the value of the measurement just created.
  var measure = performance.getEntriesByName(name)[0];

  // Returns the measure duration.
  return measure.duration;
}

Để bắt đầu sử dụng hàm tiện ích này, hãy tạo một tệp mới có tên là perf-analytics.js (trong cùng thư mục với tệp index.html) rồi sao chép và dán đoạn mã trên vào tệp đó.

Bây giờ, khi hàm này được xác định, bạn có thể gọi hàm này và truyền tên dấu "css:unblock". Để không ảnh hưởng đến quá trình tải bất kỳ tài nguyên nào khác, bạn nên hoãn chạy các phép đo này cho đến sau khi sự kiện tải của cửa sổ kích hoạt.

Sau khi bạn viết một hàm để gọi mã này, tệp perf-analytics.js của bạn sẽ có dạng như sau:

window.onload = function() {
  measureCssUnblockTime();
};


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the CSS stops blocking rendering, then logs that value to the console.
 */
function measureCssUnblockTime() {
  console.log('CSS', 'unblock', measureDuration('css:unblock'));
}


/**
 * Accepts a mark name and an optional reference point in the navigation timing
 * API and returns the time duration between the reference point and the last
 * mark (chronologically).
 * @param {string} mark The mark name.
 * @param {string=} opt_reference An optional reference point from the
 *     navigation timing API. Defaults to 'responseEnd'.
 * @return {number} The time duration
 */
function measureDuration(mark, opt_reference) {
  var reference = opt_reference || 'responseEnd';
  var name = reference + ':' + mark;

  // Clears any existing measurements with the same name.
  performance.clearMeasures(name);

  // Creates a new measurement from the reference point to the specified mark.
  // If more than one mark with this name exists, the most recent one is used.
  performance.measure(name, reference, mark);

  // Gets the value of the measurement just created.
  var measure = performance.getEntriesByName(name)[0];

  // Returns the measure duration.
  return measure.duration;
}

Cuối cùng, bạn cần tải tập lệnh perf-analytics.js từ index.html. Để làm như vậy, hãy thêm thẻ tập lệnh sau vào tài liệu chính của bạn. Hãy nhớ thêm thẻ này vào cuối cùng để thẻ không ảnh hưởng đến việc tải các tài nguyên khác.

<!-- Start performance analytics -->
<script async src="perf-analytics.js"></script>
<!-- End performance analytics -->

Sau khi hoàn tất bước này, mã của bạn sẽ khớp với mã trong thư mục 01-css của kho lưu trữ lớp học lập trình.

Nếu tải trang này trong trình duyệt và mở bảng điều khiển dành cho nhà phát triển, bạn sẽ thấy kết quả tương tự như sau:

Screen Shot 2016-05-17 at 11.13.02 AM.png

Phông chữ trên web thường được tải thông qua một biểu định kiểu bên ngoài, như trong tệp minh hoạ ban đầu:

<!-- Start fonts -->
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700,400italic" rel="stylesheet">
<!-- End fonts -->

Vì đây là thẻ <link> cho một tệp CSS, nên có vẻ như việc xác định thời điểm các phông chữ được tải và sẵn sàng sử dụng cũng đơn giản như việc thêm một dấu bên trong thẻ <script> ngay sau <link>, giống như trong bước 1.

Rất tiếc, không đơn giản như vậy.

Biểu định kiểu chặn việc thực thi JavaScript vì nội dung của biểu định kiểu được dùng để tạo CSSOM. Vì có thể JavaScript đang tải sẽ cần truy cập vào CSSOM, nên quá trình thực thi phải bị trì hoãn cho đến khi CSSOM được tạo hoàn toàn.

Vấn đề là trình duyệt có thể tạo CSSOM mà không cần tải phông chữ xuống. Điều này có nghĩa là nếu bạn thêm một điểm đánh dấu vào DOM thông qua thẻ tập lệnh nội tuyến ngay sau thẻ <link> của biểu định kiểu phông chữ, thì có thể điểm đánh dấu sẽ xuất hiện trước khi phông chữ được tải đầy đủ.

Cho đến khi các sự kiện tải phông chữ có sẵn trong trình duyệt, bạn cần JavaScript để xác định thời điểm phông chữ thực sự hoạt động và sẵn sàng sử dụng trên trang. Rất may, việc tải phông chữ qua JavaScript cũng giúp cải thiện hiệu suất, vì việc này không yêu cầu thêm một yêu cầu chặn nào đối với tệp CSS.

Hầu hết các phông chữ web (bao gồm cả phông chữ của Google, Typekit và font.com) đều có thể được tải thông qua tập lệnh webfont.js. Tập lệnh này do Google và Typekit cùng phát triển.

Để cập nhật tài liệu chính nhằm sử dụng webfont.js để tải các phông chữ (thay vì thẻ <link>), hãy thay thế phần phông chữ của mã bằng nội dung sau:

<!-- Start fonts -->
<script>
window.WebFontConfig = {
  google: {families: ['Roboto:400,700,400italic']},
  timeout: 10000,
  active: function() {
    performance.mark('fonts:active');
  }
};
</script>
<script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
<!-- End fonts -->

Có hai điều quan trọng cần lưu ý về đoạn mã trên:

  • Thao tác này sẽ tạo dấu "fonts:active" trong lệnh gọi lại đang hoạt động, nhờ đó, sau này bạn có thể đo thời gian tải phông chữ.
  • Thẻ <script> tải webfonts.js có chứa thuộc tính async, nên thẻ này sẽ không chặn việc phân tích cú pháp hoặc hiển thị phần còn lại của tài liệu (điều này không đúng với thẻ <link>).

Mặc dù đoạn mã trên tạo ra dấu "fonts:active", nhưng việc đo lường dấu này và ghi nhật ký vào bảng điều khiển không đơn giản như đối với dấu "css:unblock". Lý do là quá trình tải phông chữ hiện diễn ra không đồng bộ, vì vậy, nếu bạn cố gắng đo lường dấu "fonts:active" trong trình xử lý window.onload (như bạn đã làm với "css:unblock"), thì rất có thể phông chữ sẽ chưa được tải.

Để giải quyết vấn đề này, bạn có thể tạo một promise được giải quyết sau khi phông chữ được tải. Hàm sau đây sẽ thực hiện việc này cho bạn. Sao chép và dán vào tệp perf-analytics.js:

/**
 * Creates a promise that is resolved once the web fonts are fully load or
 * is reject if the fonts fail to load. The resolved callback calculates the
 * time duration between the responseEnd timing event and when the web fonts
 * are downloaded and active. If an error occurs loading the font, this fact
 * is logged to the console.
 */
function measureWebfontPerfAndFailures() {
  new Promise(function(resolve, reject) {
    // The classes `wf-active` or `wf-inactive` are added to the <html>
    // element once the fonts are loaded (or error).
    var loaded = /wf-(in)?active/.exec(document.documentElement.className);
    var success = loaded && !loaded[1]; // No "in" in the capture group.
    // If the fonts are already done loading, resolve immediately.
    // Otherwise resolve/reject in the active/inactive callbacks, respectively.
    if (loaded) {
      success ? resolve() : reject();
    }
    else {
      var originalAciveCallback = WebFontConfig.active;
      WebFontConfig.inactive = reject;
      WebFontConfig.active = function() {
        originalAciveCallback();
        resolve();
      };
      // In case the webfont.js script fails to load, always reject the
      // promise after the timeout amount.
      setTimeout(reject, WebFontConfig.timeout);
    }
  })
  .then(function() {
    console.log('Fonts', 'active', measureDuration('fonts:active'));
  })
  .catch(function() {
    console.error('Error loading web fonts')
  });
}

Đồng thời, hãy cập nhật trình xử lý window.onload để gọi hàm mới này

window.onload = function() {
  measureCssUnblockTime();
  measureWebfontPerfAndFailures();
};

Sau khi hoàn tất bước này, mã của bạn sẽ khớp với mã trong thư mục 02-fonts của kho lưu trữ lớp học lập trình.

Nếu tải trang này trong trình duyệt và mở bảng điều khiển dành cho nhà phát triển, bạn sẽ thấy kết quả tương tự như sau:

Screen Shot 2016-05-17 at 11.13.22 AM.png

Việc biết thời điểm một hình ảnh xuất hiện không đơn giản như bạn nghĩ. Bạn biết từ các bước trước rằng biểu định kiểu và thẻ <script> đồng bộ có thể chặn quá trình kết xuất, phân tích cú pháp và thực thi tập lệnh. Có thể bạn không biết rằng cả hai đều không chặn trình quét tải trước của trình duyệt.

Trình quét tải trước là một thành phần mà mọi trình duyệt hiện đại đều triển khai như một trong nhiều nỗ lực cải thiện hiệu suất, ngay cả trên những trang web không chú trọng đến hiệu suất có chứa nhiều tài sản chặn. Ý tưởng là mặc dù một số thành phần có thể chặn việc phân tích cú pháp hoặc hiển thị hoặc thực thi tập lệnh, nhưng chúng không cần chặn hoạt động tải xuống. Vì vậy, trình duyệt sẽ quét tệp HTML trước khi bắt đầu tạo DOM và tìm kiếm những tài sản mà trình duyệt có thể bắt đầu tải xuống ngay lập tức.

Đối với hình ảnh, điều này có nghĩa là có khả năng cao hình ảnh của bạn đã được tải xuống vào thời điểm chúng được thêm vào DOM. Điều này cũng có nghĩa là thời điểm tải một hình ảnh xuống không nhất thiết là một chỉ số hiệu suất tốt. Chỉ số hiệu suất mà bạn nên quan tâm là thời điểm người dùng nhìn thấy hình ảnh.

Khi một hình ảnh được tải xuống trước khi được thêm vào DOM, thời điểm hình ảnh đó xuất hiện là thời điểm hình ảnh đó nằm trong DOM. Mặt khác, nếu một hình ảnh không được tải xuống trước khi được thêm vào DOM, thì thời điểm hình ảnh đó xuất hiện là khi trình xử lý onload của hình ảnh đó kích hoạt.

Vì vậy, để biết thời điểm một hình ảnh xuất hiện, bạn phải xử lý cả hai trường hợp.

Bạn có thể thực hiện việc này bằng cách thêm các dấu trong trình xử lý onload của mỗi hình ảnh cũng như trong thẻ <script> nội tuyến ngay sau hình ảnh cuối cùng trong DOM. Ý tưởng là dấu đánh dấu xuất hiện sau cùng sẽ là dấu đánh dấu biểu thị thời điểm tất cả hình ảnh đều hiển thị.

Để thêm dấu cho cả khi hình ảnh được tải và khi hình ảnh được kết xuất, hãy cập nhật mã hình ảnh trong index.html như sau:

<!-- Start images -->
<div class="gallery">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
  <img onload="performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>performance.mark('img:visible')</script>
<!-- End images -->

Vì phương thức performance.measure cho một tên dấu cụ thể sẽ luôn sử dụng dấu cuối cùng (nếu tìm thấy nhiều dấu có cùng tên), nên bạn có thể sử dụng hàm tiện ích measureDuration trong tệp perf-analytics.js cho việc này mà không cần sửa đổi gì thêm:

/**
 * Calculates the time duration between the responseEnd timing event and when
 * all images are loaded and visible on the page, then logs that value to the
 * console.
 */
function measureImagesVisibleTime() {
  console.log('Images', 'visible', measureDuration('img:visible'));
}

Thêm hàm trên vào tệp perf-analytics.js, rồi cập nhật trình xử lý window.onload để gọi hàm đó:

window.onload = function() {
  measureCssBlockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
};

Sau khi hoàn tất bước này, mã của bạn sẽ khớp với mã trong thư mục 03-images của kho lưu trữ lớp học lập trình.

Nếu tải trang này trong trình duyệt và mở bảng điều khiển dành cho nhà phát triển, bạn sẽ thấy kết quả tương tự như sau:

Screen Shot 2016-05-17 at 11.13.39 AM.png

Vì các thẻ <script> không có thuộc tính async sẽ chặn việc phân tích cú pháp DOM cho đến khi cả hai thẻ này được tải xuống và thực thi, nên bạn có thể xác định thời điểm tất cả tập lệnh đã thực thi xong bằng cách thêm một dấu trong thẻ tập lệnh nội tuyến ngay sau <script> đồng bộ cuối cùng trong DOM.

Xin lưu ý rằng việc sử dụng trình xử lý onload sẽ không hoạt động trong trường hợp này vì trình duyệt phải thực thi tập lệnh sau khi tải tập lệnh đó và việc này mất thời gian. Một tập lệnh tải nhanh nhưng thực thi chậm cũng có thể gây ra vấn đề tương tự như một tập lệnh tải chậm.

Để theo dõi thời điểm tất cả JavaScript được tải và thực thi trong tài liệu chính, hãy cập nhật phần JavaScript trong index.html bằng đoạn mã sau:

<!-- Start JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>performance.mark('js:execute');</script>
<!-- End JavaScript -->

Thao tác này sẽ thêm một dấu có tên "js:execute" ngay sau khi các tập lệnh cho jQuery và các trình bổ trợ của Bootstrap đã tải xuống và hoàn tất quá trình thực thi.

Để đo thời gian cần thiết, hãy thêm hàm sau vào perf-analytics.js:

/**
 * Calculates the time duration between the responseEnd timing event and when
 * all synchronous JavaScript files have been downloaded and executed, then
 * logs that value to the console.
 */
function measureJavaSciptExecutionTime() {
  console.log('JavaScript', 'execute', measureDuration('js:execute'));
}

Sau đó, gọi hàm này từ trình xử lý window.onload:

window.onload = function() {
  measureCssBlockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
  measureJavaSciptExecutionTime();
};

Sau khi hoàn tất bước này, mã của bạn sẽ khớp với mã trong thư mục 04-javascript của kho lưu trữ lớp học lập trình.

Nếu tải trang này trong trình duyệt và mở bảng điều khiển dành cho nhà phát triển, bạn sẽ thấy kết quả tương tự như sau:

Screen Shot 2016-05-17 at 11.14.03 AM.png

Không phải trình duyệt nào cũng hỗ trợ các lời hứa JavaScript hoặc User Timing API. Nếu chạy mã mà bạn đã viết cho đến nay trong một trình duyệt không hỗ trợ một trong các tính năng này, bạn sẽ gặp lỗi.

Để xử lý vấn đề này, bạn có thể sử dụng tính năng phát hiện. Thêm đoạn mã sau đây ngay trước phần phông chữ. Dòng JavaScript này phát hiện khả năng hỗ trợ phương thức performance.mark, vì vậy, bạn phải thêm dòng này vào trang trước khi sử dụng phương thức đó:

<!-- Start feature detects -->
<script>window.__perf = window.performance && performance.mark;</script>
<!-- End feature detects -->

Tiếp theo, ở bất kỳ vị trí nào trong index.html mà bạn gọi performance.mark, hãy thêm tiền tố cho vị trí đó bằng tính năng phát hiện. Dưới đây là một ví dụ cập nhật mã trong khối hình ảnh, nhưng bạn cũng phải nhớ cập nhật các phần khác.

<!-- Start images -->
<div class="gallery">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
  <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
</div>
<script>__perf && performance.mark('img:visible')</script>
<!-- End images -->

Giờ đây, khi biến __perf được đặt trên window, bạn cũng có thể sử dụng biến này trong perf-analytics.js để đảm bảo bạn không gọi một phương thức không được hỗ trợ trong trình duyệt hiện tại.

Bạn phải cập nhật hàm measureDuration để thêm điều kiện sau:

function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    // ...
  }
}

Cuối cùng, vì không phải trình duyệt nào cũng hỗ trợ các promise của JavaScript, nên hàm measureWebfontPerfAndFailures cũng phải được bao bọc trong một điều kiện:

function measureWebfontPerfAndFailures() {
  if (window.Promise) {
    // ...
  }
}

Giờ đây, bạn có thể chạy mã của mình trong mọi trình duyệt mà không gặp vấn đề gì.

Sau khi hoàn tất bước này, mã của bạn sẽ khớp với mã trong thư mục 05-feature-detects của kho lưu trữ lớp học lập trình.

Bước cuối cùng trong lớp học lập trình này là lấy dữ liệu đang được ghi vào bảng điều khiển và thay vào đó, hãy gửi dữ liệu đó đến Google Analytics. Và trước khi có thể gửi dữ liệu đến Google Analytics, bạn phải thêm thư viện analytics.jsđoạn mã theo dõi mặc định vào trang của mình.

Thêm mã sau vào index.html sau khối JavaScript chính nhưng trước khi tập lệnh perf-analytics.js được tải:

<!-- Start analytics tracking snippet -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src="https://www.google-analytics.com/analytics_debug.js"></script>
<!-- End analytics tracking snippet -->

Nếu đã thêm Google Analytics vào một trang web trước đây, bạn sẽ biết rằng bạn phải thay thế phần giữ chỗ "UA-XXXXX-Y" bằng mã theo dõi mà bạn nhận được khi tạo một tài sản mới trong Google Analytics.

Đoạn mã theo dõi analytics.js thực hiện 4 việc chính:

Mặc dù dữ liệu chỉ được thu thập từ lượt xem trang là hữu ích, nhưng dữ liệu này không cung cấp đầy đủ thông tin. Để nắm được thông tin chi tiết hơn về trải nghiệm của người dùng trên trang web hoặc ứng dụng của bạn, bạn phải gửi thêm dữ liệu tương tác đến Google Analytics.

Google Analytics hỗ trợ nhiều loại dữ liệu tương tác: lượt xem trang, sự kiện, tương tác trên mạng xã hội, ngoại lệ và (cuối cùng nhưng không kém phần quan trọng) thời gian của người dùng. Để gửi dữ liệu về thời gian của người dùng đến Google Analytics, bạn có thể sử dụng chữ ký lệnh sau:

ga('send', 'timing', timingCategory, timingVar, timingValue);

Trong đó, timingCategory là một chuỗi cho phép bạn sắp xếp các lượt truy cập về thời gian thành nhóm logic, timingVar là biến mà bạn đang đo lường và timingValue là khoảng thời gian thực tế tính bằng mili giây.

Để xem cách hoạt động của câu lệnh này trên thực tế, bạn có thể cập nhật câu lệnh console.log trong hàm measureCssUnblockTime như sau:

ga('send', 'timing', 'CSS', 'unblock', measureDuration('css:unblock'));

Mặc dù đoạn mã trên sẽ hoạt động trong một số trường hợp, nhưng bạn cần lưu ý 2 điểm quan trọng sau:

  • Bước trước đó đã cập nhật hàm measureDuration để chỉ chạy nếu trình duyệt hỗ trợ User Timings API (API Thời gian của người dùng). Điều này có nghĩa là đôi khi hàm này sẽ trả về undefined. Vì không có lý do gì để gửi dữ liệu không xác định đến Google Analytics (trong một số trường hợp, việc này thậm chí có thể làm rối báo cáo của bạn), nên bạn chỉ nên gửi lượt truy cập tính thời gian này nếu measureDuration trả về một giá trị.
  • Khi measureDuration trả về một giá trị, đó là DOMHighResTimeStamp, có độ chính xác lớn hơn độ chính xác tính bằng mili giây. Vì timingValue trong Google Analytics phải là một số nguyên, nên bạn phải làm tròn giá trị do measureDuration trả về.

Để tính đến những điểm cần lưu ý này, hãy cập nhật câu lệnh trả về trong hàm measureDuration để làm tròn giá trị trả về:

function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    // ...
    return Math.round(measure.duration);
  }
}

Đồng thời, hãy cập nhật các lệnh định thời gian để chỉ chạy nếu có giá trị cho chỉ số được đề cập. Ví dụ: bạn nên cập nhật hàm measureCssUnblockTime thành một hàm như sau:

function measureCssUnblockTime() {
  var cssUnblockTime = measureDuration('css:unblock');
  if (cssUnblockTime) {
    ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
  }
}

Bạn sẽ cần thực hiện các nội dung cập nhật tương tự cho tất cả các hàm đo lường khác. Sau khi hoàn tất, tệp perf-analytics.js cuối cùng sẽ có dạng như sau:

window.onload = function() {
  measureCssUnblockTime();
  measureWebfontPerfAndFailures();
  measureImagesVisibleTime();
  measureJavaSciptExecutionTime();
};


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the CSS stops blocking rendering, then sends this measurement to Google
 * Analytics via a timing hit.
 */
function measureCssUnblockTime() {
  var cssUnblockTime = measureDuration('css:unblock');
  if (cssUnblockTime) {
    ga('send', 'timing', 'CSS', 'unblock', cssUnblockTime);
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * the web fonts are downloaded and active, then sends this measurement to
 * Google Analytics via a timing hit. If an error occurs loading the font, an
 * error event is sent to Google Analytics.
 */
function measureWebfontPerfAndFailures() {
  if (window.Promise) {
    new Promise(function(resolve, reject) {
      var loaded = /wf-(in)?active/.exec(document.documentElement.className);
      var success = loaded && !loaded[1]; // No "in" in the capture group.
      if (loaded) {
        success ? resolve() : reject();
      }
      else {
        var originalAciveCallback = WebFontConfig.active;
        WebFontConfig.inactive = reject;
        WebFontConfig.active = function() {
          originalAciveCallback();
          resolve();
        };
        // In case the webfont.js script failed to load.
        setTimeout(reject, WebFontConfig.timeout);
      }
    })
    .then(function() {
      var fontsActiveTime = measureDuration('fonts:active');
      if (fontsActiveTime) {
        ga('send', 'timing', 'Fonts', 'active', fontsActiveTime);
      }
    })
    .catch(function() {
      ga('send', 'event', 'Fonts', 'error');
    });
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * all images are loaded and visible on the page, then sends this measurement
 * to Google Analytics via a timing hit.
 */
function measureImagesVisibleTime() {
  var imgVisibleTime = measureDuration('img:visible');
  if (imgVisibleTime) {
    ga('send', 'timing', 'Images', 'visible', imgVisibleTime);
  }
}


/**
 * Calculates the time duration between the responseEnd timing event and when
 * all synchronous JavaScript files are downloaded and executed, then sends
 * this measurement to Google Analytics via a timing hit.
 */
function measureJavaSciptExecutionTime() {
  var jsExecuteTime = measureDuration('js:execute');
  if (jsExecuteTime) {
    ga('send', 'timing', 'JavaScript', 'execute', jsExecuteTime);
  }
}


/**
 * Accepts a mark name and an optional reference point in the navigation timing
 * API and returns the time duration between the reference point and the last
 * mark (chronologically). The return value is rounded to the nearest whole
 * number to be compatible with Google Analytics.
 * @param {string} mark The mark name.
 * @param {string=} opt_reference An optional reference point from the
 *     navigation timing API. Defaults to 'responseEnd'.
 * @return {?number} The time duration as an integer or undefined if no
 *     matching marks can be found.
 */
function measureDuration(mark, opt_reference) {
  if (window.__perf) {
    var reference = opt_reference || 'responseEnd';
    var name = reference + ':' + mark;

    // Clears any existing measurements with the same name.
    performance.clearMeasures(name);

    // Creates a new measurement from the reference point to the specified mark.
    // If more than one mark with this name exists, the most recent one is used.
    performance.measure(name, reference, mark);

    // Gets the value of the measurement just created.
    var measure = performance.getEntriesByName(name)[0];

    // Returns the measure duration.
    return Math.round(measure.duration);
  }
}

Và tệp index.html cuối cùng sẽ có dạng như sau:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Performance Analytics Demo</title>

  <!-- Start navigation timing feature detect -->
  <script>window.__perf = window.performance && performance.mark;</script>
  <!-- End navigation timing feature detect -->

  <!-- Start fonts -->
  <script>
  window.WebFontConfig = {
    google: {families: ['Roboto:400,700,400italic']},
    timeout: 10000,
    active: function() {
      __perf && performance.mark('fonts:active');
    }
  };
  </script>
  <script async src="https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
  <!-- End fonts -->

  <!-- Start CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
  <style>
    body { font-family: Roboto, sans-serif; margin: 1em; }
    img { float: left; height: auto; width: 33.33%; }
    .gallery { overflow: hidden; }
  </style>
  <script>__perf && performance.mark('css:unblock');</script>
  <!-- End CSS -->

</head>
<body>

  <div class="container">

    <!-- Start images -->
    <div class="gallery">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/1/">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/2/">
      <img onload="__perf && performance.mark('img:visible')" src="http://lorempixel.com/380/200/animals/3/">
    </div>
    <script>__perf && performance.mark('img:visible')</script>
    <!-- End images -->

    <h1>Performance Analytics Demo</h1>
    <p>Real performance data from real users.</p>

  </div>

  <!-- Start JavaScript -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  <script>__perf && performance.mark('js:execute');</script>
  <!-- End JavaScript -->

  <!-- Start analytics tracking snippet -->
  <script>
  window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
  ga('create', 'UA-XXXXX-Y', 'auto');
  ga('send', 'pageview');
  </script>
  <script async src="https://www.google-analytics.com/analytics.js"></script>
  <!-- End analytics tracking snippet -->

  <!-- Start performance analytics -->
  <script async src="perf-analytics.js"></script>
  <!-- End performance analytics -->

</body>
</html>

Nếu tải trang này và xem các yêu cầu trong bảng điều khiển mạng, bạn sẽ thấy nội dung như sau xuất hiện:

Screen Shot 2016-05-10 at 6.57.23 PM.png

Điều này rất hữu ích, nhưng có thể gây khó khăn khi xem dữ liệu này dưới dạng một yêu cầu được mã hoá bằng URL. Và nếu vì bất kỳ lý do gì mà bạn không thấy những yêu cầu này, thì rất khó để theo dõi xem lỗi xảy ra ở đâu.

Cách tốt hơn khi phát triển cục bộ là sử dụng phiên bản gỡ lỗi của analytics.js. Phiên bản này sẽ ghi lại thông tin gỡ lỗi hữu ích vào bảng điều khiển khi mỗi lệnh analytics.js được chạy. Nếu bạn

cập nhật URL analytics.js trong index.html thành analytics_debug.js và mở bảng điều khiển trình duyệt, bạn sẽ thấy các câu lệnh được in ra như sau:

Screen Shot 2016-05-10 at 6.54.13 PM.png

Giờ đây, khi đã hiểu cách triển khai tính năng đo lường hiệu suất cho trang minh hoạ này, bạn có thể thử thêm tính năng này vào trang web của riêng mình, gửi dữ liệu người dùng thực tế đến Google Analytics.

Báo cáo về dữ liệu bạn đã thu thập

Sau khi thu thập dữ liệu hiệu suất trong vài ngày, bạn sẽ có thể báo cáo về dữ liệu đó để nắm được thông tin chi tiết hữu ích về tốc độ tải thực tế của trang web và tài nguyên trên trang web đối với người dùng thực.

Để chuyển đến báo cáo Thời gian của người dùng trong Google Analytics, hãy nhấp vào thẻ Báo cáo ở trên cùng rồi chọn "Hành vi > Tốc độ trang web > Thời gian của người dùng" trong bảng điều hướng bên (hoặc làm theo hướng dẫn để xem báo cáo Thời gian của người dùng trong Trung tâm trợ giúp).

Khi tải báo cáo Thời gian của người dùng trong Google Analytics, bạn sẽ có thể thấy các danh mục thời gian tương ứng với dữ liệu mà bạn đã gửi. Nhấp vào một trong các chỉ số đó để xem hình ảnh trực quan chi tiết về dữ liệu thời gian. Hình ảnh sau đây là ví dụ về thời gian tải phông chữ trên một trang web thực tế sử dụng Phông chữ của Google trong 24 giờ qua.

Screen Shot 2016-05-10 at 7.16.07 PM.png

Xin chúc mừng! Bạn đã hoàn tất thành công lớp học lập trình này. Nếu bạn muốn tìm hiểu sâu hơn, phần tiếp theo sẽ đưa ra một số đề xuất về cách xây dựng dựa trên mã này để có được thông tin chi tiết hơn nữa.

Các chỉ số hiệu suất được đề cập trong lớp học lập trình này rất quan trọng để đo lường cách trang web của bạn tải cho người dùng thực, nhưng đó chỉ là bước khởi đầu. Nếu bạn muốn tìm hiểu sâu hơn về số liệu phân tích hiệu suất, thì bước tiếp theo đơn giản là theo dõi thêm các chỉ số.

Trong lớp học lập trình này, bạn đã theo dõi các chỉ số liên quan đến thời điểm người dùng có thể truy cập vào tài nguyên. Nếu muốn, bạn có thể chia nhỏ hầu hết các mục này hơn nữa. Ví dụ: thay vì chỉ đo lường thời điểm JavaScript thực thi xong, bạn có thể đo lường thời điểm JavaScript bắt đầu tải, thời điểm tải xong, thời điểm bắt đầu thực thi và cuối cùng là thời điểm thực thi xong. Mỗi chỉ số này có thể cho thấy một vấn đề mà chỉ một chỉ số không thể cho biết.

Ngoài việc xem xét chi tiết hơn, bạn cũng nên suy nghĩ một cách toàn diện hơn về chiến lược phân tích hiệu suất chung của mình. Mục tiêu là gì? Thành công là gì?

Khi nói đến bất kỳ loại số liệu phân tích nào, bạn thường muốn bắt đầu bằng một câu hỏi nào đó, rồi tìm ra cách sử dụng dữ liệu để trả lời câu hỏi đó.

Ví dụ: hãy xem xét danh sách câu hỏi sau đây và cách bạn sử dụng kiến thức đã học được trong lớp học lập trình này để dùng số liệu phân tích nhằm trả lời các câu hỏi đó:

  • Giá trị của các chỉ số mà bạn đang theo dõi có giảm hay tăng theo thời gian không?
  • Việc sử dụng tính năng lưu vào bộ nhớ đệm ngoại tuyến thông qua worker dịch vụ hoặc bộ nhớ cục bộ sẽ ảnh hưởng như thế nào đến hiệu suất tổng thể của trang web?
  • Tài nguyên của bạn có đang được tải một cách tối ưu không? Tức là có khoảng cách lớn giữa thời điểm tài nguyên được tải xuống và thời điểm tài nguyên có thể sử dụng được không?
  • Có mối tương quan nào giữa hiệu suất và các chỉ số khác mà bạn đang theo dõi (ví dụ: tỷ lệ đăng ký, thời gian trên trang web, lượt mua hàng, v.v.) không?

Cuối cùng, nếu bạn muốn tìm hiểu thêm về hiệu suất trang web hoặc Google Analytics, hãy tham khảo một số tài nguyên hữu ích sau đây để bắt đầu: