绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
如何在PostgreSQL中查找代码
2020-05-19 17:32:47

PostgreSQL代码大约有130w行,大部分代码耦合度较高,想完全读懂所有代码需要比较长的时间。但是在做一些内核开发时,不得不找到需要的代码进行调研、修改等工作,那么我们该如何进行呢?

下面根据这几年的工作经验进行了总结,一共有5种方法可以长时间进行代码搜索和定位。

关键字搜索

当我们进行代码搜索时,常用的办法就是关键字搜索。比如在Linux下,我们可以使用:

[postgres@izj6ccelicizfq7z026sowz ~]$ cd pg13
[postgres@izj6ccelicizfq7z026sowz pg13]$ grep -r "current_date" -w --include="*.c"
src/interfaces/ecpg/preproc/preproc.c: (yyval.str) = mm_strdup("current_date");
src/interfaces/ecpg/preproc/preproc.c: (yyval.str) = mm_strdup("current_date");
src/backend/parser/parse_target.c:					*name = "current_date";
[postgres@izj6ccelicizfq7z026sowz pg13]$

或者使用IDE进行搜索:

关键字搜索需要比较的关键字,否则会找到非常多的冗余数据,造成大量时间消耗。

错误信息搜索

我们可以根据已知的错误信息或者制造错误信息来获取关键字,以此进行搜索和定位。

比如,使用varchar类型时,它的长度定义是有限制的,那它是如何定义或者怎样限制的,我们可以通过以下错误信息进行搜索:

postgres=# create table testv(t1 varchar(99999999));
ERROR:  length for type varchar cannot exceed 10485760
LINE 1: create table testv(t1 varchar(99999999));
                              ^
postgres=#

关键信息为:length for type varchar cannot exceed 10485760,这里要注意的是,错误信息有的时候是使用“%s”来进行替换的,所以有时候直接搜索错误信息是无法达到目标的,比如:

[postgres@izj6ccelicizfq7z026sowz pg13]$ grep -r "length for type varchar cannot exceed"
[postgres@izj6ccelicizfq7z026sowz pg13]$

我们通过一定判断,去掉部分信息在进行搜索(--include是避免查找范围变大,得到冗余信息):

[postgres@izj6ccelicizfq7z026sowz pg13]$ grep -r "length for type" --include="*.c"
src/backend/utils/adt/varbit.c:				 errmsg("length for type %s must be at least 1",
src/backend/utils/adt/varbit.c:				 errmsg("length for type %s cannot exceed %d",
src/backend/utils/adt/varchar.c:				 errmsg("length for type %s must be at least 1", typename)));
src/backend/utils/adt/varchar.c:				 errmsg("length for type %s cannot exceed %d",
[postgres@izj6ccelicizfq7z026sowz pg13]$ 

通过GDB进行定位

当我们通过关键字搜索或者错误信息搜索,我们只能得到一个点的信息,但对于整个过程或者函数调用关系还是不够清楚。这个时候可以利用GDB调试来对函数进行定位。

比如在通过错误信息搜索时,我们使用到的varchar的报错信息,能够定位到函数anychar_typmodin,那么是什么时候,谁调用的这个函数,就可以通过GDB调试来搜索。

  1. 查询当前回话进程的pid
postgres=# select pg_backend_pid();
 pg_backend_pid 
----------------
          19541
(1 row)

2. 使用GDB进行调试

[postgres@izj6ccelicizfq7z026sowz ~]$ cd db13
[postgres@izj6ccelicizfq7z026sowz db13]$ cd bin
[postgres@izj6ccelicizfq7z026sowz bin]$ gdb postgres
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/postgres/db13/bin/postgres...done.
(gdb) attach 19541 --打开对应的进程

3. 设置断点

(gdb) b anychar_typmodin
Breakpoint 1 at 0x870f70: file varchar.c, line 34.

4. 执行命令

postgres=# create table testv(t1 varchar(99999999));
--hung住

5. 这时候因为当前进程进入调试状态,需要使用GDB继续执行,直到断点

(gdb) c
Continuing.

Breakpoint 1, anychar_typmodin (ta=0x286b138, typename=typename@entry=0x90bcec "varchar")
    at varchar.c:34
34	{
(gdb)

6. 使用bt命令查看函数调用栈

Breakpoint 1, anychar_typmodin (ta=0x286b138, typename=typename@entry=0x90bcec "varchar")
    at varchar.c:34
34	{
(gdb) bt
#0  anychar_typmodin (ta=0x286b138, typename=typename@entry=0x90bcec "varchar")
    at varchar.c:34
#1  0x00000000008719af in varchartypmodin (fcinfo=<optimized out>) at varchar.c:647
#2  0x00000000008a0663 in FunctionCall1Coll (flinfo=0x7ffda0d18b90, 
    collation=<optimized out>, arg1=<optimized out>) at fmgr.c:1142
#3  0x00000000008a0d15 in OidFunctionCall1Coll (functionId=functionId@entry=2915, 
    collation=collation@entry=0, arg1=arg1@entry=42381624) at fmgr.c:1420
#4  0x0000000000591d0d in typenameTypeMod (typ=0x7f805b329de8, typeName=0x286ad40, 
    pstate=0x286aed8) at parse_type.c:417
