Phân tích hiệu suất phát hành công khai bằng Trình phân tích tài nguyên trên đám mây

1. Tổng quan

Mặc dù các nhà phát triển ứng dụng khách và nhà phát triển web giao diện người dùng thường sử dụng các công cụ như Trình phân tích CPU của Android Studio hoặc các công cụ lập hồ sơ có trong Chrome để cải thiện hiệu suất mã của họ, nhưng các kỹ thuật tương đương lại không dễ tiếp cận hoặc được những người làm việc trên các dịch vụ phụ trợ áp dụng. Cloud Profiler mang đến những chức năng tương tự cho nhà phát triển dịch vụ, bất kể mã của họ có đang chạy trên Google Cloud Platform hay không.

95c034c70c9cac22.png

Công cụ này thu thập thông tin về mức sử dụng CPU và việc phân bổ bộ nhớ từ các ứng dụng phát hành của bạn. Thao tác này sẽ gán thông tin đó cho mã nguồn của ứng dụng, giúp bạn xác định những phần của ứng dụng tiêu thụ nhiều tài nguyên nhất, đồng thời làm nổi bật các đặc điểm hiệu suất của mã. Tổng chi phí thấp của các kỹ thuật thu thập mà công cụ này sử dụng khiến nó phù hợp để sử dụng liên tục trong môi trường sản xuất.

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách thiết lập Cloud Profiler cho một chương trình Go và làm quen với những thông tin chi tiết về hiệu suất ứng dụng mà công cụ này có thể trình bày.

Kiến thức bạn sẽ học được

  • Cách định cấu hình chương trình Go để lập hồ sơ bằng Cloud Profiler.
  • Cách thu thập, xem và phân tích dữ liệu hiệu suất bằng Cloud Profiler.

Bạn cần có

  • Một dự án trên Google Cloud Platform
  • Một trình duyệt, chẳng hạn như Chrome hoặc Firefox
  • Làm quen với các trình chỉnh sửa văn bản tiêu chuẩn của Linux, chẳng hạn như Vim, EMAC hoặc Nano

Bạn sẽ sử dụng hướng dẫn này như thế nào?

Chỉ đọc Đọc và hoàn thành bài tập

Bạn đánh giá thế nào về trải nghiệm của mình với Google Cloud Platform?

Người mới bắt đầu Trung cấp Thành thạo

2. Thiết lập và yêu cầu

Thiết lập môi trường theo tốc độ của riêng bạn

  1. Đăng nhập vào Cloud Console rồi tạo một dự án mới hoặc sử dụng lại một dự án hiện có. Nếu chưa có tài khoản Gmail hoặc Google Workspace, bạn phải tạo một tài khoản.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Hãy nhớ mã dự án, một tên duy nhất trên tất cả các dự án trên Google Cloud (tên ở trên đã được sử dụng và sẽ không hoạt động đối với bạn, xin lỗi!). Sau này trong lớp học lập trình này, chúng ta sẽ gọi nó là PROJECT_ID.

  1. Tiếp theo, bạn cần bật tính năng thanh toán trong Cloud Console để sử dụng các tài nguyên của Google Cloud.

Việc thực hiện lớp học lập trình này sẽ không tốn nhiều chi phí, nếu có. Hãy nhớ làm theo mọi hướng dẫn trong phần "Dọn dẹp" để biết cách tắt các tài nguyên nhằm tránh bị tính phí ngoài phạm vi hướng dẫn này. Người dùng mới của Google Cloud đủ điều kiện tham gia chương trình Dùng thử miễn phí trị giá 300 USD.

Google Cloud Shell

Mặc dù bạn có thể vận hành Google Cloud từ xa trên máy tính xách tay, nhưng để đơn giản hoá quá trình thiết lập trong lớp học lập trình này, chúng ta sẽ sử dụng Google Cloud Shell, một môi trường dòng lệnh chạy trên Cloud.

Kích hoạt Cloud Shell

  1. Trong Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell 4292cbf4971c9786.png.

bce75f34b2c53987.png

