Машинный перевод, не проверенный носителем языка. Эталоном служит английская версия.

Синхронизация времениПравить на GitHubИсточник: docs/CLOCK-LOG-FORMATS.md

Форматы журналов часов

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 найдено не было), а эталонный читатель имеет того же автора, что и читатель движка.