我该如何计时管道?


27

我想要time一个命令,该命令由两个单独的命令组成,一个管道输出到另一个管道。例如,考虑以下两个脚本:

$ 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

如果我创建仅运行管道的第三个脚本,它甚至不起作用:

$ 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有其他功能强大的shell可以解决它,这对我也不是可行的解决方案。

那么,如何获得一组管道命令运行所需的时间?而且,当我们使用它时,为什么它不起作用?它看起来像time马上就第一个命令完成退出。为什么?

我知道我可以通过以下方式获得个人时间:

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

但是我仍然想知道是否有可能将整个事情作为一个单一的操作进行计时。


1 这似乎不是缓冲区问题,我尝试使用unbuffered和运行脚本,并且stdbuf -i0 -o0 -e0仍然在time输出之前打印数字。


您是否尝试过物理秒表?
pericynthion

@pericynthion是的,最终我做到了。而且,这也表明了答案的解释:时间实际上是有效的,但是(显然,正如我应该意识到的那样),管道中的命令是同时运行的,因此所花费的时间实质上是最慢的时间。
terdon

Answers:


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

因此,总而言之,由于缓冲了对echoin 的前两个调用的输出,因此流水线需要4秒钟而不是6秒钟foo.sh


1
@terdon值是总和,但是脚本占用的用户和系统时间很少-它们只是等待而已,这不会计数(墙上时钟时间除外)。
史蒂芬·基特

2
请注意,某些外壳(例如Bourne外壳)或ksh93仅等待管道的最后一个组件(sleep 3 | sleep 1将持续1秒)。Bourne shell没有time关键字,但是在中ksh93,当运行时time,将等待所有组件。
斯特凡Chazelas

3
我只是说,在ksh93 中sleep 10 | sleep 1花一秒钟而time sleep 10 | sleep 1花10秒可能会感到惊讶。在Bourne shell time sleep 10 | sleep 1中将花费一秒钟,但是9秒钟后,您将获得时间输出(sleep 10仅用于和/usr/bin/time)。
斯特凡Chazelas

1
那不是在保护任何东西。time正确地对管道进行计时,但会更改ksh93中shell的行为。(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秒后。如果将代码放在函数中并计时函数,则相同。
斯特凡Chazelas

1
需要注意的是在ksh93zsh-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

脚本busyloop持续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输出。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.