Nếu chưa từng khởi động Cloud Shell, bạn sẽ thấy một màn hình trung gian (bên dưới phần hiển thị đầu tiên) mô tả về Cloud Shell. Nếu vậy, hãy nhấp vào Tiếp tục (và bạn sẽ không bao giờ thấy màn hình này nữa). Sau đây là giao diện của màn hình xuất hiện một lần:

70f315d7b402b476.png

Quá trình cung cấp và kết nối với Cloud Shell chỉ mất vài giây.

fbe3a0674c982259.png

Máy ảo này được trang bị tất cả các công cụ phát triển mà bạn cần. Nền tảng này cung cấp một thư mục chính có dung lượng 5 GB và chạy trong Google Cloud, giúp tăng cường đáng kể hiệu suất mạng và hoạt động xác thực. Bạn có thể thực hiện hầu hết, nếu không muốn nói là tất cả, công việc trong lớp học lập trình này chỉ bằng một trình duyệt hoặc Chromebook.

Sau khi kết nối với Cloud Shell, bạn sẽ thấy rằng mình đã được xác thực và dự án đã được đặt thành mã dự án của bạn.

  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng bạn đã được xác thực:
gcloud auth list

Đầu ra của lệnh

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Chạy lệnh sau trong Cloud Shell để xác nhận rằng lệnh gcloud biết về dự án của bạn:
gcloud config list project

Đầu ra của lệnh

[core]
project = <PROJECT_ID>

Nếu không, bạn có thể đặt nó bằng lệnh sau:

gcloud config set project <PROJECT_ID>

Đầu ra của lệnh

Updated property [core/project].

3. Chuyển đến Cloud Profiler

Trong Cloud Console, hãy chuyển đến giao diện người dùng Trình phân tích tài nguyên bằng cách nhấp vào "Trình phân tích tài nguyên" trong thanh điều hướng bên trái:

37ad0df7ddb2ad17.png

Ngoài ra, bạn có thể sử dụng thanh tìm kiếm trên Cloud Console để chuyển đến giao diện người dùng Profiler: chỉ cần nhập "Cloud Profiler" rồi chọn mục tìm thấy. Dù bằng cách nào, bạn cũng sẽ thấy giao diện người dùng Profiler kèm theo thông báo "Không có dữ liệu để hiển thị" như bên dưới. Dự án này là dự án mới nên chưa thu thập được dữ liệu lập hồ sơ nào.

d275a5f61ed31fb2.png

Bây giờ là lúc để lấy một số thông tin về hồ sơ!

4. Lập hồ sơ Điểm chuẩn

Chúng ta sẽ sử dụng một ứng dụng Go tổng hợp đơn giản có trên GitHub. Trong cửa sổ dòng lệnh Cloud Shell mà bạn vẫn đang mở (và trong khi thông báo "Không có dữ liệu để hiển thị" vẫn xuất hiện trong giao diện người dùng Trình phân tích tài nguyên), hãy chạy lệnh sau:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

Sau đó, chuyển sang thư mục ứng dụng:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

Thư mục này chứa tệp "main.go", là một ứng dụng tổng hợp đã bật tác nhân lập hồ sơ:

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)
        }
        ...
}

Theo mặc định, tác nhân lập hồ sơ sẽ thu thập hồ sơ CPU, heap và luồng. Mã ở đây cho phép thu thập hồ sơ mutex (còn gọi là "tranh chấp").

Bây giờ, hãy chạy chương trình:

$ go run main.go

Khi chương trình chạy, tác nhân lập hồ sơ sẽ định kỳ thu thập hồ sơ của 5 loại được định cấu hình. Bộ sưu tập được tạo ngẫu nhiên theo thời gian (với tốc độ trung bình là một hồ sơ mỗi phút cho mỗi loại), vì vậy, có thể mất tối đa 3 phút để thu thập từng loại. Chương trình sẽ cho bạn biết thời điểm tạo hồ sơ. Thông báo được bật bằng cờ DebugLogging trong cấu hình ở trên; nếu không, tác nhân sẽ chạy âm thầm:

$ 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
...

