103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
# 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() |