راقِب إجمالي استخدام الذاكرة في صفحة الويب من خلال قياسUserAgentspecificMemory()

تعرَّف على كيفية قياس استخدام الذاكرة في صفحة الويب أثناء الإنتاج لرصد حالات التراجع.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

تتولّى المتصفحات إدارة ذاكرة صفحات الويب تلقائيًا. عندما تنشئ صفحة ويب كائنًا، يخصّص المتصفّح جزءًا من الذاكرة "في الخلفية" لتخزين الكائن. بما أنّ الذاكرة مورد محدود، ينفّذ المتصفّح عملية جمع البيانات المُهمَلة لرصد الحالات التي لم يعُد فيها العنصر مطلوبًا ولتحرير جزء الذاكرة الأساسي.

مع ذلك، لا يمكن أن يكون الرصد مثاليًا، وقد أثبتت مسألة التوقف لآلان تورينغ أنّ الرصد المثالي هو مهمة مستحيلة. لذلك، تقرّب المتصفّحات مفهوم "مطلوب كائن" بمفهوم "يمكن الوصول إلى كائن". إذا تعذّر على صفحة الويب الوصول إلى أحد العناصر من خلال متغيراته وحقول العناصر الأخرى التي يمكن الوصول إليها، يمكن للمتصفّح استرداد العنصر بأمان. يؤدي الاختلاف بين هذين المفهومين إلى حدوث تسرّب للذاكرة كما يوضّح المثال التالي.

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

في هذه الحالة، لم تعُد المصفوفة الأكبر b مطلوبة، ولكن المتصفّح لا يستردّها لأنّه لا يزال يمكن الوصول إليها من خلال object.b في دالة معاودة الاتصال. وبالتالي، يتم تسريب ذاكرة المصفوفة الأكبر.

تنتشر أخطاء تسريب الذاكرة على الويب، كما يتضح من هذه الدراسة. يمكن أن يحدث ذلك بسهولة من خلال نسيان إلغاء تسجيل متتبِّع الأحداث، أو من خلال التقاط عناصر من إطار iframe عن طريق الخطأ، أو من خلال عدم إغلاق منفِّذ، أو من خلال تجميع عناصر في مصفوفات، وما إلى ذلك. إذا كانت صفحة الويب تتضمّن تسريبات للذاكرة، سيزداد استخدامها للذاكرة بمرور الوقت وستبدو بطيئة ومضخّمة للمستخدمين.

تتمثل الخطوة الأولى في حلّ هذه المشكلة في قياسها. تتيح واجهة برمجة التطبيقات الجديدة performance.measureUserAgentSpecificMemory() للمطوّرين قياس استخدام الذاكرة لصفحاتهم على الويب في مرحلة الإنتاج، وبالتالي رصد تسريبات الذاكرة التي لا يتم رصدها أثناء الاختبارات المحلية.

ما هو الفرق بين performance.measureUserAgentSpecificMemory() وواجهة برمجة التطبيقات القديمة performance.memory؟

إذا كنت على دراية بواجهة برمجة التطبيقات الحالية غير المتوافقة مع المعايير performance.memory، قد تتساءل عن الفرق بينها وبين واجهة برمجة التطبيقات الجديدة. يكمن الاختلاف الرئيسي في أنّ واجهة برمجة التطبيقات القديمة تعرض حجم مساحة التخزين المؤقت في JavaScript، بينما تقدّر واجهة برمجة التطبيقات الجديدة مقدار الذاكرة التي تستخدمها صفحة الويب. يصبح هذا الاختلاف مهمًا عندما يشارك Chrome الذاكرة المكدّسة نفسها مع صفحات ويب متعددة (أو مثيلات متعددة من صفحة الويب نفسها). في مثل هذه الحالات، قد تكون نتيجة واجهة برمجة التطبيقات القديمة غير دقيقة بشكل عشوائي، وبما أنّ واجهة برمجة التطبيقات القديمة معرَّفة بمصطلحات خاصة بالتنفيذ مثل "الكومة"، فإنّ توحيدها أمر غير ممكن.

يتمثّل الاختلاف الآخر في أنّ واجهة برمجة التطبيقات الجديدة تنفّذ قياس الذاكرة أثناء عملية جمع البيانات المُهمَلة. يؤدي ذلك إلى تقليل التشويش في النتائج، ولكن قد يستغرق الأمر بعض الوقت إلى أن يتم إنتاج النتائج. يُرجى العِلم أنّ المتصفّحات الأخرى قد تقرّر تنفيذ واجهة برمجة التطبيقات الجديدة بدون الاعتماد على جمع البيانات المُهمَلة.

