Research-Stack/4-Infrastructure/NoDupeLabs/nodupe/tools/time_sync/README.md

409 lines
12 KiB
Markdown

# TimeSync Plugin
The TimeSync plugin provides NTP-based time synchronization and FastDate64 timestamp encoding for the NoDupeLabs system. It ensures accurate, monotonic timekeeping that is immune to system clock changes.
**Algorithm Attribution:**
- FastDate64 encoding/decoding based on Ben Joffe's work
- Reference: https://www.benjoffe.com/fast-date-64
## Features
- **NTP Time Synchronization**: Query multiple NTP servers (Google, Cloudflare, pool) for accurate time
- **FastDate64 Encoding**: Compact 64-bit timestamp encoding for efficient storage and sorting
- **Monotonic Timekeeping**: Process-local corrected clock immune to system clock jumps
- **Background Synchronization**: Automatic periodic time synchronization
- **Runtime Controls**: Enable/disable network operations and background sync
- **Environment Configuration**: Configurable via environment variables
## Installation
The TimeSync plugin is included with NoDupeLabs and requires no additional dependencies.
## Usage
### Basic Usage
```python
from nodupe.plugins.time_sync import TimeSyncPlugin
# Create and initialize the plugin
plugin = TimeSyncPlugin()
plugin.initialize()
# Get corrected timestamp
corrected_time = plugin.get_corrected_time()
print(f"Corrected time: {corrected_time}")
# Get compact 64-bit timestamp
compact_ts = plugin.get_corrected_fast64()
print(f"FastDate64: {compact_ts}")
```
### Advanced Configuration
```python
# Custom configuration
plugin = TimeSyncPlugin(
servers=["custom.ntp.com", "pool.ntp.org"],
timeout=5.0, # Socket timeout in seconds
attempts=3, # Number of attempts per server
max_acceptable_delay=1.0, # Maximum network delay in seconds
smoothing_alpha=0.3 # Offset smoothing factor (0.0-1.0)
)
# Enable the plugin
plugin.enable()
# Perform initial synchronization
try:
host, server_time, offset, delay = plugin.force_sync()
print(f"Synchronized with {host}")
print(f"Offset: {offset:.3f}s, Delay: {delay:.3f}s")
except Exception as e:
print(f"Synchronization failed: {e}")
```
### Background Synchronization
```python
# Start background synchronization (every 5 minutes)
plugin.start_background(interval=300.0)
# Check status
status = plugin.get_status()
print(f"Background running: {status['background_running']}")
# Stop background synchronization
plugin.stop_background()
```
### Runtime Controls
```python
# Control plugin state
plugin.enable() # Enable the plugin
plugin.disable() # Disable the plugin
# Control network operations
plugin.enable_network() # Allow network queries
plugin.disable_network() # Block network queries
# Control background synchronization
plugin.enable_background() # Allow background sync
plugin.disable_background() # Stop and disable background sync
```
### Fallback Synchronization
The plugin provides automatic fallback when NTP network is unavailable:
```python
# Primary method: NTP synchronization (preferred)
try:
host, server_time, offset, delay = plugin.force_sync()
print(f"Synchronized via NTP: {host}")
except Exception as e:
print(f"NTP failed: {e}")
# Fallback: Use automatic fallback strategy
source, server_time, offset, delay = plugin.sync_with_fallback()
print(f"Synchronized via {source}: offset={offset:.3f}s, delay={delay:.3f}s")
# Check synchronization status
status = plugin.get_sync_status()
print(f"Sync method: {status['sync_method']}")
print(f"Has external reference: {status['has_external_reference']}")
```
**Fallback Strategy:**
1. **Primary (Preferred)**: NTP synchronization via network
2. **Fallback 1**: System RTC with monotonic correction
3. **Fallback 2**: Pure monotonic time (when RTC unavailable)
**When to Use Fallback:**
- Network connectivity issues
- Firewall blocking UDP port 123
- NTP server unavailability
- Restricted environments without internet access
**Benefits:**
- **Graceful Degradation**: Continues working even without network
- **Monotonic Guarantee**: Always provides monotonic time progression
- **Best Available Reference**: Uses the most accurate local time source
- **Transparent Operation**: Same API regardless of sync method
### FastDate64 Utilities
```python
from nodupe.plugins.time_sync import TimeSyncPlugin
# Encode timestamp to 64-bit integer (Ben Joffe's FastDate64)
# Reference: https://www.benjoffe.com/fast-date-64
ts = time.time()
encoded = TimeSyncPlugin.encode_fastdate64(ts)
# Decode back to timestamp
decoded = TimeSyncPlugin.decode_fastdate64(encoded)
# Convert to ISO 8601 string
iso_string = TimeSyncPlugin.fastdate64_to_iso(encoded)
# Convert from ISO 8601 string
encoded_from_iso = TimeSyncPlugin.iso_to_fastdate64(iso_string)
```
### FastDate Utilities (32-bit)
```python
# Encode timestamp to 32-bit integer (Ben Joffe's FastDate)
# Reference: https://www.benjoffe.com/fast-date
# Supports ~47 days from Unix epoch with millisecond precision
ts = time.time()
encoded = TimeSyncPlugin.encode_fastdate(ts)
# Decode back to timestamp
decoded = TimeSyncPlugin.decode_fastdate(encoded)
```
### SafeDate Utilities (32-bit with 2024 epoch)
```python
# Encode timestamp to safe 32-bit integer (Ben Joffe's SafeDate)
# Reference: https://www.benjoffe.com/safe-date
# Uses 2024 as epoch offset for reasonable range with millisecond precision
ts = time.time()
encoded = TimeSyncPlugin.encode_safedate(ts)
# Decode back to timestamp
decoded = TimeSyncPlugin.decode_safedate(encoded)
```
### Algorithm Comparison
| Algorithm | Bits | Range | Precision | Use Case |
|-----------|------|-------|-----------|----------|
| FastDate64 | 64 | ~544 years | Sub-microsecond | General purpose, high precision |
| FastDate | 32 | ~47 days | Millisecond | Short-term, compact storage |
| SafeDate | 32 | 2024±~24 days | Millisecond | Modern applications, safe range |
### Leap Year Integration
The TimeSync plugin integrates with the LeapYear plugin for optimal leap year calculations:
```python
# Check if leap year (uses LeapYear plugin if available, falls back to built-in)
is_leap = plugin.is_leap_year(2024) # True
is_leap = plugin.is_leap_year(2023) # False
# Get days in February for a year
feb_days = plugin.get_days_in_february(2024) # 29 (leap year)
feb_days = plugin.get_days_in_february(2023) # 28 (non-leap year)
# Check if LeapYear plugin is available
plugin_available = plugin.is_leap_year_plugin_available()
print(f"LeapYear plugin available: {plugin_available}")
# Performance comparison:
# - With LeapYear plugin: ~2-5 nanoseconds per calculation (Ben Joffe's algorithm)
# - Without plugin: ~10-20 nanoseconds per calculation (built-in algorithm)
```
**Integration Benefits:**
- **Automatic Plugin Detection**: Loads LeapYear plugin if available
- **Seamless Fallback**: Uses built-in calculations if plugin unavailable
- **Error Resilience**: Falls back to built-in if plugin encounters errors
- **Performance Optimization**: Uses Ben Joffe's fast bitwise algorithm when possible
## Environment Variables
Configure the plugin behavior using environment variables:
- `NODUPE_TIMESYNC_ENABLED` (default: `1`): Enable/disable the plugin
- `NODUPE_TIMESYNC_NO_NETWORK` (default: `0`): Disable network operations
- `NODUPE_TIMESYNC_ALLOW_BG` (default: `1`): Allow background synchronization
Example:
```bash
export NODUPE_TIMESYNC_ENABLED=1
export NODUPE_TIMESYNC_NO_NETWORK=0
export NODUPE_TIMESYNC_ALLOW_BG=1
```
## FastDate64 Format
FastDate64 encodes timestamps into a 64-bit unsigned integer:
- **34 bits** for seconds (supports ~544 years from Unix epoch)
- **30 bits** for fractional seconds (nanosecond precision)
This format provides:
- **Compact storage**: 8 bytes vs 8+ bytes for float/double
- **Fast sorting**: Integer comparison is faster than float
- **Precision**: Sub-microsecond accuracy
- **Range**: Sufficient for most applications
## Error Handling
The plugin provides several error conditions:
```python
from nodupe.plugins.time_sync import TimeSyncPlugin
try:
plugin.force_sync()
except TimeSyncPlugin._get_exception_class() as e:
if "disabled" in str(e):
print("Plugin or network is disabled")
elif "noisy" in str(e):
print("Network delay too high")
else:
print(f"Sync failed: {e}")
# Use maybe_sync() for non-throwing alternative
result = plugin.maybe_sync()
if result is None:
print("Sync not available")
else:
host, server_time, offset, delay = result
```
## Integration with NoDupeLabs
The TimeSync plugin integrates seamlessly with the NoDupeLabs plugin system:
```python
from nodupe.core.plugin_system import PluginManager
# The plugin will be automatically discovered and loaded
manager = PluginManager()
timesync_plugin = manager.get_plugin("TimeSync")
if timesync_plugin:
corrected_time = timesync_plugin.get_corrected_time()
```
## Performance Considerations
- **Network overhead**: NTP queries are lightweight (single UDP packet)
- **CPU overhead**: FastDate64 encoding/decoding is very fast
- **Memory overhead**: Minimal (plugin stores only a few float values)
- **Background sync**: Runs in separate thread, configurable interval
## Security Notes
- Uses standard NTP protocol over UDP port 123
- No system clock modifications (process-local only)
- DNS resolution uses system resolver
- Consider firewall rules if network access is restricted
## Troubleshooting
### Plugin won't sync
- Check network connectivity to NTP servers
- Verify UDP port 123 is not blocked
- Check DNS resolution for NTP servers
- Review plugin status: `plugin.get_status()`
### High network delay
- Try different NTP servers
- Check network congestion
- Increase `max_acceptable_delay` if needed
### Background sync not working
- Verify `allow_background` is enabled
- Check that network operations are allowed
- Review thread status in plugin status
## Examples
### File Metadata with Accurate Timestamps
```python
from nodupe.plugins.time_sync import TimeSyncPlugin
import os
plugin = TimeSyncPlugin()
plugin.initialize()
# Get file creation time with NTP correction
file_path = "/path/to/file"
stat = os.stat(file_path)
file_time = stat.st_mtime
# Encode with FastDate64 for compact storage
metadata_timestamp = plugin.encode_fastdate64(file_time)
creation_timestamp = plugin.get_corrected_fast64()
print(f"File time (FastDate64): {metadata_timestamp}")
print(f"Creation time (FastDate64): {creation_timestamp}")
```
### Database Timestamps
```python
from nodupe.plugins.time_sync import TimeSyncPlugin
plugin = TimeSyncPlugin()
plugin.initialize()
# Get current time for database record
current_time = plugin.get_corrected_fast64()
# Store in database as integer
# INSERT INTO events (timestamp, data) VALUES (?, ?)
# cursor.execute("INSERT INTO events (timestamp, data) VALUES (?, ?)",
# (current_time, "event_data"))
# Query and decode
# cursor.execute("SELECT timestamp FROM events ORDER BY timestamp")
# for row in cursor.fetchall():
# ts = TimeSyncPlugin.decode_fastdate64(row[0])
# print(f"Event time: {ts}")
```
## API Reference
### TimeSyncPlugin Class
#### Constructor
```python
TimeSyncPlugin(
servers: Optional[Iterable[str]] = None,
timeout: float = 3.0,
attempts: int = 2,
max_acceptable_delay: float = 0.5,
smoothing_alpha: float = 0.3,
*,
enabled: Optional[bool] = None,
allow_network: Optional[bool] = None,
allow_background: Optional[bool] = None,
)
```
#### Methods
- `initialize()`: Initialize the plugin
- `shutdown()`: Shutdown the plugin
- `force_sync() -> Tuple[str, float, float, float]`: Force synchronization
- `maybe_sync() -> Optional[Tuple[str, float, float, float]]`: Try synchronization
- `start_background(interval: float = 300.0, initial_sync: bool = True)`: Start background sync
- `stop_background(wait: bool = True, timeout: Optional[float] = None)`: Stop background sync
- `get_corrected_time() -> float`: Get corrected timestamp
- `get_corrected_fast64() -> int`: Get corrected timestamp as FastDate64
- `get_status() -> dict`: Get plugin status
#### Static Methods
- `encode_fastdate64(ts: float) -> int`: Encode timestamp
- `decode_fastdate64(value: int) -> float`: Decode timestamp
- `fastdate64_to_iso(value: int) -> str`: Convert to ISO string
- `iso_to_fastdate64(iso: str) -> int`: Convert from ISO string
#### Runtime Control Methods
- `is_enabled() -> bool`: Check if enabled
- `enable()`: Enable plugin
- `disable()`: Disable plugin
- `is_network_allowed() -> bool`: Check network access
- `enable_network()`: Enable network
- `disable_network()`: Disable network
- `is_background_allowed() -> bool`: Check background sync
- `enable_background()`: Enable background sync
- `disable_background()`: Disable background sync