Tìm hiểu về Go Profiling

Profiler là một công cụ phân tích hiệu suất, cung cấp các thông tin quan trọng về thực thi ở nhiều khía cạnh khác nhau, cho phép giải quyết vấn đề về performance , memory leaks, thread contention và hơn thế nữa.
- Về cơ bản, Profiling là một kỹ thuật để hiểu chương trình của chúng ta dành thời gian ở đâu và chức năng nào tốn kém nhất về mức sử dụng CPU, phân bổ bộ nhớ, hoạt động mạng, v.v.
- Nó đo lường và giúp chúng tôi phân tích hành vi thời gian chạy của một chương trình.
Các loại Profiling
Có một số loại profiling, bao gồm:
Profiling CPU:
- Để đo lường thời gian mà các phần khác nhau của code được thực thi.
- Để tìm ra các hàm hoặc phần của code đang mất quá nhiều thời gian để thực thi.
Profiling bộ nhớ:
- Để đo lường lượng bộ nhớ đang được sử dụng bởi chương trình của chúng ta.
- Để tìm ra memory leaks nơi bộ nhớ được sử dụng không hiệu quả.
Profiling đồng thời:
- Để đo lường hiệu suất của chương trình khi sử dụng nhiều goroutine hoặc các channel.
- Để xác định các vấn đề về race conditions và deadlocks.
Tiến hành
Đầu tiên , chúng ta xây dựng một web server mô phỏng chức năng Caching. Tuy nhiên chức năng Caching này lại thiếu đi các bước để remove các data cũ , hay cơ chế hết hạn cho data. Điều này dẫn đến khi có một lượng lớn request tới server. Sẽ dẫn đến tình trạng out memory
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
_ "net/http/pprof"
"sync"
)
type Cache struct {
sync.Mutex
Data map[int]interface{}
}
func main() {
cache := Cache{
Data: make(map[int]interface{}),
}
go func() {
http.ListenAndServe(":6060", nil)
}()
r := gin.Default()
r.GET("/action", func(c *gin.Context) {
cache.Lock()
defer cache.Unlock()
index := len(cache.Data)
cache.Data[index] = make([]byte, 1000000)
log.Printf("Add more cacahing %d", index)
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
Giải thích Code:
- Chúng ta import package
net/http/pprof, đây là package giúp đo lường hiệu suất của những dòng code được thực thi. Với cách import này, function init sẽ được khởi chạy ngay khi package được import. Hàm init() đăng ký các HTTP Handler cho các endpoint khác nhau như /debug/pprof/, /debug/pprof/heap, /debug/pprof/goroutine, /debug/pprof/block, v.v. Đây là những endpoint chúng ta có thể sử dụng sau này phục vụ các chức năng đo lường - Expose 1 port 6060 phục vụ các endpoint của profiling
- Chúng ta tạo 1
Cache struct, có thêm khóa Mutex để đảm bảo rằng mỗi request chỉ được tuần tự lấy giá trị trong struct này ra để xử lý. - Khi có 1 request tới , chúng ta giả định rằng sẽ có 1 lương lớn data được caching (
cache.Data[index] = make([]byte, 1000000))
Chúng ta chạy server bằng câu lệnh
go run main.go
Và giờ chúng ta tiến hành việc mô phỏng một lượng lớn request tới server, chúng ta sẽ sử dụng Apache Benhmark cho chức năng này. Đầu tiên, cài đặt Apache Benhmark tại đây:
https://www.apachelounge.com/download/
Sau khi download về và giải nén, copy file ab.exe tại thư mục /bin/ab.exe vào thư mục chứa file main.go của project
Tại đây , chúng ta thực hiện dòng lệnh bên dưới
ab.exe -n 10000 -c 1000 http://localhost:8080/actionSau đó , mở 1 terminal khác và thực hiện 1 trong các câu lệnh
go tool pprof -alloc_space http://localhost:6060/debug/pprof/heap
go tool pprof -alloc_space http://localhost:6060/debug/pprof/profile
Sau đó , tiếp tục câu lệnh
top -> Xem các đoạn code consume lượng lớn bộ nhớ nhất

Chúng ta có thể thấy tại main.func2 tiêu thụ một lớn lớn bộ nhớ (3.88GB) cho hàng lọat request vừa rồi
Tiếp tục thực hiện câu lệnh
list main.func2

Chúng ta có thể thấy ngay đoạn code đang làm cho chương trình của chúng ta tiêu thụ lương lớn bộ nhớ. Từ đó có thể đưa ra các giải pháp khắc phục
Ngoài ra để visualize kết quả sau khi profiling, chúng ta dùng lệnh
web