#5  LookupTypeNameExtended (pstate=pstate@entry=0x286aed8, typeName=typeName@entry=0x286ad40, 
    typmod_p=typmod_p@entry=0x0, temp_ok=temp_ok@entry=true, 
    missing_ok=missing_ok@entry=false) at parse_type.c:211
#6  0x0000000000592203 in LookupTypeName (missing_ok=false, typmod_p=0x0, typeName=0x286ad40, 
    pstate=0x286aed8) at parse_type.c:41
#7  typenameType (pstate=0x286aed8, typeName=0x286ad40, typmod_p=typmod_p@entry=0x0)
    at parse_type.c:268
#8  0x00000000005938e5 in transformColumnType (cxt=0x7ffda0d18e40, column=0x2908960, 
    column=0x2908960) at parse_utilcmd.c:3526
#9  transformColumnDefinition (cxt=cxt@entry=0x7ffda0d18e40, column=column@entry=0x2908960)
    at parse_utilcmd.c:565
#10 0x0000000000596996 in transformCreateStmt (stmt=0x2908818, stmt@entry=0x2846bd0, 
    queryString=queryString@entry=0x2845ee8 "create table testv(t1 varchar(99999999));")
    at parse_utilcmd.c:277
#11 0x0000000000786562 in ProcessUtilitySlow (pstate=pstate@entry=0x2908708, 
    pstmt=pstmt@entry=0x2846ce0, 
    queryString=queryString@entry=0x2845ee8 "create table testv(t1 varchar(99999999));", 
---Type <return> to continue, or q <return> to quit---
    context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=params@entry=0x0, 
    queryEnv=queryEnv@entry=0x0, qc=qc@entry=0x7ffda0d19490, dest=0x2846fa0) at utility.c:1140
#12 0x0000000000785401 in standard_ProcessUtility (pstmt=0x2846ce0, 
    queryString=0x2845ee8 "create table testv(t1 varchar(99999999));", 
    context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x2846fa0, 
    qc=0x7ffda0d19490) at utility.c:1067
#13 0x0000000000782966 in PortalRunUtility (portal=0x28acea8, pstmt=0x2846ce0, 
    isTopLevel=<optimized out>, setHoldSnapshot=<optimized out>, dest=0x2846fa0, 
    qc=0x7ffda0d19490) at pquery.c:1157
#14 0x0000000000783408 in PortalRunMulti (portal=portal@entry=0x28acea8, 
    isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, 
    dest=dest@entry=0x2846fa0, altdest=altdest@entry=0x2846fa0, qc=qc@entry=0x7ffda0d19490)
    at pquery.c:1310
#15 0x0000000000783faa in PortalRun (portal=portal@entry=0x28acea8, 
    count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, 
    run_once=run_once@entry=true, dest=dest@entry=0x2846fa0, altdest=altdest@entry=0x2846fa0, 
    qc=qc@entry=0x7ffda0d19490) at pquery.c:779
#16 0x000000000077fca0 in exec_simple_query (
    query_string=0x2845ee8 "create table testv(t1 varchar(99999999));") at postgres.c:1239
#17 0x000000000078102e in PostgresMain (argc=<optimized out>, argv=argv@entry=0x2870f80, 
    dbname=0x2870ea8 "postgres", username=<optimized out>) at postgres.c:4298
#18 0x0000000000480e36 in BackendRun (port=<optimized out>, port=<optimized out>)
    at postmaster.c:4510
#19 BackendStartup (port=0x2868e80) at postmaster.c:4202
#20 ServerLoop () at postmaster.c:1727
#21 0x000000000070b89e in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0x2840ab0)
---Type <return> to continue, or q <return> to quit---
    at postmaster.c:1400
#22 0x0000000000481b6f in main (argc=3, argv=0x2840ab0) at main.c:210
(gdb)

至此搜索结束,可以根据此进行函数查找和修改。

使用GPROF查找函数

当我们对函数一无所知时,连报错信息或者关键字都不清楚时,我们可以使用GPROF进行调试,找到对应函数。

gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。

虽然他不是专门用来定位搜索函数的,但是其记录所有函数执行时间,这会缩减我们代码定位时间的消耗。比如我近在看排序的代码,但是我对排序完全不清楚,那么我可以使用此方法来查找,排序时都使用了哪些函数,然后依次对函数进行判断找到我需要的函数。

  1. 编译数据库时添加编译选项 --enable-profiling
./configure --prefix=/home/postgres/db13 --enable-debug --enable-profiling
make; make install

