Ejecute el comando unix con precisión a intervalos muy cortos SIN acumular retraso de tiempo con el tiempo

38

Pregunta

Me gustaría poder ejecutar un comando UNIX precisamente cada segundo durante un largo período de tiempo .

Necesito una solución, que no se quede atrás después de un cierto tiempo, debido al tiempo que el comando en sí necesita para su ejecución. dormir , mirar y un cierto script de Python me fallaron a este respecto.

En el microcontrolador como el http://Arduino.cc lo haría a través de interrupciones de reloj de hardware. Me gustaría saber si existe una solución similar de script de shell con precisión de tiempo. Todas las soluciones que encontré dentro de StackExchange.com, resultaron en un notable retraso de tiempo, si se ejecuta durante horas. Vea los detalles abajo.

Propósito práctico / aplicación

Quiero probar si mi conexión de red está continuamente ncactiva enviando marcas de tiempo a través de (netcat) cada 1 segundo.

Remitente:

precise-timestamp-generator | tee netcat-sender.txt | nc $receiver $port

Receptor:

nc -l -p $port > netcat-receiver.txt

Después de la finalización, compare los dos registros:

diff netcat-sender.txt netcat-receiver.txt

Las diferencias serían las marcas de tiempo no transmitidas. De esto sabría a qué hora mi LAN / WAN / ISP causa problemas.


Solución SUEÑO

while [ true ]; do date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done | tee timelog-sleep.txt

Obtiene un cierto desplazamiento con el tiempo, ya que el comando dentro del bucle también lleva un poco de tiempo.

Precisión

cat timelog-sleep.txt

2012-07-16 00:45:16
[...]
2012-07-16 10:20:36

Segundos transcurridos: 34520

wc -l timelog-sleep.txt

Líneas en el archivo: 34243

Precisión resumida:

  • 34520-34243 = 277 problemas de tiempo
  • 34520/34243 = 1.008 = 0.8% de descuento

Solución REPETIR PYTHON

Encontrado en: Repita un comando Unix cada x segundos para siempre

repeat.py 1 "date '+%Y-%m-%d %H:%M:%S'" >> timelog-repeat-py.txt

Se supone que evita el desplazamiento de tiempo, pero no lo hace.

Precisión

wc -l timelog-repeat-py.txt

2012-07-16 13:42:44
[...]
2012-07-16 16:45:24

Segundos transcurridos: 10960

wc -l timelog-repeat-py.txt

Líneas en el archivo: 10859

Precisión resumida:

  • 10960-10859 = 101 problemas de tiempo
  • 10960/10859 = 1.009 = 0.9% de descuento

RELOJ de solución

watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> ~/Desktop/timelog-watch.txt"

Precisión

wc -l timelog-watch.txt
2012-07-16 11:04:08
[...]
2012-07-16 13:25:47

Segundos transcurridos: 8499

wc -l timelog-watch.txt

Líneas en el archivo: 8366

Precisión resumida:

  • 8499-8366 = 133 problemas de tiempo.
  • 8499/8366 = 1.016 = 1.6% de descuento.
porg
fuente
55
¿Cuál es la resolución requerida, la precisión y por qué la necesita / para qué la está utilizando?
jippie
¿Qué pasa si niceel proceso que duerme?
Tilo Wiklund
1
¿Ha pensado en usar la programación en TIEMPO REAL para minimizar la demora en una llamada sleep ()?
mdpc
Me parece que su tiempo siempre se verá afectado por el estado operativo actual de la caja. Si algo hace que el programa que le interesa se descargue del caché, obtendrá un aumento de tiempo, a menos que pueda garantizar que el tiempo de ejecución típico del programa sea significativamente menor que el intervalo que desea. Me gustaría estar en un sistema en tiempo real, en una configuración significativamente reducida sin que nadie más haya iniciado sesión, o en modo de usuario único. Probablemente una mejor solución es modificar el programa en cuestión para hacer el ciclo en sí, en lugar de invocarlo desde otro programa.
Hack Saw
2
Inicie cada comando en su propio hilo, de esa manera cualquier bloqueo relacionado con IO no le quita tiempo.
Joel Cornett

Respuestas:

12

¿Cómo funciona este script de Perl que acabo de crear?

#!/usr/bin/perl

use strict;
use warnings;
use Time::HiRes qw/time sleep/;

sub launch {
    return if fork;
    exec @_;
    die "Couldn't exec";
}

$SIG{CHLD} = 'IGNORE';

my $interval = shift;
my $start = time();
while (1) {
    launch(@ARGV);
    $start += $interval;
    sleep $start - time();
}

Utilizar: perl timer.pl 1 date '+%Y-%m-%d %H:%M:%S'

Ha estado funcionando 45 minutos sin un solo salto, y sospecho que continuará haciéndolo a menos que a) la carga del sistema sea tan alta que fork () tarde más de un segundo ob) se inserte un segundo intercalar.

Sin embargo, no puede garantizar que el comando se ejecute en segundos segundos exactos, ya que hay algo de sobrecarga, pero dudo que sea mucho peor que una solución basada en interrupciones.

Lo ejecuté durante aproximadamente una hora con date +%N(nanosegundos, extensión GNU) y ejecuté algunas estadísticas al respecto. El mayor retraso fue de 1 155 microsegundos. Promedio (media aritmética) 216 µs, mediana 219 µs, desviación estándar 42 µs. Funcionó más rápido que 270 µs el 95% del tiempo. No creo que puedas vencerlo excepto con un programa en C.

hhaamu
fuente
1
Lo ejecuté durante la noche sin otras aplicaciones de usuario activas con un intervalo de 1 segundo, ¡y funcionó durante 29241 segundos, sin un solo segundo omitido! Esto se ajustará a mi propósito. Entonces me encontré de nuevo esta mañana con un intervalo de 0,1 segundos, GNU datecon +%Ny después de sólo 3 min arrojó que el error: Time::HiRes::sleep(-0.00615549): negative time not invented yet at ~/bin/repeat.pl line 23.Línea 23 en mi script guardado:sleep $start - time();
porg
Si lo ejecuta con intervalos de 0.01 segundos o 0.001 segundos, es solo cuestión de unos segundos o menos hasta que el programa cancele con el error de "tiempo negativo". Pero para mi propósito, ¡encaja!
porg
28

La ualarm()función POSIX le permite programar el kernel para señalar periódicamente su proceso, con una precisión de microsegundos.

Prepare un programa simple:

 #include<unistd.h>
 #include<signal.h>
 void tick(int sig){
     write(1, "\n", 1);
 }
 int main(){
     signal(SIGALRM, tick);
     ualarm(1000000, 1000000); //alarm in a second, and every second after that.
     for(;;)
         pause();
 }

Compilar

 gcc -O2 tick.c -o tick

Luego adjúntalo a lo que necesites hacer periódicamente de esta manera:

./tick | while read x; do
    date "+%Y-%m-%d %H:%M:%S"
done | tee timelog-sleep.txt
Dave
fuente
¿Necesito un shell especial o C-std para eso? Lo he compilado (lo que dio una pequeña advertencia sobre el retorno perdido) pero no se produjo ningún resultado.
matemáticas
@math With -std=c99, no recibirá la advertencia sobre el retorno perdido. De lo contrario, no debería necesitar nada especial. ¿Escribiste mal un cero extra? strace ./tickle mostrará lo que está haciendo desde una perspectiva syscall
Dave
Obtengo: gcc -O2 -std = c99 -o tick tick.c tick.c: En la función 'main': tick.c: 10: 5: advertencia: declaración implícita de función 'ualarm' [-Wimplicit-function-declaración ] tick.c: En la función 'tick': tick.c: 5: 10: advertencia: ignorando el valor de retorno de 'write', declarado con el atributo warn_unused_result [-Wunused-result] :: Parece que mi sistema (Ubuntu 12.04) lo hace No lo soporto. Sin embargo, al menos hay una página de manual donde ualarm debería estar en unistd.h. (gcc es 4.6.3)
matemática
28

