From 08186b629743dc82cae4c8c6215f44f718fe8b4b Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 23 Apr 2015 11:12:09 -0700 Subject: [PATCH 1/3] Rename StatsBuffer to TimedStore in utils --- storage/memory/memory.go | 5 +++-- .../stats_buffer.go => utils/timed_store.go | 18 +++++++++--------- .../timed_store_test.go | 14 +++++++------- 3 files changed, 19 insertions(+), 18 deletions(-) rename storage/memory/stats_buffer.go => utils/timed_store.go (89%) rename storage/memory/stats_buffer_test.go => utils/timed_store_test.go (95%) diff --git a/storage/memory/memory.go b/storage/memory/memory.go index 7e7e7c94..a0a8f1cf 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -22,13 +22,14 @@ import ( "github.com/golang/glog" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/storage" + "github.com/google/cadvisor/utils" ) // TODO(vmarmol): See about refactoring this class, we have an unecessary redirection of containerStorage and InMemoryStorage. // containerStorage is used to store per-container information type containerStorage struct { ref info.ContainerReference - recentStats *StatsBuffer + recentStats *utils.TimedStore maxAge time.Duration lock sync.RWMutex } @@ -51,7 +52,7 @@ func (self *containerStorage) RecentStats(start, end time.Time, maxStats int) ([ func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *containerStorage { return &containerStorage{ ref: ref, - recentStats: NewStatsBuffer(maxAge), + recentStats: utils.NewTimedStore(maxAge), maxAge: maxAge, } } diff --git a/storage/memory/stats_buffer.go b/utils/timed_store.go similarity index 89% rename from storage/memory/stats_buffer.go rename to utils/timed_store.go index 4fee8911..8582a3e2 100644 --- a/storage/memory/stats_buffer.go +++ b/utils/timed_store.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package memory +package utils import ( "sort" @@ -22,21 +22,21 @@ import ( ) // A time-based buffer for ContainerStats. Holds information for a specific time period. -type StatsBuffer struct { +type TimedStore struct { buffer []*info.ContainerStats age time.Duration } -// Returns a new thread-compatible StatsBuffer. -func NewStatsBuffer(age time.Duration) *StatsBuffer { - return &StatsBuffer{ +// Returns a new thread-compatible TimedStore. +func NewTimedStore(age time.Duration) *TimedStore { + return &TimedStore{ buffer: make([]*info.ContainerStats, 0), age: age, } } // Adds an element to the start of the buffer (removing one from the end if necessary). -func (self *StatsBuffer) Add(item *info.ContainerStats) { +func (self *TimedStore) Add(item *info.ContainerStats) { // Remove any elements before the eviction time. evictTime := item.Timestamp.Add(-self.age) index := sort.Search(len(self.buffer), func(index int) bool { @@ -53,7 +53,7 @@ func (self *StatsBuffer) Add(item *info.ContainerStats) { // Returns up to maxResult elements in the specified time period (inclusive). // Results are from first to last. maxResults of -1 means no limit. When first // and last are specified, maxResults is ignored. -func (self *StatsBuffer) InTimeRange(start, end time.Time, maxResults int) []*info.ContainerStats { +func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*info.ContainerStats { // No stats, return empty. if len(self.buffer) == 0 { return []*info.ContainerStats{} @@ -117,10 +117,10 @@ func (self *StatsBuffer) InTimeRange(start, end time.Time, maxResults int) []*in } // Gets the element at the specified index. Note that elements are output in LIFO order. -func (self *StatsBuffer) Get(index int) *info.ContainerStats { +func (self *TimedStore) Get(index int) *info.ContainerStats { return self.buffer[len(self.buffer)-index-1] } -func (self *StatsBuffer) Size() int { +func (self *TimedStore) Size() int { return len(self.buffer) } diff --git a/storage/memory/stats_buffer_test.go b/utils/timed_store_test.go similarity index 95% rename from storage/memory/stats_buffer_test.go rename to utils/timed_store_test.go index 065e244f..2a7aa6fc 100644 --- a/storage/memory/stats_buffer_test.go +++ b/utils/timed_store_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package memory +package utils import ( "strconv" @@ -38,13 +38,13 @@ func createStats(id int32) *info.ContainerStats { } } -func expectSize(t *testing.T, sb *StatsBuffer, expectedSize int) { +func expectSize(t *testing.T, sb *TimedStore, expectedSize int) { if sb.Size() != expectedSize { t.Errorf("Expected size %v, got %v", expectedSize, sb.Size()) } } -func expectAllElements(t *testing.T, sb *StatsBuffer, expected []int32) { +func expectAllElements(t *testing.T, sb *TimedStore, expected []int32) { size := sb.Size() els := make([]*info.ContainerStats, size) for i := 0; i < size; i++ { @@ -81,7 +81,7 @@ func expectElement(t *testing.T, stat *info.ContainerStats, expected int32) { } func TestAdd(t *testing.T) { - sb := NewStatsBuffer(5 * time.Second) + sb := NewTimedStore(5 * time.Second) // Add 1. sb.Add(createStats(0)) @@ -110,7 +110,7 @@ func TestAdd(t *testing.T) { } func TestGet(t *testing.T) { - sb := NewStatsBuffer(5 * time.Second) + sb := NewTimedStore(5 * time.Second) sb.Add(createStats(1)) sb.Add(createStats(2)) sb.Add(createStats(3)) @@ -122,7 +122,7 @@ func TestGet(t *testing.T) { } func TestInTimeRange(t *testing.T) { - sb := NewStatsBuffer(5 * time.Second) + sb := NewTimedStore(5 * time.Second) assert := assert.New(t) var empty time.Time @@ -199,7 +199,7 @@ func TestInTimeRange(t *testing.T) { } func TestInTimeRangeWithLimit(t *testing.T) { - sb := NewStatsBuffer(5 * time.Second) + sb := NewTimedStore(5 * time.Second) sb.Add(createStats(1)) sb.Add(createStats(2)) sb.Add(createStats(3)) From 8d2f81e73f6be89c335b95b227c81de5bb5a2db2 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 23 Apr 2015 11:34:56 -0700 Subject: [PATCH 2/3] Make the TimedStore generic. Accepts a timestamp and an interface to store and use. --- utils/timed_store.go | 45 ++++++----- utils/timed_store_test.go | 157 ++++++++++++++++---------------------- 2 files changed, 94 insertions(+), 108 deletions(-) diff --git a/utils/timed_store.go b/utils/timed_store.go index 8582a3e2..153d8e32 100644 --- a/utils/timed_store.go +++ b/utils/timed_store.go @@ -17,46 +17,52 @@ package utils import ( "sort" "time" - - info "github.com/google/cadvisor/info/v1" ) // A time-based buffer for ContainerStats. Holds information for a specific time period. type TimedStore struct { - buffer []*info.ContainerStats + buffer []timedStoreData age time.Duration } +type timedStoreData struct { + timestamp time.Time + data interface{} +} + // Returns a new thread-compatible TimedStore. func NewTimedStore(age time.Duration) *TimedStore { return &TimedStore{ - buffer: make([]*info.ContainerStats, 0), + buffer: make([]timedStoreData, 0), age: age, } } // Adds an element to the start of the buffer (removing one from the end if necessary). -func (self *TimedStore) Add(item *info.ContainerStats) { +func (self *TimedStore) Add(timestamp time.Time, item interface{}) { // Remove any elements before the eviction time. - evictTime := item.Timestamp.Add(-self.age) + evictTime := timestamp.Add(-self.age) index := sort.Search(len(self.buffer), func(index int) bool { - return self.buffer[index].Timestamp.After(evictTime) + return self.buffer[index].timestamp.After(evictTime) }) if index < len(self.buffer) { self.buffer = self.buffer[index:] } - copied := *item - self.buffer = append(self.buffer, &copied) + copied := item + self.buffer = append(self.buffer, timedStoreData{ + timestamp: timestamp, + data: copied, + }) } // Returns up to maxResult elements in the specified time period (inclusive). // Results are from first to last. maxResults of -1 means no limit. When first // and last are specified, maxResults is ignored. -func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*info.ContainerStats { +func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []interface{} { // No stats, return empty. if len(self.buffer) == 0 { - return []*info.ContainerStats{} + return []interface{}{} } // Return all results in a time range if specified. @@ -77,11 +83,11 @@ func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*inf // before that element startIndex = sort.Search(len(self.buffer), func(index int) bool { // buffer[index] < start - return self.Get(index).Timestamp.Before(start) + return self.getData(index).timestamp.Before(start) }) - 1 // Check if start is after all the data we have. if startIndex < 0 { - return []*info.ContainerStats{} + return []interface{}{} } } @@ -93,11 +99,11 @@ func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*inf // End is the first index smaller than or equal to it (so, not larger). endIndex = sort.Search(len(self.buffer), func(index int) bool { // buffer[index] <= t -> !(buffer[index] > t) - return !self.Get(index).Timestamp.After(end) + return !self.getData(index).timestamp.After(end) }) // Check if end is before all the data we have. if endIndex == len(self.buffer) { - return []*info.ContainerStats{} + return []interface{}{} } } @@ -109,7 +115,7 @@ func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*inf } // Return in sorted timestamp order so from the "back" to "front". - result := make([]*info.ContainerStats, numResults) + result := make([]interface{}, numResults) for i := 0; i < numResults; i++ { result[i] = self.Get(startIndex - i) } @@ -117,7 +123,12 @@ func (self *TimedStore) InTimeRange(start, end time.Time, maxResults int) []*inf } // Gets the element at the specified index. Note that elements are output in LIFO order. -func (self *TimedStore) Get(index int) *info.ContainerStats { +func (self *TimedStore) Get(index int) interface{} { + return self.getData(index).data +} + +// Gets the data at the specified index. Note that elements are output in LIFO order. +func (self *TimedStore) getData(index int) timedStoreData { return self.buffer[len(self.buffer)-index-1] } diff --git a/utils/timed_store_test.go b/utils/timed_store_test.go index 2a7aa6fc..4ac114a7 100644 --- a/utils/timed_store_test.go +++ b/utils/timed_store_test.go @@ -15,12 +15,9 @@ package utils import ( - "strconv" - "strings" "testing" "time" - info "github.com/google/cadvisor/info/v1" "github.com/stretchr/testify/assert" ) @@ -29,96 +26,74 @@ func createTime(id int) time.Time { return zero.Add(time.Duration(id+1) * time.Second) } -func createStats(id int32) *info.ContainerStats { - return &info.ContainerStats{ - Timestamp: createTime(int(id)), - Cpu: info.CpuStats{ - LoadAverage: id, - }, - } -} - func expectSize(t *testing.T, sb *TimedStore, expectedSize int) { if sb.Size() != expectedSize { t.Errorf("Expected size %v, got %v", expectedSize, sb.Size()) } } -func expectAllElements(t *testing.T, sb *TimedStore, expected []int32) { +func expectAllElements(t *testing.T, sb *TimedStore, expected []int) { size := sb.Size() - els := make([]*info.ContainerStats, size) + els := make([]interface{}, size) for i := 0; i < size; i++ { els[i] = sb.Get(size - i - 1) } - expectElements(t, els, expected) + expectElements(t, []interface{}(els), expected) } -func getActualElements(actual []*info.ContainerStats) string { - actualElements := make([]string, len(actual)) - for i, element := range actual { - actualElements[i] = strconv.Itoa(int(element.Cpu.LoadAverage)) - } - return strings.Join(actualElements, ",") -} - -func expectElements(t *testing.T, actual []*info.ContainerStats, expected []int32) { +func expectElements(t *testing.T, actual []interface{}, expected []int) { if len(actual) != len(expected) { - t.Errorf("Expected elements %v, got %v", expected, getActualElements(actual)) + t.Errorf("Expected elements %v, got %v", expected, actual) return } for i, el := range actual { - if el.Cpu.LoadAverage != expected[i] { - t.Errorf("Expected elements %v, got %v", expected, getActualElements(actual)) + if el.(int) != expected[i] { + t.Errorf("Expected elements %v, got %v", expected, actual) return } } } -func expectElement(t *testing.T, stat *info.ContainerStats, expected int32) { - if stat.Cpu.LoadAverage != expected { - t.Errorf("Expected %d, but received %d", expected, stat.Cpu.LoadAverage) - } -} - func TestAdd(t *testing.T) { sb := NewTimedStore(5 * time.Second) // Add 1. - sb.Add(createStats(0)) + sb.Add(createTime(0), 0) expectSize(t, sb, 1) - expectAllElements(t, sb, []int32{0}) + expectAllElements(t, sb, []int{0}) // Fill the buffer. for i := 1; i <= 5; i++ { expectSize(t, sb, i) - sb.Add(createStats(int32(i))) + sb.Add(createTime(i), i) } expectSize(t, sb, 5) - expectAllElements(t, sb, []int32{1, 2, 3, 4, 5}) + expectAllElements(t, sb, []int{1, 2, 3, 4, 5}) // Add more than is available in the buffer - sb.Add(createStats(6)) + sb.Add(createTime(6), 6) expectSize(t, sb, 5) - expectAllElements(t, sb, []int32{2, 3, 4, 5, 6}) + expectAllElements(t, sb, []int{2, 3, 4, 5, 6}) // Replace all elements. for i := 7; i <= 10; i++ { - sb.Add(createStats(int32(i))) + sb.Add(createTime(i), i) } expectSize(t, sb, 5) - expectAllElements(t, sb, []int32{6, 7, 8, 9, 10}) + expectAllElements(t, sb, []int{6, 7, 8, 9, 10}) } func TestGet(t *testing.T) { sb := NewTimedStore(5 * time.Second) - sb.Add(createStats(1)) - sb.Add(createStats(2)) - sb.Add(createStats(3)) + sb.Add(createTime(1), 1) + sb.Add(createTime(2), 2) + sb.Add(createTime(3), 3) expectSize(t, sb, 3) - expectElement(t, sb.Get(0), 3) - expectElement(t, sb.Get(1), 2) - expectElement(t, sb.Get(2), 1) + assert := assert.New(t) + assert.Equal(sb.Get(0).(int), 3) + assert.Equal(sb.Get(1).(int), 2) + assert.Equal(sb.Get(2).(int), 1) } func TestInTimeRange(t *testing.T) { @@ -134,60 +109,60 @@ func TestInTimeRange(t *testing.T) { assert.Empty(sb.InTimeRange(empty, empty, 10)) // One element. - sb.Add(createStats(1)) + sb.Add(createTime(1), 1) expectSize(t, sb, 1) - expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int32{1}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int32{1}) - expectElements(t, sb.InTimeRange(createTime(0), createTime(1), 10), []int32{1}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int32{1}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(1), 10), []int{1}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int{1}) assert.Empty(sb.InTimeRange(createTime(2), createTime(5), 10)) // Two element. - sb.Add(createStats(2)) + sb.Add(createTime(2), 2) expectSize(t, sb, 2) - expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int32{1}) - expectElements(t, sb.InTimeRange(createTime(2), createTime(2), 10), []int32{2}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(1), 10), []int{1}) + expectElements(t, sb.InTimeRange(createTime(2), createTime(2), 10), []int{2}) assert.Empty(sb.InTimeRange(createTime(3), createTime(5), 10)) // Many elements. - sb.Add(createStats(3)) - sb.Add(createStats(4)) + sb.Add(createTime(3), 3) + sb.Add(createTime(4), 4) expectSize(t, sb, 4) - expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(0), createTime(4), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(4), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(createTime(2), createTime(3), 10), []int32{2, 3}) - expectElements(t, sb.InTimeRange(createTime(3), createTime(4), 10), []int32{3, 4}) - expectElements(t, sb.InTimeRange(createTime(3), createTime(5), 10), []int32{3, 4}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(5), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(4), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(4), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(0), createTime(2), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(2), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(createTime(2), createTime(3), 10), []int{2, 3}) + expectElements(t, sb.InTimeRange(createTime(3), createTime(4), 10), []int{3, 4}) + expectElements(t, sb.InTimeRange(createTime(3), createTime(5), 10), []int{3, 4}) assert.Empty(sb.InTimeRange(createTime(5), createTime(5), 10)) // Start and end time ignores maxResults. - expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 1), []int32{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(1), createTime(5), 1), []int{1, 2, 3, 4}) // No start time. - expectElements(t, sb.InTimeRange(empty, createTime(5), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(empty, createTime(4), 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(empty, createTime(3), 10), []int32{1, 2, 3}) - expectElements(t, sb.InTimeRange(empty, createTime(2), 10), []int32{1, 2}) - expectElements(t, sb.InTimeRange(empty, createTime(1), 10), []int32{1}) + expectElements(t, sb.InTimeRange(empty, createTime(5), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(empty, createTime(4), 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(empty, createTime(3), 10), []int{1, 2, 3}) + expectElements(t, sb.InTimeRange(empty, createTime(2), 10), []int{1, 2}) + expectElements(t, sb.InTimeRange(empty, createTime(1), 10), []int{1}) // No end time. - expectElements(t, sb.InTimeRange(createTime(0), empty, 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(1), empty, 10), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(2), empty, 10), []int32{2, 3, 4}) - expectElements(t, sb.InTimeRange(createTime(3), empty, 10), []int32{3, 4}) - expectElements(t, sb.InTimeRange(createTime(4), empty, 10), []int32{4}) + expectElements(t, sb.InTimeRange(createTime(0), empty, 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(1), empty, 10), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(2), empty, 10), []int{2, 3, 4}) + expectElements(t, sb.InTimeRange(createTime(3), empty, 10), []int{3, 4}) + expectElements(t, sb.InTimeRange(createTime(4), empty, 10), []int{4}) // No start or end time. - expectElements(t, sb.InTimeRange(empty, empty, 10), []int32{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(empty, empty, 10), []int{1, 2, 3, 4}) // Start after data. assert.Empty(sb.InTimeRange(createTime(5), createTime(5), 10)) @@ -200,18 +175,18 @@ func TestInTimeRange(t *testing.T) { func TestInTimeRangeWithLimit(t *testing.T) { sb := NewTimedStore(5 * time.Second) - sb.Add(createStats(1)) - sb.Add(createStats(2)) - sb.Add(createStats(3)) - sb.Add(createStats(4)) + sb.Add(createTime(1), 1) + sb.Add(createTime(2), 2) + sb.Add(createTime(3), 3) + sb.Add(createTime(4), 4) expectSize(t, sb, 4) var empty time.Time // Limit cuts off from latest timestamp. - expectElements(t, sb.InTimeRange(empty, empty, 4), []int32{1, 2, 3, 4}) - expectElements(t, sb.InTimeRange(empty, empty, 3), []int32{2, 3, 4}) - expectElements(t, sb.InTimeRange(empty, empty, 2), []int32{3, 4}) - expectElements(t, sb.InTimeRange(empty, empty, 1), []int32{4}) + expectElements(t, sb.InTimeRange(empty, empty, 4), []int{1, 2, 3, 4}) + expectElements(t, sb.InTimeRange(empty, empty, 3), []int{2, 3, 4}) + expectElements(t, sb.InTimeRange(empty, empty, 2), []int{3, 4}) + expectElements(t, sb.InTimeRange(empty, empty, 1), []int{4}) assert.Empty(t, sb.InTimeRange(empty, empty, 0)) } From c72445a6984ff0c126abd13393a35bf069658d6b Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 23 Apr 2015 11:43:32 -0700 Subject: [PATCH 3/3] Use generic TimedStore for stats storage --- storage/memory/memory.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/storage/memory/memory.go b/storage/memory/memory.go index a0a8f1cf..5a53c17d 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -39,14 +39,19 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error { defer self.lock.Unlock() // Add the stat to storage. - self.recentStats.Add(stats) + self.recentStats.Add(stats.Timestamp, stats) return nil } func (self *containerStorage) RecentStats(start, end time.Time, maxStats int) ([]*info.ContainerStats, error) { self.lock.RLock() defer self.lock.RUnlock() - return self.recentStats.InTimeRange(start, end, maxStats), nil + result := self.recentStats.InTimeRange(start, end, maxStats) + converted := make([]*info.ContainerStats, len(result)) + for i, el := range result { + converted[i] = el.(*info.ContainerStats) + } + return converted, nil } func newContainerStore(ref info.ContainerReference, maxAge time.Duration) *containerStorage {