1. סקירה כללית
מפתחי אתרים ואפליקציות לקוח בדרך כלל משתמשים בכלים כמו הכלי לבדיקת ביצועי המעבד (CPU) של Android Studio או בכלי הפרופיילינג שכלולים ב-Chrome כדי לשפר את ביצועי הקוד שלהם, אבל שיטות עבודה מקבילות עדיין לא נגישות או אימוץ כל כך אצל מי שעובדים בשירותים לקצה העורפי. הכלי Cloud Profiler מספק את אותן היכולות למפתחי שירותים, ולא משנה אם הקוד שלהם פועל ב-Google Cloud Platform או במקום אחר.
הכלי אוסף מידע על השימוש במעבד (CPU) ועל הקצאת הזיכרון מהאפליקציות בסביבת הייצור. הוא משייך את המידע הזה לקוד המקור של האפליקציה, עוזר לכם לזהות את החלקים באפליקציה שצורכים הכי הרבה משאבים, ומואר בדרכים אחרות את מאפייני הביצועים של הקוד. תקורה נמוכה של שיטות האיסוף שבהן הכלי משתמש, מתאימה לשימוש מתמשך בסביבות ייצור.
ב-Codelab הזה, תלמדו איך להגדיר את Cloud Profiler לתוכנית Go, ותכירו את סוגי התובנות על ביצועי האפליקציות שהכלי יכול להציג.
מה תלמדו
- איך להגדיר תוכנית של Go ליצירת פרופילים ב-Cloud Profiler.
- איך לאסוף, להציג ולנתח את נתוני הביצועים באמצעות Cloud Profiler.
מה צריך להכין
- פרויקט ב-Google Cloud Platform
- דפדפן, למשל Chrome או Firefox
- היכרות עם עורכי טקסט סטנדרטיים של Linux, כגון Vim, EMAC או Nano
איך תשתמשו במדריך הזה?
איזה דירוג מגיע לחוויה שלך עם Google Cloud Platform?
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמאי
- נכנסים למסוף Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.
חשוב לזכור את מזהה הפרויקט, שם ייחודי לכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא מתאים לכם, סליחה). בהמשך ב-Codelab הזה, היא תיקרא PROJECT_ID
.
- בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים של Google Cloud.
מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. חשוב לבצע את כל ההוראות בקטע 'ניקוי' שמסביר איך להשבית משאבים כדי שלא תצברו חיובים מעבר למדריך הזה. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.
Google Cloud Shell
אומנם אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל כדי לפשט את תהליך ההגדרה ב-Codelab הזה, נשתמש ב-Google Cloud Shell, סביבת שורת הפקודה שפועלת ב-Cloud.
הפעלת Cloud Shell
- במסוף Cloud, לוחצים על 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, עוברים לממשק המשתמש של Profiler בלחיצה על Profiler בסרגל הניווט הימני:
לחלופין, אפשר להשתמש בסרגל החיפוש של Cloud Console כדי לעבור לממשק המשתמש של Profiler: פשוט מקלידים "Cloud Profiler" ובוחרים את הפריט שנמצא. בכל מקרה, ממשק המשתמש של Profiler אמור להופיע עם ההודעה "אין נתונים להצגה". כמו ההודעה הבאה. הפרויקט חדש, כך שעדיין לא נאספו ממנו נתונים של פרופיילינג.
זה הזמן ליצור פרופיל למשהו!
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 באופן ידני. לשם כך, לוחצים פעמיים על הלחצן 'עכשיו' בבורר מרווח הזמן:
אחרי שממשק המשתמש יתרענן, תראו משהו כזה:
בורר סוג הפרופיל מציג את חמשת הסוגים הזמינים של הפרופילים:
בואו נעבור עכשיו על כל אחד מסוגי הפרופילים ועל מספר יכולות חשובות של ממשק המשתמש, ולאחר מכן נערוך מספר ניסויים. בשלב הזה כבר לא צריך את הטרמינל של Cloud Shell, ולכן אפשר לצאת ממנו על ידי לחיצה על CTRL-C וההקלדה של "exit".
5. ניתוח נתוני Profiler
עכשיו, לאחר שאספנו נתונים, נבחן אותם מקרוב. אנחנו משתמשים באפליקציה סינתטית (המקור זמין ב-GitHub) שמדמה התנהגויות שאופייניות לסוגים שונים של בעיות בביצועים בסביבת הייצור.
קוד שצורך הרבה מעבד (CPU)
בוחרים את סוג הפרופיל של ה-CPU. אחרי שממשק המשתמש יטען אותו, תראו בתרשים הלהבות את ארבעת בלוקי העלה של הפונקציה load
, שמביאים בחשבון את כל צריכת המעבדים (CPU):
הפונקציה הזו נכתבה באופן ספציפי כדי לצרוך מספר רב של מחזורי CPU על ידי הרצת לולאה מתוחכמת:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
הפונקציה נקראת באופן עקיף מ-busyloop
() דרך ארבעה נתיבי קריאה: busyloop
← {foo1
, foo2
} ← {bar
, baz
} ← load
. הרוחב של תיבת פונקציות מייצג את העלות היחסית של נתיב הקריאה הספציפי. במקרה כזה, לכל ארבעת הנתיבים יש אותה עלות. בתוכנית אמיתית, כדאי להתמקד באופטימיזציה של נתיבי השיחות החשובים ביותר מבחינת הביצועים. תרשים הלהבות, שמדגיש באופן חזותי את הנתיבים היקרים יותר באמצעות תיבות גדולות יותר, מאפשר לזהות את הנתיבים האלה בקלות.
אפשר להשתמש במסנן נתוני הפרופיל כדי לצמצם עוד יותר את התצוגה. לדוגמה, אפשר לנסות להוסיף את הביטוי 'הצגת מקבצים' מסנן שמציין baz כמחרוזת הסינון. אתם אמורים לראות משהו כמו צילום המסך הבא, שבו מוצגים רק שניים מתוך ארבעת נתיבי השיחות אל load()
. שני הנתיבים האלה הם היחידים שעוברים דרך פונקציה עם המחרוזת 'baz'. בשם. סינון כזה שימושי אם רוצים להתמקד בחלק משנה של תוכנית גדולה יותר (למשל, כי רק חלק ממנה נמצא בבעלותכם).
קוד שצורך הרבה זיכרון
עכשיו צריך לעבור למצב 'ערימה' סוג הפרופיל. חשוב להסיר מסננים שיצרתם בניסויים קודמים. עכשיו אמור להופיע תרשים להבות שבו allocImpl
, שנקרא על ידי alloc
, מוצג כצרכן הזיכרון הראשי באפליקציה:
טבלת הסיכום שמעל תרשים הלהבות מציינת שנפח הזיכרון הכולל בשימוש באפליקציה הוא בממוצע כ-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
:
בסיכום שמעל תרשים הלהבות, אפשר לראות שיש 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 שניות:
הקוד שיוצר את הפרופיל הזה מורכב מ-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 ולהשתמש בו!
מידע נוסף
- Cloud Profiler: https://cloud.google.com/profiler/
- עוברים לחבילת זמן ריצה/pprof שבה נעשה שימוש ב-Cloud Profiler: https://golang.org/pkg/runtime/pprof/
רישיון
היצירה הזו בשימוש ברישיון Creative Commons Attribution 2.0 גנרי.