大数跨境
0
0

可观测性利器--USDT用户态静态追踪详解(一)

可观测性利器--USDT用户态静态追踪详解(一) IT知识刺客
2025-11-09
1
导读:Userland Statically Defined Tracing,简称 USDT,用户态静态追踪,是eBPF/systemtap/Dtrace等的基础,本文主要介绍USDT的基础技术,更详细的将
周未,本来不想写技术,但第八届中国PostgresSQL数据库生态大会召开在即,还是写一篇关于可观测性的新技术吧:Userland Statically Defined Tracing,简称 USDT,用户态静态追踪。
先介绍下PG生态大会和中国开源软件推进联盟PostgreSQL分会,
中国开源软件推进联盟PostgreSQL分会,是目前国内最为活跃的数据库开源社区,自成立以为,为PG在国内的推广,作了很多工作。这是他们的官网:https://www.postgresqlchina.com/
中国PostgresSQL数据库生态大会,作为中国开源软件推进联盟PostgreSQL分会 的年度大型活动,已经举办了 7 届。前几年都在北京中科院软件研究所举办:
去年在上海国际饭店,今年来到了杭州西溪湿地畔:
今年以“开源无界 探索无限可能”为主题。形式上延续去年风格,主题演讲 + 公众研讨会。
主题演讲是业内专家名宿对某一个话题的深度探讨。公众研讨会则是PG生态爱好者发表想法、碰撞思想的舞台。
今年我受邀下午一场公众研讨会的主持人:“源于PG,胜于PG?-- 国产数据库功能创新思辩”,有想表达想法的朋友,可以公众号内私信,也可以直接微信上找我。
好,回来聊技术,USDT,用户态静态追踪,到底是个啥东西?
先说它主要针对的场景:可观测性。
为了增加可观测性,我们时常要在代码中加入如下的片段:
type func_XXX(...){    code_row1();    ......    if ( 打开可观测性 )    {         记录数据到某处 或 输出trace信息;         ......    }    ......     code_rowN();    return type;}
示例 1
第 6 至 10行的 if,就是专为可观测性准备的,相当于为了可观测性在程序中增加的“观测点”。
为了不影响性能,可以少打开一些“观测点”,甚至不打开任何“观测点”。等有需要时,再打开更多的“观测点”。
但是,一个观测点,那怕不打开,if()中的条件那怕不满足,也会影响性能。
我做过测试分析,程序中每多出5%的 if 语句,那怕任何情况下条件都不满足,性能的下降也在5% ~ 10%间。
if() 对性能的影响是很大的。如果真像“示例1”那样,在代码中增加很多"观测点“,性能必然打大折扣。
怎么办?
USDT,用户态静态追踪,就是专门解决这个问题的。
而我们,有比USDT更好的方式增加观测点。我们称之为halo watch,中文名字:谛听。
先说USDT吧,它和gcc等编译器协作,让编译器在代码中植入“探针”。这个探针,就是观测点。
比如,在PG开始执行SQL处,埋入一个“query_start"探针:
1067 static void1068 standard_exec_simple_query(const char *query_string)1069 { 。。。。。。。1089         tsc1 = rdtscp();1090         TRACE_POSTGRESQL_QUERY_START((char *)tsc1);1091         tsc2 = rdtscp();1092         elog(INFO, "TSC1:%ldTSC2:%ld\n", tsc1, tsc2);
示例 2
1090行就是探针:
       “TRACE_POSTGRESQL_QUERY_START((char *)tsc1);
前面红色字体的,是USDT的要求,后面黑色字体的,是探针的名字和参数。
然后,编译时需增加一个选项,--enable-dtrace:
./configure --enable-dtrace 。。。make && make install

示例3

gcc会在编译后的可执行文件中,埋入“探针”。

你是否好奇这个探针长啥样啊?

反汇编一下就能看到: 

standard_exec_simple_query()中的 query start探针:   0x00000000009a6ec0 <+512>:   callq  0x9c3540 <pgstat_report_activity>=> 0x00000000009a6ec5 <+517>:   rdtscp    0x00000000009a6ec8 <+520>:   shl    $0x20,%rdx   0x00000000009a6ecc <+524>:   mov    %eax,%eax   0x00000000009a6ece <+526>:   mov    %rdx,%rbx   0x00000000009a6ed1 <+529>:   or     %rax,%rbx   0x00000000009a6ed4 <+532>:   nop   0x00000000009a6ed5 <+533>:   rdtscp    0x00000000009a6ed8 <+536>:   xor    %esi,%esi   0x00000000009a6eda <+538>:   mov    $0x11,%edi   0x00000000009a6edf <+543>:   mov    %edx,%ebp   0x00000000009a6ee1 <+545>:   mov    %eax,%r12d   0x00000000009a6ee4 <+548>:   callq  0xada2d0 <errstart>

示例4

注意,在第8行“0x00000000009a6ed4 <+532>”处,有一个nop指令。这条nop指令,就是探针。

这条nop指令,只有1个字节。CPU在执行这种空指令时,并不会让它走完指令全部的流水线,在指令译码阶段,CPU发现:“哦,这是一条空指令”,然后就把它凉一边不管它了。

简而言之,CPU执行nop指令完全不费时间。

注意,我这里连“几乎”都没加,我没有用这样的语言描述:“CPU执行nop指令几乎完全不费时间。”

加个几乎吗,给自己留点余地,万一真的占了点时间呢?

你可以放宽心,我研究体系结构有好些年了,我可以负责任的告诉你,nop完全不占时间。

这是因为CPU一个周期可以执行4条以上指令,相当于4车道以上的宽度公路。

但对于数据库这样逻辑复杂的软件,根本不可能同时有4辆车跑在公路上。反正公路上有空闲,nop这样的空指令,在同一周期和其他指令一起就被执行了,CPU不必为nop专门多用一个周期。

因此,nop指令的执行,不会额外多占一个周期。

如“示例4”展示的,USDT在程序中埋入的探针,就是这种一字节的nop空指令。既然nop执行不占用CPU时间,USDT的探针(观测点)也不占时间(不激活时不占时间)。

也就是说,你可以在代码中植入任意数量的探针(观测点),而不会对性能有负面影响。

当然,激活探针后,对性能还是有影响的。不过,对于正常运行的程序,多数情况下不需要激探针。eBPF、systemtap、DTtrace等跟踪工具,都是这样的工作方式,不激活时,它们的探针都是在程序内的nop指令。

这样的探针怎样激活呢?

这条一字节的nop,怎么就能变成我想执行的可观测性代码呢?

这个问题,我们到下一篇中详细描述。还有,我们在PG中打造的更好的可观性功能:谛听,都在下一篇中揭晓。

更详细的,我会在29号PG生态大会中“源于PG、胜于PG”研讨会中讨论:

【声明】内容源于网络
0
0
IT知识刺客
基础软件开发 HPC(高性能计算) HPC数据库研发 数据库内核 向量计算 计算机体系结构 数据库 DBA CPU原理
内容 83
粉丝 0
IT知识刺客 基础软件开发 HPC(高性能计算) HPC数据库研发 数据库内核 向量计算 计算机体系结构 数据库 DBA CPU原理
总阅读36
粉丝0
内容83