Thiết bị CPython với DTrace và SystemTap¶
- tác giả:
David Malcolm
- tác giả:
Łukasz Langa
DTrace và SystemTap là các công cụ giám sát, mỗi công cụ cung cấp một cách để kiểm tra xem các quy trình trên hệ thống máy tính đang thực hiện những gì. Cả hai đều sử dụng ngôn ngữ dành riêng cho miền cho phép người dùng viết các tập lệnh:
lọc các quy trình sẽ được quan sát
thu thập dữ liệu từ các quá trình quan tâm
tạo báo cáo về dữ liệu
Kể từ Python 3.6, CPython có thể được xây dựng bằng các "điểm đánh dấu" nhúng, còn được gọi là "đầu dò", có thể được quan sát bằng tập lệnh DTrace hoặc SystemTap, giúp giám sát những gì quy trình CPython trên hệ thống đang thực hiện dễ dàng hơn.
Điểm đánh dấu DTrace là chi tiết triển khai của trình thông dịch CPython. Không có đảm bảo nào được đưa ra về khả năng tương thích của đầu dò giữa các phiên bản CPython. Các tập lệnh DTrace có thể ngừng hoạt động hoặc hoạt động không chính xác mà không có cảnh báo khi thay đổi phiên bản CPython.
Kích hoạt các điểm đánh dấu tĩnh¶
macOS có hỗ trợ tích hợp sẵn cho DTrace. Trên Linux, để xây dựng CPython với các điểm đánh dấu được nhúng cho SystemTap, bạn phải cài đặt các công cụ phát triển SystemTap.
Trên máy Linux, việc này có thể được thực hiện thông qua
$ yum cài đặt systemtap-sdt-devel
hoặc:
$ sudo apt-get cài đặt systemtap-sdt-dev
CPython thì phải là configured with the --with-dtrace option:
đang kiểm tra --with-dtrace... vâng
Trên macOS, bạn có thể liệt kê các đầu dò DTrace có sẵn bằng cách chạy quy trình Python ở chế độ nền và liệt kê tất cả các đầu dò do nhà cung cấp Python cung cấp:
$ python3.6 -q &
$ sudo dtrace -l -P python$! # or: dtrace -l -m python3.6
ID PROVIDER MODULE FUNCTION NAME
29564 python18035 python3.6 _PyEval_EvalFrameDefault mục nhập hàm
29565 python18035 python3.6 dtrace_function_entry hàm-entry
29566 python18035 python3.6 _PyEval_EvalFrameDefault hàm trả về
29567 python18035 python3.6 dtrace_function_return hàm-return
29568 python18035 python3.6 thu thập gc-done
29569 python18035 python3.6 thu thập gc-start
29570 python18035 python3.6 _PyEval_EvalFrameDòng mặc định
29571 python18035 python3.6 dòng may_dtrace_line
Trên Linux, bạn có thể xác minh xem các điểm đánh dấu tĩnh SystemTap có trong tệp nhị phân được xây dựng hay không bằng cách xem liệu nó có chứa phần ".note.stapsdt" hay không.
$ readelf -S ./python | grep .note.stapsdt
[30] .note.stapsdt NOTE 00000000000000000 00308d78
Nếu bạn đã xây dựng Python làm thư viện dùng chung (với tùy chọn cấu hình --enable-shared), thay vào đó, bạn cần xem xét bên trong thư viện dùng chung. Ví dụ:
$ readelf -S libpython3.3dm.so.1.0 | grep .note.stapsdt
[29] .note.stapsdt NOTE 00000000000000000 00365b68
Readelf đủ hiện đại có thể in siêu dữ liệu:
$ readelf -n ./python
Hiển thị các ghi chú được tìm thấy ở tệp offset 0x00000254 với độ dài 0x00000020:
Kích thước dữ liệu của chủ sở hữu Mô tả
GNU 0x00000010 NT_GNU_ABI_TAG (thẻ phiên bản ABI)
Hệ điều hành: Linux, ABI: 2.6.32
Hiển thị các ghi chú được tìm thấy ở tệp offset 0x00000274 với độ dài 0x00000024:
Kích thước dữ liệu của chủ sở hữu Mô tả
GNU 0x00000014 NT_GNU_BUILD_ID (chuỗi bit ID bản dựng duy nhất)
ID bản dựng: df924a2b08a7e89f6e11251d4602022977af2670
Hiển thị các ghi chú được tìm thấy tại tệp offset 0x002d6c30 với độ dài 0x00000144:
Kích thước dữ liệu của chủ sở hữu Mô tả
stapsdt 0x00000031 NT_STAPSDT (mô tả thăm dò SystemTap)
Nhà cung cấp: trăn
Tên: gc__start
Vị trí: 0x00000000004371c3, Cơ sở: 0x0000000000630ce2, Semaphore: 0x00000000008d6bf6
Đối số: -4@%ebx
stapsdt 0x00000030 NT_STAPSDT (mô tả thăm dò SystemTap)
Nhà cung cấp: trăn
Tên: gc__done
Vị trí: 0x00000000004374e1, Cơ sở: 0x0000000000630ce2, Semaphore: 0x00000000008d6bf8
Đối số: -8@%rax
stapsdt 0x00000045 NT_STAPSDT (mô tả thăm dò SystemTap)
Nhà cung cấp: trăn
Tên: hàm__entry
Vị trí: 0x000000000053db6c, Cơ sở: 0x0000000000630ce2, Semaphore: 0x00000000008d6be8
Đối số: 8@%rbp 8@%r12 -4@%eax
stapsdt 0x00000046 NT_STAPSDT (mô tả thăm dò SystemTap)
Nhà cung cấp: trăn
Tên: hàm__return
Vị trí: 0x000000000053dba8, Cơ sở: 0x0000000000630ce2, Semaphore: 0x00000000008d6bea
Đối số: 8@%rbp 8@%r12 -4@%eax
Siêu dữ liệu ở trên chứa thông tin cho SystemTap mô tả cách nó có thể vá các hướng dẫn mã máy được đặt ở vị trí chiến lược để kích hoạt các móc theo dõi được sử dụng bởi tập lệnh SystemTap.
Đầu dò DTrace tĩnh¶
Tập lệnh DTrace mẫu sau đây có thể được sử dụng để hiển thị hệ thống phân cấp gọi/trả về của tập lệnh Python, chỉ theo dõi trong lệnh gọi hàm có tên "bắt đầu". Nói cách khác, các lệnh gọi hàm tại thời điểm nhập sẽ không được liệt kê:
tự thụt lề;
python$target:::function-entry
/copyinstr(arg1) == "bắt đầu"/
{
tự-> dấu vết = 1;
}
python$target:::function-entry
/tự->dấu vết/
{
printf("%d\t%*s:", dấu thời gian, 15, tên thăm dò);
printf("%*s", self->thụt lề, "");
printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);
tự-> thụt lề++;
}
python$target:::function-return
/tự->dấu vết/
{
tự-> thụt lề--;
printf("%d\t%*s:", dấu thời gian, 15, tên thăm dò);
printf("%*s", self->thụt lề, "");
printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);
}
python$target:::function-return
/copyinstr(arg1) == "bắt đầu"/
{
tự->dấu vết = 0;
}
Nó có thể được gọi như thế này
$ sudo dtrace -q -s call_stack.d -c "python3.6 script.py"
Đầu ra trông như thế này:
156641360502280 hàm-entry:call_stack.py:start:23
156641360518804 mục nhập hàm: call_stack.py:function_1:1
156641360532797 mục nhập hàm: call_stack.py:function_3:9
156641360546807 hàm trả về: call_stack.py:function_3:10
156641360563367 trả về hàm: call_stack.py:function_1:2
156641360578365 mục nhập hàm: call_stack.py:function_2:5
156641360591757 mục nhập hàm: call_stack.py:function_1:1
156641360605556 mục nhập hàm: call_stack.py:function_3:9
156641360617482 trả về hàm: call_stack.py:function_3:10
156641360629814 hàm trả về: call_stack.py:function_1:2
156641360642285 trả về hàm: call_stack.py:function_2:6
156641360656770 mục nhập hàm: call_stack.py:function_3:9
156641360669707 hàm trả về: call_stack.py:function_3:10
156641360687853 mục nhập hàm: call_stack.py:function_4:13
156641360700719 hàm trả về: call_stack.py:function_4:14
156641360719640 mục nhập hàm: call_stack.py:function_5:18
156641360732567 trả về hàm: call_stack.py:function_5:21
156641360747370 hàm-return:call_stack.py:start:28
Điểm đánh dấu hệ thống tĩnh¶
Cách cấp thấp để sử dụng tích hợp SystemTap là sử dụng trực tiếp các điểm đánh dấu tĩnh. Điều này yêu cầu bạn phải nêu rõ tệp nhị phân chứa chúng.
Ví dụ: tập lệnh SystemTap này có thể được sử dụng để hiển thị hệ thống phân cấp cuộc gọi/trả lại của tập lệnh Python:
quá trình thăm dò ("python"). mark ("function__entry") {
tên tệp = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s => %s trong %s:%d\\n",
thread_indent(1), funcname, filename, lineno);
}
quá trình thăm dò ("python"). mark ("function__return") {
tên tệp = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
printf("%s <= %s in %s:%d\\n",
thread_indent(-1), funcname, filename, lineno);
}
Nó có thể được gọi như thế này
$ dừng lại \
show-call-hierarchy.stp \
-c "./python test.py"
Đầu ra trông như thế này:
11408 python(8274): => __contains__ trong Lib/_abcoll.py:362
11414 trăn(8274): => __getitem__ trong Lib/os.py:425
11418 python(8274): => mã hóa trong Lib/os.py:490
11424 python(8274): <= mã hóa trong Lib/os.py:493
11428 trăn (8274): <= __getitem__ trong Lib/os.py:426
11433 trăn (8274): <= __contains__ trong Lib/_abcoll.py:366
nơi các cột ở:
thời gian tính bằng micro giây kể từ khi bắt đầu tập lệnh
tên thực thi
PID của quá trình
và phần còn lại biểu thị hệ thống phân cấp cuộc gọi/trả lại khi tập lệnh thực thi.
Đối với bản dựng CPython --enable-shared, các điểm đánh dấu được chứa trong thư viện chia sẻ libpython và đường dẫn chấm của đầu dò cần phản ánh điều này. Ví dụ: dòng này từ ví dụ trên:
quá trình thăm dò ("python"). mark ("function__entry") {
thay vào đó nên đọc:
quá trình thăm dò ("python"). thư viện ("libpython3.6dm.so.1.0").mark ("function__entry") {
(giả sử debug build của CPython 3.6)
Điểm đánh dấu tĩnh có sẵn¶
- function__entry(str filename, str funcname, int lineno)
Điểm đánh dấu này cho biết rằng việc thực thi hàm Python đã bắt đầu. Nó chỉ được kích hoạt cho các hàm thuần Python (mã byte).
Tên tệp, tên hàm và số dòng được cung cấp trở lại tập lệnh theo dõi dưới dạng đối số vị trí, phải được truy cập bằng
$arg1,$arg2,$arg3:$arg1: tên tệp(const char *), có thể truy cập bằnguser_string($arg1)$arg2: tên hàm(const char *), có thể truy cập bằnguser_string($arg2)$arg3: số dòngint
- function__return(str filename, str funcname, int lineno)
Điểm đánh dấu này là điểm đảo ngược của
function__entry()và cho biết rằng việc thực thi hàm Python đã kết thúc (thông quareturnhoặc thông qua một ngoại lệ). Nó chỉ được kích hoạt cho các hàm thuần Python (mã byte).Các đối số giống như đối với
function__entry()
- line(str filename, str funcname, int lineno)
Điểm đánh dấu này cho biết một dòng Python sắp được thực thi. Nó tương đương với việc theo dõi từng dòng một bằng trình lược tả Python. Nó không được kích hoạt trong các hàm C.
Các đối số giống như đối với
function__entry().
- gc__start(int generation)
Kích hoạt khi trình thông dịch Python bắt đầu chu trình thu gom rác.
arg0là thế hệ quét, giống nhưgc.collect().
- gc__done(long collected)
Kích hoạt khi trình thông dịch Python kết thúc chu trình thu gom rác.
arg0là số lượng đồ vật được thu thập.
- import__find__load__start(str modulename)
Kích hoạt trước khi
importlibcố gắng tìm và tải mô-đun.arg0là tên mô-đun.Added in version 3.7.
- import__find__load__done(str modulename, int found)
Kích hoạt sau khi hàm find_and_load của
importlibđược gọi.arg0là tên mô-đun,arg1cho biết mô-đun đã được tải thành công hay chưa.Added in version 3.7.
- audit(str event, void *tuple)
Kích hoạt khi
sys.audit()hoặcPySys_Audit()được gọi.arg0là tên sự kiện dưới dạng chuỗi C,arg1là con trỏPyObjecttới một đối tượng tuple.Added in version 3.8.
Điểm vào C¶
Để đơn giản hóa việc kích hoạt các điểm đánh dấu DTrace, C API của Python đi kèm với một số hàm trợ giúp phản ánh từng điểm đánh dấu tĩnh. Trên các bản dựng Python không kích hoạt DTrace, những bản dựng này không có tác dụng gì.
Nói chung, bạn không cần phải tự mình gọi những thứ này vì Python sẽ làm việc đó cho bạn.
Chức năng C API |
Điểm đánh dấu tĩnh |
Ghi chú |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Không được Python sử dụng |
|
|
Không được Python sử dụng |
|
|
Không được Python sử dụng |
|
|
Không được Python sử dụng |
|
|
|
|
|
|
|
|
C Kiểm tra thăm dò¶
-
int PyDTrace_LINE_ENABLED(void)¶
-
int PyDTrace_FUNCTION_ENTRY_ENABLED(void)¶
-
int PyDTrace_FUNCTION_RETURN_ENABLED(void)¶
-
int PyDTrace_GC_START_ENABLED(void)¶
-
int PyDTrace_GC_DONE_ENABLED(void)¶
-
int PyDTrace_INSTANCE_NEW_START_ENABLED(void)¶
-
int PyDTrace_INSTANCE_NEW_DONE_ENABLED(void)¶
-
int PyDTrace_INSTANCE_DELETE_START_ENABLED(void)¶
-
int PyDTrace_INSTANCE_DELETE_DONE_ENABLED(void)¶
-
int PyDTrace_IMPORT_FIND_LOAD_START_ENABLED(void)¶
-
int PyDTrace_IMPORT_FIND_LOAD_DONE_ENABLED(void)¶
-
int PyDTrace_AUDIT_ENABLED(void)¶
Tất cả các lệnh gọi đến hàm
PyDTracephải được bảo vệ bằng lệnh gọi đến một trong các hàm này. Điều này cho phép Python giảm thiểu tác động đến hiệu suất khi việc thăm dò bị vô hiệu hóa.Trên các bản dựng không bật DTrace, các hàm này không làm gì và trả về
0.
Bộ vòi SystemTap¶
Cách cấp cao hơn để sử dụng tích hợp SystemTap là sử dụng "tapset": Thư viện tương đương của SystemTap, thư viện này ẩn một số chi tiết cấp thấp hơn của điểm đánh dấu tĩnh.
Đây là tệp tapset, dựa trên bản dựng CPython không được chia sẻ:
/*
Cung cấp gói cấp độ cao hơn xung quanh hàm__entry và
chức năng__điểm đánh dấu trả về:
\*/
thăm dò python.function.entry = process("python").mark("function__entry")
{
tên tệp = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
khungptr = $arg4
}
thăm dò python.function.return = process("python").mark("function__return")
{
tên tệp = user_string($arg1);
funcname = user_string($arg2);
lineno = $arg3;
khungptr = $arg4
}
Nếu tệp này được cài đặt trong thư mục tapset của SystemTap (ví dụ: /usr/share/systemtap/tapset), thì các điểm thăm dò bổ sung này sẽ khả dụng:
- python.function.entry(str filename, str funcname, int lineno, frameptr)
Điểm thăm dò này cho biết rằng việc thực thi hàm Python đã bắt đầu. Nó chỉ được kích hoạt cho các hàm thuần Python (mã byte).
- python.function.return(str filename, str funcname, int lineno, frameptr)
Điểm thăm dò này là điểm đảo ngược của
python.function.returnvà cho biết rằng việc thực thi hàm Python đã kết thúc (thông quareturnhoặc thông qua một ngoại lệ). Nó chỉ được kích hoạt cho các hàm thuần Python (mã byte).
Ví dụ¶
Tập lệnh SystemTap này sử dụng tapset ở trên để triển khai rõ ràng hơn ví dụ được đưa ra ở trên về việc theo dõi hệ thống phân cấp lệnh gọi hàm Python mà không cần đặt tên trực tiếp cho các điểm đánh dấu tĩnh:
thăm dò python.function.entry
{
printf("%s => %s trong %s:%d\n",
thread_indent(1), funcname, filename, lineno);
}
thăm dò python.function.return
{
printf("%s <= %s in %s:%d\n",
thread_indent(-1), funcname, filename, lineno);
}
Tập lệnh sau sử dụng tapset ở trên để cung cấp chế độ xem giống như trên cùng của tất cả mã CPython đang chạy, hiển thị 20 khung mã byte được nhập thường xuyên nhất, mỗi giây, trên toàn bộ hệ thống:
fn_calls toàn cầu;
thăm dò python.function.entry
{
fn_calls[pid(), tên tệp, funcname, lineno] += 1;
}
thăm dò bộ đếm thời gian.ms(1000) {
printf("\033[2J\033[1;1H") /* xóa màn hình \*/
printf("%6s %80s %6s %30s %6s\n",
"PID", "FILENAME", "LINE", "FUNCTION", "CALLS")
foreach ([pid, filename, funcname, lineno] trong fn_calls- limit 20) {
printf("%6d %80s %6d %30s %6d\n",
pid, tên tệp, lineno, funcname,
fn_calls[pid, tên tệp, funcname, lineno]);
}
xóa fn_calls;
}