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

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

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

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

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

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

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

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

دعم المتصفح

  • 89
  • 89
  • x
  • x

المصدر

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

جارٍ استخدام 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 دقيقة. ومع ذلك، فإن هذا يؤدي إلى تحيز للبيانات لأن الذروة للذاكرة قد تحدث بين العينات.

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

عليك أولاً تحديد دالة يمكنها جدولة القياس التالي للذاكرة باستخدام 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().

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

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

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

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

إظهار الدعم

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

روابط مفيدة

شكر وتقدير

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

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