¿Cómo puedo saber si el buffer de la tubería está lleno?

11

Estoy canalizando la salida de un programa a algún Perl que escribí. Este es un proceso de larga duración, a veces días, por lo que quiero saber dónde están mis cuellos de botella e intentar abrirlos. Quiero saber si los datos se están canalizando a mi script más rápido de lo que mi script puede procesarlo. Si este es el caso, intentaré afinar mi script, pero no si no lo necesito. Veo hablar de que se establece un indicador cuando el búfer está lleno, lo que impide que se escriban más, pero ¿cómo puedo verificar si o con qué frecuencia se establece ese indicador? ¿Algunas ideas?

Dan Sphar
fuente
3
Creo que solo el proceso de escritura podría saberlo.
enzotib
55
Podrías buscar usar en pvalgún lugar a lo largo de la cadena de tuberías.
anfetamáquinas

Respuestas:

9

Rastrearía su script Perl con una herramienta de rastreo de llamadas del sistema: strace(Linux), dtruss(OS X), ktrace(FreeBSD), truss(Solaris), etc. El objetivo sería ver cuánto tiempo pasa su script Perl esperando a leer de su stdin y cuánto tiempo pasa el otro programa esperando a escribir en su stdout.

Aquí estoy probando esto con el escritor como el cuello de botella:

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>

El primer número aquí es el tiempo desde el inicio de la llamada al sistema anterior, y el último número es el tiempo que pasó en la llamada al sistema. Entonces podemos postprocesar con Perl un poco para agregarlo ... [*]

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
...

Podría volverse más elegante y crear un script SystemTap o DTrace que rastrea ambos lados a la vez, solo rastrea el descriptor de archivo correcto e imprime una actualización de estado agradable cada segundo más o menos con qué porcentaje de tiempo cada uno estaba esperando al otro.

[*] - Advertencia: mi agregación cruda no es del todo correcta si se llama a lectura / escritura en otros descriptores de archivo; subestimará el tiempo de trabajo en ese caso.


La versión dtrace es bastante bonita en realidad.

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

Y la versión de 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
fuente
6

Puede insertar un pv -TCcomando en su tubería:

cmd1 | pv -TC | cmd2

pvusa su propio búfer y -Tle informa que está lleno en promedio en períodos de 1 segundo (por defecto).

Si siempre es 100%, entonces eso significa que cmd1es más rápido en producir resultados que cmd2en consumirlos. Si no, entonces es al revés. Tenga en cuenta que las tuberías pueden contener 64kB.

Consulte también -Bpara especificar pvel tamaño del búfer. Puede usar varios pvs como en:

$ 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
fuente