Theo dõi tổng mức sử dụng bộ nhớ trên trang web của bạn bằng measureUserAgentspecificMemory()

Tìm hiểu cách đo lường mức sử dụng bộ nhớ của trang web trong phiên bản chính thức để phát hiện sự hồi quy.

Ulan Degenbaev
Ulan Degenbaev

Trình duyệt tự động quản lý bộ nhớ của các trang web. Bất cứ khi nào một trang web tạo một đối tượng, trình duyệt sẽ phân bổ một phần bộ nhớ "dưới mui xe" để lưu trữ đối tượng đó. Vì bộ nhớ là một tài nguyên có giới hạn, nên trình duyệt sẽ thực hiện thu thập rác để phát hiện thời điểm một đối tượng không còn cần thiết và giải phóng vùng bộ nhớ cơ bản.

Tuy nhiên, tính năng phát hiện này chưa hoàn hảo và đã được chứng minh rằng việc phát hiện hoàn hảo là một nhiệm vụ bất khả thi. Do đó, trình duyệt sẽ ước chừng khái niệm "cần có đối tượng" bằng khái niệm "có thể truy cập vào đối tượng". Nếu trang web không thể truy cập vào một đối tượng thông qua các biến và trường của các đối tượng khác có thể truy cập, thì trình duyệt có thể thu hồi đối tượng một cách an toàn. Sự khác biệt giữa hai khái niệm này dẫn đến việc rò rỉ bộ nhớ như minh hoạ trong ví dụ sau.

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

Ở đây, mảng lớn hơn b không còn cần thiết nữa, nhưng trình duyệt không thu hồi mảng này vì vẫn có thể truy cập được thông qua object.b trong lệnh gọi lại. Do đó, bộ nhớ của mảng lớn hơn sẽ bị rò rỉ.

Lỗi rò rỉ bộ nhớ phổ biến trên Web. Bạn có thể dễ dàng tạo một lỗi này bằng cách quên huỷ đăng ký trình nghe sự kiện, vô tình chụp các đối tượng từ một iframe, không đóng worker, tích luỹ các đối tượng trong mảng, v.v. Nếu một trang web bị rò rỉ bộ nhớ, thì mức sử dụng bộ nhớ của trang web đó sẽ tăng lên theo thời gian và người dùng sẽ thấy trang web đó chậm và cồng kềnh.

Bước đầu tiên để giải quyết vấn đề này là đo lường vấn đề. API performance.measureUserAgentSpecificMemory() mới cho phép nhà phát triển đo lường mức sử dụng bộ nhớ của các trang web trong môi trường thực tế, từ đó phát hiện rò rỉ bộ nhớ trong quá trình kiểm thử cục bộ.

performance.measureUserAgentSpecificMemory() khác với API performance.memory cũ như thế nào?

Nếu đã quen với API performance.memory không chuẩn hiện có, bạn có thể thắc mắc API mới khác với API đó như thế nào. Điểm khác biệt chính là API cũ trả về kích thước của vùng nhớ khối xếp JavaScript, trong khi API mới ước tính bộ nhớ mà trang web sử dụng. Sự khác biệt này trở nên quan trọng khi Chrome chia sẻ cùng một vùng nhớ khối xếp với nhiều trang web (hoặc nhiều phiên bản của cùng một trang web). Trong những trường hợp như vậy, kết quả của API cũ có thể bị tắt tuỳ ý. Vì API cũ được xác định theo các thuật ngữ dành riêng cho việc triển khai, chẳng hạn như "vùng nhớ khối xếp", nên việc chuẩn hoá API này là vô vọng.

Một điểm khác biệt khác là API mới thực hiện việc đo lường bộ nhớ trong quá trình thu thập rác. Việc này giúp giảm độ nhiễu trong kết quả, nhưng có thể mất một chút thời gian cho đến khi kết quả được tạo. Xin lưu ý rằng các trình duyệt khác có thể quyết định triển khai API mới mà không cần dựa vào tính năng thu gom rác.

