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

Các trình duyệt tự động quản lý bộ nhớ của 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 đoạn bộ nhớ "nâng cao" đế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 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 và để giải phóng phân đoạn bộ nhớ cơ bản.

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

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

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

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

Bước đầu tiên để giải quyết vấn đề này là đo lường độ chính xác. Gói thuê bao mới API performance.measureUserAgentSpecificMemory() 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 thực tế và do đó phát hiện bộ nhớ những sự cố rò rỉ xảy ra 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 bạn đã quen với API performance.memory không chuẩn hiện có, có thể bạn đang thắc mắc API mới khác với nó như thế nào. Điểm khác biệt chính là rằng 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ớ được sử dụng bởi trang web. Sự khác biệt này trở thành rất 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 bản sao 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ó thể bị tắt tuỳ ý. Vì API cũ được định nghĩa trong các thuật ngữ dành riêng cho việc triển khai như "vùng nhớ khối xếp", việc chuẩn hoá là vô vọng.

Một điểm khác biệt nữa là API mới thực hiện việc đo lường bộ nhớ trong thu gom rác. Việc này làm giảm độ nhiễu trong kết quả, nhưng có thể mất một chút thời gian cho đến khi tạo kết quả. 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 dựa vào tính năng thu thập 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 diễn ra sự kiện, thao tác của người dùng và thu gom rác. Đó là lý do API đo lường bộ nhớ được dành cho tổng hợp dữ liệu sử dụng bộ nhớ từ phiên bản chính thức. Kết quả của từng cuộc gọi 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 phát hành phiên bản mới của trang web để phát hiện các sự cố 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 tình trạng rò rỉ bộ nhớ.
  • So sánh 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ó rò rỉ bộ nhớ.
  • So sánh mức sử dụng bộ nhớ với các chỉ số người dùng để nắm được 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

  • Chrome: 89.
  • Cạnh: 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, kể từ Chrome 89. Chiến lược phát hành đĩa đơn kết quả của API phụ thuộc rất lớn vào việc triển khai vì các trình duyệt đã những cách khác nhau để biểu thị đối tượng trong bộ nhớ và các 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 xem xét liệu việc kế toán phù hợp là quá tốn kém hoặc không khả thi. Do đó, kết quả không thể so sánh giữa các trình duyệt. Chỉ nên so sánh 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 về bảo mật để ngăn chặn việc rò rỉ thông tin trên nhiều nguồn gốc. Chiến lược này hoạt động dựa trên tính năng tách biệt nhiều nguồn gốc mà một trang web có thể kích hoạt bằng cách đặt tiêu đề COOP+COEP.

Có thể phát hiện hoạt độ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, tức là rằng API không giải quyết kết quả hứa hẹn ngay lập tức mà thay vào đó sẽ chờ cho 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ờ, tức là 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 --enable-blink-features='ForceEagerMeasureMemory' cờ hiệu dòng lệnh giảm về 0 và hữu ích cho việc 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ớ chung 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ỳ, ví dụ mỗi M phút. Tuy nhiên, điều đó gây ra sai lệch cho dữ liệu vì cao nhất của bộ nhớ có thể xảy ra giữa các mẫu.

Ví dụ sau đây trình bày cách thực hiện phép đo bộ nhớ không sai lệch bằ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 mọi thời điểm (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() có 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 theo mili giây vậy là trung bình cứ năm phút sẽ có một lần đo. Xem phần Hàm số mũ phân phối nếu bạn quan tâm đến toán học đằng sau hàm đó.

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, bản ghi 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 là phụ thuộc nhiều vào việc triển khai và không thể so sánh giữa các trình duyệt. 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 Bộ nhớ JavaScript và DOM của tất cả cá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ột mục nhập mô tả một số phần của bộ nhớ và gán nó cho một tập hợp windows, iframe và worker được xác định theo URL. Danh sách trường types các loại bộ nhớ triển khai cụ thể được liên kết với bộ nhớ này.

Bạn phải xử lý tất cả danh sách theo cách chung và không mã hoá cứng 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ề một breakdown trống hoặc một 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 các mục nhập đó không thể phân biệt mục nhập nào trong số này sở hữu bộ nhớ này.

Phản hồi

Nhóm cộng đồng hiệu suất web và nhóm Chrome rất mong được để nghe 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ế API

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

Báo cáo sự cố về triển khai

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

Thể hiện sự ủng hộ

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

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 Weiss, 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à Leonard Mckay vì đã cung cấp phản hồi có giá trị cho người dùng và giúp họ đã cải thiện API.

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