libgocryptfs/internal/fusefrontend/dircache.go
Jakob Unterwurzacher f6088e5008 dircache: improve debug messages
Before:

Lookup "errno.html/1/2/3/4/5": miss
Store: "errno.html/1/2/3/4/5" fd=26 iv=21be6e083d60dcabfe7368264d5082b7
Lookup "errno.html": hit 25 6d68a16d217978915036a3bd55428ae7
Lookup "errno.html/1": hit 25 932a464c299b3430c5e55c924f98ac4d
Lookup "errno.html/1/2": hit 25 7d53348b1692d537f017bf86b3cf5feb
Lookup "errno.html/1/2/3": hit 25 2aef1c9d1ab2b55b163215053fefe703
Lookup "errno.html/1/2/3/4": hit 25 cb802be53721c46a46247c5e4e0f4ce6
Lookup "errno.html/1/2/3/4": hit 25 cb802be53721c46a46247c5e4e0f4ce6
Lookup "errno.html": hit 25 6d68a16d217978915036a3bd55428ae7

After:

Lookup "earlyoom/.git/refs"                     hit fd=10 dup=17 iv=6ae2cecd269a25e8d946aff6afe9b8b8
Lookup "earlyoom/.git/refs/remotes"             hit fd=19 dup=17 iv=f04c2d2a5bcc33ebdeaca664859c980d
Lookup "earlyoom/.git/refs/remotes/origin"      miss
Store  "earlyoom/.git/refs/remotes/origin"      fd=17 iv=834a64a1697c9f5705455ba6dbed22b5
Lookup "earlyoom"                               hit fd=7 dup=25 iv=2303a892d6e2357c483574a8070b7679
Lookup "earlyoom/.git"                          hit fd=11 dup=25 iv=d43ca4aff23720c57789c9f62f0aee00
Lookup "earlyoom/.git"                          hit fd=11 dup=25 iv=d43ca4aff23720c57789c9f62f0aee00
Lookup "earlyoom/.git/refs"                     hit fd=10 dup=25 iv=6ae2cecd269a25e8d946aff6afe9b8b8
Lookup "earlyoom/.git/refs/heads"               hit fd=13 dup=25 iv=f9245e7c066b9adc768a1a666da9fbc8
2020-05-17 21:26:56 +02:00

176 lines
4.3 KiB
Go

package fusefrontend
import (
"fmt"
"log"
"sync"
"syscall"
"time"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
const (
// Number of entries in the dirCache. Three entries work well for two
// (probably also three) parallel tar extracts (hit rate around 92%).
// Keep in sync with test_helpers.maxCacheFds !
// TODO: How to share this constant without causing an import cycle?
dirCacheSize = 3
// Enable Lookup/Store/Clear debug messages
enableDebugMessages = false
// Enable hit rate statistics printing
enableStats = false
pathFmt = "%-40q"
)
type dirCacheEntryStruct struct {
// relative plaintext path to the directory
dirRelPath string
// fd to the directory (opened with O_PATH!)
fd int
// content of gocryptfs.diriv in this directory
iv []byte
}
func (e *dirCacheEntryStruct) Clear() {
// An earlier clear may have already closed the fd, or the cache
// has never been filled (fd is 0 in that case).
// Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2.
if e.fd > 0 {
err := syscall.Close(e.fd)
if err != nil {
tlog.Warn.Printf("dirCache.Clear: Close failed: %v", err)
}
}
e.fd = -1
e.dirRelPath = ""
e.iv = nil
}
type dirCacheStruct struct {
sync.Mutex
// Cache entries
entries [dirCacheSize]dirCacheEntryStruct
// Where to store the next entry (index into entries)
nextIndex int
// On the first Lookup(), the expire thread is started, and this flag is set
// to true.
expireThreadRunning bool
// Hit rate stats. Evaluated and reset by the expire thread.
lookups uint64
hits uint64
}
// Clear clears the cache contents.
func (d *dirCacheStruct) Clear() {
d.dbg("Clear\n")
d.Lock()
defer d.Unlock()
for i := range d.entries {
d.entries[i].Clear()
}
}
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
// can close their copy at will.
func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
// Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2.
if fd <= 0 || len(iv) != nametransform.DirIVLen {
log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
}
d.Lock()
defer d.Unlock()
e := &d.entries[d.nextIndex]
// Round-robin works well enough
d.nextIndex = (d.nextIndex + 1) % dirCacheSize
// Close the old fd
e.Clear()
fd2, err := syscall.Dup(fd)
if err != nil {
tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err)
return
}
d.dbg("Store "+pathFmt+" fd=%d iv=%x\n", dirRelPath, fd2, iv)
e.fd = fd2
e.dirRelPath = dirRelPath
e.iv = iv
// expireThread is started on the first Lookup()
if !d.expireThreadRunning {
d.expireThreadRunning = true
go d.expireThread()
}
}
// Lookup checks if relPath is in the cache, and returns an (fd, iv) pair.
// It returns (-1, nil) if not found. The fd is internally Dup()ed and the
// caller must close it when done.
func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
d.Lock()
defer d.Unlock()
if enableStats {
d.lookups++
}
var e *dirCacheEntryStruct
for i := range d.entries {
e = &d.entries[i]
if e.fd <= 0 {
// Cache slot is empty
continue
}
if dirRelPath != e.dirRelPath {
// Not the right path
continue
}
var err error
fd, err = syscall.Dup(e.fd)
if err != nil {
tlog.Warn.Printf("dirCache.Lookup: Dup failed: %v", err)
return -1, nil
}
iv = e.iv
break
}
if fd == 0 {
d.dbg("Lookup "+pathFmt+" miss\n", dirRelPath)
return -1, nil
}
if enableStats {
d.hits++
}
if fd <= 0 || len(iv) != nametransform.DirIVLen {
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
}
d.dbg("Lookup "+pathFmt+" hit fd=%d dup=%d iv=%x\n", dirRelPath, e.fd, fd, iv)
return fd, iv
}
// expireThread is started on the first Lookup()
func (d *dirCacheStruct) expireThread() {
for {
time.Sleep(1 * time.Second)
d.Clear()
if enableStats {
d.Lock()
lookups := d.lookups
hits := d.hits
d.lookups = 0
d.hits = 0
d.Unlock()
if lookups > 0 {
fmt.Printf("dirCache: hits=%3d lookups=%3d, rate=%3d%%\n", hits, lookups, (hits*100)/lookups)
}
}
}
}
// dbg prints a debug message. Usually disabled.
func (d *dirCacheStruct) dbg(format string, a ...interface{}) {
if enableDebugMessages {
fmt.Printf(format, a...)
}
}