added preload transfer rate
This commit is contained in:
parent
0c4376ec32
commit
470eeb921c
5 changed files with 111 additions and 13 deletions
|
|
@ -42,6 +42,7 @@ func (p *preload) Read(data []byte) (n int, err error) {
|
||||||
if p.f.hasChunk(n) {
|
if p.f.hasChunk(n) {
|
||||||
p.skipped++
|
p.skipped++
|
||||||
_, err = p.f.Seek(int64(n), io.SeekCurrent)
|
_, err = p.f.Seek(int64(n), io.SeekCurrent)
|
||||||
|
n = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n, err = p.f.readToCache(data)
|
n, err = p.f.readToCache(data)
|
||||||
|
|
@ -49,8 +50,9 @@ func (p *preload) Read(data []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) Preload(ctx context.Context, fin func(), errf func(), stop func()) {
|
func (f *File) Preload(ctx context.Context, r *Rate, fin, errf, stop func()) {
|
||||||
log := f.log
|
log := f.log
|
||||||
|
defer r.Stop()
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if f.offline {
|
if f.offline {
|
||||||
log.V(2).Error(errors.New("no preload in offline mode"), "error preloading file")
|
log.V(2).Error(errors.New("no preload in offline mode"), "error preloading file")
|
||||||
|
|
@ -65,7 +67,8 @@ func (f *File) Preload(ctx context.Context, fin func(), errf func(), stop func()
|
||||||
|
|
||||||
log.V(2).Info("preload started")
|
log.V(2).Info("preload started")
|
||||||
p := &preload{f: f, ctx: ctx}
|
p := &preload{f: f, ctx: ctx}
|
||||||
_, err := io.Copy(Discard, p)
|
r.SetReader(p)
|
||||||
|
_, err := io.Copy(Discard, r)
|
||||||
if err == context.Canceled {
|
if err == context.Canceled {
|
||||||
log.V(2).Info("preload canceled", "skipped", p.skipped, "written", p.written)
|
log.V(2).Info("preload canceled", "skipped", p.skipped, "written", p.written)
|
||||||
stop()
|
stop()
|
||||||
|
|
|
||||||
15
pkg/fs/fs.go
15
pkg/fs/fs.go
|
|
@ -126,12 +126,7 @@ const (
|
||||||
TiB
|
TiB
|
||||||
)
|
)
|
||||||
|
|
||||||
func (fs *FS) FileSize(name string) (size string) {
|
func FileSize(s int64) string {
|
||||||
fi, err := fs.Stat(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s := fi.Size()
|
|
||||||
switch {
|
switch {
|
||||||
case s >= TiB:
|
case s >= TiB:
|
||||||
return strconv.Itoa(int(s/TiB)) + " TiB"
|
return strconv.Itoa(int(s/TiB)) + " TiB"
|
||||||
|
|
@ -145,6 +140,14 @@ func (fs *FS) FileSize(name string) (size string) {
|
||||||
return strconv.Itoa(int(s)) + " B"
|
return strconv.Itoa(int(s)) + " B"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *FS) FileSize(name string) string {
|
||||||
|
fi, err := fs.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return FileSize(fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *FS) RemoveDst(name string) error {
|
func (fs *FS) RemoveDst(name string) error {
|
||||||
return fs.dst.Remove(name)
|
return fs.dst.Remove(name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
|
|
@ -54,14 +57,89 @@ func (q *queue) GetPreload(name string) *Preload {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Rate struct {
|
||||||
|
r io.Reader
|
||||||
|
done chan struct{}
|
||||||
|
rate atomic.Pointer[string]
|
||||||
|
num int64
|
||||||
|
last int64
|
||||||
|
interval int
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroRate = fmtRate(0)
|
||||||
|
|
||||||
|
func newRate() *Rate {
|
||||||
|
r := &Rate{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
interval: 10,
|
||||||
|
}
|
||||||
|
r.rate.Store(&zeroRate)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rate) SetReader(reader io.Reader) {
|
||||||
|
r.r = reader
|
||||||
|
go r.sampler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rate) Read(b []byte) (n int, err error) {
|
||||||
|
n, err = r.r.Read(b)
|
||||||
|
atomic.AddInt64(&r.num, int64(n))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fmtRate(r int) string {
|
||||||
|
switch {
|
||||||
|
case r >= GiB:
|
||||||
|
return strconv.FormatFloat(float64(r)/GiB, 'f', 2, 64) + " GiB/s"
|
||||||
|
case r >= MiB:
|
||||||
|
return strconv.FormatFloat(float64(r)/MiB, 'f', 2, 64) + " MiB/s"
|
||||||
|
case r >= KiB:
|
||||||
|
return strconv.FormatFloat(float64(r)/KiB, 'f', 2, 64) + " KiB/s"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(r) + " B/s"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rate) sampler() {
|
||||||
|
t := time.NewTicker(time.Duration(r.interval) * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-r.done:
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
num := atomic.LoadInt64(&r.num)
|
||||||
|
rate := int(num-r.last) / r.interval
|
||||||
|
r.last = num
|
||||||
|
s := fmtRate(rate)
|
||||||
|
r.rate.Store(&s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rate) String() string {
|
||||||
|
s := r.rate.Load()
|
||||||
|
if s == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rate) Stop() {
|
||||||
|
close(r.done)
|
||||||
|
r.rate.Store(&zeroRate)
|
||||||
|
}
|
||||||
|
|
||||||
type Preload struct {
|
type Preload struct {
|
||||||
ph *PreloadHandler
|
ph *PreloadHandler
|
||||||
|
rate *Rate
|
||||||
delay time.Duration
|
delay time.Duration
|
||||||
Name string
|
Name string
|
||||||
cancel func()
|
cancel func()
|
||||||
Prio int
|
Prio int
|
||||||
Status int
|
Status int
|
||||||
Errc int
|
Errc int
|
||||||
|
Size int64
|
||||||
Running bool
|
Running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +210,13 @@ func (ph *PreloadHandler) schedule() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Preload) Rate() string {
|
||||||
|
if p.rate != nil {
|
||||||
|
return p.rate.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Preload) start() {
|
func (p *Preload) start() {
|
||||||
if p.Running {
|
if p.Running {
|
||||||
return
|
return
|
||||||
|
|
@ -153,7 +238,10 @@ func (p *Preload) start() {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
p.Running = true
|
p.Running = true
|
||||||
p.cancel = cancel
|
p.cancel = cancel
|
||||||
|
p.rate = newRate()
|
||||||
|
p.Size = f.size()
|
||||||
go f.Preload(ctx,
|
go f.Preload(ctx,
|
||||||
|
p.rate,
|
||||||
func() {
|
func() {
|
||||||
p.ph.fin <- name
|
p.ph.fin <- name
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ type file struct {
|
||||||
URI template.HTML
|
URI template.HTML
|
||||||
Anchor string
|
Anchor string
|
||||||
Size string
|
Size string
|
||||||
|
Rate string
|
||||||
Status int
|
Status int
|
||||||
Prio int
|
Prio int
|
||||||
Errc int
|
Errc int
|
||||||
|
|
@ -95,7 +96,7 @@ func (r *responseInterceptor) Status() int {
|
||||||
return r.status
|
return r.status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseInterceptor) GetPaths(path string, fs *fs.FS, relative bool) (dc dirContents, err error) {
|
func (r *responseInterceptor) GetPaths(path string, filesystem *fs.FS, relative bool) (dc dirContents, err error) {
|
||||||
buf := r.buf.Bytes()
|
buf := r.buf.Bytes()
|
||||||
buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&"))
|
buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&"))
|
||||||
err = xml.Unmarshal(buf, &dc)
|
err = xml.Unmarshal(buf, &dc)
|
||||||
|
|
@ -123,8 +124,8 @@ func (r *responseInterceptor) GetPaths(path string, fs *fs.FS, relative bool) (d
|
||||||
Name: name,
|
Name: name,
|
||||||
URI: uri,
|
URI: uri,
|
||||||
Anchor: anchor(p),
|
Anchor: anchor(p),
|
||||||
Size: fs.FileSize(full),
|
Size: filesystem.FileSize(full),
|
||||||
Status: fs.CacheStatus(full),
|
Status: filesystem.CacheStatus(full),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,12 +134,14 @@ func (r *responseInterceptor) GetPaths(path string, fs *fs.FS, relative bool) (d
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPreloads(path string, fs *fs.FS) (dc dirContents) {
|
func getPreloads(path string, filesystem *fs.FS) (dc dirContents) {
|
||||||
dc.Base = path
|
dc.Base = path
|
||||||
for _, p := range fs.Preloads() {
|
for _, p := range filesystem.Preloads() {
|
||||||
dc.Files = append(dc.Files, file{
|
dc.Files = append(dc.Files, file{
|
||||||
Name: template.HTML(p.Name),
|
Name: template.HTML(p.Name),
|
||||||
URI: template.HTML(p.Name),
|
URI: template.HTML(p.Name),
|
||||||
|
Size: fs.FileSize(p.Size),
|
||||||
|
Rate: p.Rate(),
|
||||||
Status: p.Status,
|
Status: p.Status,
|
||||||
Prio: p.Prio,
|
Prio: p.Prio,
|
||||||
Errc: p.Errc,
|
Errc: p.Errc,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ Quota: {{.QuotaCur}} / {{.QuotaMax}} GiB
|
||||||
<span class="status-yellow">[queued]</span>
|
<span class="status-yellow">[queued]</span>
|
||||||
{{end -}}
|
{{end -}}
|
||||||
{{end -}}
|
{{end -}}
|
||||||
|
<span>{{$s.Rate}}</span>
|
||||||
<span>{{$s.Size}}</span>
|
<span>{{$s.Size}}</span>
|
||||||
<span>{{$s.Prio}}</span><a href="{{$s.URI}}?o=v">[v]</a><a href="{{$s.Name}}?o=s&r=preloads">[s]</a><a href="{{$s.Name}}?o=i&r=preloads">[+]</a><a href="{{$s.Name}}?o=d&r=preloads">[-]</a>
|
<span>{{$s.Prio}}</span><a href="{{$s.URI}}?o=v">[v]</a><a href="{{$s.Name}}?o=s&r=preloads">[s]</a><a href="{{$s.Name}}?o=i&r=preloads">[+]</a><a href="{{$s.Name}}?o=d&r=preloads">[-]</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue