Tìm hiểu về systrace

systrace là công cụ chính để phân tích hiệu suất của thiết bị Android. Tuy nhiên, đây thực sự là một trình bao bọc xung quanh các công cụ khác. Đây là trình bao bọc phía máy chủ lưu trữ xung quanh atrace, tệp thực thi phía thiết bị kiểm soát hoạt động theo dõi không gian người dùng và thiết lập ftrace, cũng như cơ chế theo dõi chính trong nhân Linux. systrace sử dụng atrace để bật tính năng theo dõi, sau đó đọc bộ đệm ftrace và bao bọc tất cả trong một trình xem HTML độc lập. (Mặc dù các nhân mới hơn có hỗ trợ Bộ lọc gói Berkeley nâng cao (eBPF) của Linux, nhưng trang này liên quan đến nhân 3.18 (không có eFPF) vì đó là nhân được dùng trên Pixel hoặc Pixel XL.)

systrace thuộc sở hữu của nhóm Google Android và Google Chrome, đồng thời là nguồn mở trong dự án Catapult. Ngoài systrace, Catapult còn có các tiện ích hữu ích khác. Ví dụ: ftrace có nhiều tính năng hơn so với những tính năng có thể được systrace hoặc atrace bật trực tiếp và chứa một số chức năng nâng cao quan trọng đối với việc gỡ lỗi các vấn đề về hiệu suất. (Các tính năng này yêu cầu quyền truy cập vào thư mục gốc và thường là một nhân mới.)

Chạy systrace

Khi gỡ lỗi hiện tượng rung trên Pixel hoặc Pixel XL, hãy bắt đầu bằng lệnh sau:

./systrace.py sched freq idle am wm gfx view sync binder_driver irq workq input -b 96000

Khi kết hợp với các điểm theo dõi bổ sung cần thiết cho hoạt động của GPU và quy trình hiển thị, bạn có thể theo dõi từ thông tin đầu vào của người dùng đến khung hình hiển thị trên màn hình. Đặt kích thước vùng đệm thành một giá trị lớn để tránh mất các sự kiện (vì nếu không có vùng đệm lớn, một số CPU sẽ không chứa sự kiện sau một thời điểm nào đó trong dấu vết).

Khi xem xét systrace, hãy nhớ rằng mọi sự kiện đều được kích hoạt bởi một thành phần nào đó trên CPU.

Vì systrace được xây dựng dựa trên ftrace và ftrace chạy trên CPU, nên một thành phần nào đó trên CPU phải ghi vào vùng đệm ftrace để ghi nhật ký các thay đổi về phần cứng. Điều này có nghĩa là nếu tò mò về lý do hàng rào hiển thị thay đổi trạng thái, bạn có thể xem những gì đang chạy trên CPU tại thời điểm chuyển đổi chính xác (một thứ gì đó đang chạy trên CPU đã kích hoạt thay đổi đó trong nhật ký). Khái niệm này là nền tảng để phân tích hiệu suất bằng systrace.

Ví dụ: Khung làm việc

Ví dụ này mô tả một systrace cho quy trình giao diện người dùng thông thường. Để theo dõi ví dụ này, hãy tải tệp zip của các dấu vết xuống (tệp này cũng bao gồm các dấu vết khác được đề cập trong phần này), giải nén tệp và mở tệp systrace_tutorial.html trong trình duyệt.

Đối với một khối lượng công việc nhất quán, định kỳ như TouchLatency, quy trình giao diện người dùng sẽ tuân theo trình tự sau:

  1. EventThread trong SurfaceFlinger đánh thức luồng giao diện người dùng của ứng dụng, báo hiệu rằng đã đến lúc kết xuất một khung hình mới.
  2. Ứng dụng kết xuất một khung hình trong luồng giao diện người dùng, RenderThread và các tác vụ HWUI, bằng cách sử dụng tài nguyên CPU và GPU. Đây là phần lớn dung lượng được dùng cho giao diện người dùng.
  3. Ứng dụng gửi khung hình đã kết xuất đến SurfaceFlinger bằng một liên kết, sau đó SurfaceFlinger chuyển sang chế độ ngủ.
  4. EventThread thứ hai trong SurfaceFlinger đánh thức SurfaceFlinger để kích hoạt thành phần và đầu ra hiển thị. Nếu SurfaceFlinger xác định rằng không có việc gì cần làm, thì nó sẽ quay lại trạng thái ngủ.
  5. SurfaceFlinger xử lý thành phần bằng cách sử dụng Trình kết hợp phần cứng (HWC) hoặc Trình kết hợp phần cứng 2 (HWC2) hoặc GL. Thành phần HWC và HWC2 có tốc độ nhanh hơn và tiêu thụ ít điện năng hơn nhưng có những hạn chế tuỳ thuộc vào hệ thống trên một vi mạch (SoC). Quá trình này thường mất khoảng 4 đến 6 mili giây, nhưng có thể trùng lặp với bước 2 vì các ứng dụng Android luôn được đệm gấp ba. (Mặc dù các ứng dụng luôn có bộ nhớ đệm gấp ba lần, nhưng có thể chỉ có một khung hình đang chờ trong SurfaceFlinger, khiến khung hình này xuất hiện giống hệt như bộ nhớ đệm gấp đôi.)
  6. SurfaceFlinger gửi đầu ra cuối cùng đến màn hình bằng trình điều khiển của nhà cung cấp và chuyển về chế độ ngủ, chờ EventThread đánh thức.

Sau đây là ví dụ về chuỗi khung hình bắt đầu từ 15409 mili giây:

Quy trình giao diện người dùng thông thường có EventThread đang chạy

Hình 1. Quy trình giao diện người dùng thông thường, EventThread đang chạy.

Hình 1 là một khung hình bình thường được bao quanh bởi các khung hình bình thường, vì vậy, đây là một điểm khởi đầu tốt để tìm hiểu cách hoạt động của quy trình giao diện người dùng. Hàng luồng giao diện người dùng cho TouchLatency có nhiều màu sắc ở các thời điểm khác nhau. Các thanh biểu thị các trạng thái khác nhau của luồng:

  • Xám. Ngủ.
  • Xanh dương. Có thể chạy (có thể chạy, nhưng trình lập lịch chưa chọn để chạy).
  • Xanh lục. Đang chạy (trình lập lịch cho rằng nó đang chạy).
  • Đỏ. Giấc ngủ không bị gián đoạn (thường là ngủ trên một khoá trong nhân). Có thể cho biết mức tải I/O. Cực kỳ hữu ích khi gỡ lỗi các vấn đề về hiệu suất.
  • Orange. Chế độ ngủ không bị gián đoạn do tải I/O.

Để xem lý do khiến giấc ngủ không bị gián đoạn (có trong điểm theo dõi sched_blocked_reason), hãy chọn lát giấc ngủ không bị gián đoạn màu đỏ.

Trong khi EventThread đang chạy, luồng giao diện người dùng cho TouchLatency sẽ trở thành luồng có thể chạy. Để xem nguyên nhân khiến thiết bị thức dậy, hãy nhấp vào phần màu xanh dương.

Luồng giao diện người dùng cho TouchLatency

Hình 2. Luồng giao diện người dùng cho TouchLatency.

Hình 2 cho thấy luồng giao diện người dùng TouchLatency được đánh thức bởi tid 6843, tương ứng với EventThread. Luồng giao diện người dùng sẽ kích hoạt, hiển thị một khung hình và đưa khung hình đó vào hàng đợi để SurfaceFlinger sử dụng.

Luồng giao diện người dùng đánh thức, kết xuất một khung hình và đưa khung hình đó vào hàng đợi để SurfaceFlinger sử dụng

Hình 3. Luồng giao diện người dùng đánh thức, kết xuất một khung hình và đưa khung hình đó vào hàng đợi để SurfaceFlinger sử dụng.

Nếu thẻ binder_driver được bật trong một dấu vết, bạn có thể chọn một giao dịch liên kết để xem thông tin về tất cả các quy trình liên quan đến giao dịch đó.

Giao dịch nhị phân

Hình 4. Giao dịch liên kết.

Hình 4 cho thấy rằng, ở 15.423,65 mili giây, Binder:6832_1 trong SurfaceFlinger trở thành có thể chạy do tid 9579, là RenderThread của TouchLatency. Bạn cũng có thể thấy queueBuffer ở cả hai phía của giao dịch liên kết.

Trong queueBuffer ở phía SurfaceFlinger, số lượng khung hình đang chờ xử lý từ TouchLatency tăng từ 1 lên 2.

Khung hình đang chờ xử lý sẽ chuyển từ 1 sang 2

Hình 5. Số khung hình đang chờ xử lý tăng từ 1 lên 2.

Hình 5 cho thấy bộ nhớ đệm gấp ba, trong đó có 2 khung hình đã hoàn tất và ứng dụng sắp bắt đầu kết xuất khung hình thứ ba. Điều này là do chúng ta đã giảm một số khung hình, vì vậy, ứng dụng sẽ giữ lại 2 khung hình đang chờ xử lý thay vì 1 để cố gắng tránh giảm thêm khung hình.

Ngay sau đó, luồng chính của SurfaceFlinger sẽ được EventThread thứ hai đánh thức để có thể xuất khung đang chờ xử lý cũ hơn ra màn hình:

Luồng chính của SurfaceFlinger được đánh thức bởi EventThread thứ hai

Hình 6. Luồng chính của SurfaceFlinger được đánh thức bởi EventThread thứ hai.

Trước tiên, SurfaceFlinger sẽ chốt vùng đệm đang chờ xử lý cũ, khiến số lượng vùng đệm đang chờ xử lý giảm từ 2 xuống 1:

SurfaceFlinger trước tiên sẽ chốt vào vùng đệm đang chờ xử lý cũ hơn

Hình 7. SurfaceFlinger trước tiên sẽ gắn vào bộ đệm đang chờ xử lý cũ hơn.

Sau khi kết nối vùng đệm, SurfaceFlinger sẽ thiết lập thành phần và gửi khung hình cuối cùng đến màn hình. (Một số phần trong số này được bật trong mdsstracepoint, vì vậy, chúng có thể không có trên SoC của bạn.)

SurfaceFlinger thiết lập thành phần và gửi khung hình cuối cùng

Hình 8. SurfaceFlinger thiết lập thành phần và gửi khung hình cuối cùng.

Tiếp theo, mdss_fb0 sẽ hoạt động trên CPU 0. mdss_fb0 là luồng hạt nhân của quy trình hiển thị để xuất một khung hình đã kết xuất ra màn hình. Xin lưu ý rằng mdss_fb0 là hàng riêng trong dấu vết (hãy di chuyển xuống để xem):

mdss_fb0 đánh thức CPU 0

Hình 9. mdss_fb0 đánh thức CPU 0.

mdss_fb0 thức dậy, chạy trong thời gian ngắn, chuyển sang chế độ ngủ không gián đoạn, sau đó thức dậy lần nữa.

Ví dụ: Khung không hoạt động

Ví dụ này mô tả một systrace dùng để gỡ lỗi hiện tượng giật hình trên Pixel hoặc Pixel XL. Để theo dõi ví dụ này, hãy tải tệp zip của các dấu vết xuống (bao gồm cả các dấu vết khác được đề cập trong phần này), giải nén tệp và mở tệp systrace_tutorial.html trong trình duyệt của bạn.

Khi mở systrace, bạn sẽ thấy nội dung tương tự như hình sau:

TouchLatency chạy trên Pixel XL với hầu hết các lựa chọn được bật

Hình 10. TouchLatency chạy trên Pixel XL với hầu hết các lựa chọn được bật.

Trong hình 10, hầu hết các lựa chọn đều được bật, bao gồm cả các điểm theo dõi mdsskgsl.

Khi tìm hiện tượng giật, hãy kiểm tra hàng FrameMissed trong SurfaceFlinger. FrameMissed là một tính năng cải thiện chất lượng cuộc sống do HWC2 cung cấp. Khi xem systrace cho các thiết bị khác, hàng FrameMissed có thể không xuất hiện nếu thiết bị không dùng HWC2. Trong cả hai trường hợp, FrameMissed đều tương quan với SurfaceFlinger bị thiếu một trong các thời gian chạy thông thường và số lượng bộ đệm đang chờ xử lý không thay đổi cho ứng dụng (com.prefabulated.touchlatency) tại một VSync.

Mối tương quan giữa FrameMissed và SurfaceFlinger

Hình 11. Mức độ tương quan giữa FrameMissed và SurfaceFlinger.

Hình 11 cho thấy một khung hình bị bỏ lỡ ở 15598, 29 ms.SurfaceFlinger đã hoạt động trong thời gian ngắn ở khoảng thời gian VSync và quay lại trạng thái ngủ mà không thực hiện bất kỳ thao tác nào. Điều này có nghĩa là SurfaceFlinger xác định rằng không đáng để cố gắng gửi lại khung hình đến màn hình.

Để hiểu lý do khiến quy trình bị gián đoạn đối với khung hình này, trước tiên, hãy xem xét ví dụ về khung hình đang hoạt động ở trên để biết quy trình giao diện người dùng bình thường xuất hiện như thế nào trong systrace. Khi đã sẵn sàng, hãy quay lại khung hình bị bỏ lỡ và làm việc ngược lại. Lưu ý rằng SurfaceFlinger sẽ thức và đi ngủ ngay lập tức. Khi xem số lượng khung hình đang chờ xử lý từ TouchLatency, có 2 khung hình (một manh mối hữu ích giúp xác định điều gì đang xảy ra).

SurfaceFlinger đánh thức và chuyển sang chế độ ngủ ngay lập tức

Hình 12. SurfaceFlinger đánh thức và ngay lập tức chuyển sang chế độ ngủ.

Vì có các khung hình trong SurfaceFlinger nên đây không phải là vấn đề về ứng dụng. Ngoài ra, SurfaceFlinger đang hoạt động vào đúng thời điểm, vì vậy, đây không phải là vấn đề về SurfaceFlinger. Nếu SurfaceFlinger và ứng dụng đều có vẻ bình thường, thì có thể đó là vấn đề về trình điều khiển.

