כלים וביצועים

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

רמקולים

  • Rahul Ravikumar

    מהנדס תוכנה בכיר

מידע כללי

מידע על ההרצאה

כדי לבצע אופטימיזציה של אפליקציות ל-Android, צריך להבין לעומק את תהליכי העבודה המקבילים ואת ההשהיות של פונקציות ה-coroutine.

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

בשיעור הזה נציג תוכנית אדריכלית ללולאת משוב רציפה על הביצועים.

‫androidx.tracing 2.0: החלפת קצוות עורפיים באופן דינמי והפצת הקשרים של פונקציות Coroutine (תהליכי Perfetto) בין שרשורים.

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

Macrobenchmarking: מעקב אחרי רגרסיות ואימות של שיפורים בביצועים במכשירים פיזיים ב-CI.

מעקב בשטח: מעבר למעקב מקומי ו-CI, באמצעות ProfilingManager.

סיכום

  • ‫Tracing 2.0 מחליף ממשקי API ישנים יותר למעקב ב-Android כדי להסיר את המגבלות על אורך התוויות ולפעול במהירות גבוהה פי שבעה. הוא גם מאפשר לכם ליצור כיורים מותאמים אישית לקצוות עורפיים כמו OpenTelemetry.
  • הפצת ההקשר ממפה קשרים סיבתיים בין שרשורים, פונקציות קורוטין ותהליכים. אפשר לעקוב אחרי ביצוע אסינכרוני באמצעות traceCoroutine או להעביר אסימוני הפצה לקוד שמבוסס על קריאה חוזרת (callback).
  • בגרסה 1.13 של Jetpack Compose נוסף מעקב אחרי הרכבה מחדש, שמשתמש במטא-נתונים מותאמים אישית כדי להראות למה מתרחשות הרכבות מחדש. המעקבים מתעדים את ערכי המצב ששונו ואת סטאק הביצוע המדויק שהפעיל אותם.
  • כלי הפיתוח של Android משתמשים ב-Tracing 2.0 כדי לזהות צווארי בקבוק ולהאיץ את תהליכי ה-build. אופטימיזציות מונחות מעקב שהאיצו את כיווץ הקוד של R8 ב-25% וקיצרו את זמני השליחה של Metalava ב-15%.

Gemini Notebook

נושאים

  • מעקב
  • android-performance
  • perfetto
  • context-propagation
  • jetpack-compose
  • שגרות המשך (coroutines)
  • פרופיילינג

תמליל

הורדת התמליל