Các trường hợp sử dụng được đề xuất

Mức sử dụng bộ nhớ của một trang web phụ thuộc vào thời gian của các sự kiện, hành động của người dùng và các lần thu gom rác. Đó là lý do API đo lường bộ nhớ được dùng để tổng hợp dữ liệu sử dụng bộ nhớ từ bản phát hành chính thức. Kết quả của các lệnh gọi riêng lẻ sẽ kém hữu ích hơn. Ví dụ về trường hợp sử dụng:

  • Phát hiện hồi quy trong quá trình triển khai phiên bản mới của trang web để phát hiện rò rỉ bộ nhớ mới.
  • Thử nghiệm A/B một tính năng mới để đánh giá tác động của tính năng đó đối với bộ nhớ và phát hiện rò rỉ bộ nhớ.
  • Liên kết mức sử dụng bộ nhớ với thời lượng phiên để xác minh xem có rò rỉ bộ nhớ hay không.
  • Liên kết mức sử dụng bộ nhớ với các chỉ số người dùng để hiểu được tác động tổng thể của mức sử dụng bộ nhớ.

Khả năng tương thích với trình duyệt

Hỗ trợ trình duyệt

  • Chrome: 89.
  • Edge: 89.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Hiện tại, API này chỉ được hỗ trợ trong các trình duyệt dựa trên Chromium, bắt đầu từ Chrome 89. Kết quả của API phụ thuộc rất nhiều vào cách triển khai vì các trình duyệt có nhiều cách biểu thị đối tượng trong bộ nhớ và nhiều cách ước tính mức sử dụng bộ nhớ. Trình duyệt có thể loại trừ một số vùng bộ nhớ khỏi việc tính toán nếu việc tính toán đúng cách quá tốn kém hoặc không khả thi. Do đó, bạn không thể so sánh kết quả giữa các trình duyệt. Bạn chỉ nên so sánh kết quả cho cùng một trình duyệt.

Sử dụng performance.measureUserAgentSpecificMemory()

Phát hiện tính năng

Hàm performance.measureUserAgentSpecificMemory sẽ không hoạt động hoặc có thể không thành công với SecurityError nếu môi trường thực thi không đáp ứng các yêu cầu bảo mật để ngăn chặn rò rỉ thông tin trên nhiều nguồn gốc. Tính năng này dựa trên tính năng tách biệt nhiều nguồn gốc mà trang web có thể kích hoạt bằng cách đặt tiêu đề COOP+COEP.

Bạn có thể phát hiện tính năng hỗ trợ trong thời gian chạy:

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

Kiểm thử cục bộ

Chrome thực hiện việc đo lường bộ nhớ trong quá trình thu thập rác, có nghĩa là API không phân giải ngay lời hứa về kết quả mà thay vào đó sẽ chờ quá trình thu thập rác tiếp theo.

Việc gọi API sẽ buộc thực hiện một lần thu gom rác sau một khoảng thời gian chờ, hiện được đặt thành 20 giây, mặc dù có thể xảy ra sớm hơn. Việc khởi động Chrome bằng cờ dòng lệnh --enable-blink-features='ForceEagerMeasureMemory' sẽ giảm thời gian chờ về 0 và rất hữu ích khi gỡ lỗi và kiểm thử cục bộ.

Ví dụ:

Bạn nên sử dụng API này để xác định một trình giám sát bộ nhớ toàn cầu lấy mẫu mức sử dụng bộ nhớ của toàn bộ trang web và gửi kết quả đến máy chủ để tổng hợp và phân tích. Cách đơn giản nhất là lấy mẫu định kỳ, ví dụ: M phút một lần. Tuy nhiên, điều đó sẽ gây ra sự sai lệch cho dữ liệu vì các đỉnh bộ nhớ có thể xảy ra giữa các mẫu.

Ví dụ sau đây cho thấy cách đo lường bộ nhớ không thiên vị bằng cách sử dụng quy trình Poisson. Quy trình này đảm bảo rằng các mẫu có khả năng xảy ra như nhau tại bất kỳ thời điểm nào (bản minh hoạ, nguồn).

