国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Linux > 正文

Linux動態(tài)追蹤工具是什么

2024-08-27 23:55:10
字體:
供稿:網(wǎng)友
  今天小編給大家分享一下Linux動態(tài)追蹤工具是什么的相關(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
 
  Linux動態(tài)追蹤工具是什么
 
  線程與內(nèi)存剖析,只能觀測到進程的整體情況,有些時候我們需要觀測到某一方法級別,比如調(diào)用方法test()時,傳入的參數(shù)是什么,返回值是多少,花費了多少時間?這種情況下,我們就需要使用一些動態(tài)追蹤工具了,如strace、arthas、bpftrace、systemtap等。
 
  strace與ltrace
 
  strace是Linux中用來觀測系統(tǒng)調(diào)用的工具,學過操作系統(tǒng)原理都知道,操作系統(tǒng)向應(yīng)用程序暴露了一批系統(tǒng)調(diào)用接口,應(yīng)用程序只能通過這些系統(tǒng)調(diào)用接口來訪問操作系統(tǒng),比如申請內(nèi)存、文件或網(wǎng)絡(luò)io操作等。
 
  用法如下:
 
  # -T 打印系統(tǒng)調(diào)用花費的時間
  # -tt 打印系統(tǒng)調(diào)用的時間點
  # -s 輸出的最大長度,默認32,對于調(diào)用參數(shù)較長的場景,建議加大
  # -f 是否追蹤fork出來子進程的系統(tǒng)調(diào)用,由于服務(wù)端服務(wù)普通使用線程池,建議加上
  # -p 指定追蹤的進程pid
  # -o 指定追蹤日志輸出到哪個文件,不指定則直接輸出到終端
  $ strace -T -tt -f -s 10000 -p 87 -o strace.log
  實例:抓取實際發(fā)送的SQL
 
  有些時候,我們會發(fā)現(xiàn)代碼中完全沒問題的SQL,卻查不到數(shù)據(jù),這極有可能是由于項目中一些底層框架改寫了SQL,導致真實發(fā)送的SQL與代碼中的SQL不一樣。
 
  遇到這種情況,先別急著扒底層框架代碼,那樣會比較花時間,畢竟程序員的時間很寶貴,不然要加更多班的,怎么快速確認是不是這個原因呢?
 
  有兩種方法,第一種是使用wireshark抓包,第二種就是本篇介紹的strace了,由于程序必然要通過網(wǎng)絡(luò)io相關(guān)的系統(tǒng)調(diào)用,將SQL命令發(fā)送到數(shù)據(jù)庫,因此我們只需要用strace追蹤所有系統(tǒng)調(diào)用,然后grep出發(fā)送SQL的系統(tǒng)調(diào)用即可,如下:
 
  $ strace -T -tt -f -s 10000 -p 87 |& tee strace.log
  Linux動態(tài)追蹤工具是什么
 
  從圖中可以清晰看到,mysql的jdbc驅(qū)動是通過sendto系統(tǒng)調(diào)用來發(fā)送SQL,通過recvfrom來獲取返回結(jié)果,可以發(fā)現(xiàn),由于SQL是字符串,strace自動幫我們識別出來了,而返回結(jié)果因為是二進制的,就不容易識別了,需要非常熟悉mysql協(xié)議才行。
 
  另外,從上面其實也很容易看出SQL執(zhí)行耗時,計算相同線程號的sendto與recvfrom之間的時間差即可。
 
  ltrace
 
  由于大多數(shù)進程基本都會使用基礎(chǔ)c庫,而不是系統(tǒng)調(diào)用,如Linux上的glibc,Windows上的msvc,所以還有一個工具ltrace,可以用來追蹤庫調(diào)用,如下:
 
  $ ltrace -T -tt -f -s 10000 -p 87 -o ltrace.log
  基本用法和strace一樣,一般來說,使用strace就夠了。
 
  arthas
 
  arthas是java下的一款動態(tài)追蹤工具,可以觀測到j(luò)ava方法的調(diào)用參數(shù)、返回值等,除此之外,還提供了很多實用功能,如反編譯、線程剖析、堆內(nèi)存轉(zhuǎn)儲、火焰圖等。
 
  下載與使用
 
  # 下載arthas
  $ wget https://arthas.aliyun.com/download/3.4.6?mirror=aliyun -O arthas-packaging-3.4.6-bin.zip
  # 解壓
  $ unzip arthas-packaging-3.4.6-bin.zip -d arthas && cd arthas/
  # 進入arthas命令交互界面
  $ java -jar arthas-boot.jar `pgrep -n java`
  [INFO] arthas-boot version: 3.4.6
  [INFO] arthas home: /home/work/arthas
  [INFO] Try to attach process 3368243
  [INFO] Attach process 3368243 success.
  [INFO] arthas-client connect 127.0.0.1 3658
    ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
   /  O  / |  .--. ''--.  .--'|  '--'  | /  O  / '   .-'
  |  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
  |  | |  ||  |/  /    |  |   |  |  |  ||  | |  |.-'    |
  `--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'
  wiki      https://arthas.aliyun.com/doc
  tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
  version   3.4.6
  pid       3368243
  time      2021-11-13 13:35:49
  # help可查看arthas提供了哪些命令
  [arthas@3368243]$ help
  # help watch可查看watch命令具體用法
  [arthas@3368243]$ help watch
  watch、trace與stack
 
  在arthas中,使用watch、trace、stack命令可以觀測方法調(diào)用情況,如下:
 
  # watch觀測執(zhí)行的查詢SQL,-x 3指定對象展開層級
  [arthas@3368243]$ watch org.apache.ibatis.executor.statement.PreparedStatementHandler parameterize '{target.boundSql.sql,target.boundSql.parameterObject}' -x 3
  method=org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize location=AtExit
  ts=2021-11-13 14:50:34; [cost=0.071342ms] result=@ArrayList[
      @String[select id,log_info,create_time,update_time,add_time from app_log where id=?],
      @ParamMap[
          @String[id]:@Long[41115],
          @String[param1]:@Long[41115],
      ],
  ]
  # watch觀測耗時超過200ms的SQL
  [arthas@3368243]$ watch com.mysql.jdbc.PreparedStatement execute '{target.toString()}' 'target.originalSql.contains("select") && #cost > 200' -x 2
  Press Q or Ctrl+C to abort.
  Affect(class count: 3 , method count: 1) cost in 123 ms, listenerId: 25
  method=com.mysql.jdbc.PreparedStatement.execute location=AtExit
  ts=2021-11-13 14:58:42; [cost=1001.558851ms] result=@ArrayList[
      @String[com.mysql.jdbc.PreparedStatement@6283cfe6: select count(*) from app_log],
  ]
  # trace追蹤方法耗時,層層追蹤,就可找到耗時根因,--skipJDKMethod false顯示jdk方法耗時,默認不顯示
  [arthas@3368243]$ trace com.mysql.jdbc.PreparedStatement execute 'target.originalSql.contains("select") && #cost > 200'  --skipJDKMethod false
  Press Q or Ctrl+C to abort.
  Affect(class count: 3 , method count: 1) cost in 191 ms, listenerId: 26
  ---ts=2021-11-13 15:00:40;thread_name=http-nio-8080-exec-47;id=76;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@5a2d131d
      ---[1001.465544ms] com.mysql.jdbc.PreparedStatement:execute()
          +---[0.022119ms] com.mysql.jdbc.PreparedStatement:checkClosed() #1274
          +---[0.016294ms] com.mysql.jdbc.MySQLConnection:getConnectionMutex() #57
          +---[0.017862ms] com.mysql.jdbc.PreparedStatement:checkReadOnlySafeStatement() #1278
          +---[0.008996ms] com.mysql.jdbc.PreparedStatement:createStreamingResultSet() #1294
          +---[0.010783ms] com.mysql.jdbc.PreparedStatement:clearWarnings() #1296
          +---[0.017843ms] com.mysql.jdbc.PreparedStatement:fillSendPacket() #1316
          +---[0.008543ms] com.mysql.jdbc.MySQLConnection:getCatalog() #1320
          +---[0.009293ms] java.lang.String:equals() #57
          +---[0.008824ms] com.mysql.jdbc.MySQLConnection:getCacheResultSetMetadata() #1328
          +---[0.009892ms] com.mysql.jdbc.MySQLConnection:useMaxRows() #1354
          +---[1001.055229ms] com.mysql.jdbc.PreparedStatement:executeInternal() #1379
          +---[0.02076ms] com.mysql.jdbc.ResultSetInternalMethods:reallyResult() #1388
          +---[0.011517ms] com.mysql.jdbc.MySQLConnection:getCacheResultSetMetadata() #57
          +---[0.00842ms] com.mysql.jdbc.ResultSetInternalMethods:getUpdateID() #1404
          ---[0.008112ms] com.mysql.jdbc.ResultSetInternalMethods:reallyResult() #1409
  # stack追蹤方法調(diào)用棧,找到耗時SQL來源
  [arthas@3368243]$ stack com.mysql.jdbc.PreparedStatement execute 'target.originalSql.contains("select") && #cost > 200'
  Press Q or Ctrl+C to abort.
  Affect(class count: 3 , method count: 1) cost in 138 ms, listenerId: 27
  ts=2021-11-13 15:01:55;thread_name=http-nio-8080-exec-5;id=2d;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@5a2d131d
      @com.mysql.jdbc.PreparedStatement.execute()
          at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:493)
          at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:63)
          at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
          at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
          at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:326)
          at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
          at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:136)
          at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
          at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
          at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
          at sun.reflect.GeneratedMethodAccessor75.invoke(null:-1)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
          at com.sun.proxy.$Proxy113.selectOne(null:-1)
          at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
          at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:83)
          at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
          at com.sun.proxy.$Proxy119.selectCost(null:-1)
          at com.demo.example.web.controller.TestController.select(TestController.java:57)
  可以看到watch、trace、stack命令中都可以指定條件表達式,只要滿足ognl表達式語法即可,ognl完整語法很復雜,如下是一些經(jīng)常使用的:
 
  Linux動態(tài)追蹤工具是什么
 
  ognl
 
  通過ognl命令,可直接查看靜態(tài)變量的值,如下:
 
  # 調(diào)用System.getProperty靜態(tài)函數(shù),查看jvm默認字符編碼
  [arthas@3368243]$ ognl '@System@getProperty("file.encoding")'
  @String[UTF-8]
  # 找到springboot類加載器
  [arthas@3368243]$ classloader -t
  +-BootstrapClassLoader
  +-sun.misc.Launcher$ExtClassLoader@492691d7
    +-sun.misc.Launcher$AppClassLoader@764c12b6
      +-org.springframework.boot.loader.LaunchedURLClassLoader@4361bd48
  # 獲取springboot中所有的beanName,-c指定springboot的classloader的hash值
  # 一般Spring項目,都會定義一個SpringUtil的,用于獲取bean容器ApplicationContext
  [arthas@3368243]$ ognl -c 4361bd48 '#context=@com.demo.example.web.SpringUtil@applicationContext, #context.beanFactory.beanDefinitionNames'
  @String[][
      @String[org.springframework.context.annotation.internalConfigurationAnnotationProcessor],
      @String[org.springframework.context.annotation.internalAutowiredAnnotationProcessor],
      @String[org.springframework.context.annotation.internalCommonAnnotationProcessor],
      @String[testController],
      @String[apiController],
      @String[loginService],
      ...
  ]
  # 獲取springboot配置,如server.port是配置http服務(wù)端口的
  [arthas@3368243]$ ognl -c 4361bd48 '#context=@com.demo.example.web.SpringUtil@applicationContext, #context.getEnvironment().getProperty("server.port")'
  @String[8080]
  # 查看server.port定義在哪個配置文件中
  # 可以很容易看到,server.port定義在application-web.yml
  [arthas@3368243]$ ognl -c 4361bd48 '#context=@com.demo.example.web.SpringUtil@applicationContext, #context.environment.propertySources.propertySourceList.{? containsProperty("server.port")}'
  @ArrayList[
      @ConfigurationPropertySourcesPropertySource[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}],
      @OriginTrackedMapPropertySource[OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application-web.yml]'}],
  ]
  # 調(diào)用springboot中bean的方法,獲取返回值
  [arthas@3368243]$ ognl -c 4361bd48 '#context=@com.demo.example.web.SpringUtil@applicationContext, #context.getBean("demoMapper").queryOne(12)' -x 2
  @ArrayList[
      @HashMap[
          @String[update_time]:@Timestamp[2021-11-09 18:38:13,000],
          @String[create_time]:@Timestamp[2021-04-17 15:52:55,000],
          @String[log_info]:@String[TbTRNsh3SixuFrkYLTeb25a6zklEZj0uWANKRMe],
          @String[id]:@Long[12],
          @String[add_time]:@Integer[61],
      ],
  ]
  # 查看springboot自帶tomcat的線程池的情況
  [arthas@3368243]$ ognl -c 4361bd48 '#context=@com.demo.example.web.SpringUtil@applicationContext, #context.webServer.tomcat.server.services[0].connectors[0].protocolHandler.endpoint.executor'
  @ThreadPoolExecutor[
      sm=@StringManager[org.apache.tomcat.util.res.StringManager@16886f49],
      submittedCount=@AtomicInteger[1],
      threadRenewalDelay=@Long[1000],
      workQueue=@TaskQueue[isEmpty=true;size=0],
      mainLock=@ReentrantLock[java.util.concurrent.locks.ReentrantLock@69e9cf90[Unlocked]],
      workers=@HashSet[isEmpty=false;size=10],
      largestPoolSize=@Integer[49],
      completedTaskCount=@Long[10176],
      threadFactory=@TaskThreadFactory[org.apache.tomcat.util.threads.TaskThreadFactory@63c03c4f],
      handler=@RejectHandler[org.apache.tomcat.util.threads.ThreadPoolExecutor$RejectHandler@3667e559],
      keepAliveTime=@Long[60000000000],
      allowCoreThreadTimeOut=@Boolean[false],
      corePoolSize=@Integer[10],
      maximumPoolSize=@Integer[8000],
  ]
  其它命令
 
  arthas還提供了jvm大盤、線程剖析、堆轉(zhuǎn)儲、反編譯、火焰圖等功能,如下:
 
  # 顯示耗cpu較多的前4個線程
  [arthas@3368243]$ thread -n 4
  "C2 CompilerThread0" [Internal] cpuUsage=8.13% deltaTime=16ms time=46159ms
  "C2 CompilerThread1" [Internal] cpuUsage=4.2% deltaTime=8ms time=47311ms
  "C1 CompilerThread2" [Internal] cpuUsage=3.06% deltaTime=6ms time=17402ms
  "http-nio-8080-exec-40" Id=111 cpuUsage=1.29% deltaTime=2ms time=624ms RUNNABLE (in native)
      at java.net.SocketInputStream.socketRead0(Native Method)
      ...
      at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4113)
      at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2570)
      at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2731)
      at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2818)
      ...
      at com.demo.example.web.controller.TestController.select(TestController.java:57)
  # 堆轉(zhuǎn)儲
  [arthas@3368243]$ heapdump
  Dumping heap to /tmp/heapdump2021-11-13-15-117226383240040009563.hprof ...
  Heap dump file created
  # cpu火焰圖,容器環(huán)境下profiler start可能用不了,可用profiler start -e itimer替代
  [arthas@3368243]$ profiler start
  Started [cpu] profiling
  [arthas@3368243]$ profiler stop
  OK
  profiler output file: /home/work/app/arthas-output/20211113-151208.svg
  # dashboard就類似Linux下的top一樣,可看jvm線程、堆內(nèi)存的整體情況
  [arthas@3368243]$ dashboard
  # jvm就類似Linux下的ps一樣,可以看jvm進程的一些基本信息,如:jvm參數(shù)、類加載、線程數(shù)、打開文件描述符數(shù)等
  [arthas@3368243]$ jvm
  # 反編譯
  [arthas@3368243]$ jad com.demo.example.web.controller.TestController
  可見,arthas已經(jīng)不是一個單純的動態(tài)追蹤工具了,它把jvm下常用的診斷功能幾乎全囊括了。
 
  bpftrace
 
  arthas只能追蹤java程序,對于原生程序(如MySQL)就無能為力了,好在Linux生態(tài)提供了大量的機制以及配套工具,可用于追蹤原生程序的調(diào)用,如perf、bpftrace、systemtap等,由于bpftrace使用難度較小,本篇主要介紹它的用法。
 
  bpftrace是基于ebpf技術(shù)實現(xiàn)的動態(tài)追蹤工具,它對ebpf技術(shù)進行封裝,實現(xiàn)了一種腳本語言,就像上面介紹的arthas基于ognl一樣,它實現(xiàn)的腳本語言類似于awk,封裝了常見語句塊,并提供內(nèi)置變量與內(nèi)置函數(shù),如下:
 
  $ sudo bpftrace -e 'BEGIN { printf("Hello, World!/n"); } '
  Attaching 1 probe...
  Hello, World!
  實例:在調(diào)用端追蹤慢SQL
 
  前面我們用strace追蹤過mysql的jdbc驅(qū)動,它使用sendto與recvfrom系統(tǒng)調(diào)用來與mysql服務(wù)器通信,因此,我們在sendto調(diào)用時,計下時間點,然后在recvfrom結(jié)束時,計算時間之差,就可以得到相應(yīng)SQL的耗時了,如下:
 
  先找到sendto與recvfrom系統(tǒng)調(diào)用在bpftrace中的追蹤點,如下:
 
  # 查找sendto|recvfrom系統(tǒng)調(diào)用的追蹤點,可以看到sys_enter_開頭的追蹤點應(yīng)該是進入時觸發(fā),sys_exit_開頭的退出時觸發(fā)
  $ sudo bpftrace -l '*tracepoint:syscalls*' |grep -E 'sendto|recvfrom'
  tracepoint:syscalls:sys_enter_sendto  
  tracepoint:syscalls:sys_exit_sendto   
  tracepoint:syscalls:sys_enter_recvfrom  
  tracepoint:syscalls:sys_exit_recvfrom
  # 查看系統(tǒng)調(diào)用參數(shù),方便我們編寫腳本
  $ sudo bpftrace -lv tracepoint:syscalls:sys_enter_sendto
  tracepoint:syscalls:sys_enter_sendto
      int __syscall_nr;
      int fd;
      void * buff;
      size_t len;
      unsigned int flags;
      struct sockaddr * addr;
      int addr_len;
  編寫追蹤腳本trace_slowsql_from_syscall.bt,腳本代碼如下:
 
  #!/usr/local/bin/bpftrace
  BEGIN {
      printf("Tracing jdbc SQL slower than %d ms by sendto/recvfrom syscall/n", $1);
      printf("%-10s %-6s %6s %s/n", "TIME(ms)", "PID", "MS", "QUERY");
  }
  tracepoint:syscalls:sys_enter_sendto /comm == "java"/ {
      // mysql協(xié)議中,包開始的第5字節(jié)指示命令類型,3代表SQL查詢
      $com = *(((uint8 *) args->buff)+4);
      if($com == (uint8)3){
          @query[tid]=str(((uint8 *) args->buff)+5, (args->len)-5);
          @start[tid]=nsecs;
      }
  }
  tracepoint:syscalls:sys_exit_recvfrom /comm == "java" && @start[tid]/ {
      $dur = (nsecs - @start[tid]) / 1000000;
      if ($dur > $1) {
          printf("%-10u %-6d %6d %s/n", elapsed / 1000000, pid, $dur, @query[tid]);
      }
      delete(@query[tid]);
      delete(@start[tid]);
  }
  其中,comm表示進程名稱,tid表示線程號,@query[tid]與@start[tid]類似map,以tid為key的話,這個變量就像一個線程本地變量了。
 
  調(diào)用上面的腳本,可以看到各SQL執(zhí)行耗時,如下:
 
  $ sudo BPFTRACE_STRLEN=80 bpftrace trace_slowsql_from_syscall.bt
  Attaching 3 probes...
  Tracing jdbc SQL slower than 0 ms by sendto/recvfrom syscall
  TIME(ms)   PID        MS QUERY
  6398       3368243    125 select sleep(0.1)
  16427      3368243     22 select id from app_log al order by id desc limit 1
  16431      3368243     20 select id,log_info,create_time,update_time,add_time from app_log where id=11692
  17492      3368243     21 select id,log_info,create_time,update_time,add_time from app_log where id=29214
  實例:在服務(wù)端追蹤慢SQL
 
  從調(diào)用端來追蹤SQL耗時,會包含網(wǎng)絡(luò)往返時間,為了得到更精確的SQL耗時,我們可以寫一個追蹤服務(wù)端mysql的腳本,來觀測SQL耗時,如下:
 
  確定mysqld服務(wù)進程的可執(zhí)行文件與入口函數(shù)
 
  $ which mysqld
  /usr/local/mysql/bin/mysqld
  # objdump可導出可執(zhí)行文件的動態(tài)符號表,做幾張mysqld的火焰圖就可發(fā)現(xiàn),dispatch_command是SQL處理的入口函數(shù)
  # 另外,由于mysql是c++寫的,方法名是編譯器改寫過的,這也是為啥下面腳本中要用*dispatch_command*模糊匹配
  $ objdump -tT /usr/local/mysql/bin/mysqld | grep dispatch_command
  00000000014efdf3 g     F .text  000000000000252e              _Z16dispatch_commandP3THDPK8COM_DATA19enum_server_command
  00000000014efdf3 g    DF .text  000000000000252e  Base        _Z16dispatch_commandP3THDPK8COM_DATA19enum_server_command
  使用uprobe追蹤dispatch_command的調(diào)用,如下:
 
  #!/usr/bin/bpftrace
  BEGIN{
      printf("Tracing mysqld SQL slower than %d ms. Ctrl-C to end./n", $1);
      printf("%-10s %-6s %6s %s/n", "TIME(ms)", "PID", "MS", "QUERY");
  }
  uprobe:/usr/local/mysql/bin/mysqld:*dispatch_command*{
      if (arg2 == (uint8)3) {
          @query[tid] = str(*arg1);
          @start[tid] = nsecs;
      }
  }
  uretprobe:/usr/local/mysql/bin/mysqld:*dispatch_command* /@start[tid]/{
      $dur = (nsecs - @start[tid]) / 1000000;
      if ($dur > $1) {
          printf("%-10u %-6d %6d %s/n", elapsed / 1000000, pid, $dur, @query[tid]);
      }
      delete(@query[tid]);
      delete(@start[tid]);
  }
  追蹤腳本整體上與之前系統(tǒng)調(diào)用版本的類似,不過追蹤點不一樣而已。
 
  實例:找出掃描大量行的SQL
 
  眾所周知,SQL執(zhí)行時需要掃描數(shù)據(jù),并且掃描的數(shù)據(jù)越多,SQL性能就會越差。
 
  但對于一些中間情況,SQL掃描行數(shù)不多也不少,如2w條。且這2w條數(shù)據(jù)都在緩存中的話,SQL執(zhí)行時間不會很長,導致沒有記錄在慢查詢?nèi)罩局校绻@樣的SQL并發(fā)量大起來的話,會非常耗費CPU。
 
  對于mysql的話,掃描行的函數(shù)是row_search_mvcc(如果你經(jīng)常抓取mysql棧的話,很容易發(fā)現(xiàn)這個函數(shù)),每掃一行調(diào)用一次,如果在追蹤腳本中追蹤此函數(shù),記錄下調(diào)用次數(shù),就可以觀測SQL的掃描行數(shù)了,如下:
 
  #!/usr/bin/bpftrace
  BEGIN{
      printf("Tracing mysqld SQL scan row than %d. Ctrl-C to end./n", $1);
      printf("%-10s %-6s %6s %10s %s/n", "TIME(ms)", "PID", "MS", "SCAN_NUM", "QUERY");
  }
  uprobe:/usr/local/mysql/bin/mysqld:*dispatch_command*{
      $COM_QUERY = (uint8)3;
      if (arg2 == $COM_QUERY) {
          @query[tid] = str(*arg1);
          @start[tid] = nsecs;
      }
  }
  uprobe:/usr/local/mysql/bin/mysqld:*row_search_mvcc*{
      @scan_num[tid]++;
  }
  uretprobe:/usr/local/mysql/bin/mysqld:*dispatch_command* /@start[tid]/{
      $dur = (nsecs - @start[tid]) / 1000000;
      if (@scan_num[tid] > $1) {
          printf("%-10u %-6d %6d %10d %s/n", elapsed / 1000000, pid, $dur, @scan_num[tid], @query[tid]);
      }
      delete(@query[tid]);
      delete(@start[tid]);
      delete(@scan_num[tid]);
  }
  腳本運行效果如下:
 
  $ sudo BPFTRACE_STRLEN=80 bpftrace trace_mysql_scan.bt 200
  Attaching 4 probes...
  Tracing mysqld SQL scan row than 200. Ctrl-C to end.
  TIME(ms)   PID        MS   SCAN_NUM QUERY
  150614     1892        4        300 select * from app_log limit 300
                                      # 全表掃描,慢!
  17489      1892      424      43717 select count(*) from app_log                                     
                                      # 大范圍索引掃描,慢!
  193013     1892      253      20000 select count(*) from app_log where id < 20000                    
                                      # 深分頁,會查詢前20300條,取最后300條,慢!
  213395     1892      209      20300 select * from app_log limit 20000,300                            
                                      # 索引效果不佳,雖然只會查到一條數(shù)據(jù),但掃描數(shù)據(jù)量不會少,慢!
  430374     1892      186      15000 select * from app_log where id < 20000 and seq = 15000 limit 1
  如上所示,app_log是我建的一張測試表,共43716條數(shù)據(jù),其中id字段是自增主鍵,seq值與id值一樣,但沒有索引。
 
  可以看到上面的幾個場景,不管什么場景,只要掃描行數(shù)變大,耗時就會變長,但也都沒有超過500毫秒的,原因是這個表很小,數(shù)據(jù)可以全部緩存在內(nèi)存中。
 
  可見,像bpftrace這樣的動態(tài)追蹤工具是非常強大的,而且比arthas更加靈活,arthas只能追蹤單個函數(shù),而bpftrace可以跨函數(shù)追蹤。

(編輯:武林網(wǎng))

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 阳原县| 赣榆县| 南郑县| 德州市| 海淀区| 景洪市| 遵义市| 壶关县| 海南省| 钟山县| 甘孜县| 德保县| 绥江县| 綦江县| 拉萨市| 寿宁县| 永泰县| 安溪县| 霸州市| 灵川县| 临海市| 长武县| 诸城市| 桐梓县| 三台县| 斗六市| 深州市| 漠河县| 高州市| 太白县| 滦南县| 白河县| 盐津县| 岳阳县| 宁国市| 崇文区| 商城县| 延川县| 文山县| 广汉市| 武威市|