パイプの時間を計るにはどうすればよいですか?


27

time1つのパイプ出力から別のパイプ出力を持つ2つの個別のコマンドで構成されるコマンドが必要です。たとえば、次の2つのスクリプトを考えます。

$ cat foo.sh
#!/bin/sh
sleep 4

$ cat bar.sh
#!/bin/sh
sleep 2

さて、どのようにtimeかかった時間を報告することができますかfoo.sh | bar.sh(そして、はい、ここではパイプが意味をなさないことを知っていますが、これは単なる例です)?パイプなしでサブシェルで順番に実行すると、期待どおりに動作します:

$ time ( foo.sh; bar.sh )

real    0m6.020s
user    0m0.010s
sys     0m0.003s

しかし、パイピングすると動作しません:

$ time ( foo.sh | bar.sh )

real    0m4.009s
user    0m0.007s
sys     0m0.003s

$ time ( { foo.sh | bar.sh; } )

real    0m4.008s
user    0m0.007s
sys     0m0.000s

$ time sh -c "foo.sh | bar.sh "

real    0m4.006s
user    0m0.000s
sys     0m0.000s

私は同様の質問(複数のコマンドで時間を実行し、時間出力をファイルに書き込む方法)を読み、スタンドアロンのtime実行可能ファイルも試しました:

$ /usr/bin/time -p sh -c "foo.sh | bar.sh"
real 4.01
user 0.00
sys 0.00

パイプのみを実行する3番目のスクリプトを作成しても機能しません。

$ cat baz.sh
#!/bin/sh
foo.sh | bar.sh

そして、その時間:

$ time baz.sh

real    0m4.009s
user    0m0.003s
sys     0m0.000s

興味深いことに、time最初のコマンドが終了するとすぐに終了するようには見えません。に変更bar.shした場合:

#!/bin/sh
sleep 2
seq 1 5

そしてtime再び、私はtime前に出力が印刷されることを期待していましたseqが、そうではありません:

$ time ( { foo.sh | bar.sh; } )
1
2
3
4
5

real    0m4.005s
user    0m0.003s
sys     0m0.000s

レポート1を印刷する前に終了するのを待っているにもかかわらずtime、実行にかかった時間はカウントされないようです。bar.sh

すべてのテストはArchシステムで実行され、bash 4.4.12(1)-releaseを使用しました。私はこれが一部であるプロジェクトにのみbashを使用することができますのでzsh、他の強力なシェルがそれを回避できる場合でも、それは私にとって実行可能なソリューションではありません。

それで、パイプされた一連のコマンドの実行にかかった時間を取得するにはどうすればよいですか?そして、私たちがそれに取り組んでいる間に、なぜ機能しないのですか?time最初のコマンドが終了するとすぐに終了するようです。どうして?

私はこのようなもので個々の時間を取得できることを知っています:

( time foo.sh ) 2>foo.time | ( time bar.sh ) 2> bar.time

しかし、すべてを単一の操作として時間計測することが可能かどうかを知りたいです。


1 これはバッファの問題ではないようです。スクリプトをで実行しようunbufferedstdbuf -i0 -o0 -e0しましたが、time出力前に数値が出力されたままでした。


物理的なストップウォッチで試しましたか?
ペリシンチオン

@pericynthionうん、結局私はやった。そして、それは答えが説明するものも示しました:時間は実際には動作していますが、パイプラインのコマンドは同時に実行されるので(明らかに十分です)、要する時間は本質的に最も遅い時間です。
テルドン

回答:


33

それはされての作業します。

パイプラインのさまざまな部分が同時に実行されます。パイプライン内のプロセスを同期化/シリアル化する唯一のことはIOです。つまり、あるプロセスがパイプライン内の次のプロセスに書き込み、次のプロセスが最初のプロセスの書き込みを読み取ります。それとは別に、それらは互いに独立して実行しています。

パイプラインのプロセス間で読み取りまたは書き込みが行われないため、パイプラインの実行にかかる時間は最長のsleep呼び出しの時間です。

あなたも書いているかもしれません

time ( foo.sh & bar.sh &; wait )

Terdon は、チャットにわずかに変更したスクリプト例をいくつか投稿しまし

#!/bin/sh
# This is "foo.sh"
echo 1; sleep 1
echo 2; sleep 1
echo 3; sleep 1
echo 4

そして

#!/bin/sh
# This is "bar.sh"
sleep 2
while read line; do
  echo "LL $line"
done
sleep 1

クエリは、「time ( sh foo.sh | sh bar.sh )3 + 3 = 6秒ではなく4秒を返すのはなぜですか?」でした。

各コマンドが実行されるおおよその時間を含めて、何が起こっているのかを見るために、これを行うことができます(出力には私の注釈が含まれています):