حالات الاستخدام المقترَحة

يعتمد استخدام الذاكرة في صفحة الويب على توقيت الأحداث وإجراءات المستخدم وعمليات جمع البيانات غير الضرورية. لهذا السبب، تم تصميم واجهة برمجة تطبيقات لقياس البيانات بهدف تجميع بيانات استخدام الذاكرة من الإصدارات العلنية. نتائج المكالمات الفردية أقل فائدة. أمثلة على حالات الاستخدام:

  • رصد المشاكل المتكررة أثناء طرح إصدار جديد من صفحة الويب لرصد أي تسريبات جديدة للذاكرة
  • إجراء اختبار A/B لميزة جديدة لتقييم تأثيرها في الذاكرة ورصد تسرُّب الذاكرة
  • ربط استخدام الذاكرة بمدة الجلسة للتحقّق من وجود تسريبات للذاكرة أو عدم وجودها
  • ربط استخدام الذاكرة بمقاييس المستخدمين لفهم التأثير الإجمالي لاستخدام الذاكرة

توافُق المتصفح

Browser Support

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

Source

في الوقت الحالي، تتوفّر واجهة برمجة التطبيقات في المتصفّحات المستندة إلى Chromium فقط، بدءًا من الإصدار 89 من Chrome. تعتمد نتيجة واجهة برمجة التطبيقات بشكل كبير على التنفيذ لأنّ المتصفّحات لديها طرق مختلفة لتمثيل العناصر في الذاكرة وطرق مختلفة لتقدير استخدام الذاكرة. قد تستبعد المتصفّحات بعض مناطق الذاكرة من المحاسبة إذا كانت المحاسبة السليمة مكلفة أو غير ممكنة. وبالتالي، لا يمكن مقارنة النتائج بين المتصفحات المختلفة. لا يمكن مقارنة النتائج إلا إذا كانت من المتصفّح نفسه.

جارٍ استخدام performance.measureUserAgentSpecificMemory()

رصد الميزات

لن تكون الدالة performance.measureUserAgentSpecificMemory متاحة أو قد يتعذّر تنفيذها مع ظهور الخطأ SecurityError إذا لم تستوفِ بيئة التنفيذ متطلبات الأمان اللازمة لمنع تسرُّب المعلومات من مصادر متعددة. تعتمد هذه الميزة على حظر الوصول من نطاقات أخرى، ويمكن لصفحة الويب تفعيلها من خلال ضبط عناوين COOP وCOEP.

يمكن رصد التوافق في وقت التشغيل:

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

الاختبار المحلي

يُجري Chrome قياس الذاكرة أثناء عملية جمع البيانات غير الضرورية، ما يعني أنّ واجهة برمجة التطبيقات لا تحلّ وعد النتيجة على الفور، بل تنتظر عملية جمع البيانات غير الضرورية التالية.

يؤدي طلب البيانات من واجهة برمجة التطبيقات إلى فرض عملية جمع البيانات المُهمَلة بعد انتهاء مهلة معيّنة، والتي تبلغ حاليًا 20 ثانية، ولكن قد تحدث العملية قبل ذلك. يؤدي بدء Chrome باستخدام علامة سطر الأوامر --enable-blink-features='ForceEagerMeasureMemory' إلى تقليل المهلة إلى صفر، وهو أمر مفيد لتصحيح الأخطاء والاختبار محليًا.

مثال

الاستخدام المقترَح لواجهة برمجة التطبيقات هو تحديد أداة مراقبة الذاكرة العامة التي تأخذ عيّنات من استخدام الذاكرة في صفحة الويب بأكملها وترسل النتائج إلى خادم لتجميعها وتحليلها. أبسط طريقة هي أخذ عيّنات بشكل دوري، مثلاً كل M دقيقة. ومع ذلك، يؤدي ذلك إلى حدوث انحياز في البيانات لأنّه قد تحدث ذروات في الذاكرة بين العيّنات.

يوضّح المثال التالي كيفية إجراء قياسات غير متحيزة للذاكرة باستخدام عملية بواسون، ما يضمن أنّه من المحتمل حدوث العيّنات بالتساوي في أي وقت (العرض التوضيحي، المصدر).

