2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-25 22:27:35 +00:00

backup: include start and end time in json output

The timestamps were already stored in the created snapshot.
This commit is contained in:
Michael Eischer 2024-11-01 15:50:09 +01:00
parent e18a2a0072
commit e65f4e2231
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,
}) })
@ -229,20 +231,22 @@ type verboseUpdate struct {
} }
type summaryOutput struct { type summaryOutput struct {
MessageType string `json:"message_type"` // "summary" MessageType string `json:"message_type"` // "summary"
FilesNew uint `json:"files_new"` FilesNew uint `json:"files_new"`
FilesChanged uint `json:"files_changed"` FilesChanged uint `json:"files_changed"`
FilesUnmodified uint `json:"files_unmodified"` FilesUnmodified uint `json:"files_unmodified"`
DirsNew uint `json:"dirs_new"` DirsNew uint `json:"dirs_new"`
DirsChanged uint `json:"dirs_changed"` DirsChanged uint `json:"dirs_changed"`
DirsUnmodified uint `json:"dirs_unmodified"` DirsUnmodified uint `json:"dirs_unmodified"`
DataBlobs int `json:"data_blobs"` DataBlobs int `json:"data_blobs"`
TreeBlobs int `json:"tree_blobs"` TreeBlobs int `json:"tree_blobs"`
DataAdded uint64 `json:"data_added"` DataAdded uint64 `json:"data_added"`
DataAddedPacked uint64 `json:"data_added_packed"` DataAddedPacked uint64 `json:"data_added_packed"`
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
SnapshotID string `json:"snapshot_id,omitempty"` BackupStart time.Time `json:"backup_start"`
DryRun bool `json:"dry_run,omitempty"` BackupEnd time.Time `json:"backup_end"`
SnapshotID string `json:"snapshot_id,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 {