systemadmin.es > Utilidades > Estadísiticas de progreso con dd

Estadísiticas de progreso con dd

El programa dd es una utilidad que forma parte del paquete coreutils. Resulta especialmente apropiada para la creación de imágenes y eliminación de rastros entre muchas otras muchísimas posibles funciones.

Un tema que a veces pone nervioso con el dd es cuando nos salta la pregunta: “¿Falta mucho?

Si nos descargamos el paquete coreutils y editamos el fichero coreutils-6.9/src/dd.c nos encontraremos con la siguiente función main (reducido):

  install_signal_handlers ();

  start_time = gethrxtime ();

  exit_status = dd_copy ();

  quit (exit_status);

Si miramos la función install_signal_handlers veremos que registra dos señales:

  if (sigismember (&caught_signals, SIGINFO))
    {
      act.sa_handler = siginfo_handler;
      act.sa_flags = 0;
      sigaction (SIGINFO, &act, NULL);
    }

  if (sigismember (&caught_signals, SIGINT))
    {
      /* POSIX 1003.1-2001 says SA_RESETHAND implies SA_NODEFER,
         but this is not true on Solaris 8 at least.  It doesn't
         hurt to use SA_NODEFER here, so leave it in.  */
      act.sa_handler = interrupt_handler;
      act.sa_flags = SA_NODEFER | SA_RESETHAND;
      sigaction (SIGINT, &act, NULL);
    }

En el caso de la SIGINT es conocida, peró la SIGINFO en realidad es un define de SIGUSR1:

#ifndef SIGINFO
# define SIGINFO SIGUSR1
#endif

Las funciones registradas vemos que sólo modifican dos variables (interrupt_signal y info_signal_count) y acaban rápidamente:

/* An ordinary signal was received; arrange for the program to exit.  */
    
static void
interrupt_handler (int sig)
{   
  if (! SA_RESETHAND)
    signal (sig, SIG_DFL);
  interrupt_signal = sig;
}

/* An info signal was received; arrange for the program to print status.  */
    
static void
siginfo_handler (int sig)
{
  if (! SA_NOCLDSTOP)
    signal (sig, siginfo_handler);
  info_signal_count++; 
}

Pero si nos fijamos en las funciones que leen la entrada y lo copian el un buffer del tamaño del parámetro bs (block size) veremos que se llama a cada passada a una función llamada process_signals:

/* Read from FD into the buffer BUF of size SIZE, processing any
   signals that arrive before bytes are read.  Return the number of
   bytes read if successful, -1 (setting errno) on failure.  */

static ssize_t
iread (int fd, char *buf, size_t size)
{
  for (;;)
    {
      ssize_t nread;
      process_signals ();
      nread = read (fd, buf, size);
      if (! (nread < 0 && errno == EINTR))
        return nread;
    }
}

Como deciamos, también se aprecia en la función que copia del buffer al destino:

/* Write to FD the buffer BUF of size SIZE, processing any signals
   that arrive.  Return the number of bytes written, setting errno if
   this is less than SIZE.  Keep trying if there are partial
   writes.  */

static size_t
iwrite (int fd, char const *buf, size_t size)
{
  size_t total_written = 0;

  while (total_written < size)
    {
      ssize_t nwritten;
      process_signals ();
      nwritten = write (fd, buf + total_written, size - total_written);
      if (nwritten < 0)
        {
          if (errno != EINTR)
            break;
        }
      else if (nwritten == 0)
        {
          /* Some buggy drivers return 0 when one tries to write beyond
             a device's end.  (Example: Linux 1.2.13 on /dev/fd0.)
             Set errno to ENOSPC so they get a sensible diagnostic.  */
          errno = ENOSPC;
          break;
        }
      else
        total_written += nwritten;
    }

  return total_written;
}

Respecto a la función process_signals, la cual su código és:

/* Process any pending signals.  If signals are caught, this function
   should be called periodically.  Ideally there should never be an
   unbounded amount of time when signals are not being processed.  */

static void
process_signals (void)
{
  while (interrupt_signal | info_signal_count)
    {
      int interrupt;
      int infos;
      sigset_t oldset;

      sigprocmask (SIG_BLOCK, &caught_signals, &oldset);

      /* Reload interrupt_signal and info_signal_count, in case a new
         signal was handled before sigprocmask took effect.  */
      interrupt = interrupt_signal;
      infos = info_signal_count;

      if (infos)
        info_signal_count = infos - 1;

      sigprocmask (SIG_SETMASK, &oldset, NULL);

      if (interrupt)
        cleanup ();
      print_stats ();
      if (interrupt)
        raise (interrupt);
    }
}

Básicamente su función es esta:

      if (interrupt)
        cleanup ();
      print_stats ();
      if (interrupt)
        raise (interrupt);

Dicho en otras palabras, en el caso que sea un un TERM, ejecuta cleanup (cierra descriptores de fichero) muesta estadísticas y cierra. Pero en el caso de un USR1 no hace el cleanup y sólo muestra las estadísticas y continua.

Así, si ejecutamos un dd en un terminal:

$ dd if=/dev/zero of=/dev/zero 

Y desde otro mandamos un USR1 al dd:

$ pkill -USR1 dd

En la otra terminal aparecerán el estado del dd en ese instánte:

$ dd if=/dev/zero of=/dev/zero 
5445236+0 records in
5445235+0 records out
2787960320 bytes (2.8 GB) copied, 5.89386 s, 473 MB/s

Por lo que si mandamos la señal USR1 periódicamente (por ejemplo con el watch) podremos ver el progreso del dd mientras se ejecuta.

2 comments to “Estadísiticas de progreso con dd”

  1. Muchas gracias por el análisis y la explicación sobre cómo ver el progreso del dd. Ésto combinado con el conocimiento del tamaño a copiar puede incluso permitir la creación de una barrita de progreso.

  2. De nada!

Deja un comentario:

XHTML - Tags permitidos:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>