Выполните команду unix точно с очень короткими интервалами, БЕЗ накопления временной задержки

38

Вопрос

Я хотел бы иметь возможность выполнять команду UNIX точно каждую секунду в течение длительного периода времени .

Мне нужно решение, которое не отстает от определенного времени из-за времени, необходимого самой команде для выполнения. сон , часы и некий скрипт на python все меня подвели в этом отношении.

На микроконтроллере, таком как http://Arduino.cc, я бы делал это с помощью аппаратных прерываний часов. Я хотел бы знать, есть ли подобное решение для сценариев оболочки с точным временем. Все решения, которые я нашел на StackExchange.com, привели к заметной задержке во времени, если работать в течение нескольких часов. Подробности смотрите ниже.

Практическое назначение / применение

Я хочу проверить, постоянно ли работает мое сетевое соединение, посылая временные метки через nc(netcat) каждую 1 секунду.

Отправитель:

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

Приемник:

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

После завершения сравните два журнала:

diff netcat-sender.txt netcat-receiver.txt

Различия будут непередаваемыми временными метками. Из этого я бы узнал, в какое время мой LAN / WAN / ISP создает проблемы.


Решение СОН

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

Получает определенное смещение с течением времени, так как команда в цикле также занимает немного времени.

точность

cat timelog-sleep.txt

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

Прошло секунд: 34520

wc -l timelog-sleep.txt

Строки в файле: 34243

Точность суммируется:

  • 34520-34243 = 277 проблем с синхронизацией
  • 34520/34243 = 1,008 = 0,8%

Решение ПОВТОРНЫЙ ПИТОН

Найдено в: Повторите команду Unix каждые х секунд навсегда

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

Предполагается избежать временного смещения, но не может этого сделать.

точность

wc -l timelog-repeat-py.txt

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

Прошло секунд: 10960

wc -l timelog-repeat-py.txt

Строки в файле: 10859

Точность суммируется:

  • 10960-10859 = 101 проблема синхронизации
  • 10960/10859 = 1,009 = 0,9%

Решение СМОТРЕТЬ

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

точность

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

Прошло секунд: 8499

wc -l timelog-watch.txt

Строки в файле: 8366

Точность суммируется:

  • 8499-8366 = 133 проблемы со временем.
  • 8499/8366 = 1,016 = 1,6%.
PORG
источник
5
Каково требуемое разрешение, точность и зачем оно вам / для чего вы его используете?
Джиппи
Что будет, если у вас niceпроцесс, который спит?
Тило Виклунд
1
Хотели ли вы использовать планирование РЕАЛЬНОГО ВРЕМЕНИ, чтобы надеяться минимизировать задержку вызова sleep ().
MDPC
Меня поражает, что ваше время всегда будет зависеть от текущего рабочего состояния коробки. Если что-то приводит к тому, что программа, о которой вы заботитесь, выгружается из кэша, вы получите увеличение времени, если только вы не можете гарантировать, что типичное время выполнения программы значительно меньше требуемого интервала. Я хотел бы быть в системе реального времени, в значительно урезанной установке, в которой больше никто не вошел, или в однопользовательском режиме. Вероятно, лучшим решением является модификация рассматриваемой программы для выполнения самого цикла вместо вызова ее из другой программы.
Hack Saw
2
Запустите каждую команду в своем собственном потоке, чтобы любая блокировка, связанная с IO, не тратила ваше время.
Джоэл Корнетт

Ответы:

12

Как работает этот Perl-скрипт, который я только что написал?

#!/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();
}

Использование: perl timer.pl 1 date '+%Y-%m-%d %H:%M:%S'

Он работал 45 минут без единого пропуска, и я подозреваю, что он будет продолжать это делать, если а) нагрузка на систему не станет настолько высокой, что fork () займет больше секунды или б) будет добавлена ​​високосная секунда.

Однако он не может гарантировать, что команда выполняется с точными вторыми интервалами, поскольку есть некоторые издержки, но я сомневаюсь, что это намного хуже, чем решение на основе прерываний.

Я запускал его около часа с date +%N(наносекунды, расширение GNU) и запускал некоторую статистику по нему. Самое большое отставание было 1 155 микросекунд. Среднее (среднее арифметическое) 216 мкс, медиана 219 мкс, стандартное отклонение 42 мкс. Он работал быстрее, чем 270 мкс в 95% случаев. Я не думаю, что вы можете победить это, кроме как на C-программе.

hhaamu
источник
1
Я запускал его всю ночь без других активных пользовательских приложений с интервалом в 1 секунду, и он работал в течение 29241 секунды, без единой пропущенной секунды! Это будет соответствовать моей цели. Тогда я побежал снова это утро с интервалом 0,1 сек, GNU dateс +%Nи только после 3 минут он бросил эту ошибку: Time::HiRes::sleep(-0.00615549): negative time not invented yet at ~/bin/repeat.pl line 23.Line 23 в моем сохраненном сценарии:sleep $start - time();
PORG
Если вы запускаете его с интервалами 0,01 с или 0,001 с, то всего несколько секунд или меньше, пока программа не прекратит работу с ошибкой «отрицательное время». Но для моей цели это подходит!
porg
28

Функция POSIX ualarm()позволяет планировать ядро ​​для периодической сигнализации вашего процесса с точностью до микросекунды.

Подбери простую программу:

 #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();
 }

Compile

 gcc -O2 tick.c -o tick

Затем прикрепите его к тому, что вам нужно, периодически, например, так:

./tick | while read x; do
    date "+%Y-%m-%d %H:%M:%S"
done | tee timelog-sleep.txt
Дейв
источник
Нужна ли для этого специальная оболочка или C-STD? Я скомпилировал его (который выдал небольшое предупреждение о пропущенном возврате), но вывод не был получен.
математика
@math С -std=c99, вы не получите предупреждение о пропущенном возврате. В противном случае вам не нужно ничего особенного. Вы неправильно набрали лишний ноль? strace ./tickпокажет вам, что он делает с точки зрения системного вызова
Дейв
Я получаю: gcc -O2 -std = c99 -o tick tick.c tick.c: в функции 'main': tick.c: 10: 5: предупреждение: неявное объявление функции 'ualarm' [-Wimplicit-function-объявление ] tick.c: В функции 'tick': tick.c: 5: 10: warning: игнорировать возвращаемое значение 'write', объявленное с атрибутом warn_unused_result [-Wunused-result] :: Кажется, что моя система (Ubuntu 12.04) делает не поддерживает это. Однако, по крайней мере, есть справочная страница, где ualarm должен быть в unistd.h. (gcc - 4.6.3)
математика,
28

Вы пробовали watchс параметром --precise?

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

Со страницы руководства:

Обычно этот интервал интерпретируется как промежуток времени между завершением одного прогона команды и началом следующего прогона. Однако, с опцией -p или --precise, вы можете делать попытку попытки запуска команды каждые интервалы секунд. Попробуйте это с ntptime и обратите внимание, что доли секунды остаются (почти) одинаковыми, в отличие от обычного режима, где они непрерывно увеличиваются.

Однако этот параметр может быть недоступен в вашей системе.

Вы также должны учитывать, что должно произойти, когда выполнение вашей программы требует более одной секунды. Должно ли быть пропущено следующее запланированное выполнение или оно должно быть запущено поздно?

Обновление : я запустил скрипт некоторое время, и он не потерял ни одного шага:

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

Обновление:--precise флаг является дополнением Debian, патч, однако , довольно просто: http://patch-tracker.debian.org/patch/series/view/procps/1:3.2.8-9squeeze1/watch_precision_time.patch

Даниэль Куллманн
источник
Точно путь. Хотел бы я +10 это.
krlmlr
Какая версия watchподдерживает эту опцию? Это было ни на одной из машин, которые я проверял.
Tylerl
Его версия 0.3.0, которая является текущей версией на Ubuntu 12.04. Он поставляется с версии 3.2.8-11ubuntu6 пакета procps.
Даниэль Куллманн
Хм, пакет с исходным кодом procps не поддерживает --precise. Это дополнение к Debian (3.2.8-9, watch_precision_time.patch)
Даниэль Куллманн
1
Хорошо, но так же, как mdpc в комментариях к поставленному вопросу: Это также может дать сбой, когда ваша система находится под большой нагрузкой. Я только что проверил это в сочетании со стрессом (нагрузка на диск и ядра) и получил это: в 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 режиме реального времени (ядро и т. Д.) Есть причина по причине!
математика
18

crontabимеет разрешение 1 минута. Если вы согласны с тем, что время задержки накапливается за одну минуту, а затем сбрасывается на следующую минуту, эта базовая идея может сработать:

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

Обратите внимание, что script.shтакже работает в фоновом режиме. Это должно помочь минимизировать отставание, которое накапливается при каждой итерации цикла.

В зависимости от того, сколько лага sleepгенерирует, есть вероятность того, что секунда 59 будет совпадать со секундой 0 следующей минуты.

РЕДАКТИРОВАТЬ, чтобы добавить некоторые результаты в том же формате, что и в вопросе:

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

1 час 52 минуты = 6720 секунд

$ wc -l timelog-cron
6720 timelog-cron

0 проблем с синхронизацией, 0% скидка. В любое время накопление сбрасывается каждую минуту.