أولاً، حدِّد دالة تحدّد موعد قياس الذاكرة التالي باستخدام setTimeout() مع فاصل زمني عشوائي.

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

تحسب الدالة measurementInterval() فاصلًا زمنيًا عشوائيًا بالمللي ثانية بحيث يكون هناك قياس واحد كل خمس دقائق في المتوسط. اطّلِع على التوزيع الأسي إذا كنت مهتمًا بالرياضيات التي تستند إليها الدالة.

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

أخيرًا، تستدعي الدالة غير المتزامنة performMeasurement() واجهة برمجة التطبيقات، وتسجّل النتيجة، وتجدول عملية القياس التالية.

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

أخيرًا، ابدأ القياس.

// Start measurements.
scheduleMeasurement();

قد تبدو النتيجة على النحو التالي:

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

يتم عرض تقدير إجمالي استخدام الذاكرة في الحقل bytes. تعتمد هذه القيمة بشكل كبير على التنفيذ ولا يمكن مقارنتها بين المتصفّحات. وقد يختلف أيضًا بين الإصدارات المختلفة من المتصفّح نفسه. تتضمّن القيمة ذاكرة JavaScript وذاكرة DOM لجميع إطارات iframe والنوافذ ذات الصلة وعمال الويب في العملية الحالية.

تقدّم قائمة breakdown مزيدًا من المعلومات حول الذاكرة المستخدَمة. يصف كل إدخال جزءًا من الذاكرة وينسبه إلى مجموعة من النوافذ وإطارات iframe وعناصر العامل التي يتم تحديدها من خلال عنوان URL. يسرد حقل types أنواع الذاكرة الخاصة بالتنفيذ والمرتبطة بالذاكرة.

من المهم التعامل مع جميع القوائم بطريقة عامة وعدم ترميز الافتراضات بشكل ثابت استنادًا إلى متصفّح معيّن. على سبيل المثال، قد تعرض بعض المتصفحات قيمة فارغة في breakdown أو attribution. قد تعرض متصفّحات أخرى إدخالات متعدّدة في attribution، ما يشير إلى أنّها لم تتمكّن من التمييز بين هذه الإدخالات لتحديد الإدخال الذي يملك الذاكرة.

الملاحظات

يسرّ فريق مجموعة Web Performance Community Group وفريق Chrome معرفة آرائك وتجاربك بشأن performance.measureUserAgentSpecificMemory().

أخبِرنا عن تصميم واجهة برمجة التطبيقات

هل هناك مشكلة في واجهة برمجة التطبيقات؟ أو هل هناك سمات ناقصة تحتاج إلى تنفيذها لتحقيق فكرتك؟ يمكنك الإبلاغ عن مشكلة في المواصفات على مستودع GitHub الخاص بـ performance.measureUserAgentSpecificMemory()‎ أو إضافة أفكارك إلى مشكلة حالية.

الإبلاغ عن مشكلة في عملية التنفيذ

هل عثرت على خطأ في تنفيذ Chrome؟ أو هل يختلف التنفيذ عن المواصفات؟ يمكنك الإبلاغ عن الخطأ على new.crbug.com. احرص على تضمين أكبر قدر ممكن من التفاصيل، وتقديم تعليمات بسيطة لإعادة إنتاج الخطأ، وضبط المكوّنات على Blink>PerformanceAPIs.

إظهار الدعم

هل تخطّط لاستخدام performance.measureUserAgentSpecificMemory()؟ يساعد دعمك العلني فريق Chrome في تحديد أولويات الميزات، ويوضّح لمورّدي المتصفّحات الآخرين مدى أهمية توفيرها. يمكنك إرسال تغريدة إلى ‎@ChromiumDev وإخبارنا بمكان استخدامك لها وكيفية استخدامها.

روابط مفيدة

الإقرارات

نشكر كلّ من دومينيك دينيكولا ويواف فايس وماثياس بينينز على مراجعات تصميم واجهة برمجة التطبيقات، ودومينيك إنفور وهانيس باير وكينتارو هارا ومايكل ليباوتز على مراجعات الرموز البرمجية في Chrome. أودّ أيضًا أن أشكر "بير باركر" و"فيليب فايس" و"أولغا بيلوميستنيخ" و"ماثيو بولوغان" و"نيل ماكاي" على تقديم ملاحظات قيّمة من المستخدمين ساهمت بشكل كبير في تحسين واجهة برمجة التطبيقات.