2. 准备数据,准备完成后退出当前会话,因为gprof只会统计当前进程的函数调用,避免其他操作干扰,建议只执行简单的命令来获取相应信息

postgres=# create table aa(a1 int);
CREATE TABLE
postgres=# insert into aa select generate_series(1, 10000);
INSERT 0 10000
postgres=# checkpoint ;
postgres=# \q

3. 查询排序

postgres=# select pg_backend_pid();
 pg_backend_pid 
----------------
          18713
(1 row)

postgres=# select * from aa order by ctid;
  a1   
-------
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
postgres=# \q

4. 找到对应文件,并转换数据为可读信息

[postgres@izj6ccelicizfq7z026sowz bin]$ cd ../data/
[postgres@izj6ccelicizfq7z026sowz data]$ ls
base          pg_hba.conf    pg_replslot   pg_subtrans  pg_xact
global        pg_ident.conf  pg_serial     pg_tblspc    postgresql.auto.conf
gprof         pg_logical     pg_snapshots  pg_twophase  postgresql.conf
pg_commit_ts  pg_multixact   pg_stat       PG_VERSION   postmaster.opts
pg_dynshmem   pg_notify      pg_stat_tmp   pg_wal       postmaster.pid
[postgres@izj6ccelicizfq7z026sowz data]$ cd gprof/
[postgres@izj6ccelicizfq7z026sowz gprof]$ ls
18685  18687  18688  18690  18695  18703  18713  avworker
[postgres@izj6ccelicizfq7z026sowz gprof]$ cd 18713
[postgres@izj6ccelicizfq7z026sowz 18713]$ ls
gmon.out
[postgres@izj6ccelicizfq7z026sowz 18713]$ gprof /home/postgres/db13/bin/postgres gmon.out > gmon.txt
[postgres@izj6ccelicizfq7z026sowz 18713]$ vim gmon.txt

5. 相应信息

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
100.00      0.01     0.01      103     0.10     0.10  _bt_binsrch
  0.00      0.01     0.00    34270     0.00     0.00  LWLockInitialize
  0.00      0.01     0.00    30063     0.00     0.00  internal_putbytes
  0.00      0.01     0.00    30044     0.00     0.00  enlargeStringInfo
  0.00      0.01     0.00    23506     0.00     0.00  AllocSetAlloc
  0.00      0.01     0.00    20010     0.00     0.00  tts_virtual_clear
  0.00      0.01     0.00    20008     0.00     0.00  MemoryContextReset
  0.00      0.01     0.00    20000     0.00     0.00  slot_getsomeattrs_int
  0.00      0.01     0.00    11331     0.00     0.00  palloc
  0.00      0.01     0.00    10432     0.00     0.00  palloc0
  0.00      0.01     0.00    10142     0.00     0.00  CheckForSerializableConflictOutNeeded
  0.00      0.01     0.00    10142     0.00     0.00  HeapCheckForSerializableConflictOut
  0.00      0.01     0.00    10130     0.00     0.00  ExecStoreBufferHeapTuple
  0.00      0.01     0.00    10025     0.00     0.00  pg_server_to_any
  0.00      0.01     0.00    10025     0.00     0.00  pg_server_to_client
  0.00      0.01     0.00    10023     0.00     0.00  appendBinaryStringInfoNT
  0.00      0.01     0.00    10021     0.00     0.00  AllocSetReset
  0.00      0.01     0.00    10021     0.00     0.00  MemoryContextStatsPrint
  0.00      0.01     0.00    10021     0.00     0.00  socket_putmessage
  0.00      0.01     0.00    10009     0.00     0.00  AllocSetGetChunkSpace
  0.00      0.01     0.00    10009     0.00     0.00  GetMemoryChunkSpace

通过GPROF找到相应函数后,可以再使用GDB方法来定位函数调用栈。

通过相关文献擎查找

我们可以进行文献查询,比如我们的官方文档。

再比如我们可以通过搜索引擎去查找有关的博客或者教程,来查找相应的关键信息。

通过文件直接搜索

当我们对代码非常熟悉后,就可以直接通过文件直接进行搜索。

比如,src/backend/utils/adt这里是大部分的系统函数实现代码,直接可以在这里查找信息。
通常系统函数会通过类型进行分类,比如char.c就是类型char的相关函数。

以上就是我的经验总结,还有很多其他方式也欢迎各位一起进行讨论。

分享好友

分享这个小栈给你的朋友们,一起进步吧。

华山论剑
创建时间:2019-02-22 18:53:00
没了烟火气,人生就是一段孤独的旅程·····于是,在ITPUB,我们以武论英雄!
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 栈栈
    栈主
  • ?
    嘉宾

小栈成员

查看更多
  • u_9a3ed7a37f8e4a
  • daisyplay
  • boss_ch
  • Jack2k
戳我,来吐槽~