web-dev-qa-db-ja.com

どうすればパイプの時間を計ることができますか?

timeコマンドを2つの別々のコマンドで構成し、1つの出力を別のパイプに出力したいと思います。たとえば、次の2つのスクリプトを考えてみます。

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

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

timefoo.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

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

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

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

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

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

それで、パイプされたコマンドのセットの実行にかかった時間をどのように取得できますか?そして、私たちがそこにいる間、なぜそれはうまくいかないのですか? timeは、最初のコマンドが終了するとすぐに終了するように見えます。どうして?

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

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

しかし、私はそれでもすべてを単一の操作として計時することが可能であるかどうか知りたいです。


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

28
terdon

それは機能しています

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

パイプライン内のプロセス間で読み取りまたは書き込みが発生しないため、パイプラインの実行にかかる時間は、最長の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

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

35
Kusalananda

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

$ 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時間は使用されません。

10
ilkkachu

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出力をいじることができます。

3
thrig

楽しすぎて、これが古いスレッドであることを知っていますが、同じ状況に陥っていました。エイリアスは簡単な回避策であることがわかりました。少なくともこれはbashとfishで機能します。すべてのシェルについてはわかりません。

代わりに:time ( foo.sh | bar.sh )

試してください:

alias foobar="foo.sh | bar.sh" time foobar

0
BoeroBoy