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

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

מפתחי אפליקציות לקוח ומפתחי חזית (frontend) של אתרים משתמשים בדרך כלל בכלים כמו Android Studio CPU Profiler או כלי הפרופילים שכלולים ב-Chrome כדי לשפר את הביצועים של הקוד שלהם. לעומת זאת, טכניקות מקבילות לא היו נגישות או פופולריות בקרב מי שעובד על שירותי קצה עורפי (backend). 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.png

b9a10ebdf5b5a448.png

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

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

הפונקציה מופעלת פעם אחת, מקצה 64MiB במנות קטנות יותר, ואז מאחסנת מצביעים למנות האלה במשתנה גלובלי כדי להגן עליהן מפני איסוף אשפה. שימו לב שכמות הזיכרון שמוצגת כזיכרון בשימוש בכלי ליצירת תמונת מצב של ערימה (heap profiler) שונה במקצת מ-64MiB: הכלי ליצירת תמונת מצב של ערימה (heap profiler) של Go הוא כלי סטטיסטי, ולכן המדידות הן בעלות תקורה נמוכה אבל לא מדויקות ברמת הבייט. אל תופתעו אם תראו הבדל של כ-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,, כי אלה לרוב שגרות משנה של goroutine במצב בלי פעילות, ופחות מעניינות מאלה שממתינות לקלט/פלט.

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

קוד עם הרבה מחלוקות

סוג הפרופיל Contention מזהה את הנעילות הכי 'מבוקשות' בתוכנית. סוג הפרופיל הזה זמין לתוכניות Go, אבל צריך להפעיל אותו באופן מפורש על ידי ציון הערך MutexProfiling: true בקוד ההגדרה של הסוכן. האיסוף מתבצע על ידי הקלטה (במדד Contentions) של מספר הפעמים שבהן נעילה ספציפית, בזמן שהיא נפתחת על ידי שגרת משנה A, גרמה לשגרת משנה 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 כללי.