كيفية تقييم أداء التحميل في الميدان باستخدام توقيت التنقّل وتوقيت الموارد

تعرَّف على أساسيات استخدام واجهتَي برمجة التطبيقات Navigation وResource Timing لتقييم أداء التحميل في الحقل.

تاريخ النشر: 8 تشرين الأول (أكتوبر) 2021

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

لا يُعدّ الاختبار الاصطناعي سيئًا في حد ذاته، ولكنّه لا يمثّل مدى سرعة تحميل موقعك الإلكتروني للمستخدمين الفعليين. يتطلّب ذلك بيانات ميدانية يمكنك جمعها من واجهتَي برمجة التطبيقات Navigation Timing وResource Timing.

واجهات برمجة التطبيقات التي تساعدك في تقييم أداء التحميل في بيئة التشغيل

‫Navigation Timing وResource Timing هما واجهتا برمجة تطبيقات متشابهتان تتضمّنان تداخلاً كبيرًا، وهما تقيسان شيئَين مختلفَين:

  • يقيس Navigation Timing سرعة طلبات مستندات HTML (أي طلبات التنقّل).
  • تقيس Resource Timing سرعة الطلبات المتعلقة بالموارد التي تعتمد على المستند، مثل CSS وJavaScript والصور وأنواع الموارد الأخرى.

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

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

تقبل السمة performance.getEntriesByType سلسلة تصف نوع الإدخالات التي تريد استردادها من مخزن مؤقت لإدخالات الأداء. يستردّ 'navigation' التوقيتات لواجهة برمجة التطبيقات Navigation Timing، بينما يستردّ 'resource' التوقيتات لواجهة برمجة التطبيقات Resource Timing.

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

مدة معالجة طلب الشبكة وتوقيته

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

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

تتضمّن دورة حياة طلب الشبكة مراحل متميّزة، مثل بحث نظام أسماء النطاقات (DNS)، وإنشاء الاتصال، والتفاوض بشأن بروتوكول أمان طبقة النقل (TLS)، ومصادر أخرى لوقت الاستجابة. يتم تمثيل هذه التوقيتات على شكل DOMHighResTimestamp. استنادًا إلى المتصفّح، قد تصل دقة التوقيتات إلى الميكروثانية، أو يتم تقريبها إلى أجزاء من الألف من الثانية. عليك دراسة هذه المراحل بالتفصيل وكيفية ارتباطها بمقياسَي Navigation Timing وResource Timing.

بحث نظام أسماء النطاقات

عندما ينتقل مستخدم إلى عنوان URL، يتم طلب نظام أسماء النطاقات (DNS) لترجمة نطاق إلى عنوان IP. قد تستغرق هذه العملية وقتًا طويلاً، لذا ننصحك بقياسها ميدانيًا. تعرض واجهة برمجة التطبيقات Navigation Timing وResource Timing وقتَين مرتبطَين بنظام أسماء النطاقات:

  • domainLookupStart هو الوقت الذي تبدأ فيه عملية بحث نظام أسماء النطاقات.
  • domainLookupEnd هو الوقت الذي ينتهي فيه البحث في نظام أسماء النطاقات.

يمكن احتساب إجمالي وقت البحث عن نظام أسماء النطاقات من خلال طرح مقياس البدء من مقياس الانتهاء:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

التفاوض بشأن الاتصال

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

  • connectStart هو الوقت الذي يبدأ فيه المتصفّح بفتح اتصال بخادم ويب.
  • تشير secureConnectionStart إلى الوقت الذي يبدأ فيه العميل عملية التفاوض بشأن بروتوكول أمان طبقة النقل (TLS).
  • connectEnd هو الوقت الذي تم فيه إنشاء الاتصال بخادم الويب.

يشبه قياس إجمالي وقت الاتصال قياس إجمالي وقت بحث نظام أسماء النطاقات: عليك طرح وقت البدء من وقت الانتهاء. ومع ذلك، هناك سمة secureConnectionStart إضافية قد تكون قيمتها 0 في حال عدم استخدام بروتوكول HTTPS أو إذا كان الاتصال مستمرًا. إذا أردت قياس وقت التفاوض على بروتوكول TLS، عليك مراعاة ما يلي:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

بعد انتهاء البحث عن نظام أسماء النطاقات (DNS) والتفاوض على الاتصال، تبدأ التوقيتات المتعلقة بجلب المستندات والموارد التابعة لها.

الطلبات والردود

يتأثّر أداء التحميل بنوعَين من العوامل:

  • العوامل الخارجية: تشمل هذه العوامل وقت الاستجابة وعرض النطاق الترددي. وبالإضافة إلى اختيار شركة استضافة وربما شبكة توصيل محتوى (CDN)، لا يمكننا (في معظم الحالات) التحكّم في هذه العوامل، لأنّ المستخدمين يمكنهم الوصول إلى الويب من أي مكان.
  • العوامل الجوهرية: تشمل هذه العوامل بنية الخادم ومن جهة العميل، بالإضافة إلى حجم الموارد وقدرتنا على تحسين هذه العوامل، وهي عوامل يمكننا التحكّم فيها.

