diff --git a/storage/local/chunk.go b/storage/local/chunk.go index ac54a5dc1d..6fd10153ca 100644 --- a/storage/local/chunk.go +++ b/storage/local/chunk.go @@ -194,19 +194,19 @@ func (cd *chunkDesc) maybePopulateLastTime() { } } -// lastSamplePair returns the last sample pair of the underlying chunk, or nil -// if the chunk is evicted. For safe concurrent access, this method requires the -// fingerprint of the time series to be locked. +// lastSamplePair returns the last sample pair of the underlying chunk, or +// ZeroSampleValue if the chunk is evicted. For safe concurrent access, this +// method requires the fingerprint of the time series to be locked. // TODO(beorn7): Move up into memorySeries. -func (cd *chunkDesc) lastSamplePair() *model.SamplePair { +func (cd *chunkDesc) lastSamplePair() model.SamplePair { cd.Lock() defer cd.Unlock() if cd.c == nil { - return nil + return ZeroSamplePair } it := cd.c.newIterator() - return &model.SamplePair{ + return model.SamplePair{ Timestamp: it.lastTimestamp(), Value: it.lastSampleValue(), } @@ -293,8 +293,7 @@ type chunkIterator interface { lastSampleValue() model.SampleValue // Gets the value that is closest before the given time. In case a value // exist at precisely the given time, that value is returned. If no - // applicable value exists, a SamplePair with timestamp model.Earliest - // and value 0.0 is returned. + // applicable value exists, ZeroSamplePair is returned. valueAtOrBeforeTime(model.Time) model.SamplePair // Gets all values contained within a given interval. rangeValues(metric.Interval) []model.SamplePair diff --git a/storage/local/delta.go b/storage/local/delta.go index 7222c5a157..99da249c41 100644 --- a/storage/local/delta.go +++ b/storage/local/delta.go @@ -307,7 +307,7 @@ func (it *deltaEncodedChunkIterator) valueAtOrBeforeTime(t model.Time) model.Sam return it.timestampAtIndex(i).After(t) }) if i == 0 { - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } return model.SamplePair{ Timestamp: it.timestampAtIndex(i - 1), diff --git a/storage/local/doubledelta.go b/storage/local/doubledelta.go index e0e0856ce6..cb50db6a7c 100644 --- a/storage/local/doubledelta.go +++ b/storage/local/doubledelta.go @@ -413,7 +413,7 @@ func (it *doubleDeltaEncodedChunkIterator) valueAtOrBeforeTime(t model.Time) mod return it.timestampAtIndex(i).After(t) }) if i == 0 { - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } return model.SamplePair{ Timestamp: it.timestampAtIndex(i - 1), diff --git a/storage/local/interface.go b/storage/local/interface.go index a62a13b19a..4007215708 100644 --- a/storage/local/interface.go +++ b/storage/local/interface.go @@ -42,10 +42,12 @@ type Storage interface { // label matchers. At least one label matcher must be specified that does not // match the empty string. MetricsForLabelMatchers(...*metric.LabelMatcher) map[model.Fingerprint]metric.Metric - // LastSamplePairForFingerprint returns the last sample pair for the - // provided fingerprint. If the respective time series does not exist or - // has an evicted head chunk, nil is returned. - LastSamplePairForFingerprint(model.Fingerprint) *model.SamplePair + // LastSamplePairForFingerprint returns the last sample pair that has + // been ingested for the provided fingerprint. If this instance of the + // Storage has never ingested a sample for the provided fingerprint (or + // the last ingestion is so long ago that the series has been archived), + // ZeroSamplePair is returned. + LastSamplePairForFingerprint(model.Fingerprint) model.SamplePair // Get all of the label values that are associated with a given label name. LabelValuesForLabelName(model.LabelName) model.LabelValues // Get the metric associated with the provided fingerprint. @@ -73,8 +75,7 @@ type Storage interface { type SeriesIterator interface { // Gets the value that is closest before the given time. In case a value // exist at precisely the given time, that value is returned. If no - // applicable value exists, a SamplePair with timestamp model.Earliest - // and value 0.0 is returned. + // applicable value exists, ZeroSamplePair is returned. ValueAtOrBeforeTime(model.Time) model.SamplePair // Gets the boundary values of an interval: the first and last value // within a given interval. @@ -94,3 +95,10 @@ type Preloader interface { // Close unpins any previously requested series data from memory. Close() } + +// ZeroSamplePair is the pseudo zero-value of model.SamplePair used by the local +// package to signal a non-existing sample. It is a SamplePair with timestamp +// model.Earliest and value 0.0. Note that the natural zero value of SamplePair +// has a timestamp of 0, which is possible to appear in a real SamplePair and +// thus not suitable to signal a non-existing SamplePair. +var ZeroSamplePair = model.SamplePair{Timestamp: model.Earliest} diff --git a/storage/local/series.go b/storage/local/series.go index 6943e925a9..425366c868 100644 --- a/storage/local/series.go +++ b/storage/local/series.go @@ -479,7 +479,7 @@ func (it *memorySeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePa } if len(it.chunks) == 0 { - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } // Find the last chunk where firstTime() is before or equal to t. @@ -489,7 +489,7 @@ func (it *memorySeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePa }) if i == len(it.chunks) { // Even the first chunk starts after t. - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } it.chunkIt = it.chunkIterator(l - i) return it.chunkIt.valueAtOrBeforeTime(t) @@ -598,7 +598,7 @@ type nopSeriesIterator struct{} // ValueAtTime implements SeriesIterator. func (i nopSeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePair { - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } // BoundaryValues implements SeriesIterator. diff --git a/storage/local/storage.go b/storage/local/storage.go index 2f869b5ad4..8c140379c0 100644 --- a/storage/local/storage.go +++ b/storage/local/storage.go @@ -346,13 +346,13 @@ func (s *memorySeriesStorage) WaitForIndexing() { } // LastSampleForFingerprint implements Storage. -func (s *memorySeriesStorage) LastSamplePairForFingerprint(fp model.Fingerprint) *model.SamplePair { +func (s *memorySeriesStorage) LastSamplePairForFingerprint(fp model.Fingerprint) model.SamplePair { s.fpLocker.Lock(fp) defer s.fpLocker.Unlock(fp) series, ok := s.fpToSeries.get(fp) if !ok { - return nil + return ZeroSamplePair } return series.head().lastSamplePair() } @@ -367,7 +367,7 @@ type boundedIterator struct { // ValueAtOrBeforeTime implements the SeriesIterator interface. func (bit *boundedIterator) ValueAtOrBeforeTime(ts model.Time) model.SamplePair { if ts < bit.start { - return model.SamplePair{Timestamp: model.Earliest} + return ZeroSamplePair } return bit.it.ValueAtOrBeforeTime(ts) } diff --git a/web/federate.go b/web/federate.go index 81f39302ef..d9baf676b5 100644 --- a/web/federate.go +++ b/web/federate.go @@ -67,7 +67,7 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) { sp := h.storage.LastSamplePairForFingerprint(fp) // Discard if sample does not exist or lays before the staleness interval. - if sp == nil || sp.Timestamp.Before(minTimestamp) { + if sp.Timestamp.Before(minTimestamp) { continue }