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

הכלי אוסף נתונים על השימוש במעבד (CPU) ועל הקצאת הזיכרון מהאפליקציות שלכם בסביבת הייצור. הוא משייך את המידע הזה לקוד המקור של האפליקציה, ועוזר לכם לזהות את החלקים באפליקציה שצורכים הכי הרבה משאבים, וגם מספק מידע על מאפייני הביצועים של הקוד. התקורה הנמוכה של טכניקות האיסוף שבהן נעשה שימוש בכלי הופכת אותו למתאים לשימוש רציף בסביבות ייצור.
ב-codelab הזה תלמדו איך להגדיר את Cloud Profiler לתוכנית Go, ותכירו את סוגי התובנות לגבי ביצועי האפליקציה שהכלי יכול להציג.
מה תלמדו
- איך מגדירים תוכנית Go ליצירת פרופילים באמצעות Cloud Profiler.
- איך אוספים, מציגים ומנתחים את נתוני הביצועים באמצעות Cloud Profiler.
מה תצטרכו
- פרויקט ב-Google Cloud Platform
- דפדפן, כמו Chrome או Firefox
- היכרות עם כלים לעריכת טקסט של Linux, כמו Vim, EMACs או Nano
איך תשתמש במדריך הזה?
איזה דירוג מגיע לדעתך לחוויית השימוש שלך ב-Google Cloud Platform?
2. הגדרה ודרישות
הגדרת סביבה בקצב אישי
- נכנסים אל Cloud Console ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.



חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא יתאים לכם, מצטערים!). בהמשך ה-codelab הזה נתייחס אליו כאל PROJECT_ID.
- לאחר מכן, תצטרכו להפעיל את החיוב ב-Cloud Console כדי להשתמש במשאבים של Google Cloud.
העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי אפילו לא תצטרכו לשלם בכלל. חשוב לפעול לפי ההוראות בקטע 'ניקוי' כדי להשבית את המשאבים, וכך לא תחויבו אחרי שתסיימו את המדריך הזה. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.
Google Cloud Shell
אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל כדי לפשט את ההגדרה ב-Codelab הזה נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן.
הפעלת Cloud Shell
- ב-Cloud Console, לוחצים על Activate Cloud Shell
.

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

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

המכונה הווירטואלית הזו כוללת את כל הכלים שדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן או Chromebook.
אחרי שמתחברים ל-Cloud Shell, אמור להופיע אימות שכבר בוצע ושהפרויקט כבר הוגדר לפי מזהה הפרויקט.
- מריצים את הפקודה הבאה ב-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`
- מריצים את הפקודה הבאה ב-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 בסרגל הניווט הימני:

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

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

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

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

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

הפונקציה הזו נכתבה במיוחד כדי לצרוך הרבה מחזורי CPU על ידי הפעלת לולאה צפופה:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
הפונקציה נקראת באופן עקיף מ-busyloop() דרך ארבעה נתיבי קריאה: busyloop → {foo1, foo2} → {bar, baz} → load. הרוחב של תיבת פונקציה מייצג את העלות היחסית של נתיב הקריאה הספציפי. במקרה הזה, העלות של כל ארבעת הנתיבים דומה. בתוכנית אמיתית, כדאי להתמקד באופטימיזציה של נתיבי שיחות שהכי חשובים מבחינת ביצועים. תרשים הלהבות מדגיש באופן חזותי את הנתיבים היקרים יותר באמצעות תיבות גדולות יותר, וכך קל לזהות את הנתיבים האלה.
אפשר להשתמש במסנן נתוני הפרופיל כדי לחדד את התצוגה. לדוגמה, אפשר לנסות להוסיף מסנן 'הצגת ערימות' ולציין 'baz' כמחרוזת המסנן. אמור להופיע מסך כמו בצילום המסך שלמטה, שבו מוצגים רק שניים מתוך ארבעת נתיבי השיחות אל load(). שני הנתיבים האלה הם היחידים שעוברים דרך פונקציה עם המחרוזת 'baz' בשם שלה. סינון כזה שימושי כשרוצים להתמקד בחלק משני של תוכנית גדולה יותר (למשל, כי אתם הבעלים של חלק מהתוכנית).

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

טבלת הסיכום שמעל תרשים הלהבות מציינת שכמות הזיכרון הכוללת שנעשה בה שימוש באפליקציה היא בממוצע ~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:

בסיכום שמעל תרשים הלהבות, אפשר לראות שיש 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 שניות:

הקוד שיוצר את הפרופיל הזה מורכב מ-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 ואיך להשתמש בו.
מידע נוסף
- Cloud Profiler: https://cloud.google.com/profiler/
- חבילת זמן הריצה של Go/pprof שבה נעשה שימוש ב-Cloud Profiler: https://golang.org/pkg/runtime/pprof/
רישיון
עבודה זו מורשית תחת רישיון Creative Commons שמותנה בייחוס 2.0 כללי.