Giao diện người dùng sẽ tự động cập nhật ngay sau khi thu thập được hồ sơ đầu tiên. Sau đó, dữ liệu sẽ không tự động cập nhật. Vì vậy, để xem dữ liệu mới, bạn cần làm mới giao diện người dùng Profiler theo cách thủ công. Để thực hiện việc này, hãy nhấp vào nút Hiện tại trong bộ chọn khoảng thời gian hai lần:

650051097b651b91.png

Sau khi giao diện người dùng làm mới, bạn sẽ thấy nội dung như sau:

47a763d4dc78b6e8.png

Bộ chọn loại hồ sơ cho thấy 5 loại hồ sơ hiện có:

b5d7b4b5051687c9.png

Bây giờ, hãy xem xét từng loại hồ sơ và một số chức năng quan trọng của giao diện người dùng, sau đó tiến hành một số thử nghiệm. Ở giai đoạn này, bạn không cần đến thiết bị đầu cuối Cloud Shell nữa, vì vậy, bạn có thể thoát bằng cách nhấn CTRL-C rồi nhập "exit".

5. Phân tích dữ liệu của Profiler

Sau khi thu thập một số dữ liệu, hãy xem xét kỹ hơn. Chúng tôi đang sử dụng một ứng dụng tổng hợp (nguồn có trên Github) mô phỏng các hành vi thường thấy của nhiều loại vấn đề về hiệu suất trong quá trình phát hành công khai.

Mã sử dụng nhiều CPU

Chọn loại hồ sơ CPU. Sau khi giao diện người dùng tải xong, bạn sẽ thấy 4 khối lá cho hàm load trong biểu đồ ngọn lửa. Các khối này chiếm toàn bộ mức tiêu thụ CPU:

fae661c9fe6c58df.png

Hàm này được viết riêng để tiêu thụ nhiều chu kỳ CPU bằng cách chạy một vòng lặp chặt chẽ:

main.go

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

Hàm này được gọi gián tiếp từ busyloop() thông qua 4 đường dẫn gọi: busyloop → {foo1, foo2} → {bar, baz} → load. Chiều rộng của một hộp hàm biểu thị chi phí tương đối của đường dẫn gọi cụ thể. Trong trường hợp này, cả 4 đường dẫn đều có chi phí tương đương. Trong một chương trình thực tế, bạn nên tập trung vào việc tối ưu hoá những đường dẫn lệnh gọi quan trọng nhất về hiệu suất. Biểu đồ ngọn lửa (nhấn mạnh trực quan các đường dẫn tốn kém hơn bằng các hộp lớn hơn) giúp bạn dễ dàng xác định các đường dẫn này.

Bạn có thể sử dụng bộ lọc dữ liệu hồ sơ để tinh chỉnh thêm chế độ hiển thị. Ví dụ: hãy thử thêm bộ lọc "Hiện ngăn xếp" và chỉ định "baz" làm chuỗi bộ lọc. Bạn sẽ thấy một hình ảnh như ảnh chụp màn hình bên dưới, trong đó chỉ có 2 trong số 4 đường dẫn gọi đến load() được hiển thị. Hai đường dẫn này là hai đường dẫn duy nhất đi qua một hàm có chuỗi "baz" trong tên. Việc lọc này sẽ hữu ích khi bạn muốn tập trung vào một phần nhỏ của một chương trình lớn hơn (ví dụ: vì bạn chỉ sở hữu một phần của chương trình đó).

eb1d97491782b03f.png

Mã sử dụng nhiều bộ nhớ

Bây giờ, hãy chuyển sang loại hồ sơ "Heap". Nhớ xoá mọi bộ lọc mà bạn đã tạo trong các thử nghiệm trước đó. Bây giờ, bạn sẽ thấy một biểu đồ dạng ngọn lửa, trong đó allocImpl (do alloc gọi) được hiển thị là thành phần chính tiêu thụ bộ nhớ trong ứng dụng:

f6311c8c841d04c4.png

Bảng tóm tắt phía trên biểu đồ ngọn lửa cho biết tổng dung lượng bộ nhớ đã dùng trong ứng dụng trung bình là ~57,4 MiB, phần lớn được phân bổ bởi hàm allocImpl. Điều này không có gì đáng ngạc nhiên, vì việc triển khai hàm này như sau:

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))
        }
}

Hàm này thực thi một lần, phân bổ 64 MiB thành các khối nhỏ hơn, sau đó lưu trữ con trỏ đến các khối đó trong một biến chung để bảo vệ chúng khỏi bị thu gom rác. Xin lưu ý rằng lượng bộ nhớ mà trình phân tích tài nguyên cho thấy là hơi khác so với 64 MiB: trình phân tích tài nguyên heap Go là một công cụ thống kê, vì vậy, các phép đo có mức hao tổn thấp nhưng không chính xác đến từng byte. Đừng ngạc nhiên khi thấy sự khác biệt khoảng 10% như thế này.

Mã có mức sử dụng I/O cao

Nếu bạn chọn "Luồng" trong bộ chọn loại hồ sơ, màn hình sẽ chuyển sang biểu đồ ngọn lửa, trong đó hầu hết chiều rộng được chiếm bởi các hàm waitwaitImpl:

ebd57fdff01dede9.png

Trong phần tóm tắt phía trên biểu đồ ngọn lửa, bạn có thể thấy có 100 goroutine tăng ngăn xếp lệnh gọi từ hàm wait. Điều này hoàn toàn chính xác, vì mã khởi tạo các lệnh chờ này có dạng như sau:

main.go

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

Loại hồ sơ này hữu ích khi bạn muốn biết liệu chương trình có tốn thời gian chờ đợi không mong muốn hay không (chẳng hạn như I/O). Thông thường, những ngăn xếp lệnh gọi như vậy sẽ không được trình phân tích tài nguyên CPU lấy mẫu vì chúng không tiêu thụ bất kỳ phần đáng kể nào trong thời gian CPU. Bạn thường muốn sử dụng bộ lọc "Hide stacks" (Ẩn ngăn xếp) với hồ sơ Threads (Luồng) – ví dụ: để ẩn tất cả các ngăn xếp kết thúc bằng một lệnh gọi đến gopark, vì đó thường là các goroutine ở trạng thái rảnh và ít thú vị hơn so với các goroutine chờ trên I/O.

Loại hồ sơ luồng cũng có thể giúp xác định những điểm trong chương trình mà các luồng đang chờ một mutex do một phần khác của chương trình sở hữu trong một khoảng thời gian dài, nhưng loại hồ sơ sau đây sẽ hữu ích hơn cho việc đó.

Mã có mức độ tranh chấp cao

Loại hồ sơ Contention (Tranh chấp) xác định những khoá "được yêu cầu" nhiều nhất trong chương trình. Loại hồ sơ này có sẵn cho các chương trình Go nhưng bạn phải bật rõ ràng bằng cách chỉ định "MutexProfiling: true" trong mã cấu hình tác nhân. Hoạt động thu thập này ghi lại (theo chỉ số "Tranh chấp") số lần một khoá cụ thể, khi được mở khoá bởi một goroutine A, có một goroutine B khác đang chờ khoá được mở khoá. Công cụ này cũng ghi lại (trong chỉ số "Độ trễ") thời gian mà goroutine bị chặn đã chờ khoá. Trong ví dụ này, có một ngăn xếp tranh chấp duy nhất và tổng thời gian chờ khoá là 10,5 giây:

83f00dca4a0f768e.png

Mã tạo hồ sơ này bao gồm 4 goroutine tranh giành một 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)
        }
}

6. Tóm tắt

Trong phòng thí nghiệm này, bạn đã tìm hiểu cách định cấu hình một chương trình Go để sử dụng với Cloud Profiler. Bạn cũng đã tìm hiểu cách thu thập, xem và phân tích dữ liệu hiệu suất bằng công cụ này. Giờ đây, bạn có thể áp dụng kỹ năng mới của mình cho các dịch vụ thực mà bạn chạy trên Google Cloud Platform.

7. Xin chúc mừng!

Bạn đã tìm hiểu cách định cấu hình và sử dụng Cloud Profiler!

Tìm hiểu thêm

Giấy phép

Tác phẩm này được cấp phép theo giấy phép Ghi công theo Creative Commons 2.0 Chung.