Merge pull request #5119 from MichaelEischer/backup-json-start-end-time

backup: include start and end time in json output
This commit is contained in:
Michael Eischer 2024-11-11 21:50:30 +01:00 committed by GitHub
commit 9c758313e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 56 additions and 34 deletions

View file

@ -0,0 +1,6 @@
Enhancement: Include backup start and end in JSON output
The JSON output of the backup command now also includes the timestamps
of the `backup_start` and `backup_end` times.
https://github.com/restic/restic/pull/5119

View file

@ -226,6 +226,10 @@ Summary is the last output line in a successful backup.
+---------------------------+---------------------------------------------------------+ +---------------------------+---------------------------------------------------------+
| ``total_bytes_processed`` | Total number of bytes processed | | ``total_bytes_processed`` | Total number of bytes processed |
+---------------------------+---------------------------------------------------------+ +---------------------------+---------------------------------------------------------+
| ``backup_start`` | Time at which the backup was started |
+---------------------------+---------------------------------------------------------+
| ``backup_end`` | Time at which the backup was completed |
+---------------------------+---------------------------------------------------------+
| ``total_duration`` | Total time it took for the operation to complete | | ``total_duration`` | Total time it took for the operation to complete |
+---------------------------+---------------------------------------------------------+ +---------------------------+---------------------------------------------------------+
| ``snapshot_id`` | ID of the new snapshot. Field is omitted if snapshot | | ``snapshot_id`` | ID of the new snapshot. Field is omitted if snapshot |

View file

