ניתוח ביצועי הייצור באמצעות Cloud Profiler

1. סקירה כללית

מפתחי אפליקציות לקוח ומפתחי קצה קדמי משתמשים בדרך כלל בכלים כמו Android Studio CPU Profiler או כלי הפרופילים שכלולים ב-Chrome כדי לשפר את הביצועים של הקוד שלהם. לעומת זאת, שיטות מקבילות לא היו נגישות או פופולריות בקרב מי שעובד על שירותי קצה עורפי. Cloud Profiler מספק את אותן יכולות למפתחי שירותים, בלי קשר לשאלה אם הקוד שלהם פועל ב-Google Cloud Platform או במקום אחר.

95c034c70c9cac22.png

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

ב-Codelab הזה תלמדו איך להגדיר את Cloud Profiler לתוכנית Go, ותכירו את סוגי התובנות לגבי ביצועי האפליקציה שהכלי יכול להציג.

מה תלמדו

  • איך מגדירים תוכנית Go ליצירת פרופילים באמצעות Cloud Profiler.
  • איך אוספים, מציגים ומנתחים את נתוני הביצועים באמצעות Cloud Profiler.

מה תצטרכו

  • פרויקט ב-Google Cloud Platform
  • דפדפן, כמו Chrome או Firefox
  • היכרות עם כלים לעריכת טקסט של Linux, כמו Vim,‏ EMACs או Nano

איך תשתמשו במדריך הזה?

רק לקרוא לקרוא ולבצע את התרגילים

איך היית מדרג את חוויית השימוש שלך ב-Google Cloud Platform?

מתחילים ביניים מומחים

2. הגדרה ודרישות

הגדרת סביבה בקצב אישי

  1. נכנסים אל Cloud Console ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.

96a9c957bc475304.pngb9a10ebdf5b5a448.pnga1e3c01a38fa61c2.png

חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא יתאים לכם, מצטערים!). בהמשך ה-codelab הזה נתייחס אליו כאל PROJECT_ID.

  1. לאחר מכן, תצטרכו להפעיל את החיוב ב-Cloud Console כדי להשתמש במשאבים של Google Cloud.

העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי לא תהיה עלות בכלל. חשוב לפעול לפי ההוראות בקטע 'ניקוי' שבו מוסבר איך להשבית משאבים כדי שלא תחויבו אחרי שתסיימו את המדריך הזה. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.

Google Cloud Shell

אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל כדי לפשט את ההגדרה ב-Codelab הזה נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן.

הפעלת Cloud Shell

  1. ב-Cloud Console, לוחצים על Activate Cloud Shell 4292cbf4971c9786.png.

bce75f34b2c53987.png

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים (מתחת לקו הקיפול) עם תיאור של הכלי. במקרה כזה, לוחצים על המשך (והמסך הזה לא יוצג לכם יותר). כך נראה המסך החד-פעמי:

70f315d7b402b476.png

יחלפו כמה רגעים עד שההקצאה והחיבור ל-Cloud Shell יושלמו.

fbe3a0674c982259.png

המכונה הווירטואלית הזו כוללת את כל הכלים שדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות של הרשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן או Chromebook.

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

  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שאתם מאומתים:
gcloud auth list

פלט הפקודה

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שפקודת gcloud מכירה את הפרויקט:
gcloud config list project

פלט הפקודה

[core]
project = <PROJECT_ID>

אם לא, אפשר להגדיר אותו באמצעות הפקודה הזו:

gcloud config set project <PROJECT_ID>

פלט הפקודה

Updated property [core/project].

3. מעבר אל Cloud Profiler

ב-Cloud Console, עוברים לממשק המשתמש של Profiler על ידי לחיצה על Profiler בסרגל הניווט הימני:

37ad0df7ddb2ad17.png

אפשר גם להשתמש בסרגל החיפוש ב-Cloud Console כדי לנווט לממשק המשתמש של Profiler: פשוט מקלידים Cloud Profiler ובוחרים את הפריט שנמצא. בכל מקרה, אמור להופיע ממשק המשתמש של כלי הפרופיל עם ההודעה 'אין נתונים להצגה' כמו בדוגמה שלמטה. הפרויקט חדש, ולכן עדיין לא נאספו בו נתוני פרופיל.

d275a5f61ed31fb2.png

הגיע הזמן ליצור פרופיל של משהו!

4. תיעוד של Benchmark

נשתמש באפליקציית Go סינתטית פשוטה שזמינה ב-Github. בטרמינל של Cloud Shell שעדיין פתוח (ובזמן שההודעה 'אין נתונים להצגה' עדיין מוצגת בממשק המשתמש של כלי הפרופיל), מריצים את הפקודה הבאה:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

לאחר מכן עוברים לספריית האפליקציה:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

הספרייה מכילה את הקובץ main.go, שהוא אפליקציה סינתטית שסוכן הפרופילים מופעל בה:

main.go

...
import (
        ...
        "cloud.google.com/go/profiler"
)
...
func main() {
        err := profiler.Start(profiler.Config{
                Service:        "hotapp-service",
                DebugLogging:   true,
                MutexProfiling: true,
        })
        if err != nil {
                log.Fatalf("failed to start the profiler: %v", err)
        }
        ...
}

סוכן הפרופילים אוסף כברירת מחדל פרופילים של מעבד (CPU), ערימה ושרשור. הקוד כאן מאפשר איסוף של פרופילים של mutex (נקרא גם "התנגשות").

עכשיו מריצים את התוכנה:

$ go run main.go

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

$ go run main.go
2018/03/28 15:10:24 profiler has started
2018/03/28 15:10:57 successfully created profile THREADS
2018/03/28 15:10:57 start uploading profile
2018/03/28 15:11:19 successfully created profile CONTENTION
2018/03/28 15:11:30 start uploading profile
2018/03/28 15:11:40 successfully created profile CPU
2018/03/28 15:11:51 start uploading profile
2018/03/28 15:11:53 successfully created profile CONTENTION
2018/03/28 15:12:03 start uploading profile
2018/03/28 15:12:04 successfully created profile HEAP
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:04 successfully created profile THREADS
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:25 successfully created profile HEAP
2018/03/28 15:12:25 start uploading profile
2018/03/28 15:12:37 successfully created profile CPU
...

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

650051097b651b91.png

אחרי רענון ממשק המשתמש, יופיע משהו כזה:

47a763d4dc78b6e8.png

בורר סוגי הפרופילים מציג את חמשת סוגי הפרופילים שזמינים:

b5d7b4b5051687c9.png

עכשיו נסקור כל אחד מסוגי הפרופילים וכמה יכולות חשובות בממשק המשתמש, ואז נבצע כמה ניסויים. בשלב הזה, כבר לא צריך את מסוף Cloud Shell, לכן אפשר לצאת ממנו על ידי הקשה על CTRL-C והקלדה של exit.

5. ניתוח הנתונים של כלי הפרופיל

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

קוד שדורש הרבה משאבי CPU

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

fae661c9fe6c58df.png

הפונקציה הזו נכתבה במיוחד כדי לצרוך הרבה מחזורי CPU על ידי הפעלת לולאה צפופה:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

הפונקציה נקראת באופן עקיף מ-busyloop() דרך ארבעה נתיבי קריאה: busyloop → {foo1, foo2} → {bar, baz} → load. הרוחב של תיבת פונקציה מייצג את העלות היחסית של נתיב הקריאה הספציפי. במקרה הזה, העלות של כל ארבעת הנתיבים זהה בערך. בתוכנית אמיתית, כדאי להתמקד באופטימיזציה של נתיבי שיחות שהכי חשובים מבחינת ביצועים. תרשים הלהבות, שמדגיש חזותית את הנתיבים היקרים יותר באמצעות תיבות גדולות יותר, מאפשר לזהות בקלות את הנתיבים האלה.

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

eb1d97491782b03f.png

קוד שדורש הרבה זיכרון

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

f6311c8c841d04c4.png

טבלת הסיכום שמעל תרשים הלהבות מציינת שכמות הזיכרון הכוללת שנעשה בה שימוש באפליקציה היא בממוצע ‎~57.4 MiB, ורוב הזיכרון מוקצה על ידי הפונקציה allocImpl. זה לא מפתיע, בהתחשב בהטמעה של הפונקציה הזו:

main.go

func allocImpl() {
        // Allocate 64 MiB in 64 KiB chunks
        for i := 0; i < 64*16; i++ {
                mem = append(mem, make([]byte, 64*1024))
        }
}

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

קוד עם פעולות קלט/פלט רבות

אם בוחרים באפשרות Threads (שרשורים) בתפריט לבחירת סוג הפרופיל, התצוגה תשתנה לתרשים להבה שבו רוב הרוחב מוקצה לפונקציות wait ו-waitImpl:

ebd57fdff01dede9.png

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

main.go

func main() {
        ...
        // Simulate some waiting goroutines.
        for i := 0; i < 100; i++ {
                go wait()
        }

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

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

קוד עם שימוש אינטנסיבי במשאבים

סוג פרופיל ההתנגשות מזהה את הנעילות הכי 'מבוקשות' בתוכנית. סוג הפרופיל הזה זמין לתוכניות Go, אבל צריך להפעיל אותו באופן מפורש על ידי ציון הערך MutexProfiling: true בקוד ההגדרה של הסוכן. האיסוף מתבצע על ידי הקלטה (במדד Contentions) של מספר הפעמים שבהן נעילה ספציפית, בזמן שהיא נפתחת על ידי goroutine A, גרמה ל-goroutine B אחר להמתין עד שהנעילה תיפתח. הוא גם מתעד (במדד 'השהיה') את הזמן שבו ה-goroutine החסום המתין לנעילה. בדוגמה הזו, יש ערימה אחת של התנגשות והזמן הכולל להמתנה לנעילה היה 10.5 שניות:

83f00dca4a0f768e.png

הקוד שיוצר את הפרופיל הזה מורכב מ-4 goroutines שנלחמים על mutex:

main.go

func contention(d time.Duration) {
        contentionImpl(d)
}

func contentionImpl(d time.Duration) {
        for {
                mu.Lock()
                time.Sleep(d)
                mu.Unlock()
        }
}
...
func main() {
        ...
        for i := 0; i < 4; i++ {
                go contention(time.Duration(i) * 50 * time.Millisecond)
        }
}

6. סיכום

בשיעור ה-Lab הזה למדתם איך להגדיר תוכנית Go לשימוש ב-Cloud Profiler. בנוסף, למדתם איך לאסוף, להציג ולנתח את נתוני הביצועים באמצעות הכלי הזה. עכשיו תוכלו ליישם את המיומנות החדשה בשירותים האמיתיים שאתם מפעילים ב-Google Cloud Platform.

7. מעולה!

למדתם איך להגדיר את Cloud Profiler ולהשתמש בו.

מידע נוסף

רישיון

העבודה הזו בשימוש במסגרת רישיון Creative Commons שמותנה בייחוס כללי מגרסה 2.0.