Add Start and End to ContainerInfoRequests.

This will allow querying a certain time range.
This commit is contained in:
Victor Marmol 2015-02-22 19:07:40 -08:00
parent 38203d1d49
commit 63f5714db8
5 changed files with 48 additions and 28 deletions

View File

@ -44,16 +44,19 @@ func testGetJsonData(
return nil return nil
} }
func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) { func cadvisorTestClient(path string, expectedPostObj *info.ContainerInfoRequest, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path { if r.URL.Path == path {
if expectedPostObj != nil { if expectedPostObj != nil {
expectedPostObjEmpty := new(info.ContainerInfoRequest)
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(expectedPostObjEmpty); err != nil { if err := decoder.Decode(expectedPostObjEmpty); err != nil {
t.Errorf("Received invalid object: %v", err) t.Errorf("Received invalid object: %v", err)
} }
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) { if expectedPostObj.NumStats != expectedPostObjEmpty.NumStats ||
t.Errorf("Received unexpected object: %+v", expectedPostObjEmpty) expectedPostObj.Start.Unix() != expectedPostObjEmpty.Start.Unix() ||
expectedPostObj.End.Unix() != expectedPostObjEmpty.End.Unix() {
t.Errorf("Received unexpected object: %+v, expected: %+v", expectedPostObjEmpty, expectedPostObj)
} }
} }
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
@ -88,7 +91,7 @@ func TestGetMachineinfo(t *testing.T) {
}, },
}, },
} }
client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, nil, minfo, t) client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, minfo, t)
if err != nil { if err != nil {
t.Fatalf("unable to get a client %v", err) t.Fatalf("unable to get a client %v", err)
} }
@ -110,7 +113,7 @@ func TestGetContainerInfo(t *testing.T) {
} }
containerName := "/some/container" containerName := "/some/container"
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second)
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t) client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/containers%v", containerName), query, cinfo, t)
if err != nil { if err != nil {
t.Fatalf("unable to get a client %v", err) t.Fatalf("unable to get a client %v", err)
} }
@ -125,7 +128,7 @@ func TestGetContainerInfo(t *testing.T) {
} }
} }
// Test a requesy failing // Test a request failing
func TestRequestFails(t *testing.T) { func TestRequestFails(t *testing.T) {
errorText := "there was an error" errorText := "there was an error"
// Setup a server that simply fails. // Setup a server that simply fails.
@ -162,7 +165,7 @@ func TestGetSubcontainersInfo(t *testing.T) {
*cinfo1, *cinfo1,
*cinfo2, *cinfo2,
} }
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/subcontainers%v", containerName), query, &info.ContainerInfoRequest{}, response, t) client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/subcontainers%v", containerName), query, response, t)
if err != nil { if err != nil {
t.Fatalf("unable to get a client %v", err) t.Fatalf("unable to get a client %v", err)
} }

View File

