SystemTap Beginners Guide 5.3と5.4のメモ
5.3 プロファイリング
以下のセクションでは、関数呼び出しの監視によりカーネルの活動状況を分析する方法を説明する。
5.3.1 関数呼び出しを数える
このセクションでは、カーネルが30秒間に何回カーネルの関数を呼び出すのか特定する方法について説明する。
functioncallcount.stp
#!/usr/bin/stap # The following line command will probe all the functions # in kernel's memory management code: # # stap functioncallcount.stp "*@mm/*.c" probe kernel.function(@1).call { # probe functions listed on commandline called[probefunc()] <<< 1 # add a count efficiently } global called probe end { foreach (fn in called-) # Sort by call count (in decreasing order) # (fn+ in called) # Sort by function name printf("%s %d\n", fn, @count(called[fn])) exit() }
functioncallcount.stpは対象のカーネル関数を引数に取る。
ワイルドカードでも使えるので、別の環境でも確実に使えるはずである。
以下に、'stap functioncallcount.stp "*@mm/*.c"'を実行した結果を示す。
__vma_link 97 __vma_link_file 66 __vma_link_list 97 __vma_link_rb 97 __xchg 103 add_page_to_active_list 102 add_page_to_inactive_list 19 add_to_page_cache 19 add_to_page_cache_lru 7 all_vm_events 6 alloc_pages_node 4630 alloc_slabmgmt 67 anon_vma_alloc 62 anon_vma_free 62 anon_vma_lock 66 anon_vma_prepare 98 anon_vma_unlink 97 anon_vma_unlock 66 arch_get_unmapped_area_topdown 94 arch_get_unmapped_exec_area 3 arch_unmap_area_topdown 97 atomic_add 2 atomic_add_negative 97 atomic_dec_and_test 5153 atomic_inc 470 atomic_inc_and_test 1
5.3.2 コールグラフのトレース
このセクションでは、関数呼び出しにおける関数に入る箇所と出る箇所をトレースする。
#!/usr/bin/stap function trace(entry_p, extra) { %( $# > 1 %? if (tid() in trace) %) printf("%s%s%s %s\n", thread_indent (entry_p), (entry_p>0?"->":"<-"), probefunc (), extra) } %( $# > 1 %? global trace probe $2.call { trace[tid()] = 1 } probe $2.return { delete trace[tid()] } %) probe $1.call { trace(1, $$parms) } probe $1.return { trace(-1, $$return) }
para-callgraph.stp は2つのコマンドライン引数をとる。
- トレースしたい関数 ($1)
- 2つ目はオプションのトリガー関数($2)。トリガー関数が指定されている場合、この関数に入った後から、この関数から出るまでの間でトレースを実行する。
以下に、"stap para-callgraph.stp 'kernel.function("*@fs/*.c")' 'kernel.function("sys_read")'"の実行例を示す。
... 267 gnome-terminal(2921): <-do_sync_read return=0xfffffffffffffff5 269 gnome-terminal(2921):<-vfs_read return=0xfffffffffffffff5 0 gnome-terminal(2921):->fput file=0xffff880111eebbc0 2 gnome-terminal(2921):<-fput 0 gnome-terminal(2921):->fget_light fd=0x3 fput_needed=0xffff88010544df54 3 gnome-terminal(2921):<-fget_light return=0xffff8801116ce980 0 gnome-terminal(2921):->vfs_read file=0xffff8801116ce980 buf=0xc86504 count=0x1000 pos=0xffff88010544df48 4 gnome-terminal(2921): ->rw_verify_area read_write=0x0 file=0xffff8801116ce980 ppos=0xffff88010544df48 count=0x1000 7 gnome-terminal(2921): <-rw_verify_area return=0x1000 12 gnome-terminal(2921): ->do_sync_read filp=0xffff8801116ce980 buf=0xc86504 len=0x1000 ppos=0xffff88010544df48 15 gnome-terminal(2921): <-do_sync_read return=0xfffffffffffffff5 18 gnome-terminal(2921):<-vfs_read return=0xfffffffffffffff5 0 gnome-terminal(2921):->fput file=0xffff8801116ce980
5.3.3 カーネル空間とユーザ空間での消費時間の決定
このセクションでは、スレッドに与えられた時間がどのくらいカーネルで消費されたか、
またはユーザー空間で消費されたか決定する方法について説明する。
#!/usr/bin/stap probe perf.sw.cpu_clock!, timer.profile { // NB: To avoid contention on SMP machines, no global scalars/arrays used, // only contention-free statistics aggregates. tid=tid(); e=execname() if (!user_mode()) kticks[e,tid] <<< 1 else uticks[e,tid] <<< 1 ticks <<< 1 tids[e,tid] <<< 1 } global uticks, kticks, ticks global tids probe timer.s(5), end { allticks = @count(ticks) printf ("%16s %5s %7s %7s (of %d ticks)\n", "comm", "tid", "%user", "%kernel", allticks) foreach ([e,tid] in tids- limit 20) { uscaled = @count(uticks[e,tid])*10000/allticks kscaled = @count(kticks[e,tid])*10000/allticks printf ("%16s %5d %3d.%02d%% %3d.%02d%%\n", e, tid, uscaled/100, uscaled%100, kscaled/100, kscaled%100) } printf("\n") delete uticks delete kticks delete ticks delete tids }
thread-times.stpは5秒間にCPUを最も消費した上位20プロセスを列挙する。
また、CPUの消費時間をユーザー空間とカーネル空間に分けて、パーセント表示する。
このスクリプトの実行にかかる時間も計測結果に含まれてしまっている。
以下に実行例を示す。
comm tid %user %kernel (of 9999 ticks) swapper/1 0 0.00% 45.17% swapper/0 0 0.00% 44.32% gedit 1709 1.87% 0.10% gnome-shell 1133 1.18% 0.26% Xorg 823 0.75% 0.68% gdbus 1566 1.10% 0.22% mozc_server 1744 1.23% 0.04% ibus-daemon 1475 0.59% 0.05% ibus-engine-moz 1738 0.25% 0.25% gdbus 1713 0.34% 0.06% mozc_renderer 1748 0.27% 0.01% gdbus 1740 0.22% 0.04% ibus-ui-gtk3 1568 0.23% 0.01% kworker/0:2 2344 0.00% 0.12% kworker/1:1 2182 0.00% 0.10% gdbus 1574 0.06% 0.02% kworker/u:0 5 0.00% 0.07% gnome-terminal 1780 0.04% 0.02% gdbus 1579 0.06% 0.00% ibus-engine-sim 1578 0.05% 0.00%
5.3.4 ポーリング中のアプリケーションの監視
このセクションでは、ポーリング中のアプリケーションの監視と特定について説明する。
不要であったり高価であったりするポーリングを追跡できるので、性能改善に役立つだろう。
#!/usr/bin/stap # Copyright (C) 2009 Red Hat, Inc. # Written by Ulrich Drepper <drepper@redhat.com> # Modified by William Cohen <wcohen@redhat.com> global process, timeout_count, to global poll_timeout, epoll_timeout, select_timeout, itimer_timeout global nanosleep_timeout, futex_timeout, signal_timeout probe syscall.poll, syscall.epoll_wait { if (timeout) to[pid()]=timeout } probe syscall.poll.return { p = pid() if ($return == 0 && to[p] > 0 ) { poll_timeout[p]++ timeout_count[p]++ process[p] = execname() delete to[p] } } probe syscall.epoll_wait.return { p = pid() if ($return == 0 && to[p] > 0 ) { epoll_timeout[p]++ timeout_count[p]++ process[p] = execname() delete to[p] } } probe syscall.select.return { if ($return == 0) { p = pid() select_timeout[p]++ timeout_count[p]++ process[p] = execname() } } probe syscall.futex.return { if (errno_str($return) == "ETIMEDOUT") { p = pid() futex_timeout[p]++ timeout_count[p]++ process[p] = execname() } } probe syscall.nanosleep.return { if ($return == 0) { p = pid() nanosleep_timeout[p]++ timeout_count[p]++ process[p] = execname() } } probe kernel.function("it_real_fn") { p = pid() itimer_timeout[p]++ timeout_count[p]++ process[p] = execname() } probe syscall.rt_sigtimedwait.return { if (errno_str($return) == "EAGAIN") { p = pid() signal_timeout[p]++ timeout_count[p]++ process[p] = execname() } } probe syscall.exit { p = pid() if (p in process) { delete process[p] delete timeout_count[p] delete poll_timeout[p] delete epoll_timeout[p] delete select_timeout[p] delete itimer_timeout[p] delete futex_timeout[p] delete nanosleep_timeout[p] delete signal_timeout[p] } } probe timer.s(1) { ansi_clear_screen() printf (" pid | poll select epoll itimer futex nanosle signal| process\n") foreach (p in timeout_count- limit 20) { printf ("%5d |%7d %7d %7d %7d %7d %7d %7d| %-.38s\n", p, poll_timeout[p], select_timeout[p], epoll_timeout[p], itimer_timeout[p], futex_timeout[p], nanosleep_timeout[p], signal_timeout[p], process[p]) } }
timeout.stpでは、以下のシステムコールが、実イベントの発生ではなく時間経過によって完了した回数を追跡する。
- poll
- select
- epoll
- itimer
- futex
- nanosleep
- signal
サンプル時間を増やしたい場合には、timer.s(1)の引数を増やせばよい。
timeout.stpの出力結果には、ポーリングシステムコールを実行して時間超過した数の順番に、上位の20個ポーリング中のアプリケーションのUIDと名前が表示される。
以下に実行例を示す。
pid | poll select epoll itimer futex nanosle signal| process 3397 | 3 0 0 0 156 0 0| firefox 1748 | 0 0 0 0 0 143 0| mozc_renderer 1780 | 120 0 0 0 0 3 0| gnome-terminal 822 | 0 64 0 0 0 0 0| wpa_supplicant 823 | 0 55 0 0 0 0 0| Xorg 1133 | 46 0 0 0 0 0 0| gnome-shell 697 | 10 0 0 0 0 0 0| NetworkManager 805 | 0 6 0 0 0 0 0| sendmail 1092 | 4 0 0 0 0 0 0| gnome-settings- 909 | 3 0 0 0 0 0 0| rtkit-daemon 670 | 0 0 0 0 0 3 0| abrt-watch-log 862 | 1 0 0 0 0 0 0| upowerd 0 | 0 0 0 1 0 0 0| swapper/0 1475 | 1 0 0 0 0 0 0| gdbus 1144 | 0 1 0 0 0 0 0| syndaemon 741 | 0 1 0 0 0 0 0| crond 1111 | 0 0 1 0 0 0 0| cupsd
5.3.5 最高頻度で使われるシステムコールの追跡
timeout.stpは、一部のシステムコール(poll等)を実行してポーリングしているアプリケーションを特定した。
しかし、その一部のシステムコール以外のシステムコールがカーネル内で時間を消費しているケースもあり得る。
もし、あるアプリケーションが無駄にシステムコールを使っていると考えているのであれば、
最高頻度で使われるシステムコールを特定する必要がある。そして、それにはtopsys.stpを使うことが出来る。
#!/usr/bin/stap # # This script continuously lists the top 20 systemcalls in the interval # 5 seconds # global syscalls_count probe syscall.* { syscalls_count[name]++ } function print_systop () { printf ("%25s %10s\n", "SYSCALL", "COUNT") foreach (syscall in syscalls_count- limit 20) { printf("%25s %10d\n", syscall, syscalls_count[syscall]) } delete syscalls_count } probe timer.s(5) { print_systop () printf("--------------------------------------------------------------\n") }
topsys.stpは5秒間隔にシステムコールが使われた回数を数えて、上位20のシステムコールを列挙する。
以下に実行例を示す。
SYSCALL COUNT ioctl 904 write 593 poll 574 sendmsg 478 read 450 recvmsg 398 recvfrom 260 select 152 setitimer 128 writev 102 lseek 90 rt_sigprocmask 82 close 53 munmap 51 mmap2 50 fstat 50 open 49 ppoll 25 nanosleep 22 epoll_wait 7
5.3.6 プロセス毎のシステコールの総量の追跡
このセクションでは、システムコールを元も実行しているプロセスを特定する方法を説明する。
前節では、ある時間で最も呼ばれるシステムコールを関しする方法について説明した。
また、どのアプリケーションがポーリング関連のシステムコールを最も呼び出しているのか特定する方法も説明した。
それぞれのプロセスがシステムコールを呼ぶ量を監視することで、ポーリング中のプロセスやリソース喰いを調査するのに役立つ情報を、さらに提供することが出来る。
syscalls_by_proc.stp
#!/usr/bin/stap # Copyright (C) 2006 IBM Corp. # # This file is part of systemtap, and is free software. You can # redistribute it and/or modify it under the terms of the GNU General # Public License (GPL); either version 2, or (at your option) any # later version. # # Print the system call count by process name in descending order. # global syscalls probe begin { print ("Collecting data... Type Ctrl-C to exit and display results\n") } probe syscall.* { syscalls[execname()]++ } probe end { printf ("%-10s %-s\n", "#SysCalls", "Process Name") foreach (proc in syscalls-) printf("%-10d %-s\n", syscalls[proc], proc) }
syscalls_by_proc.stpは、システムコールを呼び出した回数が多いプロセスの上位20を表示する。
また、システムコールを呼び出した回数も同時に表示する。
以下に実行例を示す。
SysCalls Process Name 30120 Xorg 5405 gnome-shell 3957 evince 1742 gnome-terminal 1412 gdbus 1188 NetworkManager 985 gnome-settings- 493 dbus-daemon 433 stapio 278 wpa_supplicant 183 upowerd 168 mozc_renderer 151 nm-applet 148 mission-control 123 ibus-ui-gtk3 116 ibus-daemon 109 systemd-journal 64 firefox 60 pool 56 sendmail 46 gnome-session 37 syndaemon 36 mozc_server 35 tracker-miner-f 31 systemd 24 rtkit-daemon 24 crond 20 modem-manager 20 gdm-binary 18 ibus-engine-sim 18 avahi-daemon 15 abrt-watch-log 7 cupsd 7 sudo 5 gnome-screensav 5 stap 4 fprintd 4 polkitd 4 gnome-shell-cal 4 evolution-alarm 4 gedit 3 rsyslogd 3 rs:main Q:Reg 2 audispd 1 kthreadd 1 gconfd-2
プロセス名の代わりにプロセスIDを表示する場合には以下のスクリプトを使用する。
#!/usr/bin/stap # Copyright (C) 2006 IBM Corp. # # This file is part of systemtap, and is free software. You can # redistribute it and/or modify it under the terms of the GNU General # Public License (GPL); either version 2, or (at your option) any # later version. # # Print the system call count by process ID in descending order. # global syscalls probe begin { print ("Collecting data... Type Ctrl-C to exit and display results\n") } probe syscall.* { syscalls[pid()]++ } probe end { printf ("%-10s %-s\n", "#SysCalls", "PID") foreach (pid in syscalls-) printf("%-10d %-d\n", syscalls[pid], pid) }
時間経過で終了させたい場合には、timer.s()を追加してexit関数を実行せよ。
5.4 競合したユーザー空間のロックを特定する
このセクションでは、ある期間にシステム全体に渡って、競合したユーザー空間のロックを特定する方法について説明する。
これはfutexの競合を原因とする貧弱な性能のプログラムを改善するのに役立つだろう。
単純に言って、futexの競合は複数のプロセスが同じ時期に同じロックにアクセスしようとするために発生する。
このためロック機構による並列実行は貧弱な性能の原因となる。
ロック機構の並列実行とは、あるプロセスがロックを得ると、他のプロセスはロックを使えるようになるまで待つといったものである。
futexes.stpスクリプトはfutexシステムコールをプローブして、ロックの競合を表示する。
#!/usr/bin/stap # This script tries to identify contended user-space locks by hooking # into the futex system call. global FUTEX_WAIT = 0 /*, FUTEX_WAKE = 1 */ global FUTEX_PRIVATE_FLAG = 128 /* linux 2.6.22+ */ global FUTEX_CLOCK_REALTIME = 256 /* linux 2.6.29+ */ global lock_waits # long-lived stats on (tid,lock) blockage elapsed time global process_names # long-lived pid-to-execname mapping probe syscall.futex.return { if (($op & ~(FUTEX_PRIVATE_FLAG|FUTEX_CLOCK_REALTIME)) != FUTEX_WAIT) next process_names[pid()] = execname() elapsed = gettimeofday_us() - @entry(gettimeofday_us()) lock_waits[pid(), $uaddr] <<< elapsed } probe end { foreach ([pid+, lock] in lock_waits) printf ("%s[%d] lock %p contended %d times, %d avg us\n", process_names[pid], pid, lock, @count(lock_waits[pid,lock]), @avg(lock_waits[pid,lock])) }
futexes.stp は手動で停止させる必要がある。exitすると、以下の情報を出力する。
- ある競合に関して責任を持つプロセスIDと名前
- 対象となるロック変数の位置
- ロック変数が競合された回数
- プローブ中の競合に関する時間の平均
以下に実行例を示す。
rs:main Q:Reg[694] lock 0x7f6489d2ff04 contended 1 times, 25790639 avg us pool[1120] lock 0x14f59f0 contended 1 times, 595 avg us gnome-shell[1133] lock 0x1d86b50 contended 1 times, 252 avg us gnome-shell[1133] lock 0x1d97bb0 contended 1 times, 9 avg us gnome-shell[1133] lock 0x2c2af1c contended 1 times, 29665866 avg us ibus-daemon[1475] lock 0x25ef7d0 contended 3 times, 23 avg us gdbus[1780] lock 0x7f22e400a1d0 contended 2 times, 13 avg us gdbus[1780] lock 0x19762f0 contended 2 times, 11 avg us gdbus[1780] lock 0x7f22e4009560 contended 2 times, 22 avg us