Vì các điểm theo dõi mdsssync được bật, nên bạn có thể nhận thông tin về các hàng rào (được chia sẻ giữa trình điều khiển màn hình và SurfaceFlinger) kiểm soát thời điểm khung hình được gửi đến màn hình. Các hàng rào này được liệt kê trong mdss_fb0_retire, cho biết thời điểm một khung hình xuất hiện trên màn hình. Các hàng rào này được cung cấp trong danh mục dấu vết sync. Hàng rào nào tương ứng với các sự kiện cụ thể trong SurfaceFlinger sẽ tuỳ thuộc vào SOC và ngăn xếp trình điều khiển của bạn. Vì vậy, hãy làm việc với nhà cung cấp SOC để hiểu ý nghĩa của các danh mục hàng rào trong dấu vết của bạn.

mdss_fb0_retire fences

Hình 13. mdss_fb0_retire fences.

Hình 13 cho thấy một khung hình được hiển thị trong 33 mili giây, chứ không phải 16,7 mili giây như dự kiến. Khi được một nửa lát, khung hình đó lẽ ra phải được thay thế bằng một khung hình mới nhưng không được. Xem khung hình trước và tìm kiếm mọi thứ.

Khung hình trước khung hình bị hỏng

Hình 14. Khung hình trước khung hình bị hỏng.

Hình 14 cho thấy một khung hình 14,482 mili giây. Đoạn gồm 2 khung hình bị hỏng là 33,6 mili giây, gần bằng với thời gian dự kiến cho 2 khung hình (kết xuất ở 60 Hz, 16,7 mili giây mỗi khung hình). Nhưng 14,482 mili giây không gần với 16,7 mili giây, cho thấy có vấn đề với quy trình hiển thị.

Hãy tìm hiểu chính xác vị trí kết thúc của hàng rào đó để xác định yếu tố kiểm soát hàng rào:

Điều tra điểm cuối của hàng rào

Hình 15. Điều tra điểm cuối của hàng rào.

Hàng đợi công việc chứa __vsync_retire_work_handler, đang chạy khi hàng rào thay đổi. Khi xem qua nguồn kernel, bạn có thể thấy rằng đây là một phần của trình điều khiển màn hình. Thao tác này có vẻ nằm trên đường dẫn quan trọng cho quy trình hiển thị, vì vậy, thao tác này phải chạy nhanh nhất có thể. Có thể chạy trong khoảng 70 μs (không phải là độ trễ lập lịch dài), nhưng đây là một hàng đợi công việc và có thể không được lập lịch chính xác.

Kiểm tra khung hình trước đó để xác định xem khung hình đó có góp phần gây ra vấn đề này hay không; đôi khi độ rung có thể tích luỹ theo thời gian và cuối cùng gây ra tình trạng trễ thời hạn:

Khung hình trước

Hình 16. Khung hình trước.

Dòng có thể chạy trên kworker không hiển thị vì trình xem chuyển dòng này thành màu trắng khi được chọn, nhưng số liệu thống kê cho thấy: độ trễ của trình lập lịch là 2,3 mili giây đối với một phần của đường dẫn quan trọng trong quy trình hiển thị là không tốt. Trước khi tiếp tục, hãy khắc phục độ trễ bằng cách di chuyển phần đường dẫn quan trọng của quy trình hiển thị này từ hàng đợi công việc (chạy dưới dạng luồng SCHED_OTHERCFS) sang SCHED_FIFOkthread chuyên dụng. Hàm này cần đảm bảo thời gian mà workqueue không thể (và không có ý định) cung cấp.

Không rõ đây có phải là lý do gây ra hiện tượng giật hay không. Ngoài các trường hợp dễ chẩn đoán như tranh chấp khoá nhân khiến các luồng quan trọng đối với màn hình chuyển sang trạng thái ngủ, các dấu vết thường không chỉ rõ vấn đề. Độ trễ này có thể là nguyên nhân gây ra khung hình bị rớt. Thời gian hàng rào phải là 16,7 mili giây, nhưng hoàn toàn không gần với thời gian đó trong các khung hình dẫn đến khung hình bị rớt. Do quy trình hiển thị được liên kết chặt chẽ, nên có thể độ trễ xung quanh thời gian hàng rào dẫn đến khung hình bị sụt.

Trong ví dụ này, giải pháp liên quan đến việc chuyển đổi __vsync_retire_work_handler từ một hàng đợi công việc sang một kthread chuyên dụng. Điều này giúp cải thiện đáng kể hiện tượng rung hình và giảm hiện tượng giật hình trong kiểm thử bóng nảy. Các dấu vết tiếp theo cho thấy thời gian hàng rào gần như ở mức 16,7 ms.