cache read performance improvements
This commit is contained in:
parent
39c4720e40
commit
f69d3149f3
14 changed files with 140 additions and 71 deletions
4
TODO.txt
4
TODO.txt
|
|
@ -8,9 +8,5 @@
|
||||||
* implement read ahead buffer?
|
* implement read ahead buffer?
|
||||||
|
|
||||||
* add breadcrumb
|
* add breadcrumb
|
||||||
* add copyright footer
|
|
||||||
* add stopped status?
|
|
||||||
* replace stop with preload link
|
|
||||||
* should redirect back to preloads page
|
|
||||||
* config file?
|
* config file?
|
||||||
* whitelist allowed src paths
|
* whitelist allowed src paths
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -51,6 +52,9 @@ func main() {
|
||||||
flag.Int64Var("a, "quota", 1, "max disk usage quota for the dst cache in GiB")
|
flag.Int64Var("a, "quota", 1, "max disk usage quota for the dst cache in GiB")
|
||||||
flag.BoolVar(&block, "block", true, "block until sftp is connected")
|
flag.BoolVar(&block, "block", true, "block until sftp is connected")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
go func() {
|
||||||
|
http.ListenAndServe("127.0.0.1:7777", nil)
|
||||||
|
}()
|
||||||
|
|
||||||
log = klogr.New().WithName("main")
|
log = klogr.New().WithName("main")
|
||||||
log.Info("starting cachefs", "version", version)
|
log.Info("starting cachefs", "version", version)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
type File struct {
|
type File struct {
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
f provider.File
|
f provider.File
|
||||||
|
cache provider.File
|
||||||
md *Metadata
|
md *Metadata
|
||||||
offset int64
|
offset int64
|
||||||
offline bool
|
offline bool
|
||||||
|
|
@ -83,7 +84,7 @@ func (f *File) Preload(ctx context.Context, unlock func()) {
|
||||||
func (f *File) Read(p []byte) (n int, err error) {
|
func (f *File) Read(p []byte) (n int, err error) {
|
||||||
log := f.log
|
log := f.log
|
||||||
if f.hasChunk(len(p)) {
|
if f.hasChunk(len(p)) {
|
||||||
n, err = f.md.ReadAt(p, f.offset)
|
n, err = f.cache.ReadAt(p, f.offset)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -172,8 +173,8 @@ func (f *File) Stat() (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Close() error {
|
func (f *File) Close() error {
|
||||||
if f.offline {
|
if !f.offline && f.cache != nil {
|
||||||
return nil
|
f.cache.Close()
|
||||||
}
|
}
|
||||||
return f.f.Close()
|
return f.f.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
pkg/fs/fs.go
17
pkg/fs/fs.go
|
|
@ -135,6 +135,7 @@ func (fs *FS) OpenDst(name string) (f http.File, err error) {
|
||||||
f = &File{
|
f = &File{
|
||||||
log: log.WithName("file"),
|
log: log.WithName("file"),
|
||||||
f: file,
|
f: file,
|
||||||
|
cache: file,
|
||||||
md: md,
|
md: md,
|
||||||
offline: true,
|
offline: true,
|
||||||
}
|
}
|
||||||
|
|
@ -198,13 +199,25 @@ func (fs *FS) Open(name string) (f http.File, err error) {
|
||||||
f = &Dir{log: log.WithName("dir"), f: file, dc: fs.dc}
|
f = &Dir{log: log.WithName("dir"), f: file, dc: fs.dc}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
md := fs.mh.Metadata(name, fi.Size())
|
||||||
|
|
||||||
|
var cache provider.File
|
||||||
if offline {
|
if offline {
|
||||||
log.V(2).Info("file offline mode")
|
log.V(2).Info("file offline mode")
|
||||||
|
cache = file
|
||||||
|
} else {
|
||||||
|
cache, err = fs.dst.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
if !skipLog(name) {
|
||||||
|
log.Error(err, "error opening cache file")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
md := fs.mh.Metadata(name, fi.Size())
|
|
||||||
f = &File{
|
f = &File{
|
||||||
log: log.WithName("file"),
|
log: log.WithName("file"),
|
||||||
f: file,
|
f: file,
|
||||||
|
cache: cache,
|
||||||
md: md,
|
md: md,
|
||||||
offline: offline,
|
offline: offline,
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +244,7 @@ func (fs *FS) openCacheFile(name string, size int64) (df provider.File, err erro
|
||||||
log.Error(err, "error opening cache file")
|
log.Error(err, "error opening cache file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if truncate {
|
if truncate && size > 0 {
|
||||||
err = df.Truncate(size)
|
err = df.Truncate(size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "error truncating cache file")
|
log.Error(err, "error truncating cache file")
|
||||||
|
|
|
||||||
|
|
@ -389,16 +389,16 @@ func (md *Metadata) AddChunk(off int64, n int) {
|
||||||
md.Chunks.Add(off, n)
|
md.Chunks.Add(off, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) {
|
//func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) {
|
||||||
if md.f == nil {
|
// if md.f == nil {
|
||||||
err := md.openCacheFile()
|
// err := md.openCacheFile()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return 0, err
|
// return 0, err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
atomic.StoreInt64(&md.Atime, now())
|
// atomic.StoreInt64(&md.Atime, now())
|
||||||
return md.f.ReadAt(p, pos)
|
// return md.f.ReadAt(p, pos)
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) {
|
func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) {
|
||||||
if md.f == nil {
|
if md.f == nil {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ func (q *queue) Add(ph *PreloadHandler, name string, prio int) {
|
||||||
for _, p := range *q {
|
for _, p := range *q {
|
||||||
if p.Name == name {
|
if p.Name == name {
|
||||||
p.Prio += prio
|
p.Prio += prio
|
||||||
|
if p.Prio < 0 {
|
||||||
|
p.Prio = 0
|
||||||
|
}
|
||||||
q.Sort()
|
q.Sort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ func (f *file) Seek(offset int64, whence int) (n int64, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.nonce = [NonceSize]byte{}
|
f.nonce = [NonceSize]byte{}
|
||||||
inc(&f.nonce, cn)
|
//inc(&f.nonce, cn)
|
||||||
|
put(&f.nonce, cn)
|
||||||
f.r.nonce = f.nonce
|
f.r.nonce = f.nonce
|
||||||
f.w.nonce = f.nonce
|
f.w.nonce = f.nonce
|
||||||
f.r.off = roff
|
f.r.off = roff
|
||||||
|
|
@ -187,7 +188,8 @@ func (f *file) WriteAt(data []byte, pos int64) (n int, err error) {
|
||||||
c, ok := f.w.cm[cn]
|
c, ok := f.w.cm[cn]
|
||||||
if !ok {
|
if !ok {
|
||||||
c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn}
|
c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn}
|
||||||
inc(&c.nonce, cn)
|
//inc(&c.nonce, cn)
|
||||||
|
put(&c.nonce, cn)
|
||||||
f.w.cm[cn] = c
|
f.w.cm[cn] = c
|
||||||
if f.w.last == nil {
|
if f.w.last == nil {
|
||||||
f.w.last = c
|
f.w.last = c
|
||||||
|
|
@ -199,7 +201,6 @@ func (f *file) WriteAt(data []byte, pos int64) (n int, err error) {
|
||||||
if c.full() {
|
if c.full() {
|
||||||
// async flush
|
// async flush
|
||||||
go func() {
|
go func() {
|
||||||
//_, err = c.flush(notLastChunk)
|
|
||||||
c.flush(notLastChunk)
|
c.flush(notLastChunk)
|
||||||
}()
|
}()
|
||||||
delete(f.w.cm, cn)
|
delete(f.w.cm, cn)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package crypto
|
||||||
import (
|
import (
|
||||||
"cachefs/pkg/provider"
|
"cachefs/pkg/provider"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
@ -29,7 +30,6 @@ func initNonce(f provider.File) (nonce []byte, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = f.Write(nonce)
|
_, err = f.Write(nonce)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -76,3 +76,7 @@ func inc(nonce *[NonceSize]byte, n int64) {
|
||||||
incNonce(nonce)
|
incNonce(nonce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func put(nonce *[NonceSize]byte, n int64) {
|
||||||
|
binary.BigEndian.PutUint64((*nonce)[3:11], uint64(n))
|
||||||
|
}
|
||||||
|
|
|
||||||
15
pkg/provider/crypto/helper_test.go
Normal file
15
pkg/provider/crypto/helper_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkInc(b *testing.B) {
|
||||||
|
var n [NonceSize]byte
|
||||||
|
inc(&n, int64(b.N))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPut(b *testing.B) {
|
||||||
|
var n [NonceSize]byte
|
||||||
|
put(&n, int64(b.N))
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,11 @@ import (
|
||||||
|
|
||||||
type cryptoReader interface {
|
type cryptoReader interface {
|
||||||
io.Reader
|
io.Reader
|
||||||
io.ReaderAt
|
}
|
||||||
|
|
||||||
|
type nonce struct {
|
||||||
|
N [NonceSize]byte
|
||||||
|
C int
|
||||||
}
|
}
|
||||||
|
|
||||||
type reader struct {
|
type reader struct {
|
||||||
|
|
@ -24,6 +28,7 @@ type reader struct {
|
||||||
err error
|
err error
|
||||||
nonce [NonceSize]byte
|
nonce [NonceSize]byte
|
||||||
cn int64
|
cn int64
|
||||||
|
outBuf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReader(key []byte, src cryptoReader) (*reader, error) {
|
func newReader(key []byte, src cryptoReader) (*reader, error) {
|
||||||
|
|
@ -35,6 +40,7 @@ func newReader(key []byte, src cryptoReader) (*reader, error) {
|
||||||
a: aead,
|
a: aead,
|
||||||
src: src,
|
src: src,
|
||||||
cn: -1,
|
cn: -1,
|
||||||
|
outBuf: make([]byte, 0, FullSize),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,19 +97,24 @@ func (r *reader) readChunk() (last bool, err error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
outBuf := make([]byte, 0, ChunkSize)
|
//outBuf := make([]byte, 0, ChunkSize)
|
||||||
out, err := r.a.Open(outBuf, r.nonce[:], in, nil)
|
out, err := r.a.Open(r.outBuf, r.nonce[:], in, nil)
|
||||||
if err != nil && !last {
|
if err != nil && !last {
|
||||||
// Check if this was a full-length final chunk.
|
// Check if this was a full-length final chunk.
|
||||||
last = true
|
last = true
|
||||||
setLastChunkFlag(&r.nonce)
|
setLastChunkFlag(&r.nonce)
|
||||||
out, err = r.a.Open(outBuf, r.nonce[:], in, nil)
|
out, err = r.a.Open(r.outBuf, r.nonce[:], in, nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New("failed to decrypt and authenticate payload chunk")
|
return false, errors.New("failed to decrypt and authenticate payload chunk")
|
||||||
}
|
}
|
||||||
|
|
||||||
incNonce(&r.nonce)
|
incNonce(&r.nonce)
|
||||||
r.unread = r.buf[:copy(r.buf[:], out)]
|
//r.unread = r.buf[:copy(r.buf[:], out)]
|
||||||
|
size := FullSize
|
||||||
|
if len(out) < FullSize {
|
||||||
|
size = len(out)
|
||||||
|
}
|
||||||
|
r.unread = out[:size]
|
||||||
return last, nil
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
pkg/provider/sftp/file.go
Normal file
34
pkg/provider/sftp/file.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (C) 2022 Marius Schellenberger
|
||||||
|
|
||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
*sftp.File
|
||||||
|
fs *FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) ReadDir(_ int) (d []fs.DirEntry, err error) {
|
||||||
|
list, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d = make([]fs.DirEntry, len(list))
|
||||||
|
for i, v := range list {
|
||||||
|
d[i] = fs.FileInfoToDirEntry(v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Readdir(_ int) ([]fs.FileInfo, error) {
|
||||||
|
return f.fs.client.ReadDir(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file) Fd() uintptr {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -101,11 +101,16 @@ func (fs *FS) connect(ctx context.Context) {
|
||||||
go fs.keepAlive(ctx)
|
go fs.keepAlive(ctx)
|
||||||
fs.keepalive = true
|
fs.keepalive = true
|
||||||
}
|
}
|
||||||
fs.client, err = sftp.NewClient(fs.c,
|
client, err := sftp.NewClient(fs.c,
|
||||||
sftp.UseFstat(true),
|
sftp.UseFstat(true),
|
||||||
sftp.UseConcurrentReads(true),
|
sftp.UseConcurrentReads(true),
|
||||||
sftp.UseConcurrentWrites(true),
|
sftp.UseConcurrentWrites(true),
|
||||||
|
//sftp.MaxPacketChecked(32768*2),
|
||||||
|
//sftp.MaxPacketUnchecked(32768*2),
|
||||||
|
//sftp.MaxConcurrentRequestsPerFile(128),
|
||||||
)
|
)
|
||||||
|
if err == nil {
|
||||||
|
fs.client = client
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
@ -115,6 +120,9 @@ func (fs *FS) connect(ctx context.Context) {
|
||||||
fs.client = connError{}
|
fs.client = connError{}
|
||||||
fs.c = nil
|
fs.c = nil
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Error(err, "error creating client")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error(err, "error connecting")
|
log.Error(err, "error connecting")
|
||||||
}
|
}
|
||||||
|
|
@ -197,31 +205,6 @@ func (fs *FS) path(p string) string {
|
||||||
return filepath.Join(fs.root, filepath.FromSlash(path.Clean("/"+p)))
|
return filepath.Join(fs.root, filepath.FromSlash(path.Clean("/"+p)))
|
||||||
}
|
}
|
||||||
|
|
||||||
type file struct {
|
|
||||||
*sftp.File
|
|
||||||
fs *FS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *file) ReadDir(_ int) (d []fs.DirEntry, err error) {
|
|
||||||
list, err := f.Readdir(0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d = make([]fs.DirEntry, len(list))
|
|
||||||
for i, v := range list {
|
|
||||||
d[i] = fs.FileInfoToDirEntry(v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *file) Readdir(_ int) ([]fs.FileInfo, error) {
|
|
||||||
return f.fs.client.ReadDir(f.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (file) Fd() uintptr {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) {
|
func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) {
|
||||||
if u.User == nil {
|
if u.User == nil {
|
||||||
return nil, errors.New("missing username")
|
return nil, errors.New("missing username")
|
||||||
|
|
@ -233,8 +216,9 @@ func sshConfig(u *url.URL) (c *ssh.ClientConfig, err error) {
|
||||||
c = &ssh.ClientConfig{
|
c = &ssh.ClientConfig{
|
||||||
Config: ssh.Config{
|
Config: ssh.Config{
|
||||||
//KeyExchanges: []string{"curve25519-sha256"},
|
//KeyExchanges: []string{"curve25519-sha256"},
|
||||||
Ciphers: []string{"aes128-ctr"},
|
//Ciphers: []string{"aes128-ctr"},
|
||||||
MACs: []string{"hmac-sha2-256"},
|
Ciphers: []string{"chacha20-poly1305@openssh.com"},
|
||||||
|
//MACs: []string{"hmac-sha2-256"},
|
||||||
},
|
},
|
||||||
User: u.User.Username(),
|
User: u.User.Username(),
|
||||||
Timeout: time.Second * 30,
|
Timeout: time.Second * 30,
|
||||||
|
|
|
||||||
|
|
@ -111,5 +111,8 @@ span {
|
||||||
<article>
|
<article>
|
||||||
{{template "body" .}}
|
{{template "body" .}}
|
||||||
</article>
|
</article>
|
||||||
|
<footer>
|
||||||
|
<pre>© CacheFS v1.0 <a href="https://git.giftfish.de/ston1th/cachefs" rel="nofollow noreferrer noopener" target="_blank">[Source]</a></pre>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{{define "body" -}}
|
{{define "body" -}}
|
||||||
<pre>[v]: show video
|
<pre>[v]: show video
|
||||||
[n]: skip file caching
|
[n]: stream file without caching
|
||||||
[p]: preload file
|
[p]: preload file
|
||||||
<a href="/?o=preloads">[Preloads]</a></pre>
|
<a href="/?o=preloads">[Preloads]</a></pre>
|
||||||
<table>
|
<table>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue