مقدمة لواجهة برمجة تطبيقات الأداء
نشرت: 2022-08-10يقيس Performance API استجابة تطبيق الويب المباشر الخاص بك على أجهزة المستخدم الحقيقية واتصالات الشبكة. يمكن أن يساعد في تحديد الاختناقات في التعليمات البرمجية من جانب العميل والخادم من خلال:
- توقيت المستخدم: قياس مخصص لأداء وظيفة JavaScript من جانب العميل
- توقيت الطلاء: مقاييس عرض المتصفح
- توقيت الموارد: تحميل أداء الأصول ومكالمات Ajax
- توقيت التنقل: مقاييس تحميل الصفحة ، بما في ذلك عمليات إعادة التوجيه وعمليات البحث في نظام أسماء النطاقات واستعداد DOM والمزيد
تعالج API العديد من المشكلات المرتبطة بتقييم الأداء النموذجي:
- غالبًا ما يختبر المطورون التطبيقات على أجهزة الكمبيوتر المتطورة المتصلة بشبكة سريعة. يمكن لـ DevTools محاكاة الأجهزة الأبطأ ، ولكنها لن تسلط الضوء دائمًا على مشكلات العالم الحقيقي عندما يقوم غالبية العملاء بتشغيل هاتف محمول عمره عامين متصل بشبكة WiFi في المطار.
- غالبًا ما يتم حظر خيارات الجهات الخارجية مثل Google Analytics ، مما يؤدي إلى نتائج وافتراضات منحرفة. قد تواجه أيضًا آثارًا تتعلق بالخصوصية في بعض البلدان.
- يمكن لواجهة برمجة التطبيقات للأداء قياس المقاييس المختلفة بدقة أفضل من طرق مثل
Date()
.
تصف الأقسام التالية الطرق التي يمكنك من خلالها استخدام Performance API. يوصى ببعض المعرفة بجافا سكريبت ومقاييس تحميل الصفحة.
توافر أداء API
تدعم معظم المتصفحات الحديثة Performance API - بما في ذلك IE10 و IE11 (حتى IE9 لديه دعم محدود). يمكنك الكشف عن وجود API باستخدام:
if ('performance' in window) { // use Performance API }
لا يمكن استخدام Polyfill لواجهة برمجة التطبيقات بالكامل ، لذا كن حذرًا بشأن المتصفحات المفقودة. إذا كان 90٪ من المستخدمين يتصفحون بسعادة باستخدام Internet Explorer 8 ، فأنت تقيس 10٪ فقط من العملاء الذين لديهم تطبيقات أكثر قدرة.
يمكن استخدام API في Web Workers ، والتي توفر طريقة لتنفيذ العمليات الحسابية المعقدة في سلسلة رسائل في الخلفية دون إيقاف عمليات المتصفح.
يمكن استخدام معظم طرق API في Node.js من جانب الخادم مع الوحدة النمطية perf_hooks القياسية:
// Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );
يوفر Deno واجهة API القياسية للأداء:
// Deno performance console.log( performance.now() );
ستحتاج إلى تشغيل البرامج النصية --allow-hrtime
لتمكين قياس الوقت بدقة عالية:
deno run --allow-hrtime index.js
عادةً ما يكون تقييم الأداء من جانب الخادم أسهل في التقييم والإدارة لأنه يعتمد على الحمل ووحدات المعالجة المركزية وذاكرة الوصول العشوائي والأقراص الثابتة وحدود الخدمة السحابية. يمكن أن تكون ترقيات الأجهزة أو خيارات إدارة العمليات مثل PM2 والتجميع و Kubernetes أكثر فاعلية من كود إعادة البناء.
تركز الأقسام التالية على أداء العميل لهذا السبب.
قياس الأداء المخصص
يمكن استخدام Performance API لتوقيت سرعة تنفيذ وظائف التطبيق الخاص بك. ربما تكون قد استخدمت أو صادفت وظائف التوقيت باستخدام Date()
:
const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
يقدم Performance API ميزتين أساسيتين:
- دقة أفضل: يقيس
Date()
أقرب مللي ثانية ، لكن Performance API يمكنها قياس أجزاء من ميلي ثانية (اعتمادًا على المستعرض). - موثوقية أفضل: يمكن للمستخدم أو نظام التشغيل تغيير وقت النظام بحيث لا تكون المقاييس المستندة إلى
Date()
دقيقة دائمًا. هذا يعني أن وظائفك قد تبدو بطيئة بشكل خاص عندما تتحرك الساعات إلى الأمام!
المكافئ Date()
هو performance.now()
الذي يعرض طابعًا زمنيًا عالي الدقة يتم تعيينه على الصفر عندما تبدأ العملية المسؤولة عن إنشاء المستند (تم تحميل الصفحة):
const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
performance.timeOrigin
غير قياسي.يمكن لخاصية timeOrigin أيضًا إرجاع طابع زمني من 1 يناير 1970 على الرغم من أن هذا غير متاح في IE و Deno.
performance.now()
يصبح غير عملي عند إجراء أكثر من عدد قليل من القياسات. يوفر Performance API مخزنًا مؤقتًا حيث يمكنك تسجيل حدث لتحليله لاحقًا عن طريق تمرير اسم التصنيف إلى performance.mark()
:
performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');
يمكن استخراج مصفوفة من جميع كائنات العلامات في المخزن المؤقت للأداء باستخدام:
const mark = performance.getEntriesByType('mark');
نتيجة المثال:
[ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]
تحسب طريقة performance.measure()
الوقت بين علامتين وتخزنه أيضًا في المخزن المؤقت للأداء. يمكنك تمرير اسم مقياس جديد ، واسم علامة البداية (أو فارغ للقياس من تحميل الصفحة) ، واسم علامة النهاية (أو فارغ للقياس إلى الوقت الحالي):
performance.measure('init', 'start:init', 'end:init');
يتم إلحاق كائن PerformanceMeasure بالمخزن المؤقت بالمدة الزمنية المحسوبة. للحصول على هذه القيمة ، يمكنك إما طلب مصفوفة من جميع المقاييس:
const measure = performance.getEntriesByType('measure');
أو طلب إجراء باسمه:
performance.getEntriesByName('init');
نتيجة المثال:
[ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]
استخدام المخزن المؤقت للأداء
بالإضافة إلى العلامات والمقاييس ، يتم استخدام المخزن المؤقت للأداء لتسجيل توقيت التنقل وتوقيت المورد وتوقيت الطلاء تلقائيًا (والذي سنناقشه لاحقًا). يمكنك الحصول على مصفوفة من كافة الإدخالات في المخزن المؤقت:
performance.getEntries();
بشكل افتراضي ، توفر معظم المتصفحات مخزنًا مؤقتًا يخزن ما يصل إلى 150 مقياسًا للموارد. يجب أن يكون هذا كافيًا لمعظم التقييمات ، ولكن يمكنك زيادة أو تقليل حد المخزن المؤقت إذا لزم الأمر:
// record 500 metrics performance.setResourceTimingBufferSize(500);
يمكن مسح العلامات بالاسم أو يمكنك تحديد قيمة فارغة لمسح جميع العلامات:
performance.clearMarks('start:init');
وبالمثل ، يمكن مسح المقاييس بالاسم أو بقيمة فارغة لمسح الكل:
performance.clearMeasures();
مراقبة تحديثات المخزن المؤقت للأداء
يمكن لخادم PerformanceObserver مراقبة التغييرات التي يتم إجراؤها على المخزن المؤقت للأداء وتشغيل وظيفة عند حدوث أحداث معينة. سيكون بناء الجملة مألوفًا إذا استخدمت MutationObserver للرد على تحديثات DOM أو IntersectionObserver لاكتشاف متى يتم تمرير العناصر في إطار العرض.
يجب عليك تحديد وظيفة مراقب مع معلمتين:
- مجموعة من إدخالات المراقب التي تم اكتشافها ، و
- كائن المراقب. إذا لزم الأمر ، يمكن استدعاء طريقة
disconnect()
لإيقاف المراقب.
function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }
يتم تمرير الوظيفة إلى كائن PerformanceObserver جديد. يتم تمرير أسلوب observe()
الخاص به في مصفوفة من إدخال المخزن المؤقت للأداء.
let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });
في هذا المثال ، تؤدي إضافة علامة أو مقياس جديد إلى تشغيل وظيفة performanceCallback()
. بينما يقوم بتسجيل الرسائل هنا فقط ، يمكن استخدامه لتشغيل تحميل البيانات أو إجراء المزيد من العمليات الحسابية.

