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:

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:

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:

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:

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:

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:

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 để:

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.

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.