מבוא והתפתחות של מעקב ב-Android

  • Rahul Ravikumar

    שלום לכולם,

  • שמי Rahul.

  • אני מהנדס בצוות חוויית המפתחים של Android.

  • אני מתמקד בביצועים של אפליקציות וספריות, וההרצאה הזו עוסקת במעקב.

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

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

  • אני לא מדבר על פרופיילרים אוטומטיים, שאליהם אכנס בהמשך.

  • אז מעקב ידני הוא האומנות של השארת פירורי לחם ב-code base, כדי שתוכלו להבין אותו מאוחר יותר כשתהיה לכם בעיה בביצועים או כל בעיה אחרת.

  • בדרך כלל זה הרבה יותר טוב מיומנים.

  • אז אני לא יודע אם ראיתם פלט Logcat של אפליקציה גדולה.

  • בדרך כלל מדובר בטקסט ארוך מאוד שקשה להבין אותו בקלות.

  • לעומת זאת, מעקב אחר שיחות הוא הרבה יותר נחמד.

  • קל יותר להמחיש את זה.

  • יש לכם משכי זמן.

  • יש לכם מבנה מסוים.

  • יש לכם כלים שמאפשרים לכם לנתח את הנתונים ולראות בדיוק מה קרה ומתי.

  • וזה הרבה יותר נחמד.

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

  • אתם יכולים להשתמש בכלים כמו Trace Processor, בדיקות ביצועים ואפילו להמיר את המעקבים למדדים.

  • אז אלה כל הדברים שאפשר לעשות עם מעקבים, שקשה לעשות עם Logcat.

  • לעומת זאת, יצירת פרופילים, ואני מדבר על יצירת פרופילים אוטומטית, היא בדרך כלל תגובה לבעיה שאתם מודעים לה.

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

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

  • אפשר להשתמש במשהו כמו תיעוד method של ART, שבו מתעדים כל כניסה ויציאה של method.

  • או שאתם בודקים דגימת מחסנית באמצעות כלים כמו Simpleperf.

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

  • אנחנו בשנת 2026, זיכרון RAM הוא בעיה בכל מקום, אז יכול להיות שאתם בודקים את הזיכרון, דליפות זיכרון ותמונות מצב של ערימה.

  • אז זה עוד כלי ליצירת פרופילים שאתם יכולים להשתמש בו.

  • אתם יכולים לבדוק את נתיבי השמירה של ה-heap.

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

  • בסדר.

  • עכשיו יש לנו את אוצר המילים הזה.

  • עכשיו נדבר על הסיבה שבגללה יצרנו ספרייה חדשה.

  • ממשקי API למעקב, ממשקי ה-API בגרסה 1.0 הוצגו במקור ב-Android Jelly Bean.

  • זה היה ב-2012.

  • זה היה לפני יותר מ-14 שנים.

  • מאז, ממשקי ה-API לא התפתחו כמו שאתם חושבים.

  • הוספנו כמה תוספים.

  • אירועים ומונים אסינכרוניים נוספו ב-Android 10.

  • זה היה ב-2019.

  • וזהו.

  • ה-API לא השתנה מאז.

  • עם זאת, הסביבה העסקית השתנתה מאוד.

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

  • אנחנו רוצים לשנות את זה.

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

  • ‫Atrace היא תת-מערכת המעקב של Android שבבעלותה הנתונים האלה.

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

  • הפורמט הזה מתועד היטב, אבל יכול להיות שיהיה קשה לצרוך אותו.

  • אז זה משהו שאנחנו רוצים לתקן.

  • לכן, אנחנו רוצים לתת לכם את הכלים להעביר את נתוני המעקב שלכם לאן שתרצו.

  • בנוסף, ב-Tracing 1.0 הייתה הגבלה על אורך התוויות – עד 128 תווים.

  • יכול להיות שההגבלה הזו הייתה הגיונית ב-2012, אבל ב-2026 היא נראית קצת מוזרה.

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

  • ויש גם את ההוצאות הכלליות.

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

  • לכן, אם יש מרוץ תהליכים שאתם רוצים לשחזר, המעקב לא אמור לגרום למרוץ התהליכים להיעלם.

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

  • ל-Tracing 1.0 היה תקורה גבוהה יחסית, ורצינו לשפר את זה.

  • אוקיי.

  • דיברתי הרבה על הבעיות.

  • עכשיו נראה כמה מהבעיות באופן חזותי, ואיך הן מצטברות בפועל.

  • אז כאן יש לנו מעקב של Perfetto.

  • הוא מיועד לאפליקציה com.example.app.

  • יש לנו שרשור ראשי ופרוסה אחת שנקראת initializeComponent.

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

  • אז בואו נוסיף פרט.

  • עכשיו אנחנו יודעים ש-initializeComponent קורא ל-performInitialize.

  • אנחנו עדיין לא יודעים איזה רכיב מאותחל.

  • עכשיו, הכלי היחיד בארגז הכלים שלנו עם Tracing 1 הוא השם, אז אנחנו מוסיפים אותו לשם של הפרוסה.

  • חשוב לזכור: השמות של פרוסות המעקב מוגבלים ל-128 תווים, ולכן הם נחתכים במעקב.

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

  • אז עכשיו, אתם נתקלים בבעיה הזו די מהר.

  • אז זה משהו שבאמת רצינו לתקן.

  • ראיתם את המעקב של Perfetto ואת הייצוג החזותי שלו.

  • עכשיו נסתכל על זה מנקודת המבט של כתיבת הקוד כדי להפיק את קטעי המעקב האלה.

  • שוב, יש לנו את ה-API של initializeComponent.

  • היא מקבלת הקשר ושם, וקוראת ל-performInitialize.

  • עד עכשיו, הכול טוב ויפה.

  • עכשיו אנחנו רוצים להוסיף מעקב באמצעות Tracing 1.0 API, אז מייבאים את androidx.tracing.trace ומשתמשים בבלוק מעקב.

  • אתם נותנים לו שם.

  • הנה, זה performInitialize.

  • אבל כמו שרצינו לעשות קודם, רצינו לדעת את שם הרכיב, אז נשתמש בהשלמת מחרוזת של Kotlin ונוסיף את השם.

  • וכאן נמצאת הבעיה הראשונה.

  • אז כאן יש לנו את מגבלת 128 התווים.

סקירה כללית של מעקב 2.0 וביצועים

  • Rahul Ravikumar

    אנחנו יכולים לתקן את זה באמצעות Tracing 2.

  • במקום להשתמש ב-androidx.tracing.trace, משתמשים ב-Tracer.

  • ה-API נראה דומה מאוד.

  • במקום trace, יש לך Tracer.global.trace.

  • יש לכם קטגוריה.

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

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

  • עכשיו כבר אין מגבלת תווים של 128 תווים.

  • דיברתי על תקורה של מעקב.

  • אני רוצה להסביר קצת יותר לעומק איך שיפרנו את זה.

  • במכשיר Pixel 6a עם Android 16, הוספה של קטע התחלה וקטע סיום בודדים נמשכה כ-1,200 ננו-שניות.

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

  • אנחנו לא רוצים לבזבז את הזמן שלכם.

  • למעשה, שיפרנו את הדברים לא מעט באמצעות AndroidX Perfetto SDK.

  • זה היה SDK ניסיוני שהצגנו למעקב אחר קומפוזיציה, וזה למעשה שיפר מאוד את המצב.

  • עכשיו, אנחנו ממש לא רצינו שתשלחו את זה כי זה Perfetto C SDK עם קישורי JNI, וה-C SDK הוא בערך 4MB בתוך ה-APK שלכם.

  • אנחנו לא רוצים שדבר כזה יקרה.

  • עכשיו, עם Tracing 2, יש לנו ספרייה ייעודית.

  • שיפרנו את התקורה עוד יותר, ועכשיו תשמחו לדעת ש-Tracing 2 מהיר פי 7, מה שאומר שיש לכם יותר זמן לעשות את הדברים שרציתם לעשות, ואתם יודעים, התשתית לוקחת פחות זמן.

  • מה אפשר לעשות עכשיו שלא היה אפשר לעשות קודם?

  • אחת הדוגמאות היא תכונת המעקב בזמן הריצה של Metro.

  • אז Metro, למי שלא יודע, היא ספריית הזרקת תלויות.

  • היא תומכת ב-Kotlin Multiplatform.

  • הוא משתמש בפלאגין של מהדר Kotlin.

  • הוא יוצר את כל הקוד להזרקת תלויות, ומשתלב עם Tracing 2.0 כדי שיהיה קל יותר להמחיש את העלות של כל @Inject בקוד.

  • הנה דוגמה.

  • נניח שיש לנו תרשים של אפליקציה.

  • ב-Metro, כל דבר מוגדר בתרשים, ולכן יש לנו שדה אחד, AnalyticsTracker.

  • יש לנו גם קישור מסוג provides.

  • יש לנו לקוח רשת עם OkHttpClient כהטמעה, ואז ההטמעה של AnalyticsTracker מגיעה מ-AnalyticsTrackerImpl, ואנחנו צריכים מופע של לקוח הרשת, שהתרשים יודע איך לספק.

  • אז מאחורי הקלעים, הפלאגין של המקמפל יוצר קוד שנראה ככה.

  • לכן, אם יש לכם את AnalyticsTracker, ה-getter יוצר מופע של לקוח הרשת ומעביר אותו ל-constructor של AnalyticsTrackerImpl, וזה טוב.

  • אבל עכשיו, כשהמעקב מופעל, אפשר לראות את עלות היצירה לא רק של הטמעת המעקב, אלא של כל יחסי התלות שהוא צריך.

  • עכשיו אתם יכולים לראות לא רק את העלות של יצירת מופע של הסוג, אלא גם את כל יחסי התלות שלו במעקב, וזה ממש יעיל.

  • לדוגמה, בעזרת המידע הזה, Wise, אפליקציית שירותים פיננסיים, שיפרה את זמן ההפעלה של האפליקציה ב-45%.

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

  • אם מסתכלים על מדדי הסטארט-אפ שלהם, הם ממש מרשימים.

  • הם לא רק שיפרו את הזמן עד להצגת הפריים הראשוני, אלא גם את הזמן עד להצגה מלאה.

  • שותף אחר, BandLab, משתמש באותו מידע על CI.

  • כך אתם לא רק יודעים את העלות של כל קישור @Inject, אלא גם את ה-thread שגרם לעלות הזו.

  • אז אחרי שיודעים את זה, אפשר למצוא את כל הקישורים היקרים של @Inject בשרשור הראשי וב-CI, אפשר להריץ שאילתה כדי לומר, היי, תן לי רשימה של כל הקישורים היקרים ואז לייחס אותה בחזרה לשינוי שהציג אותם ואז אפשר עכשיו להאשים את השינוי בפועל ולנסות לתקן אותו.

