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

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

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

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

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

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

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

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

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

תודות

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

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