Bagaimana saya bisa mengatur waktu pipa?

27

Saya ingin timeperintah yang terdiri dari dua perintah terpisah dengan satu keluaran perpipaan ke yang lain. Misalnya, perhatikan dua skrip di bawah ini:

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

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

Sekarang, bagaimana saya bisa timemelaporkan waktu yang diperlukan foo.sh | bar.sh(dan ya, saya tahu pipa itu tidak masuk akal di sini, tetapi ini hanya sebuah contoh)? Itu berfungsi seperti yang diharapkan jika saya menjalankannya secara berurutan dalam subkulit tanpa memipis:

$ time ( foo.sh; bar.sh )

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

Tapi saya tidak bisa membuatnya bekerja ketika memiploskan:

$ 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

Saya sudah membaca pertanyaan serupa ( Bagaimana menjalankan waktu pada banyak perintah DAN menulis waktu keluaran ke file? ) Dan juga mencoba timedieksekusi mandiri :

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

Bahkan tidak berfungsi jika saya membuat skrip ketiga yang hanya menjalankan pipa:

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

Dan waktu itu:

$ time baz.sh

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

Menariknya, itu tidak muncul seolah-olah timekeluar segera setelah perintah pertama selesai. Jika saya berubah bar.shmenjadi:

#!/bin/sh
sleep 2
seq 1 5

Dan sekali timelagi, saya mengharapkan timehasilnya akan dicetak sebelum seqtetapi tidak:

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

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

Sepertinya timetidak menghitung waktu yang diperlukan untuk mengeksekusi bar.shmeskipun menunggu sampai selesai sebelum mencetak laporannya 1 .

Semua tes dijalankan pada sistem Arch dan menggunakan bash 4.4.12 (1) -release. Saya hanya dapat menggunakan bash untuk proyek ini adalah bagian dari jadi bahkan jika zshatau beberapa shell kuat lainnya dapat mengatasinya, itu tidak akan menjadi solusi yang layak untuk saya.

Jadi, bagaimana saya bisa mendapatkan waktu yang dibutuhkan seperangkat perintah pipa untuk dijalankan? Dan, sementara kita melakukannya, mengapa itu tidak berhasil? Sepertinya timesegera keluar segera setelah perintah pertama selesai. Mengapa?

Saya tahu saya bisa mendapatkan waktu individu dengan sesuatu seperti ini:

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

Tapi saya masih ingin tahu apakah mungkin mengatur waktu semuanya sebagai satu operasi.


1 Ini sepertinya bukan masalah penyangga, saya mencoba menjalankan skrip dengan unbuffereddan stdbuf -i0 -o0 -e0dan angkanya masih dicetak sebelum timehasilnya.

terdon
sumber
Sudahkah Anda mencobanya dengan stopwatch fisik?
pericynthion
@pericynthion ya akhirnya saya lakukan. Dan itu juga menunjukkan apa yang dijelaskan oleh jawaban: waktu sebenarnya bekerja tetapi (jelas cukup dan seperti yang seharusnya saya sadari) perintah-perintah dalam pipa berjalan bersamaan sehingga waktu yang diambil pada dasarnya adalah waktu yang paling lambat.
terdon

Jawaban:

33

Hal ini bekerja.

Bagian-bagian berbeda dari sebuah pipa dieksekusi bersamaan. Satu-satunya hal yang menyinkronkan / membuat serialisasi proses dalam pipa adalah IO, yaitu satu proses menulis ke proses berikutnya dalam pipa dan proses selanjutnya membaca apa yang ditulis pertama. Selain itu, mereka mengeksekusi secara independen satu sama lain.

Karena tidak ada pembacaan atau penulisan yang terjadi di antara proses-proses dalam pipeline Anda, waktu yang diperlukan untuk mengeksekusi pipeline adalah sleeppanggilan yang terlama .

Anda mungkin juga telah menulis

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

Terdon memposting beberapa skrip contoh yang sedikit dimodifikasi dalam obrolan :

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

dan

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

Pertanyaannya adalah "mengapa time ( sh foo.sh | sh bar.sh )kembali 4 detik daripada 3 + 3 = 6 detik?"

Untuk melihat apa yang terjadi, termasuk perkiraan waktu setiap perintah dieksekusi, seseorang dapat melakukan ini (output berisi anotasi saya):

$ 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

Jadi, untuk menyimpulkan, pipa membutuhkan 4 detik, bukan 6, karena buffering dari output dari dua panggilan pertama echomasuk foo.sh.

Kusalananda
sumber
1
@terdon nilai-nilai adalah jumlah, tetapi skrip mengambil sangat sedikit waktu pengguna dan sistem - mereka hanya menunggu, yang tidak masuk hitungan (kecuali dalam waktu jam dinding).
Stephen Kitt
2
Perhatikan bahwa beberapa shell seperti shell Bourne atau ksh93hanya menunggu komponen terakhir dari pipa ( sleep 3 | sleep 1akan bertahan 1 detik). shell Bourne tidak memiliki timekata kunci, tetapi pada ksh93saat dijalankan dengan time, semua komponen menunggu.
Stéphane Chazelas
3
Saya hanya mengatakan bahwa orang mungkin terkejut menemukan bahwa itu sleep 10 | sleep 1membutuhkan waktu satu detik, sementara time sleep 10 | sleep 1butuh 10 detik di ksh93. Dalam shell Bourne time sleep 10 | sleep 1akan mengambil satu detik, tetapi Anda akan mendapatkan output waktu ( sleep 10hanya untuk dan dari /usr/bin/time) keluar dari biru 9 detik kemudian.
Stéphane Chazelas
1
Itu bukan tentang menjaga apa pun. timekali benar pipa, tetapi mengubah perilaku shell di ksh93. (sleep 10 | sleep 1)butuh 1 detik, time (sleep 10 | sleep 1)butuh 10 detik. { (sleep 10 | sleep 1); echo x; }output xsetelah 1 detik, time { (sleep 10 | sleep 1); echo x; }output xsetelah 10 detik. Sama jika Anda meletakkan kode itu di fungsi dan waktu fungsi.
Stéphane Chazelas
1
Perhatikan bahwa dalam ksh93seperti di zsh( di -o promptsubstsini), Anda dapat melakukan typeset -F SECONDSuntuk mendapatkan perkiraan jumlah detik yang kurang (POSIX shtidak memiliki SECONDS)
Stéphane Chazelas
10

Apakah ini contoh yang lebih baik?

$ 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

Script busyloop selama 3 dan 4 detik (resp.), Mengambil total 4 detik secara real time karena eksekusi paralel, dan 7 detik waktu CPU. (setidaknya sekitar.)

Atau ini:

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

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

Ini tidak berjalan secara paralel, sehingga total waktu yang diambil adalah 5 detik. Semuanya dihabiskan untuk tidur, jadi tidak ada waktu CPU yang digunakan.

ilkkachu
sumber
3

Jika sudah, sysdigAnda dapat memasukkan pelacak pada titik-titik sembarang, dengan asumsi Anda dapat memodifikasi kode untuk menambahkan penulisan yang diperlukan/dev/null

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

(tapi itu gagal "operasi tunggal" persyaratan Anda) dan kemudian merekam hal-hal melalui

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

dan kemudian Anda mungkin perlu pahat sysdig untuk mengekspor hanya durasinya

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

yang pernah disimpan ke sysdig/chiselsdirektori Anda sebagai file spantagduration.luadapat digunakan sebagai

$ sysdig -r blalog -c spantagduration
...

Atau Anda bisa bermain-main dengan csysdigatau output JSON.

thrig
sumber