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 lỗi hồi quy.

Brendan Kenny
Brendan Kenny
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 trang web tạo một đối tượng, trình duyệt sẽ phân bổ một phần bộ nhớ "ẩn" để lưu trữ đối tượng đó. Vì bộ nhớ là một tài nguyên hữu hạn, nên trình duyệt sẽ thực hiện việc thu gom rác để phát hiện thời điểm một đối tượng không còn cần thiết nữa và để giải phóng phần bộ nhớ cơ bản.

Tuy nhiên, tính năng phát hiện không 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 đó, các trình duyệt ước chừng khái niệm "cần có một đối tượng" với khái niệm "có thể truy cập một đố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 của nó và trường của đối tượng có thể truy cập khác, thì trình duyệt có thể lấy lạ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ư được 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 xác nhận lại mảng đó vì vẫn có thể truy cập mảng này qua object.b trong lệnh gọi lại. Do đó, bộ nhớ của mảng lớn hơn bị rò rỉ.

Rò rỉ bộ nhớ thường thấy trên mạng. Bạn có thể dễ dàng giới thiệu trình nghe bằng cách quên huỷ đăng ký trình nghe sự kiện, bằng cách vô tình thu thập các đối tượng từ iframe, không đóng một trình thực thi, bằng cách tích luỹ các đối tượng trong các 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 đó sẽ tăng lên theo thời gian và trang web sẽ xuất hiện chậm và cồng kềnh đối với người dùng.

Bước đầu tiên để giải quyết vấn đề này là đo lường. performance.measureUserAgentSpecificMemory() API 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 phiên bản chính thức và theo đó phát hiện các sự cố rò rỉ bộ nhớ xảy ra khi kiểm thử cục bộ.

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

Nếu đã quen thuộc với API performance.memory không tiêu chuẩn hiện có, bạn có thể sẽ thắc mắc về sự khác biệt giữa API mới và API này. Đ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 định nghĩa trong các thuật ngữ triển khai cụ thể, 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 nữa là API mới thực hiện hoạt động đo lường bộ nhớ trong quá trình thu gom 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.

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, thao tác của người dùng và việc thu gom rác. Đó là lý do khiến API đo lường bộ nhớ được dùng để tổng hợp dữ liệu sử dụng bộ nhớ trong quá trình sản xuất. Kết quả của từng lệnh gọi riêng lẻ ít hữu ích hơn. Ví dụ về các trường hợp sử dụng:

  • Phát hiện hồi quy trong quá trình triển khai một phiên bản mới của trang web để phát hiện lỗi rò rỉ bộ nhớ mới.
  • Tiến hành thử nghiệm A/B cho 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 lỗi rò rỉ bộ nhớ.
  • Mối liên hệ giữa mức sử dụng bộ nhớ với thời lượng phiên để xác minh sự hiện diện hay không có sự cố rò rỉ bộ nhớ.
  • Mối liên hệ giữa mức sử dụng bộ nhớ với chỉ số người dùng để hiểu tác động tổng thể của việ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

  • 89
  • 89
  • x
  • x

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, kể từ Chrome 89. Kết quả của API phụ thuộc nhiều vào việc triển khai vì các trình duyệt có các cách khác nhau để biểu thị các đối tượng trong bộ nhớ và các cách ước tính mức sử dụng bộ nhớ khác nhau. Các 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. Việc so sánh kết quả cho cùng một trình duyệt chỉ có ý nghĩa.

Sử dụng performance.measureUserAgentSpecificMemory()

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

Hàm performance.measureUserAgentSpecificMemory sẽ không dùng được hoặc có thể gặp lỗi SecurityError nếu môi trường thực thi không đáp ứng các yêu cầu về bảo mật để ngăn chặn việc rò rỉ thông tin trên nhiều nguồn gốc. Tính năng này dựa trên cơ chế 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.

Việc hỗ trợ có thể được phát hiện 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 gom rác, nghĩa là API không phân giải lời hứa kết quả ngay lập tức mà sẽ chờ lần thu gom rác tiếp theo.

Việc gọi API sẽ buộc thu thập 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. Khởi động Chrome bằng cờ hiệu dòng lệnh --enable-blink-features='ForceEagerMeasureMemory' sẽ giảm thời gian chờ về 0 và rất hữu ích cho việc gỡ lỗi cũng như 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ớ chung giúp lấy mẫu mức sử dụng bộ nhớ của toàn bộ trang web rồi 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ỳ, chẳng hạn như M phút một lần. Tuy nhiên, điều đó gây ra sự thiên 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 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, đả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 cho lần đ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 khoảng thời gian ngẫu nhiên bằng mili giây sao cho trung bình cứ 5 phút có một phép đo. Hãy xem phần Phân phối mũ nếu bạn quan tâm đến phép toán đằ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ể có dạng 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 sẽ được trả về trong trường bytes. Giá trị này phụ thuộc nhiều vào quá trình triển khai và không thể so sánh giữa các trình duyệt. Quá trình này thậm chí có thể thay đổi giữa các phiên bản khác nhau của cùng một trình duyệt. Giá trị này bao gồm JavaScript và bộ nhớ DOM của tất cả các iframe, cửa sổ liên quan và trình thực thi 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 số phần bộ nhớ và phân bổ 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ớ triển khai cụ thể được liên kết với bộ nhớ đó.

Bạn phải xử lý tất cả danh sách theo cách chung chung và không nhập trực tiếp vào mã 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 trống 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 nào trong số này sở hữu bộ nhớ.

Ý kiến phản hồi

Nhóm cộng đồng 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ới performance.measureUserAgentSpecificMemory().

Cho chúng tôi biết về thiết kế của API

Có vấn đề nào về API không 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 không? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ performance.measureUserAgentSpecificMemory() trên kho lưu trữ GitHub hoặc thêm ý kiến vào vấn đề hiện tại.

Báo cáo sự cố với quá trình triển khai

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

Hiển thị sự ủng hộ

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

Các đường liên kết hữu ích

Xác nhận

Xin chân thành cảm ơn Domenic Denicola, Yoav {/9}, Mathias Bynens đã đánh giá thiết kế API và Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz đánh giá mã trong Chrome. Tôi cũng cảm ơn Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan và Neil Mckay vì đã cung cấp ý kiến phản hồi hữu ích từ 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