go project - add a migration and rename the dockerfiles so that Go is the primary
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrOutOfSequence is returned when a heartbeat timestamp is earlier than
|
||||
// the latest accepted heartbeat for that device.
|
||||
var ErrOutOfSequence = errors.New("heartbeat out of sequence")
|
||||
|
||||
// heartbeatHistory tracks one device's heartbeat timeline.
|
||||
// Unexported: only Store methods expose behavior.
|
||||
type heartbeatHistory struct {
|
||||
mu sync.Mutex
|
||||
count int64
|
||||
first time.Time
|
||||
last time.Time
|
||||
}
|
||||
|
||||
func (h *heartbeatHistory) add(sentAt time.Time) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if h.first.IsZero() {
|
||||
h.first = sentAt
|
||||
h.last = sentAt
|
||||
h.count = 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal timestamps are allowed (matches C#: sentAt < LastHeartBeat only).
|
||||
if sentAt.Before(h.last) {
|
||||
return ErrOutOfSequence
|
||||
}
|
||||
|
||||
h.last = sentAt
|
||||
h.count++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *heartbeatHistory) uptime() float64 {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if h.first.IsZero() {
|
||||
return 0
|
||||
}
|
||||
|
||||
minutes := h.last.Sub(h.first).Minutes()
|
||||
// Single heartbeat (or identical timestamps): treat span as one minute.
|
||||
if minutes == 0 {
|
||||
minutes = 1
|
||||
}
|
||||
|
||||
return float64(h.count) / minutes * 100
|
||||
}
|
||||
|
||||
// historyFor returns the per-device history, creating it if needed.
|
||||
// The store-level lock only covers map insert/lookup; callers then use
|
||||
// the history's own lock for mutations/reads.
|
||||
func (s *Store) historyFor(id DeviceID) *heartbeatHistory {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
h, ok := s.heartbeats[id]
|
||||
if !ok {
|
||||
h = &heartbeatHistory{}
|
||||
s.heartbeats[id] = h
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// AddHeartbeat records a heartbeat for deviceID.
|
||||
// Returns ErrOutOfSequence if sentAt is earlier than the last accepted beat.
|
||||
func (s *Store) AddHeartbeat(deviceID DeviceID, sentAt time.Time) error {
|
||||
return s.historyFor(deviceID).add(sentAt)
|
||||
}
|
||||
|
||||
// Uptime returns uptime percent for deviceID:
|
||||
//
|
||||
// (heartbeatCount / minutesBetweenFirstAndLast) * 100
|
||||
//
|
||||
// Unknown / never-seen devices return 0. A zero-length span is treated as 1 minute.
|
||||
func (s *Store) Uptime(deviceID DeviceID) float64 {
|
||||
s.mu.Lock()
|
||||
h, ok := s.heartbeats[deviceID]
|
||||
s.mu.Unlock()
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return h.uptime()
|
||||
}
|
||||
Reference in New Issue
Block a user