วิเคราะห์ประสิทธิภาพการใช้งานจริงด้วย Stackdriver Profiler

แม้ว่านักพัฒนาแอปไคลเอ็นต์และนักพัฒนาเว็บส่วนหน้ามักจะใช้เครื่องมือต่างๆ เช่น Android Studio CPU Profiler หรือเครื่องมือการจัดทำโปรไฟล์ที่รวมอยู่ใน Chrome เพื่อปรับปรุงประสิทธิภาพของโค้ด แต่เทคนิคที่เทียบเท่ากันนั้นยังไม่สามารถเข้าถึงได้ง่ายหรือเป็นที่นิยมในหมู่ผู้ที่ทำงานกับบริการแบ็กเอนด์ Stackdriver Profiler มอบความสามารถเดียวกันนี้ให้แก่นักพัฒนาบริการ ไม่ว่าโค้ดจะทำงานบน Google Cloud Platform หรือที่อื่นก็ตาม

เครื่องมือจะรวบรวมข้อมูลการใช้งาน CPU และการจัดสรรหน่วยความจำจากแอปพลิเคชันที่ใช้งานจริง โดยจะระบุข้อมูลดังกล่าวกับซอร์สโค้ดของแอปพลิเคชัน ซึ่งจะช่วยให้คุณระบุส่วนของแอปพลิเคชันที่ใช้ทรัพยากรมากที่สุด และแสดงลักษณะด้านประสิทธิภาพของโค้ด เทคนิคการรวบรวมข้อมูลที่มีค่าใช้จ่ายต่ำของเครื่องมือนี้ทำให้เหมาะสำหรับการใช้งานอย่างต่อเนื่องในสภาพแวดล้อมการผลิต

ใน Codelab นี้ คุณจะได้เรียนรู้วิธีตั้งค่า 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 ทั้งหมด (ชื่อด้านบนถูกใช้ไปแล้วและจะใช้ไม่ได้ ขออภัย) ซึ่งจะเรียกว่า PROJECT_ID ในภายหลังใน Codelab นี้

จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร Google Cloud

การทำ Codelab นี้ไม่ควรมีค่าใช้จ่ายเกิน 2-3 ดอลลาร์ แต่ก็อาจมีค่าใช้จ่ายมากกว่านี้หากคุณตัดสินใจใช้ทรัพยากรเพิ่มเติมหรือปล่อยให้ทรัพยากรทำงานต่อไป (ดูส่วน "การล้างข้อมูล" ที่ท้ายเอกสารนี้)

ผู้ใช้ใหม่ของ Google Cloud Platform มีสิทธิ์ทดลองใช้ฟรี$300

Google Cloud Shell

แม้ว่าคุณจะใช้งาน Google Cloud จากระยะไกลในแล็ปท็อปได้ แต่เพื่อให้การตั้งค่าในโค้ดแล็บนี้ง่ายขึ้น เราจะใช้ Google Cloud Shell ซึ่งเป็นสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานในระบบคลาวด์

เปิดใช้งาน Google Cloud Shell

จาก GCP Console ให้คลิกไอคอน Cloud Shell ในแถบเครื่องมือด้านขวาบน

จากนั้นคลิก "เริ่ม Cloud Shell"

การจัดสรรและเชื่อมต่อกับสภาพแวดล้อมจะใช้เวลาเพียงไม่กี่นาที

เครื่องเสมือนนี้มาพร้อมเครื่องมือพัฒนาทั้งหมดที่คุณต้องการ โดยมีไดเรกทอรีหลักขนาด 5 GB ที่คงอยู่ถาวร และทำงานบน 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 ให้ไปที่ UI ของ Profiler โดยคลิก "Profiler" ในแถบนำทางด้านซ้าย

หรือคุณจะใช้แถบค้นหาของ Cloud Console เพื่อไปยัง UI ของ Profiler ก็ได้ เพียงพิมพ์ "Stackdriver Profiler" แล้วเลือกรายการที่พบ ไม่ว่าจะด้วยวิธีใด คุณควรเห็น UI ของเครื่องมือสร้างโปรไฟล์พร้อมข้อความ "ไม่มีข้อมูลที่จะแสดง" ดังที่แสดงด้านล่าง โปรเจ็กต์นี้เป็นโปรเจ็กต์ใหม่ จึงยังไม่มีข้อมูลการจัดทำโปรไฟล์ที่รวบรวมไว้

ตอนนี้ถึงเวลาสร้างโปรไฟล์แล้ว

เราจะใช้แอปพลิเคชัน Go สังเคราะห์อย่างง่ายที่มีอยู่ใน GitHub ในเทอร์มินัล Cloud Shell ที่คุณยังเปิดอยู่ (และขณะที่ข้อความ "ไม่มีข้อมูลที่จะแสดง" ยังคงแสดงใน UI ของ 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)
        }
        ...
}

โดยค่าเริ่มต้น Agent การสร้างโปรไฟล์จะรวบรวมโปรไฟล์ CPU, ฮีป และเธรด โค้ดที่นี่ช่วยให้รวบรวมโปรไฟล์ Mutex (หรือที่เรียกว่า "การแย่งชิง") ได้

