时钟日志格式
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。日志的相位(单位ns)即对照模板检查的时间误差序列,报告会增加一个clock_log块,说明日志是如何读取的。- 时钟类型:
[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:TimeLab风格的ASCII相位或频率日志,即TimeLab导出、其ASCII导入和Stable32所读取的纯文本形式。每行一个读数:要么是均匀tau0下的单列,要么是两列time value。各列可用空白、逗号或分号分隔。
- 以
#、;或%开头的行为注释。形如key = value(或key: value)的注释会设置tau0(s)、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 Operation Manual,2016年十一月23日和2025年十二月15日的修订版,“measurement modes”)。每个数据字段都是秒,带12位小数(自固件20251101起默认为11位:最多15位的任何位数都可读取)。时间戳按其十进制文本读取为整数个飞秒,而不是二进制浮点数,因此在计数器启动数日之后采集的日志的相位仍保持计数器自身的分辨率:3.0e5 s处的f64时间戳间隔为5.8e-11 s,约为计数器的60 ps。
- 时间戳模式的行(
<seconds> chA、<seconds> chB)以及TimeLab模式的伪时间戳通道(chC)是事件时间戳。所选通道的相位是其时间戳减去标称节拍:x[k] = t[k] − t[0] − n[k]·tau0,其中n[k]是自第一个事件起经过的tau0的整数个数。漏掉的事件是一处间隙。 - 间隔模式的行(
<seconds> TI(A->B),或以TI开头的任何标签)直接读为相位,每个tau0一个读数。场景必须给出tau0_s,因为这些行不带时间。 - 成对时间戳模式按通道顺序打印相同的行,并按时间戳模式读取;3-Corner Hat模式增加合成的
chC,按上述方式读取。 - 周期模式和调试模式的输出会被拒绝。读数不递增的通道会被报告为周期模式。二进制时间戳模式不是文本,其文件会被拒绝,并给出说明此点的消息。
- 以
#开头的行,以及不是<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]前缀的eth0 master、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)、并以YYYY-MM-DD HH:MM:SS形式的UTC日期和时间开头的行。列标题行被跳过并计数;其下的====分隔线被跳过,但不计数。
- 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上,缺失的历元是一处间隙。偏离网格超过tau0的1 % 的读数会被丢弃并计数,重复项同样处理(保留第一个)。间隙策略为下列之一:
refuse(默认):任何间隙都是错误,并统计间隙数;longest:保留最长的无间隙连续段;linear:用线性插值填补内部间隙,并报告填补的数量。
估计器需要无间隙的记录,因此在这里处理间隙。
自己的时钟#
时钟类型上的[clock_log]表会在模拟结果旁报告自己的时钟有多稳定:scenarios/clock-measured-log.toml(kshana example clock-measured-log)就是clock-holdover.toml,另附一份内联TimeLab日志形式的、含96个读数的IGS卫星时钟摘录。若使用自己的文件,把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噪声辨识,在自由度至少为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)。没有频率噪声的拟合、过短而无法拟合的日志,以及不是quantum或classical的replaces,都是会明确说明原因的错误。scenarios/clock-fitted-log.toml是一个示例(6小时的IGS卫星时钟)。
已知噪声上的比较(tests/fixtures/clock_fit/RESULTS.md)显示了什么,以及需要记住什么:
- 达到判据:在每条含有白调频噪声的合成记录上,其水平恢复到5 %之内;拟合所得的卡片在其拟合所用的每个平均时间上,复现真实阿伦偏差的误差在1.35倍之内;拟合辨识出的噪声指数在每个点上都与allantools的相同(在5071A记录上,要在把调理所去除的相位阶跃也从allantools的输入中去除之后)。
- 未达到,并由测试固定:随机游走调频噪声只有在其主导记录最长平均时间时才能恢复,即便如此,在一条131,072 s序列的三条记录中也只有一条做到;在交越点为3,000 s时,拟合把它归给了闪烁调频噪声和漂移。随机游走调频噪声与闪烁调频噪声之间的划分,仅凭一条记录并不可靠。该运行在日志所覆盖的平均时间上复现日志的稳定度;超出之后,两种选择会分道扬镳(闪烁调频噪声是恒定的下限,随机游走调频噪声则按平均时间的平方根增长)。
- 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的读取器有解析器测试,但没有与参考基准(oracle)的比较。该比较所用的日志是合成的,因此其中没有任何内容描述真实的时钟或计数器;对实测序列的比较见下文。
在实测数据上#
tests/clock_log_real_allantools.rs运行tests/fixtures/clock_log_real/PREREGISTRATION.md中预先登记的比较(在任何测试夹具或运行之前已提交),对象为四条实测序列:Stable32的PHASE.DAT、5071A铯钟对氢脉泽的前65,536个读数,以及来自2026-08-02的IGS最终钟差产品中的IGS卫星G01(30 s)和测站ABMF(300 s)的时钟,作为带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采集),而参考读取器与引擎的读取器出自同一作者。