github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/timetable.go (about)

     1  package nomad
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/ugorji/go/codec"
     9  )
    10  
    11  // TimeTable is used to associate a Raft index with a timestamp.
    12  // This is used so that we can quickly go from a timestamp to an
    13  // index or visa versa.
    14  type TimeTable struct {
    15  	granularity time.Duration
    16  	limit       time.Duration
    17  	table       []TimeTableEntry
    18  	l           sync.RWMutex
    19  }
    20  
    21  // TimeTableEntry is used to track a time and index
    22  type TimeTableEntry struct {
    23  	Index uint64
    24  	Time  time.Time
    25  }
    26  
    27  // NewTimeTable creates a new time table which stores entries
    28  // at a given granularity for a maximum limit. The storage space
    29  // required is (limit/granularity)
    30  func NewTimeTable(granularity time.Duration, limit time.Duration) *TimeTable {
    31  	size := limit / granularity
    32  	if size < 1 {
    33  		size = 1
    34  	}
    35  	t := &TimeTable{
    36  		granularity: granularity,
    37  		limit:       limit,
    38  		table:       make([]TimeTableEntry, 1, size),
    39  	}
    40  	return t
    41  }
    42  
    43  // Serialize is used to serialize the time table
    44  func (t *TimeTable) Serialize(enc *codec.Encoder) error {
    45  	t.l.RLock()
    46  	defer t.l.RUnlock()
    47  	return enc.Encode(t.table)
    48  }
    49  
    50  // Deserialize is used to deserialize the time table
    51  // and restore the state
    52  func (t *TimeTable) Deserialize(dec *codec.Decoder) error {
    53  	// Decode the table
    54  	var table []TimeTableEntry
    55  	if err := dec.Decode(&table); err != nil {
    56  		return err
    57  	}
    58  
    59  	// Witness from oldest to newest
    60  	n := len(table)
    61  	for i := n - 1; i >= 0; i-- {
    62  		t.Witness(table[i].Index, table[i].Time)
    63  	}
    64  	return nil
    65  }
    66  
    67  // Witness is used to witness a new index and time.
    68  func (t *TimeTable) Witness(index uint64, when time.Time) {
    69  	t.l.Lock()
    70  	defer t.l.Unlock()
    71  
    72  	// Ensure monotonic indexes
    73  	if t.table[0].Index > index {
    74  		return
    75  	}
    76  
    77  	// Skip if we already have a recent enough entry
    78  	if when.Sub(t.table[0].Time) < t.granularity {
    79  		return
    80  	}
    81  
    82  	// Grow the table if we haven't reached the size
    83  	if len(t.table) < cap(t.table) {
    84  		t.table = append(t.table, TimeTableEntry{})
    85  	}
    86  
    87  	// Add this entry
    88  	copy(t.table[1:], t.table[:len(t.table)-1])
    89  	t.table[0].Index = index
    90  	t.table[0].Time = when
    91  }
    92  
    93  // NearestIndex returns the nearest index older than the given time
    94  func (t *TimeTable) NearestIndex(when time.Time) uint64 {
    95  	t.l.RLock()
    96  	defer t.l.RUnlock()
    97  
    98  	n := len(t.table)
    99  	idx := sort.Search(n, func(i int) bool {
   100  		return !t.table[i].Time.After(when)
   101  	})
   102  	if idx < n && idx >= 0 {
   103  		return t.table[idx].Index
   104  	}
   105  	return 0
   106  }
   107  
   108  // NearestTime returns the nearest time older than the given index
   109  func (t *TimeTable) NearestTime(index uint64) time.Time {
   110  	t.l.RLock()
   111  	defer t.l.RUnlock()
   112  
   113  	n := len(t.table)
   114  	idx := sort.Search(n, func(i int) bool {
   115  		return t.table[i].Index <= index
   116  	})
   117  	if idx < n && idx >= 0 {
   118  		return t.table[idx].Time
   119  	}
   120  	return time.Time{}
   121  }