$ time ( env PS4='$SECONDS foo: ' sh -x foo.sh | PS4='$SECONDS bar: ' sh -x bar.sh )
0 bar: sleep 2
0 foo: echo 1     ; The output is buffered
0 foo: sleep 1
1 foo: echo 2     ; The output is buffered
1 foo: sleep 1
2 bar: read line  ; "bar" wakes up and reads the two first echoes
2 bar: echo LL 1
LL 1
2 bar: read line
2 bar: echo LL 2
LL 2
2 bar: read line  ; "bar" waits for more
2 foo: echo 3     ; "foo" wakes up from its second sleep
2 bar: echo LL 3
LL 3
2 bar: read line
2 foo: sleep 1
3 foo: echo 4     ; "foo" does the last echo and exits
3 bar: echo LL 4
LL 4
3 bar: read line  ; "bar" fails to read more
3 bar: sleep 1    ; ... and goes to sleep for one second

real    0m4.14s
user    0m0.00s
sys     0m0.10s

結論として、echoinの最初の2つの呼び出しの出力がバッファリングされるため、パイプラインは6秒ではなく4秒かかりますfoo.sh


1
@terdon値は合計ですが、スクリプトはユーザーとシステムの時間をほとんど必要としません。待機するだけで、カウントされません(実時間を除く)。
スティーブンキット

2
Bourneシェルなどの一部のシェルksh93は、パイプラインの最後のコンポーネントのみを待機することに注意してください(sleep 3 | sleep 11秒間持続します)。Bourneシェルにはtimeキーワードはありませんが、ksh93で実行するとtime、すべてのコンポーネントが待機します。
ステファンシャゼラス

3
ksh93では1秒かかりますが、10秒sleep 10 | sleep 1かかることに驚くかもしれませんtime sleep 10 | sleep 1。Bourneシェルでtime sleep 10 | sleep 1は1秒かかりますが、9秒後に青の時間出力(sleep 10およびからのみ/usr/bin/time)を取得します。
ステファンシャゼラス

1
それは何かを守ることではありません。timeパイプラインの時間を正しく調整しますが、ksh93のシェルの動作を変更します。(sleep 10 | sleep 1)1 time (sleep 10 | sleep 1)秒かかり、10秒かかります。{ (sleep 10 | sleep 1); echo x; }出力x1秒後に、time { (sleep 10 | sleep 1); echo x; }出力x10秒後。そのコードを関数に入れ、関数の時間を計る場合も同じです。
ステファンシャゼラス

1
であることに注意してくださいksh93ようでzsh-o promptsubstここでは)、あなたが行うことができますtypeset -F SECONDSあまり得るためにおおよそ(POSIXには秒数をsh全く持っていないSECONDS
ステファンChazelas

10

これはより良い例でしょうか?

$ time perl -e 'alarm(3); 1 while 1;' | perl -e 'alarm(4); 1 while 1;'
Alarm clock

real    0m4.004s
user    0m6.992s
sys     0m0.004s

スクリプトは3秒と4秒(それぞれ)ビジーループし、並列実行のためにリアルタイムで合計4秒、CPU時間は7秒かかります。(少なくともおよそ)。

またはこれ:

$ time ( sleep 2; echo) | ( read x; sleep 3 )

real    0m5.004s
user    0m0.000s
sys     0m0.000s

これらは並行して実行されないため、合計所要時間は5秒です。すべてスリープ状態であるため、CPU時間は使用されません。


3

あなたが持っている場合はsysdig、あなたがトレーサーを挿入することができますあなたが必要な書き込みを追加するために、コードを変更することができると仮定すると、任意の点で/dev/null

echo '>::blah::' >/dev/null
foo.sh | bar.sh
echo '<::blah::' >/dev/null

(ただし、これは「単一操作」の要件を満たしていません)、その後、

$ sudo sysdig -w blalog "span.tags contains blah"

そして、おそらくあなただけの期間をエクスポートするにはsysdigノミが必要になります

description = "Exports sysdig span tag durations";
short_description = "Export span tag durations.";
category = "Tracers";

args = {}

function on_init()
    ftags = chisel.request_field("span.tags")
    flatency = chisel.request_field("span.duration")
    chisel.set_filter("evt.type=tracer and evt.dir=<")
    return true
end

function on_event()
    local tags = evt.field(ftags)
    local latency = evt.field(flatency)
    if latency then
        print(tostring(tags) .. "\t" .. tonumber(latency) / 1e9)
    end
    return true
end

sysdig/chiselsファイルspantagduration.luaを次のように使用できるため、ディレクトリに 保存されます

$ sysdig -r blalog -c spantagduration
...

またはcsysdig、JSON出力を試してみることもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.