ממשק API לתזמון משתמש

הסבר על אפליקציית האינטרנט

Alex Danilo

אפליקציות אינטרנט עם ביצועים גבוהים הן חיוניות לחוויית משתמש מעולה. ככל שפלטפורמות האינטרנט הופכות ליותר ויותר מורכבות, חשוב להבין את ההשפעה על הביצועים כדי ליצור חוויית שימוש מרתקת. בשנים האחרונות הופיעו בדפדפן מספר ממשקי API שונים שיעזרו לנתח את ביצועי הרשת, זמני הטעינה וכו', אבל הם לא מספקים בהכרח פרטים מפורטים עם גמישות מספקת כדי למצוא מה מאט את האפליקציה. מזינים את User Timing API, שמספק מנגנון שבעזרתו אפשר להתקין רכיבים באפליקציית האינטרנט כדי לזהות איפה האפליקציה מבזבזת את הזמן. במאמר הזה נסביר על ה-API ונספק דוגמאות לשימוש בו.

אי אפשר לבצע אופטימיזציה של דברים שאי אפשר למדוד

השלב הראשון בהאצת אפליקציית אינטרנט איטית הוא להבין איפה הזמן מתבזבז. מדידת ההשפעה של זמן הביצוע על אזורים בקוד JavaScript היא הדרך האידיאלית לזהות נקודות חמות, והיא הצעד הראשון בזיהוי הדרכים לשיפור הביצועים. למרבה המזל, User Timing API מאפשר להוסיף קריאות ל-API בחלקים שונים של ה-JavaScript, ולאחר מכן לחלץ נתוני תזמון מפורטים שאפשר להשתמש בהם כדי לבצע אופטימיזציה.

זמן ברזולוציה גבוהה ו-now()

דיוק הוא חלק מהותי במדידת זמן מדויקת. בעבר, התזמון היה מבוסס על מדידה במילישניות, וזה בסדר, אבל כדי ליצור אתר ללא תנודות בקצב של 60FPS, כל פריים צריך להיוצר תוך 16 אלפיות השנייה. לכן, כשיש דיוק רק ברמת אלפית השנייה, אין את הדיוק הנדרש לניתוח טוב. מזינים High Resolution Time, סוג חדש של תזמון שמובנה בדפדפנים מודרניים. High Resolution Time (זמן ברזולוציה גבוהה) מאפשר לנו להשתמש בחותמות זמן של נקודה צפה שיכולות להיות מדויקות ברזולוציה של מיקרו-שנייה – פי אלף יותר טוב ממה שהיה קודם.

כדי לקבל את השעה הנוכחית באפליקציית האינטרנט, צריך להפעיל את השיטה now(), שהיא תוסף לממשק Performance. הקוד הבא מראה איך עושים את זה:

var myTime = window.performance.now();

יש ממשק נוסף שנקרא PerformanceTiming, שמספק מספר זמנים שונים שקשורים לאופן שבו אפליקציית האינטרנט נטענת. השיטה now() מחזירה את הזמן שחלף מאז שהזמן navigationStart ב-PerformanceTiming התרחש.

הסוג DOMHighResTimeStamp

בעבר, כשניסיתם למדוד את הזמן של אפליקציות אינטרנט, הייתם משתמשים בפונקציה כמו Date.now() שמחזירה DOMTimeStamp. הפונקציה DOMTimeStamp מחזירה מספר שלם של אלפיות שנייה כערך שלה. כדי לספק את הדיוק הגבוה יותר שנחוץ לשעון ברזולוציה גבוהה, הושק סוג חדש שנקרא DOMHighResTimeStamp. זהו ערך נקודה צפה (float) שמחזיר גם את הזמן באלפיות שנייה. אבל מכיוון שמדובר בנקודה צפה, הערך יכול לייצג אלפיות שנייה, ולכן יכול לספק דיוק של אלפית אלפית שנייה.

ממשק התזמון של המשתמשים

עכשיו, כשיש לנו חותמות זמן ברזולוציה גבוהה, נשתמש בממשק User Timing כדי לשלוף את פרטי התזמון.

ממשק User Timing מספק פונקציות שמאפשרות לנו להפעיל שיטות במקומות שונים באפליקציה, שיכולות לספק נתיב של פירוט נתיב ב-Hansel and Gretel כדי שנוכל לעקוב אחרי המקום שבו הזמן נצרך.

שימוש ב-mark()

השיטה mark() היא הכלי הראשי בכלי הניתוחים שלנו בנושא תזמון. הפונקציה mark() מאחסנת עבורנו חותמת זמן. היתרון הגדול של mark() הוא שאפשר לתת שם לחותמת הזמן, וה-API ישמור את השם ואת חותמת הזמן כיחידה אחת.

קריאה ל-mark() במקומות שונים באפליקציה מאפשרת לכם לחשב כמה זמן חלף עד שהגעתם ל'סימן' הזה באפליקציית האינטרנט.

במפרט מפורטות כמה הצעות לשמות של סימנים שעשויים להיות מעניינים וברורים למדי, כמו mark_fully_loaded,‏ mark_fully_visible,‏ mark_above_the_fold וכו'.

לדוגמה, אפשר להגדיר סימן למועד שבו האפליקציה נטענת במלואה באמצעות הקוד הבא:

window.performance.mark('mark_fully_loaded');

כשמגדירים סמנים עם שמות בכל רחבי אפליקציית האינטרנט, אפשר לאסוף המון נתוני תזמון ולנתח אותם בזמן שנוח לכם כדי להבין מה האפליקציה עושה ומתי.

חישוב מדידות באמצעות measure()

אחרי שמגדירים כמה סימני זמן, רוצים לדעת כמה זמן חלף ביניהם. כדי לעשות זאת, משתמשים ב-method‏ measure().

השיטה measure() מחשבת את משך הזמן שחלף בין סימנים, ויכולה גם למדוד את הזמן שחלף בין הסמן לבין כל אחד משמות האירועים המוכרים בממשק PerformanceTiming.

לדוגמה, אפשר לחשב את הזמן שחלף מהשלמת ה-DOM ועד לטעינת מצב האפליקציה במלואו באמצעות קוד כמו:

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

כשקוראים ל-measure(), התוצאה מאוחסנת ללא קשר לסימונים שהגדרתם, כך שתוכלו לאחזר אותם מאוחר יותר. אחסון זמני ההשבתה בזמן שהאפליקציה פועלת מאפשר לה להגיב במהירות, ואפשר למחוק את כל הנתונים אחרי שהאפליקציה מסיימת עבודה מסוימת כדי לנתח אותם מאוחר יותר.

ביטול סימנים באמצעות clearMarks()

לפעמים כדאי להסיר כמה סימנים שהגדרתם. לדוגמה, יכול להיות שאתם מבצעים הפעלות באצווה באפליקציית האינטרנט שלכם, ולכן אתם רוצים להתחיל מחדש בכל הפעלה.

קל להסיר את כל הסימונים שהגדרתם על ידי קריאה לפונקציה clearMarks().

לכן, הקוד לדוגמה שבהמשך ימחק את כל הסימונים הקיימים, כדי שתוכלו להגדיר שוב ריצה למדידת זמן אם תרצו.

window.performance.clearMarks();

כמובן, יש תרחישים מסוימים שבהם לא כדאי למחוק את כל הסימונים. לכן, אם רוצים להסיר סמנים ספציפיים, אפשר פשוט להעביר את שם הסימן שרוצים להסיר. לדוגמה, הקוד הבא:

window.performance.clearMarks('mark_fully_loaded');

מסיר את הסימון שהגדרתם בדוגמה הראשונה, בלי לשנות את הסימנים האחרים שהגדרתם.

יכול להיות שתרצו גם להסיר את כל האמצעים שביצעתם, ויש לכך שיטה מתאימה שנקראת clearMeasures(). הוא פועל בדיוק כמו clearMarks(), אבל במקום זאת הוא פועל על כל המדידות שביצעת. לדוגמה, הקוד:

window.performance.clearMeasures('measure_load_from_dom');

