diff --git a/backup/manager.go b/backup/manager.go index 0d52cbf..dacc908 100644 --- a/backup/manager.go +++ b/backup/manager.go @@ -19,10 +19,11 @@ import ( ) type DBStatus struct { - Name string `json:"name"` - LastBackupTime time.Time `json:"last_backup_time"` - LastBackupSize int64 `json:"last_backup_size"` - LastBackupResult string `json:"last_backup_result"` // success, skipped, failed + Name string `json:"name"` + LastBackupTime time.Time `json:"last_backup_time"` + LastBackupSize int64 `json:"last_backup_size"` + LastBackupResult string `json:"last_backup_result"` // success, skipped, failed + LastBackupDuration time.Duration `json:"last_backup_duration"` } type InstanceStatus struct { @@ -38,10 +39,11 @@ type InstanceStatus struct { } type DBStatusSnapshot struct { - Name string `json:"name"` - LastBackupTime time.Time `json:"last_backup_time"` - LastBackupSize int64 `json:"last_backup_size"` - LastBackupResult string `json:"last_backup_result"` + Name string `json:"name"` + LastBackupTime time.Time `json:"last_backup_time"` + LastBackupSize int64 `json:"last_backup_size"` + LastBackupResult string `json:"last_backup_result"` + LastBackupDuration time.Duration `json:"last_backup_duration"` } type InstanceSnapshot struct { @@ -70,10 +72,11 @@ func (s *InstanceStatus) Snapshot() InstanceSnapshot { for _, db := range s.Databases { snap.Databases = append(snap.Databases, DBStatusSnapshot{ - Name: db.Name, - LastBackupTime: db.LastBackupTime, - LastBackupSize: db.LastBackupSize, - LastBackupResult: db.LastBackupResult, + Name: db.Name, + LastBackupTime: db.LastBackupTime, + LastBackupSize: db.LastBackupSize, + LastBackupResult: db.LastBackupResult, + LastBackupDuration: db.LastBackupDuration, }) } @@ -294,6 +297,7 @@ func (m *Manager) RunInstance(name string) { inst.mu.Lock() dbStatus := inst.Databases[db] dbStatus.LastBackupTime = time.Now() + dbStatus.LastBackupDuration = duration if err != nil { logger.Error(name, "Failed backup for database %s: %v", db, err) dbStatus.LastBackupResult = "failed" @@ -330,14 +334,18 @@ func (m *Manager) RunInstance(name string) { OverallResult: inst.OverallResult, Time: time.Now(), } + var totalDuration time.Duration for _, db := range dbs { dbStat := inst.Databases[db] payload.Databases = append(payload.Databases, notify.DBResult{ - Name: dbStat.Name, - Size: dbStat.LastBackupSize, - Result: dbStat.LastBackupResult, + Name: dbStat.Name, + Size: dbStat.LastBackupSize, + Result: dbStat.LastBackupResult, + Duration: dbStat.LastBackupDuration, }) + totalDuration += dbStat.LastBackupDuration } + payload.TotalDuration = totalDuration inst.mu.Unlock() logger.Info(name, "Backup job completed. Result: %s", inst.OverallResult) diff --git a/notify/notify.go b/notify/notify.go index 54e6edd..1e46cf3 100644 --- a/notify/notify.go +++ b/notify/notify.go @@ -14,16 +14,18 @@ import ( ) type DBResult struct { - Name string `json:"name"` - Size int64 `json:"size"` - Result string `json:"result"` + Name string `json:"name"` + Size int64 `json:"size"` + Result string `json:"result"` + Duration time.Duration `json:"duration"` } type Payload struct { - InstanceName string `json:"instance_name"` - OverallResult string `json:"overall_result"` - Time time.Time `json:"time"` - Databases []DBResult `json:"databases"` + InstanceName string `json:"instance_name"` + OverallResult string `json:"overall_result"` + Time time.Time `json:"time"` + TotalDuration time.Duration `json:"total_duration"` + Databases []DBResult `json:"databases"` } func shouldSend(global config.NotificationEventsConfig, local *config.NotificationEventsConfig, result string) bool { @@ -58,8 +60,9 @@ func sendWebhook(cfg config.WebhookConfig, payload Payload) { message := fmt.Sprintf("**GoDump Backup Result**\nInstance: %s\nResult: %s\nTime: %s\n\nDatabases:\n", payload.InstanceName, payload.OverallResult, payload.Time.Format("2006-01-02 15:04:05")) for _, db := range payload.Databases { - message += fmt.Sprintf("- %s: %s (%s)\n", db.Name, db.Result, formatBytes(db.Size)) + message += fmt.Sprintf("- %s: %s (%s, %s)\n", db.Name, db.Result, formatBytes(db.Size), formatDuration(db.Duration)) } + message += fmt.Sprintf("\n**Total Duration:** %s", formatDuration(payload.TotalDuration)) webhookPayload := map[string]interface{}{ "content": message, @@ -113,6 +116,13 @@ func formatBytes(b int64) string { return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) } +func formatDuration(d time.Duration) string { + if d < time.Second { + return d.Round(time.Millisecond).String() + } + return d.Round(time.Second).String() +} + const emailTmplStr = ` @@ -139,6 +149,7 @@ const emailTmplStr = ` Database Size + Duration Result @@ -147,18 +158,21 @@ const emailTmplStr = ` {{.Name}} {{formatBytes .Size}} + {{formatDuration .Duration}} {{.Result}} {{end}} + +

Total Duration: {{formatDuration .TotalDuration}}

` func sendEmail(cfg config.EmailConfig, payload Payload) { - tmpl, err := template.New("email").Funcs(template.FuncMap{"formatBytes": formatBytes}).Parse(emailTmplStr) + tmpl, err := template.New("email").Funcs(template.FuncMap{"formatBytes": formatBytes, "formatDuration": formatDuration}).Parse(emailTmplStr) if err != nil { logger.Error(payload.InstanceName, "Failed to parse email template: %v", err) return