Điều hướng và thời gian tài nguyên

Định thời điều hướng là các chỉ số đo sự kiện điều hướng tài liệu của trình duyệt. Định thời tài nguyên là các phép đo thời gian mạng chi tiết liên quan đến việc tải tài nguyên của một ứng dụng. Cả hai đều cung cấp cùng các thuộc tính chỉ đọc, nhưng định thời điều hướng đo thời gian của tài liệu chính, còn định thời tài nguyên cung cấp thời gian cho mọi tài sản hoặc tài nguyên mà tài liệu chính đó gọi tới và các tài nguyên được các tài nguyên đó yêu cầu.

Các mốc thời gian hiệu năng tổng quát bên dưới đã bị loại bỏ để thay thế bằng API mục nhập hiệu năng, API này cho phép đánh dấu và đo thời gian trong suốt quá trình điều hướng và tải tài nguyên. Dù đã bị loại bỏ, chúng vẫn được hỗ trợ trong mọi trình duyệt.

Các mốc thời gian hiệu năng

PerformanceTiming API, một API JavaScript dùng để đo hiệu năng tải của trang được yêu cầu, đã bị loại bỏ nhưng vẫn được hỗ trợ trong mọi trình duyệt. Nó đã được thay thế bằng API PerformanceNavigationTiming.

Performance timing API cung cấp các thời điểm chỉ đọc, tính bằng mili giây (ms), mô tả từng mốc trong quá trình tải trang đã được đạt tới vào thời điểm nào. Như được hiển thị trong hình bên dưới, quá trình điều hướng đi qua navigationStart, unloadEventStart, unloadEventEnd, redirectStart, redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd, domLoading, domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, domComplete, loadEventStartloadEventEnd.

Các chỉ số sự kiện Navigation Timing

Từ các chỉ số ở trên, cộng thêm một chút toán học, chúng ta có thể tính nhiều chỉ số quan trọng như thời gian đến byte đầu tiên, thời gian tải trang, tra cứu DNS và việc kết nối có an toàn hay không.

Để giúp đo thời gian cần thiết để hoàn tất tất cả các bước, Performance Timing API cung cấp các phép đo chỉ đọc về navigation timings. Để xem và lấy thời gian của ứng dụng, ta nhập:

js
let time = window.performance.timing;

Sau đó chúng ta có thể dùng kết quả để đo ứng dụng hoạt động tốt đến mức nào.

Nhập window.performance.timing trong console sẽ liệt kê tất cả các mốc thời gian trong giao diện PerformanceNavigationTiming

Thứ tự là:

Performance Timings Chi tiết
navigationStart Khi lời nhắc dỡ tải kết thúc trên tài liệu trước đó trong cùng ngữ cảnh duyệt. Nếu không có tài liệu trước đó, giá trị này sẽ giống PerformanceTiming.fetchStart.
secureConnectionStart Khi bắt đầu bắt tay kết nối an toàn. Nếu không có kết nối như vậy được yêu cầu, nó trả về 0.
redirectStart Khi chuyển hướng HTTP đầu tiên bắt đầu. Nếu không có chuyển hướng, hoặc nếu một trong các chuyển hướng không cùng nguồn gốc, giá trị trả về là 0.
redirectEnd

Khi chuyển hướng HTTP cuối cùng hoàn tất, tức là khi byte cuối cùng của phản hồi HTTP đã được nhận. Nếu không có chuyển hướng, hoặc nếu một trong các chuyển hướng không cùng nguồn gốc, giá trị trả về là 0.

