用DTrace和bpftrace仪器Julia
DTrace和bpftrace是实现流程轻量级检测的工具。 您可以在进程运行时打开和关闭仪器,并且关闭仪器的开销是最小的。
|
兼容性
Julia1.8中增加了对探测的支持 |
|
请注意,本文档是从Linux的角度编写的,其中大部分应该适用于Mac OS/Darwin和FreeBSD。 |
启用支持
在Linux上安装 系统图 有一个版本的软件包 dtrace 并创建一个 做吧。用户 文件包含
WITH_DTRACE=1
以启用USDT探针。
核实资料
> readelf -n usr/lib/libjulia-internal.so.1
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 57161002f35548772a87418d2385c284ceb3ead8
Displaying notes found in: .note.stapsdt
Owner Data size Description
stapsdt 0x00000029 NT_STAPSDT (SystemTap probe descriptors)
Provider: julia
Name: gc__begin
Location: 0x000000000013213e, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cac
Arguments:
stapsdt 0x00000032 NT_STAPSDT (SystemTap probe descriptors)
Provider: julia
Name: gc__stop_the_world
Location: 0x0000000000132144, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cae
Arguments:
stapsdt 0x00000027 NT_STAPSDT (SystemTap probe descriptors)
Provider: julia
Name: gc__end
Location: 0x000000000013214a, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cb0
Arguments:
stapsdt 0x0000002d NT_STAPSDT (SystemTap probe descriptors)
Provider: julia
Name: gc__finalizer
Location: 0x0000000000132150, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cb2
Arguments:
在libjulia中添加探针
探测在文件中以dtraces格式声明 src/uprobe。d. 生成的头文件包含在 src/julia_internal.h 如果你添加了探测,你应该在那里提供一个noop实现。
标头将包含一个信号量 *已启用 以及对探测器的实际调用。 如果探测参数的计算成本很高,则应首先检查探测是否已启用,然后计算参数并调用探测。
if (JL_PROBE_{PROBE}_ENABLED())
auto expensive_arg = ...;
JL_PROBE_{PROBE}(expensive_arg);
如果你的探测器没有参数,最好不包括信号量检查。 启用USDT探针时,信号量的成本是内存负载,无论探针是否启用。
#define JL_PROBE_GC_BEGIN_ENABLED() __builtin_expect (julia_gc__begin_semaphore, 0)
__extension__ extern unsigned short julia_gc__begin_semaphore __attribute__ ((unused)) __attribute__ ((section (".probes")));
而探针本身是一个noop sled,它将被修补到探针处理程序的蹦床上。
可用探针
GC探针
-
朱莉娅:gc__开始:GC开始在一个线程上运行,并触发stop-the-world。 -
朱莉娅:gc__stop_the_world:所有线程都到达安全点,GC运行。 -
julia:gc__mark__begin:开始标记阶段 -
julia:gc__mark_end(scanned_bytes,perm_scanned):标记阶段结束 -
julia:gc__sweep_begin(已满):开始扫荡 -
朱莉娅:gc__sweep_end:扫荡阶段结束 -
朱莉娅:gc__end:GC完成,其他线程继续工作 -
朱莉娅:gc__终结器:Initial GC thread has finished running finalizers
任务运行时探测
-
julia:rt__run__task(任务):切换到任务任务在当前线程上。 -
julia:rt__pause__task(任务):从任务切换任务在当前线程上。 -
julia:rt__new__task(parent,child):任务家长/家长创建的任务儿童在当前线程上。 -
julia:rt__start__task(任务):任务任务第一次用一个新的堆栈开始。 -
julia:rt__finish__task(任务):任务任务完成,将不再执行。 -
julia:rt__start__process__events(任务):任务任务开始处理libuv事件。 -
julia:rt__finish__process__events(任务):任务任务完成处理libuv事件。
任务队列探测
-
julia:rt__taskq__insert(ptls,task):线程[医]ptls试图插入任务成PARTR multiq。 -
julia:rt__taskq__get(ptls,task):线程[医]ptls弹出任务来自PARTR multiq。
线程睡眠/唤醒探针
-
julia:rt__sleep__check__wake(ptls,old_state):螺纹(PTLS[医]ptls)醒来,以前处于状态旧状态. -
julia:rt__sleep__check__wakeup(ptls):螺纹(PTLS[医]ptls)自己醒了。 -
朱莉娅:rt__睡眠__检查__睡眠(ptls):螺纹(PTLS)[医]ptls)正试图入睡。 -
julia:rt__sleep__check__taskq__wake(ptls):螺纹(PTLS)[医]ptls)由于PARTR multiq中的任务导致睡眠失败。 -
julia:rt__sleep__check__task__wake(ptls):螺纹(PTLS)[医]ptls)由于Base workqueue中的任务导致睡眠失败。 -
julia:rt__sleep__check__uv__wake(ptls):螺纹(PTLS)[医]ptls)由于libuv唤醒而无法入睡。
探针使用示例
GC停止世界延迟
一个例子 bpftrace 脚本在给出 contrib/gc_stop_the_world_latency.bt 它创建了所有线程到达安全点的延迟直方图。
运行这个Julia代码, 朱莉娅-t2
using Base.Threads
fib(x) = x <= 1 ? 1 : fib(x-1) + fib(x-2)
beaver = @spawn begin
while true
fib(30)
# A manual safepoint is necessary since otherwise this loop
# may never yield to GC.
GC.safepoint()
end
end
allocator = @spawn begin
while true
zeros(1024)
end
end
wait(allocator)
并在第二终端
> sudo contrib/bpftrace/gc_stop_the_world_latency.bt Attaching 4 probes... Tracing Julia GC Stop-The-World Latency... Hit Ctrl-C to end. ^C @usecs[1743412]: [4, 8) 971 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8, 16) 837 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [16, 32) 129 |@@@@@@ | [32, 64) 10 | | [64, 128) 1 | |
我们可以在执行的Julia进程中看到stop-the-world阶段的延迟分布。
任务生成监视器
知道某个任务何时生成其他任务有时很有用。 这很容易看到 rt__new__任务. 探头的第一个参数, 家长/家长,是正在创建新任务的现有任务。 这意味着,如果您知道要监视的任务的地址,则可以轻松地查看该特定任务产生的任务。 让我们看看如何做到这一点;首先让我们开始一个Julia会话并获取PID和REPL的任务地址:
> julia
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.6.2 (2021-07-14)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org release
|__/ |
1> getpid()
997825
2> current_task()
Task (runnable) @0x00007f524d088010
现在我们可以开始了 bpftrace 并让它监控 rt__new__任务 对于_only_这个父母:
sudo bpftrace-p997825-e’usdt:usr/lib/libjulia-internal.so:julia:rt__new__task/arg0==0x00007f524d088010/{printf("Task:%x\n",arg0);}'
(注意,在上面, arg0 是第一个参数, 家长/家长).
如果我们产生一个任务:
线程。@产卵1+1
我们看到这个任务正在创建:
任务:4d088010
但是,如果我们从新产生的任务中产生了一堆任务:
Threads.@spawn for i in 1:10
Threads.@spawn 1+1
end
我们仍然只看到一个任务 bpftrace:
任务:4d088010
这仍然是我们监控的任务! 当然,我们可以删除这个过滤器来查看_all_新创建的任务:
sudo bpftrace-p997825-e’usdt:usr/lib/libjulia-internal.so:julia:rt__new__task { printf("Task: %x\n", arg0); }'
Task: 4d088010 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290 Task: 4dc4e290
我们可以看到我们的根任务,以及新产生的任务作为十个更新的任务的父级。
雷鸣般的牛群探测
任务运行时通常会受到"雷鸣般的群众性"问题的影响:当一些工作被添加到安静的任务运行时时,所有线程都可能从沉睡中被唤醒,即使没有足够的工作供每个线程处理。 这可能会导致额外的延迟和CPU周期,而所有线程唤醒(并同时回到睡眠状态,没有找到任何工作要执行)。
我们可以看到这个问题说明与 bpftrace 很容易。 首先,在一个终端中,我们用多个线程(本例中为6)启动Julia,并获取该进程的PID:
> julia -t 6
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.6.2 (2021-07-14)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org release
|__/ |
1> getpid()
997825
而在另一个终端,我们开始 bpftrace 监控我们的过程,特别是探测 rt__睡眠__检查__唤醒 钩,钩:
sudo bpftrace-p997825-e’usdt:usr/lib/libjulia-internal.so:julia:rt__sleep__check__wake { printf("Thread wake up! %x\n", arg0); }'
现在,我们在Julia中创建并执行单个任务:
线程。@产卵1+1
而在 bpftrace 我们看到打印出来的东西像:
Thread wake up! 3f926100 Thread wake up! 3ebd5140 Thread wake up! 3f876130 Thread wake up! 3e2711a0 Thread wake up! 3e312190
即使我们只产生了一个任务(一次只有一个线程可以处理),我们唤醒了所有其他线程! 将来,一个更聪明的任务运行时可能只唤醒一个线程(或者根本没有;生成的线程可以执行这个任务!),我们应该看到这种行为消失。
使用须知 bpftrace
Bpftrace格式的示例探针如下所示:
usdt:usr/lib/libjulia-internal.so:julia:gc__begin
{
@start[pid] = nsecs;
}
探测声明采用的是 ust/ust,然后是库的路径或PID,提供者名称 朱莉娅 和探针名称 gc__开始. 请注意,我正在使用一个相对路径到 libjulia-internal.so,但这可能需要是生产系统上的绝对路径。
有用的参考资料:
*Julia Evans blog on Linux tracing systems *关于USDT和BPF的LWN文章 *gdb支持探针 *Brendan Gregg—Linux性能