הפצת הקשר, שגרות המשך (coroutine) ו-Jetpack Compose

  • Rahul Ravikumar

    אוקיי.

  • עד עכשיו דיברתי על כל הדברים ששיפרנו ביחס ל-Tracing 1, אבל עכשיו אדבר על תכונות חדשות ב-Tracing 2 שלא היו אפשריות בעבר.

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

  • אתם רוצים לראות איך בקשה מופצת בין כל שרשור לעומת תהליך.

  • וזה מה שהפצת ההקשר מאפשרת.

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

  • אבל אם הייתי אומר לכם שזו שרשרת האירועים הסיבתית שהתרחשה, היה לכם הרבה יותר קל להבין מה קרה ואז לשייך את העלות של כל פרוסה ופרוסה.

  • זה מה שמאפשרת הפצת ההקשר.

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

  • נחזור למעקב של com.example.app ב-Perfetto. נניח שהפעולה performInitialize נמשכה יותר מדי זמן ורציתם לדחות את העלות או להעביר אותה לשרשור אחר.

  • משהו שאתם לגמרי יכולים לעשות.

  • אז נניח שהעברתי את performInitialize לשרשור אחר.

  • עכשיו, איבדתם את האופי ההיררכי של הפרוסה.

  • עכשיו כבר אי אפשר לדעת ש-initializeComponent גרם ל-performInitialize לקרות, אלא אם מסתכלים על הקוד.

  • בעולם אידיאלי, הייתם רוצים חץ שיגיד לכם ש-A גרם ל-B, ואז תוכלו להסיק מסקנות לגבי שרשרת האירועים הסיבתית.

  • יכול להיות שאפילו ראיתם עקבות מערכת שבהן אנחנו עושים את זה בפועל לעסקאות Binder בפלטפורמה.

  • אז כאן אפשר לראות שהבנצ'מרק של Compose מבצע טרנזקציית Binder אל system_server כדי להכין פריים.

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

  • עכשיו יש תמיכה מלאה בהפצת הקשר של פונקציות Coroutine ב-Kotlin ב-Tracing 2.0.

  • לא נדרשת פעולה כלשהי מצידך.

  • פשוט מוציאים אותו מהקופסה.

  • אז כאן יש לנו method השעיה אחת שנקראת fetchProfile.

  • הוא קורא ל-client.get, שהוא method השהיה נוסף, ואז הוא עובר ל-Dispatchers.Default כדי לבטל את הסריאליזציה של התגובה.

  • קוד שגרתי מאוד של פונקציות Coroutine, משהו שכנראה נתקלתם בו כל יום.

  • עכשיו, נניח שאנחנו רוצים לעקוב אחרי זה.

  • אנחנו מוסיפים קטע מעקב.

  • אז כאן, במקום trace, נשתמש ב-API של traceCoroutine כי זה בלוק קוד מושהה, ואז, בקריאה הפנימית לפני שנעביר את ההקשר ל-Dispatchers.Default, נוסיף עוד קטע מעקב פנימי באמצעות אותו API של traceCoroutine.

  • עכשיו, במעקב, תראו משהו כזה.

  • אפשר לראות שהעבודה שלנו התחילה ב-worker-1, ואז היא למעשה החליפה הקשר ועברה ל-worker-3. אפשר לראות חצים מועילים במעקב שמצביעים על כך.

  • לא רק זה, אם מגדילים את הריבוע באמצע, אפשר לראות ש-deserialize למעשה התחיל ב-worker-1 והקריאה ל-withContext היא מה שגרם להחלפת ההקשר לקרות ואז, זה המשיך ל-worker-3 ואז, fetchProfile המשיך ב-worker-3 כי מנגנון ה-coroutines רוצה למזער את מספר החלפות ההקשר שקורות.

  • אפשר לראות בדיוק מתי שגרת המשך (coroutine) הושעתה, מתי היא חזרה לפעול ואת שרשרת האירועים הסיבתית המלאה.

  • בנוסף, ב-Tracing 2 אפשר להוסיף מטא-נתונים בהתאמה אישית למעקב.

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

  • ואפשר להוסיף את זה למעקב.

  • כשמסתכלים על המעקב בפועל, אפשר לראות שכל המטא-נתונים שהוספתם נמצאים במעקב, וכל הכלים יכולים לצרוך אותם ולהשתמש בהם.

  • עכשיו אתם יכולים להטמיע את כל מה שאתם יודעים, את כל המידע הנוסף שתמיד רציתם להוסיף.

  • אז נתתי לכם כמה תכונות מופשטות שמעקב מאפשר, אבל כל זה בא לידי ביטוי בתכונה חדשה ב-Jetpack Compose שנקראת Recomposition Tracing.

  • התכונה הזו נשלחת כחלק מ-Compose 1.13, אז היא מוכנה לשימוש כבר היום.

  • הנה דוגמה.

  • זהו מעקב אחר קומפוזיציה שכולכם כנראה מכירים.

  • הוא קיים מאז הימים הראשונים של Compose.

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

  • זה די פשוט.

  • מה שחסר בנתונים האלה הוא ההקשר.

  • הוא לא מסביר למה זה קרה.

  • הוא לא מסביר למה דברים עברו עיבוד מחדש.

  • אז אם אני אקטין את התצוגה, זה האזור שמעניין אותנו שראינו בשקף הקודם.

  • בצד שמאל אפשר לראות שהתרחש אירוע קלט של שליחה.

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

  • אבל זה לא ברור.

  • אתם לא יודעים בוודאות.

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

  • ולא רק זה, עכשיו קל מאוד לראות שהאירועים האלה קשורים.

  • אם תלחצו עליהם, תראו בדיוק איזה מצב השתנה.

  • הוא יגיד לכם שזה היה מצב, זה היה ערך מצב שניתן לשינוי שהוחלף ל-false, שהיה קודם לכן true.

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

  • אז כאן אפשר לראות ש-AbstractClickableNode.performClick קרה, וזה גרם לשינוי במצב שניתן לשינוי, וזה גרם להרכבה מחדש.

  • זה ממש ממש מגניב, משהו שלא יכולנו לעשות בעבר.

  • הכלים האלה זמינים גם, סליחה, כל הפונקציונליות הזו זמינה גם בכלי החדש של Android Performance Analyzer, אם אתם רוצים לנסות חלק מהכלים של הסוכן.

  • וגם נכון.

  • אז אמרתי לכם שהפצת ההקשר נתמכת באופן מלא בשגרות ההמשך של Kotlin.

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

  • אוקיי.

  • אז החדשות הטובות הן שעדיין אפשר לקבל את כל היתרונות של הפצת ההקשר.

  • נניח שיש לנו API קיים שמשתמש בקריאה חוזרת (callback) וב-executor.

  • ההטמעה משתמשת ב-executor.execute, קוראת ל-runCatching, שהוא פרט ההטמעה, מקבלת את התגובה ואז מעבירה את התגובה לקריאה החוזרת (callback).

  • אוקיי.

  • עכשיו, אם אני רוצה לעקוב אחרי זה, אני עושה Tracer.global.trace, נותן לזה תווית ואז מקיף את הבלוק.

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

  • ואז אני פשוט אעביר את הטוקן ל-API הפנימי של המעקב.

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

  • אוקיי.

  • עכשיו, הגורם ששולח קריאה ל-API הזה יכול להיות API מושהה.

  • אז כאן יש לי פונקציית השהיה profileRequest, שעכשיו הולכת לקרוא ל-API שמשתמש בקריאה חוזרת (callback) ובמבצע (executor).

  • כאן, בגלל שאני מגשר בין שני עולמות, בין עולם ההשהיה לעולם ה-callback, אני אשתמש ב-builder suspendCoroutine.

  • אני אקבל המשך, ואז פשוט אפעיל את ה-API, ואז אמשיך את ההמשך בקריאה החוזרת, ברגע שאקבל את התגובה.

  • וכאן מופיע ה-executor שאני מגדיר, שנקרא profileRequestContext.

  • זהו מנהל משימות עם שרשור יחיד.

  • ובגלל ש-traceCoroutine מטמיע אסימון הפצה בתוך ההקשר של ה-coroutine, אני יכול לקבל את אסימון ההפצה מתוך ההקשר של ה-coroutine, ואז להעביר אותו כקלט ל-API של ה-brownfield, כדי שאוכל עדיין לשמר את שרשרת האירועים הסיבתית.

  • ובמעקב, זה הופך את זה למאוד ברור.

  • אז כאן יש לכם משהו שהתחיל ב-worker-3, עבר ל-single-threaded executor אחרי שעשינו את החלפת ההקשר, ואז חזר ל-worker.

  • זה די מגניב.

