Files
kopia/fs/loggingfs/loggingfs.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

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{}
)