ฉันจะจับเวลาท่อได้อย่างไร


27

ฉันต้องการtimeคำสั่งที่ประกอบด้วยคำสั่งสองคำสั่งแยกกันโดยมีหนึ่ง piping output ไปยังอีกคำสั่งหนึ่ง ตัวอย่างเช่นพิจารณาสองสคริปต์ด้านล่าง:

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

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

ตอนนี้ฉันtimeจะรายงานเวลาที่ใช้ไปได้อย่างไรfoo.sh | bar.sh(และใช่ฉันรู้แล้วว่าไพพ์ไม่มีความหมายที่นี่ แต่นี่เป็นเพียงตัวอย่าง) มันทำงานได้อย่างที่คาดไว้ถ้าฉันใช้มันต่อเนื่องใน subshell โดยไม่ต้อง piping:

$ time ( foo.sh; bar.sh )

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

แต่ฉันไม่สามารถทำให้มันทำงานได้เมื่อ piping:

$ 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

มันไม่ทำงานเลยถ้าฉันสร้างสคริปต์ที่สามซึ่งรันไปป์เท่านั้น:

$ 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

ดูเหมือนtimeจะไม่นับรวมระยะเวลาที่ใช้ในการดำเนินการbar.shแม้จะรอให้เสร็จสิ้นก่อนที่จะพิมพ์รายงาน1

การทดสอบทั้งหมดถูกเรียกใช้บนระบบ Arch และใช้ bash 4.4.12 (1) -release ฉันสามารถใช้ bash สำหรับโปรเจ็กต์นี้ได้เพียงบางส่วนเท่านั้นแม้ว่าzshบางอันที่ทรงพลังเชลล์สามารถไปถึงที่ ๆ มันจะไม่เป็นทางออกสำหรับฉัน

ดังนั้นฉันจะได้รับเวลาที่คำสั่ง piped ใช้เพื่อให้ทำงานได้อย่างไร และในขณะที่เราอยู่ที่นี่ทำไมมันไม่ทำงาน ดูเหมือนว่าtimeจะจบการทำงานทันทีที่คำสั่งแรกเสร็จสิ้น ทำไม?

ฉันรู้ว่าฉันสามารถทำให้แต่ละครั้งด้วยสิ่งนี้:

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

แต่ฉันก็ยังอยากจะรู้ว่ามันเป็นไปได้ที่จะใช้เวลาทั้งหมดในการทำงานครั้งเดียว


1 สิ่งนี้ดูเหมือนจะไม่เป็นปัญหาของบัฟเฟอร์ฉันพยายามเรียกใช้สคริปต์ด้วยunbufferedและstdbuf -i0 -o0 -e0ตัวเลขยังคงถูกพิมพ์ก่อนที่จะtimeส่งออก


คุณเคยลองนาฬิกาจับเวลาจริงหรือไม่?
pericynthion

@pericynthion อ๋อในที่สุดฉันก็ทำ และนั่นก็แสดงให้เห็นถึงสิ่งที่คำตอบอธิบาย: เวลาทำงานจริง แต่ (เห็นได้ชัดพอและอย่างที่ฉันควรจะรู้) คำสั่งในไปป์ไลน์ทำงานพร้อมกันดังนั้นเวลาที่ใช้จึงเป็นเวลาที่ช้าที่สุด
terdon

คำตอบ:


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 )กลับ 4 วินาทีแทนที่จะ 3 + 3 = 6 วินาที"

หากต้องการดูสิ่งที่เกิดขึ้นรวมถึงเวลาโดยประมาณที่คำสั่งแต่ละคำสั่งจะถูกดำเนินการอย่างใดอย่างหนึ่งอาจทำเช่นนี้ (ผลลัพธ์ที่มีคำอธิบายประกอบของฉัน):

$ 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

ดังนั้นเพื่อสรุปท่อใช้เวลา 4 วินาทีไม่ 6 เนื่องจากบัฟเฟอร์ของการส่งออกของทั้งสองสายแรกไปในechofoo.sh


1
@terdon ค่าเป็นผลรวม แต่สคริปต์ใช้เวลาผู้ใช้และระบบน้อยมาก - พวกเขารอซึ่งไม่นับ (ยกเว้นในเวลาที่นาฬิกาแขวน)
Stephen Kitt

2
โปรดทราบว่าเชลล์บางตัวเช่น Bourne shell หรือksh93รอเพียงองค์ประกอบสุดท้ายของไปป์ไลน์ ( sleep 3 | sleep 1จะมีอายุ 1 วินาที) เชลล์เป้าหมายไม่มีtimeคำหลัก แต่ในksh93เมื่อใช้timeงานคอมโพเนนต์ทั้งหมดจะถูกรอ
Stéphane Chazelas

3
ฉันแค่บอกว่าอาจจะแปลกใจที่พบว่าsleep 10 | sleep 1ใช้เวลาหนึ่งวินาทีในขณะที่time sleep 10 | sleep 1ใช้เวลา 10 วินาทีใน ksh93 ในเชลล์เป้าหมายtime sleep 10 | sleep 1จะใช้เวลาหนึ่งวินาที แต่คุณจะได้เวลาออก ( sleep 10จากและจาก/usr/bin/time) ออกจากสีน้ำเงิน 9 วินาทีในภายหลัง
Stéphane Chazelas

1
นั่นไม่เกี่ยวกับการปกป้องอะไรเลย timeถูกต้องคูณไพพ์ไลน์ แต่เปลี่ยนพฤติกรรมของเชลล์ใน ksh93 (sleep 10 | sleep 1)ใช้เวลา 1 วินาทีtime (sleep 10 | sleep 1)ใช้เวลา 10 วินาที { (sleep 10 | sleep 1); echo x; }เอาต์พุตxหลังจาก 1 วินาที, time { (sleep 10 | sleep 1); echo x; }เอาต์พุตxหลังจาก 10 วินาที เหมือนกันถ้าคุณใส่รหัสนั้นในฟังก์ชั่นและเวลาฟังก์ชั่น
Stéphane Chazelas

1
โปรดทราบว่าในksh93ทำนองเดียวกันzsh( -o promptsubstที่นี่) คุณสามารถทำได้typeset -F SECONDSเพื่อให้ได้จำนวนวินาทีโดยประมาณน้อยกว่า(POSIX shไม่มีSECONDS)
Stéphane 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"

และจากนั้นคุณอาจจะต้องใช้สิ่วสิ่วเพื่อส่งออกเพียงระยะเวลา

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.