connectEnd Khi kết nối được mở trên mạng. Nếu tầng truyền tải báo lỗi và quá trình thiết lập kết nối bắt đầu lại, thời điểm kết thúc thiết lập kết nối cuối cùng sẽ được trả về. Nếu dùng kết nối liên tục, giá trị sẽ giống PerformanceTiming.fetchStart. Một kết nối được xem là mở khi toàn bộ quá trình bắt tay kết nối an toàn hoặc xác thực SOCKS đã kết thúc.
connectStart Khi yêu cầu mở kết nối được gửi tới mạng. Nếu tầng truyền tải báo lỗi và quá trình thiết lập kết nối bắt đầu lại, thời điểm bắt đầu thiết lập kết nối cuối cùng sẽ được trả về. Nếu dùng kết nối liên tục, giá trị sẽ giống PerformanceTiming.fetchStart.
domainLookupEnd Khi tra cứu tên miền hoàn tất. Nếu dùng kết nối liên tục, hoặc thông tin được lưu trong bộ nhớ đệm hay tài nguyên cục bộ, giá trị sẽ giống PerformanceTiming.fetchStart.
domainLookupStart Khi tra cứu tên miền bắt đầu. Nếu dùng kết nối liên tục, hoặc thông tin được lưu trong bộ nhớ đệm hay tài nguyên cục bộ, giá trị sẽ giống PerformanceTiming.fetchStart.
fetchStart Khi trình duyệt đã sẵn sàng để lấy tài liệu bằng một yêu cầu HTTP. Thời điểm này xảy ra trước lần kiểm tra bất kỳ application cache nào.
requestStart Khi trình duyệt gửi yêu cầu để lấy tài liệu thực tế, từ máy chủ hoặc từ bộ nhớ đệm. Nếu tầng truyền tải thất bại sau khi yêu cầu bắt đầu và kết nối được mở lại, thuộc tính này sẽ được đặt thành thời điểm tương ứng với yêu cầu mới.
responseStart Khi trình duyệt nhận byte đầu tiên của phản hồi, từ máy chủ, từ bộ nhớ đệm hoặc từ tài nguyên cục bộ.
responseEnd Khi trình duyệt nhận byte cuối cùng của phản hồi, hoặc khi kết nối đóng nếu việc đó xảy ra trước, từ máy chủ, bộ nhớ đệm hoặc tài nguyên cục bộ.
domLoading Khi trình phân tích cú pháp bắt đầu làm việc, tức là khi Document.readyState của nó đổi thành 'loading' và sự kiện readystatechange tương ứng được phát ra.
unloadEventStart Khi sự kiện unload được phát ra, cho biết thời điểm tài liệu trước đó trong cửa sổ bắt đầu dỡ tải. Nếu không có tài liệu trước đó, hoặc nếu tài liệu trước đó hay một trong các chuyển hướng cần thiết không cùng nguồn gốc, giá trị trả về là 0.
unloadEventEnd Khi trình xử lý sự kiện unload kết thúc. Nếu không có tài liệu trước đó, hoặc nếu tài liệu trước đó hay một trong các chuyển hướng cần thiết không cùng nguồn gốc, giá trị trả về là 0.
domInteractive Khi trình phân tích cú pháp hoàn tất công việc trên tài liệu chính, tức là khi Document.readyState đổi thành 'interactive' và sự kiện readystatechange tương ứng được phát ra.
domContentLoadedEventStart Ngay trước khi trình phân tích cú pháp phát sự kiện DOMContentLoaded, tức là ngay sau khi tất cả các script cần được thực thi ngay sau khi phân tích xong đã được thực thi.
domContentLoadedEventEnd Ngay sau khi tất cả các script cần được thực thi càng sớm càng tốt, theo thứ tự hay không theo thứ tự, đã được thực thi xong.
domComplete Khi trình phân tích cú pháp hoàn tất công việc trên tài liệu chính, tức là khi Document.readyState đổi thành 'complete' và sự kiện readystatechange tương ứng được phát ra.
loadEventStart Khi sự kiện load được gửi cho tài liệu hiện tại. Nếu sự kiện này chưa được gửi, nó trả về 0.
loadEventEnd Khi trình xử lý sự kiện load kết thúc, tức là khi sự kiện load hoàn tất. Nếu sự kiện này chưa được gửi, hoặc chưa hoàn tất, nó trả về 0.

Tính toán thời gian

Chúng ta có thể dùng các giá trị này để đo những khoảng thời gian cụ thể:

js
const dns = time.domainLookupEnd - time.domainLookupStart;
const tcp = time.connectEnd - time.connectStart;
const tls = time.requestStart - time.secureConnectionStart;

Thời gian đến byte đầu tiên

Time to First Byte là khoảng thời gian giữa navigationStart (bắt đầu điều hướng) và responseStart (khi byte đầu tiên của dữ liệu phản hồi được nhận) có sẵn trong API performanceTiming:

js
const ttfb = time.responseStart - time.navigationStart;

Thời gian tải trang

Page load time là khoảng thời gian giữa navigationStart và thời điểm bắt đầu gửi sự kiện load cho tài liệu hiện tại. Chúng chỉ có sẵn trong API performanceTiming.

js
let pageloadTime = time.loadEventStart - time.navigationStart;

Thời gian tra cứu DNS

Thời gian tra cứu DNS là khoảng thời gian giữa domainLookupStartdomainLookupEnd. Cả hai đều có trong cả API performanceTimingperformanceNavigationTiming.

js
const dns = time.domainLookupEnd - time.domainLookupStart;

TCP

Thời gian bắt tay TCP là khoảng thời gian giữa lúc bắt đầu và kết thúc kết nối:

js
const tcp = time.connectEnd - time.connectStart;

Thương lượng TLS

secureConnectionStart sẽ là undefined nếu không khả dụng, 0 nếu không dùng HTTPS, hoặc là một dấu thời gian nếu khả dụng và được dùng. Nói cách khác, nếu một kết nối an toàn được dùng thì secureConnectionStart sẽ là truthy, và thời gian giữa secureConnectionStartrequestStart sẽ lớn hơn 0.

js
const tls = time.requestStart - time.secureConnectionStart;

API mục nhập hiệu năng

Các mốc thời gian hiệu năng tổng quát ở trên đã bị loại bỏ nhưng vẫn được hỗ trợ đầy đủ. Hiện giờ chúng ta có API mục nhập hiệu năng, API này cung cấp khả năng đánh dấu và đo thời gian dọc theo quá trình điều hướng và tải tài nguyên. Bạn cũng có thể tạo các dấu:

js
performance.getEntriesByType("navigation").forEach((navigation) => {
  console.dir(navigation);
});

performance.getEntriesByType("resource").forEach((resource) => {
  console.dir(resource);
});

performance.getEntriesByType("mark").forEach((mark) => {
  console.dir(mark);
});

performance.getEntriesByType("measure").forEach((measure) => {
  console.dir(measure);
});

performance.getEntriesByType("paint").forEach((paint) => {
  console.dir(paint);
});

performance.getEntriesByType("frame").forEach((frame) => {
  console.dir(frame);
});

Trong các trình duyệt hỗ trợ, bạn có thể dùng performance.getEntriesByType('paint') để truy vấn phép đo cho first-paintfirst-contentful-paint. Chúng ta dùng performance.getEntriesByType('navigation')performance.getEntriesByType('resource') để truy vấn lần lượt navigation timings và resource timings.

Định thời điều hướng

Khi người dùng yêu cầu một website hoặc ứng dụng, để đổ nội dung vào trình duyệt user agent đi qua một chuỗi bước, bao gồm tra cứu DNS, TCP handshake và thương lượng TLS, trước khi user agent thực hiện yêu cầu thực sự và các máy chủ trả về tài sản được yêu cầu. Sau đó, trình duyệt phân tích nội dung nhận được, xây dựng DOM, CSSOM, accessibility tree và render tree, cuối cùng hiển thị trang. Khi user agent ngừng phân tích tài liệu, nó đặt trạng thái sẵn sàng của tài liệu thành interactive. Nếu có các script trì hoãn cần được phân tích, nó sẽ làm vậy, rồi phát DOMContentLoaded, sau đó trạng thái sẵn sàng được đặt thành complete. Lúc này Document có thể xử lý các tác vụ sau tải, và sau đó tài liệu được đánh dấu là đã tải hoàn toàn.

js
const navigationTimings = performance.getEntriesByType("navigation");

performance.getEntriesByType('navigation') trả về một mảng các đối tượng PerformanceEntry cho navigation type.

Kết quả khi nhập performance.getEntriesByType('navigation'); trong console cho tài liệu này

Có thể rút ra rất nhiều từ các mốc thời gian này. Trong hình trên, thông qua thuộc tính name ta thấy tệp đang được đo là chính tài liệu này. Ở phần còn lại của giải thích, chúng ta dùng biến sau:

js
const timing = performance.getEntriesByType("navigation")[0];

Giao thức

Chúng ta có thể kiểm tra giao thức được dùng bằng cách truy vấn:

js
const protocol = timing.nextHopProtocol;

Nó trả về giao thức mạng được dùng để lấy tài nguyên: trong trường hợp này là h2 cho http/2.

Nén

Để lấy phần trăm tiết kiệm từ nén, chúng ta chia transferSize cho decodedBodySize, rồi lấy 100% trừ đi kết quả đó. Ta thấy mức tiết kiệm hơn 74%.

js
const compressionSavings = 1 - timing.transferSize / timing.decodedBodySize;

