typefunc_XXX(...){code_row1();......if ( 打开可观测性 ){记录数据到某处 或 输出trace信息;......}......code_rowN();return type;}
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);
./configure --enable-dtrace 。。。make && make install
示例3
gcc会在编译后的可执行文件中,埋入“探针”。
你是否好奇这个探针长啥样啊?
反汇编一下就能看到:
standard_exec_simple_query()中的 query start探针:0x00000000009a6ec0 <+512>: callq 0x9c3540 <pgstat_report_activity>=> 0x00000000009a6ec5 <+517>: rdtscp0x00000000009a6ec8 <+520>: shl $0x20,%rdx0x00000000009a6ecc <+524>: mov %eax,%eax0x00000000009a6ece <+526>: mov %rdx,%rbx0x00000000009a6ed1 <+529>: or %rax,%rbx0x00000000009a6ed4 <+532>: nop0x00000000009a6ed5 <+533>: rdtscp0x00000000009a6ed8 <+536>: xor %esi,%esi0x00000000009a6eda <+538>: mov $0x11,%edi0x00000000009a6edf <+543>: mov %edx,%ebp0x00000000009a6ee1 <+545>: mov %eax,%r12d0x00000000009a6ee4 <+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”研讨会中讨论:

