precise-time-ntp
Version:
⏰ Simple NTP time sync for Node.js - Auto-drift, WebSocket & HTML clocks
521 lines (405 loc) • 14.6 kB
Markdown
# Smooth Time Correction
The smooth correction feature provides gradual time adjustments to prevent application disruption while maintaining precision.
## Features
- Automatic convergence detection (within 0.5ms)
- Timeout protection (30 seconds max)
- Adaptive correction intervals
- Server coherence validation
- Drift detection and warnings
## Configuration
### 1. Convergence Detection
The correction system ensures convergence within 0.5ms precision:
```javascript
// Automatic convergence detection
timeSync.on('correctionComplete', (data) => {
if (data.converged) {
console.log(`Perfect convergence: ${data.finalOffset}ms`);
}
});
```
### 2. Server Coherence Validation
Automatically tests multiple NTP servers and detects anomalies:
```javascript
await timeSync.sync({ coherenceValidation: true });
timeSync.on('coherenceWarning', (data) => {
console.log(`Variance between servers: ${data.variance}ms`);
console.log('Servers:', data.servers);
});
```
### 3. Drift Detection
Monitors system clock drift and warns when re-sync is needed:
```javascript
timeSync.on('driftWarning', (data) => {
const hours = (data.elapsed / 3600000).toFixed(1);
console.log(`Clock has been running for ${hours} hours without sync`);
});
```
### 4. Timeout Protection
Prevents infinite correction loops with automatic timeout:
```javascript
// Correction automatically times out after 30 seconds
timeSync.on('correctionComplete', (data) => {
if (data.timeout) {
console.log('Correction timed out, applied final offset');
}
});
```
### 5. Adaptive Intervals
Correction intervals automatically adjust based on offset size for optimal performance.
## The Time Jump Problem
When your system clock has a significant offset, instant correction can cause serious issues in running applications.
### What Happens Without Smooth Correction
```javascript
// ❌ PROBLEM: Without smooth correction
const timeSync = require('precise-time-ntp');
await timeSync.sync(); // System is 3 seconds behind
// Later, during auto-sync...
// Time instantly jumps forward 3 seconds!
// This can break:
// - Running timers and intervals
// - Performance measurements
// - Real-time applications
// - Log timestamps
```
**Real-world consequences:**
- `setTimeout()` and `setInterval()` behave erratically
- Performance measurements show negative durations
- Database timestamps become inconsistent
- Real-time games and animations stutter
- Monitoring systems show false spikes
## The Solution: Gradual Correction
Smooth correction gradually adjusts time over multiple sync cycles with convergence detection and server validation.
### Basic Setup
```javascript
const timeSync = require('precise-time-ntp');
// Enable smooth correction (recommended for production)
timeSync.setSmoothCorrection(true, {
maxCorrectionJump: 1000, // Max 1s instant jump
correctionRate: 0.1, // Correct 10% per sync cycle
maxOffsetThreshold: 5000 // Force instant if >5s off
});
await timeSync.sync();
timeSync.startAutoSync(300000); // Auto-sync every 5 minutes
// Listen for completion events
timeSync.on('correctionComplete', (data) => {
console.log(`Correction completed: ${data.finalOffset}ms`);
if (data.converged) console.log('Perfect convergence achieved');
});
```
### How It Works
1. **Offset Detection**: During sync, compare new offset with current
2. **Correction Decision**:
- If `offset < maxCorrectionJump` → Brutal correction (fast)
- If `offset > maxOffsetThreshold` → Brutal correction (necessary)
- Otherwise → Smooth correction
3. **Gradual Application**: Offset is corrected progressively at configured rate
### Practical Example
```javascript
// Detected offset: 2500ms
// maxCorrectionJump: 1000ms → Smooth correction
// correctionRate: 0.1 (10%)
// Sync 1: 2500ms → 2250ms (corrected 250ms)
// Sync 2: 2250ms → 2025ms (corrected 225ms)
// Sync 3: 2025ms → 1822ms (corrected 203ms)
// ... until convergence
```
## Events and Monitoring
### Listen to Corrections
```javascript
timeSync.on('sync', (data) => {
if (data.gradualCorrection) {
console.log(`🔧 Smooth correction enabled`);
console.log(` Real offset: ${data.offset}ms`);
console.log(` Applied offset: ${data.correctedOffset}ms`);
console.log(` Difference: ${data.offsetDiff}ms`);
}
});
timeSync.on('correctionComplete', (data) => {
console.log(`✅ Correction completed: ${data.finalOffset}ms`);
});
```
### Detailed Statistics
```javascript
const stats = timeSync.stats();
console.log({
offset: stats.offset, // Real server offset
correctedOffset: stats.correctedOffset, // Applied offset
targetOffset: stats.targetOffset, // Target offset
correctionInProgress: stats.correctionInProgress
});
```
## Advanced Configuration
### Specific Use Cases
```javascript
// For precise logs (slow but smooth correction)
timeSync.setSmoothCorrection(true, {
maxCorrectionJump: 500, // Very small jumps
correctionRate: 0.05, // Very gradual (5%)
maxOffsetThreshold: 10000 // High threshold
});
// For real-time apps (balanced)
timeSync.setSmoothCorrection(true, {
maxCorrectionJump: 1000, // Moderate jumps
correctionRate: 0.1, // Normal correction (10%)
maxOffsetThreshold: 5000 // Standard threshold
});
// For performance measurements (fast correction)
timeSync.setSmoothCorrection(true, {
maxCorrectionJump: 2000, // Larger accepted jumps
correctionRate: 0.2, // Fast correction (20%)
maxOffsetThreshold: 3000 // Low threshold
});
```
### Dynamic Disable
```javascript
// In emergency, force correction
timeSync.forceCorrection();
// Or temporarily disable
timeSync.setSmoothCorrection(false);
```
## Recommendations
### When to Use Smooth Correction
✅ **Recommended for:**
- Web applications with animations
- Precise logging systems
- Real-time applications
- Performance measurements
- Any system requiring monotonic time
❌ **Not necessary for:**
- Simple batch scripts
- Applications tolerant to jumps
- Systems with very frequent sync (< 1min)
### Best Practices
1. **Test first**: Use examples to see the effect
2. **Adjust for context**: Different parameters per application
3. **Monitor**: Watch correction events
4. **Fallback**: Keep ability to force correction
```javascript
// Robust configuration
timeSync.setSmoothCorrection(true, {
maxCorrectionJump: 1000,
correctionRate: 0.1,
maxOffsetThreshold: 5000
});
// With emergency fallback
setTimeout(() => {
## Monitoring and Best Practices
### Event-Driven Monitoring
```javascript
// Comprehensive monitoring setup
timeSync.on('sync', (data) => {
console.log(`✅ Synced with ${data.server}`);
console.log(`📊 Offset: ${data.offset}ms`);
if (data.coherenceVariance) {
console.log(`🔍 Server variance: ${data.coherenceVariance}ms`);
}
});
timeSync.on('coherenceWarning', (data) => {
// Log warning and potentially alert administrators
console.warn(`⚠️ NTP servers variance: ${data.variance}ms`);
console.warn('Servers tested:', data.servers);
// Consider switching to backup servers if variance is too high
if (data.variance > 500) {
console.error('🚨 Critical NTP variance detected!');
}
});
timeSync.on('driftWarning', (data) => {
const hours = (data.elapsed / 3600000).toFixed(1);
console.warn(`⏰ Clock drift: ${hours}h without sync`);
// Trigger immediate re-sync
timeSync.sync().catch(console.error);
});
timeSync.on('correctionComplete', (data) => {
console.log(`🎯 Correction completed: ${data.finalOffset}ms`);
if (data.timeout) {
console.warn('⚠️ Correction timed out');
}
if (data.converged) {
console.log('Perfect convergence achieved');
}
});
```
### Health Check Function
```javascript
function checkTimeSyncHealth() {
const stats = timeSync.stats();
const health = {
synchronized: stats.synchronized,
offset: stats.offset,
correctedOffset: stats.correctedOffset,
correctionInProgress: stats.correctionInProgress,
uptime: stats.uptime,
// Advanced metrics
serverCoherence: stats.serverOffsets ?
Object.keys(stats.serverOffsets).length > 1 : false,
lastSyncAge: Date.now() - (stats.lastSync?.getTime() || 0),
// Health indicators
status: 'healthy'
};
// Determine health status
if (!health.synchronized) {
health.status = 'critical';
health.issue = 'Not synchronized';
} else if (health.lastSyncAge > 3600000) { // > 1 hour
health.status = 'warning';
health.issue = 'Sync too old';
} else if (Math.abs(health.offset) > 1000) { // > 1 second
health.status = 'warning';
health.issue = 'High offset';
}
return health;
}
// Use in monitoring
setInterval(() => {
const health = checkTimeSyncHealth();
if (health.status !== 'healthy') {
console.warn('TimeSync Health Issue:', health);
}
}, 60000); // Check every minute
```
### Robust Error Handling
```javascript
// Comprehensive error handling
async function robustTimeSync() {
const maxRetries = 3;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await timeSync.sync({
coherenceValidation: true,
timeout: 5000
});
console.log(`✅ Sync successful on attempt ${attempt}`);
return;
} catch (error) {
lastError = error;
console.warn(`❌ Sync attempt ${attempt} failed: ${error.message}`);
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`⏳ Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// All attempts failed
console.error(`🚨 All sync attempts failed. Last error: ${lastError.message}`);
// Fallback: disable coherence validation and try once more
try {
await timeSync.sync({ coherenceValidation: false });
console.log('✅ Fallback sync successful (coherence validation disabled)');
} catch (error) {
console.error('🚨 Even fallback sync failed:', error.message);
throw error;
}
}
```
Smooth correction transforms your application from a system with jarring time jumps to one with fluid and predictable time behavior! 🎯
## ❓ FAQ: Why Does the Offset Stay Constant?
### This is Normal! Here's Why
A constant offset (e.g., 40ms) is **NOT a problem** - it's normal behavior:
```javascript
// Typical example
await timeSync.sync();
console.log(timeSync.offset()); // 42ms
// ... 5 minutes later ...
console.log(timeSync.offset()); // 42ms (still the same)
```
### Technical Explanation
The **offset** represents the **constant difference** between:
- Your computer's clock (which may be slightly off)
- The precise time from the NTP server
```javascript
// Simplified formula:
const offset = ntpServerTime - computerTime;
// If your PC is 40ms behind: offset = +40ms
// If your PC is 40ms ahead: offset = -40ms
```
### Why the Offset Doesn't Change
1. **Stable system clock**: Your computer maintains a regular rhythm
2. **Fixed difference**: The difference with the NTP server remains constant
3. **Precision**: This is exactly what we want!
```javascript
// Real time example:
// 14:30:00.000 (NTP server)
// 14:29:59.960 (your PC) → offset = +40ms
// 5 minutes later:
// 14:35:00.000 (NTP server)
// 14:34:59.960 (your PC) → offset = +40ms (still the same!)
```
### Quand l'Offset Doit-il Changer ?
L'offset ne change que si :
✅ **Changements normaux :**
- Température du processeur (dilatation des composants)
- Charge système élevée
- Mise à jour système
- Redémarrage
❌ **Problèmes à surveiller :**
- Offset qui dérive constamment (horloge défaillante)
- Sauts brusques très fréquents
- Offset qui augmente sans cesse
### Surveillance des Dérives
```javascript
const timeSync = require('precise-time-ntp');
let lastOffset = 0;
let driftHistory = [];
timeSync.on('sync', (data) => {
const currentOffset = data.offset;
const drift = Math.abs(currentOffset - lastOffset);
driftHistory.push(drift);
// Garder seulement les 10 dernières mesures
if (driftHistory.length > 10) {
driftHistory.shift();
}
const avgDrift = driftHistory.reduce((a, b) => a + b, 0) / driftHistory.length;
console.log(`📊 Offset: ${currentOffset}ms | Dérive: ${drift}ms | Moy: ${avgDrift.toFixed(1)}ms`);
// Alerte si dérive excessive
if (avgDrift > 100) {
console.warn('⚠️ Dérive d\'horloge élevée détectée !');
}
lastOffset = currentOffset;
});
```
### Offsets Typiques par Appareil
| Type d'appareil | Offset typique | Commentaire |
|------------------|----------------|-------------|
| PC Desktop | 10-50ms | Très stable |
| Laptop | 20-80ms | Varie selon l'alimentation |
| Serveur | 5-20ms | Horloge précise |
| Raspberry Pi | 50-200ms | Horloge moins précise |
| VM/Container | 10-100ms | Dépend de l'hyperviseur |
### Exemple de Monitoring
```javascript
// Script de surveillance d'horloge
const timeSync = require('precise-time-ntp');
async function monitorClock() {
await timeSync.sync();
timeSync.startAutoSync(60000); // Sync toutes les minutes
let measurements = [];
timeSync.on('sync', (data) => {
measurements.push({
timestamp: new Date(),
offset: data.offset,
server: data.server
});
// Analyse des 10 dernières mesures
if (measurements.length >= 10) {
const recent = measurements.slice(-10);
const offsets = recent.map(m => m.offset);
const min = Math.min(...offsets);
const max = Math.max(...offsets);
const avg = offsets.reduce((a, b) => a + b) / offsets.length;
const stability = max - min;
console.log(`\n📈 Analyse de stabilité :`);
console.log(` Offset moyen: ${avg.toFixed(1)}ms`);
console.log(` Plage: ${min}ms - ${max}ms`);
console.log(` Stabilité: ${stability.toFixed(1)}ms`);
if (stability > 500) {
console.warn('⚠️ Horloge instable détectée !');
} else {
console.log('✅ Horloge stable');
}
}
});
}
monitorClock();
```