ตอนนี้ให้เรียกใช้โปรแกรมโดยทำดังนี้

$ go run main.go

ขณะที่โปรแกรมทำงาน Agent การสร้างโปรไฟล์จะรวบรวมโปรไฟล์ของประเภทที่กำหนดค่าไว้ 5 ประเภทเป็นระยะๆ ระบบจะสุ่มรวบรวมข้อมูลเมื่อเวลาผ่านไป (โดยมีอัตราเฉลี่ย 1 โปรไฟล์ต่อนาทีสำหรับแต่ละประเภท) ดังนั้นอาจใช้เวลาสูงสุด 3 นาทีในการรวบรวมข้อมูลแต่ละประเภท โปรแกรมจะแจ้งให้คุณทราบเมื่อสร้างโปรไฟล์ ข้อความจะเปิดใช้โดยค่าสถานะ 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
...

UI จะอัปเดตตัวเองหลังจากรวบรวมโปรไฟล์แรกได้ไม่นาน หลังจากนั้นระบบจะไม่ทำการอัปเดตโดยอัตโนมัติ ดังนั้นหากต้องการดูข้อมูลใหม่ คุณจะต้องรีเฟรช UI ของ Profiler ด้วยตนเอง โดยคลิกปุ่ม "ตอนนี้" ในเครื่องมือเลือกช่วงเวลา 2 ครั้ง

หลังจากที่ UI รีเฟรชแล้ว คุณจะเห็นข้อความคล้ายกับข้อความต่อไปนี้

ตัวเลือกประเภทโปรไฟล์จะแสดงโปรไฟล์ 5 ประเภทที่พร้อมใช้งาน ดังนี้

ตอนนี้มาดูโปรไฟล์แต่ละประเภทและความสามารถที่สำคัญบางอย่างของ UI แล้วทำการทดสอบกัน ในขั้นตอนนี้ คุณไม่จำเป็นต้องใช้เทอร์มินัล Cloud Shell อีกต่อไป จึงออกได้โดยกด CTRL-C แล้วพิมพ์ "exit"

ตอนนี้เราได้รวบรวมข้อมูลบางส่วนแล้ว มาดูข้อมูลอย่างละเอียดกัน เราใช้แอปสังเคราะห์ (แหล่งที่มามีอยู่ใน GitHub) ที่จำลองลักษณะการทำงานซึ่งมักพบในปัญหาด้านประสิทธิภาพประเภทต่างๆ ในเวอร์ชันที่ใช้งานจริง

โค้ดที่ใช้ CPU สูง

เลือกประเภทโปรไฟล์ CPU หลังจาก UI โหลดแล้ว คุณจะเห็นบล็อกใบไม้ 4 บล็อกสำหรับฟังก์ชัน load ในกราฟเปลวไฟ ซึ่งรวมกันแล้วจะคิดเป็นปริมาณการใช้ CPU ทั้งหมด

ฟังก์ชันนี้เขียนขึ้นมาโดยเฉพาะเพื่อใช้รอบ CPU จำนวนมากด้วยการเรียกใช้ลูปที่เข้มงวด

main.go

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

ฟังก์ชันนี้เรียกใช้โดยอ้อมจาก busyloop() ผ่านเส้นทางการเรียกใช้ 4 เส้นทาง ได้แก่ busyloop → {foo1, foo2} → {bar, baz} → load ความกว้างของกล่องฟังก์ชันแสดงถึงต้นทุนโดยเทียบเคียงของเส้นทางการเรียกที่เฉพาะเจาะจง ในกรณีนี้ เส้นทางทั้ง 4 เส้นทางมีต้นทุนใกล้เคียงกัน ในโปรแกรมจริง คุณควรเน้นการเพิ่มประสิทธิภาพเส้นทางการโทรที่มีความสำคัญมากที่สุดในแง่ของประสิทธิภาพ กราฟเปลวไฟซึ่งเน้นเส้นทางที่มีค่าใช้จ่ายสูงกว่าด้วยกล่องที่ใหญ่กว่าจะช่วยให้ระบุเส้นทางเหล่านี้ได้ง่าย

คุณสามารถใช้ตัวกรองข้อมูลโปรไฟล์เพื่อปรับแต่งการแสดงผลเพิ่มเติมได้ เช่น ลองเพิ่มตัวกรอง "แสดงสแต็ก" โดยระบุ "baz" เป็นสตริงตัวกรอง คุณควรเห็นลักษณะคล้ายกับภาพหน้าจอด้านล่าง ซึ่งแสดงเส้นทางการโทรไปยัง load() เพียง 2 เส้นทางจากทั้งหมด 4 เส้นทาง เส้นทางทั้ง 2 นี้เป็นเส้นทางเดียวที่ผ่านฟังก์ชันที่มีสตริง "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))
        }
}

