מידע על העקרונות הבסיסיים של השימוש בממשקי ה-API של Navigation ו-Resource Timing להערכת ביצועי הטעינה בשטח.
תאריך פרסום: 8 באוקטובר 2021
אם השתמשתם בוויסות הקצב של החיבורים בחלונית הרשת בכלים למפתחים בדפדפן (או ב-Lighthouse ב-Chrome) כדי להעריך את ביצועי הטעינה, אתם יודעים כמה נוחים הכלים האלה לכוונון הביצועים. בעזרת מהירות חיבור יציבה ועקבית, תוכלו למדוד במהירות את ההשפעה של אופטימיזציות הביצועים. הבעיה היחידה היא שמדובר בבדיקות סינתטיות שמניבות נתוני מעבדה, ולא נתוני שדה.
בדיקה סינתטית היא לא גרועה מטבעה, אבל היא לא מייצגת את מהירות הטעינה של האתר אצל משתמשים אמיתיים. לשם כך נדרשים נתוני שדה, שאפשר לאסוף מ-Navigation Timing API ומ-Resource Timing API.
ממשקי API שיעזרו לכם להעריך את ביצועי הטעינה בשטח
Navigation Timing ו-Resource Timing הם שני ממשקי API דומים עם חפיפה משמעותית, שמודדים שני דברים נפרדים:
- המדד תזמון ניווט מודד את מהירות הבקשות למסמכי HTML (כלומר, בקשות ניווט).
- תזמון משאבים: המדד הזה מודד את מהירות הבקשות למשאבים שמוגדרים למסמך, כמו CSS, JavaScript, תמונות וסוגים אחרים של משאבים.
ממשקי ה-API האלה חושפים את הנתונים שלהם במאגר נתוני ביצועים, שאפשר לגשת אליו בדפדפן באמצעות JavaScript. יש כמה דרכים לשליחת שאילתות למאגר ביצועים, אבל דרך נפוצה היא באמצעות performance.getEntriesByType
:
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');
// Get Resource Timing entries:
performance.getEntriesByType('resource');
הפונקציה performance.getEntriesByType
מקבלת מחרוזת שמתארת את סוג הרשומות שרוצים לאחזר מהמאגר של רשומות הביצועים. 'navigation'
ו-'resource'
מאחזרים את זמני האירועים של ממשקי ה-API Navigation Timing ו-Resource Timing, בהתאמה.
כמות המידע שמספקים ממשקי ה-API האלה עשויה להיות מרתיעה, אבל הם המפתח למדידת ביצועי הטעינה בשטח, כי אפשר לאסוף את זמני הטעינה האלה ממשתמשים בזמן שהם מבקרים באתר.
משך החיים והתזמון של בקשת רשת
איסוף וניתוח של תזמונים של ניווט ומשאבים הוא תהליך שדומה לארכיאולוגיה, כי אתם משחזרים את החיים הקצרים של בקשת רשת לאחר מעשה. לפעמים קל יותר להבין מושגים באמצעות הדמיה, ובמקרה של בקשות רשת, הכלים למפתחים בדפדפן יכולים לעזור.
משך החיים של בקשת רשת כולל שלבים ייחודיים, כמו חיפוש DNS, יצירת חיבור, משא ומתן של TLS ומקורות אחרים של זמן אחזור. התזמונים האלה מיוצגים כ-DOMHighResTimestamp
. בהתאם לדפדפן, רמת הפירוט של הזמנים עשויה להיות עד מיקרו-שנייה, או להיחשב למילי-שנייה. מומלץ לבחון את השלבים האלה בפירוט, ואת האופן שבו הם קשורים לזמני הניווט ולזמני המשאבים.
חיפוש DNS
כשמשתמש עובר לכתובת URL, מתבצעת שאילתה במערכת שמות הדומיינים (DNS) כדי לתרגם דומיין לכתובת IP. התהליך הזה עשוי להימשך זמן רב, אפילו משך זמן שכדאי למדוד בשטח. במדדים 'תזמון הניווט' ו'תזמון המשאבים' מוצגים שני תזמונים שקשורים ל-DNS:
domainLookupStart
הוא המועד שבו מתחיל חיפוש ה-DNS.domainLookupEnd
הוא הזמן שבו חיפוש ה-DNS מסתיים.
כדי לחשב את משך הזמן הכולל של חיפוש ה-DNS, מחסירים את מדד ההתחלה ממדד הסיום:
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
משא ומתן על חיבור
גורם נוסף שמשפיע על ביצועי הטעינה הוא משא ומתן על חיבור, שהוא זמן האחזור שנוצר כשמתחברים לשרת אינטרנט. אם מעורב HTTPS, התהליך הזה יכלול גם את זמן ניהול המשא ומתן באמצעות TLS. שלב החיבור מורכב משלושה תזמונים:
connectStart
הוא האירוע שבו הדפדפן מתחיל לפתוח חיבור לשרת אינטרנט.secureConnectionStart
מסמן מתי הלקוח מתחיל במשא ומתן ב-TLS.connectEnd
הוא הזמן שבו החיבור לשרת האינטרנט הוקם.
מדידה של זמן החיבור הכולל דומה למדידת זמן החיפוש הכולל של DNS: מחסירים את תזמון ההתחלה מתזמון הסיום. עם זאת, יש נכס 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, הם (בעיקר) מחוץ לשליטתנו, כי המשתמשים יכולים לגשת לאינטרנט מכל מקום.
- גורמים פנימיים: אלה דברים כמו ארכיטקטורות של שרתים וצד לקוח, וגם גודל המשאבים והיכולת שלנו לבצע אופטימיזציה לדברים האלה, שאנחנו שולטים בהם.
שני סוגי הגורמים משפיעים על ביצועי הטעינה. לזמנים שקשורים לגורמים האלה יש חשיבות רבה, כי הם מתארים את משך הזמן שלוקח להוריד את המשאבים. גם המדד 'זמני ניווט' וגם המדד 'זמני טעינה של משאבים' מתארים את ביצועי הטעינה באמצעות המדדים הבאים:
fetchStart
מסמנת את המועד שבו הדפדפן מתחיל לאחזר משאב (זמן אחזור משאבים) או מסמך עבור בקשת ניווט (זמן ניווט). השלב הזה מתרחש לפני הבקשה בפועל, והוא השלב שבו הדפדפן בודק מטמון (לדוגמה, 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;
מדידות נוספות שאפשר לבצע
אפשר להשתמש בנתוני 'תזמון ניווט' ובנתוני 'תזמון משאבים' למטרות נוספות מעבר לאלה שמפורטות בדוגמאות הקודמות. ריכזנו כאן כמה מצבים נוספים עם תזמונים רלוונטיים שכדאי לבדוק:
- הפניות אוטומטיות לדפים: הפניות אוטומטיות הן מקור מוזנח לעיכוב נוסף, במיוחד שרשרות של הפניות אוטומטיות. זמן האחזור מתווסף במספר דרכים, כגון צעדים מסוג HTTP-ל-HTTP וכן הפניות 301/שלא נשמרו במטמון. התזמונים
redirectStart
,redirectEnd
ו-redirectCount
עוזרים בהערכת זמן האחזור של הפניה אוטומטית. - פריקה של מסמך: בדפים שמריצים קוד במתבצע אירוע
unload
, הדפדפן צריך להריץ את הקוד הזה לפני שהוא יכול לנווט לדף הבא.unloadEventStart
ו-unloadEventEnd
מודדים את טעינת המסמך. - עיבוד מסמכים: ייתכן שלזמן עיבוד המסמכים לא תהיה השפעה, אלא אם האתר שלכם שולח מטענים ייעודיים (payloads) גדולים מאוד של 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
});
השיטה הזו של איסוף תזמונים עשויה להיראות מוזרה בהשוואה לגישה ישירה למאגר הנתונים הזמני של הביצועים, אבל עדיף לקשר את ה-thread הראשי לעבודה שלא משרתת מטרה קריטית ולא משרתת את המשתמשים.
איך להתקשר הביתה
אחרי שאתם אוספים את כל הזמנים הנדרשים, אתם יכולים לשלוח אותם לנקודת קצה לצורך ניתוח נוסף. אפשר לעשות זאת בשתי דרכים: באמצעות 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 תגיע במטען ייעודי (payload) מסוג POST
, שאפשר לפענח, לעבד ולאחסן בקצה העורפי של האפליקציה לפי הצורך.
סיכום
אחרי שאוספים את המדדים, אתם צריכים להחליט איך לנתח את נתוני השדה האלה. כשמנתחים נתונים מהשטח, יש כמה כללים כלליים שכדאי לפעול לפיהם כדי לוודא שמגיעים למסקנות משמעותיות:
- הימנעו משימוש בערכים ממוצעים, כי הם לא מייצגים את חוויית המשתמש של אף משתמש, ויכול להיות שהם מוטים בגלל ערכים חריגים.
- להסתמך על מאונים. במערכי נתונים של מדדי ביצועים מבוססי-זמן, עדיף שהערך יהיה נמוך יותר. כלומר, כשנותנים עדיפות לפרמטרים של אחוזונים נמוכים, מתמקדים רק בחוויית השימוש המהירה ביותר.
- נותנים עדיפות לערכים בחלק הארוך של הטווח. כשנותנים עדיפות לחוויות שנמצאות ב-75% העליונים או יותר, אפשר להתמקד במקום הנכון: בחוויות האיטיות ביותר.
המדריך הזה לא מיועד להיות מקור מידע מקיף בנושא ניווט או תזמון משאבים, אלא נקודת התחלה. הנה כמה מקורות מידע נוספים שיכולים לעזור:
- מפרט תזמון הניווט.
- Resource Timing Spec.
- שימוש ב-ResourceTiming
- Navigation Timing API (MDN)
- Resource Timing API (MDN)
בעזרת ממשקי ה-API האלה והנתונים שהם מספקים, תוכלו להבין טוב יותר איך משתמשים אמיתיים חווים את ביצועי הטעינה, וכך תהיה לכם יותר ביטחון באבחון ובפתרון בעיות בביצועי הטעינה בשטח.