¿Has probado watchcon el parámetro --precise?

watch -n 1 --precise "date '+%Y-%m-%d %H:%M:%S.%N' >> ~/Desktop/timelog-watch.txt"

Desde la página del manual:

Normalmente, este intervalo se interpreta como el tiempo transcurrido entre la finalización de una ejecución de comando y el comienzo de la siguiente ejecución. Sin embargo, con la opción -p o --precise, puede hacer que el reloj intente ejecutar el comando cada intervalo de segundos. Pruébelo con ntptime y observe cómo los segundos fraccionarios permanecen (casi) iguales, a diferencia del modo normal donde aumentan continuamente.

Sin embargo, es posible que el parámetro no esté disponible en su sistema.

También debe considerar lo que debe suceder cuando la ejecución de su programa necesita más de un segundo. ¿Debería saltarse la próxima ejecución programada, o debería ejecutarse tarde?

Actualización : Ejecuté el script por algún tiempo, y no perdió un solo paso:

2561 lines
start: 2012-07-17 09:46:34.938805108
end:   2012-07-17 10:29:14.938547796

Actualización: el --preciseindicador es una adición de Debian, sin embargo, el parche es bastante simple: http://patch-tracker.debian.org/patch/series/view/procps/1:3.2.8-9squeeze1/watch_precision_time.patch

daniel kullmann
fuente
Exactamente el camino a seguir. Ojalá pudiera +10 esto.
krlmlr
¿Qué versión de watchadmite esa opción? No estaba en ninguna de las máquinas que revisé.
tylerl
Su versión 0.3.0, que es la versión actual en Ubuntu 12.04. Viene de la versión 3.2.8-11ubuntu6 del paquete procps.
daniel kullmann
Hmm, el paquete fuente de procps no es compatible --precise. Esta es una adición de Debian (3.2.8-9, watch_precision_time.patch)
daniel kullmann
1
Ok, pero igual que mdpc en los comentarios sobre la pregunta indicada: Esto también puede fallar cuando su sistema está bajo una carga pesada. Simplemente lo probé junto con el estrés (poner carga en el disco y los núcleos) y obtuve esto: ¡ 2012-07-24 07:20:21.864818595 2012-07-24 07:20:22.467458430 2012-07-24 07:20:23.068575669 2012-07-24 07:20:23.968415439 El material en tiempo real (kernel, etc.) está disponible por alguna razón!
matemáticas
18

crontabTiene una resolución de 1 minuto. Si está de acuerdo con el tiempo de retraso acumulado por ese minuto y luego reiniciar el siguiente minuto, esta idea básica podría funcionar:

* * * * * for second in $(seq 0 59); do /path/to/script.sh & sleep 1s;done

Tenga en cuenta que script.shtambién se ejecuta en segundo plano. Esto debería ayudar a minimizar el retraso que se acumula con cada iteración del bucle.

Dependiendo de cuánto retraso sleep genere, existe la posibilidad de que el segundo 59 se superponga con el segundo 0 del siguiente minuto.

EDITAR para agregar algunos resultados, en el mismo formato que en la pregunta:

$ cat timelog-cron
2012-07-16 20:51:01
...
2012-07-16 22:43:00

1 hora 52 minutos = 6720 segundos

$ wc -l timelog-cron
6720 timelog-cron

0 problemas de tiempo, 0% de descuento. En cualquier momento la acumulación se reinicia cada minuto.

