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

הכלי אוסף מידע על השימוש במעבד (CPU) ועל הקצאת הזיכרון מהאפליקציות שלכם בסביבת הייצור. הוא משייך את המידע הזה לקוד המקור של האפליקציה, ועוזר לכם לזהות את החלקים באפליקציה שצורכים הכי הרבה משאבים, וגם מספק מידע על מאפייני הביצועים של הקוד. התקורה הנמוכה של טכניקות האיסוף שבהן נעשה שימוש בכלי הופכת אותו למתאים לשימוש רציף בסביבות ייצור.
ב-codelab הזה תלמדו איך להגדיר את Stackdriver Profiler לתוכנית Go, ותכירו את סוגי התובנות לגבי ביצועי האפליקציה שהכלי יכול להציג.
מה תלמדו
- איך מגדירים תוכנית Go ליצירת פרופיל באמצעות Stackdriver Profiler.
- איך אוספים, מציגים ומנתחים את נתוני הביצועים באמצעות Stackdriver Profiler.
מה צריך להכין
- פרויקט ב-Google Cloud Platform
- דפדפן, כמו Chrome או Firefox
- היכרות עם כלים לעריכת טקסט של Linux, כמו Vim, EMACs או Nano
איך תשתמשו במדריך הזה?
איזה דירוג מגיע לדעתך לחוויה שלך עם Google Cloud Platform?
הגדרת סביבה בקצב אישי
אם עדיין אין לכם חשבון Google (Gmail או Google Apps), אתם צריכים ליצור חשבון. נכנסים ל-Google Cloud Platform Console (console.cloud.google.com) ויוצרים פרויקט חדש:
חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שמופיע למעלה כבר תפוס ולא יתאים לכם, מצטערים!). בהמשך ה-codelab הזה, נתייחס אליו כאל PROJECT_ID.
בשלב הבא, תצטרכו להפעיל את החיוב ב-Cloud Console כדי להשתמש במשאבים של Google Cloud.
העלות של התרגיל הזה לא אמורה להיות גבוהה, אבל היא יכולה להיות גבוהה יותר אם תחליטו להשתמש ביותר משאבים או אם תשאירו אותם פועלים (ראו את הקטע 'ניקוי' בסוף המסמך הזה).
משתמשים חדשים ב-Google Cloud Platform זכאים לתקופת ניסיון בחינם בשווי 300$.
Google Cloud Shell
אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל כדי לפשט את ההגדרה בשיעור הזה, נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן.
הפעלת Google Cloud Shell
ב-GCP Console, לוחצים על סמל Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:
לאחר מכן לוחצים על 'הפעלת Cloud Shell':
יחלפו כמה רגעים עד שההקצאה והחיבור לסביבת העבודה יושלמו:
המכונה הווירטואלית הזו כוללת את כל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. את רוב העבודה במעבדה הזו, אם לא את כולה, אפשר לבצע באמצעות דפדפן או Google Chromebook.
אחרי שמתחברים ל-Cloud Shell, אמור להופיע אימות שכבר בוצע ושהפרויקט כבר הוגדר לפי PROJECT_ID.
מריצים את הפקודה הבאה ב-Cloud Shell כדי לוודא שהאימות בוצע:
gcloud auth list
פלט הפקודה
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
פלט הפקודה
[core] project = <PROJECT_ID>
אם הוא לא מוגדר, אפשר להגדיר אותו באמצעות הפקודה הבאה:
gcloud config set project <PROJECT_ID>
פלט הפקודה
Updated property [core/project].
ב-Cloud Console, עוברים לממשק המשתמש של Profiler על ידי לחיצה על Profiler בסרגל הניווט הימני:

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

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

בסיכום שמעל לתרשים הלהבות אפשר לראות שיש 100 שגרות Goroutine שמגדילות את מחסנית הקריאות שלהן מהפונקציה wait. זה בדיוק נכון, בהתחשב בכך שהקוד שמתחיל את ההמתנה הזו נראה כך:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}סוג הפרופיל הזה שימושי כדי להבין אם התוכנית מבזבזת זמן לא צפוי בהמתנות (כמו קלט/פלט). בדרך כלל, פרופיל המעבד לא יכלול דגימה של מחסניות קריאות כאלה, כי הן לא צורכות חלק משמעותי מזמן המעבד. לעתים קרובות כדאי להשתמש במסננים מסוג 'הסתרת ערימות' עם פרופילים של Threads – לדוגמה, כדי להסתיר את כל הערימות שמסתיימות בקריאה ל-gopark,, כי אלה לרוב שגרות משנה במצב המתנה ולא מעניינות כמו אלה שממתינות לקלט/פלט.
סוג הפרופיל threads יכול גם לעזור לזהות נקודות בתוכנית שבהן ה-threads מחכים ל-mutex שבבעלות חלק אחר בתוכנית במשך תקופה ארוכה, אבל סוג הפרופיל הבא שימושי יותר למטרה הזו.
קוד עם הרבה מחלוקות
סוג הפרופיל Contention מזהה את הנעילות הכי 'מבוקשות' בתוכנית. סוג הפרופיל הזה זמין לתוכניות Go, אבל צריך להפעיל אותו באופן מפורש על ידי ציון הערך MutexProfiling: true בקוד ההגדרה של הסוכן. האיסוף מתבצע על ידי הקלטה (במדד Contentions) של מספר הפעמים שבהן נעילה ספציפית, בזמן שהיא נפתחת על ידי goroutine A, גרמה ל-goroutine B אחר להמתין עד שהנעילה תיפתח. הוא גם מתעד (במדד 'השהיה') את הזמן שבו ה-goroutine החסום המתין לנעילה. בדוגמה הזו, יש ערימה אחת של התנגשויות וזמן ההמתנה הכולל לנעילה היה 11.03 שניות:

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