Measuring NTP and PTP Accuracy With An Oscilloscope (part 2: Chrony's poll and filter settings)
In part 1 yesterday I went through all of the work needed to measure NTP and PTP accuracy between two computers on my desk using an oscilloscope. I demonstrated that PTP was accurate to a mean of 4 ns with 2 ns of standard deviation. Under ideal circumstances NTP was only slightly worse at 8–10 ns with a SD of 12–20 ns, depending on the test setup.
I measured these with extremely high NTP polling rates, thousands of times more frequent than Chrony’s defaults. I ran a few tests with slower polling rates but had a hard time getting stable results due to issues with my test environment. I went back and rethought a few things, and I was able to do a bunch more testing overnight.
I wanted answers to these two questions:
What is the best polling rate for Chrony on an extremely low-latency, low-jitter LAN? Does Chrony’s per-source filter setting improve accuracy? After running tests overnight, I have my answers:
For the best accuracy, use something between minpoll -4 and minpoll -1.Please only poll this aggressively when you control the NTP server that you’re polling. Don’t DoS public servers.
Above 1 second or so, error starts increasing exponentially. Very high polling rates (1/128th and sometimes 1/64th of a second) show added error as well. The filter keyword is never a win in my environment. Small values (filter=2 and filter=4) don’t make a huge difference in results; larger values add increasing amounts of error. Here’s how the measured time offset varied as the update period and filter settings changed:
{"legend":{"bottom":5,"data":["filter=1","filter=2","filter=4","filter=8","filter=16"]},"series":[{"data":[[0.0625,2,17.5,12684],[0.125,5.1,18.3,12559],[0.25,1.5,20,12235],[0.5,5.6,39.8,12271],[1,1.3,40.5,12279],[2,78.1,155.6,12359],[4,26.7,285.3,12258],[8,475,567.4,12343]],"name":"filter=1","smooth":false,"type":"line"},{"data":[[0.0625,1.8,25,12764],[0.125,1.7,22,12691],[0.25,9.5,23.3,12560],[0.5,14.8,28.9,12336],[1,3.9,27.8,12302],[2,177.6,285.2,12258],[4,203.6,448.3,12295],[8,228.5,298.2,12307]],"name":"filter=2","smooth":false,"type":"line"},{"data":[[0.0625,7.7,22.4,12820],[0.125,3.5,20.5,12773],[0.25,1.4,20.2,12695],[0.5,23.9,38.7,12606],[1,57.3,81.3,12304],[2,60.7,124.1,12264],[4,77.4,174.8,12187],[8,128.9,289.9,12360]],"name":"filter=4","smooth":false,"type":"line"},{"data":[[0.0625,12.8,32.3,12950],[0.125,7,17.7,12804],[0.25,14.5,24.2,12760],[0.5,11.7,35.2,12714],[1,75,121.3,12624],[2,4.8,69.6,12315],[4,47.7,213,12299],[8,889.5,1070.5,12324]],"name":"filter=8","smooth":false,"type":"line"},{"data":[[0.125,15.7,37.1,12981],[0.25,11.2,27,12795],[0.5,43.5,76,12769],[1,43.9,89.9,12695],[2,163.8,312.4,12538],[4,424.1,8255.2,12404],[8,515.6,1350.4,12254]],"name":"filter=16","smooth":false,"type":"line"}],"title":{"left":"center","text":"NTP Clock offset by effective polling rate and filter"},"tooltip":{"trigger":"axis"},"xAxis":{"logBase":2,"max":8,"min":0.0625,"name":"effective seconds between polls (including filtering period)","nameGap":20,"nameLocation":"middle","type":"log"},"yAxis":{"logBase":10,"name":"nanoseconds →","nameGap":40,"nameLocation":"middle","nameRotate":90,"type":"log"}} And here’s the measured jitter in the same environment:
{"legend":{"bottom":5,"data":["filter=1","filter=2","filter=4","filter=8","filter=16"]},"series":[{"data":[[0.0625,17.5,12684],[0.125,18.3,12559],[0.25,20,12235],[0.5,39.8,12271],[1,40.5,12279],[2,155.6,12359],[4,285.3,12258],[8,567.4,12343]],"name":"filter=1","smooth":false,"type":"line"},{"data":[[0.0625,25,12764],[0.125,22,12691],[0.25,23.3,12560],[0.5,28.9,12336],[1,27.8,12302],[2,285.2,12258],[4,448.3,12295],[8,298.2,12307]],"name":"filter=2","smooth":false,"type":"line"},{"data":[[0.0625,22.4,12820],[0.125,20.5,12773],[0.25,20.2,12695],[0.5,38.7,12606],[1,81.3,12304],[2,124.1,12264],[4,174.8,12187],[8,289.9,12360]],"name":"filter=4","smooth":false,"type":"line"},{"data":[[0.0625,32.3,12950],[0.125,17.7,12804],[0.25,24.2,12760],[0.5,35.2,12714],[1,121.3,12624],[2,69.6,12315],[4,213,12299],[8,1070.5,12324]],"name":"filter=8","smooth":false,"type":"line"},{"data":[[0.125,37.1,12981],[0.25,27,12795],[0.5,76,12769],[1,89.9,12695],[2,312.4,12538],[4,8255.2,12404],[8,1350.4,12254]],"name":"filter=16","smooth":false,"type":"line"}],"title":{"left":"center","text":"NTP Clock jitter by effective polling rate and filter"},"tooltip":{"trigger":"axis"},"xAxis":{"logBase":2,"max":8,"min":0.0625,"name":"effective seconds between polls (including filtering period)","nameGap":20,"nameLocation":"middle","type":"log"},"yAxis":{"name":"nanoseconds →","nameGap":40,"nameLocation":"middle","nameRotate":90,"type":"log"}} Read on for details on how I measured these.