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

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

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

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

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

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

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

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

תאימות דפדפן

תמיכה בדפדפן

  • 89
  • 89
  • x
  • x

מקור

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

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

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

פנייה לתמיכה

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

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

אישורים

תודה רבה ל-Dominic Denicola, Yoav Weiss, ל-Mathias Bynens לביקורות על עיצוב ה-API, ול-Dominik Inführ, Hannes Payer, Kantaro Hara, Michael Lippautz על בדיקות הקוד ב-Chrome. אני גם מודה ל'פר פארקר', פיליפ וייס, אולגה בלומסטיק, מת'יו בולולהאן וניל מקיי על מתן משוב רב-ערך ממשתמשים, ששיפר מאוד את ממשק ה-API.

תמונה ראשית (Hero) מאת האריסון ברודבנט בתוכנית Unused