328 lines
5.3 KiB
Go
328 lines
5.3 KiB
Go
// Copyright (C) 2022 Marius Schellenberger
|
|
|
|
package fs
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"io"
|
|
"slices"
|
|
"strconv"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
)
|
|
|
|
type queue []*Preload
|
|
|
|
func (queue) CompReverse(a, b *Preload) int { return cmp.Compare(b.Prio, a.Prio) }
|
|
|
|
func (q *queue) Sort() {
|
|
slices.SortFunc(*q, q.CompReverse)
|
|
}
|
|
|
|
func (q *queue) Add(ph *PreloadHandler, name string, prio int) {
|
|
for _, p := range *q {
|
|
if p.Name == name {
|
|
p.Prio += prio
|
|
if p.Prio < 0 {
|
|
p.Prio = 0
|
|
}
|
|
q.Sort()
|
|
return
|
|
}
|
|
}
|
|
p := &Preload{Name: name, ph: ph}
|
|
*q = append(*q, p)
|
|
file, err := ph.fs.Open(name)
|
|
if err == nil {
|
|
defer file.Close()
|
|
f, ok := file.(*File)
|
|
if ok {
|
|
p.Size = f.size()
|
|
}
|
|
}
|
|
q.Sort()
|
|
}
|
|
|
|
func (q *queue) Remove(name string, collect bool) {
|
|
for i, p := range *q {
|
|
if p.Name == name {
|
|
p.stop(collect)
|
|
*q = slices.Delete(*q, i, i+1)
|
|
q.Sort()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (q *queue) GetPreload(name string) *Preload {
|
|
for _, p := range *q {
|
|
if p.Name == name {
|
|
return p
|
|
}
|
|
}
|
|
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)
|
|
if n > 0 {
|
|
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 {
|
|
ph *PreloadHandler
|
|
rate *Rate
|
|
delay time.Duration
|
|
Name string
|
|
cancel func()
|
|
Prio int
|
|
Status int
|
|
Errc int
|
|
Size int64
|
|
Running bool
|
|
}
|
|
|
|
type Preloads []Preload
|
|
|
|
type PreloadHandler struct {
|
|
mu sync.RWMutex
|
|
log logr.Logger
|
|
fs *FS
|
|
q queue
|
|
fin chan string
|
|
err chan string
|
|
stop chan struct{}
|
|
done chan struct{}
|
|
max int
|
|
}
|
|
|
|
func NewPreloadHandler(ctx context.Context, fs *FS, max int, log logr.Logger) (ph *PreloadHandler) {
|
|
if max <= 0 {
|
|
max = 1
|
|
}
|
|
ph = &PreloadHandler{
|
|
log: log,
|
|
fs: fs,
|
|
q: make(queue, 0),
|
|
fin: make(chan string, 1),
|
|
err: make(chan string, 1),
|
|
stop: make(chan struct{}, 1),
|
|
done: make(chan struct{}),
|
|
max: max,
|
|
}
|
|
go ph.preloadStatus(ctx)
|
|
return
|
|
}
|
|
|
|
func (ph *PreloadHandler) RemovePreload(name string) {
|
|
ph.mu.RLock()
|
|
defer ph.mu.RUnlock()
|
|
ph.q.Remove(name, true)
|
|
ph.schedule()
|
|
}
|
|
|
|
func (ph *PreloadHandler) Preloads() (ps Preloads) {
|
|
ph.mu.RLock()
|
|
defer ph.mu.RUnlock()
|
|
ps = make(Preloads, len(ph.q))
|
|
for i, v := range ph.q {
|
|
ps[i] = *v
|
|
ps[i].Status = ph.fs.CacheStatus(v.Name)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (ph *PreloadHandler) Preload(name string, prio int) {
|
|
ph.mu.Lock()
|
|
defer ph.mu.Unlock()
|
|
ph.q.Add(ph, name, prio)
|
|
ph.schedule()
|
|
}
|
|
|
|
func (ph *PreloadHandler) schedule() {
|
|
for i := 0; i < len(ph.q); i++ {
|
|
if i < ph.max {
|
|
go ph.q[i].start()
|
|
} else {
|
|
ph.q[i].stop(true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Preload) Rate() string {
|
|
if p.rate != nil {
|
|
return p.rate.String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (p *Preload) start() {
|
|
if p.Running {
|
|
return
|
|
}
|
|
if p.delay != 0 {
|
|
time.Sleep(p.delay)
|
|
}
|
|
name := p.Name
|
|
file, err := p.ph.fs.Open(name)
|
|
if err != nil {
|
|
p.ph.log.Error(err, "error staring next preload", "file", name)
|
|
p.ph.err <- name
|
|
return
|
|
}
|
|
f, ok := file.(*File)
|
|
if !ok {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
p.Running = true
|
|
p.cancel = cancel
|
|
p.rate = newRate()
|
|
p.Size = f.size()
|
|
go f.Preload(ctx,
|
|
p.rate,
|
|
func() {
|
|
p.ph.fin <- name
|
|
},
|
|
func() {
|
|
p.ph.err <- name
|
|
},
|
|
func() {
|
|
p.ph.stop <- struct{}{}
|
|
},
|
|
)
|
|
}
|
|
|
|
func (p *Preload) stop(collect bool) {
|
|
if p.Running {
|
|
p.Running = false
|
|
p.cancel()
|
|
if collect {
|
|
<-p.ph.stop
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ph *PreloadHandler) preloadStatus(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
ph.close()
|
|
return
|
|
case name := <-ph.fin:
|
|
ph.mu.Lock()
|
|
ph.q.Remove(name, false)
|
|
ph.schedule()
|
|
ph.mu.Unlock()
|
|
case name := <-ph.err:
|
|
ph.mu.Lock()
|
|
p := ph.q.GetPreload(name)
|
|
if p != nil {
|
|
p.stop(false)
|
|
p.delay = time.Second * 2
|
|
if p.Errc >= 10 {
|
|
p.Prio = -10
|
|
ph.schedule()
|
|
} else {
|
|
p.Errc += 1
|
|
go p.start()
|
|
}
|
|
}
|
|
ph.mu.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ph *PreloadHandler) close() {
|
|
ph.mu.Lock()
|
|
defer ph.mu.Unlock()
|
|
n := len(ph.q)
|
|
for _, p := range ph.q {
|
|
p.stop(false)
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
select {
|
|
case <-ph.fin:
|
|
case <-ph.err:
|
|
case <-ph.stop:
|
|
}
|
|
}
|
|
close(ph.done)
|
|
}
|
|
|
|
func (ph *PreloadHandler) Done() <-chan struct{} {
|
|
return ph.done
|
|
}
|