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

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

Alex Danilo

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

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

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

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

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

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

var myTime = window.performance.now();

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

סוג DOMHighResTimeStamp

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

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

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

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

שימוש ב-mark()

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

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

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

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

window.performance.mark('mark_fully_loaded');

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

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

לאחר שהגדרתם כמה סימני תזמון, תרצו לדעת את הזמן שחלף ביניהם. לשם כך, עליכם להשתמש בשיטה 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_ שאי אפשר להשתמש' במאפיין startTime.

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

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

קודם נשנה את כל הבקשות של 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 באפליקציות שלך כבר עכשיו, ולגלות איך מאיצים אותן והמשתמשים יודו לך ששיפרת את החוויה שלהם הרבה יותר.