it-swarm.com.de

Wie kann ich eine Pfeife zeitlich festlegen?

Ich möchte time einen Befehl, der aus zwei separaten Befehlen mit einer Piping-Ausgabe zu einer anderen besteht. Betrachten Sie beispielsweise die beiden folgenden Skripte:

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

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

Wie kann ich nun time dazu bringen, die von foo.sh | bar.sh Benötigte Zeit zu melden (und ja, ich weiß, dass die Pipe hier keinen Sinn ergibt, aber dies ist nur ein Beispiel)? Es funktioniert wie erwartet, wenn ich sie nacheinander in einer Subshell ohne Piping ausführe:

$ time ( foo.sh; bar.sh )

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

Aber ich kann es nicht zum Laufen bringen, wenn ich Rohrleitungen verlege:

$ 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

Ich habe eine ähnliche Frage gelesen ( Wie wird die Zeit für mehrere Befehle ausgeführt UND die Zeitausgabe in eine Datei geschrieben? ) und auch die eigenständige ausführbare Datei time ausprobiert:

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

Es funktioniert nicht einmal, wenn ich ein drittes Skript erstelle, das nur die Pipe ausführt:

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

Und dann mal das:

$ time baz.sh

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

Interessanterweise sieht es nicht so aus, als würde time beendet, sobald der erste Befehl ausgeführt wird. Wenn ich bar.sh Ändere zu:

#!/bin/sh
sleep 2
seq 1 5

Und dann time wieder, ich hatte erwartet, dass die time Ausgabe vor dem seq gedruckt wird, aber es ist nicht:

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

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

Es sieht so aus, als würde time die Zeit für die Ausführung von bar.sh Nicht zählen, obwohl vor dem Drucken des Berichts auf den Abschluss gewartet wurde1.

Alle Tests wurden auf einem Arch-System und unter Verwendung der Bash 4.4.12 (1) -Version durchgeführt. Ich kann bash nur für das Projekt verwenden, zu dem dies gehört. Selbst wenn zsh oder eine andere leistungsstarke Shell es umgehen kann, ist dies für mich keine praktikable Lösung.

Wie kann ich die Zeit ermitteln, die ein Satz von Piped-Befehlen für die Ausführung benötigt hat? Und wenn wir schon dabei sind, warum funktioniert es nicht? Es sieht so aus, als würde time sofort beendet, sobald der erste Befehl beendet ist. Warum?

Ich weiß, dass ich mit so etwas die einzelnen Zeiten erreichen kann:

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

Aber ich würde immer noch gerne wissen, ob es möglich ist, das Ganze als eine einzige Operation zu planen.


1Dies scheint kein Pufferproblem zu sein. Ich habe versucht, die Skripte mit unbuffered und stdbuf -i0 -o0 -e0 Auszuführen, und die Zahlen wurden noch vor der Ausgabe von time gedruckt.

28
terdon

Es funktioniert .

Die verschiedenen Teile einer Pipeline werden gleichzeitig ausgeführt. Das einzige, was die Prozesse in der Pipeline synchronisiert/serialisiert, ist E/A, d. H. Ein Prozess, der in den nächsten Prozess in der Pipeline schreibt, und der nächste Prozess, der liest, was der erste schreibt. Abgesehen davon führen sie unabhängig voneinander aus.

Da zwischen den Prozessen in Ihrer Pipeline kein Lesen oder Schreiben stattfindet, ist die Zeit für die Ausführung der Pipeline die des längsten sleep -Aufrufs.

Du hättest genauso gut schreiben können

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

Terdon hat ein paar leicht modifizierte Beispielskripte im Chat gepostet :

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

und

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

Die Abfrage lautete: "Warum gibt time ( sh foo.sh | sh bar.sh ) 4 Sekunden zurück und nicht 3 + 3 = 6 Sekunden?"

Um zu sehen, was passiert, einschließlich der ungefähren Zeit, zu der jeder Befehl ausgeführt wird, kann man dies tun (die Ausgabe enthält meine Anmerkungen):

$ 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

Zusammenfassend lässt sich sagen, dass die Pipeline aufgrund der Pufferung der Ausgabe der ersten beiden Aufrufe von echo in foo.sh 4 Sekunden und nicht 6 Sekunden dauert.

35
Kusalananda

Wäre das ein besseres Beispiel?

$ 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

Die Skripte werden 3 bzw. 4 Sekunden lang ausgeführt, was aufgrund der parallelen Ausführung insgesamt 4 Sekunden in Echtzeit und 7 Sekunden CPU-Zeit in Anspruch nimmt. (zumindest ungefähr.)

Oder dieses:

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

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

Diese laufen nicht parallel, sodass die Gesamtzeit 5 Sekunden beträgt. Es wird alles geschlafen, daher wird keine CPU-Zeit benötigt.

10
ilkkachu

Wenn Sie sysdig haben, können Sie Tracer einfügen an beliebigen Punkten, vorausgesetzt, Sie können den Code ändern, um die erforderlichen Schreibvorgänge zu /dev/null Hinzuzufügen.

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

(aber das scheitert an Ihrer "Einzeloperation" -Anforderung) und zeichnen Sie dann Dinge über auf

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

und dann benötigen Sie wahrscheinlich einen Sysdig-Meißel, um nur die Dauer zu exportieren

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

die einmal in Ihrem sysdig/chisels Verzeichnis als Datei spantagduration.lua gespeichert wurde, kann als verwendet werden

$ sysdig -r blalog -c spantagduration
...

Oder Sie können mit csysdig oder der JSON-Ausgabe herumspielen.

3
thrig

Zu lustig, ich weiß, dass dies ein alter Thread ist, aber ich befand mich in der gleichen Situation. Ich fand heraus, dass Aliase eine einfache Problemumgehung sind. Zumindest funktioniert dies bei Bash und Fisch. Ich bin mir nicht sicher über alle Muscheln.

Anstelle von: time ( foo.sh | bar.sh )

Versuchen:

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

0
BoeroBoy