מעקב גלובלי והפעלה בהתאמה אישית

  • Rahul Ravikumar

    אוקיי.

  • אפשר גם להתאים אישית את Tracer.global.

  • עד עכשיו, Tracing 2.0 API נתן לכם ברירות מחדל ממש טובות למה ש-Tracer.global צריך להיות, אבל אם אי פעם תרצו להתאים אותו אישית, תוכלו לעשות את זה.

  • בנוסף, Tracing 2.0 תומך ב-JVM וב-Android כי אנחנו רוצים שתהיה תמיכה מלאה במעקב גם בספריות של Java וגם בספריות של Android.

  • אז שלושת השלבים לאתחול הם: יצירת כיור מעקב.

  • כיור מעקב הוא למעשה האופן שבו חבילות מעקב מאוחסנות.

  • הוא מגדיר את הפורמט של ה-wire ואת המיקום שבו נשמרים המעקבים של הפלט.

  • אנחנו מספקים הטמעה מוכנה לשימוש של trace sink שתומך בפורמט Perfetto, אבל אתם יכולים כמובן לכתוב trace sink משלכם.

  • זה מחזיר אותנו לניסיון לפתור את הבעיה ש-atrace היה הקצה העורפי היחיד שהיה זמין ב-Tracing 1.

  • רצינו לתקן את זה.

  • אז עכשיו, אם אתם רוצים לכתוב כיור OpenTelemetry או להביא כיור משלכם, זה אפשרי לחלוטין.

  • אפשר לשנות את ברירת המחדל הזו.

  • אחרי שיש לכם sink, אתם יכולים ליצור מנהל מעקב ולתת לו את ה-sink.

  • ואז אפשר גם לבחור אילו קטגוריות רוצים להפעיל.

  • כאן הקטגוריות באמת נכנסות לתמונה.

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

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

  • וזה בסדר גמור.

  • אפשר גם להוסיף מאפיינים מותאמים אישית שיהיו גלובליים למעקב.

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

  • לבסוף, אפשר לרשום מעקב באמצעות קריאה ל-setGlobalTracer.

  • זה מה שמקשר את Tracer.global למופע הזה של המעקב, וזה מה שזמין לתהליך.

  • ב-Android, לתהליכים יש מחזור חיים מוגדר הרבה יותר, אז כל מה שצריך לעשות הוא להטמיע את AbstractTraceDriver.Factory, לשנות את ה-method‏ create שלו, ולעשות בדיוק מה שעשיתם קודם.

  • אז אתם יוצרים את ה-sink ואז את הדרייבר.

  • אין צורך להירשם כאן.

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

  • אוקיי.

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

  • זו בחירה מכוונת מאוד, כי אנחנו יודעים ש-Compose פועל בכמה פלטפורמות, ואנחנו רוצים לאפשר לכם לבצע מעקב בכמה שיותר מקומות.

  • היום אנחנו תומכים ב-Android וב-JVM, אבל ההטמעה שלנו מאפשרת לנו לתמוך בפלטפורמות עתידיות אם נרצה בכך.