Trước tiên, hãy xác định một hàm lên lịch đo lường bộ nhớ tiếp theo bằng cách sử dụng setTimeout() với khoảng thời gian ngẫu nhiên.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

Hàm measurementInterval() tính toán một khoảng thời gian ngẫu nhiên tính bằng mili giây sao cho trung bình cứ 5 phút lại có một lần đo. Hãy xem phần Phân phối mũ nếu bạn quan tâm đến toán học đằng sau hàm này.

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

Cuối cùng, hàm performMeasurement() không đồng bộ sẽ gọi API, ghi lại kết quả và lên lịch đo lường tiếp theo.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

Cuối cùng, hãy bắt đầu đo lường.

// Start measurements.
scheduleMeasurement();

Kết quả có thể như sau:

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

Tổng mức sử dụng bộ nhớ ước tính được trả về trong trường bytes. Giá trị này phụ thuộc rất nhiều vào cách triển khai và không thể so sánh giữa các trình duyệt. Thậm chí, kích thước này có thể thay đổi giữa các phiên bản của cùng một trình duyệt. Giá trị này bao gồm bộ nhớ JavaScript và DOM của tất cả iframe, cửa sổ liên quan và trình chạy web trong quy trình hiện tại.

Danh sách breakdown cung cấp thêm thông tin về bộ nhớ đã sử dụng. Mỗi mục nhập mô tả một phần bộ nhớ và gán phần bộ nhớ đó cho một tập hợp các cửa sổ, iframe và worker được xác định bằng URL. Trường types liệt kê các loại bộ nhớ dành riêng cho quá trình triển khai liên kết với bộ nhớ.

Điều quan trọng là phải xử lý tất cả danh sách theo cách chung chung và không mã hoá cứng các giả định dựa trên một trình duyệt cụ thể. Ví dụ: một số trình duyệt có thể trả về breakdown hoặc attribution trống. Các trình duyệt khác có thể trả về nhiều mục nhập trong attribution cho biết rằng chúng không thể phân biệt mục nhập nào trong số này sở hữu bộ nhớ.

Phản hồi

Nhóm cộng đồng về hiệu suất web và nhóm Chrome rất muốn biết suy nghĩ và trải nghiệm của bạn về performance.measureUserAgentSpecificMemory().

Giới thiệu cho chúng tôi về thiết kế API

API có hoạt động như mong đợi không? Hay có thuộc tính nào bị thiếu mà bạn cần để triển khai ý tưởng của mình không? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub performance.measureUserAgentSpecificMemory() hoặc thêm ý kiến của bạn vào một vấn đề hiện có.

Báo cáo vấn đề về việc triển khai

Bạn có phát hiện lỗi khi triển khai Chrome không? Hay cách triển khai khác với thông số kỹ thuật? Gửi lỗi tại new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, cung cấp hướng dẫn đơn giản để tái hiện lỗi và đặt Components (Thành phần) thành Blink>PerformanceAPIs. Glitch rất hữu ích để chia sẻ các bản tái hiện nhanh chóng và dễ dàng.

Thể hiện sự ủng hộ

Bạn có định sử dụng performance.measureUserAgentSpecificMemory() không? Sự ủng hộ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác thấy tầm quan trọng của việc hỗ trợ các tính năng đó. Gửi một tweet đến @ChromiumDev và cho chúng tôi biết bạn đang sử dụng tính năng này ở đâu và như thế nào.

Đường liên kết hữu ích

Lời cảm ơn

Cảm ơn Domenic Denicola, Yoav Weiss, Mathias Bynens đã xem xét thiết kế API, và Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz đã xem xét mã trong Chrome. Tôi cũng cảm ơn Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan và Neil Mckay đã cung cấp ý kiến phản hồi có giá trị của người dùng, giúp cải thiện đáng kể API.

Hình ảnh chính của Harrison Broadbent trên Unsplash