ฟังก์ชันจะทำงานครั้งเดียว โดยจัดสรร 64 MiB เป็นก้อนเล็กๆ จากนั้นจัดเก็บพอยน์เตอร์ไปยังก้อนเหล่านั้นในตัวแปรส่วนกลางเพื่อป้องกันไม่ให้ระบบเก็บขยะ โปรดทราบว่าปริมาณหน่วยความจำที่แสดงว่าใช้โดยโปรไฟล์เลอร์จะแตกต่างจาก 64 MiB เล็กน้อย เนื่องจากโปรไฟล์เลอร์ฮีปของ Go เป็นเครื่องมือทางสถิติ ดังนั้นการวัดจึงมีค่าใช้จ่ายต่ำแต่ไม่แม่นยำในระดับไบต์ อย่าแปลกใจเมื่อเห็นความแตกต่างประมาณ 10% เช่นนี้

โค้ดที่ใช้ I/O จำนวนมาก

หากเลือก "Threads" ในตัวเลือกประเภทโปรไฟล์ จอแสดงผลจะเปลี่ยนเป็นกราฟเปลวไฟซึ่งฟังก์ชัน wait และ waitImpl จะใช้พื้นที่ส่วนใหญ่

ในข้อมูลสรุปเหนือกราฟเปลวไฟ คุณจะเห็นว่ามี Goroutine 100 รายการที่ขยายสแต็กการเรียกจากฟังก์ชัน wait ซึ่งถูกต้องแล้ว เนื่องจากโค้ดที่เริ่มต้นการรอเหล่านี้มีลักษณะดังนี้

main.go

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

ประเภทโปรไฟล์นี้มีประโยชน์ในการทำความเข้าใจว่าโปรแกรมใช้เวลาในการรอ (เช่น I/O) โดยไม่คาดคิดหรือไม่ โดยปกติแล้วโปรไฟล์เลอร์ CPU จะไม่สุ่มตัวอย่างสแต็กการเรียกดังกล่าว เนื่องจากไม่ได้ใช้เวลา CPU เป็นสัดส่วนที่สำคัญ คุณมักจะต้องการใช้ตัวกรอง "ซ่อนสแต็ก" กับโปรไฟล์ Threads เช่น เพื่อซ่อนสแต็กทั้งหมดที่ลงท้ายด้วยการเรียกใช้ gopark, เนื่องจากมักจะเป็น Goroutine ที่ไม่ได้ใช้งานและน่าสนใจน้อยกว่า Goroutine ที่รอ I/O

ประเภทโปรไฟล์ของเธรดอาจช่วยระบุจุดในโปรแกรมที่เธรดรอ Mutex ที่เป็นของส่วนอื่นของโปรแกรมเป็นเวลานานได้ด้วย แต่ประเภทโปรไฟล์ต่อไปนี้มีประโยชน์มากกว่าสำหรับกรณีดังกล่าว

โค้ดที่มีการแย่งชิงสูง

ประเภทโปรไฟล์การแย่งชิงจะระบุการล็อกที่ "ต้องการ" มากที่สุดในโปรแกรม โปรไฟล์ประเภทนี้ใช้ได้กับโปรแกรม Go แต่ต้องเปิดใช้โดยระบุ "MutexProfiling: true" ในโค้ดการกำหนดค่าเอเจนต์อย่างชัดเจน การรวบรวมข้อมูลจะทำงานโดยการบันทึก (ภายใต้เมตริก "การโต้แย้ง") จำนวนครั้งที่เมื่อมีการปลดล็อกที่เฉพาะเจาะจงโดย Goroutine A จะมี Goroutine B อีกตัวรอให้ปลดล็อก นอกจากนี้ยังบันทึกเวลาที่ Goroutine ที่ถูกบล็อกรอการล็อก (ภายใต้เมตริก "Delay") ด้วย ในตัวอย่างนี้ มีกองซ้อนการแย่งชิงรายการเดียวและเวลารอทั้งหมดสำหรับการล็อกคือ 11.03 วินาที

โค้ดที่สร้างโปรไฟล์นี้ประกอบด้วย Goroutine 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)
        }
}

ในห้องทดลองนี้ คุณได้เรียนรู้วิธีกำหนดค่าโปรแกรม Go เพื่อใช้กับ Stackdriver Profiler นอกจากนี้ คุณยังได้เรียนรู้วิธีรวบรวม ดู และวิเคราะห์ข้อมูลประสิทธิภาพด้วยเครื่องมือนี้ ตอนนี้คุณสามารถนำทักษะใหม่ไปใช้กับบริการจริงที่ทำงานใน Google Cloud Platform ได้แล้ว

คุณได้เรียนรู้วิธีกำหนดค่าและใช้ Stackdriver Profiler แล้ว

ดูข้อมูลเพิ่มเติม

ใบอนุญาต

ผลงานนี้ได้รับอนุญาตภายใต้สัญญาอนุญาตครีเอทีฟคอมมอนส์สำหรับยอมรับสิทธิของผู้สร้าง (Creative Commons Attribution License) 2.0 แบบทั่วไป