قياس أداء الدهان
لا تتوفر واجهة برمجة تطبيقات Paint Timing إلا في JavaScript من جانب العميل وتقوم تلقائيًا بتسجيل مقياسين مهمين لـ Core Web Vitals:
- الرسم الأول: بدأ المتصفح في رسم الصفحة.
- الرسم الأول للمحتوى : رسم المتصفح أول عنصر مهم من محتوى DOM ، مثل عنوان أو صورة.
يمكن استخراجها من المخزن المؤقت للأداء إلى مصفوفة:
const paintTimes = performance.getEntriesByType('paint');
كن حذرًا بشأن تشغيل هذا قبل أن يتم تحميل الصفحة بالكامل ؛ القيم لن تكون جاهزة. إما أن تنتظر حدث window.load
أو استخدم PerformanceObserver
لمراقبة أنواع إدخال paint
.
نتيجة المثال:
[ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]
غالبًا ما ينتج الطلاء الأول البطيء عن حظر عرض CSS أو JavaScript. قد تكون الفجوة في الرسم ذي المحتوى الأول كبيرة إذا كان على المستعرض تنزيل صورة كبيرة أو عرض عناصر معقدة.
قياس أداء الموارد
يتم تسجيل توقيتات الشبكة لموارد مثل الصور وأوراق الأنماط وملفات JavaScript تلقائيًا في المخزن المؤقت للأداء. على الرغم من أنه لا يمكنك فعل الكثير لحل مشكلات سرعة الشبكة (بخلاف تقليل أحجام الملفات) ، إلا أنه يمكن أن يساعد في تسليط الضوء على المشكلات المتعلقة بالأصول الكبيرة أو استجابات Ajax البطيئة أو البرامج النصية لجهات خارجية سيئة الأداء.
يمكن استخراج مجموعة من مقاييس PerformanceResourceTiming من المخزن المؤقت باستخدام:
const resources = performance.getEntriesByType('resource');
بدلاً من ذلك ، يمكنك جلب مقاييس لأحد الأصول عن طريق تمرير عنوان URL الكامل الخاص به:
const resource = performance.getEntriesByName('https://test.com/script.js');
نتيجة المثال:
[ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]
يمكن فحص الخصائص التالية:
- الاسم : عنوان URL للمورد
- نوع الإدخال : "مورد"
- initatorType : كيف تم بدء المورد ، مثل "script" أو "link"
- serverTiming : مجموعة من كائنات
PerformanceServerTiming
التي تم تمريرها بواسطة الخادم في رأس HTTP Server-Timing (يمكن للتطبيق من جانب الخادم إرسال المقاييس إلى العميل لمزيد من التحليل) - startTime : الطابع الزمني عندما بدأ الجلب
- nextHopProtocol : بروتوكول الشبكة المستخدم
- workerStart : الطابع الزمني قبل بدء عامل خدمة تطبيق الويب التقدمي (0 إذا لم يتم اعتراض الطلب من قبل عامل الخدمة)
- redirectStart : الطابع الزمني عند بدء إعادة التوجيه
- redirectEnd : الطابع الزمني بعد البايت الأخير لاستجابة إعادة التوجيه الأخيرة
- fetchStart : الطابع الزمني قبل جلب المورد
- domainLookupStart : الطابع الزمني قبل بحث DNS
- domainLookupEnd : الطابع الزمني بعد البحث عن DNS
- connectStart : الطابع الزمني قبل إنشاء اتصال بالخادم
- connectEnd : الطابع الزمني بعد إنشاء اتصال بالخادم
- secureConnectionStart : الطابع الزمني قبل تأكيد اتصال SSL
- requestStart : الطابع الزمني قبل أن يطلب المتصفح المورد
- responseStart : الطابع الزمني عندما يتلقى المتصفح البايت الأول من البيانات
- responseEnd : الطابع الزمني بعد استلام البايت الأخير أو إغلاق الاتصال
- المدة : الفرق بين startTime و responseEnd
- TransferSize : حجم المورد بالبايت بما في ذلك الرأس والجسم المضغوط
- encodedBodySize : نص المصدر بالبايت قبل فك الضغط
- decodedBodySize : نص المصدر بالبايت بعد فك الضغط
يسترد هذا البرنامج النصي النموذجي جميع طلبات Ajax التي بدأتها Fetch API ويعيد إجمالي حجم ومدة النقل:
const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );
قياس أداء الملاحة
يتم تسجيل توقيتات الشبكة لتفريغ الصفحة السابقة وتحميل الصفحة الحالية تلقائيًا في المخزن المؤقت للأداء ككائن PerformanceNavigationTiming
واحد.
استخرجه إلى مصفوفة باستخدام:
const pageTime = performance.getEntriesByType('navigation');
... أو بتمرير عنوان URL للصفحة إلى .getEntriesByName()
:
const pageTiming = performance.getEntriesByName(window.location);
المقاييس مماثلة لتلك الخاصة بالموارد ولكنها تتضمن أيضًا قيمًا خاصة بالصفحة:
- entryType : على سبيل المثال ، "navigation"
- النوع : إما "تنقل" أو "إعادة تحميل" أو "back_forward" أو "العرض المسبق"
- redirectCount : عدد عمليات إعادة التوجيه
- unloadEventStart : الطابع الزمني قبل حدث إلغاء التحميل للمستند السابق
- unloadEventEnd : الطابع الزمني بعد حدث إلغاء التحميل للمستند السابق
- domInteractive : الطابع الزمني عندما يقوم المتصفح بتحليل HTML وإنشاء DOM
- domContentLoadedEventStart : الطابع الزمني قبل إطلاق حدث DOMContentLoaded الخاص بالمستند
- domContentLoadedEventEnd : الطابع الزمني بعد اكتمال حدث DOMContentLoaded للمستند
- domComplete : الطابع الزمني بعد بناء DOM واكتمال أحداث DOMContentLoaded
- loadEventStart : الطابع الزمني قبل بدء حدث تحميل الصفحة
- loadEventEnd : الطابع الزمني بعد حدث تحميل الصفحة وجميع الأصول متاحة
تشمل القضايا النموذجية ما يلي:
- تأخير طويل بين unloadEventEnd و domInteractive . قد يشير هذا إلى بطء استجابة الخادم.
- تأخير طويل بين domContentLoadedEventStart و domComplete . قد يشير هذا إلى أن البرامج النصية لبدء الصفحة بطيئة جدًا.
- تأخير طويل بين domComplete و loadEventEnd . قد يشير هذا إلى أن الصفحة بها عدد كبير جدًا من الأصول أو أن العديد منها يستغرق وقتًا طويلاً للتحميل.
تسجيل وتحليل الأداء
تسمح لك واجهة برمجة التطبيقات Performance API بجمع بيانات الاستخدام الواقعية وتحميلها على الخادم لمزيد من التحليل. يمكنك استخدام خدمة تابعة لجهة خارجية مثل Google Analytics لتخزين البيانات ، ولكن هناك خطر من إمكانية حظر البرنامج النصي لجهة خارجية أو إدخال مشكلات جديدة في الأداء. يمكن تخصيص الحل الخاص بك وفقًا لمتطلباتك لضمان عدم تأثير المراقبة على الوظائف الأخرى.
كن حذرًا من المواقف التي لا يمكن فيها تحديد الإحصائيات - ربما لأن المستخدمين يستخدمون متصفحات قديمة ، أو يحظرون JavaScript ، أو خلف وكيل شركة. يمكن أن يكون فهم البيانات المفقودة أكثر فائدة من وضع افتراضات بناءً على معلومات غير كاملة.
من الناحية المثالية ، لن تؤثر البرامج النصية للتحليل سلبًا على الأداء عن طريق إجراء عمليات حسابية معقدة أو تحميل كميات كبيرة من البيانات. ضع في اعتبارك استخدام العاملين على الويب وتقليل استخدام مكالمات التخزين المحلي المتزامن. من الممكن دائمًا تجميع البيانات الأولية في وقت لاحق.
أخيرًا ، كن حذرًا من القيم المتطرفة مثل الأجهزة والوصلات السريعة جدًا أو البطيئة جدًا التي تؤثر سلبًا على الإحصائيات. على سبيل المثال ، إذا قام تسعة مستخدمين بتحميل صفحة في ثانيتين ، لكن التنزيل العاشر يستغرق 60 ثانية ، فإن متوسط وقت الاستجابة يصل إلى ما يقرب من 8 ثوانٍ. المقياس الأكثر واقعية هو الرقم المتوسط (ثانيتان) أو النسبة المئوية التسعون (9 من كل 10 مستخدمين يواجهون وقت تحميل يبلغ ثانيتين أو أقل).
ملخص
يظل أداء الويب عاملاً بالغ الأهمية للمطورين. يتوقع المستخدمون استجابة المواقع والتطبيقات على معظم الأجهزة. يمكن أن يتأثر تحسين محركات البحث أيضًا حيث يتم إرجاع المواقع الأبطأ إلى إصدار سابق في Google.
هناك الكثير من أدوات مراقبة الأداء ، ولكن معظمها يقيم سرعات التنفيذ من جانب الخادم أو يستخدم عددًا محدودًا من العملاء المؤهلين للحكم على عرض المتصفح. توفر واجهة برمجة تطبيقات الأداء طريقة لجمع مقاييس المستخدم الحقيقية بحيث لا يمكن حسابها بأي طريقة أخرى.