feat: track and report per-database and total backup duration in status and notifications
Build and Push / build (godump, amd64, linux) (push) Successful in 15s

This commit is contained in:
2026-07-11 14:54:58 +01:00
parent b1c265bbef
commit 4c776fd841
2 changed files with 46 additions and 24 deletions
+8
View File
@@ -23,6 +23,7 @@ type DBStatus struct {
LastBackupTime time.Time `json:"last_backup_time"` LastBackupTime time.Time `json:"last_backup_time"`
LastBackupSize int64 `json:"last_backup_size"` LastBackupSize int64 `json:"last_backup_size"`
LastBackupResult string `json:"last_backup_result"` // success, skipped, failed LastBackupResult string `json:"last_backup_result"` // success, skipped, failed
LastBackupDuration time.Duration `json:"last_backup_duration"`
} }
type InstanceStatus struct { type InstanceStatus struct {
@@ -42,6 +43,7 @@ type DBStatusSnapshot struct {
LastBackupTime time.Time `json:"last_backup_time"` LastBackupTime time.Time `json:"last_backup_time"`
LastBackupSize int64 `json:"last_backup_size"` LastBackupSize int64 `json:"last_backup_size"`
LastBackupResult string `json:"last_backup_result"` LastBackupResult string `json:"last_backup_result"`
LastBackupDuration time.Duration `json:"last_backup_duration"`
} }
type InstanceSnapshot struct { type InstanceSnapshot struct {
@@ -74,6 +76,7 @@ func (s *InstanceStatus) Snapshot() InstanceSnapshot {
LastBackupTime: db.LastBackupTime, LastBackupTime: db.LastBackupTime,
LastBackupSize: db.LastBackupSize, LastBackupSize: db.LastBackupSize,
LastBackupResult: db.LastBackupResult, LastBackupResult: db.LastBackupResult,
LastBackupDuration: db.LastBackupDuration,
}) })
} }
@@ -294,6 +297,7 @@ func (m *Manager) RunInstance(name string) {
inst.mu.Lock() inst.mu.Lock()
dbStatus := inst.Databases[db] dbStatus := inst.Databases[db]
dbStatus.LastBackupTime = time.Now() dbStatus.LastBackupTime = time.Now()
dbStatus.LastBackupDuration = duration
if err != nil { if err != nil {
logger.Error(name, "Failed backup for database %s: %v", db, err) logger.Error(name, "Failed backup for database %s: %v", db, err)
dbStatus.LastBackupResult = "failed" dbStatus.LastBackupResult = "failed"
@@ -330,14 +334,18 @@ func (m *Manager) RunInstance(name string) {
OverallResult: inst.OverallResult, OverallResult: inst.OverallResult,
Time: time.Now(), Time: time.Now(),
} }
var totalDuration time.Duration
for _, db := range dbs { for _, db := range dbs {
dbStat := inst.Databases[db] dbStat := inst.Databases[db]
payload.Databases = append(payload.Databases, notify.DBResult{ payload.Databases = append(payload.Databases, notify.DBResult{
Name: dbStat.Name, Name: dbStat.Name,
Size: dbStat.LastBackupSize, Size: dbStat.LastBackupSize,
Result: dbStat.LastBackupResult, Result: dbStat.LastBackupResult,
Duration: dbStat.LastBackupDuration,
}) })
totalDuration += dbStat.LastBackupDuration
} }
payload.TotalDuration = totalDuration
inst.mu.Unlock() inst.mu.Unlock()
logger.Info(name, "Backup job completed. Result: %s", inst.OverallResult) logger.Info(name, "Backup job completed. Result: %s", inst.OverallResult)
+16 -2
View File
@@ -17,12 +17,14 @@ type DBResult struct {
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"` Size int64 `json:"size"`
Result string `json:"result"` Result string `json:"result"`
Duration time.Duration `json:"duration"`
} }
type Payload struct { type Payload struct {
InstanceName string `json:"instance_name"` InstanceName string `json:"instance_name"`
OverallResult string `json:"overall_result"` OverallResult string `json:"overall_result"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
TotalDuration time.Duration `json:"total_duration"`
Databases []DBResult `json:"databases"` Databases []DBResult `json:"databases"`
} }
@@ -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", 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")) payload.InstanceName, payload.OverallResult, payload.Time.Format("2006-01-02 15:04:05"))
for _, db := range payload.Databases { 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{}{ webhookPayload := map[string]interface{}{
"content": message, "content": message,
@@ -113,6 +116,13 @@ func formatBytes(b int64) string {
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) 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 = ` const emailTmplStr = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -139,6 +149,7 @@ const emailTmplStr = `
<tr> <tr>
<th>Database</th> <th>Database</th>
<th>Size</th> <th>Size</th>
<th>Duration</th>
<th>Result</th> <th>Result</th>
</tr> </tr>
</thead> </thead>
@@ -147,18 +158,21 @@ const emailTmplStr = `
<tr> <tr>
<td>{{.Name}}</td> <td>{{.Name}}</td>
<td>{{formatBytes .Size}}</td> <td>{{formatBytes .Size}}</td>
<td>{{formatDuration .Duration}}</td>
<td class="{{if eq .Result "success"}}accent{{else}}error{{end}}">{{.Result}}</td> <td class="{{if eq .Result "success"}}accent{{else}}error{{end}}">{{.Result}}</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>
</table> </table>
<p style="margin-top: 20px;">Total Duration: <strong>{{formatDuration .TotalDuration}}</strong></p>
</div> </div>
</body> </body>
</html> </html>
` `
func sendEmail(cfg config.EmailConfig, payload Payload) { 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 { if err != nil {
logger.Error(payload.InstanceName, "Failed to parse email template: %v", err) logger.Error(payload.InstanceName, "Failed to parse email template: %v", err)
return return