@ -49,6 +49,8 @@ type ChangeStats struct {
} }
type Summary struct { type Summary struct {
BackupStart time.Time
BackupEnd time.Time
Files, Dirs ChangeStats Files, Dirs ChangeStats
ProcessedBytes uint64 ProcessedBytes uint64
ItemStats ItemStats
@ -811,7 +813,9 @@ func (arch *Archiver) stopWorkers() {
// Snapshot saves several targets and returns a snapshot. // Snapshot saves several targets and returns a snapshot.
func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts SnapshotOptions) (*restic.Snapshot, restic.ID, *Summary, error) { func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts SnapshotOptions) (*restic.Snapshot, restic.ID, *Summary, error) {
arch.summary = &Summary{} arch.summary = &Summary{
BackupStart: opts.BackupStart,
}
cleanTargets, err := resolveRelativeTargets(arch.FS, targets) cleanTargets, err := resolveRelativeTargets(arch.FS, targets)
if err != nil { if err != nil {
@ -894,9 +898,10 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
sn.Parent = opts.ParentSnapshot.ID() sn.Parent = opts.ParentSnapshot.ID()
} }
sn.Tree = &rootTreeID sn.Tree = &rootTreeID
arch.summary.BackupEnd = time.Now()
sn.Summary = &restic.SnapshotSummary{ sn.Summary = &restic.SnapshotSummary{
BackupStart: opts.BackupStart, BackupStart: arch.summary.BackupStart,
BackupEnd: time.Now(), BackupEnd: arch.summary.BackupEnd,
FilesNew: arch.summary.Files.New, FilesNew: arch.summary.Files.New,
FilesChanged: arch.summary.Files.Changed, FilesChanged: arch.summary.Files.Changed,

View file

@ -1692,14 +1692,17 @@ func (f MockFile) Read(p []byte) (int, error) {
} }
func checkSnapshotStats(t *testing.T, sn *restic.Snapshot, stat Summary) { func checkSnapshotStats(t *testing.T, sn *restic.Snapshot, stat Summary) {
rtest.Equals(t, stat.Files.New, sn.Summary.FilesNew) t.Helper()
rtest.Equals(t, stat.Files.Changed, sn.Summary.FilesChanged) rtest.Equals(t, stat.BackupStart, sn.Summary.BackupStart, "BackupStart")
rtest.Equals(t, stat.Files.Unchanged, sn.Summary.FilesUnmodified) // BackupEnd is set to time.Now() and can't be compared to a fixed value
rtest.Equals(t, stat.Dirs.New, sn.Summary.DirsNew) rtest.Equals(t, stat.Files.New, sn.Summary.FilesNew, "FilesNew")
rtest.Equals(t, stat.Dirs.Changed, sn.Summary.DirsChanged) rtest.Equals(t, stat.Files.Changed, sn.Summary.FilesChanged, "FilesChanged")
rtest.Equals(t, stat.Dirs.Unchanged, sn.Summary.DirsUnmodified) rtest.Equals(t, stat.Files.Unchanged, sn.Summary.FilesUnmodified, "FilesUnmodified")
rtest.Equals(t, stat.ProcessedBytes, sn.Summary.TotalBytesProcessed) rtest.Equals(t, stat.Dirs.New, sn.Summary.DirsNew, "DirsNew")
rtest.Equals(t, stat.Files.New+stat.Files.Changed+stat.Files.Unchanged, sn.Summary.TotalFilesProcessed) rtest.Equals(t, stat.Dirs.Changed, sn.Summary.DirsChanged, "DirsChanged")
rtest.Equals(t, stat.Dirs.Unchanged, sn.Summary.DirsUnmodified, "DirsUnmodified")
rtest.Equals(t, stat.ProcessedBytes, sn.Summary.TotalBytesProcessed, "TotalBytesProcessed")
rtest.Equals(t, stat.Files.New+stat.Files.Changed+stat.Files.Unchanged, sn.Summary.TotalFilesProcessed, "TotalFilesProcessed")
bothZeroOrNeither(t, uint64(stat.DataBlobs), uint64(sn.Summary.DataBlobs)) bothZeroOrNeither(t, uint64(stat.DataBlobs), uint64(sn.Summary.DataBlobs))
bothZeroOrNeither(t, uint64(stat.TreeBlobs), uint64(sn.Summary.TreeBlobs)) bothZeroOrNeither(t, uint64(stat.TreeBlobs), uint64(sn.Summary.TreeBlobs))
bothZeroOrNeither(t, uint64(stat.DataSize+stat.TreeSize), uint64(sn.Summary.DataAdded)) bothZeroOrNeither(t, uint64(stat.DataSize+stat.TreeSize), uint64(sn.Summary.DataAdded))

View file

@ -162,7 +162,7 @@ func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
} }
// Finish prints the finishing messages. // Finish prints the finishing messages.
func (b *JSONProgress) Finish(snapshotID restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) { func (b *JSONProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool) {
id := "" id := ""
// empty if snapshot creation was skipped // empty if snapshot creation was skipped
if !snapshotID.IsNull() { if !snapshotID.IsNull() {
@ -182,7 +182,9 @@ func (b *JSONProgress) Finish(snapshotID restic.ID, start time.Time, summary *ar
DataAddedPacked: summary.ItemStats.DataSizeInRepo + summary.ItemStats.TreeSizeInRepo, DataAddedPacked: summary.ItemStats.DataSizeInRepo + summary.ItemStats.TreeSizeInRepo,
TotalFilesProcessed: summary.Files.New + summary.Files.Changed + summary.Files.Unchanged, TotalFilesProcessed: summary.Files.New + summary.Files.Changed + summary.Files.Unchanged,
TotalBytesProcessed: summary.ProcessedBytes, TotalBytesProcessed: summary.ProcessedBytes,
TotalDuration: time.Since(start).Seconds(), BackupStart: summary.BackupStart,
BackupEnd: summary.BackupEnd,
TotalDuration: summary.BackupEnd.Sub(summary.BackupStart).Seconds(),
SnapshotID: id, SnapshotID: id,
DryRun: dryRun, DryRun: dryRun,
}) })
@ -243,6 +245,8 @@ type summaryOutput struct {
TotalFilesProcessed uint `json:"total_files_processed"` TotalFilesProcessed uint `json:"total_files_processed"`
TotalBytesProcessed uint64 `json:"total_bytes_processed"` TotalBytesProcessed uint64 `json:"total_bytes_processed"`
TotalDuration float64 `json:"total_duration"` // in seconds TotalDuration float64 `json:"total_duration"` // in seconds
BackupStart time.Time `json:"backup_start"`
BackupEnd time.Time `json:"backup_end"`
SnapshotID string `json:"snapshot_id,omitempty"` SnapshotID string `json:"snapshot_id,omitempty"`
DryRun bool `json:"dry_run,omitempty"` DryRun bool `json:"dry_run,omitempty"`
} }

View file

@ -17,7 +17,7 @@ type ProgressPrinter interface {
ScannerError(item string, err error) error ScannerError(item string, err error) error
CompleteItem(messageType string, item string, s archiver.ItemStats, d time.Duration) CompleteItem(messageType string, item string, s archiver.ItemStats, d time.Duration)
ReportTotal(start time.Time, s archiver.ScanStats) ReportTotal(start time.Time, s archiver.ScanStats)
Finish(snapshotID restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool)
Reset() Reset()
P(msg string, args ...interface{}) P(msg string, args ...interface{})
@ -173,5 +173,5 @@ func (p *Progress) ReportTotal(item string, s archiver.ScanStats) {
func (p *Progress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryrun bool) { func (p *Progress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryrun bool) {
// wait for the status update goroutine to shut down // wait for the status update goroutine to shut down
p.Updater.Done() p.Updater.Done()
p.printer.Finish(snapshotID, p.start, summary, dryrun) p.printer.Finish(snapshotID, summary, dryrun)
} }

View file

@ -33,7 +33,7 @@ func (p *mockPrinter) CompleteItem(messageType string, _ string, _ archiver.Item
} }
func (p *mockPrinter) ReportTotal(_ time.Time, _ archiver.ScanStats) {} func (p *mockPrinter) ReportTotal(_ time.Time, _ archiver.ScanStats) {}
func (p *mockPrinter) Finish(id restic.ID, _ time.Time, _ *archiver.Summary, _ bool) { func (p *mockPrinter) Finish(id restic.ID, _ *archiver.Summary, _ bool) {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()

View file

@ -130,7 +130,7 @@ func (b *TextProgress) Reset() {
} }
// Finish prints the finishing messages. // Finish prints the finishing messages.
func (b *TextProgress) Finish(id restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) { func (b *TextProgress) Finish(id restic.ID, summary *archiver.Summary, dryRun bool) {
b.P("\n") b.P("\n")
b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged) b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged)
b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged) b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged)
@ -147,7 +147,7 @@ func (b *TextProgress) Finish(id restic.ID, start time.Time, summary *archiver.S
b.P("processed %v files, %v in %s", b.P("processed %v files, %v in %s",
summary.Files.New+summary.Files.Changed+summary.Files.Unchanged, summary.Files.New+summary.Files.Changed+summary.Files.Unchanged,
ui.FormatBytes(summary.ProcessedBytes), ui.FormatBytes(summary.ProcessedBytes),
ui.FormatDuration(time.Since(start)), ui.FormatDuration(summary.BackupEnd.Sub(summary.BackupStart)),
) )
if !dryRun { if !dryRun {