diff --git a/tsdb/engine.go b/tsdb/engine.go index c78c9052f80..e0ef33f4731 100644 --- a/tsdb/engine.go +++ b/tsdb/engine.go @@ -83,6 +83,8 @@ type Engine interface { DiskSize() int64 IsIdle() (bool, string) Free() error + // TimeRange will return the min/max time range of shard + TimeRange() (int64, int64) io.WriterTo } diff --git a/tsdb/engine/tsm1/engine.go b/tsdb/engine/tsm1/engine.go index 364823a8d3e..6fb7946e242 100644 --- a/tsdb/engine/tsm1/engine.go +++ b/tsdb/engine/tsm1/engine.go @@ -677,6 +677,11 @@ func (e *Engine) LastModified() time.Time { return fsTime } +// TimeRange returns the min and max time range for this shard +func (e *Engine) TimeRange() (int64, int64) { + return e.FileStore.TimeRange() +} + // EngineStatistics maintains statistics for the engine. type EngineStatistics struct { CacheCompactions int64 // Counter of cache compactions that have ever run. diff --git a/tsdb/engine/tsm1/file_store.go b/tsdb/engine/tsm1/file_store.go index dbb826fd643..38bc5609ef4 100644 --- a/tsdb/engine/tsm1/file_store.go +++ b/tsdb/engine/tsm1/file_store.go @@ -1080,6 +1080,27 @@ func (f *FileStore) LastModified() time.Time { return f.lastModified } +// TimeRange returns the minimum and maximum times across all TSM files in the FileStore. +// Returns (math.MaxInt64, math.MinInt64) if there are no files. +func (f *FileStore) TimeRange() (min, max int64) { + f.mu.RLock() + defer f.mu.RUnlock() + + min, max = math.MaxInt64, math.MinInt64 + + for _, file := range f.files { + fileMin, fileMax := file.TimeRange() + if fileMin < min { + min = fileMin + } + if fileMax > max { + max = fileMax + } + } + + return min, max +} + // We need to determine the possible files that may be accessed by this query given // the time range. func (f *FileStore) cost(key []byte, min, max int64) query.IteratorCost { diff --git a/tsdb/shard.go b/tsdb/shard.go index bac6ca2bbd8..a56a6c3f521 100644 --- a/tsdb/shard.go +++ b/tsdb/shard.go @@ -474,6 +474,16 @@ func (s *Shard) LastModifiedWithErr() (time.Time, error) { return engine.LastModified(), nil } +func (s *Shard) TimeRange() (int64, int64, error) { + engine, err := s.Engine() + if err != nil { + return int64(0), int64(0), fmt.Errorf("failed getting shard %d time range: %w", s.id, err) + } + + minT, maxT := engine.TimeRange() + return minT, maxT, nil +} + // Index returns a reference to the underlying index. It returns an error if // the index is nil. func (s *Shard) Index() (Index, error) { diff --git a/tsdb/shard_internal_test.go b/tsdb/shard_internal_test.go index eeee14cfa75..1e7f38ccfb0 100644 --- a/tsdb/shard_internal_test.go +++ b/tsdb/shard_internal_test.go @@ -2,6 +2,7 @@ package tsdb import ( "fmt" + "math" "os" "path" "path/filepath" @@ -301,3 +302,32 @@ func (sh *TempShard) MustWritePointsString(s string) { panic(err) } } + +func TestShard_TimeRange(t *testing.T) { + for _, index := range RegisteredIndexes() { + t.Run(index, func(t *testing.T) { + sh := NewTempShard(index) + defer CloseShard(t, sh) + + require.NoError(t, sh.Open()) + + min, max, err := sh.TimeRange() + require.NoError(t, err) + require.Equal(t, int64(math.MaxInt64), min) + require.Equal(t, int64(math.MinInt64), max) + + sh.MustWritePointsString(` +cpu value=100 1000000000 +cpu value=200 2000000000 +mem value=300 1500000000`) + + // Force flush WAL to TSM files + require.NoError(t, sh.ScheduleFullCompaction()) + + min, max, err = sh.TimeRange() + require.NoError(t, err) + require.Equal(t, int64(1000000000000000000), min) + require.Equal(t, int64(2000000000000000000), max) + }) + } +}