Ta cũng có thể dùng

js
const compressionSavings = 1 - timing.encodedBodySize / timing.decodedBodySize;

nhưng dùng transferSize sẽ bao gồm các byte overhead.

Để so sánh, chúng ta có thể xem tab mạng và thấy rằng đã truyền 22.04KB cho một tệp chưa nén có kích thước 87.24KB.

Chế độ xem số byte đã truyền và kích thước qua tab mạng

Nếu tính toán với các con số này, ta được kết quả giống nhau: 1 - (22.04 / 87.24) = 0.747. Định thời điều hướng cho chúng ta một cách để kiểm tra tự động kích thước truyền và phần tiết kiệm băng thông.

Lưu ý đây chỉ là kích thước của riêng tài liệu này: chỉ cho tài nguyên này, chứ không phải cho toàn bộ các tài nguyên cộng lại. Tuy nhiên, thời lượng, sự kiện tải và các mốc liên quan đến DOM là về toàn bộ điều hướng, không phải chỉ riêng tài sản này. Các ứng dụng web phía client có thể trông nhanh hơn trang này với kích thước truyền dưới 10000 và decoded body size dưới 30000, nhưng điều đó không có nghĩa là JavaScript, CSS hoặc media không làm tăng kích thước không cần thiết. Việc kiểm tra tỷ lệ nén là quan trọng nhưng cũng cần kiểm tra thời lượng và khoảng thời gian giữa lúc sự kiện DOMContentLoaded kết thúc và khi DOM hoàn tất, vì việc chạy JavaScript trên main thread trong thời gian dài có thể dẫn đến giao diện người dùng không phản hồi.

Thời gian yêu cầu

API không cung cấp mọi phép đo mà bạn có thể muốn. Ví dụ, yêu cầu mất bao lâu? Chúng ta có thể dùng các phép đo sẵn có để tìm câu trả lời.

Để đo thời gian phản hồi, trừ thời điểm bắt đầu yêu cầu khỏi thời điểm bắt đầu phản hồi. Request start là khoảnh khắc ngay trước khi user agent bắt đầu yêu cầu tài nguyên từ máy chủ, từ các application cache phù hợp hoặc từ tài nguyên cục bộ. Response start là thời điểm ngay sau khi bộ phân tích HTTP của user agent nhận byte đầu tiên của phản hồi từ các application cache phù hợp, từ tài nguyên cục bộ hoặc từ máy chủ, xảy ra sau khi yêu cầu được nhận và xử lý.

js
const request = timing.responseStart - timing.requestStart;

Thời lượng sự kiện tải

Bằng cách lấy dấu thời gian ngay trước khi sự kiện load của tài liệu hiện tại được phát và trừ đi thời điểm sự kiện load của tài liệu hiện tại hoàn tất, bạn có thể đo thời lượng của sự kiện load.

js
const load = timing.loadEventEnd - timing.loadEventStart;

Sự kiện DOMContentLoaded

Thời lượng sự kiện DOMContentLoaded được đo bằng cách trừ giá trị thời gian ngay trước khi user agent phát sự kiện DOMContentLoaded khỏi giá trị thời gian ngay sau khi sự kiện kết thúc. Giữ mức này ở 50ms hoặc thấp hơn giúp bảo đảm giao diện người dùng phản hồi tốt.

js
const DOMContentLoaded =
  timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart;

Thời lượng

Chúng ta được cung cấp giá trị thời lượng. Thời lượng là hiệu giữa thuộc tính PerformanceNavigationTiming.loadEventEndPerformanceEntry.startTime.

Giao diện PerformanceNavigationTiming cũng cung cấp thông tin về loại điều hướng bạn đang đo, trả về navigate, reload hoặc back_forward.

Định thời tài nguyên

Trong khi navigation timing dùng để đo hiệu năng của trang chính, thường là tệp HTML mà từ đó mọi tài sản khác được yêu cầu, resource timing đo thời gian cho các tài nguyên riêng lẻ, tức các tài sản được trang chính gọi tới và mọi tài sản mà các tài nguyên đó yêu cầu. Nhiều phép đo tương tự nhau: có tra cứu DNS, bắt tay TCP và bắt đầu kết nối an toàn, mỗi thứ diễn ra một lần cho mỗi miền.

Đồ họa các dấu thời gian Resource Timing

Điều chính cần xem xét cho từng tài nguyên.

Xem thêm