ניתוח ביצועי הפקה באמצעות Stackdriver Profiler

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

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

במעבד הקוד הזה תלמדו איך להגדיר Stackdriver Profiler לתוכנית Go ותכירו תובנות לגבי ביצועי האפליקציות שהכלי יכול להציג.

מה תלמדו

  • איך להגדיר תוכנית Go ליצירת פרופיל באמצעות Stackdriver Profiler.
  • איך לאסוף, להציג ולנתח נתוני ביצועים באמצעות Stackdriver Profiler.

מה צריך?

  • פרויקט ב-Google Cloud Platform
  • דפדפן, למשל Chrome או Firefox
  • היכרות עם עורכי טקסט רגילים של Linux, כמו Vim , EMAC או Nano

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

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

איך היית מדרג את החוויה שלך ב-Google Cloud Platform?

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

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

אם עדיין אין לכם חשבון Google (Gmail או Google Apps), עליכם ליצור חשבון. נכנסים למסוף ב-Google Cloud Platform (console.cloud.google.com) ויוצרים פרויקט חדש:

צילום מסך מ-2016-02-10 12:45:26.png

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

לאחר מכן, עליך להפעיל חיוב ב-Cloud Console כדי להשתמש במשאבים של Google Cloud.

התהליך של קוד Lab זה לא אמור לעלות יותר מדולר אחד, אבל יכול להיות שתצטרכו לשלם על משאבים נוספים או להשאיר אותו פעיל (עיינו בקטע "cleanup" בסוף המסמך).

משתמשים חדשים ב-Google Cloud Platform זכאים לתקופת ניסיון בחינם בשווי 300 $.

Google Cloud Shell

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

הפעלת Google Cloud Shell

במסוף GCP, לוחצים על הסמל של Cloud Shell בסרגל הכלים שבפינה השמאלית העליונה:

לאחר מכן לוחצים על "Start Cloud Shell":

יחלפו רק כמה רגעים עד שההקצאה והחיבור לסביבת העבודה יושלמו:

המכונה הווירטואלית הזו נטענת באמצעות כל כלי הפיתוח הדרושים לך. יש בה ספריית בית בנפח עקבי של 5GB, והיא פועלת ב-Google Cloud, וכך משפרת באופן משמעותי את הביצועים ואת האימות של הרשת. את רוב העבודה שלכם בשיעור ה-Lab הזה (אם לא כולם) אפשר לבצע בדפדפן או ב-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 כדי לנווט לממשק הפרופיל. פשוט מקלידים "Stackdriver Profiler" ובוחרים את הפריט שנמצא. בשני המקרים אתם אמורים לראות את ממשק המשתמש של הכלי לניתוח ביצועים בעזרת ההודעה "אין נתונים להצגה" כמו בדוגמה הבאה. הפרויקט הוא חדש, ולכן עדיין לא נאספים נתונים ליצירת פרופילים.

הגיע הזמן ליצור פרופיל!

נשתמש ביישום סינתטי פשוט של Go 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) והשרשור של השרשורים (thread). הקוד כאן מאפשר איסוף פרופילים של mutex (שנקראים גם "contention" ).

עכשיו אפשר להריץ את התוכנית:

$ 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). אחרי שממשק המשתמש נטען, יופיעו בתרשים ההצתה את ארבעת העלים הירוקים לפונקציה load, שמביאים בחשבון את כל הצריכה של המעבד (CPU):

פונקציה זו נכתבה במיוחד כדי לצרוך מחזורי מעבד רבים על ידי הפעלת לולאה חזקה:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

הפונקציה נקראת עקיפה מ-busyloop() באמצעות ארבעה נתיבי שיחות: busyloop ← {foo1, foo2} ← {bar, baz} ← load. הרוחב של תיבת פונקציה מייצג את העלות היחסית של נתיב השיחה הספציפי. במקרה הזה, כל ארבעת הנתיבים נושאים בערך את אותה העלות. בתוכנית אמיתית, המטרה היא להתמקד באופטימיזציה של נתיבי השיחות החשובים ביותר מבחינת ביצועים. תרשים ההצתה, המדגיש את המסלולים היקרים יותר בעזרת תיבות גדולות יותר, מקל על הזיהוי של הנתיבים.

אפשר להשתמש במסנן נתוני הפרופיל כדי לצמצם עוד יותר את התצוגה. לדוגמה, נסו להוסיף "Show stacks" מסנן המציין "baz" כמחרוזת המסנן. אמור להופיע משהו כמו צילום המסך הבא, שבו מוצגים רק שניים מתוך ארבעת נתיבי השיחות אל load(). שני הנתיבים האלה הם היחידים שעוברים לפונקציה שיש לה מחרוזת "baz" בשם. סינון כזה שימושי כאשר רוצים להתמקד בחלק משנה של תוכנית גדולה יותר (לדוגמה, מפני שיש לכם רק חלק ממנו).

קוד עתיר זיכרון

עכשיו עוברים ל &ציטוט;Happ" סוג פרופיל. חשוב להסיר את כל המסננים שיצרתם בניסויים הקודמים. עכשיו אמור להופיע תרשים להבות, שבו 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))
        }
}

הפונקציה מתבצעת פעם אחת, מקצה 64 MiB במנות קטנות יותר, ואז שומרת את הסמן על הגושים במשתנה גלובלי כדי להגן עליהם מפני איסוף אשפה. שימו לב: נפח הזיכרון המוצג כפי שהוא בשימוש ב-profiler שונה מעט מ-64 MiB: Goapheer הוא כלי סטטיסטי, כך שהמדידות הן גבוהות יותר אך לא מדויקות ברמת הבייט. לא מפתיע לראות כ-10% הפרשים כאלה.

קוד עתיר משאבי IO

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

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

main.go

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

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

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

קוד עתיר תוכן

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

הקוד שיוצר את הפרופיל הזה מורכב מ-4 תווי מוגזמות שנלחמים בהשתקפות:

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 ולהשתמש בו!

למידע נוסף

רישיון

היצירה הזו ברישיון תחת רישיון Creative Commons Attribution 2.0.