gdb(ptrace)+ gdb_bthread_stack.py主要的缺点是要慢和阻塞进程,需要一种高效的追踪bthread调用栈的方法。
bRPC框架的协作式用户态协程无法像Golang内建的抢占式协程一样实现高效的STW(Stop the World),框架也无法干预用户逻辑的执行,所以要追踪bthread调用栈是比较困难的。
在线追踪bthread调用栈需要解决以下问题:
以下是目前的bthread状态模型。
为了解决上述两个问题,该方案实现了STB(Stop The Bthread),核心思路可以简单总结为,在追踪bthread调用栈的过程中,状态不能流转到当前追踪方法不支持的状态。STB包含了两种追踪模式:上下文(context)追踪模式和信号追踪模式。
上下文追踪模式可以追踪挂起bthread的调用栈。挂起的bthread栈是稳定的,利用TaskMeta.stack中保存的上下文信息(x86_64下关键的寄存器主要是RIP、RSP、RBP),通过一些可以回溯指定上下文调用栈的库来追踪bthread调用栈。但是挂起的bthread随时可能会被唤醒,执行逻辑(包括jump_stack),则bthread栈会一直变化。不稳定的上下文是不能用来追踪调用栈的,需要在jump_stack前拦截bthread的调度,等到调用栈追踪完成后才继续运行bthread。所以,上下文追踪模式支持就绪、挂起这两个状态。
信号追踪模式可以追踪运行中bthread的调用栈。运行中bthread是不稳定的,不能使用TaskMeta.stack来追踪bthread调用栈。只能另辟蹊径,使用信号中断bthread运行逻辑,在信号处理函数中回溯bthread调用栈。使用信号有两个问题:
所以,追踪模式只支持运行状态。
jump_stack是bthread挂起或者运行的必经之路,也是STB的拦截点。STB将状态分成三类:
以下是引入STB后的bthread状态模型,在原来bthread状态模型的基础上,加入两个状态(拦截点):将运行、挂起中。
经过上述分析,总结出STB的流程:
--with-bthread-tracer
选项或者给cmake增加-DWITH_BTHREAD_TRACER=ON
选项或者给bazel(Bzlmod模式)增加--define with_bthread_tracer=true
选项。http://ip:port/bthreads/<bthread_id>?st=1
或者代码里调用bthread::stack_trace()
函数。bthread::init_for_pthread_stack_trace()
函数获取一个伪bthread_t,然后使用步骤3即可获取pthread调用栈。下面是追踪bthread调用栈的输出示例:
#0 0x00007fdbbed500b5 __clock_gettime_2 #1 0x000000000041f2b6 butil::cpuwide_time_ns() #2 0x000000000041f289 butil::cpuwide_time_us() #3 0x000000000041f1b9 butil::EveryManyUS::operator bool() #4 0x0000000000413289 (anonymous namespace)::spin_and_log() #5 0x00007fdbbfa58dc0 bthread::TaskGroup::task_runner()
signal_trace_timeout_ms
:信号追踪模式的超时时间,默认为50ms。