Je veux time
une commande qui se compose de deux commandes distinctes avec une sortie de tuyauterie à l'autre. Par exemple, considérez les deux scripts ci-dessous:
$ cat foo.sh
#!/bin/sh
sleep 4
$ cat bar.sh
#!/bin/sh
sleep 2
Maintenant, comment puis-je demander à time
de signaler le temps pris par foo.sh | bar.sh
(et oui, je sais que la pipe n'a aucun sens ici, mais ce n'est qu'un exemple)? Cela fonctionne comme prévu si je les exécute séquentiellement dans un sous-shell sans tuyauterie:
$ time ( foo.sh; bar.sh )
real 0m6.020s
user 0m0.010s
sys 0m0.003s
Mais je ne peux pas le faire fonctionner lors de la tuyauterie:
$ 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
J'ai lu une question similaire ( Comment exécuter le temps sur plusieurs commandes ET écrire la sortie de temps dans un fichier? ) et j'ai également essayé l'exécutable autonome time
:
$ /usr/bin/time -p sh -c "foo.sh | bar.sh"
real 4.01
user 0.00
sys 0.00
Cela ne fonctionne même pas si je crée un troisième script qui exécute uniquement le canal:
$ cat baz.sh
#!/bin/sh
foo.sh | bar.sh
Et puis le temps que:
$ time baz.sh
real 0m4.009s
user 0m0.003s
sys 0m0.000s
Fait intéressant, il ne semble pas que time
se termine dès que la première commande est exécutée. Si je change bar.sh
à:
#!/bin/sh
sleep 2
seq 1 5
Et puis time
à nouveau, je m'attendais à ce que la sortie de time
soit imprimée avant le seq
mais ce n'est pas le cas:
$ time ( { foo.sh | bar.sh; } )
1
2
3
4
5
real 0m4.005s
user 0m0.003s
sys 0m0.000s
Il semble que time
ne compte pas le temps nécessaire pour exécuter bar.sh
malgré l'attente de la fin avant d'imprimer son rapport1.
Tous les tests ont été exécutés sur un système Arch et en utilisant la version bash 4.4.12 (1). Je ne peux utiliser que bash pour le projet dont il fait partie, même si zsh
ou un autre shell puissant peut le contourner, ce ne sera pas une solution viable pour moi.
Alors, comment puis-je obtenir le temps nécessaire à l'exécution d'un ensemble de commandes dirigées? Et pendant que nous y sommes, pourquoi ça ne marche pas? Il semble que time
se ferme immédiatement dès que la première commande est terminée. Pourquoi?
Je sais que je peux obtenir les temps individuels avec quelque chose comme ça:
( time foo.sh ) 2>foo.time | ( time bar.sh ) 2> bar.time
Mais je voudrais quand même savoir s'il est possible de chronométrer le tout en une seule opération.
1Cela ne semble pas être un problème de tampon, j'ai essayé d'exécuter les scripts avec unbuffered
et stdbuf -i0 -o0 -e0
et les chiffres étaient toujours imprimés avant la sortie time
.
Il fonctionne .
Les différentes parties d'un pipeline sont exécutées simultanément. La seule chose qui synchronise/sérialise les processus dans le pipeline est IO, c'est-à-dire qu'un processus écrit dans le processus suivant dans le pipeline et le processus suivant lit ce que le premier écrit. En dehors de cela, ils s'exécutent indépendamment les uns des autres.
Puisqu'il n'y a pas de lecture ou d'écriture entre les processus de votre pipeline, le temps nécessaire pour exécuter le pipeline est celui de l'appel sleep
le plus long.
Vous pourriez aussi bien avoir écrit
time ( foo.sh & bar.sh &; wait )
Terdon a posté quelques exemples de scripts légèrement modifiés dans le chat :
#!/bin/sh
# This is "foo.sh"
echo 1; sleep 1
echo 2; sleep 1
echo 3; sleep 1
echo 4
et
#!/bin/sh
# This is "bar.sh"
sleep 2
while read line; do
echo "LL $line"
done
sleep 1
La requête était "pourquoi time ( sh foo.sh | sh bar.sh )
retourne 4 secondes au lieu de 3 + 3 = 6 secondes?"
Pour voir ce qui se passe, y compris l'heure approximative à laquelle chaque commande est exécutée, on peut le faire (la sortie contient mes annotations):
$ 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
Donc, pour conclure, le pipeline prend 4 secondes, pas 6, en raison de la mise en mémoire tampon de la sortie des deux premiers appels à echo
dans foo.sh
.
Serait-ce un meilleur exemple?
$ 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
Les scripts busyloop pendant 3 et 4 secondes (resp.), Ce qui prend un total de 4 secondes en temps réel en raison de l'exécution parallèle, et 7 secondes de temps CPU. (au moins environ.)
Ou ca:
$ time ( sleep 2; echo) | ( read x; sleep 3 )
real 0m5.004s
user 0m0.000s
sys 0m0.000s
Ceux-ci ne fonctionnent pas en parallèle, donc le temps total pris est de 5 secondes. Tout est passé à dormir, donc pas de temps CPU utilisé.
Si vous avez sysdig
, vous pouvez insérer des traceurs à des points arbitraires, en supposant que vous pouvez modifier le code pour ajouter les écritures nécessaires à /dev/null
echo '>::blah::' >/dev/null
foo.sh | bar.sh
echo '<::blah::' >/dev/null
(mais cela échoue à votre exigence "d'opération unique"), puis enregistrez les choses via
$ Sudo sysdig -w blalog "span.tags contains blah"
puis vous aurez probablement besoin d'un burin sysdig pour exporter uniquement les durées
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
qui une fois enregistré dans votre sysdig/chisels
répertoire comme fichier spantagduration.lua
peut être utilisé comme
$ sysdig -r blalog -c spantagduration
...
Ou vous pouvez jouer avec csysdig
ou la sortie JSON.
Trop amusant je sais que c'est un vieux fil mais je me suis retrouvé dans la même situation. J'ai découvert que les alias sont une solution de contournement simple. Au moins, cela fonctionne pour bash et poisson. Pas sûr de tous les obus.
Au lieu de: time ( foo.sh | bar.sh )
Essayer:
alias foobar="foo.sh | bar.sh" time foobar