يؤثر كلا النوعين من العوامل في أداء التحميل. تُعدّ التوقيتات المرتبطة بهذه العوامل مهمة لأنّها توضّح المدة التي تستغرقها الموارد للتنزيل. يصف كلّ من Navigation Timing وResource Timing أداء التحميل باستخدام المقاييس التالية:

  • يمثّل fetchStart الوقت الذي يبدأ فيه المتصفّح في جلب مورد (Resource Timing) أو مستند لطلب تنقّل (Navigation Timing). يسبق ذلك الطلب الفعلي، وهو النقطة التي يتحقّق فيها المتصفّح من ذاكرات التخزين المؤقت (على سبيل المثال، مثيلات HTTP وCache).
  • تحدّد workerStart وقت بدء معالجة الطلب ضمن معالج أحداث fetch في Service Worker. ستكون القيمة 0 عندما لا يتحكّم أي مشغّل خدمات في الصفحة الحالية.
  • requestStart هو الوقت الذي يقدّم فيه المتصفّح الطلب.
  • responseStart هو الوقت الذي يصل فيه أول بايت من الردّ.
  • responseEnd هو الوقت الذي يصل فيه آخر بايت من الرد.

تتيح لك هذه التوقيتات قياس جوانب متعددة من أداء التحميل، مثل البحث في ذاكرة التخزين المؤقت ضمن أحد مشغّلات الخدمات و وقت التنزيل:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

يمكنك أيضًا قياس جوانب أخرى من وقت استجابة الطلب والرد:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

القياسات الأخرى التي يمكنك إجراؤها

تُعدّ Navigation Timing وResource Timing مفيدتَين لأكثر من الأمثلة الموضّحة سابقًا. في ما يلي بعض الحالات الأخرى التي تتضمّن توقيتات ذات صلة قد يكون من المفيد استكشافها:

  • عمليات إعادة توجيه الصفحات: عمليات إعادة التوجيه هي مصدر يتم تجاهله للتأخير المضاف، لا سيما سلاسل إعادة التوجيه. تتم إضافة وقت الاستجابة بعدة طرق، مثل قفزات HTTP إلى HTTPS، بالإضافة إلى عمليات إعادة التوجيه 302/301 غير المخزّنة مؤقتًا. تساعد توقيتات redirectStart وredirectEnd وredirectCount في تقييم وقت استجابة عمليات إعادة التوجيه.
  • إلغاء تحميل المستند: في الصفحات التي تنفّذ الرمز في معالج أحداث unload، يجب أن ينفّذ المتصفّح هذا الرمز قبل أن يتمكّن من الانتقال إلى الصفحة التالية. يقيس المقياسان unloadEventStart وunloadEventEnd عملية إلغاء تحميل المستند.
  • معالجة المستندات: قد لا تكون مدة معالجة المستندات مهمة إلا إذا كان موقعك الإلكتروني يرسل حمولات HTML كبيرة جدًا. إذا كان هذا الوصف ينطبق على حالتك، قد تهمّك مواعيد domInteractive وdomContentLoadedEventStart وdomContentLoadedEventEnd وdomComplete.

كيفية الحصول على التوقيتات في الرمز

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

الأسلوب المقترَح لجمع الإدخالات من مخزن مؤقت لإدخالات الأداء هو استخدام PerformanceObserver. يستمع PerformanceObserver إلى إدخالات الأداء، ويقدّمها عند إضافتها إلى المخزن المؤقت:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

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

كيفية الاتصال بالشبكة

بعد جمع كل التوقيتات التي تحتاج إليها، يمكنك إرسالها إلى نقطة نهاية لإجراء المزيد من التحليل. يمكنك إجراء ذلك بطريقتَين، إما باستخدام navigator.sendBeacon أو fetch مع ضبط الخيار keepalive. سترسل كلتا الطريقتين طلبًا إلى نقطة نهاية محدّدة بطريقة غير حظر، وسيتم وضع الطلب في قائمة الانتظار بطريقة تتجاوز مدة جلسة الصفحة الحالية إذا لزم الأمر:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

في هذا المثال، ستصل سلسلة JSON في حمولة POST يمكنك فك ترميزها ومعالجتها وتخزينها في الخلفية لتطبيقك حسب الحاجة.

الخاتمة

بعد جمع المقاييس، يعود إليك تحديد كيفية تحليل بيانات الحقل هذه. عند تحليل بيانات الحقول، هناك بعض القواعد العامة التي يجب اتّباعها لضمان استخلاص نتائج مفيدة:

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

لا يهدف هذا الدليل إلى أن يكون مرجعًا شاملاً بشأن Navigation API أو Resource Timing API، بل هو نقطة انطلاق. في ما يلي بعض المراجع الإضافية التي قد تكون مفيدة:

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