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

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

بريندان كيني
بريندان كيني
أولان ديغنباييف
أولان ديغنباييف

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 89
  • 89
  • x
  • x

المصدر

لا تتوفَّر واجهة برمجة التطبيقات حاليًا إلا في المتصفِّحات المستنِدة إلى 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 تشير إلى عدم تمكُّنها من التمييز بين هذه الإدخالات التي تمتلك الذاكرة.

إضافة ملاحظات

يسرّ منتدى أداء الويب وفريق Chrome الاطّلاع على أفكارك وتجاربك بشأن performance.measureUserAgentSpecificMemory().

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

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

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

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

إظهار الدعم

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

روابط مفيدة

شكر وتقدير

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

صورة رئيسية من إعداد هاريسون برودبنت على قناة Unسبلاش