Skip to content

Commit

Permalink
Kopia restore result parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
e-sumin committed Aug 2, 2024
1 parent a121b50 commit 631ae77
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 2 deletions.
64 changes: 62 additions & 2 deletions pkg/kopia/command/parse_command_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
//nolint:lll
snapshotCreateOutputRegEx = `(?P<spinner>[|/\-\\\*]).+[^\d](?P<numHashed>\d+) hashed \((?P<hashedSize>[^\)]+)\), (?P<numCached>\d+) cached \((?P<cachedSize>[^\)]+)\), uploaded (?P<uploadedSize>[^\)]+), (?:estimating...|estimated (?P<estimatedSize>[^\)]+) \((?P<estimatedProgress>[^\)]+)\%\).+)`
restoreOutputRegEx = `Processed (?P<processedCount>\d+) \((?P<processedSize>.*)\) of (?P<totalCount>\d+) \((?P<totalSize>.*)\) (?P<dataRate>.*) \((?P<percentage>.*)%\) remaining (?P<remainingTime>.*)\.`
restoreResultOutputRegEx = `Restored (?P<processedFilesCount>\d+) files, (?P<processedDirCount>\d+) directories and (?P<processedSymLinksCount>\d+) symbolic links \((?P<restoredSize>)[^\)]+\).`
extractSnapshotIDRegEx = `Created snapshot with root ([^\s]+) and ID ([^\s]+).*$`
repoTotalSizeFromBlobStatsRegEx = `Total: (\d+)$`
repoCountFromBlobStatsRegEx = `Count: (\d+)$`
Expand Down Expand Up @@ -207,8 +208,9 @@ type SnapshotCreateStats struct {
}

var (
kopiaProgressPattern = regexp.MustCompile(snapshotCreateOutputRegEx)
kopiaRestorePattern = regexp.MustCompile(restoreOutputRegEx)
kopiaProgressPattern = regexp.MustCompile(snapshotCreateOutputRegEx)
kopiaRestorePattern = regexp.MustCompile(restoreOutputRegEx)
kopiaRestoreResultPattern = regexp.MustCompile(restoreResultOutputRegEx)
)

// SnapshotStatsFromSnapshotCreate parses the output of a `kopia snapshot
Expand Down Expand Up @@ -430,6 +432,64 @@ func parseKopiaRestoreProgressLine(line string) (stats *RestoreStats) {
}
}

// RestoreResultFromRestoreOutput parses the output of a `kopia restore`
// line-by-line in search of restore result statistics.
// It returns nil if no final statistics are found.
func RestoreResultFromRestoreOutput(
restoreStderrOutput string,
) (stats *RestoreStats) {
if restoreStderrOutput == "" {
return nil
}
logs := regexp.MustCompile("[\r\n]").Split(restoreStderrOutput, -1)

for _, l := range logs {
lineStats := parseKopiaRestoreResultLine(l)
if lineStats != nil {
stats = lineStats
}
}

return stats
}

// parseKopiaRestoreResultLine parses final restore stats from the output log line,
// which is expected to be in the following format:
// Restored 1 files, 1 directories and 0 symbolic links (1.1 GB).
func parseKopiaRestoreResultLine(line string) (stats *RestoreStats) {
match := kopiaRestoreResultPattern.FindStringSubmatch(line)
if len(match) < 4 {
return nil
}

groups := make(map[string]string)
for i, name := range kopiaRestorePattern.SubexpNames() {
if i != 0 && name != "" {
groups[name] = match[i]
}
}

processedFilesCount, err := strconv.Atoi(groups["processedFilesCount"])
if err != nil {
log.WithError(err).Print("Skipping entry due to inability to parse number of processed files", field.M{"processedFilesCount": groups["processedFilesCount"]})
return nil
}

restoredSize, err := humanize.ParseBytes(groups["restoredSize"])
if err != nil {
log.WithError(err).Print("Skipping entry due to inability to parse amount of restored bytes", field.M{"restoredSize": groups["restoredSize"]})
return nil
}

return &RestoreStats{
FilesProcessed: int64(processedFilesCount),
SizeProcessedB: int64(restoredSize),
FilesTotal: int64(processedFilesCount),
SizeTotalB: int64(restoredSize),
ProgressPercent: int64(100),
}
}

// RepoSizeStatsFromBlobStatsRaw takes a string as input, interprets it as a kopia blob stats
// output in an expected format (Contains the line "Total: <size>"), and returns the integer
// size in bytes or an error if parsing is unsuccessful.
Expand Down
43 changes: 43 additions & 0 deletions pkg/kopia/command/parse_command_output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,49 @@ func (kParse *KopiaParseUtilsTestSuite) TestRestoreStatsFromRestoreOutput(c *C)
}
}

func (kParse *KopiaParseUtilsTestSuite) TestRestoreResultFromRestoreOutput(c *C) {
type args struct {
restoreOutput string
}
tests := []struct {
name string
args args
wantStats *RestoreStats
}{
{
name: "Basic test case",
args: args{
restoreOutput: "Processed 2 (397.5 MB) of 3 (3.1 GB) 14.9 MB/s (12.6%) remaining 3m3s.\r\nRestored 1 files, 1 directories and 0 symbolic links (1.1 GB).",
},
wantStats: &RestoreStats{
FilesProcessed: 1,
SizeProcessedB: 1,
FilesTotal: 1,
SizeTotalB: 1,
ProgressPercent: 100,
},
},
{
name: "Not finished test case",
args: args{
restoreOutput: "Processed 2 (13.7 MB) of 2 (3.1 GB) 8.5 MB/s (0.4%) remaining 6m10s.",
},
wantStats: nil,
},
{
name: "Ignore incomplete stats without during estimation",
args: args{
restoreOutput: "Processed 2 (32.8 KB) of 2 (3.1 GB).",
},
wantStats: nil,
},
}
for _, tt := range tests {
stats := RestoreResultFromRestoreOutput(tt.args.restoreOutput)
c.Check(stats, DeepEquals, tt.wantStats, Commentf("Failed for %s", tt.name))
}
}

func (kParse *KopiaParseUtilsTestSuite) TestPhysicalSizeFromBlobStatsRaw(c *C) {
for _, tc := range []struct {
blobStatsOutput string
Expand Down

0 comments on commit 631ae77

Please sign in to comment.