Форматы журналов часов
src/clock_log.rs читает записанный журнал часов в единый нормализованный ряд фазы: равномерную сетку отсчётов (time_s, phase_s) с интервалом отсчётов tau0, заданным или найденным, с учётом каждого пропуска и указанием единиц журнала. Читаются восемь форматов, каждый — документированное подмножество. Всё, что выходит за подмножество, отвергается с ошибкой, называющей найденное, а не читается приблизительно.
Сокращения. RINEX — Receiver Independent Exchange Format. TICC — счётчик временных интервалов TAPR (Tucson Amateur Packet Radio). ADEV — перекрывающаяся девиация Аллана, TDEV — девиация времени, MTIE — максимальная ошибка временного интервала. tau0 — интервал отсчётов в секундах.
Где можно использовать журнал#
telecom-timing:[series.clock_log]вместоseries.samplesилиseries.csv_path. Фаза журнала в нс — это ряд ошибки времени, проверяемый по маскам, а в отчёте появляется блокclock_log, поясняющий, как журнал был прочитан.- Вид clock: таблица
[clock_log]добавляет в прогон блокmeasured: как был прочитан журнал, SHA-256 таблицы (input_sha256;scenario_hashпрогона таблицу не охватывает) и отсчётов фазы, а также перекрывающиеся ADEV, TDEV и MTIE при октавных коэффициентах усреднения. Требуетсяruns = 1. Моделируемый holdover не меняется.
Без таблицы оба вида разбираются, хешируются и описываются в отчёте точно так же, как прежде.
[clock_log] # or [series.clock_log] in a telecom-timing scenario
format = "ticc" # "rinex-clk", "timelab", "ticc", "ptp4l", "phc2sys", "ts2phc",
# "chrony-tracking" or "chrony-statistics"
path = "logs/ticc.txt" # or text = "..." inline (the only choice in WebAssembly)
channel = "chA" # TICC: needed when the log holds more than one channel
gaps = "linear" # "refuse" (default), "longest" or "linear"
# clock = "G08" # RINEX, linuxptp, chrony: needed when the log holds more than one clock or source
# tau0_s = 1.0 # overrides a header and the detected spacing
# units = "ns" # TimeLab phase units: s (default), ms, us, ns, ps
# data = "phase" # TimeLab: phase (default) or frequency
Форматы#
rinex-clk: файлы часов RINEX версий 2,00 и от 3,00 до 3,04 (включая девятисимвольное поле имени 3,04). Запись RINEX VERSION / TYPE проверяется, а любая другая версия отклоняется с указанием найденной версии: IGS публикует формат часов (rinex_clock300.txt, 302, 304) до 3,04, а документы RINEX 4,00, 4,01 и 4,02 определяют только файлы наблюдений, навигации и метеорологии, поэтому версии 4,00 файла часов для чтения не существует. Заголовок читается только ради TIME SYSTEM ID и пропускается до END OF HEADER. Из записей данных сохраняются только записи AS (спутник) и AR (приёмник или станция) одних часов, выбранных по имени. Их первое поле значения, смещение часов в секундах, является фазой. Строки продолжения и записи CR, DR и MS пропускаются. Эпохи — календарные поля записи, в секундах от 1980-01-06 00:00:00 в собственной шкале времени файла, без преобразования.
timelab: ASCII-журналы фазы или частоты в стиле TimeLab — простая текстовая форма, которую экспортирует TimeLab и читают его ASCII-импорт и Stable32. В каждой строке одно показание: либо один столбец при равномерном tau0, либо два столбца time value. Столбцы могут разделяться пробелами, запятыми или точками с запятой.
- Строки, начинающиеся с
#,;или%, являются комментариями. Комментарий видаkey = value(илиkey: value) задаётtau0(с),units(s,ms,us,nsилиpsдля фазы),data(phaseилиfrequency) илиtime_units(sилиmjdдля первого из двух столбцов). - Одна начальная нечисловая строка принимается за заголовок столбцов.
- Данные о частоте (относительная частота) принимаются только в виде одного столбца. Они интегрируются в фазу как
x[i+1] = x[i] + y[i]·tau0(NIST SP 1065, 2008, §4,1). - Двоичные файлы TimeLab и файлы проектов
.timне читаются.
ticc: журналы счётчика TAPR TICC (руководство по эксплуатации TICC, редакции от 23 ноября 2016 г. и 15 декабря 2025 г., «режимы измерений»). Каждое поле данных — секунды с 12 десятичными знаками (по умолчанию 11 начиная с прошивки 20251101: читается любое число до 15). Отметка времени читается из своего десятичного текста как целое число фемтосекунд, а не как двоичное число с плавающей запятой, поэтому фаза журнала, снятого через несколько дней после запуска счётчика, сохраняет собственное разрешение счётчика: отметка времени f64 при 3.0e5 с имеет шаг 5.8e-11 с, около 60 пс у счётчика.
- Строки режима Timestamp (
<seconds> chA,<seconds> chB) и канал псевдометок времени режима TimeLab (chC) являются метками времени событий. Фаза выбранного канала — это его метка времени за вычетом номинального такта:x[k] = t[k] − t[0] − n[k]·tau0, гдеn[k]— целое числоtau0с первого события. Пропущенное событие — это пропуск. - Строки режима Interval (
<seconds> TI(A->B)или любая метка, начинающаяся сTI) читаются непосредственно как фаза, по одному показанию наtau0. Сценарий должен задаватьtau0_s, поскольку строка не содержит времени. - Режим парных отметок времени печатает те же строки в порядке каналов и читается как режим отметок времени; режим 3-Corner Hat добавляет синтезированный
chC, читаемый как выше. - Вывод режимов Period и Debug отклоняется. Канал, показания которого не возрастают, сообщается как режим Period. Двоичный режим отметок времени не является текстом, и файл такого режима отклоняется с сообщением об этом.
- Строки, начинающиеся с
#, и строки, не имеющие вида<number> <label>(например, меню запуска), пропускаются и подсчитываются.
Журналы linuxptp и chrony#
Эти пять форматов читают то, что работающий демон времени пишет о собственных часах. Они предназначены для проверки цепочки синхронизации времени постфактум (граничные часы, PHC главных часов, клиент chrony) теми же оценщиками, что и для других журналов. Они не читают конфигурацию демона, и здесь ничто не обращается к демону.
ptp4l, phc2sys, ts2phc: строки сервопривода linuxptp, причём каждый формат читает только строки собственной программы. Показание — это строка вида
ptp4l[5003.000]: master offset 40 s2 freq +1480 path delay 497
phc2sys[7000.000]: CLOCK_REALTIME phc offset -30 s2 freq +100 delay 1200
ts2phc[8000.000]: /dev/ptp0 offset 3 s2 freq -2
- Метка времени — первое
[seconds.fraction]в строке (монотонные часы демона). Она читается и после префикса syslog, где идентификатор процесса, напримерptp4l[812]:, не имеет дробной части и не принимается. - Фаза — это записанное в журнале значение
offsetв наносекундах, переведённое в секунды. Это измеренная демоном ошибка времени его подчинённых часов относительно опорных, как она записана: положительная, когда подчинённые часы опережают. Это ошибка, остающаяся под управлением сервопривода, а не дрейф свободно идущих часов. - Слова между меткой времени и
offsetназывают источник (master,eth0 masterдля префикса[eth0],CLOCK_REALTIME phc,/dev/ptp0). Журнал с более чем одним источником требуетclock = "<that text>"; ошибка перечисляет их. - Показания до того, как сервопривод впервые сообщит
s2(захвачен), отбрасываются и подсчитываются вn_skipped_lines:s0иs1— состояния «не захвачен» и «скачок», смещения в которых являются переходными процессами втягивания. Сервопривод, который никогда не достигаетs2, отвергается. Строка без поля состояния читается как есть. - Строки сводки (
rms ... max ...), сообщенияdelayиselected best masterи строки других программ пропускаются и подсчитываются. - Метки времени, идущие назад (перезапущенный демон), отвергаются; разделите журнал.
chrony-tracking, chrony-statistics: файлы chrony log tracking и log statistics, читаемые по позиции столбца. Показание — это строка не менее чем с 14 столбцами (tracking) или 13 (statistics), начинающаяся с даты и времени UTC вида YYYY-MM-DD HH:MM:SS. Строка заголовка столбцов пропускается и подсчитывается; линия ==== под ней пропускается без подсчёта.
- Tracking: столбец 7 — локальное смещение при обновлении часов, в секундах. IP-адрес или идентификатор опоры меняется вместе с выбранным источником, поэтому без
clockкаждая строка читается как одни локальные часы.clock = "<id>"сохраняет один идентификатор опоры. - Statistics: столбец 5 — оценённое смещение источника, в секундах. Для каждого адреса источника есть один ряд, и журналу нужен
clock = "<address>", если в нём больше одного источника. - Эпохи — секунды от 1980-01-06 00:00:00 UTC, без учёта високосных секунд. Chrony пишет строку на каждое обновление или на каждый отсчёт источника, поэтому интервал неравномерен.
tau0— медианный интервал; показание, отстоящее менее чем на половинуtau0от предыдущего сохранённого, отбрасывается и подсчитывается вn_duplicates, а больший интервал — это пропуск, о чём сообщаетgaps = "refuse". Задайтеtau0_sиgaps = "linear"для журнала со стабильным интервалом опроса.
Столбцы chrony и форматы строк linuxptp взяты из общедоступной документации этих демонов. Тестовые данные в tests/fixtures/clock_log/linuxptp_chrony/ написаны для Kshana с вымышленными значениями (см. его NOTICE.md); ни один реальный журнал не читался, поэтому формат, различающийся между версиями демонов, всё ещё может быть отвергнут или прочитан неверно. Строка, которая не совпадает, пропускается и подсчитывается, но никогда не угадывается.
tau0 и пропуски#
Если журнал содержит метки времени, а tau0 не задан, tau0 — это медианный интервал между последовательными метками времени, округлённый до четырёх значащих цифр, а источник сообщается как median-spacing. Интервал счётчика отличается от номинального на смещение частоты опоры, порядка 10^6 или меньше. Значение tau0_s в сценарии всегда приоритетно (источник input), затем комментарий заголовка tau0 (источник header).
Отсчёты размещаются на сетке t0 + i·tau0, а отсутствующая эпоха — это пропуск. Показания, отстоящие от сетки более чем на 1 % от tau0, отбрасываются и подсчитываются, так же как и дубликаты (первый сохраняется). Политика обработки пропусков — одна из следующих:
refuse(по умолчанию): любой пропуск — это ошибка, в которой указано число пропусков;longest: сохранить самый длинный участок без пропусков;linear: заполнить внутренние пропуски линейной интерполяцией и сообщить число заполненных.
Оценщикам нужна запись без пропусков, поэтому пропуски устраняются здесь.
Ваши собственные часы#
Таблица [clock_log] у вида clock сообщает, насколько стабильны были ваши собственные часы, рядом с симуляцией: scenarios/clock-measured-log.toml (kshana example clock-measured-log) — это clock-holdover.toml с выдержкой из 96 показаний спутниковых часов IGS в виде встроенного журнала TimeLab. Замените text на path и format для вашего собственного файла. Блок measured в результате содержит, как был прочитан журнал (формат, tau0 и откуда он взят, каждый пропуск), а также его ADEV, TDEV и MTIE; он не меняет симуляцию.
Управление holdover собственными часами: [clock_fit]#
[clock_log] # the measured series, as above
format = "ticc"
path = "my-clock.txt"
channel = "chA"
[clock_fit]
replaces = "classical" # or "quantum": the simulated clock whose noise the log replaces
Журнал подгоняется по процедуре карточки устройства из src/clock_library/card.rs (подготовка, удаление квадратичного члена, идентификация шума по лагу 1, взвешенный метод наименьших квадратов по белому PM, белому FM, фликкер-FM и случайному блужданию FM на временах усреднения не менее чем с 30 степенями свободы). Названные часы затем принимают q_wf = h_0 / 2, q_rw = 2 pi^2 h_-2, flicker_floor = sqrt(2 ln 2 h_-1) и подогнанный drift; y0 остаётся таким, как в сценарии, потому что журнал содержит смещение своего эталона, а не часов после дисциплинирования. Белый фазовый шум подгоняется и сообщается, но не симулируется. Результат получает блок fitted (коэффициенты, точки подгонки, значения, использованные прогоном, что было исключено, SHA-256 журнала), а часы получают идентификатор measured:<12 hex of the phase hash>. При runs > 1 ансамбль берёт отсчёты из подогнанных часов (блок measured требует runs = 1). Подгонка без частотного шума, слишком короткий для подгонки журнал и replaces, отличный от quantum или classical, являются ошибками, которые об этом говорят. scenarios/clock-fitted-log.toml — пример (6 часов спутниковых часов IGS).
Что показывает сравнение на известном шуме (tests/fixtures/clock_fit/RESULTS.md) и что следует иметь в виду:
- Достигнуто: уровень белого FM восстанавливается в пределах 5 % на каждой синтетической записи, где он есть; подогнанная карточка воспроизводит истинную девиацию Аллана в пределах множителя 1,35 на каждом времени усреднения, на котором она подгонялась; показатель шума, который определяет подгонка, равен показателю allantools в каждой точке (на записи 5071A, когда скачок фазы, удаляемый подготовкой, удалён и из входных данных allantools).
- Не достигнуто и зафиксировано тестом: случайное блуждание FM восстанавливается только тогда, когда оно преобладает на самых длинных временах усреднения записи, и даже тогда лишь на одной из трёх записей ряда в 131 072 с с переходом на 3 000 с подгонка отдала его фликкер-FM и дрейфу. Разделение между случайным блужданием FM и фликкер-FM по одной записи ненадёжно. Прогон воспроизводит стабильность журнала на временах усреднения, которые журнал покрывает; за ними два варианта расходятся (фликкер-FM — постоянный пол, случайное блуждание FM растёт как квадратный корень времени усреднения).
- MODELLED: сценарий «что, если» для шума этих часов в том виде, как его показывает журнал, а не калибровка часов и не прогноз их будущего поведения.
Некорректный журнал — это ошибка, называющая найденное (строку, версию, отсутствующий селектор), и никогда не частичный результат: tests/clock_log_real_allantools.rs закрепляет 24 таких случая.
Доказательства#
MODELLED. Это разборщик, и его вывод питает оценщики, которые проверены в других местах по allantools. Модульные тесты в src/clock_log.rs используют небольшие журналы, написанные вручную, а для форматов linuxptp и chrony — образцы в tests/fixtures/clock_log/linuxptp_chrony/. tests/clock_log_allantools.rs выполняет сравнение, заранее зарегистрированное в tests/fixtures/clock_log/PREREGISTRATION.md: три синтетических журнала с заданным начальным значением, по одному на формат, прочитанные независимым читателем, написанным по этой странице тем же автором, с ADEV, TDEV и MTIE, проверенными по allantools 2024,6; это сравнение охватывает только первые три формата, а для читателей linuxptp и chrony есть тесты разборщика, но нет сравнения с оракулом. Журналы этого сравнения синтетические, поэтому ничто в нём не описывает реальные часы или счётчик; сравнение на измеренных рядах описано ниже.
На измеренных данных#
tests/clock_log_real_allantools.rs выполняет сравнение, заранее зарегистрированное в tests/fixtures/clock_log_real/PREREGISTRATION.md (зафиксировано до любого образца или прогона), на четырёх измеренных рядах: Stable32 PHASE.DAT, первые 65 536 показаний цезиевых часов 5071A относительно водородного мазера, а также часы спутника IGS G01 (30 с) и станции ABMF (300 с) из итоговых часов IGS от 2026-08-02, как вендорный срез с её NOTICE. Первые два не имеют права на распространение: написанные по ним журналы находятся в игнорируемой git папке realdata-cache/ (scripts/gen_clock_log_real_ref.py), и тест об этом сообщает и проходит, когда их нет, если не задано KSHANA_REQUIRE_REALDATA=1. Каждый журнал читается движком и независимым читателем, а ADEV, TDEV и MTIE сравниваются с allantools 2024,6. Записано в tests/fixtures/clock_log_real/RESULTS.md: на поставляемом читателе журналы TICC, версия заголовка и пять сообщений об ошибках не достигли заранее зарегистрированных порогов; их исправили, и сравнение выполнили заново без изменений. Достигнуто, кроме одной пары: MTIE журнала частоты отличается от allantools, потому что allantools интегрирует частоту за вычетом её среднего (зафиксировано как NOT MET; сопоставимое по условиям сравнение достигнуто). Строка остаётся MODELLED: журналы TICC — это измеренные данные часов, перекодированные нашим скриптом в формат счётчика (реальной записи TICC найдено не было), а эталонный читатель имеет того же автора, что и читатель движка.