From 4d0b365d432d9676984bd52f7019bf5ef1b70bea Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Mon, 7 Jul 2014 22:04:30 -0700 Subject: [PATCH] let users decide how many stats/samples they want to retrieve --- api/handler.go | 22 ++- cadvisor.go | 2 +- info/container.go | 28 ++++ info/test/datagen.go | 1 + manager/manager.go | 16 +- manager/manager_test.go | 337 ++++++++++++++++++++++++++++++++++++++++ pages/containers.go | 2 +- 7 files changed, 392 insertions(+), 16 deletions(-) create mode 100644 manager/manager_test.go diff --git a/api/handler.go b/api/handler.go index 409dc917..0740354a 100644 --- a/api/handler.go +++ b/api/handler.go @@ -21,10 +21,10 @@ import ( "fmt" "log" "net/http" - "net/url" "strings" "time" + "github.com/google/cadvisor/info" "github.com/google/cadvisor/manager" ) @@ -34,9 +34,11 @@ const ( MachineApi = "machine" ) -func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { +func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) error { start := time.Now() + u := r.URL + // Get API request type. requestType := u.Path[len(ApiResource):] i := strings.Index(requestType, "/") @@ -46,7 +48,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { requestType = requestType[:i] } - if requestType == MachineApi { + switch { + case requestType == MachineApi: log.Printf("Api - Machine") // Get the MachineInfo @@ -60,14 +63,21 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err) } w.Write(out) - } else if requestType == ContainersApi { + case requestType == ContainersApi: // The container name is the path after the requestType containerName := requestArgs log.Printf("Api - Container(%s)", containerName) + var query info.ContainerInfoQuery + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&query) + if err != nil { + fmt.Fprintf(w, "unable to decode the json value: %v", err) + return err + } // Get the container. - cont, err := m.GetContainerInfo(containerName) + cont, err := m.GetContainerInfo(containerName, &query) if err != nil { fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err) return err @@ -79,7 +89,7 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err) } w.Write(out) - } else { + default: return fmt.Errorf("unknown API request type %q", requestType) } diff --git a/cadvisor.go b/cadvisor.go index 8fe7a7a8..72c9e57b 100644 --- a/cadvisor.go +++ b/cadvisor.go @@ -79,7 +79,7 @@ func main() { // Handler for the API. http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) { - err := api.HandleRequest(containerManager, w, r.URL) + err := api.HandleRequest(containerManager, w, r) if err != nil { fmt.Fprintf(w, "%s", err) } diff --git a/info/container.go b/info/container.go index 6e987916..bf8a15f6 100644 --- a/info/container.go +++ b/info/container.go @@ -57,6 +57,34 @@ type ContainerReference struct { Aliases []string `json:"aliases,omitempty"` } +type ContainerInfoQuery struct { + NumStats int `json:"num_stats,omitempty"` + NumSamples int `json:"num_samples,omitempty"` + + CpuUsagePercentages []int `json:"cpu_usage_percentages,omitempty"` + MemoryUsagePercentages []int `json:"memory_usage_percentages,omitempty"` +} + +func (self *ContainerInfoQuery) FillWithDefaultValues() *ContainerInfoQuery { + ret := self + if ret == nil { + ret = new(ContainerInfoQuery) + } + if ret.NumStats <= 0 { + ret.NumStats = 1024 + } + if ret.NumSamples <= 0 { + ret.NumSamples = 1024 + } + if len(ret.CpuUsagePercentages) == 0 { + ret.CpuUsagePercentages = []int{50, 80, 90, 99} + } + if len(ret.MemoryUsagePercentages) == 0 { + ret.MemoryUsagePercentages = []int{50, 80, 90, 99} + } + return ret +} + type ContainerInfo struct { ContainerReference diff --git a/info/test/datagen.go b/info/test/datagen.go index 36841774..cb55ec45 100644 --- a/info/test/datagen.go +++ b/info/test/datagen.go @@ -46,6 +46,7 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info stats.Cpu.Usage.User = stats.Cpu.Usage.Total stats.Cpu.Usage.System = 0 stats.Memory.Usage = uint64(rand.Int63n(4096)) + ret[i] = stats } return ret } diff --git a/manager/manager.go b/manager/manager.go index 1b050355..07f09225 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -30,7 +30,7 @@ type Manager interface { Start() error // Get information about a container. - GetContainerInfo(containerName string) (*info.ContainerInfo, error) + GetContainerInfo(containerName string, query *info.ContainerInfoQuery) (*info.ContainerInfo, error) // Get information about the machine. GetMachineInfo() (*info.MachineInfo, error) @@ -106,8 +106,8 @@ func (m *manager) Start() error { } // Get a container by name. -func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) { - log.Printf("Get(%s)", containerName) +func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoQuery) (*info.ContainerInfo, error) { + log.Printf("Get(%s); %+v", containerName, query) var cont *containerData var ok bool func() { @@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e var percentiles *info.ContainerStatsPercentiles var samples []*info.ContainerStatsSample var stats []*info.ContainerStats - // TODO(monnand): These numbers should not be hard coded + query = query.FillWithDefaultValues() percentiles, err = m.storageDriver.Percentiles( cinfo.Name, - []int{50, 80, 90, 99}, - []int{50, 80, 90, 99}, + query.CpuUsagePercentages, + query.MemoryUsagePercentages, ) if err != nil { return nil, err } - samples, err = m.storageDriver.Samples(cinfo.Name, 1024) + samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples) if err != nil { return nil, err } - stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024) + stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats) if err != nil { return nil, err } diff --git a/manager/manager_test.go b/manager/manager_test.go new file mode 100644 index 00000000..1d241d24 --- /dev/null +++ b/manager/manager_test.go @@ -0,0 +1,337 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Per-container manager. + +package manager + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + "time" + + "github.com/google/cadvisor/container" + ctest "github.com/google/cadvisor/container/test" + "github.com/google/cadvisor/info" + itest "github.com/google/cadvisor/info/test" + stest "github.com/google/cadvisor/storage/test" +) + +func createManagerAndAddContainers( + driver *stest.MockStorageDriver, + containers []string, + f func(*ctest.MockContainerHandler), + t *testing.T, +) *manager { + if driver == nil { + driver = &stest.MockStorageDriver{} + } + factory := &ctest.FactoryForMockContainerHandler{ + Name: "factoryForManager", + PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { + handler.Name = name + found := false + for _, c := range containers { + if c == name { + found = true + } + } + if !found { + t.Errorf("Asked to create a container with name %v, which is unknown.", name) + } + f(handler) + }, + } + container.RegisterContainerHandlerFactory("/", factory) + mif, err := New(driver) + if err != nil { + t.Fatal(err) + } + if ret, ok := mif.(*manager); ok { + for _, container := range containers { + ret.containers[container], err = NewContainerData(container, driver) + if err != nil { + t.Fatal(err) + } + } + return ret + } + t.Fatal("Wrong type") + return nil +} + +func randomStatsAndSamples(numStats, numSamples int) ([]*info.ContainerStats, []*info.ContainerStatsSample, error) { + stats := itest.GenerateRandomStats(numStats, 4, 1*time.Second) + samples := make([]*info.ContainerStatsSample, 0, numSamples) + for i, s := range stats[1:] { + prev := stats[i] + sample, err := info.NewSample(prev, s) + if err != nil { + return nil, nil, fmt.Errorf("Unable to generate sample from %+v and %+v: %v", + prev, s, err) + } + samples = append(samples, sample) + if len(samples) == numSamples { + break + } + } + return stats, samples, nil +} + +func TestGetContainerInfo(t *testing.T) { + containers := []string{ + "/c1", + "/c2", + } + + query := &info.ContainerInfoQuery{ + NumStats: 256, + NumSamples: 128, + CpuUsagePercentages: []int{10, 50, 90}, + MemoryUsagePercentages: []int{10, 80, 90}, + } + + infosMap := make(map[string]*info.ContainerInfo, len(containers)) + handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers)) + + for _, container := range containers { + stats, samples, err := randomStatsAndSamples(query.NumStats, query.NumSamples) + if err != nil { + t.Fatal(err) + } + + cpuPercentiles := make([]info.Percentile, 0, len(query.CpuUsagePercentages)) + for _, p := range query.CpuUsagePercentages { + percentile := info.Percentile{p, uint64(rand.Int63n(1000))} + cpuPercentiles = append(cpuPercentiles, percentile) + } + memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages)) + for _, p := range query.MemoryUsagePercentages { + percentile := info.Percentile{p, uint64(rand.Int63n(1000))} + memPercentiles = append(memPercentiles, percentile) + } + + percentiles := &info.ContainerStatsPercentiles{ + MaxMemoryUsage: uint64(rand.Int63n(4096)), + MemoryUsagePercentiles: memPercentiles, + CpuUsagePercentiles: cpuPercentiles, + } + + spec := itest.GenerateRandomContainerSpec(4) + + infosMap[container] = &info.ContainerInfo{ + ContainerReference: info.ContainerReference{ + Name: container, + }, + Spec: spec, + StatsPercentiles: percentiles, + Samples: samples, + Stats: stats, + } + } + + driver := &stest.MockStorageDriver{} + m := createManagerAndAddContainers( + driver, + containers, + func(h *ctest.MockContainerHandler) { + cinfo := infosMap[h.Name] + stats := cinfo.Stats + samples := cinfo.Samples + percentiles := cinfo.StatsPercentiles + spec := cinfo.Spec + driver.On( + "Percentiles", + h.Name, + query.CpuUsagePercentages, + query.MemoryUsagePercentages, + ).Return( + percentiles, + nil, + ) + + driver.On( + "Samples", + h.Name, + query.NumSamples, + ).Return( + samples, + nil, + ) + + driver.On( + "RecentStats", + h.Name, + query.NumStats, + ).Return( + stats, + nil, + ) + + h.On("ListContainers", container.LIST_SELF).Return( + []info.ContainerReference(nil), + nil, + ) + h.On("GetSpec").Return( + spec, + nil, + ) + handlerMap[h.Name] = h + }, + t, + ) + + returnedInfos := make(map[string]*info.ContainerInfo, len(containers)) + + for _, container := range containers { + cinfo, err := m.GetContainerInfo(container, query) + if err != nil { + t.Fatalf("Unable to get info for container %v: %v", container, err) + } + returnedInfos[container] = cinfo + } + + for container, handler := range handlerMap { + handler.AssertExpectations(t) + returned := returnedInfos[container] + expected := infosMap[container] + if !reflect.DeepEqual(returned, expected) { + t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected) + } + } + +} + +func TestGetContainerInfoWithDefaultValue(t *testing.T) { + containers := []string{ + "/c1", + "/c2", + } + + var query *info.ContainerInfoQuery + query = query.FillWithDefaultValues() + + infosMap := make(map[string]*info.ContainerInfo, len(containers)) + handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers)) + + for _, container := range containers { + stats, samples, err := randomStatsAndSamples(query.NumStats, query.NumSamples) + if err != nil { + t.Fatal(err) + } + + cpuPercentiles := make([]info.Percentile, 0, len(query.CpuUsagePercentages)) + for _, p := range query.CpuUsagePercentages { + percentile := info.Percentile{p, uint64(rand.Int63n(1000))} + cpuPercentiles = append(cpuPercentiles, percentile) + } + memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages)) + for _, p := range query.MemoryUsagePercentages { + percentile := info.Percentile{p, uint64(rand.Int63n(1000))} + memPercentiles = append(memPercentiles, percentile) + } + + percentiles := &info.ContainerStatsPercentiles{ + MaxMemoryUsage: uint64(rand.Int63n(4096)), + MemoryUsagePercentiles: memPercentiles, + CpuUsagePercentiles: cpuPercentiles, + } + + spec := itest.GenerateRandomContainerSpec(4) + + infosMap[container] = &info.ContainerInfo{ + ContainerReference: info.ContainerReference{ + Name: container, + }, + Spec: spec, + StatsPercentiles: percentiles, + Samples: samples, + Stats: stats, + } + } + + driver := &stest.MockStorageDriver{} + m := createManagerAndAddContainers( + driver, + containers, + func(h *ctest.MockContainerHandler) { + cinfo := infosMap[h.Name] + stats := cinfo.Stats + samples := cinfo.Samples + percentiles := cinfo.StatsPercentiles + spec := cinfo.Spec + driver.On( + "Percentiles", + h.Name, + query.CpuUsagePercentages, + query.MemoryUsagePercentages, + ).Return( + percentiles, + nil, + ) + + driver.On( + "Samples", + h.Name, + query.NumSamples, + ).Return( + samples, + nil, + ) + + driver.On( + "RecentStats", + h.Name, + query.NumStats, + ).Return( + stats, + nil, + ) + + h.On("ListContainers", container.LIST_SELF).Return( + []info.ContainerReference(nil), + nil, + ) + h.On("GetSpec").Return( + spec, + nil, + ) + handlerMap[h.Name] = h + }, + t, + ) + + returnedInfos := make(map[string]*info.ContainerInfo, len(containers)) + + for _, container := range containers { + // nil should give us default values + cinfo, err := m.GetContainerInfo(container, nil) + if err != nil { + t.Fatalf("Unable to get info for container %v: %v", container, err) + } + returnedInfos[container] = cinfo + } + + for container, handler := range handlerMap { + handler.AssertExpectations(t) + returned := returnedInfos[container] + expected := infosMap[container] + if !reflect.DeepEqual(returned, expected) { + t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected) + } + } + +} diff --git a/pages/containers.go b/pages/containers.go index 58605a8b..d229775f 100644 --- a/pages/containers.go +++ b/pages/containers.go @@ -162,7 +162,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) containerName := u.Path[len(ContainersPage)-1:] // Get the container. - cont, err := m.GetContainerInfo(containerName) + cont, err := m.GetContainerInfo(containerName, nil) if err != nil { return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err) }