Comment savoir si la mémoire tampon du tuyau est pleine?

11

Je redirige la sortie d'un programme vers un Perl que j'ai écrit. C'est un processus long, parfois des jours, donc je veux savoir où sont mes goulots d'étranglement et essayer de les ouvrir. Je veux savoir si les données sont transférées dans mon script plus rapidement que mon script ne peut les traiter. Si c'est le cas, j'essaierai de peaufiner mon script, mais pas si je n'en ai pas besoin. Je vois parler d'un indicateur défini lorsque le tampon est plein, ce qui empêche d'autres écritures, mais comment puis-je vérifier si ou à quelle fréquence cet indicateur est défini? Des idées?

Dan Sphar
la source
3
Je pense que seul le processus d'écriture pourrait le savoir.
enzotib
5
Vous pourriez envisager d'utiliser pvquelque part le long de la chaîne de tuyaux.
amphetamachine

Réponses:

9

Je tracerais votre script Perl avec un outil de trace d'appel système: strace(Linux), dtruss(OS X), ktrace(FreeBSD), truss(Solaris), etc. Le but serait de voir combien de temps votre script Perl passe à attendre la lecture de son stdin et combien de temps l'autre programme passe à attendre l'écriture sur sa sortie standard.

Ici, je teste cela avec l'écrivain comme goulot d'étranglement:

terminal 1$ gzip -c < /dev/urandom | cat > /dev/null

terminal 2$ ps auxw | egrep 'gzip|cat'
slamb    25311 96.0  0.0  2852  516 pts/0    R+   23:35   3:40 gzip -c
slamb    25312  0.8  0.0  2624  336 pts/0    S+   23:35   0:01 cat

terminal 2$ strace -p 25312 -s 0 -rT -e trace=read
Process 25312 attached - interrupt to quit
     0.000000 read(0, ""..., 4096) = 4096 <0.005207>
     0.005531 read(0, ""..., 4096) = 4096 <0.000051>

Le premier nombre ici est le temps écoulé depuis le début du précédent appel système, et le dernier nombre est le temps passé dans l'appel système. Nous pouvons donc un peu post-traiter avec Perl pour l'agréger ... [*]

terminal 2$ strace -p 25312 -s 0 -rT -e trace=read 2>&1 | perl -nle 'm{^\s*([\d.]+) read\(0, .*<([\d.]+)>} or next; $total_work += $1 - $last_wait; $total_wait += $2; $last_wait = $2; print "working for $total_work sec, waiting for $total_wait sec"; $last_wait = $2;'
working for 0 sec, waiting for 0.005592 sec
...
working for 0.305356 sec, waiting for 2.28624900000002 sec
...

terminal 2$ strace -p 25311 -s 0 -rT -e trace=write 2>&1 | perl -nle 'm{^\s*([\d.]+) write\(1, .*<([\d.]+)>} or next; $total_work += $1 - $last_wait; $total_wait += $2; $last_wait = $2; print "working for $total_work sec, waiting for $total_wait sec"; $last_wait = $2;'
...
working for 5.15862000000001 sec, waiting for 0.0555740000000007 sec
...

Vous pourriez devenir plus sophistiqué et créer un script SystemTap ou DTrace qui trace les deux côtés à la fois, ne suit que le descripteur de fichier correct et imprime une belle mise à jour de statut toutes les secondes environ avec quel pourcentage de temps chacun attendait l'autre.

[*] - Attention: mon agrégation grossière n'est pas tout à fait correcte si la lecture / écriture est appelée sur d'autres descripteurs de fichiers; il sous-estimera le temps de travail dans ce cas.


La version dtrace est plutôt soignée en fait.

terminal 1$ gzip -c < /dev/urandom | cat > /dev/null

terminal 2$ ps aux | egrep 'gzip| cat'
slamb    54189  95.8  0.0   591796    584 s006  R+   12:49AM  22:49.55 gzip -c
slamb    54190   0.4  0.0   599828    392 s006  S+   12:49AM   0:06.08 cat

terminal 2$ cat > pipe.d <<'EOF'
#!/usr/sbin/dtrace -qs

BEGIN
{
  start = timestamp;
  writer_blocked = 0;
  reader_blocked = 0;
}

tick-1s, END
{
  this->elapsed = timestamp - start;
  printf("since startup, writer blocked %3d%% of time, reader %3d%% of time\n",
         100 * writer_blocked / this->elapsed,
         100 * reader_blocked / this->elapsed);
}

syscall::write:entry
/pid == $1 && arg0 == 1/
{
  self->entry = timestamp;
}

syscall::write:return
/pid == $1 && self->entry != 0/
{
  writer_blocked += timestamp - self->entry;
  self->entry = 0;
}

syscall::read:entry
/pid == $2 && arg0 == 0/
{
  self->entry = timestamp;
}

syscall::read:return
/pid == $2 && self->entry != 0/
{
  reader_blocked += timestamp - self->entry;
  self->entry = 0;
}
EOF

terminal 2$ chmod u+x pipe.d
terminal 2$ sudo ./pipe.d 54189 54190
since startup, writer blocked   0% of time, reader  98% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
^C
since startup, writer blocked   0% of time, reader  99% of time

Et la version SystemTap:

terminal 1$ gzip -c /dev/urandom | cat > /dev/null

terminal 2$ ps auxw | egrep 'gzip| cat'
slamb     3405  109  0.0   4356   584 pts/1    R+   02:57   0:04 gzip -c /dev/urandom
slamb     3406  0.2  0.0  10848   588 pts/1    S+   02:57   0:00 cat

terminal 2$ cat > probes.stp <<'EOF'
#!/usr/bin/env stap

global start
global writer_pid
global writes
global reader_pid
global reads

probe begin {
  start = gettimeofday_us()
  writer_pid = strtol(argv[1], 10)
  reader_pid = strtol(argv[2], 10)
}

probe timer.s(1), end {
  elapsed = gettimeofday_us() - start
  printf("since startup, writer blocked %3d%% of time, reader %3d%% of time\n",
         100 * @sum(writes) / elapsed,
         100 * @sum(reads) / elapsed)
}

probe syscall.write.return {
  if (pid() == writer_pid && $fd == 1)
    writes <<< gettimeofday_us() - @entry(gettimeofday_us())
}

probe syscall.read.return {
  if (pid() == reader_pid && $fd == 0)
    reads <<< gettimeofday_us() - @entry(gettimeofday_us())
}
EOF

terminal 2$ chmod a+x probes.stp
terminal 2$ sudo ./pipe.stp 3405 3406
since startup, writer blocked   0% of time, reader  99% of time
...
Scott Lamb
la source
6

Vous pouvez insérer une pv -TCcommande dans votre pipeline:

cmd1 | pv -TC | cmd2

pvutilise son propre tampon et lui -Tfait rapporter combien il est plein en moyenne sur des périodes de 1 seconde (par défaut).

Si elle est toujours à 100%, cela signifie que la production cmd1est plus rapide que la cmd2consommation. Sinon, c'est l'inverse. Attention, les tuyaux eux-mêmes peuvent contenir 64 Ko.

Voir aussi -Bpour spécifier pvla taille du tampon. Vous pouvez utiliser plusieurs pvs comme dans:

$ cmd1 | pv -cCTrbN 'cmd1 -> cmd2' | cmd2 | pv -cCTrbN 'cmd2 -> cmd3' | cmd3
cmd1 -> cmd2: 1.92GiB { 53%} [ 387MiB/s]
cmd2 -> cmd3: 1.92GiB {  0%} [ 387MiB/s]
Stéphane Chazelas
la source