polish: Week 2 improvements - error messages, progress, performance

## Error Message Improvements (Phase 1)
-  Cluster backup: Added database type context to error messages
-  Rate limiting: Show specific host and wait time in errors
-  Connection failures: Added troubleshooting steps (3-point checklist)
-  Encryption errors: Include backup location in failure messages
-  Archive not found: Suggest cloud:// URI for remote backups
-  Decryption: Hint about wrong key verification
-  Backup directory: Include permission hints and --backup-dir suggestion
-  Backup execution: Show database name and diagnostic checklist
-  Incremental: Better base backup path guidance
-  File verification: Indicate silent command failure possibility

## Progress Indicator Enhancements (Phase 2)
-  ETA calculations: Real-time estimation based on transfer speed
-  Speed formatting: formatSpeed() helper (B/KB/MB/GB per second)
-  Byte formatting: formatBytes() with proper unit scaling
-  Duration display: Improved to show Xm Ys format vs decimal
-  Progress updates: Show [%] bytes/total (speed, ETA: time) format

## Performance Optimization (Phase 3)
-  Buffer sizes: Increased stderr read buffers from 4KB to 64KB
-  Scanner buffers: 64KB initial, 1MB max for command output
-  I/O throughput: Better buffer alignment for streaming operations

## Code Cleanup (Phase 4)
-  TODO comments: Converted to descriptive comments
-  Method calls: Fixed GetDatabaseType() -> DisplayDatabaseType()
-  Build verification: All changes compile successfully

## Summary
Time: ~1.5h (2-4h estimated)
Changed: 4 files (cmd/backup_impl.go, cmd/restore.go, internal/backup/engine.go, internal/progress/detailed.go)
Impact: Better UX, clearer errors, faster I/O, cleaner code
This commit is contained in:
2025-11-26 10:30:29 +00:00
parent 2039a22d95
commit 3ef57bb2f5
8 changed files with 115 additions and 47 deletions

View File

@@ -200,7 +200,7 @@ func (ot *OperationTracker) SetFileProgress(filesDone, filesTotal int) {
}
}
// SetByteProgress updates byte-based progress
// SetByteProgress updates byte-based progress with ETA calculation
func (ot *OperationTracker) SetByteProgress(bytesDone, bytesTotal int64) {
ot.reporter.mu.Lock()
defer ot.reporter.mu.Unlock()
@@ -213,6 +213,27 @@ func (ot *OperationTracker) SetByteProgress(bytesDone, bytesTotal int64) {
if bytesTotal > 0 {
progress := int((bytesDone * 100) / bytesTotal)
ot.reporter.operations[i].Progress = progress
// Calculate ETA and speed
elapsed := time.Since(ot.reporter.operations[i].StartTime).Seconds()
if elapsed > 0 && bytesDone > 0 {
speed := float64(bytesDone) / elapsed // bytes/sec
remaining := bytesTotal - bytesDone
eta := time.Duration(float64(remaining)/speed) * time.Second
// Update progress message with ETA and speed
if ot.reporter.indicator != nil {
speedStr := formatSpeed(int64(speed))
etaStr := formatDuration(eta)
progressMsg := fmt.Sprintf("[%d%%] %s / %s (%s/s, ETA: %s)",
progress,
formatBytes(bytesDone),
formatBytes(bytesTotal),
speedStr,
etaStr)
ot.reporter.indicator.Update(progressMsg)
}
}
}
break
}
@@ -418,10 +439,59 @@ func (os *OperationSummary) FormatSummary() string {
// formatDuration formats a duration in a human-readable way
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
if d < time.Second {
return "<1s"
} else if d < time.Minute {
return fmt.Sprintf("%.0fs", d.Seconds())
} else if d < time.Hour {
return fmt.Sprintf("%.1fm", d.Minutes())
mins := int(d.Minutes())
secs := int(d.Seconds()) % 60
return fmt.Sprintf("%dm%ds", mins, secs)
}
hours := int(d.Hours())
mins := int(d.Minutes()) % 60
return fmt.Sprintf("%dh%dm", hours, mins)
}
// formatBytes formats byte count in human-readable units
func formatBytes(bytes int64) string {
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
TB = 1024 * GB
)
switch {
case bytes >= TB:
return fmt.Sprintf("%.2f TB", float64(bytes)/float64(TB))
case bytes >= GB:
return fmt.Sprintf("%.2f GB", float64(bytes)/float64(GB))
case bytes >= MB:
return fmt.Sprintf("%.2f MB", float64(bytes)/float64(MB))
case bytes >= KB:
return fmt.Sprintf("%.2f KB", float64(bytes)/float64(KB))
default:
return fmt.Sprintf("%d B", bytes)
}
}
// formatSpeed formats transfer speed in appropriate units
func formatSpeed(bytesPerSec int64) string {
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
switch {
case bytesPerSec >= GB:
return fmt.Sprintf("%.2f GB", float64(bytesPerSec)/float64(GB))
case bytesPerSec >= MB:
return fmt.Sprintf("%.1f MB", float64(bytesPerSec)/float64(MB))
case bytesPerSec >= KB:
return fmt.Sprintf("%.0f KB", float64(bytesPerSec)/float64(KB))
default:
return fmt.Sprintf("%d B", bytesPerSec)
}
return fmt.Sprintf("%.1fh", d.Hours())
}