Izkata
fuente
1
¿Puedo preguntar por qué se rechazó esto?
Izkata
2
Es un truco feo
hhaamu
2
@hhaamu ¿Qué tiene de feo? Los SO de uso general en las PC no están diseñados para operaciones muy precisas de tiempo crítico, entonces ¿qué más puede esperar? Si desea una sincronización "elegante" y absolutamente precisa, tendría que usar un programador de CPU diferente, o cambiar a un kernel en tiempo real, o usar hardware dedicado, etc. Esta es una solución perfectamente legítima y no veo ninguna razón para ello. votos negativos Ciertamente es una mejora con respecto a la que solo se ejecutaba en segundo plano sin la sincronización periódica a través de cron.
jw013
1
Además, detenerlo es fácil. No es necesario arriesgarse a matarlo en medio de un ciclo: elimine la entrada del crontab y finalizará por sí solo al final del minuto.
Izkata
Tienes suerte de que en tu sistema cronsea ​​preciso para el segundo, pero no es el caso en general.
Dmitry Grigoryev
15

Su problema es que está durmiendo durante un período de tiempo fijo después de ejecutar su programa sin tener en cuenta la cantidad de tiempo transcurrido desde la última vez que durmió.

Puede hacer esto es bash o cualquier otro lenguaje de programación, pero la clave es usar el reloj para determinar cuánto tiempo programar el próximo sueño. Antes de dormir, revise el reloj, vea cuánto tiempo le queda y duerma la diferencia.

Debido a los compromisos de programación del proceso, no está garantizado que se despierte justo en el tic del reloj, pero debe estar bastante cerca (dentro de unos pocos ms sin carga, o dentro de unos pocos cientos de ms bajo carga). Y no acumulará errores con el tiempo porque cada vez que vuelve a sincronizar en cada ciclo de suspensión y elimina cualquier error acumulado.

Si necesita presionar el reloj exactamente, entonces lo que está buscando es un sistema operativo en tiempo real , diseñado exactamente para este propósito.

tylerl
fuente
Creo que también es muy probable que los programas porg hayan probado bloque mientras ejecutan el proceso previsto, lo que lógicamente deberían hacer para evitar matar la máquina en la que se ejecutan.
symcbean
Ya sea que bloquee o no, el mecanismo funciona bien. Si bloquea, duerme el tiempo restante después del bloqueo. Si no bloquea, entonces su hilo o proceso de temporización está inactivo mientras el otro está funcionando. De cualquier manera, el mismo resultado.
tylerl
@tylerl: ¿Cómo sería la línea de comando concreta para su solución?
porg
Supongo que querías
porg
@porg que necesita usar date +%S.%Npara obtener la cantidad de segundos con una precisión de menos de un segundo y usleeppara dormir con una precisión de menos de un segundo, pero después de eso es solo cuestión de matemáticas.
tylerl
7

Siempre he renunciado a que algo se ejecute exactamente en intervalos. Creo que tendrá que escribir un programa en C y prestar mucha atención a no exceder la porción del intervalo de 1 segundo con su propio código. Es probable que tenga que usar procesos de subprocesamiento o de comunicación múltiple para que esto funcione. Tenga cuidado de evitar el tiempo de inicio de subprocesos o de inicio de proceso.

Una referencia que parece relevante data de 1993: un reloj de muestreo aleatorio para la estimación de la utilización de la CPU y el perfil del código . Deseará consultar el apéndice "Código fuente del adversario" para ver cómo midieron con precisión los intervalos de tiempo y "despertaron". su programa en el momento correcto. Dado que el código tiene 19 años, probablemente no se portará directa o fácilmente, pero si lo lee y trata de entenderlo, los principios involucrados podrían guiar su código.

EDITAR: Encontré otra referencia que podría ayudar: los efectos de la resolución del reloj en la programación de procesos interactivos y en tiempo real suaves que deberían ayudarlo con cualquier fondo teórico.

Bruce Ediger
fuente
4