אופטימיזציות של כלי פיתוח (R8 ו-metalava)

  • Rahul Ravikumar

    כדאי לדעת ש-R8, הכלי שמשמש לכיווץ קוד המקור ולביצוע אופטימיזציה שלו ב-Android, משתמש ב-Tracing 2.0.

  • בעזרת המעקבים, ביצענו אופטימיזציה של R8 כדי לצמצם את הקוד ולבצע אופטימיזציה שלו מהר יותר ב-25%.

  • זה אומר שזמני ה-build קצרים יותר, וגם זמני ההמתנה לסבבי האופטימיזציה קצרים יותר.

  • אני לא אעבור על כל אופטימיזציה שעשינו ב-R8, אבל אני רוצה לעבור על אחת, רק כדי לתת לכם טעימה ממה שאפשר לעשות עם מעקב.

  • אז R8 הוא בעצם ממיר מקור למקור.

  • אז אתם מקבלים חבילה של קובצי JAR, מכווצים אותם ומבצעים אופטימיזציה, ומפיקים קבוצה נוספת של קובצי JAR שאפשר להמיר ל-dex.

  • אז כאן, AGP מתחיל בקריאת קובצי JAR, שהם לא רק האפליקציה שלכם, אלא כל יחסי התלות שלה.

  • לכן, הוא משתמש ב-Gradle workers כדי להתחיל לקרוא את כל קובצי ה-JAR של הקלט.

  • יש לכם הרבה מאוד קובצי JAR שאתם רוצים לקרוא.

  • אחרי שהקריאה של קובצי ה-JAR מסתיימת, R8 מתחיל לנתח את הקוד ולבנות מודל של מה שהקוד מייצג ושל כל נקודות הכניסה שלו, כדי שיוכל להתחיל לבצע אופטימיזציה ולכווץ את הקוד.

  • עכשיו, זהו החלק, await, שמייצג את הזמן שבו R8 באמת מוכן להתחיל את תהליך האופטימיזציה, נכון?

  • יכול להיות שאתם רואים שטח מת באמצע, ואתם תוהים למה יש לנו זמן מעבד לא מנוצל.

  • והסיבה היא שב-API הישן, היינו צריכים לחכות שכל ה-workers יקראו את כל הקבצים שלהם לפני שהתחלנו לנתח משהו.

  • כמובן שאנחנו יכולים לתקן את זה, וזה בדיוק מה שעשינו.

  • אז ב-AGP 9, אפשר לראות שברגע שסיימנו לקרוא את קובצי ה-JAR של הקלט, התחלנו לעבד אותם מיד.

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

  • זה בעצם קיצר מאוד את משך הזמן שאנחנו מחכים כדי לקרוא את כל הקלט.

  • אז זו אופטימיזציה ברורה שאפשר לראות שהיא סופר ברורה כשמסתכלים על מעקב, אבל לא כל כך ברורה כשמסתכלים רק על קוד המקור כדי לראות מה כדאי לבצע אופטימיזציה.

  • כלי נוסף שמהנדסי AndroidX משתמשים בו כל יום הוא Metalava.

  • ‫Metalava הוא כלי שמאפשר לנו לוודא שכל ממשקי ה-API שאנחנו משיקים עומדים בדרישות התאימות של ממשקי ה-API, ושאנשים חיצוניים יכולים להשתמש בהם באופן מלא בלי לגרום לבעיות שאנחנו רוצים להימנע מהן.

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

  • ב-Metalava השתמשו במעקב גם כדי לשפר את זמני הפוסט-סבמיט ב-15%.

  • ואז נכנסים למעקב כדי להסביר מה קרה, אז Metalava מריץ שני סוגים של בדיקות: הראשונה היא בדיקה של ממשקי API מוגבלים, והשנייה היא בדיקה של ממשקי API ציבוריים.

  • אבל אולי אתם מבחינים בדפוס מסוים.

  • אנחנו מבצעים הרבה הגדרות כדי להריץ את הבדיקות האלה פעמיים.

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

  • ואז אנחנו בונים את ה-AST לפני שאנחנו מתחילים להחיל את כל בדיקות ה-lint שאנחנו רוצים.

  • ואנחנו מבצעים את העבודה היקרה הזו פעמיים.

  • איך תיקנו את זה?

  • עשינו את זה רק פעם אחת.

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

  • כן.‏

  • אוקיי.

