Memantau total penggunaan memori halaman web Anda dengan measureUserAgentSpecificMemory()

Pelajari cara mengukur penggunaan memori halaman web Anda dalam produksi untuk mendeteksi regresi.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Browser mengelola memori halaman web secara otomatis. Setiap kali halaman web membuat objek, browser akan mengalokasikan bagian memori "di balik layar" untuk menyimpan objek. Karena memori adalah resource terbatas, browser melakukan pembersihan sampah memori untuk mendeteksi kapan objek tidak lagi diperlukan dan untuk mengosongkan bagian memori yang mendasarinya.

Namun, deteksi ini tidak sempurna, dan Halting Problem Alan Turing membuktikan bahwa deteksi yang sempurna adalah tugas yang mustahil. Oleh karena itu, browser memperkirakan konsep "objek diperlukan" dengan konsep "objek dapat dijangkau". Jika halaman web tidak dapat menjangkau objek melalui variabelnya dan kolom objek lain yang dapat dijangkau, browser dapat mengklaim kembali objek tersebut dengan aman. Perbedaan antara kedua konsep ini menyebabkan kebocoran memori seperti yang diilustrasikan oleh contoh berikut.

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

Di sini, array yang lebih besar b tidak lagi diperlukan, tetapi browser tidak menglaimnya kembali karena masih dapat dijangkau melalui object.b dalam callback. Dengan demikian, memori array yang lebih besar mengalami kebocoran.

Kebocoran memori banyak terjadi di Web, seperti yang dapat dilihat dari studi ini. Kebocoran memori mudah terjadi jika Anda lupa membatalkan pendaftaran pemroses peristiwa, tidak sengaja mengambil objek dari iframe, tidak menutup pekerja, mengakumulasi objek dalam array, dan sebagainya. Jika halaman web mengalami kebocoran memori, penggunaan memorinya akan bertambah dari waktu ke waktu dan halaman web akan terlihat lambat dan penuh bagi pengguna.

Langkah pertama dalam memecahkan masalah ini adalah mengukurnya. performance.measureUserAgentSpecificMemory() API baru memungkinkan developer mengukur penggunaan memori halaman web mereka dalam produksi dan dengan demikian mendeteksi kebocoran memori yang lolos dari pengujian lokal.

Apa perbedaan performance.measureUserAgentSpecificMemory() dengan performance.memory API lama?

Jika Anda sudah familiar dengan performance.memory API non-standar yang ada, Anda mungkin bertanya-tanya bagaimana API baru berbeda dari API lama. Perbedaan utamanya adalah API lama menampilkan ukuran heap JavaScript, sedangkan API baru memperkirakan memori yang digunakan oleh halaman web. Perbedaan ini menjadi penting saat Chrome berbagi heap yang sama dengan beberapa halaman web (atau beberapa instance halaman web yang sama). Dalam kasus seperti itu, hasil API lama mungkin tidak akurat. Karena API lama ditentukan dalam istilah khusus implementasi seperti "heap", menstandarkannya tidak mungkin dilakukan.

Perbedaan lainnya adalah API baru melakukan pengukuran memori selama pembersihan sampah memori. Hal ini mengurangi gangguan dalam hasil, tetapi mungkin perlu waktu hingga hasil dibuat. Perhatikan bahwa browser lain dapat memutuskan untuk menerapkan API baru tanpa mengandalkan pembersihan sampah memori.

Kasus penggunaan yang disarankan

Penggunaan memori halaman web bergantung pada waktu peristiwa, tindakan pengguna, dan pembersihan sampah memori. Itulah sebabnya API pengukuran memori ditujukan untuk menggabungkan data penggunaan memori dari produksi. Hasil panggilan individual kurang berguna. Contoh kasus penggunaan:

  • Deteksi regresi selama peluncuran versi baru halaman web untuk menangkap kebocoran memori baru.
  • Pengujian A/B fitur baru untuk mengevaluasi dampak memori dan mendeteksi kebocoran memori.
  • Mengorelasikan penggunaan memori dengan durasi sesi untuk memverifikasi ada atau tidaknya kebocoran memori.
  • Mengorelasikan penggunaan memori dengan metrik pengguna untuk memahami dampak keseluruhan penggunaan memori.

Kompatibilitas browser

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

Saat ini, API hanya didukung di browser berbasis Chromium, mulai di Chrome 89. Hasil API sangat bergantung pada implementasi karena browser memiliki cara yang berbeda untuk merepresentasikan objek dalam memori dan cara yang berbeda untuk memperkirakan penggunaan memori. Browser dapat mengecualikan beberapa region memori dari akuntansi jika akuntansi yang tepat terlalu mahal atau tidak layak. Oleh karena itu, hasil tidak dapat dibandingkan di seluruh browser. Hanya hasil untuk browser yang sama yang dapat dibandingkan.

Menggunakan performance.measureUserAgentSpecificMemory()

Deteksi fitur

