Come posso cronometrare una pipa?


27

Voglio timeun comando che consiste in due comandi separati con un output di piping a un altro. Ad esempio, considera i due script seguenti:

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

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

Ora, come posso timecomunicare il tempo impiegato foo.sh | bar.sh(e sì, so che la pipa non ha senso qui, ma questo è solo un esempio)? Funziona come previsto se li eseguo in sequenza in una subshell senza piping:

$ time ( foo.sh; bar.sh )

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

Ma non riesco a farlo funzionare durante il 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

Ho letto una domanda simile ( Come eseguire il tempo su più comandi E scrivere l'output del tempo su file? ) E ho anche provato l' timeeseguibile standalone :

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

Non funziona nemmeno se creo un terzo script che esegue solo la pipe:

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

E poi il tempo che:

$ time baz.sh

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

È interessante notare che non sembra che timeesca non appena viene eseguito il primo comando. Se cambio bar.sha:

#!/bin/sh
sleep 2
seq 1 5

E poi time, mi aspettavo che l' timeoutput venisse stampato prima del seqma non lo è:

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

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

Sembra timeche non conti il ​​tempo impiegato per l'esecuzione bar.shnonostante l'attesa che finisca prima di stampare il suo rapporto 1 .

Tutti i test sono stati eseguiti su un sistema Arch e usando bash 4.4.12 (1)-release. Posso usare solo bash per il progetto di cui fa parte, quindi anche se zsho qualche altra shell potente può aggirarlo, non sarà una soluzione praticabile per me.

Quindi, come posso ottenere il tempo necessario per l'esecuzione di una serie di comandi inoltrati? E, mentre ci siamo, perché non funziona? Sembra che timeesca immediatamente non appena il primo comando è terminato. Perché?

So di poter ottenere i singoli tempi con qualcosa del genere:

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

Ma vorrei ancora sapere se è possibile programmare l'intera operazione come un'unica operazione.


1 Questo non sembra essere un problema di buffer, ho provato a fare funzionare gli script con unbufferede stdbuf -i0 -o0 -e0ed i numeri erano ancora stampati prima della timeuscita.


L'hai provato con un cronometro fisico?
pericynthion,

@pericynthion sì, alla fine l'ho fatto. E questo ha anche mostrato ciò che spiegano le risposte: il tempo sta effettivamente funzionando ma (ovviamente abbastanza e come avrei dovuto capire) i comandi nella pipeline vengono eseguiti contemporaneamente, quindi il tempo impiegato è essenzialmente il tempo più lento.
terdon

Risposte:


33

Si sta lavorando.

Le diverse parti di una pipeline vengono eseguite contemporaneamente. L'unica cosa che sincronizza / serializza i processi nella pipeline è IO, ovvero un processo che scrive al processo successivo nella pipeline e il processo successivo che legge ciò che scrive il primo. A parte questo, stanno eseguendo indipendentemente l' uno dall'altro.

Poiché non esiste alcuna lettura o scrittura tra i processi nella pipeline, il tempo necessario per eseguire la pipeline è quello della sleepchiamata più lunga .

Potresti anche aver scritto

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

Terdon ha pubblicato un paio di script di esempio leggermente modificati nella chat :

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

e

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

La query era "perché time ( sh foo.sh | sh bar.sh )restituisce 4 secondi anziché 3 + 3 = 6 secondi?"

Per vedere cosa sta succedendo, incluso il tempo approssimativo di esecuzione di ciascun comando, è possibile farlo (l'output contiene le mie annotazioni):

$ 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

Quindi, per concludere, la conduttura prende 4 secondi, non 6, a causa del buffer di uscita dei primi due richiami echoa foo.sh.


1
@terdon i valori sono le somme, ma gli script richiedono pochissimo tempo per l'utente e il sistema - semplicemente aspettano, il che non conta (tranne che nel tempo dell'orologio da parete).
Stephen Kitt,

2
Si noti che alcune shell come la shell Bourne o ksh93aspettano solo l'ultimo componente della pipeline ( sleep 3 | sleep 1durerebbe 1 secondo). la shell Bourne non ha timeparole chiave, ma in ksh93, quando eseguito con time, tutti i componenti sono attesi.
Stéphane Chazelas,

3
Sto solo dicendo che uno potrebbe essere sorpreso di scoprire che ci sleep 10 | sleep 1vuole un secondo mentre ci time sleep 10 | sleep 1vogliono 10 secondi in ksh93. Nella shell Bourne time sleep 10 | sleep 1ci vorrebbe un secondo, ma otterresti l'output di tempo ( sleep 10solo e da /usr/bin/time) fuori dal blu 9 secondi dopo.
Stéphane Chazelas,

1
Non si tratta di proteggere nulla. timetempi correttamente la pipeline, ma cambia il comportamento della shell in ksh93. (sleep 10 | sleep 1)richiede 1 secondo, time (sleep 10 | sleep 1)richiede 10 secondi. { (sleep 10 | sleep 1); echo x; }uscite xdopo 1 secondo, time { (sleep 10 | sleep 1); echo x; }uscite xdopo 10 secondi. Lo stesso se metti quel codice in una funzione e cronometri la funzione.
Stéphane Chazelas,

1
Si noti che ksh93come in zsh( -o promptsubstqui), è possibile fare typeset -F SECONDSper ottenere un numero meno approssimativo di secondi (POSIX shnon ha SECONDS)
Stéphane Chazelas,

10

Questo sarebbe un esempio migliore?

$ 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

Gli script busyloop per 3 e 4 secondi (resp.), Impiegando un totale di 4 secondi in tempo reale a causa dell'esecuzione parallela e 7 secondi di tempo della CPU. (almeno circa.)

O questo:

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

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

Questi non funzionano in parallelo, quindi il tempo totale impiegato è di 5 secondi. È tutto speso dormendo, quindi non è stato utilizzato alcun tempo CPU.


3

Se hai sysdig, puoi inserire traccianti in punti arbitrari, supponendo che tu possa modificare il codice per aggiungere le scritture necessarie/dev/null

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

(ma questo non soddisfa il requisito della "singola operazione") e quindi registra le cose tramite

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

e quindi probabilmente avrai bisogno di uno scalpello sysdig per esportare solo le durate

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

che una volta salvato nella tua sysdig/chiselsdirectory come file spantagduration.luapuò essere utilizzato come

$ sysdig -r blalog -c spantagduration
...

Oppure puoi giocare con csysdigo l'output JSON.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.