-
Notifications
You must be signed in to change notification settings - Fork 158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Parse kopia snapshot restore
progress output.
#2776
Changes from 6 commits
f7fad5f
7e4a04c
466fc65
f932ade
fc19368
cfefd78
222b34b
2547a8d
92c4a3a
a9c5dfb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -39,6 +39,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>[^\)]+)\%\).+)` | ||||||||||||||
snapshotRestoreOutputRegEx = `Processed (?P<processedCount>\d+) \((?P<processedSize>.*)\) of (?P<totalCount>\d+) \((?P<totalSize>.*)\) (?P<dataRate>.*) \((?P<percentage>.*)%\) remaining (?P<remainingTime>.*)\.` | ||||||||||||||
extractSnapshotIDRegEx = `Created snapshot with root ([^\s]+) and ID ([^\s]+).*$` | ||||||||||||||
repoTotalSizeFromBlobStatsRegEx = `Total: (\d+)$` | ||||||||||||||
repoCountFromBlobStatsRegEx = `Count: (\d+)$` | ||||||||||||||
|
@@ -205,7 +206,10 @@ type SnapshotCreateStats struct { | |||||||||||||
ProgressPercent int64 | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
var kopiaProgressPattern = regexp.MustCompile(snapshotCreateOutputRegEx) //nolint:lll | ||||||||||||||
var ( | ||||||||||||||
kopiaProgressPattern = regexp.MustCompile(snapshotCreateOutputRegEx) | ||||||||||||||
kopiaRestorePattern = regexp.MustCompile(snapshotRestoreOutputRegEx) | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
// SnapshotStatsFromSnapshotCreate parses the output of a kopia snapshot | ||||||||||||||
// create execution for a log of the stats for that execution. | ||||||||||||||
|
@@ -327,6 +331,96 @@ func parseKopiaProgressLine(line string, matchOnlyFinished bool) (stats *Snapsho | |||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// RestoreStats is a container for stats parsed from the output of a | ||||||||||||||
// `kopia restore` command. | ||||||||||||||
type RestoreStats struct { | ||||||||||||||
FilesProcessed int64 | ||||||||||||||
SizeProcessedB int64 | ||||||||||||||
FilesTotal int64 | ||||||||||||||
SizeTotalB int64 | ||||||||||||||
ProgressPercent int64 | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// RestoreStatsFromRestoreOutput parses the output of a `kopia restore` | ||||||||||||||
// execution for a log of the stats for that execution. | ||||||||||||||
func RestoreStatsFromRestoreOutput(restoreStderrOutput string) (stats *RestoreStats) { | ||||||||||||||
if restoreStderrOutput == "" { | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
logs := regexp.MustCompile("[\r\n]").Split(restoreStderrOutput, -1) | ||||||||||||||
|
||||||||||||||
for _, l := range logs { | ||||||||||||||
lineStats := parseKopiaSnapshotRestoreProgressLine(l) | ||||||||||||||
if lineStats != nil { | ||||||||||||||
stats = lineStats | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return stats | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// parseKopiaSnapshotRestoreProgressLine parses restore stats from line | ||||||||||||||
// which expected to be in the following format: | ||||||||||||||
// Processed 5 (1.4 GB) of 5 (1.8 GB) 291.1 MB/s (75.2%) remaining 1s. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accepted, but due to renamed function, in a separate commit |
||||||||||||||
func parseKopiaSnapshotRestoreProgressLine(line string) (stats *RestoreStats) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is snapshotrestore required here? or just kopiarestore would be enough? |
||||||||||||||
match := kopiaRestorePattern.FindStringSubmatch(line) | ||||||||||||||
if len(match) < 8 { | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
groups := make(map[string]string) | ||||||||||||||
for i, name := range kopiaRestorePattern.SubexpNames() { | ||||||||||||||
if i != 0 && name != "" { | ||||||||||||||
groups[name] = match[i] | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
processedCount, err := strconv.Atoi(groups["processedCount"]) | ||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).Print("Skipping entry due to inability to parse number of processed files", field.M{"processedCount": groups["processedCount"]}) | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
processedSize, err := humanize.ParseBytes(groups["processedSize"]) | ||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).Print("Skipping entry due to inability to parse amount of processed bytes", field.M{"processedSize": groups["processedSize"]}) | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
totalCount, err := strconv.Atoi(groups["totalCount"]) | ||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).Print("Skipping entry due to inability to parse expected number of files", field.M{"totalCount": groups["totalCount"]}) | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
totalSize, err := humanize.ParseBytes(groups["totalSize"]) | ||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).Print("Skipping entry due to inability to parse expected amount of bytes", field.M{"totalSize": groups["totalSize"]}) | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
progressPercent, err := strconv.ParseFloat(groups["percentage"], 64) | ||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).Print("Skipping entry due to inability to parse progress percent string", field.M{"progressPercent": groups["progressPercent"]}) | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if progressPercent >= 100 { | ||||||||||||||
// It may happen that kopia reports progress of 100 or higher without actual | ||||||||||||||
e-sumin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
// completing the task. This can occur due to inaccurate estimation. | ||||||||||||||
// In such case, we will return the progress as 99% to avoid confusion. | ||||||||||||||
e-sumin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
progressPercent = 99 | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return &RestoreStats{ | ||||||||||||||
FilesProcessed: int64(processedCount), | ||||||||||||||
SizeProcessedB: int64(processedSize), | ||||||||||||||
FilesTotal: int64(totalCount), | ||||||||||||||
SizeTotalB: int64(totalSize), | ||||||||||||||
ProgressPercent: int64(progressPercent), | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// 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. | ||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've rephrased it according to my previous suggestion: #2776 (comment)