Izkata
источник
1
Могу я спросить, почему за это проголосовали?
Изката
2
Это уродливый хак
ха-ха-ха
2
@hhaamu Что страшного в этом? Операционные системы общего назначения на ПК не предназначены для очень точных по времени операций, так что еще можно ожидать? Если вы хотите «элегантный» и абсолютно точный тайминг, вам придется использовать другой планировщик ЦП, либо переключиться на ядро ​​реального времени, либо использовать выделенное оборудование и т. Д. Это вполне законное решение, и я не вижу причин для каких-либо downvotes. Это, безусловно, улучшение по сравнению с тем, который имел только «запуск в фоновом режиме» без периодической повторной синхронизации через cron.
jw013
1
Плюс остановка это легко. Не нужно рисковать, убивая его в середине цикла - удалите запись из crontab, и она заканчивается самостоятельно в конце минуты.
Изката
Вам просто повезло, что в вашей системе с cronточностью до секунды, но это не так в общем.
Дмитрий Григорьев
15

Ваша проблема в том, что вы спите в течение определенного периода времени после запуска вашей программы, не принимая во внимание количество времени, прошедшее с момента вашего последнего сна.

Вы можете сделать это на bash или на любом другом языке программирования, но ключ заключается в том, чтобы использовать часы, чтобы определить, как долго планировать следующий сон. Перед сном проверьте часы, посмотрите, сколько времени у вас осталось, и усните разницу.

Из-за компромиссов в планировании процессов вы не гарантированно просыпаетесь прямо на такте, но вы должны быть достаточно близко (в течение нескольких мс без нагрузки или в течение нескольких сотен мс под нагрузкой). И вы не будете накапливать ошибки с течением времени, потому что каждый раз вы будете синхронизироваться в каждом цикле сна и удаляете любую накопленную ошибку.

Если вам нужно точно нажать на часы, то вам нужна операционная система реального времени , предназначенная именно для этой цели.

tylerl
источник
Я думаю, что также очень вероятно, что программы porg протестировали блок во время выполнения намеченного процесса - что по логике они должны делать, чтобы не убить машину, на которой они работают.
Symcbean
Независимо от того, блокируете вы или нет, механизм работает просто отлично. Если вы блокируете, вы спите время, оставшееся после блокировки. Если вы не блокируете, то ваш поток или процесс синхронизации спит, пока работает другой. В любом случае, тот же результат.
Tylerl
@tylerl: Как будет выглядеть конкретная командная строка для вашего решения?
porg
Я думаю, вы имели в виду то же самое, что @lynxlynxlynx
porg
@porg, который вы должны использовать, date +%S.%Nчтобы получить количество секунд с точностью usleepдо секунды, и спать с точностью до секунды, но после этого это просто вопрос математики.
Tylerl
7

Я всегда просто разочаровывался в том, что что-то работает точно в промежутке. Я думаю, вам придется написать программу на C и уделить особое внимание тому, чтобы не превышать часть 1-секундного интервала с вашим собственным кодом. Вам, вероятно, придется использовать многопоточность или несколько взаимодействующих процессов, чтобы заставить это работать. Будьте осторожны, чтобы избежать чрезмерных затрат времени запуска потока или запуска процесса.

Одна ссылка, которая, похоже, относится к 1993 году: часы рандомизированной выборки для оценки использования ЦП и профилирования кода. Вам может понадобиться взглянуть на приложение «Исходный код злоумышленника», чтобы увидеть, как они точно измеряли временные интервалы и «проснулись». их программа в правильное время. Так как коду 19 лет, он, вероятно, не будет портировать напрямую или легко, но если вы прочитаете его и попытаетесь понять, принципы, которыми вы руководствуетесь, могут направлять ваш код.

РЕДАКТИРОВАТЬ: нашел другую ссылку, которая может помочь: Влияние разрешения часов на планирование интерактивных и мягких процессов в реальном времени, которые должны помочь вам с любой теоретической подготовкой.

Брюс Эдигер
источник
4

Посмотрите на nanosleep () (с http://linux.about.com/library/cmd/blcmdl2_nanosleep.htm ). Вместо того, чтобы заставить вашу программу спать 1 секунду, заставьте ее спать (1 - количество, необходимое для запуска) секунд. Вы получите намного лучшее разрешение.

woliveirajr
источник
Вы можете сделать то же самое с регулярными sleep, то есть sleep 0.99. Проблема в том, что количество времени, которое требуется для запуска, далеко от постоянного, даже его среднее значение может колебаться со временем.
Дмитрий Григорьев
3

Попробуйте запустить свою команду в фоновом режиме, чтобы она не сильно влияла на синхронизацию цикла, но даже этого будет недостаточно, если вы не хотите накапливать данные в течение длительного времени, поскольку с ней, безусловно, связаны затраты в несколько миллисекунд.

Таким образом, это, вероятно, лучше, но, вероятно, все еще недостаточно хорошо:

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

На моем компьютере это дало 2 ошибки за 20 минут или 0,1 в минуту, что примерно в пять раз меньше, чем за пробежку.

lynxlynxlynx
источник
Проблема в sleep 1том, что он спит не менее одной секунды, а не меньше. Следовательно ошибка накапливается.
Хааму
Сравнение результатов синхронизации двух разных компьютеров совершенно бессмысленно, если только вы не запустили оригинальный код в своей системе и не получили тот же результат, что и OP.
Дмитрий Григорьев
1

Уродливо, но это работает. Вероятно, вам следует переосмыслить дизайн вашей программы, если вам нужен такой цикл. Он в основном проверяет, равна ли текущая целая секунда предыдущей проверенной секунде, и печатает количество наносекунд с момента изменения секунды. На точность влияет сон .001.

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

Точность указывается в миллисекундах, при условии, что «полезная нагрузка» date "+%N nanoseconds late"занимает не более секунды. Вы можете снизить нагрузку на процессор, увеличив период ожидания или, если действительно не возражаете, просто замените команду sleep на 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

Это плохая практика, потому что вы в основном проводите опрос CPU для события и вы тратите впустую циклы CPU. Вы, вероятно, хотите подключиться к прерыванию по таймеру (это невозможно из bash) или использовать выделенное оборудование, такое как микроконтроллер. ПК и его операционная система не рассчитаны на высокую точность синхронизации.

jippie
источник
1

Другой метод - использовать приостановку в цикле и отправлять SIGCONT из точной внешней программы. Посылка сигнала очень легка и будет иметь намного меньшую задержку, чем выполнение чего-либо. Вы также можете поставить в очередь кучу команд с помощью команды «at», которую вряд ли кто-нибудь использует в «at», я не уверен, насколько она точна.

Если точность критична, и вы хотите серьезно отнестись к этому, это похоже на приложение, в котором вы обычно используете RTOS, что может быть сделано под Linux с исправленным ядром RT-Preempt, что даст вам точность и некоторую меру контроль прерываний, но это может быть более беспокойным, чем стоит.

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

Xenomai также может быть полезен, это полная реализация RTOS и портирована для x86 и x86_64, но есть некоторое программирование.

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

cdslashetc
источник
1

С ksh93(который имеет плавающую точку $SECONDSи встроенный sleep)

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

Тот же сценарий будет работать, zshно вызовет sleepкоманду вашей системы . zshимеет zselectвстроенный, но только с разрешением 1/100.

Стефан Шазелас
источник
0

Я бы пошел с небольшой программой на 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));
    }
}

Эта программа ожидает, что программа вызовет полный путь в качестве первого аргумента, и передаст все оставшиеся аргументы. Он не будет ждать завершения команды, поэтому он с радостью запустит несколько экземпляров.

Кроме того, стиль кодирования здесь действительно небрежный, и сделан ряд допущений, которые могут или не могут быть гарантированы применимыми стандартами, то есть качество этого кода «работает для меня».

Эта программа получит несколько более длинные или короткие интервалы, когда часы будут настроены с помощью NTP или вручную. Если программа должна обрабатывать это, POSIX предоставляет, timer_create(CLOCK_MONOTONIC, ...)что не влияет на это.

Саймон Рихтер
источник
0

Вы должны отслеживать текущее время и сравнивать его с временем начала. Таким образом, вы спите рассчитанное количество времени на каждую итерацию, а не фиксированное количество. Таким образом, вы не будете накапливать ошибки синхронизации и переходить от того, где вы должны быть, потому что вы сбрасываете свои тайминги в каждом цикле на абсолютное время от начала.

Кроме того, некоторые функции сна возвращаются рано, если происходит прерывание, поэтому в этом случае вам придется снова вызывать свой метод сна, пока не пройдет полное количество времени.

fwgx
источник
0

Вот сценарий bash, очень точный. Он использует usleep для микросекундной точности.

http://wiki.junkemailfilter.com/index.php/How_to_run_a_Linux_script_every_few_seconds_under_cron

Там есть 3 скрипа. Посмотрите на тот, что внизу. Будет выполнять до 2400 выполнений в минуту с разумной точностью. И это действительно просто.

user56318
источник
0

Этот может работать по крайней мере 100 раз в секунду с очень точным разрешением.

Наличие каталога по количеству циклов в минуту создает расписание. Эта версия поддерживает микросекундное разрешение, если ваш компьютер может справиться с этим. Количество выполнений в минуту не должно делиться равномерно на 60 и не ограничиваться 60. Я проверил его до 6000, и он работает.

Эту версию можно установить в каталоге /etc/init.d и запустить как службу.

#! /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
user56318
источник