62 lines
1.3 KiB
Go
62 lines
1.3 KiB
Go
|
|
package store
|
||
|
|
|
||
|
|
import (
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// uploadHistory tracks running total/count of upload durations for one device.
|
||
|
|
type uploadHistory struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
total time.Duration
|
||
|
|
count int64
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *uploadHistory) add(d time.Duration) {
|
||
|
|
u.mu.Lock()
|
||
|
|
defer u.mu.Unlock()
|
||
|
|
u.total += d
|
||
|
|
u.count++
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *uploadHistory) average() time.Duration {
|
||
|
|
u.mu.Lock()
|
||
|
|
defer u.mu.Unlock()
|
||
|
|
if u.count == 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
// Truncating integer division — same idea as C# BigInteger total / count.
|
||
|
|
return u.total / time.Duration(u.count)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) uploadFor(id DeviceID) *uploadHistory {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
u, ok := s.uploads[id]
|
||
|
|
if !ok {
|
||
|
|
u = &uploadHistory{}
|
||
|
|
s.uploads[id] = u
|
||
|
|
}
|
||
|
|
return u
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddUpload records an upload duration (nanoseconds as time.Duration) for deviceID.
|
||
|
|
// sent_at from the API is intentionally ignored here; the HTTP layer still accepts it.
|
||
|
|
func (s *Store) AddUpload(deviceID DeviceID, uploadTime time.Duration) {
|
||
|
|
s.uploadFor(deviceID).add(uploadTime)
|
||
|
|
}
|
||
|
|
|
||
|
|
// AverageUploadTime returns the mean upload duration for deviceID.
|
||
|
|
// Formatting for the API (duration.String()) belongs at the HTTP edge.
|
||
|
|
// Unknown / never-seen devices return 0.
|
||
|
|
func (s *Store) AverageUploadTime(deviceID DeviceID) time.Duration {
|
||
|
|
s.mu.Lock()
|
||
|
|
u, ok := s.uploads[deviceID]
|
||
|
|
s.mu.Unlock()
|
||
|
|
if !ok {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return u.average()
|
||
|
|
}
|