מעקב אחר שיטות מומלצות והשוואה לשוק

  • Rahul Ravikumar

    אז עכשיו, אחרי שנתתי לכם טעימה מהאפשרויות ש-Tracing 2.0 מציע, חשוב לדעת שמעקב הוא חשוב, אבל חשוב לא פחות לדעת אחרי מה לעקוב.

  • אתם לא רוצים להשאיר עקבות בכל מקום.

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

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

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

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

  • כדאי גם לתעדף לפי התהליכים שעוברים המשתמשים.

  • נניח שאתם רוצים לבדוק את הפעלת האפליקציה.

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

  • לכן, בכל פעם שאתם לא מצליחים להבין משהו, כדאי לבצע מעקב.

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

  • כך תוכלו לעקוב אחריהם.

  • ואז, בכל פעם שהשרשורים שלכם במצב שינה, אתם רוצים לוודא שאתם יכולים להבין למה הם במצב שינה.

  • הם צריכים להמתין לקלט/פלט, או להמתין לטרנזקציית Binder, או להמתין לאירועי קלט חיצוניים שאתם מצפים שיקרו.

  • אבל אם יש, אתם יודעים, שינה לא מוסברת, אז אלה דברים ששווה מאוד לחקור כי ברור שיכול להיות שמשהו מסתתר שם.

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

  • כך, ככל ש-code base מתפתח, אתם יכולים לראות אם המדדים משתנים, אם מרכזי הבקרה שלכם משקפים שינויים גדולים, כי אז אתם יודעים שמשהו חשוב השתנה.

  • אפשר גם לדלג על כל השלבים הקודמים ופשוט לבקש מהסוכן לעשות את זה.

  • אתם יכולים לקחת את ההנחיות האלה, להפוך אותן למיומנות, ואז להפנות אליה את הסוכן המועדף עליכם ולתת לו לנסות.

  • כן.‏

