125 lines
2.7 KiB
Go
125 lines
2.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package nomad
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-msgpack/codec"
|
|
)
|
|
|
|
// TimeTable is used to associate a Raft index with a timestamp.
|
|
// This is used so that we can quickly go from a timestamp to an
|
|
// index or visa versa.
|
|
type TimeTable struct {
|
|
granularity time.Duration
|
|
limit time.Duration
|
|
table []TimeTableEntry
|
|
l sync.RWMutex
|
|
}
|
|
|
|
// TimeTableEntry is used to track a time and index
|
|
type TimeTableEntry struct {
|
|
Index uint64
|
|
Time time.Time
|
|
}
|
|
|
|
// NewTimeTable creates a new time table which stores entries
|
|
// at a given granularity for a maximum limit. The storage space
|
|
// required is (limit/granularity)
|
|
func NewTimeTable(granularity time.Duration, limit time.Duration) *TimeTable {
|
|
size := limit / granularity
|
|
if size < 1 {
|
|
size = 1
|
|
}
|
|
t := &TimeTable{
|
|
granularity: granularity,
|
|
limit: limit,
|
|
table: make([]TimeTableEntry, 1, size),
|
|
}
|
|
return t
|
|
}
|
|
|
|
// Serialize is used to serialize the time table
|
|
func (t *TimeTable) Serialize(enc *codec.Encoder) error {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
return enc.Encode(t.table)
|
|
}
|
|
|
|
// Deserialize is used to deserialize the time table
|
|
// and restore the state
|
|
func (t *TimeTable) Deserialize(dec *codec.Decoder) error {
|
|
// Decode the table
|
|
var table []TimeTableEntry
|
|
if err := dec.Decode(&table); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Witness from oldest to newest
|
|
n := len(table)
|
|
for i := n - 1; i >= 0; i-- {
|
|
t.Witness(table[i].Index, table[i].Time)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Witness is used to witness a new index and time.
|
|
func (t *TimeTable) Witness(index uint64, when time.Time) {
|
|
t.l.Lock()
|
|
defer t.l.Unlock()
|
|
|
|
// Ensure monotonic indexes
|
|
if t.table[0].Index > index {
|
|
return
|
|
}
|
|
|
|
// Skip if we already have a recent enough entry
|
|
if when.Sub(t.table[0].Time) < t.granularity {
|
|
return
|
|
}
|
|
|
|
// Grow the table if we haven't reached the size
|
|
if len(t.table) < cap(t.table) {
|
|
t.table = append(t.table, TimeTableEntry{})
|
|
}
|
|
|
|
// Add this entry
|
|
copy(t.table[1:], t.table[:len(t.table)-1])
|
|
t.table[0].Index = index
|
|
t.table[0].Time = when
|
|
}
|
|
|
|
// NearestIndex returns the nearest index older than the given time
|
|
func (t *TimeTable) NearestIndex(when time.Time) uint64 {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
n := len(t.table)
|
|
idx := sort.Search(n, func(i int) bool {
|
|
return !t.table[i].Time.After(when)
|
|
})
|
|
if idx < n && idx >= 0 {
|
|
return t.table[idx].Index
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// NearestTime returns the nearest time older than the given index
|
|
func (t *TimeTable) NearestTime(index uint64) time.Time {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
n := len(t.table)
|
|
idx := sort.Search(n, func(i int) bool {
|
|
return t.table[i].Index <= index
|
|
})
|
|
if idx < n && idx >= 0 {
|
|
return t.table[idx].Time
|
|
}
|
|
return time.Time{}
|
|
}
|