前回の
Webサーバが呼ぶシステムコール
(2)
通常のアプリケーション開発では、
一般に、
accept(4, {sa_family=AF_INET, sin_port=htons(49150), sin_a
ddr=inet_addr("127.0.0.1")}, [16]) = 5
(中略)
read(5, "GET / HTTP/1.0\r\nHost: xxx-xxxxxx"..., 131072) = 308
(中略)
write(5, "HTTP/1.1 200 OK\r\nDate: Sun, 10 A"..., 2218) = 2218
close(5) = 0
accept(4,
Webサーバはaccept(2)で待ち受け、5
が返っていますが、
「UNIXではあらゆるものがファイルである」5
を引数にread(2)やwrite(2)を呼び出し、
利用しているWebサーバ実装によっては、
Webアプリケーションで注目すべきシステムコール
前節では
非常にざっくり分類すると、
- (a)
ネットワークソケットを開き、 読み取り、 書き込む - (b)
ファイルを開き、 読み取り、 書き込む - (c)
リクエストの内容、 および上記2つによって得られたデータに基づいて計算を行う
Perlで書かれたWebアプリケーションでは、
また、
本節の以降では、
ソケットに関するシステムコール
TCPソケットを利用してネットワークアクセスを行っているWebアプリケーションをstraceした場合、
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 10
fcntl(10, F_SETFL, O_RDONLY) = 0
fcntl(10, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(10, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(10, F_SETFL, O_RDWR|O_NONBLOCK) = 0
connect(10, {sa_family=AF_INET, sin_port=htons(3306), sin_
addr=inet_addr("10.0.0.1")}, 16) = -1 EINPROGRESS (Operati
on now in progress)
fcntl(10, F_SETFL, O_RDWR) = 0
poll([{fd=10, events=POLLIN|POLLPRI}], 1, 4000) = 1 ([{fd=
10, revents=POLLIN}])
setsockopt(10, SOL_SOCKET, SO_RCVTIMEO, "\2003\341\1\0\0\0
\0\0\0\0\0\0\0\0\0", 16) = 0
setsockopt(10, SOL_SOCKET, SO_SNDTIMEO, "\2003\341\1\0\0\0
\0\0\0\0\0\0\0\0\0", 16) = 0
setsockopt(10, SOL_IP, IP_TOS, [8], 4) = 0
setsockopt(10, SOL_TCP, TCP_NODELAY, [1], 4) = 0
setsockopt(10, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
poll([{fd=10, events=POLLIN}], 1, 4000) = 1 ([{fd=10, reve
nts=POLLIN}])
read(10, "G\0\0\0\n5.1.61-community-log\0o"..., 16384) = 75
write(10, "3\0\0\1\217\242\2\0\0\0\0@!\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0"..., 55) = 55
(以下、read, write, pollの繰り返し)
どういうシステムコールが発行されるかはクライアントライブラリの実装に依存します
より一般化すると次の流れになります。
- socket(2)でソケットを作成
- connect(2)でTCPコネクションを確立
- write(2)/read(2)の繰り返し
(pollなどを挟む場合もあり) - close(2)でTCPコネクションをクローズ
(持続的接続を行わない場合)
straceとミドルウェアへのネットワークアクセス
特定のシーケンスがどのミドルウェアに対するアクセスかを判別するうえで最も簡単な方法は、
TCPソケットに関するシステムコールをstraceでトレースすることにより、
- 想定していないIPアドレスに対して接続が行われていないか
- TCPコネクションが想定どおりのタイミングで確立・
切断されているか - 想定していないデータを受信・
送信していないか
たとえば例として使ったアプリケーションでは、
逆にMySQLへのアクセスが発生するたびに都度TCPコネクションを接続・
ファイル操作に関するシステムコール
ファイル操作周りについては、
open("/tmp/xxx/cache", O_RDONLY) = 11
ioctl(11, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffb7de9cb0) =
-1 ENOTTY (Inappropriate ioctl for device)
lseek(11, 0, SEEK_CUR) = 0
fstat(11, {st_mode=S_IFREG|0644, st_size=41, ...}) = 0
fcntl(11, F_SETFD, FD_CLOEXEC) = 0
read(11, "[{\"value\":\"10.0.0.1\",\"weight"..., 41) = 41
close(11)
ネットワークアクセスに比べるとファイルI/
straceとその他ツールの使い分け
ここまで見てきたとおり、
(a) Perlレイヤでの各モジュール・ メソッドの実行時間をトレースしたい (b) MySQL、 memcachedにアクセスする際にかかっている時間を定点観測したい (c) ネットワークレイヤで起こっている問題 (TCPにおけるSYNの再送など) を調査したい
というような個別のケースに関しては、
Devel::NYTProf――Perlレイヤにおけるパフォーマンス計測ツール
たとえば
Devel::KYTProf――ネットワーク周りのパフォーマンス計測ツール
(b)
- (一時的なトレースではなく)
アプリケーションの各種ミドルウェアとの通信時間を定点観測したい - 常時起きている問題ではなく、
確率的に起こる問題を捕まえたい
という場合には、
tcpdump、tshark――トランスポート層以下のトレース
straceでトレースできるのはシステムコールとシグナルのみであるため、
なぜstraceを使うか
ここまでで、
- 事前に仕込みを行う必要がなく、
見たいときにすぐ見られる - 特定のミドルウェアアクセスに特化したものではないので、
取り逃しがない - Linuxのプロセスなら何でもトレースできるので、
開発言語を問わず使える - ソースが手元にない
(あるいはパッと手に入らない) バイナリでも使える
Perlを使った開発が前提である場合は後者2つの利点にそこまで恩恵が感じられないかと思いますが、
<続きの