הגדרה, סיכום ומסקנות

  • Rahul Ravikumar

    אז אם שכנעתי אתכם שמעקב הוא מאמץ ראוי ושכדאי לכם לעשות את זה, הנה הגדרת הפרויקט.

  • זה די פשוט.

  • מעקב הוא פשוט, אתם יודעים, ספריית Java רגילה, אז אתם מגדירים תלות ב-androidx.tracing:tracing-wire, שהוא מנהל ההתקן של המעקב שתואם ל-Perfetto, וגם שילוב של מעקב, והגרסה היציבה הנוכחית היא 2.0.5.

  • אם אתם משתמשים בספריית Java, זה בדיוק אותו דבר. שוב, זהו API שמתאים לכל הפלטפורמות.

  • אנחנו רוצים להרחיב את התכונה לפלטפורמות אחרות כשזה יהיה רלוונטי.

  • ורציתי רק לסכם בקצרה.

  • אז דיברתי על כל התכונות החדשות והמגניבות שהוספנו.

  • אז הפצת הקשר היא כלי ממש ממש חזק.

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

  • יש הרבה מאוד דברים שלא דיברתי עליהם.

  • אתם יכולים כמובן לשנות את פורמט המעקב, את כיור המעקב, ואתם יכולים, אתם יודעים, להעביר את נתוני המעקב שלכם ל-OpenTelemetry.

  • אלה כל הדברים שאתם יכולים לעשות.

  • המעקב הוא בכמה פלטפורמות.

  • גם זו החלטה מאוד מאוד מודעת, כי אנחנו רוצים שכמה שיותר אנשים יוכלו לנצל את זה.

  • וכל הכלים שאנחנו מספקים, בין אם זה Perfetto, בין אם זה AndroidX Benchmarks, בין אם זה מנתח הביצועים החדש או Android Profiler הישן, כולם תומכים ב-Tracing 2.0.

  • תודה רבה על ההקשבה.

  • אני מקווה שיהיה לך כנס מפתחים נהדר, ותודה.