**利用systemtap分析ceph源码**
一:安装systemtap
yum install systemtap
[root@twozero iso_map]# rpm -qa | grep systemtap
systemtap-2.8-10.el7.x86_64
二:安装ceph和对应的debuginfo
安装ceph-debuginfo(必须和所安装的ceph版本一致),yum install ceph-debuginfo
方法1:
yum install ceph ceph-debuginfo
ceph-0.94.3-69.g0f85a5c.el7.centos.x86_64 ceph-debuginfo-0.94.3-69.g0f85a5c.el7.centos.x86_64
方法2:
浏览器登录http://192.168.9.17/pub/unis7.1/os/Packages/ceph-0.94.3/下载对应的安装包
方法3:自己编译ceph源码成rpm包,并安装,编译ceph源码的rpm文档地址如下:
http://192.168.9.55:8090/pages/viewpage.action?pageId=3538955
二:分析ceph源码
场景1:
ceph中刚刚进入reader线程时候,有如下几行代码
if (state == STATE_ACCEPTING) {
accept();
assert(pipe_lock.is_locked());
}
根据state变量的值来决定是否执行accept函数,经过阅读代码可以猜测知道accept函数是用来对对方(一次连接的客户端)进行一些认证之类的操作,所以利用systemtap打印state的值,来确认我们的结论:
(1) 查询在该处systemtap能否可以使用state变量
[root@twozero systemtap]# stap -L ‘process("/usr/bin/ceph-osd").function(“reader”)’
process("/usr/bin/ceph-osd").function(“reader@msg/simple/Pipe.cc:1491”) $this:class Pipe* const
(2) 编写systemtap脚本
#! /usr/bin/stap
probe process("/usr/bin/ceph-osd").function(“reader@msg/simple/Pipe.cc”)
{
printf(“state=%d\n”, $this->state);
}
(3) 运行脚本(因为进入reader线程是发生在tcp连接后,所以只有当有osd启动时才进入)
[root@twozero systemtap]# stap test1.stp
(4) 启动本端osd启动,输出如下
state=2
state=2
state=2
state=2
state=2
state=0
state=0
state=0
state=0
state=2
state=0
state=0
/*
0:STATE_ACCEPTING
2:STATE_OPEN
*/
可以知道进入reader线程时,state有两个值,一个是STATE_ACCEPTING,一个是STATE_OPEN,在,state的值在add_accept_pipe时被初始化为STATE_ACCEPTING(osd接收连接后调用),state在accept中如下几行
assert(state == STATE_ACCEPTING);
state = STATE_OPEN
而查找STATE_OPEN后,发现connect中state 被设置为STATE_OPEN,所以,可以知道当本端osd作为客户端连接到其他osd时,不运行accept,这更加确认了我们的猜想。
场景2
查询线程池中的任务数,在ThreadPool中有processing变量,该变量在线程池中每一个线程中开始处理消息的时候增加1,结束时减少1,所以可以根据该变量来判断当前线程池中的任务数。该部分的源码如下:
我们将探针放入wq->void_process(item, tp_handle)处
(1)查询该处processing变量是否可用
[root@oneeight systemtap]# stap -L ‘process("/usr/bin/ceph-osd").statement(“worker@common/WorkQueue.cc:128”)’
process("/usr/bin/ceph-osd").statement(“worker@common/WorkQueue.cc:128”) $tp_handle:class TPHandle $item:void* $wq:struct WorkQueue* $ss:stringstream $hb:struct heartbeat_handle_d* $this:class ThreadPool* const $wt:struct WorkThread*
因为processing变量属于this,所以可用。
(2)编写脚本
[root@oneeight systemtap]# cat test_4.stp
probe process("/usr/bin/ceph-osd").statement(“worker@common/WorkQueue.cc:125”)
{
printf("%s %d\n", ctime(gettimeofday_s()), $this->processing);
}
(3)运行(为了检测到数据,在脚本运行时客户端进行写操作)
stap test_4.stp
(5) 输出
Wed Oct 19 05:42:21 2016 1
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 1
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 1
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 1
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:21 2016 2
Wed Oct 19 05:42:22 2016 1
Wed Oct 19 05:42:23 2016 1
Wed Oct 19 05:42:23 2016 1
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 2
Wed Oct 19 05:42:23 2016 1
Wed Oct 19 05:42:23 2016 2
最后一列表示线程池中的任务数。
场景3
分析osd的数据处理流程
阅读源码时,可以按照流程很容易的知道是reader线程是osd中从网络读数据流程,并且也容易知道程序走到reader线程的代码流,当知道pipe模块是负责osd底层数据通信的时,就可以看到在该部分还有一个writer线程,该线程负责底层数据通信的写线程,但是很难知道程序进入该线程的流程(向网络写数据的流程)。
分析writer线程可以知道该线程是从out_q中取出消息,submit_message函数将消息插入到out_q中,所以打印submit_message函数的堆栈来分析调用过程。
(1) 编写systemtap脚本
[root@oneeight systemtap]# cat test_1.stp
probe process("/usr/bin/ceph-osd").function(“submit_message@msg/simple/SimpleMessenger.cc”)
{
printf("%s\n", print_ubacktrace());
}
(2) stap -d /usr/lib64/libpthread-2.17.so --ldd test_1.stp
(3) /*-d用来加载用到的库,-ldd用来利用-d加载的库来尽可能多的输出详细信息,如果不知道需要加载什么库,直接执行stap test_1.stp,systemtap会提示需要加载的库
*/
(4) 输出(输出的堆栈信息有多种)
0xbca450 : _ZN15SimpleMessenger14submit_messageEP7MessageP14PipeConnectionRK13entity_addr_tib+0x0/0x16d0 [/usr/bin/ceph-osd]
0xbcc3ea : _ZN15SimpleMessenger13_send_messageEP7MessageP10Connection+0xaa/0x360 [/usr/bin/ceph-osd]
0x6a4826 : _ZN10OSDService24send_message_osd_clusterEiP7Messagej+0x226/0x320 [/usr/bin/ceph-osd]
0xa25c2e : _ZN17ReplicatedBackend8issue_opERK9hobject_tRK10eversion_tm11osd_reqid_tS3_S3_S0_S0_RKSt6vectorI14pg_log_entry_tSaIS8_EERN5boost8optionalI20pg_hit_set_history_tEEPNS_12InProgressOpEPN11ObjectStore11TransactionE+0x43e/0x6d0 [/usr/bin/ceph-osd]
0xa28055 : _ZN17ReplicatedBackend18submit_transactionERK9hobject_tRK10eversion_tPN9PGBackend13PGTransactionES5_S5_RKSt6vectorI14pg_log_entry_tSaISA_EERN5boost8optionalI20pg_hit_set_history_tEEP7ContextSL_SL_m11osd_reqid_tNSt3tr110shared_ptrI9OpRequestEE+0x695/0xf40 [/usr/bin/ceph-osd]
0x86501a : _ZN12ReplicatedPG11issue_repopEPNS_9RepGatherE+0x7ba/0xb50 [/usr/bin/ceph-osd]
0x8b4e8f : _ZN12ReplicatedPG11execute_ctxEPNS_9OpContextE+0x108f/0x1dd0 [/usr/bin/ceph-osd]
0x8ba1a7 : _ZN12ReplicatedPG5do_opERNSt3tr110shared_ptrI9OpRequestEE+0x45d7/0x4960 [/usr/bin/ceph-osd]
0x8524ba : _ZN12ReplicatedPG10do_requestERNSt3tr110shared_ptrI9OpRequestEERN10ThreadPool8TPHandleE+0x68a/0x9f0 [/usr/bin/ceph-osd]
0x6a3665 : _ZN3OSD10dequeue_opEN5boost13intrusive_ptrI2PGEENSt3tr110shared_ptrI9OpRequestEERN10ThreadPool8TPHandleE+0x405/0x640 [/usr/bin/ceph-osd]
0x6a3bd0 : _ZN3OSD11ShardedOpWQ8_processEjPN4ceph18heartbeat_handle_dE+0x330/0x940 [/usr/bin/ceph-osd]
0xbde4bf : _ZN17ShardedThreadPool24shardedthreadpool_workerEj+0x86f/0xec0 [/usr/bin/ceph-osd]
0xbe05f0 : _ZN17ShardedThreadPool17WorkThreadSharded5entryEv+0x10/0x20 [/usr/bin/ceph-osd]
0x7f232af42dc5 : start_thread+0xc5/0x300 [/usr/lib64/libpthread-2.17.so]
0x7f2329a2323d : clone+0x6d/0x90 [/usr/lib64/libc-2.17.so]
根据打印出的堆栈信息并结合源码可以大致获得如下函数调用关系
start_thread
shardedthreadpool_worker-> entry
shardedthreadpool_worker
_process
ShardedOpWQ:: _process
dequeue_op
ReplicatedPG ::do_request
ReplicatedPG::do_op
execute_ctx
issue_repop
ReplicatedBackend:: submit_transaction
issue_op
OSDService ::send_message_osd_cluster
_send_message
submit_message
利用该调用关系查看源码,可以发现在ShardedOpWQ::_process函数中有对shard_list的操作(最明显的是有while循环和sdata->sdata_cond.WaitInterval等待其他线程唤醒),并且该部分处于一个独立的线程中,所以猜测有其它线程将要发送的消息存储在shard_list中,在源码中搜索shard_list变量,发现在void OSD::ShardedOpWQ::_enqueue函数中有对shard_list的入队操作,利用systemtap查看_enqueue的堆栈
(5) 获取_enqueue的精确位置
[root@oneeight systemtap]# stap -L ‘process("/usr/bin/ceph-osd").function("_enqueue")’
process("/usr/bin/ceph-osd").function("_enqueue@common/WorkQueue.h:405") $this:class GenContextWQ* const $c:class GenContextThreadPool::TPHandle&*
process("/usr/bin/ceph-osd").function("_enqueue@os/FileStore.h:341") $this:struct OpWQ* const $osr:class OpSequencer*
process("/usr/bin/ceph-osd").function("_enqueue@os/KeyValueStore.h:436") $this:struct OpWQ* const $osr:class OpSequencer*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.cc:8336") $this:class ShardedOpWQ* const $item:struct pair<boost::intrusive_ptr, std::tr1::shared_ptr >
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.cc:8740") $this:struct RecoveryWQ* const $pg:class PG*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:1582") $this:struct PeeringWQ* const $pg:class PG*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:1953") $this:struct CommandWQ* const $c:struct Command*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:2065") $this:struct SnapTrimWQ* const $pg:class PG*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:2111") $this:struct ScrubWQ* const $pg:class PG*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:2158") $this:struct RepScrubWQ* const $msg:struct MOSDRepScrub*
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:2213") $this:struct RemoveWQ* const $item:struct pair<boost::intrusive_ptr, std::tr1::shared_ptr >
process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.h:578") $this:class OSDService* const $pg:class PG* $priority:uint64_t
可以看到有许多地方定义了_enqueue函数,和源码对比后,_enqueue的精确位置在_enqueue@osd/OSD.cc:8336处。
(6) 编写systemtap脚本
[root@oneeight systemtap]# cat test_2.stp
probe begin
{
printf(“start\n”);
}
probe process("/usr/bin/ceph-osd").function("_enqueue@osd/OSD.cc:8336")
{
printf("%s\n", print_ubacktrace());
}
(7)运行脚本 stap
stap -d /usr/lib64/libpthread-2.17.so --ldd test_2.stp
(8)结果输出
0x6c9e70 : _ZN3OSD11ShardedOpWQ8_enqueueESt4pairIN5boost13intrusive_ptrI2PGEENSt3tr110shared_ptrI9OpRequestEEE+0x0/0x770 [/usr/bin/ceph-osd]
0x7cebbd : _ZN2PG8queue_opERNSt3tr110shared_ptrI9OpRequestEE+0x1ed/0x3e0 [/usr/bin/ceph-osd]
0x6769c5 : _ZN3OSD10enqueue_opEP2PGRNSt3tr110shared_ptrI9OpRequestEE+0x235/0x2f0 [/usr/bin/ceph-osd]
0x6b5a5c : _ZN3OSD9handle_opERNSt3tr110shared_ptrI9OpRequestEERNS1_IK6OSDMapEE+0xfec/0x1910 [/usr/bin/ceph-osd]
0x6b652e : _ZN3OSD16dispatch_op_fastERNSt3tr110shared_ptrI9OpRequestEERNS1_IK6OSDMapEE+0x1ae/0x3d0 [/usr/bin/ceph-osd]
0x6b67e8 : _ZN3OSD24dispatch_session_waitingEPNS_7SessionENSt3tr110shared_ptrIK6OSDMapEE+0x98/0x220 [/usr/bin/ceph-osd]
0x6b6bee : _ZN3OSD16ms_fast_dispatchEP7Message+0x27e/0x4c0 [/usr/bin/ceph-osd]
0xca59d6 : _ZN13DispatchQueue13fast_dispatchEP7Message+0x56/0x90 [/usr/bin/ceph-osd]
0xcd411a : _ZN4Pipe6readerEv+0x1c6a/0x2440 [/usr/bin/ceph-osd]
0xcd6bcd : _ZN4Pipe6Reader5entryEv+0xd/0x20 [/usr/bin/ceph-osd]
0x7f232af42dc5 : start_thread+0xc5/0x300 [/usr/lib64/libpthread-2.17.so]
0x7f2329a2323d : clone+0x6d/0x90 [/usr/lib64/libc-2.17.so]
结合源码可以看到有如下函数执行流程
start_thread
Reader::entry
Pipe::reader
DispatchQueue
ms_fast_dispatch
dispatch_session_waiting
dispatch_op_fast
handle_op
enqueue_op
queue_op
ShardedOpWQ:: _enqueue
输出的堆栈信息有许多种,除了handle_op不同以外,其它都一样,查看代码可以知道在dispatch_op_fast中是根据消息的类型来选择不同的操作,该处是处理client ops,所以调用handle_op,但是每个函数处理完消息后都会调用enqueue_op来将发送的回应消息入队。
(9)结论
经过使用两次systemtap跟踪关键函数的堆栈信息可以得出osd数据处理流程如下:
Reader::entry
Pipe::reader
DispatchQueue
ms_fast_dispatch
dispatch_session_waiting
dispatch_op_fast
handle_op
enqueue_op
queue_op
ShardedOpWQ:: _enqueue
start_thread
shardedthreadpool_worker-> entry
shardedthreadpool_worker
_process
ShardedOpWQ:: _process
dequeue_op
ReplicatedPG ::do_request
ReplicatedPG::do_op
execute_ctx
issue_repop
ReplicatedBackend:: submit_transaction
issue_op
OSDService ::send_message_osd_cluster
_send_message
submit_message
reader线程负责读取消息,并将消息一层层在osd内部封装和转换,最终到dispatch_op_fast来判断采取哪一种操作并调用enqueue_op进而调用ShardedOpWQ:: _enqueue来将消息发送给WorkThreadSharded线程池去处理,该线程池中一个线程会接收传过来的消息,并采取真正的操作do_op去处理,然后submit_message将回应消息发送给writer线程,writer线程将消息发送给连接的对应一端。
场景四
直接打印程序的代码流
因为ceph过于庞大,因此代码流只能选择的去打印,所以要求对代码有一些了解后,知道需要打印的范围和函数所属的类或者文件就比较容易了。
因为systemtap的thread_indent不能跨线程调用,所以thread_indent的规则缩进只能在一个线程里,并且有时候打印不太规则(所以需要结合代码去阅读)。假设我们需要知道ceph从ms_fast_dispatch到do_request的代码执行流程
(1) 编写脚本
[root@oneeight systemtap]# cat test_3.stp
#! /usr/bin/stap
global tg_flags;
global only_once_1=0;
global only_once_2=0;
probe begin
{
printf(“start\n”);
}
probe process("/usr/bin/ceph-osd").function(“ms_fast_dispatch@osd/OSD.cc:5454”)
{
if(only_once_1==0)
{
tg_flags[tid()]=1;
only_once_1=1;
}
printf("%s -> %s\n", thread_indent(2), ppfunc());
}
probe process("/usr/bin/ceph-osd").function(“do_request@osd/ReplicatedPG.cc:1241”)
{
if(only_once_2==0)
{
tg_flags[tid()]=1;
only_once_2=1;
}
printf("%s -> %s\n", thread_indent(2), ppfunc());
}
probe process("/usr/bin/ceph-osd").function(“SimpleMessenger::"),
process("/usr/bin/ceph-osd").function("Dispatch::"),
process("/usr/bin/ceph-osd").function("Pipe::”),
process("/usr/bin/ceph-osd").function("@osd/OSD."),
process("/usr/bin/ceph-osd").function("@common/WorkQueue."),
process("/usr/bin/ceph-osd").function(“Backend::”),
process("/usr/bin/ceph-osd").function(“PG::"),
process("/usr/bin/ceph-osd").function("ReplicatedPG::”)
{
if (tg_flags[tid()]==1) {
printf("%s -> %s\n", thread_indent(2), ppfunc());
}
}
probe process("/usr/bin/ceph-osd").function(“SimpleMessenger::").return,
process("/usr/bin/ceph-osd").function("Dispatcher::").return,
process("/usr/bin/ceph-osd").function("Pipe::”).return,
process("/usr/bin/ceph-osd").function("@osd/OSD.").return,
process("/usr/bin/ceph-osd").function("@common/WorkQueue.").return,
process("/usr/bin/ceph-osd").function(“Backend::”).return,
process("/usr/bin/ceph-osd").function(“PG::").return,
process("/usr/bin/ceph-osd").function("ReplicatedPG::”).return
{
if (tg_flags[tid()]==1) {
printf("%s <- %s\n", thread_indent(-2), ppfunc());
}
}
脚本将探针插入到了可能需要的类或者文件中,而且脚本限定了只打印两个线程的代码流。
*@common/WorkQueue.表示跟踪WorkQueue.c和WorkQueue.h中的每一个函数,"ReplicatedPG::"表示跟踪ReplicatedPG类的每一个函数。因为WorkQueue.h中有多个类所以指定文件较为方便。
(二)运行脚本
stap test_3.stp > mytrace1
(三)输出结果
因为输出太多,所以将输出重定向到文件中,文件地址在192.168.15.218: /root/systemtap/ mytrace1。
(6) 分析
从输出可以看到,systemtap输出不同线程之间的缩进是不一样的,而且有的函数重复打印。
从第2行开始到第79行,打印了线程24429中ms_fast_dispatch函数调用关系,从第85行到351行打印了线程20403中do_request函数的调用关系,因为里面有重复打印和不规则的打印,所以只能结合源码去分析。