Files
kopia/cli/command_logs_session.go
Jarek Kowalski 0d0f48a7ee clock: discard monotonic clock component in clock.Now() (#1437)
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>
2021-10-22 15:35:09 -07:00

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
}