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

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

מפתחי אתרים ואפליקציות לקוח בדרך כלל משתמשים בכלים כמו הכלי לבדיקת ביצועי המעבד (CPU) של Android Studio או בכלי הפרופיילינג שכלולים ב-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, EMAC או Nano

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

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

איזה דירוג מגיע לחוויה שלך עם Google Cloud Platform?

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

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

הגדרת סביבה בקצב עצמאי

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

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

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

  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים של Google Cloud.

מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. חשוב לבצע את כל ההוראות בקטע 'ניקוי' שמסביר איך להשבית משאבים כדי שלא תצברו חיובים מעבר למדריך הזה. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

Google Cloud Shell

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

הפעלת Cloud Shell

  1. במסוף Cloud, לוחצים על 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, עוברים לממשק המשתמש של Profiler בלחיצה על Profiler בסרגל הניווט הימני:

37ad0df7ddb2ad17.png

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

d275a5f61ed31fb2.png

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

4. יצירת פרופיל להשוואה לשוק

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

$ 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), ערימה (heap) ושרשורים. הקוד כאן מאפשר איסוף של פרופילי 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
...

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

650051097b651b91.png

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

47a763d4dc78b6e8.png

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

b5d7b4b5051687c9.png

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

5. ניתוח נתוני Profiler

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

קוד שצורך הרבה מעבד (CPU)

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

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

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

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

f6311c8c841d04c4.png

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

קוד אינטנסיבי של IO

אם בוחרים באפשרות 'שרשורים' בבורר סוג הפרופיל, התצוגה תעבור לתרשים להבות, שבו רוב הרוחב תוצר על ידי הפונקציות wait ו-waitImpl:

ebd57fdff01dede9.png

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

main.go

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

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

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

קוד שמשפיע על התחרות

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

83f00dca4a0f768e.png

הקוד שיוצר את הפרופיל הזה מורכב מ-4 גופיות שנלחמות על 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 Attribution 2.0 גנרי.