מעקב אחר השימוש הכולל בזיכרון בדף האינטרנט שלך באמצעותmeasureUserAgentspecificMemory()

איך מודדים את השימוש בזיכרון של דף האינטרנט בסביבת הייצור כדי לזהות רגרסיות

Ulan Degenbaev
Ulan Degenbaev

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

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

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

כאן אין יותר צורך במערך הגדול יותר b, אבל הדפדפן לא משחרר אותו כי עדיין אפשר לגשת אליו דרך object.b בקריאה החוזרת. כך מתרחשת דליפת זיכרון מהמערך הגדול יותר.

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

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

מה ההבדל בין performance.measureUserAgentSpecificMemory() לבין ה-API הקודם performance.memory?

אם אתם מכירים את ממשק ה-API הלא סטנדרטי הקיים של performance.memory, יכול להיות שאתם תוהים מה ההבדל בינו לבין ממשק ה-API החדש. ההבדל העיקרי הוא שב-API הישן מוחזר הגודל של אשכול ה-JavaScript, ואילו ב-API החדש מתבצעת הערכה של הזיכרון שבו נעשה שימוש בדף האינטרנט. ההבדל הזה הופך להיות חשוב כש-Chrome משתף את אותו אשכול עם כמה דפי אינטרנט (או כמה מופעים של אותו דף אינטרנט). במקרים כאלה, התוצאה של ה-API הישן עשויה להיות שגויה באופן שרירותי. מאחר שה-API הישן מוגדר במונחים ספציפיים להטמעה, כמו 'heap', אי אפשר לבצע סטנדרטיזציה שלו.

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

הצעות לתרחישים לדוגמה

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

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

תאימות דפדפן

תמיכה בדפדפנים

  • Chrome: ‏ 89.
  • Edge: ‏ 89.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

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

שימוש ב-performance.measureUserAgentSpecificMemory()

זיהוי תכונות

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

אפשר לזהות את התמיכה במהלך זמן הריצה:

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

בדיקה מקומית

Chrome מבצע את מדידת הזיכרון במהלך האיסוף של האשפה, כלומר ה-API לא פותר את הבטחת התוצאה באופן מיידי, אלא מחכה לאיסוף האשפה הבא.

קריאה ל-API מאלצת איסוף אשפה אחרי זמן קצוב לתפוגה, שמוגדר כרגע ל-20 שניות, אבל יכול לקרות מוקדם יותר. הפעלת Chrome עם הדגל --enable-blink-features='ForceEagerMeasureMemory' בשורת הפקודה מפחיתה את זמן הקצאת הזמן לתפוגה לאפס, והיא שימושית לניפוי באגים ולבדיקה מקומית.

דוגמה

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

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

קודם מגדירים פונקציה שמתזמנת את מדידת הזיכרון הבאה באמצעות setTimeout() עם מרווח זמן אקראי.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

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

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

לבסוף, הפונקציה performMeasurement() האסינכרונית מפעילה את ה-API, מתעדת את התוצאה ומקבצת את המדידה הבאה.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

לבסוף, מתחילים למדוד.

// Start measurements.
scheduleMeasurement();

התוצאה עשויה להיראות כך:

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

האומדן של סך השימוש בזיכרון מוחזר בשדה bytes. הערך הזה תלוי מאוד בהטמעה, ואי אפשר להשוות בין דפדפנים. הוא עשוי להשתנות גם בין גרסאות שונות של אותו דפדפן. הערך כולל את הזיכרון של JavaScript ו-DOM בכל iframe, בחלונות קשורים ובעובדים באינטרנט בתהליך הנוכחי.

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

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

משוב

קבוצת הקהילה בנושא ביצועי אינטרנט וצוות Chrome ישמחו לשמוע את המחשבות והחוויות שלכם לגבי performance.measureUserAgentSpecificMemory().

תיאור של עיצוב ה-API

האם יש משהו ב-API שלא פועל כצפוי? או אולי חסרים לכם מאפיינים שדרושים לכם כדי להטמיע את הרעיון? אפשר לשלוח דיווח על בעיה במפרט במאגר GitHub של performance.measureUserAgentSpecificMemory() או להוסיף את המחשבות שלכם לבעיה קיימת.

דיווח על בעיה בהטמעה

מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? שולחים דיווח על באג בכתובת new.crbug.com. חשוב לכלול כמה שיותר פרטים, לספק הוראות פשוטות לשחזור הבאג ולהגדיר את Components לערך Blink>PerformanceAPIs. Glitch הוא כלי מצוין לשיתוף שחזור מהיר וקל של באגים.

הצגת תמיכה

האם יש לך כוונה להשתמש ב-performance.measureUserAgentSpecificMemory()? התמיכה הציבורית שלכם עוזרת לצוות Chrome לתת עדיפות לתכונות, ומראה לספקי דפדפנים אחרים כמה חשובה התמיכה בהן. אפשר לשלוח ציוץ אל @ChromiumDev ולספר לנו איפה ואיך אתם משתמשים בו.

קישורים שימושיים

תודות

תודה רבה ל-Domenic Denicola, ‏ Yoav Weiss, ‏ Mathias Bynens על בדיקות העיצוב של ממשקי ה-API, ול-Dominik Inführ, ‏ Hannes Payer, ‏ Kentaro Hara, ‏ Michael Lippautz על בדיקות הקוד ב-Chrome. תודה גם ל-Per Parker,‏ Philipp Weis,‏ Olga Belomestnykh,‏ Matthew Bolohan ו-Neil Mckay על משוב חשוב ממשתמשים ששיפר מאוד את ה-API.

תמונה ראשית (Hero) של Harrison Broadbent ב-Unsplash