WORKAN WOOOO
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# stolen from https://austinsnerdythings.com/2025/02/14/revisiting-microsecond-accurate-ntp-for-raspberry-pi-with-gps-pps-in-2025/
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from datetime import datetime
|
||||
|
||||
def parse_chrony_stats(file_path):
|
||||
"""
|
||||
Parse chrony statistics log file and return a pandas DataFrame
|
||||
"""
|
||||
# read file contents first
|
||||
with open(file_path, 'r') as f:
|
||||
file_contents = f.readlines()
|
||||
|
||||
# for each line, if it starts with '=' or ' ', skip it
|
||||
file_contents = [line for line in file_contents if not line.startswith('=') and not line.startswith(' ')]
|
||||
|
||||
# exclude lines that include 'PPS'
|
||||
file_contents = [line for line in file_contents if 'PPS' not in line]
|
||||
|
||||
# Use StringIO to create a file-like object from the filtered contents
|
||||
from io import StringIO
|
||||
csv_data = StringIO(''.join(file_contents))
|
||||
|
||||
# Read the filtered data using pandas
|
||||
df = pd.read_csv(csv_data,
|
||||
delim_whitespace=True,
|
||||
names=['Date', 'Time', 'IP_Address', 'Std_dev', 'Est_offset', 'Offset_sd',
|
||||
'Diff_freq', 'Est_skew', 'Stress', 'Ns', 'Bs', 'Nr', 'Asym'])
|
||||
|
||||
|
||||
# Combine Date and Time columns into a datetime column
|
||||
df['timestamp'] = pd.to_datetime(df['Date'] + ' ' + df['Time'])
|
||||
|
||||
return df
|
||||
|
||||
def plot_est_offset(df):
|
||||
"""
|
||||
Create a plot of Est_offset vs time for each IP address
|
||||
"""
|
||||
plt.figure(figsize=(12, 6))
|
||||
|
||||
# Plot each IP address as a separate series
|
||||
for ip in df['IP_Address'].unique():
|
||||
ip_data = df[df['IP_Address'] == ip]
|
||||
plt.plot(ip_data['timestamp'], ip_data['Est_offset'],
|
||||
marker='o', label=ip, linestyle='-', markersize=4)
|
||||
|
||||
plt.xlabel('Time')
|
||||
plt.ylabel('Estimated Offset (seconds)')
|
||||
plt.title('Chrony Estimated Offset Over Time by IP Address')
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
|
||||
# Rotate x-axis labels for better readability
|
||||
plt.xticks(rotation=45)
|
||||
|
||||
# Adjust layout to prevent label cutoff
|
||||
plt.tight_layout()
|
||||
|
||||
return plt
|
||||
|
||||
def analyze_chrony_stats(file_path):
|
||||
"""
|
||||
Main function to analyze chrony statistics
|
||||
"""
|
||||
# Parse the data
|
||||
df = parse_chrony_stats(file_path)
|
||||
|
||||
# Create summary statistics
|
||||
summary = {
|
||||
'IP Addresses': df['IP_Address'].nunique(),
|
||||
'Time Range': f"{df['timestamp'].min()} to {df['timestamp'].max()}",
|
||||
'Average Est Offset by IP': df.groupby('IP_Address')['Est_offset'].mean().to_dict(),
|
||||
'Max Est Offset by IP': df.groupby('IP_Address')['Est_offset'].max().to_dict(),
|
||||
'Min Est Offset by IP': df.groupby('IP_Address')['Est_offset'].min().to_dict(),
|
||||
'Median Est Offset by IP': df.groupby('IP_Address')['Est_offset'].median().to_dict()
|
||||
}
|
||||
|
||||
# Create the plot
|
||||
plot = plot_est_offset(df)
|
||||
|
||||
return df, summary, plot
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
file_path = "chrony_statistics.log" # Replace with your file path
|
||||
df, summary, plot = analyze_chrony_stats(file_path)
|
||||
|
||||
# Print summary statistics
|
||||
print("\nChrony Statistics Summary:")
|
||||
print("-" * 30)
|
||||
print(f"Number of IP Addresses: {summary['IP Addresses']}")
|
||||
print(f"Time Range: {summary['Time Range']}")
|
||||
print("\nAverage Estimated Offset by IP:")
|
||||
for ip, avg in summary['Average Est Offset by IP'].items():
|
||||
print(f"{ip}: {avg:.2e}")
|
||||
|
||||
print("\nMedian Estimated Offset by IP:")
|
||||
for ip, median in summary['Median Est Offset by IP'].items():
|
||||
print(f"{ip}: {median:.2e}")
|
||||
|
||||
# Show the plot
|
||||
plt.show()
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
numlogs=2500
|
||||
tmp_log="./chrony_statistics.log"
|
||||
|
||||
sudo tail -n $numlogs /var/log/chrony/statistics.log > $tmp_log
|
||||
sudo chown $USER:$USER $tmp_log
|
||||
echo "$(wc -l $tmp_log) logs entered"
|
||||
python chrony_statistics.py
|
||||
rm -f $tmp_log
|
||||
@@ -0,0 +1,5 @@
|
||||
SUBSYSTEM=="pps[0-9]*", GROUP="dialout", MODE="0660"
|
||||
SUBSYSTEM=="tty[0-9]*", GROUP="dialout", MODE="0660"
|
||||
SUBSYSTEM=="tty[A-Z]*[0-9]*", GROUP="dialout", MODE="0660"
|
||||
SUBSYSTEM=="i2c[A-Z]-*[0-9]*", GROUP="dialout", MODE="0660"
|
||||
SUBSYSTEM=="rtc[0-9]*", GROUP="dialout", MODE="0660"
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
# Precision Timekeeping Fuckery Additions
|
||||
## GPS PPS GPIO Signal
|
||||
dtoverlay=pps-gpio,gpiopin=18
|
||||
## GPS GPIO UART
|
||||
enable_uart=1
|
||||
init_uart_baud=9600
|
||||
## I2C Hardware RTC Overlay
|
||||
dtoverlay=i2c-rtc,ds3231
|
||||
# End Precision Timekeeping Fuckery Additions
|
||||
@@ -0,0 +1,44 @@
|
||||
# SHM refclock is shared memory driver, it is populated by GPSd and read by chrony
|
||||
# it is SHM 0
|
||||
# refid is what we want to call this source = NMEA
|
||||
# offset = 0.000 means we do not yet know the delay
|
||||
# precision is how precise this is. not 1e-3 = 1 millisecond, so not very precision
|
||||
# poll 0 means poll every 2^0 seconds = 1 second poll interval
|
||||
# filter 3 means take the average/median (forget which) of the 3 most recent readings. NMEA can be jumpy so we're averaging here
|
||||
refclock SHM 0 refid NMEA offset 0.000 precision 1e-6 poll 0 filter 3
|
||||
|
||||
# PPS refclock is PPS specific, with /dev/pps0 being the source
|
||||
# refid PPS means call it the PPS source
|
||||
# lock NMEA means this PPS source will also lock to the NMEA source for time of day info
|
||||
# offset = 0.0 means no offset... this should probably always remain 0
|
||||
# poll 3 = poll every 2^3=8 seconds. polling more frequently isn't necessarily better
|
||||
# trust means we trust this time. the NMEA will be kicked out as false ticker eventually, so we need to trust the combo
|
||||
refclock PPS /dev/pps0 refid PPS lock NMEA offset 0.0 poll 3 trust
|
||||
|
||||
# allow local connections to ntp
|
||||
allow 10.0.0.0/24
|
||||
|
||||
# manual
|
||||
manual
|
||||
|
||||
# tell clients im the big dog
|
||||
local stratum 1
|
||||
|
||||
# NTS Servers
|
||||
## Stratum 1
|
||||
### Wiktel
|
||||
server ntp1.wiktel.com iburst nts maxsamples 1
|
||||
server ntp2.wiktel.com iburst nts maxsamples 1
|
||||
## Stratum 2
|
||||
### Ubuntu
|
||||
server 1.ntp.ubuntu.com iburst nts maxsamples 1
|
||||
server 2.ntp.ubuntu.com iburst nts maxsamples 1
|
||||
### System76
|
||||
server ohio.time.system76.com iburst nts maxsamples 1
|
||||
server virginia.time.system76.com iburst nts maxsamples 1
|
||||
### Cifelli
|
||||
server stratum1.time.cifelli.xyz iburst nts maxsamples 1
|
||||
server time.cifelli.xyz iburst nts maxsamples 1
|
||||
## Stratum 3
|
||||
### Cloudflare
|
||||
server time.cloudflare.com iburst nts maxsamples 1
|
||||
@@ -0,0 +1,14 @@
|
||||
# USB might be /dev/ttyACM0
|
||||
# serial might be /dev/ttyS0
|
||||
# pps might be /dev/pps0
|
||||
DEVICES="/dev/ttyS0 /dev/pps0"
|
||||
|
||||
# -n means start without a client connection (i.e. at boot)
|
||||
# -s 9600 is to set baud to 9600
|
||||
GPSD_OPTIONS="-n -s 9600"
|
||||
|
||||
# also start in general
|
||||
START_DAEMON="true"
|
||||
|
||||
# Automatically hot add/remove USB GPS devices via gpsdctl
|
||||
# USBAUTO="true"
|
||||
@@ -0,0 +1,26 @@
|
||||
instance_name = "Precision Timekeeping"
|
||||
|
||||
[server]
|
||||
# Protocol (http, https, h2, socket)
|
||||
protocol = https
|
||||
|
||||
# The http port to use
|
||||
http_port = 3000
|
||||
|
||||
[log]
|
||||
# Either "debug", "info", "warn", "error", "critical", default is "info"
|
||||
level = info
|
||||
|
||||
# Either "console", "file", "syslog". Default is console and file
|
||||
# Use space to separate multiple modes, e.g. "console file"
|
||||
mode = console file
|
||||
|
||||
[log.file]
|
||||
# log line format, valid options are text, console and json
|
||||
format = text
|
||||
|
||||
# This enables automated log rotate(switch of following options), default is true
|
||||
log_rotate = true
|
||||
|
||||
# Max line number of single file, default is 1000000
|
||||
max_lines = 1000000
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
# dev=$1
|
||||
@@ -0,0 +1,21 @@
|
||||
# report usage statistics to influxdb company
|
||||
reporting-enabled = false
|
||||
|
||||
[meta]
|
||||
# Where the metadata/raft database is stored
|
||||
dir = "/var/lib/influxdb/meta"
|
||||
|
||||
[data]
|
||||
# The directory where the TSM storage engine stores TSM files.
|
||||
dir = "/var/lib/influxdb/data"
|
||||
|
||||
# The directory where the TSM storage engine stores WAL files.
|
||||
wal-dir = "/var/lib/influxdb/wal"
|
||||
|
||||
# Trace logging provides more verbose output around the tsm engine. Turning
|
||||
# this on can provide more useful output for debugging tsm engine issues.
|
||||
trace-logging-enabled = false
|
||||
|
||||
[logging]
|
||||
format = "auto"
|
||||
level = "info"
|
||||
@@ -0,0 +1,8 @@
|
||||
# notify webhook of reboot
|
||||
@reboot bash /usr/share/customscripts/ifnet "/usr/share/customscripts/webhook bootup"
|
||||
|
||||
# set the system time from the RTC every reboot
|
||||
@reboot /usr/sbin/hwclock -s 2>> /var/log/root-crontab.log
|
||||
|
||||
# set the RTC from the system time every four hours
|
||||
0 */4 * * * /usr/sbin/hwclock -w 2>> /var/log/root-crontab.log
|
||||
@@ -0,0 +1,47 @@
|
||||
#
|
||||
# This file MUST be edited with the 'visudo' command as root.
|
||||
#
|
||||
# Please consider adding local content in /etc/sudoers.d/ instead of
|
||||
# directly modifying this file.
|
||||
#
|
||||
# See the man page for details on how to write a sudoers file.
|
||||
#
|
||||
Defaults env_reset
|
||||
Defaults mail_badpass
|
||||
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
# This fixes CVE-2005-4890 and possibly breaks some versions of kdesu
|
||||
# (#1011624, https://bugs.kde.org/show_bug.cgi?id=452532)
|
||||
Defaults use_pty
|
||||
|
||||
# This preserves proxy settings from user environments of root
|
||||
# equivalent users (group sudo)
|
||||
#Defaults:%sudo env_keep += "http_proxy https_proxy ftp_proxy all_proxy no_proxy"
|
||||
|
||||
# This allows running arbitrary commands, but so does ALL, and it means
|
||||
# different sudoers have their choice of editor respected.
|
||||
#Defaults:%sudo env_keep += "EDITOR"
|
||||
|
||||
# Completely harmless preservation of a user preference.
|
||||
#Defaults:%sudo env_keep += "GREP_COLOR"
|
||||
|
||||
# While you shouldn't normally run git as root, you need to with etckeeper
|
||||
#Defaults:%sudo env_keep += "GIT_AUTHOR_* GIT_COMMITTER_*"
|
||||
|
||||
# Per-user preferences; root won't have sensible values for them.
|
||||
#Defaults:%sudo env_keep += "EMAIL DEBEMAIL DEBFULLNAME"
|
||||
|
||||
# "sudo scp" or "sudo rsync" should be able to use your SSH agent.
|
||||
#Defaults:%sudo env_keep += "SSH_AGENT_PID SSH_AUTH_SOCK"
|
||||
|
||||
# User privilege specification
|
||||
root ALL=(ALL:ALL) ALL
|
||||
|
||||
# Allow members of group sudo to execute any command
|
||||
# %sudo ALL=(ALL:ALL) ALL
|
||||
|
||||
# passwordless sudo for sudo group
|
||||
%sudo ALL = (ALL) NOPASSWD: ALL
|
||||
|
||||
# See sudoers(5) for more information on "@include" directives:
|
||||
@includedir /etc/sudoers.d
|
||||
@@ -0,0 +1,22 @@
|
||||
[global_tags]
|
||||
[agent]
|
||||
interval = "10s"
|
||||
round_interval = true
|
||||
metric_batch_size = 10000
|
||||
metric_buffer_limit = 1000000
|
||||
collection_jitter = "0s"
|
||||
flush_interval = "10s"
|
||||
flush_jitter = "0s"
|
||||
precision = "0s"
|
||||
logfile = "/var/log/telegraf/telegraf.log"
|
||||
# debug = true
|
||||
|
||||
[[inputs.chrony]]
|
||||
metrics = ["tracking", "measurements", "statistics"]
|
||||
timeout = "3s"
|
||||
|
||||
[[outputs.influxdb]]
|
||||
urls = ["http://127.0.0.1:8086"]
|
||||
database = "Chrony_Stats"
|
||||
timeout = "3s"
|
||||
# skip_database_creation = true
|
||||
Reference in New Issue
Block a user