Diagnostics
Giới thiệu
Hệ sinh thái Go cung cấp một bộ API và công cụ lớn để chẩn đoán các vấn đề về logic và hiệu năng trong các chương trình Go. Trang này tóm tắt các công cụ hiện có và giúp người dùng Go chọn công cụ phù hợp cho vấn đề cụ thể của họ.
Các giải pháp chẩn đoán có thể được phân loại thành các nhóm sau:
- Profiling: Các công cụ profiling phân tích độ phức tạp và chi phí của một chương trình Go, chẳng hạn như mức sử dụng bộ nhớ và các hàm được gọi thường xuyên, để xác định các phần tốn kém của một chương trình Go.
- Tracing: Tracing là một cách thêm công cụ đo vào mã để phân tích độ trễ trong suốt vòng đời của một lệnh gọi hoặc yêu cầu người dùng. Trace cung cấp tổng quan về mức độ đóng góp độ trễ của từng thành phần vào tổng độ trễ trong một hệ thống. Trace có thể trải rộng qua nhiều tiến trình Go.
- Debugging: Debugging cho phép chúng ta tạm dừng một chương trình Go và kiểm tra quá trình thực thi của nó. Trạng thái và luồng của chương trình có thể được xác minh bằng debugging.
- Thống kê và sự kiện runtime: Việc thu thập và phân tích các thống kê và sự kiện runtime cung cấp tổng quan cấp cao về tình trạng của các chương trình Go. Các đột biến tăng/giảm của chỉ số giúp chúng ta xác định những thay đổi về thông lượng, mức sử dụng và hiệu năng.
Lưu ý: Một số công cụ chẩn đoán có thể gây ảnh hưởng lẫn nhau. Ví dụ, profiling bộ nhớ chính xác làm sai lệch các CPU profile và profiling việc goroutine bị chặn ảnh hưởng đến trace của scheduler. Hãy sử dụng các công cụ riêng biệt để có thông tin chính xác hơn.
Profiling
Profiling hữu ích để xác định các phần mã tốn kém hoặc được gọi thường xuyên.
Go runtime cung cấp
dữ liệu profiling ở định dạng được mong đợi bởi
công cụ trực quan hóa pprof.
Dữ liệu profiling có thể được thu thập trong quá trình kiểm thử
thông qua go test hoặc các endpoint được cung cấp từ gói
net/http/pprof. Người dùng cần thu thập dữ liệu profiling và sử dụng các công cụ pprof để lọc
và trực quan hóa các đường dẫn mã hàng đầu.
Các profile được định nghĩa sẵn do gói runtime/pprof cung cấp:
- cpu: CPU profile xác định nơi một chương trình dành thời gian khi đang chủ động tiêu thụ chu kỳ CPU (khác với khi đang ngủ hoặc chờ I/O).
- heap: Heap profile báo cáo các mẫu cấp phát bộ nhớ; được dùng để theo dõi mức sử dụng bộ nhớ hiện tại và trong quá khứ, cũng như kiểm tra rò rỉ bộ nhớ.
- threadcreate: Thread creation profile báo cáo các phần của chương trình dẫn đến việc tạo các OS thread mới.
- goroutine: Goroutine profile báo cáo các stack trace của tất cả goroutine hiện tại.
-
block: Block profile hiển thị nơi các goroutine bị chặn khi chờ các
primitive đồng bộ hóa (bao gồm các kênh timer). Block profile không được bật theo mặc định;
sử dụng
runtime.SetBlockProfileRateđể bật nó. -
mutex: Mutex profile báo cáo các tranh chấp khóa. Khi bạn cho rằng
CPU của mình không được sử dụng đầy đủ do tranh chấp mutex, hãy sử dụng profile này. Mutex profile
không được bật theo mặc định, xem
runtime.SetMutexProfileFractionđể bật nó.
Tôi có thể sử dụng profiler nào khác để profiling các chương trình Go?
Trên Linux, công cụ perf có thể được sử dụng để profiling các chương trình Go. Perf có thể profiling và unwind mã cgo/SWIG và kernel, vì vậy nó có thể hữu ích để có được thông tin chi tiết về các điểm nghẽn hiệu năng native/kernel. Trên macOS, bộ công cụ Instruments có thể được sử dụng để profiling các chương trình Go.
Tôi có thể profiling các dịch vụ production của mình không?
Có. Việc profiling các chương trình trong production là an toàn, nhưng việc bật một số profile (ví dụ CPU profile) sẽ làm tăng chi phí. Bạn nên dự kiến sẽ thấy hiệu năng giảm. Mức ảnh hưởng hiệu năng có thể được ước tính bằng cách đo overhead của profiler trước khi bật nó trong production.
Bạn có thể muốn profiling định kỳ các dịch vụ production của mình. Đặc biệt trong một hệ thống có nhiều bản sao của cùng một tiến trình, việc chọn một bản sao ngẫu nhiên theo định kỳ là một lựa chọn an toàn. Chọn một tiến trình production, profiling nó trong X giây cho mỗi Y giây và lưu kết quả để trực quan hóa và phân tích; sau đó lặp lại định kỳ. Kết quả có thể được xem xét thủ công và/hoặc tự động để tìm vấn đề. Việc thu thập các profile có thể gây ảnh hưởng lẫn nhau, vì vậy nên chỉ thu thập một profile tại một thời điểm.
Các cách tốt nhất để trực quan hóa dữ liệu profiling là gì?
Các công cụ Go cung cấp khả năng trực quan hóa dữ liệu profile bằng văn bản, đồ thị và callgrind
sử dụng
go tool pprof.
Đọc Profiling các chương trình Go
để xem chúng hoạt động.
Liệt kê các lời gọi tốn kém nhất dưới dạng văn bản.
Trực quan hóa các lời gọi tốn kém nhất dưới dạng đồ thị.
Chế độ xem Weblist hiển thị các phần tốn kém của mã nguồn theo từng dòng trong
một trang HTML. Trong ví dụ sau, 530ms được dành cho
runtime.concatstrings và chi phí của từng dòng được trình bày
trong danh sách.
Hình ảnh trực quan của các lệnh gọi tốn kém nhất dưới dạng weblist.
Một cách khác để trực quan hóa dữ liệu profile là biểu đồ ngọn lửa flame graph. Biểu đồ ngọn lửa cho phép bạn di chuyển trong một đường dẫn tổ tiên cụ thể, vì vậy bạn có thể phóng to/thu nhỏ các phần mã cụ thể. pprof thượng nguồn có hỗ trợ cho biểu đồ ngọn lửa.
Biểu đồ ngọn lửa cung cấp hình ảnh trực quan để phát hiện các đường dẫn mã tốn kém nhất.
Tôi có bị giới hạn trong các profile tích hợp sẵn không?
Ngoài những gì runtime cung cấp, người dùng Go có thể tạo các profile tùy chỉnh của riêng họ thông qua pprof.Profile và sử dụng các công cụ hiện có để kiểm tra chúng.
Tôi có thể phục vụ các trình xử lý profiler (/debug/pprof/...) trên một đường dẫn và cổng khác không?
Có. Gói net/http/pprof đăng ký các trình xử lý của nó vào mux mặc định
theo mặc định, nhưng bạn cũng có thể tự đăng ký chúng bằng cách sử dụng các trình xử lý
được xuất từ gói.
Ví dụ, ví dụ sau sẽ phục vụ trình xử lý pprof.Profile trên :7777 tại /custom_debug_path/profile:
package main
import (
"log"
"net/http"
"net/http/pprof"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
log.Fatal(http.ListenAndServe(":7777", mux))
}
Tracing
Tracing là một cách để chèn công cụ vào mã nhằm phân tích độ trễ trong suốt vòng đời của một chuỗi lệnh gọi. Go cung cấp gói golang.org/x/net/trace như một backend tracing tối giản cho mỗi nút Go và cung cấp một thư viện instrumentation tối giản với một bảng điều khiển đơn giản. Go cũng cung cấp một trình theo dõi thực thi để theo dõi các sự kiện runtime trong một khoảng thời gian.
Tracing cho phép chúng ta:
- Chèn công cụ và phân tích độ trễ của ứng dụng trong một tiến trình Go.
- Đo chi phí của các lệnh gọi cụ thể trong một chuỗi lệnh gọi dài.
- Tìm ra mức sử dụng và các cải tiến hiệu năng. Các điểm nghẽn không phải lúc nào cũng rõ ràng nếu không có dữ liệu tracing.
Trong các hệ thống nguyên khối, việc thu thập dữ liệu chẩn đoán từ các khối xây dựng của một chương trình tương đối dễ dàng. Tất cả mô-đun nằm trong một tiến trình và chia sẻ các tài nguyên chung để báo cáo log, lỗi và thông tin chẩn đoán khác. Khi hệ thống của bạn phát triển vượt quá một tiến trình duy nhất và bắt đầu trở thành hệ thống phân tán, việc theo dõi một lệnh gọi bắt đầu từ máy chủ web phía trước đến tất cả backend của nó cho đến khi phản hồi được trả về cho người dùng trở nên khó khăn hơn. Đây là lúc tracing phân tán đóng vai trò lớn trong việc chèn công cụ và phân tích các hệ thống production của bạn.
Tracing phân tán là một cách để chèn công cụ vào mã nhằm phân tích độ trễ trong suốt vòng đời của một yêu cầu người dùng. Khi một hệ thống được phân tán và khi các công cụ profiling và gỡ lỗi truyền thống không thể mở rộng, bạn có thể muốn sử dụng các công cụ tracing phân tán để phân tích hiệu năng của các yêu cầu người dùng và RPC.
Tracing phân tán cho phép chúng ta:
- Chèn công cụ và lập profile độ trễ của ứng dụng trong một hệ thống lớn.
- Theo dõi tất cả RPC trong vòng đời của một yêu cầu người dùng và thấy các vấn đề tích hợp chỉ có thể nhìn thấy trong production.
- Tìm ra các cải tiến hiệu năng có thể áp dụng cho hệ thống của chúng ta. Nhiều điểm nghẽn không rõ ràng trước khi thu thập dữ liệu tracing.
Hệ sinh thái Go cung cấp nhiều thư viện tracing phân tán cho từng hệ thống tracing và các thư viện không phụ thuộc backend.
Có cách nào để tự động chặn từng lệnh gọi hàm và tạo trace không?
Go không cung cấp cách để tự động chặn mọi lệnh gọi hàm và tạo trace span. Bạn cần tự chèn công cụ vào mã của mình để tạo, kết thúc và chú thích span.
Tôi nên truyền các header trace trong các thư viện Go như thế nào?
Bạn có thể truyền các định danh trace và thẻ trong
context.Context.
Hiện vẫn chưa có khóa trace chuẩn hoặc biểu diễn chung của các header trace
trong ngành. Mỗi nhà cung cấp tracing chịu trách nhiệm cung cấp các tiện ích
truyền tiếp trong các thư viện Go của họ.
Các sự kiện cấp thấp nào khác từ thư viện chuẩn hoặc runtime có thể được đưa vào một trace?
Thư viện chuẩn và runtime đang cố gắng cung cấp thêm một số API
để thông báo về các sự kiện nội bộ cấp thấp. Ví dụ,
httptrace.ClientTrace
cung cấp các API để theo dõi các sự kiện cấp thấp trong vòng đời của một yêu cầu gửi đi.
Có một nỗ lực đang diễn ra nhằm lấy các sự kiện runtime cấp thấp từ
trình theo dõi thực thi runtime và cho phép người dùng định nghĩa và ghi lại các sự kiện của riêng họ.
Gỡ lỗi
Gỡ lỗi là quá trình xác định lý do chương trình hoạt động không đúng. Trình gỡ lỗi cho phép chúng ta hiểu luồng thực thi và trạng thái hiện tại của chương trình. Có nhiều kiểu gỡ lỗi khác nhau; phần này chỉ tập trung vào việc gắn trình gỡ lỗi vào một chương trình và gỡ lỗi core dump.
Người dùng Go chủ yếu sử dụng các trình gỡ lỗi sau:
- Delve: Delve là một trình gỡ lỗi cho ngôn ngữ lập trình Go. Nó hỗ trợ các khái niệm runtime và các kiểu dựng sẵn của Go. Delve đang hướng tới trở thành một trình gỡ lỗi đầy đủ tính năng và đáng tin cậy cho các chương trình Go.
- GDB: Go cung cấp hỗ trợ GDB thông qua trình biên dịch Go chuẩn và Gccgo. Việc quản lý stack, luồng và runtime chứa các khía cạnh khác biệt đủ nhiều so với mô hình thực thi mà GDB mong đợi, đến mức có thể gây nhầm lẫn cho trình gỡ lỗi, ngay cả khi chương trình được biên dịch bằng gccgo. Mặc dù GDB có thể được sử dụng để gỡ lỗi các chương trình Go, nó không lý tưởng và có thể gây ra sự nhầm lẫn.
Trình gỡ lỗi hoạt động với các chương trình Go tốt đến mức nào?
Trình biên dịch gc thực hiện các tối ưu hóa như
inline hàm và đưa biến vào thanh ghi. Những tối ưu hóa này
đôi khi khiến việc gỡ lỗi bằng trình gỡ lỗi trở nên khó khăn hơn. Hiện đang có
nỗ lực cải thiện chất lượng thông tin DWARF được tạo ra cho
các binary đã tối ưu hóa. Cho đến khi những cải tiến đó có sẵn, chúng tôi khuyến nghị
vô hiệu hóa các tối ưu hóa khi xây dựng mã đang được gỡ lỗi. Lệnh sau
xây dựng một gói mà không có các tối ưu hóa của trình biên dịch:
$ go build -gcflags=all="-N -l"Trong khuôn khổ nỗ lực cải tiến, Go 1.10 đã giới thiệu một cờ trình biên dịch mới
-dwarflocationlists. Cờ này khiến trình biên dịch
thêm các danh sách vị trí giúp trình gỡ lỗi làm việc với các binary đã tối ưu hóa.
Lệnh sau xây dựng một gói với các tối ưu hóa nhưng có
các danh sách vị trí DWARF:
$ go build -gcflags="-dwarflocationlists=true"
Giao diện người dùng trình gỡ lỗi nào được khuyến nghị?
Mặc dù cả delve và gdb đều cung cấp CLI, hầu hết các tích hợp trình soạn thảo và IDE đều cung cấp các giao diện người dùng dành riêng cho việc gỡ lỗi.
Có thể thực hiện gỡ lỗi sau sự cố với các chương trình Go không?
Một tệp core dump là một tệp chứa bản kết xuất bộ nhớ của một tiến trình đang chạy và trạng thái của tiến trình đó. Nó chủ yếu được sử dụng để gỡ lỗi sau sự cố của một chương trình và để hiểu trạng thái của nó trong khi chương trình vẫn đang chạy. Hai trường hợp này khiến việc gỡ lỗi core dump trở thành một công cụ chẩn đoán hữu ích để kiểm tra sau sự cố và phân tích các dịch vụ production. Có thể lấy các tệp core từ các chương trình Go và sử dụng delve hoặc gdb để gỡ lỗi, xem trang gỡ lỗi core dump để biết hướng dẫn từng bước.
Số liệu thống kê và sự kiện Runtime
Runtime cung cấp số liệu thống kê và báo cáo về các sự kiện nội bộ để người dùng chẩn đoán các vấn đề về hiệu năng và mức sử dụng ở cấp độ runtime.
Người dùng có thể theo dõi các số liệu này để hiểu rõ hơn về tình trạng tổng thể và hiệu năng của các chương trình Go. Một số số liệu và trạng thái thường được theo dõi:
runtime.ReadMemStatsbáo cáo các chỉ số liên quan đến việc cấp phát heap và bộ gom rác. Số liệu bộ nhớ hữu ích để theo dõi lượng tài nguyên bộ nhớ mà một tiến trình đang sử dụng, liệu tiến trình có thể sử dụng bộ nhớ hiệu quả hay không, và phát hiện rò rỉ bộ nhớ.debug.ReadGCStatsđọc các số liệu thống kê về bộ gom rác. Hàm này hữu ích để xem lượng tài nguyên đã được sử dụng cho các lần tạm dừng GC. Nó cũng báo cáo dòng thời gian của các lần tạm dừng của bộ gom rác và các phân vị thời gian tạm dừng.debug.Stacktrả về dấu vết ngăn xếp hiện tại. Dấu vết ngăn xếp hữu ích để xem hiện có bao nhiêu goroutine đang chạy, chúng đang làm gì, và chúng có bị chặn hay không.debug.WriteHeapDumptạm dừng việc thực thi của tất cả goroutine và cho phép bạn ghi heap ra một tệp. Heap dump là một ảnh chụp nhanh bộ nhớ của một tiến trình Go tại một thời điểm nhất định. Nó chứa tất cả đối tượng đã được cấp phát cùng với goroutine, trình hoàn tất và nhiều thông tin khác.runtime.NumGoroutinetrả về số lượng goroutine hiện tại. Giá trị này có thể được theo dõi để xem có đủ goroutine được sử dụng hay không, hoặc để phát hiện rò rỉ goroutine.
Execution tracer
Go đi kèm với một công cụ theo dõi thực thi runtime để ghi lại nhiều loại sự kiện runtime. Lập lịch, syscall, bộ gom rác, kích thước heap và các sự kiện khác được runtime thu thập và có sẵn để trực quan hóa bằng go tool trace. Execution tracer là một công cụ để phát hiện các vấn đề về độ trễ và mức sử dụng. Bạn có thể kiểm tra mức độ sử dụng CPU, cũng như thời điểm mạng hoặc syscall là nguyên nhân gây ra việc preemption đối với các goroutine.
Tracer hữu ích để:
- Hiểu cách các goroutine của bạn thực thi.
- Hiểu một số sự kiện runtime cốt lõi như các lần chạy GC.
- Xác định việc thực thi được song song hóa kém.
Tuy nhiên, nó không phù hợp để xác định các điểm nóng như phân tích nguyên nhân của việc sử dụng quá mức bộ nhớ hoặc CPU. Thay vào đó, trước tiên hãy sử dụng các công cụ profiling để xử lý chúng.
Ở trên, phần trực quan hóa của go tool trace cho thấy quá trình thực thi ban đầu diễn ra tốt, sau đó trở nên tuần tự hóa. Điều này cho thấy có thể đang xảy ra tranh chấp khóa đối với một tài nguyên dùng chung, tạo ra nút thắt cổ chai.
Xem go tool trace
để thu thập và phân tích các dấu vết runtime.
GODEBUG
Runtime cũng phát ra các sự kiện và thông tin nếu biến môi trường GODEBUG được thiết lập tương ứng.
- GODEBUG=gctrace=1 in ra các sự kiện của bộ gom rác ở mỗi lần thu gom, tóm tắt lượng bộ nhớ đã được thu gom và thời lượng tạm dừng.
- GODEBUG=inittrace=1 in ra bản tóm tắt thông tin về thời gian thực thi và cấp phát bộ nhớ cho các công việc khởi tạo gói đã hoàn tất.
- GODEBUG=schedtrace=X in ra các sự kiện lập lịch sau mỗi X mili giây.
Biến môi trường GODEBUG có thể được dùng để vô hiệu hóa việc sử dụng các phần mở rộng tập lệnh trong thư viện chuẩn và runtime.
- GODEBUG=cpu.all=off vô hiệu hóa việc sử dụng tất cả các phần mở rộng tập lệnh tùy chọn.
- GODEBUG=cpu.extension=off vô hiệu hóa việc sử dụng các lệnh từ phần mở rộng tập lệnh được chỉ định.
extension là tên viết thường của phần mở rộng tập lệnh, chẳng hạn như sse41 hoặc avx.