תסיר את המדד שיצרנו בדוגמה measure() שלמעלה. אם רוצים להסיר את כל המדדים, הפעולה זהה ל-clearMarks() – פשוט קוראים ל-clearMeasures() בלי ארגומנטים.

איך מוציאים את נתוני התזמון

זה נחמד להגדיר סימנים ולמדוד מרווחי זמן, אבל בשלב מסוים רוצים לגשת לנתוני התזמון האלה כדי לבצע ניתוח כלשהו. גם זה קל מאוד, כל מה שצריך לעשות הוא להשתמש בממשק PerformanceTimeline.

לדוגמה, השיטה getEntriesByType() מאפשרת לנו לקבל את כל זמני הסימון או את כל זמני המדידה כרשימה, כדי שנוכל להריץ עליה חזרה ולעבד את הנתונים. היתרון הוא שהרשימה מוחזרת בסדר כרונולוגי, כך שאפשר לראות את הסימנים לפי הסדר שבו הם הופעלו באפליקציית האינטרנט.

הקוד הבא:

var items = window.performance.getEntriesByType('mark');

מחזירה לנו רשימה של כל הסימנים שהופעלו באפליקציית האינטרנט שלנו, בעוד שהקוד:

var items = window.performance.getEntriesByType('measure');

מחזירה לנו רשימה של כל הפעולות שביצעו.

אפשר גם לקבל רשימה של רשומות באמצעות השם הספציפי שהקציתם להן. לדוגמה, הקוד:

var items = window.performance.getEntriesByName('mark_fully_loaded');

תחזיר לנו רשימה עם פריט אחד שמכיל את חותמת הזמן 'mark_fully_loaded' במאפיין startTime.

מדידת הזמן של בקשת XHR (דוגמה)

עכשיו, כשיש לנו תמונה טובה של User Timing API, אנחנו יכולים להשתמש בו כדי לנתח את משך הזמן של כל XMLHttpRequests באפליקציית האינטרנט שלנו.

קודם נשנה את כל הבקשות של send() כך שיוציאו קריאה לפונקציה שמגדירה את הסימנים, ובמקביל נשנה את קריאות ה-callbacks להצלחה לקריאה לפונקציה שמגדירה סימן נוסף ולאחר מכן יוצרת מדד של משך הזמן שלקח לבקשה להתבצע.

לכן, בדרך כלל ה-XMLHttpRequest שלנו ייראה כך:

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

בדוגמה שלנו נוסיף מונה גלובלי כדי לעקוב אחרי מספר הבקשות, וגם כדי לאחסן בו מדד לכל בקשה שנשלחת. הקוד לביצוע הפעולה הזו נראה כך:

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

הקוד שלמעלה יוצר מדד עם ערך שם ייחודי לכל XMLHttpRequest שאנחנו שולחים. אנחנו מניחים שהבקשות פועלות ברצף – הקוד לבקשות מקבילות צריך להיות קצת יותר מורכב כדי לטפל בבקשות שמוחזרות בסדר שונה, ואנחנו נשאיר את זה כתרגיל לקורא.

אחרי שאפליקציית האינטרנט תבצע כמה בקשות, נוכל להעביר את כולן למסוף באמצעות הקוד הבא:

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

סיכום

User Timing API מספק הרבה כלים מצוינים שאפשר להחיל על כל היבט של אפליקציית האינטרנט. כדי לצמצם את נקודות החולשה באפליקציה, אפשר לפזר קריאות API ברחבי אפליקציית האינטרנט ולבצע עיבוד נתוני תזמון שנוצרו לאחר מכן, כדי ליצור תמונה ברורה של המקום שבו הזמן שלכם נבלע. אבל מה קורה אם הדפדפן לא תומך ב-API הזה? אין בעיה, אפשר למצוא polyfill מצוין כאן שמחקה את ה-API בצורה טובה מאוד ועובד גם עם webpagetest.org. אז למה אתה מחכה? כדאי לנסות את User Timing API באפליקציות שלכם כבר עכשיו. כך תוכלו להבין איך לזרז אותן, והמשתמשים שלכם יודו לכם על שיפור חוויית השימוש שלהם.