mirror of
https://github.com/kopia/kopia.git
synced 2026-01-27 15:58:03 -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>
116 lines
2.8 KiB
Go
116 lines
2.8 KiB
Go
// Package loggingfs implements a wrapper that logs all filesystem actions.
|
|
package loggingfs
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/kopia/kopia/fs"
|
|
"github.com/kopia/kopia/internal/timetrack"
|
|
)
|
|
|
|
type loggingOptions struct {
|
|
printf func(fmt string, args ...interface{})
|
|
prefix string
|
|
}
|
|
|
|
type loggingDirectory struct {
|
|
relativePath string
|
|
options *loggingOptions
|
|
fs.Directory
|
|
}
|
|
|
|
func (ld *loggingDirectory) Child(ctx context.Context, name string) (fs.Entry, error) {
|
|
timer := timetrack.StartTimer()
|
|
entry, err := ld.Directory.Child(ctx, name)
|
|
dt := timer.Elapsed()
|
|
ld.options.printf(ld.options.prefix+"Child(%v) took %v and returned %v", ld.relativePath, dt, err)
|
|
|
|
if err != nil {
|
|
// nolint:wrapcheck
|
|
return nil, err
|
|
}
|
|
|
|
return wrapWithOptions(entry, ld.options, ld.relativePath+"/"+entry.Name()), nil
|
|
}
|
|
|
|
func (ld *loggingDirectory) Readdir(ctx context.Context) (fs.Entries, error) {
|
|
timer := timetrack.StartTimer()
|
|
entries, err := ld.Directory.Readdir(ctx)
|
|
dt := timer.Elapsed()
|
|
ld.options.printf(ld.options.prefix+"Readdir(%v) took %v and returned %v items", ld.relativePath, dt, len(entries))
|
|
|
|
loggingEntries := make(fs.Entries, len(entries))
|
|
for i, entry := range entries {
|
|
loggingEntries[i] = wrapWithOptions(entry, ld.options, ld.relativePath+"/"+entry.Name())
|
|
}
|
|
|
|
// nolint:wrapcheck
|
|
return loggingEntries, err
|
|
}
|
|
|
|
type loggingFile struct {
|
|
options *loggingOptions
|
|
fs.File
|
|
}
|
|
|
|
type loggingSymlink struct {
|
|
options *loggingOptions
|
|
fs.Symlink
|
|
}
|
|
|
|
// Option modifies the behavior of logging wrapper.
|
|
type Option func(o *loggingOptions)
|
|
|
|
// Wrap returns an Entry that wraps another Entry and logs all method calls.
|
|
func Wrap(e fs.Entry, printf func(msg string, args ...interface{}), options ...Option) fs.Entry {
|
|
return wrapWithOptions(e, applyOptions(printf, options), ".")
|
|
}
|
|
|
|
func wrapWithOptions(e fs.Entry, opts *loggingOptions, relativePath string) fs.Entry {
|
|
switch e := e.(type) {
|
|
case fs.Directory:
|
|
return fs.Directory(&loggingDirectory{relativePath, opts, e})
|
|
|
|
case fs.File:
|
|
return fs.File(&loggingFile{opts, e})
|
|
|
|
case fs.Symlink:
|
|
return fs.Symlink(&loggingSymlink{opts, e})
|
|
|
|
default:
|
|
return e
|
|
}
|
|
}
|
|
|
|
func applyOptions(printf func(msg string, args ...interface{}), opts []Option) *loggingOptions {
|
|
o := &loggingOptions{
|
|
printf: printf,
|
|
}
|
|
|
|
for _, f := range opts {
|
|
f(o)
|
|
}
|
|
|
|
return o
|
|
}
|
|
|
|
// Output is an option that causes all output to be sent to a given function instead of log.Printf().
|
|
func Output(outputFunc func(fmt string, args ...interface{})) Option {
|
|
return func(o *loggingOptions) {
|
|
o.printf = outputFunc
|
|
}
|
|
}
|
|
|
|
// Prefix specifies prefix to be prepended to all log output.
|
|
func Prefix(prefix string) Option {
|
|
return func(o *loggingOptions) {
|
|
o.prefix = prefix
|
|
}
|
|
}
|
|
|
|
var (
|
|
_ fs.Directory = &loggingDirectory{}
|
|
_ fs.File = &loggingFile{}
|
|
_ fs.Symlink = &loggingSymlink{}
|
|
)
|