Fungsi performance.measureUserAgentSpecificMemory tidak akan tersedia atau mungkin gagal dengan SecurityError jika lingkungan eksekusi tidak memenuhi persyaratan keamanan untuk mencegah kebocoran informasi lintas origin. Fungsi ini mengandalkan isolasi lintas origin, yang dapat diaktifkan halaman web dengan menetapkan header COOP+COEP.

Dukungan dapat dideteksi saat runtime:

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);
}

Pengujian lokal

Chrome melakukan pengukuran memori selama pembersihan sampah memori, yang berarti API tidak langsung menyelesaikan promise hasil dan menunggu pembersihan sampah memori berikutnya.

Memanggil API akan memaksa pembersihan sampah memori setelah waktu tunggu tertentu, yang saat ini ditetapkan ke 20 detik, meskipun dapat terjadi lebih cepat. Memulai Chrome dengan --enable-blink-features='ForceEagerMeasureMemory' flag command line mengurangi waktu tunggu menjadi nol dan berguna untuk proses debug dan pengujian lokal.

Contoh

Penggunaan API yang direkomendasikan adalah menentukan monitor memori global yang mengambil sampel penggunaan memori seluruh halaman web dan mengirimkan hasilnya ke server untuk agregasi dan analisis. Cara paling sederhana adalah mengambil sampel secara berkala, misalnya setiap M menit. Namun, hal ini akan menimbulkan bias pada data karena puncak memori dapat terjadi di antara sampel.

Contoh berikut menunjukkan cara melakukan pengukuran memori yang tidak bias menggunakan proses Poisson, yang menjamin bahwa sampel memiliki kemungkinan yang sama untuk terjadi pada waktu kapan pun (demo, sumber).

Pertama, tentukan fungsi yang menjadwalkan pengukuran memori berikutnya menggunakan setTimeout() dengan interval acak.

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);
}

Fungsi measurementInterval() menghitung interval acak dalam milidetik sehingga rata-rata ada satu pengukuran setiap lima menit. Lihat Distribusi eksponensial jika Anda tertarik dengan matematika di balik fungsi ini.

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

Terakhir, fungsi performMeasurement() asinkron memanggil API, mencatat hasilnya, dan menjadwalkan pengukuran berikutnya.

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();
}

Terakhir, mulai pengukuran.

// Start measurements.
scheduleMeasurement();

Hasilnya mungkin terlihat seperti berikut:

// 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']
    },
  ],
}

Perkiraan total penggunaan memori ditampilkan di kolom bytes. Nilai ini sangat bergantung pada implementasi dan tidak dapat dibandingkan di seluruh browser. Nilai ini bahkan dapat berubah di antara berbagai versi browser yang sama. Nilai ini mencakup memori JavaScript dan DOM dari semua iframe, jendela terkait, dan pekerja web dalam proses saat ini.

Daftar breakdown memberikan informasi lebih lanjut tentang memori yang digunakan. Setiap entri menjelaskan sebagian memori dan mengaitkannya dengan sekumpulan jendela, iframe, dan pekerja yang diidentifikasi berdasarkan URL. Kolom types mencantumkan jenis memori khusus implementasi yang terkait dengan memori.

Penting untuk memperlakukan semua daftar secara umum dan tidak meng-hardcode asumsi berdasarkan browser tertentu. Misalnya, beberapa browser dapat menampilkan breakdown kosong atau attribution kosong. Browser lain dapat menampilkan beberapa entri di attribution yang menunjukkan bahwa browser tersebut tidak dapat membedakan entri mana yang memiliki memori.

Masukan

Web Performance Community Group dan tim Chrome ingin mengetahui pendapat dan pengalaman Anda terkait performance.measureUserAgentSpecificMemory().

Beri tahu kami tentang desain API

Apakah ada sesuatu tentang API yang tidak berfungsi seperti yang diharapkan? Atau apakah ada properti yang tidak ada yang Anda butuhkan untuk menerapkan ide Anda? Laporkan masalah spesifikasi di repo GitHub performance.measureUserAgentSpecificMemory() atau tambahkan pendapat Anda ke masalah yang ada.

Laporkan masalah terkait implementasi

Apakah Anda menemukan bug pada implementasi Chrome? Atau apakah implementasinya berbeda dengan spesifikasinya? Laporkan bug di new.crbug.com. Pastikan untuk menyertakan detail sebanyak mungkin, berikan petunjuk sederhana untuk mereproduksi bug, dan tetapkan Components ke Blink>PerformanceAPIs.

Tunjukkan dukungan

Apakah Anda berencana menggunakan performance.measureUserAgentSpecificMemory()? Dukungan publik Anda membantu tim Chrome memprioritaskan fitur dan menunjukkan kepada vendor browser lain betapa pentingnya mendukung fitur tersebut. Kirim tweet ke @ChromiumDev dan beri tahu kami di mana dan bagaimana Anda menggunakannya.

Link bermanfaat

Ucapan terima kasih

Terima kasih banyak kepada Domenic Denicola, Yoav Weiss, Mathias Bynens atas ulasan desain API, dan Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz atas ulasan kode di Chrome. Saya juga berterima kasih kepada Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan, dan Neil Mckay karena telah memberikan masukan pengguna yang berharga dan sangat meningkatkan kualitas API.