@ -76,6 +76,14 @@ type ContainerReference struct {
type ContainerInfoRequest struct { type ContainerInfoRequest struct {
// Max number of stats to return. // Max number of stats to return.
NumStats int `json:"num_stats,omitempty"` NumStats int `json:"num_stats,omitempty"`
// Start time for which to query information.
// If ommitted, the beginning of time is assumed.
Start time.Time `json:"start,omitempty"`
// End time for which to query information.
// If ommitted, current time is assumed.
End time.Time `json:"end,omitempty"`
} }
type ContainerInfo struct { type ContainerInfo struct {

View File

@ -25,7 +25,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/summary" "github.com/google/cadvisor/summary"
"github.com/google/cadvisor/utils/cpuload" "github.com/google/cadvisor/utils/cpuload"
) )
@ -47,7 +47,7 @@ type containerInfo struct {
type containerData struct { type containerData struct {
handler container.ContainerHandler handler container.ContainerHandler
info containerInfo info containerInfo
storageDriver storage.StorageDriver memoryStorage *memory.InMemoryStorage
lock sync.Mutex lock sync.Mutex
loadReader cpuload.CpuLoadReader loadReader cpuload.CpuLoadReader
summaryReader *summary.StatsSummary summaryReader *summary.StatsSummary
@ -107,9 +107,9 @@ func (c *containerData) DerivedStats() (info.DerivedStats, error) {
return c.summaryReader.DerivedStats() return c.summaryReader.DerivedStats()
} }
func newContainerData(containerName string, driver storage.StorageDriver, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool) (*containerData, error) { func newContainerData(containerName string, memoryStorage *memory.InMemoryStorage, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool) (*containerData, error) {
if driver == nil { if memoryStorage == nil {
return nil, fmt.Errorf("nil storage driver") return nil, fmt.Errorf("nil memory storage")
} }
if handler == nil { if handler == nil {
return nil, fmt.Errorf("nil container handler") return nil, fmt.Errorf("nil container handler")
@ -121,7 +121,7 @@ func newContainerData(containerName string, driver storage.StorageDriver, handle
cont := &containerData{ cont := &containerData{
handler: handler, handler: handler,
storageDriver: driver, memoryStorage: memoryStorage,
housekeepingInterval: *HousekeepingInterval, housekeepingInterval: *HousekeepingInterval,
loadReader: loadReader, loadReader: loadReader,
logUsage: logUsage, logUsage: logUsage,
@ -145,7 +145,8 @@ func newContainerData(containerName string, driver storage.StorageDriver, handle
// Determine when the next housekeeping should occur. // Determine when the next housekeeping should occur.
func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time { func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time {
if *allowDynamicHousekeeping { if *allowDynamicHousekeeping {
stats, err := self.storageDriver.RecentStats(self.info.Name, 2) var empty time.Time
stats, err := self.memoryStorage.RecentStats(self.info.Name, empty, empty, 2)
if err != nil { if err != nil {
if self.allowErrorLogging() { if self.allowErrorLogging() {
glog.Warningf("Failed to get RecentStats(%q) while determining the next housekeeping: %v", self.info.Name, err) glog.Warningf("Failed to get RecentStats(%q) while determining the next housekeeping: %v", self.info.Name, err)
@ -200,7 +201,8 @@ func (c *containerData) housekeeping() {
// Log usage if asked to do so. // Log usage if asked to do so.
if c.logUsage { if c.logUsage {
const numSamples = 60 const numSamples = 60
stats, err := c.storageDriver.RecentStats(c.info.Name, numSamples) var empty time.Time
stats, err := c.memoryStorage.RecentStats(c.info.Name, empty, empty, numSamples)
if err != nil { if err != nil {
if c.allowErrorLogging() { if c.allowErrorLogging() {
glog.Infof("[%s] Failed to get recent stats for logging usage: %v", c.info.Name, err) glog.Infof("[%s] Failed to get recent stats for logging usage: %v", c.info.Name, err)
@ -311,7 +313,7 @@ func (c *containerData) updateStats() error {
} }
return err return err
} }
err = c.storageDriver.AddStats(ref, stats) err = c.memoryStorage.AddStats(ref, stats)
if err != nil { if err != nil {
return err return err
} }

View File

@ -25,32 +25,33 @@ import (
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test" itest "github.com/google/cadvisor/info/test"
stest "github.com/google/cadvisor/storage/test" "github.com/google/cadvisor/storage/memory"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
const containerName = "/container" const containerName = "/container"
// Create a containerData instance for a test. // Create a containerData instance for a test.
func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *container.MockContainerHandler, *stest.MockStorageDriver) { func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData, *container.MockContainerHandler, *memory.InMemoryStorage) {
mockHandler := container.NewMockContainerHandler(containerName) mockHandler := container.NewMockContainerHandler(containerName)
mockHandler.On("GetSpec").Return( mockHandler.On("GetSpec").Return(
spec, spec,
nil, nil,
) )
mockDriver := &stest.MockStorageDriver{} memoryStorage := memory.New(60, nil)
ret, err := newContainerData(containerName, mockDriver, mockHandler, nil, false) ret, err := newContainerData(containerName, memoryStorage, mockHandler, nil, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return ret, mockHandler, mockDriver return ret, mockHandler, memoryStorage
} }
// Create a containerData instance for a test and add a default GetSpec mock. // Create a containerData instance for a test and add a default GetSpec mock.
func newTestContainerData(t *testing.T) (*containerData, *container.MockContainerHandler, *stest.MockStorageDriver) { func newTestContainerData(t *testing.T) (*containerData, *container.MockContainerHandler, *memory.InMemoryStorage) {
spec := itest.GenerateRandomContainerSpec(4) spec := itest.GenerateRandomContainerSpec(4)
ret, mockHandler, mockDriver := setupContainerData(t, spec) ret, mockHandler, memoryStorage := setupContainerData(t, spec)
return ret, mockHandler, mockDriver return ret, mockHandler, memoryStorage
} }
func TestUpdateSubcontainers(t *testing.T) { func TestUpdateSubcontainers(t *testing.T) {
@ -114,23 +115,29 @@ func TestUpdateSubcontainersWithErrorOnDeadContainer(t *testing.T) {
mockHandler.AssertExpectations(t) mockHandler.AssertExpectations(t)
} }
func checkNumStats(t *testing.T, memoryStorage *memory.InMemoryStorage, numStats int) {
var empty time.Time
stats, err := memoryStorage.RecentStats(containerName, empty, empty, -1)
require.Nil(t, err)
assert.Len(t, stats, numStats)
}
func TestUpdateStats(t *testing.T) { func TestUpdateStats(t *testing.T) {
statsList := itest.GenerateRandomStats(1, 4, 1*time.Second) statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
stats := statsList[0] stats := statsList[0]
cd, mockHandler, mockDriver := newTestContainerData(t) cd, mockHandler, memoryStorage := newTestContainerData(t)
mockHandler.On("GetStats").Return( mockHandler.On("GetStats").Return(
stats, stats,
nil, nil,
) )
mockDriver.On("AddStats", info.ContainerReference{Name: mockHandler.Name}, stats).Return(nil)
err := cd.updateStats() err := cd.updateStats()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
checkNumStats(t, memoryStorage, 1)
mockHandler.AssertExpectations(t) mockHandler.AssertExpectations(t)
} }

View File

@ -288,7 +288,7 @@ func (self *manager) containerDataToContainerInfo(cont *containerData, query *in
return nil, err return nil, err
} }
stats, err := self.memoryStorage.RecentStats(cinfo.Name, query.NumStats) stats, err := self.memoryStorage.RecentStats(cinfo.Name, query.Start, query.End, query.NumStats)
if err != nil { if err != nil {
return nil, err return nil, err
} }