Eche un vistazo a nanosleep () (de http://linux.about.com/library/cmd/blcmdl2_nanosleep.htm ). En lugar de hacer que su programa duerma 1 segundo, hágalo en reposo (1 - cantidad que se tarda en ejecutar) segundos. Obtendrá una resolución mucho mejor.

woliveirajr
fuente
Puede hacer lo mismo con regular sleep, es decir sleep 0.99. El problema es que la cantidad de tiempo que tarda en ejecutarse está lejos de ser constante, incluso su valor medio puede fluctuar con el tiempo.
Dmitry Grigoryev
3

Intente ejecutar su comando en segundo plano para que no afecte demasiado la sincronización del bucle, pero incluso eso no será suficiente si no desea ninguna acumulación durante largos períodos de tiempo, ya que seguramente hay un costo de unos pocos milisegundos asociado.

Entonces, esto es probablemente mejor, pero también es probable que no sea lo suficientemente bueno:

while [ true ]; do date "+%Y-%m-%d %H:%M:%S" & sleep 1; done | 
tee timelog-sleep.txt

En mi computadora, esto dio 2 errores en 20 minutos o 0,1 por minuto, lo que es aproximadamente una mejora de cinco veces más baja que su ejecución.

lynxlynxlynx
fuente
El problema sleep 1es que se garantiza que duerme al menos un segundo, nunca menos. Por lo tanto, el error se acumula.
hhaamu
Comparar los resultados de sincronización de dos computadoras diferentes no tiene sentido, a menos que haya ejecutado el código original en su sistema y haya obtenido el mismo resultado que el OP.
Dmitry Grigoryev
1

Feo pero funciona. Probablemente debería repensar el diseño de su programa si necesita un ciclo como este. Básicamente verifica si el segundo entero actual es igual al anterior marcado e imprime el número de nanosegundos desde el cambio del segundo. La precisión está influenciada por el sueño .001.

while true; do T=$( date +%s ); while [[ $T -eq $( date +%s ) ]]; do sleep .001; done; date "+%N nanoseconds late"; done

La precisión está en los milisegundos, siempre que la "carga útil" date "+%N nanoseconds late"no tarde más de un segundo. Puede reducir la carga de la CPU aumentando el período de suspensión o, si realmente no le importa, simplemente reemplace el comando de suspensión true.

002112890 nanoseconds late
001847692 nanoseconds late
002273652 nanoseconds late
001317015 nanoseconds late
001650504 nanoseconds late
002180949 nanoseconds late
002338716 nanoseconds late
002064578 nanoseconds late
002160883 nanoseconds late

Esta es una mala práctica porque básicamente haces una encuesta de CPU para un evento y estás desperdiciando ciclos de CPU. Es probable que desee conectarse a una interrupción del temporizador (no es posible desde bash) o usar hardware dedicado como un microcontrolador. Una PC y su sistema operativo no están diseñados para una alta precisión de sincronización.

jippie
fuente
1

Otro método sería utilizar una suspensión en un bucle y enviar SIGCONT desde un programa externo preciso. Enviar una señal es muy liviano y tendrá mucha menos latencia que ejecutar algo. También puede pre-poner en cola un montón de comandos con el comando "at", ya casi nadie usa "at". No estoy seguro de cuán preciso es.

Si la precisión es crítica y quiere tomar en serio esto, esto suena como el tipo de aplicación en la que normalmente usaría RTOS, que podría hacerse bajo Linux con el kernel parcheado RT-Preempt, que le dará la precisión y alguna medida de interrumpir el control, pero puede ser más molesto de lo que vale.

https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO

Xenomai también podría ser útil, es una implementación RTOS completa y está portada para x86 y x86_64, pero hay algo de programación involucrada.

http://www.xenomai.org/index.php/Main_Page

cdslashetc
fuente
1

Con ksh93(que tiene un punto flotante $SECONDSy un incorporado sleep)

typeset -F SECONDS=0
typeset -i i=0
while true; do
   cmd
   sleep "$((++i - SECONDS))"
done

El mismo script también funcionará zshpero invocará el sleepcomando de su sistema . zshtiene una función zselectintegrada, pero solo con una resolución de 1/100

Stéphane Chazelas
fuente
0

Me gustaría ir con un pequeño programa en C:

#include <sys/time.h>
#include <unistd.h>

int main(int argc, char **argv, char **envp)
{
    struct timeval start;
    int rc = gettimeofday(&start, NULL);
    if(rc != 0)
            return 1;

    for(;;)
    {
        struct timeval now;
        rc = gettimeofday(&now, NULL);
        useconds_t delay;
        if(now.tv_usec < start.tv_usec)
            delay = start.tv_usec - now.tv_usec;
        else
            delay = 1000000 - now.tv_usec + start.tv_usec;
        usleep(delay);
        pid_t pid = fork();
        if(pid == -1)
            return 1;
        if(pid == 0)
            _exit(execve(argv[1], &argv[1], envp));
    }
}

Este programa espera que el programa llame con la ruta completa como primer argumento, y pasa cualquier argumento restante. No esperará a que termine el comando, por lo que felizmente iniciará varias instancias.

Además, el estilo de codificación aquí es realmente descuidado, y se hacen una serie de suposiciones que pueden o no estar garantizadas por los estándares aplicables, es decir, la calidad de este código "funciona para mí".

Este programa obtendrá intervalos algo más largos o más cortos cuando el reloj se ajuste mediante NTP o configurándolo manualmente. Si el programa debe manejar esto, POSIX proporciona lo timer_create(CLOCK_MONOTONIC, ...)que no se ve afectado por esto.

Simon Richter
fuente
0

Debe estar al tanto de la hora actual y compararla con la hora de inicio. Entonces duermes una cantidad de tiempo calculada cada iteración, no una cantidad fija. De esta manera, no acumulará errores de temporización y se alejará de donde debería estar porque restablece sus temporizaciones en cada ciclo al tiempo absoluto desde el principio.

Además, algunas funciones de suspensión regresan temprano si hay una interrupción, por lo que en este caso deberá volver a llamar a su método de suspensión hasta que haya transcurrido el tiempo completo.

fwgx
fuente
0

Este puede ejecutarse al menos 100 veces por segundo con una resolución muy precisa.

La existencia del directorio del número de bucles por minuto crea la programación. Esta versión admite una resolución de microsegundos, suponiendo que su computadora pueda manejarlo. El número de ejecuciones por minuto no tiene que ser divisible por 60 ni está limitado a 60. Lo he probado a 6000 y funciona.

Esta versión puede instalarse en el directorio /etc/init.d y ejecutarse como un servicio.

#! /bin/sh

# chkconfig: 2345 91 61
# description: This program is used to run all programs in a directory in parallel every X times per minute. \
#              Think of this program as cron with microseconds resolution.

# Microsecond Cron
# Usage: cron-ms start
# Copyright 2014 by Marc Perkel
# docs at http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron"
# Free to use with attribution

# The scheduling is done by creating directories with the number of"
# executions per minute as part of the directory name."

# Examples:
#   /etc/cron-ms/7      # Executes everything in that directory  7 times a minute
#   /etc/cron-ms/30     # Executes everything in that directory 30 times a minute
#   /etc/cron-ms/600    # Executes everything in that directory 10 times a second
#   /etc/cron-ms/2400   # Executes everything in that directory 40 times a second

basedir=/etc/cron-ms

case "$1" in

   start|restart|reload)
   $0 stop
   mkdir -p /var/run/cron-ms
   for dir in $basedir/* ; do
      $0 ${dir##*/} &
   done
   exit
   ;;

   stop)
   rm -Rf /var/run/cron-ms
   exit
   ;;

esac

# Loops per minute is passed on the command line

loops=$1
interval=$((60000000/$loops))

# Just a heartbeat signal that can be used with monit to verify it's alive

touch /var/run/cron-ms

# After a restart the PIDs will be different allowing old processes to terminate

touch /var/run/cron-ms/$$

# Sleeps until a specific part of a minute with microsecond resolution. 60000000 is full minute

usleep $(( $interval - 10#$(date +%S%N) / 1000 % $interval ))

# Deleting the PID files exit the program

if [ ! -f /var/run/cron-ms/$$ ]
then
   exit
fi

# Run all the programs in the directory in parallel

for program in $basedir/$loops/* ; do
   if [ -x $program ] 
   then
      $program &> /dev/null &
   fi
done

exec $0 $loops
usuario56318
fuente