diff --git a/changelog/unreleased/issue-3129 b/changelog/unreleased/issue-3129 new file mode 100644 index 000000000..3340dca0e --- /dev/null +++ b/changelog/unreleased/issue-3129 @@ -0,0 +1,7 @@ +Enhancement: Add JSON support to prune + +Restic `prune` now also supports the `--json` option and gives all +statistics in JSON format. + +https://github.com/restic/restic/issues/3129 +https://github.com/restic/restic/pull/5239 diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index a613f2255..471fc9ecb 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -178,7 +178,12 @@ func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts GlobalOption Print("warning: running prune without a cache, this may be very slow!\n") } - printer := newTerminalProgressPrinter(gopts.verbosity, term) + var printer progress.Printer + if !gopts.JSON { + printer = newTerminalProgressPrinter(gopts.verbosity, term) + } else { + printer = &progress.NoopPrinter{} + } printer.P("loading indexes...\n") // loading the index before the snapshots is ok, as we use an exclusive lock here @@ -214,9 +219,13 @@ func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts GlobalOption printer.P("\nWould have made the following changes:") } - err = printPruneStats(printer, plan.Stats()) - if err != nil { - return err + if !gopts.JSON { + err = printPruneStats(printer, plan.Stats()) + if err != nil { + return err + } + } else { + term.Print(ui.ToJSONString(plan.Stats())) } // Trigger GC to reset garbage collection threshold @@ -235,24 +244,19 @@ func printPruneStats(printer progress.Printer, stats repository.PruneStats) erro if stats.Size.Unref > 0 { printer.V("unreferenced: %s\n", ui.FormatBytes(stats.Size.Unref)) } - totalBlobs := stats.Blobs.Used + stats.Blobs.Unused + stats.Blobs.Duplicate - totalSize := stats.Size.Used + stats.Size.Duplicate + stats.Size.Unused + stats.Size.Unref - unusedSize := stats.Size.Duplicate + stats.Size.Unused - printer.V("total: %10d blobs / %s\n", totalBlobs, ui.FormatBytes(totalSize)) - printer.V("unused size: %s of total size\n", ui.FormatPercent(unusedSize, totalSize)) + printer.V("total: %10d blobs / %s\n", stats.Blobs.Total, ui.FormatBytes(stats.Size.Total)) + printer.V("unused size: %s of total size\n", ui.FormatPercent(stats.Size.Duplicate+stats.Size.Unused, stats.Size.Total)) printer.P("\nto repack: %10d blobs / %s\n", stats.Blobs.Repack, ui.FormatBytes(stats.Size.Repack)) printer.P("this removes: %10d blobs / %s\n", stats.Blobs.Repackrm, ui.FormatBytes(stats.Size.Repackrm)) printer.P("to delete: %10d blobs / %s\n", stats.Blobs.Remove, ui.FormatBytes(stats.Size.Remove+stats.Size.Unref)) - totalPruneSize := stats.Size.Remove + stats.Size.Repackrm + stats.Size.Unref - printer.P("total prune: %10d blobs / %s\n", stats.Blobs.Remove+stats.Blobs.Repackrm, ui.FormatBytes(totalPruneSize)) + printer.P("total prune: %10d blobs / %s\n", stats.Blobs.RemoveTotal, ui.FormatBytes(stats.Size.RemoveTotal)) if stats.Size.Uncompressed > 0 { printer.P("not yet compressed: %s\n", ui.FormatBytes(stats.Size.Uncompressed)) } - printer.P("remaining: %10d blobs / %s\n", totalBlobs-(stats.Blobs.Remove+stats.Blobs.Repackrm), ui.FormatBytes(totalSize-totalPruneSize)) - unusedAfter := unusedSize - stats.Size.Remove - stats.Size.Repackrm + printer.P("remaining: %10d blobs / %s\n", stats.Blobs.Remain, ui.FormatBytes(stats.Size.Remain)) printer.P("unused size after prune: %s (%s of remaining size)\n", - ui.FormatBytes(unusedAfter), ui.FormatPercent(unusedAfter, totalSize-totalPruneSize)) + ui.FormatBytes(stats.Size.RemainUnused), ui.FormatPercent(stats.Size.RemainUnused, stats.Size.Remain)) printer.P("\n") printer.V("totally used packs: %10d\n", stats.Packs.Used) printer.V("partly used packs: %10d\n", stats.Packs.PartlyUsed) diff --git a/doc/060_forget.rst b/doc/060_forget.rst index b211148cb..8758bf7aa 100644 --- a/doc/060_forget.rst +++ b/doc/060_forget.rst @@ -467,6 +467,8 @@ The ``prune`` command accepts the following options: - ``--verbose`` increased verbosity shows additional statistics for ``prune``. +- ``--json`` gives the statistics in JSON format. + Recovering from "no free space" errors ************************************** diff --git a/internal/repository/prune.go b/internal/repository/prune.go index ba13ba1a3..9ca188f73 100644 --- a/internal/repository/prune.go +++ b/internal/repository/prune.go @@ -32,32 +32,42 @@ type PruneOptions struct { type PruneStats struct { Blobs struct { - Used uint - Duplicate uint - Unused uint - Remove uint - Repack uint - Repackrm uint - } + Used uint `json:"used"` + Duplicate uint `json:"duplicate"` + Unused uint `json:"unused"` + Total uint `json:"total"` + Repack uint `json:"repack"` + Repackrm uint `json:"repack_remove"` + Remove uint `json:"remove"` + RemoveTotal uint `json:"remove_total"` + Remain uint `json:"remaining"` + RemainUnused uint `json:"remaining_unused"` + } `json:"blobs"` Size struct { - Used uint64 - Duplicate uint64 - Unused uint64 - Remove uint64 - Repack uint64 - Repackrm uint64 - Unref uint64 - Uncompressed uint64 - } + Used uint64 `json:"used"` + Duplicate uint64 `json:"duplicate"` + Unused uint64 `json:"unused"` + Unref uint64 `json:"unreferenced"` + Uncompressed uint64 `json:"uncompressed"` + Total uint64 `json:"total"` + Repack uint64 `json:"repack"` + Repackrm uint64 `json:"repack_remove"` + Remove uint64 `json:"remove"` + RemoveTotal uint64 `json:"remove_total"` + Remain uint64 `json:"remaining"` + RemainUnused uint64 `json:"remaining_unused"` + } `json:"bytes"` Packs struct { - Used uint - Unused uint - PartlyUsed uint - Unref uint - Keep uint - Repack uint - Remove uint - } + Used uint `json:"used"` + Unused uint `json:"unused"` + PartlyUsed uint `json:"partly_used"` + Unref uint `json:"unreferenced"` + Total uint `json:"total"` + Keep uint `json:"keep"` + Repack uint `json:"repack"` + Remove uint `json:"remove"` + RemoveTotal uint `json:"remove_total"` + } `json:"packfiles"` } type PrunePlan struct { @@ -141,6 +151,17 @@ func PlanPrune(ctx context.Context, opts PruneOptions, repo *Repository, getUsed } plan.keepBlobs = keepBlobs + // calculate totals for statistics + stats.Blobs.Total = stats.Blobs.Used + stats.Blobs.Unused + stats.Blobs.Duplicate + stats.Blobs.RemoveTotal = stats.Blobs.Remove + stats.Blobs.Repackrm + stats.Blobs.Remain = stats.Blobs.Total - stats.Blobs.RemoveTotal + stats.Size.Total = stats.Size.Used + stats.Size.Duplicate + stats.Size.Unused + stats.Size.Unref + stats.Size.RemoveTotal = stats.Size.Remove + stats.Size.Repackrm + stats.Size.Unref + stats.Size.Remain = stats.Size.Total - stats.Size.RemoveTotal + stats.Size.RemainUnused = stats.Size.Duplicate + stats.Size.Unused - stats.Size.Remove - stats.Size.Repackrm + stats.Packs.Total = stats.Packs.Used + stats.Packs.PartlyUsed + stats.Packs.Unused + stats.Packs.Unref + stats.Packs.RemoveTotal = stats.Packs.Unref + stats.Packs.Remove + plan.repo = repo plan.stats = stats plan.opts = opts