Wifiアンテナを持っていて、その位置を最適化したい。その提案のために、現在のレイテンシーをリアルタイムで視覚化したいと思います。
Pingの待ち時間を出力する1行のスクリプトを作成しました。
lat.sh:
ping 8.8.8.8|gawk '/64 bytes/{ match($7,/[0-9.]+/,arr); print((i++)," ",arr[0]);}'
Gnuplotでプロットしたい:
plot "<lat.sh"
それはうまくいきません。 lat.shは、次のように端末に何を表示するかを表示していることがわかりました。
$ ./lat.sh
0 47.7
1 25.5
2 15.8
3 16.7
ただし、パイプに出力することはできません。 ./lat.sh > outfile
または./lat.sh|tee outfile
または./lat.sh &> outfile
端末には何も出力しませんが、ファイル(またはパイプ)は空のままです。 gnuplotパイプについても同じです。私は本当に混乱しています。
以下のmikeserv explains のように、これはバッファリングの問題です。これを解決する1つの方法は、fflush()
を使用してawk
にすぐに書き込むように強制することです(残りは、実行していたことの単純化されたバージョンです)。
_ping 8.8.8.8 | gawk -F'[= ]' '/^64/{print i++,$(NF-1); fflush()}'
_
fflush()
呼び出しは、バッファがいっぱいになるのを待たずに、出力が利用可能になるとすぐにawk
に出力を強制的に出力します。
または、ping
を特定の回数だけ実行してみることもできます。例:10:
_ping -c 10 8.8.8.8 | gawk ...
_
ここでの問題はawk
のようです。プログラムは出力を省略します-それをトリミングします。これは非常に便利なことですが、これを行うと、awk
のキューに入れられる出力が減ります。 stdio.h(ほとんどのUNIXプログラム)を使用するほとんどのUNIXプログラムは、出力デバイスのタイプに応じて出力をバッファリングします。出力デバイスが端末の場合、ほとんどのUNIXプログラムは出力をラインバッファリングし、1行あたりwrite()
を実行します。の場合、ではありませんが、(たとえば、パイプの場合)上記のプログラムは、書き込みごとに定義されたバイト数にバッファリングします。
これは標準的な動作であり、通常は良いです。 write()
のすべての呼び出しはシステムコールであり、システムカーネルのコアおよび最も基本的な機能へのフックです。 UNIXシステムはタイムシェアリング-カーネルは必要に応じてCPUの処理時間を各要求プロセスに共有します。したがって、効率的でI/Oを管理可能なチャンクにブロックすることは支払いです。これが、アプリケーションがバッファリングする理由です。
それがawk
が行っていることです。そして、この問題は、入力のフィルタリングによって、それ自体の出力が毎秒約8バイトまたは10バイトに減少するという点で混乱しています。 awk
が出力を4kbのチャンクにバッファリングする場合、(一般的に)であり、1秒あたり約10バイトの出力しか生成しない場合、6分に1回だけ書き込みが発生します。大陸移動の規模で周波数をチェックしない限り、待ち時間を測定するための非常に便利なアプリケーションにはなりません。
これを処理する方法があります。たとえば、パイプラインをptyでラップすることができます-screen
は、あなたと他のいくつかのアプリケーションのためにこれを行うことができます。おそらくこれを処理するためのawk
-ネイティブオプションがあります-しかし私はを知りません(たとえ terdonは間違いなくそうします )。プログラムの実行時に出力バッファを設定するためのlibc呼び出しを挿入するGNUのstdbuf
ツールがあります。これは通常非常に効果的ですが、実行するプログラムが後で実行中にバッファを調整する場合、あまり役に立ちません。
今私はawk
のせいですが、ping
も出力をバッファリングしている可能性があります。ただし、少なくとも私のLinuxシステムでは、次のように呼び出します。
_ping -O -n 8.8.8.8 | ...
_
...これはそうではありません。気になる方のために、標準のUNIXコマンドに対する非常に基本的なPOSIXオプションのみを使用して、出力行が利用可能になり次第、すべての出力行を一貫して書き込むようにしました。
最も簡単に:
_ping -On 8.8.8.8 |
sed -u 's/^64.*=\(.*\) ttl.*=/\1 /' |
more pipeline
_
... GNU、BSD、またはAST sed
のいずれかで動作します。また、...
_ping -On 8.8.8.8 |
sed 's/^64.*=\(.*\) ttl.*=/\1 /w target_file' |
more pipeline
_
...可能な処理に続いてすべての入力行をstdoutにコピーします-これはおそらくブロックバッファリングされます。ただし、成功したすべての_s///
_ ubstitutionの結果をtarget_fileにまたすぐにw
riteします。指定されたw
riteファイルのいずれか/すべてに対する即時のw
ritesはPOSIX仕様のsed
動作です-そして同じことが(ほぼ)r
eadファイルにも当てはまります。そのテーマのわずかなバリエーションとして:
_ping -On 8.8.8.8 |
sed 's/^64.*=\(.*\) ttl.*=/\1 /w /dev/fd/1' |
more pipeline
_
_/dev
_リンクを使用したファイル記述子のアドレス指定をサポートするシステムでは、少なくともsed
に関する限り、上記の結果、標準のシェルパイプラインを介して行バッファー出力が生成されます。 _/dev/fd
_リンクを直接サポートしないシステムの場合、とにかく/dev/std(in|out|err)
を処理するのはまだ人気のあるsed
機能です-古いminised
でさえこれを行います。 _-n
_を使用して、デフォルトの標準出力を完全に無効にします。
これは、その構成に基づいた、より柔軟でより複雑なソリューションです。
_mkfifo /tmp/ipipe /tmp/opipe
exec 9<>/tmp/opipe 8<>/tmp/ipipe
trap ' printf \\nTTY:STOP\\n >&8' INT
sed -n 's/^64.*=\(.*\) ttl.*=/\1\t/
/^TTY:START$/,/^TTY:STOP$/{
/^TTY/d;w /dev/tty
}; /^PIP:START$/,/^PIP:STOP$/{
/^PIP/d;w /tmp/opipe
}' <&8 >/dev/null 2>&1 & SEDPID=$!
ping -On 8.8.8.8 >&8 2>&8 & PNGPID=$!
printf \\nPIP:START\\n >&8;rm /tmp/?pipe
_
これで、常に出力をラインバッファで書き込むバックグラウンドディスパッチャプロセスが設定されました。最初に呼び出されたときに保存した、親シェルの_<>&9
_記述子に対応するファイル記述子に書き込むか、コマンドを送信するかどうかに応じてttyに書き込みます。
_PIP:START
TTY:START
_
あなたはこれらを次のように送ることができます:
_printf \\nPIP:START\\n >&8
printf \\nTTY:START\\n >&8
_
それはあなたが好きなように両方を同時に処理します。親シェルには、[〜#〜] int [〜#〜]シグナルを受信したときにコマンドTTY:STOPを送信するように構成されたtrap
があります-したがって、キーボードのCTRL + Cを押すだけで、いつでも端末への書き込みを停止できます。ただし、パイプへの書き込みを停止するように明示的に指示する必要があります。これは、必要に応じてtrap
で処理することもできます。次のように停止するように指示できます。
_printf \\nPIP:STOP\\n >&8
_
sed
will常にw
rite on a line-buffer-標準をどのようにバッファリングするかに関係なく、したがって、これを使用すると、ping
によって書き込まれた各行を書き込むとすぐに読み取ることができます。これを行うには、別のプロセスを呼び出してファイル記述子9を読み取ります。これは[〜#〜] pip [〜#〜]sed
が指示されたときに書き込むものです-またはターミナルに直接印刷するだけです。[〜#〜] pip [〜#〜]から読み取る別のプロセスを呼び出すには、次のようにします。
_cat <&9
_
...または類似。また、rm
は、パイプを必要とする可能性のあるすべてのプロセスがそれらのパイプでファイルハンドルを確実に確立するとすぐに、使用されるパイプのファイルシステムリンクを明示的にmkfifo
作成したことにも注意してください。これは、ファイルシステムを介して関連するプロセスと対話できないことを意味します-すべてのIPCは、親シェルの記述子8および9を介して調整する必要があります。バックグラウンドのsed
およびping
プロセスのPIDシェル変数_$PNGPID
_および_$SEDPID
_に保存されます。どちらもkill
でアドレス指定できます。
上記のスクリプト例を実行し、TTY:STARTコマンドを送信すると、結果は次のようになります。
_[mikeserv@localhost ~]$ printf \\nTTY:START\\n >&8
[mikeserv@localhost ~]$ 218 24.2 ms
219 21.2 ms
220 23.1 ms
221 21.3 ms
222 21.9 ms
^C
[mikeserv@localhost ~]$
_
...しかし、両方のプロセスはまだ実行中です-sed
は出力をどこにも書き込んでいません。
_ps -Fp "$SEDPID" "$PNGPID"
UID PID PPID C SZ RSS PSR STIME TTY STAT TIME CMD
mikeserv 31601 28945 0 2143 1712 4 14:22 pts/0 SN 0:00 sed -n s/^64.*q=\(.*\) ttl.*=/\1\
mikeserv 31602 28945 0 2106 740 3 14:22 pts/0 SN 0:00 ping -On 8.8.8.8
_
パイプに関しては、Unixは本当に制限されているようです。何か意味のあることをするために、最初にデータをファイルに書き込んだようです。全部書き直すとしたら、仮想端末が解決策かもしれません。しかし、派手なGUIの代わりに、私は単にターミナルに固執し、awkでプログレスバーをレンダリングします。
ping -i 0.5 8.8.8.8 | gawk -F'[= ]' '/^64/{ for(i=0;i<($(NF-1)/3);i++) printf(" "); printf("|"); for(i=120;i>$(NF-1);i--) printf(" "); printf("\r");}'
120を端末の幅に置き換えます。