mirror of
https://github.com/kopia/kopia.git
synced 2026-01-23 22:07:54 -05:00
The dual time measurement is described in https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md The fix is to discard hidden monotonic time component of time.Time by converting to unix time and back. Reviewed usage of clock.Now() and replaced with timetrack.StartTimer() when measuring time. The problem in #1402 was that passage of time was measured using the monotonic time and not wall clock time. When the computer goes to sleep, monotonic time is still monotonic while wall clock time makes a leap when the computer wakes up. This is the behavior that epoch manager (and most other compontents in Kopia) rely upon. Fixes #1402 Co-authored-by: Julio Lopez <julio+gh@kasten.io>
152 lines
3.5 KiB
Go
152 lines
3.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alecthomas/kingpin"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/internal/clock"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/content"
|
|
)
|
|
|
|
type logSessionInfo struct {
|
|
id string
|
|
startTime time.Time
|
|
endTime time.Time
|
|
segments []blob.Metadata
|
|
totalSize int64
|
|
}
|
|
|
|
type logSelectionCriteria struct {
|
|
all bool
|
|
latest int
|
|
youngerThan time.Duration
|
|
olderThan time.Duration
|
|
}
|
|
|
|
func (c *logSelectionCriteria) setup(cmd *kingpin.CmdClause) {
|
|
cmd.Flag("all", "Show all logs").BoolVar(&c.all)
|
|
cmd.Flag("latest", "Include last N logs").Short('n').IntVar(&c.latest)
|
|
cmd.Flag("younger-than", "Include logs younger than X (e.g. '1h')").DurationVar(&c.youngerThan)
|
|
cmd.Flag("older-than", "Include logs older than X (e.g. '1h')").DurationVar(&c.olderThan)
|
|
}
|
|
|
|
func (c *logSelectionCriteria) any() bool {
|
|
return c.all || c.latest > 0 || c.youngerThan > 0 || c.olderThan > 0
|
|
}
|
|
|
|
func (c *logSelectionCriteria) filterLogSessions(allSessions []*logSessionInfo) []*logSessionInfo {
|
|
if c.all {
|
|
return allSessions
|
|
}
|
|
|
|
if c.youngerThan > 0 {
|
|
allSessions = filterLogSessions(allSessions, func(ls *logSessionInfo) bool {
|
|
return clock.Now().Sub(ls.startTime) < c.youngerThan
|
|
})
|
|
}
|
|
|
|
if c.olderThan > 0 {
|
|
allSessions = filterLogSessions(allSessions, func(ls *logSessionInfo) bool {
|
|
return clock.Now().Sub(ls.startTime) > c.olderThan
|
|
})
|
|
}
|
|
|
|
if c.latest > 0 && len(allSessions) > c.latest {
|
|
allSessions = allSessions[len(allSessions)-c.latest:]
|
|
}
|
|
|
|
return allSessions
|
|
}
|
|
|
|
func getLogSessions(ctx context.Context, st blob.Reader) ([]*logSessionInfo, error) {
|
|
sessions := map[string]*logSessionInfo{}
|
|
|
|
var allSessions []*logSessionInfo
|
|
|
|
if err := st.ListBlobs(ctx, content.TextLogBlobPrefix, func(bm blob.Metadata) error {
|
|
parts := strings.Split(string(bm.BlobID), "_")
|
|
|
|
// nolint:gomnd
|
|
if len(parts) < 8 {
|
|
log(ctx).Errorf("invalid part count: %v skipping unrecognized log: %v", len(parts), bm.BlobID)
|
|
return nil
|
|
}
|
|
|
|
id := parts[2] + "_" + parts[3]
|
|
|
|
// nolint:gomnd
|
|
startTime, err := strconv.ParseInt(parts[4], 10, 64)
|
|
if err != nil {
|
|
log(ctx).Errorf("invalid start time - skipping unrecognized log: %v", bm.BlobID)
|
|
|
|
// nolint:nilerr
|
|
return nil
|
|
}
|
|
|
|
// nolint:gomnd
|
|
endTime, err := strconv.ParseInt(parts[5], 10, 64)
|
|
if err != nil {
|
|
log(ctx).Errorf("invalid end time - skipping unrecognized log: %v", bm.BlobID)
|
|
|
|
// nolint:nilerr
|
|
return nil
|
|
}
|
|
|
|
s := sessions[id]
|
|
if s == nil {
|
|
s = &logSessionInfo{
|
|
id: id,
|
|
}
|
|
sessions[id] = s
|
|
allSessions = append(allSessions, s)
|
|
}
|
|
|
|
if t := time.Unix(startTime, 0); s.startTime.IsZero() || t.Before(s.startTime) {
|
|
s.startTime = t
|
|
}
|
|
|
|
if t := time.Unix(endTime, 0); t.After(s.endTime) {
|
|
s.endTime = t
|
|
}
|
|
|
|
s.segments = append(s.segments, bm)
|
|
s.totalSize += bm.Length
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "error listing logs")
|
|
}
|
|
|
|
for _, s := range allSessions {
|
|
sort.Slice(s.segments, func(i, j int) bool {
|
|
return s.segments[i].Timestamp.Before(s.segments[j].Timestamp)
|
|
})
|
|
}
|
|
|
|
// sort sessions by start time
|
|
sort.Slice(allSessions, func(i, j int) bool {
|
|
return allSessions[i].segments[0].Timestamp.Before(allSessions[j].segments[0].Timestamp)
|
|
})
|
|
|
|
return allSessions, nil
|
|
}
|
|
|
|
func filterLogSessions(logs []*logSessionInfo, predicate func(l *logSessionInfo) bool) []*logSessionInfo {
|
|
var result []*logSessionInfo
|
|
|
|
for _, l := range logs {
|
|
if predicate(l) {
|
|
result = append(result, l)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|