Commit c7d31fab authored by Daniel Smith's avatar Daniel Smith

Merge pull request #480 from monnand/cadvisor-update-1

Update kubelet package to use most recent cAdvisor's code
parents e748a4b5 30bc2af9
...@@ -147,20 +147,6 @@ type Container struct { ...@@ -147,20 +147,6 @@ type Container struct {
LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
} }
// Percentile represents a pair which contains a percentage from 0 to 100 and
// its corresponding value.
type Percentile struct {
Percentage int `json:"percentage,omitempty"`
Value uint64 `json:"value,omitempty"`
}
// ContainerStats represents statistical information of a container
type ContainerStats struct {
CpuUsagePercentiles []Percentile `json:"cpu_usage_percentiles,omitempty"`
MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"`
MaxMemoryUsage uint64 `json:"max_memory_usage,omitempty"`
}
// Event is the representation of an event logged to etcd backends // Event is the representation of an event logged to etcd backends
type Event struct { type Event struct {
Event string `json:"event,omitempty"` Event string `json:"event,omitempty"`
......
...@@ -54,7 +54,7 @@ const milliCpuToCpu = 1000 ...@@ -54,7 +54,7 @@ const milliCpuToCpu = 1000
// CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client. // CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client.
type CadvisorInterface interface { type CadvisorInterface interface {
ContainerInfo(name string) (*info.ContainerInfo, error) ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
MachineInfo() (*info.MachineInfo, error) MachineInfo() (*info.MachineInfo, error)
} }
...@@ -844,44 +844,29 @@ func (kl *Kubelet) getDockerIDFromPodIDAndContainerName(podID, containerName str ...@@ -844,44 +844,29 @@ func (kl *Kubelet) getDockerIDFromPodIDAndContainerName(podID, containerName str
return "", errors.New("couldn't find container") return "", errors.New("couldn't find container")
} }
func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.ContainerInfoRequest {
ret := &info.ContainerInfoRequest{
NumStats: req.NumStats,
CpuUsagePercentiles: req.CpuUsagePercentiles,
MemoryUsagePercentages: req.MemoryUsagePercentages,
}
return ret
}
// This method takes a container's absolute path and returns the stats for the // This method takes a container's absolute path and returns the stats for the
// container. The container's absolute path refers to its hierarchy in the // container. The container's absolute path refers to its hierarchy in the
// cgroup file system. e.g. The root container, which represents the whole // cgroup file system. e.g. The root container, which represents the whole
// machine, has path "/"; all docker containers have path "/docker/<docker id>" // machine, has path "/"; all docker containers have path "/docker/<docker id>"
func (kl *Kubelet) statsFromContainerPath(containerPath string) (*api.ContainerStats, error) { func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
info, err := kl.CadvisorClient.ContainerInfo(containerPath) cinfo, err := kl.CadvisorClient.ContainerInfo(containerPath, getCadvisorContainerInfoRequest(req))
if err != nil { if err != nil {
return nil, err return nil, err
} }
// When the stats data for the container is not available yet. return cinfo, nil
if info.StatsPercentiles == nil {
return nil, nil
}
ret := new(api.ContainerStats)
ret.MaxMemoryUsage = info.StatsPercentiles.MaxMemoryUsage
if len(info.StatsPercentiles.CpuUsagePercentiles) > 0 {
percentiles := make([]api.Percentile, len(info.StatsPercentiles.CpuUsagePercentiles))
for i, p := range info.StatsPercentiles.CpuUsagePercentiles {
percentiles[i].Percentage = p.Percentage
percentiles[i].Value = p.Value
}
ret.CpuUsagePercentiles = percentiles
}
if len(info.StatsPercentiles.MemoryUsagePercentiles) > 0 {
percentiles := make([]api.Percentile, len(info.StatsPercentiles.MemoryUsagePercentiles))
for i, p := range info.StatsPercentiles.MemoryUsagePercentiles {
percentiles[i].Percentage = p.Percentage
percentiles[i].Value = p.Value
}
ret.MemoryUsagePercentiles = percentiles
}
return ret, nil
} }
// GetContainerStats returns stats (from Cadvisor) for a container. // GetContainerStats returns stats (from Cadvisor) for a container.
func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) { func (kl *Kubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
if kl.CadvisorClient == nil { if kl.CadvisorClient == nil {
return nil, nil return nil, nil
} }
...@@ -889,12 +874,12 @@ func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.Containe ...@@ -889,12 +874,12 @@ func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.Containe
if err != nil || len(dockerID) == 0 { if err != nil || len(dockerID) == 0 {
return nil, err return nil, err
} }
return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", string(dockerID))) return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", string(dockerID)), req)
} }
// GetMachineStats returns stats (from Cadvisor) of current machine. // GetMachineStats returns stats (from Cadvisor) of current machine.
func (kl *Kubelet) GetMachineStats() (*api.ContainerStats, error) { func (kl *Kubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return kl.statsFromContainerPath("/") return kl.statsFromContainerPath("/", req)
} }
func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) { func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) {
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
...@@ -28,6 +29,7 @@ import ( ...@@ -28,6 +29,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/google/cadvisor/info"
"gopkg.in/v1/yaml" "gopkg.in/v1/yaml"
) )
...@@ -41,8 +43,8 @@ type KubeletServer struct { ...@@ -41,8 +43,8 @@ type KubeletServer struct {
// kubeletInterface contains all the kubelet methods required by the server. // kubeletInterface contains all the kubelet methods required by the server.
// For testablitiy. // For testablitiy.
type kubeletInterface interface { type kubeletInterface interface {
GetContainerStats(podID, containerName string) (*api.ContainerStats, error) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
GetMachineStats() (*api.ContainerStats, error) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
GetPodInfo(name string) (api.PodInfo, error) GetPodInfo(name string) (api.PodInfo, error)
} }
...@@ -113,18 +115,25 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -113,18 +115,25 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (s *KubeletServer) serveStats(w http.ResponseWriter, req *http.Request) { func (s *KubeletServer) serveStats(w http.ResponseWriter, req *http.Request) {
// /stats/<podid>/<containerName> // /stats/<podid>/<containerName>
components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/") components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/")
var stats *api.ContainerStats var stats *info.ContainerInfo
var err error var err error
var query info.ContainerInfoRequest
decoder := json.NewDecoder(req.Body)
err = decoder.Decode(&query)
if err != nil && err != io.EOF {
s.error(w, err)
return
}
switch len(components) { switch len(components) {
case 1: case 1:
// Machine stats // Machine stats
stats, err = s.Kubelet.GetMachineStats() stats, err = s.Kubelet.GetMachineStats(&query)
case 2: case 2:
// pod stats // pod stats
// TODO(monnand) Implement this // TODO(monnand) Implement this
errors.New("pod level status currently unimplemented") errors.New("pod level status currently unimplemented")
case 3: case 3:
stats, err = s.Kubelet.GetContainerStats(components[1], components[2]) stats, err = s.Kubelet.GetContainerInfo(components[1], components[2], &query)
default: default:
http.Error(w, "unknown resource.", http.StatusNotFound) http.Error(w, "unknown resource.", http.StatusNotFound)
return return
......
...@@ -29,24 +29,25 @@ import ( ...@@ -29,24 +29,25 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
"github.com/google/cadvisor/info"
) )
type fakeKubelet struct { type fakeKubelet struct {
infoFunc func(name string) (api.PodInfo, error) infoFunc func(name string) (api.PodInfo, error)
containerStatsFunc func(podID, containerName string) (*api.ContainerStats, error) containerStatsFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
machineStatsFunc func() (*api.ContainerStats, error) machineStatsFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
} }
func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) { func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
return fk.infoFunc(name) return fk.infoFunc(name)
} }
func (fk *fakeKubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) { func (fk *fakeKubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return fk.containerStatsFunc(podID, containerName) return fk.containerStatsFunc(podID, containerName, req)
} }
func (fk *fakeKubelet) GetMachineStats() (*api.ContainerStats, error) { func (fk *fakeKubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return fk.machineStatsFunc() return fk.machineStatsFunc(req)
} }
type serverTestFramework struct { type serverTestFramework struct {
...@@ -148,22 +149,24 @@ func TestPodInfo(t *testing.T) { ...@@ -148,22 +149,24 @@ func TestPodInfo(t *testing.T) {
func TestContainerStats(t *testing.T) { func TestContainerStats(t *testing.T) {
fw := makeServerTest() fw := makeServerTest()
expectedStats := &api.ContainerStats{ expectedStats := &info.ContainerInfo{
MaxMemoryUsage: 1024001, StatsPercentiles: &info.ContainerStatsPercentiles{
CpuUsagePercentiles: []api.Percentile{ MaxMemoryUsage: 1024001,
{50, 150}, CpuUsagePercentiles: []info.Percentile{
{80, 180}, {50, 150},
{90, 190}, {80, 180},
}, {90, 190},
MemoryUsagePercentiles: []api.Percentile{ },
{50, 150}, MemoryUsagePercentiles: []info.Percentile{
{80, 180}, {50, 150},
{90, 190}, {80, 180},
{90, 190},
},
}, },
} }
expectedPodID := "somepod" expectedPodID := "somepod"
expectedContainerName := "goodcontainer" expectedContainerName := "goodcontainer"
fw.fakeKubelet.containerStatsFunc = func(podID, containerName string) (*api.ContainerStats, error) { fw.fakeKubelet.containerStatsFunc = func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
if podID != expectedPodID || containerName != expectedContainerName { if podID != expectedPodID || containerName != expectedContainerName {
return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName) return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName)
} }
...@@ -175,7 +178,7 @@ func TestContainerStats(t *testing.T) { ...@@ -175,7 +178,7 @@ func TestContainerStats(t *testing.T) {
t.Fatalf("Got error GETing: %v", err) t.Fatalf("Got error GETing: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var receivedStats api.ContainerStats var receivedStats info.ContainerInfo
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&receivedStats) err = decoder.Decode(&receivedStats)
if err != nil { if err != nil {
...@@ -188,20 +191,22 @@ func TestContainerStats(t *testing.T) { ...@@ -188,20 +191,22 @@ func TestContainerStats(t *testing.T) {
func TestMachineStats(t *testing.T) { func TestMachineStats(t *testing.T) {
fw := makeServerTest() fw := makeServerTest()
expectedStats := &api.ContainerStats{ expectedStats := &info.ContainerInfo{
MaxMemoryUsage: 1024001, StatsPercentiles: &info.ContainerStatsPercentiles{
CpuUsagePercentiles: []api.Percentile{ MaxMemoryUsage: 1024001,
{50, 150}, CpuUsagePercentiles: []info.Percentile{
{80, 180}, {50, 150},
{90, 190}, {80, 180},
}, {90, 190},
MemoryUsagePercentiles: []api.Percentile{ },
{50, 150}, MemoryUsagePercentiles: []info.Percentile{
{80, 180}, {50, 150},
{90, 190}, {80, 180},
{90, 190},
},
}, },
} }
fw.fakeKubelet.machineStatsFunc = func() (*api.ContainerStats, error) { fw.fakeKubelet.machineStatsFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return expectedStats, nil return expectedStats, nil
} }
...@@ -210,7 +215,7 @@ func TestMachineStats(t *testing.T) { ...@@ -210,7 +215,7 @@ func TestMachineStats(t *testing.T) {
t.Fatalf("Got error GETing: %v", err) t.Fatalf("Got error GETing: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var receivedStats api.ContainerStats var receivedStats info.ContainerInfo
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&receivedStats) err = decoder.Decode(&receivedStats)
if err != nil { if err != nil {
......
...@@ -912,8 +912,8 @@ type mockCadvisorClient struct { ...@@ -912,8 +912,8 @@ type mockCadvisorClient struct {
} }
// ContainerInfo is a mock implementation of CadvisorInterface.ContainerInfo. // ContainerInfo is a mock implementation of CadvisorInterface.ContainerInfo.
func (c *mockCadvisorClient) ContainerInfo(name string) (*info.ContainerInfo, error) { func (c *mockCadvisorClient) ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
args := c.Called(name) args := c.Called(name, req)
return args.Get(0).(*info.ContainerInfo), args.Error(1) return args.Get(0).(*info.ContainerInfo), args.Error(1)
} }
...@@ -925,7 +925,7 @@ func (c *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) { ...@@ -925,7 +925,7 @@ func (c *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) {
func areSamePercentiles( func areSamePercentiles(
cadvisorPercentiles []info.Percentile, cadvisorPercentiles []info.Percentile,
kubePercentiles []api.Percentile, kubePercentiles []info.Percentile,
t *testing.T, t *testing.T,
) { ) {
if len(cadvisorPercentiles) != len(kubePercentiles) { if len(cadvisorPercentiles) != len(kubePercentiles) {
...@@ -974,7 +974,9 @@ func TestGetContainerStats(t *testing.T) { ...@@ -974,7 +974,9 @@ func TestGetContainerStats(t *testing.T) {
} }
mockCadvisor := &mockCadvisorClient{} mockCadvisor := &mockCadvisorClient{}
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil) req := &info.ContainerInfoRequest{}
cadvisorReq := getCadvisorContainerInfoRequest(req)
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
kubelet, _, fakeDocker := makeTestKubelet(t) kubelet, _, fakeDocker := makeTestKubelet(t)
kubelet.CadvisorClient = mockCadvisor kubelet.CadvisorClient = mockCadvisor
...@@ -987,15 +989,15 @@ func TestGetContainerStats(t *testing.T) { ...@@ -987,15 +989,15 @@ func TestGetContainerStats(t *testing.T) {
}, },
} }
stats, err := kubelet.GetContainerStats("qux", "foo") stats, err := kubelet.GetContainerInfo("qux", "foo", req)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage { if stats.StatsPercentiles.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
t.Errorf("wrong max memory usage") t.Errorf("wrong max memory usage")
} }
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t) areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t) areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
mockCadvisor.AssertExpectations(t) mockCadvisor.AssertExpectations(t)
} }
...@@ -1019,7 +1021,9 @@ func TestGetMachineStats(t *testing.T) { ...@@ -1019,7 +1021,9 @@ func TestGetMachineStats(t *testing.T) {
} }
mockCadvisor := &mockCadvisorClient{} mockCadvisor := &mockCadvisorClient{}
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil) req := &info.ContainerInfoRequest{}
cadvisorReq := getCadvisorContainerInfoRequest(req)
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
kubelet := Kubelet{ kubelet := Kubelet{
DockerClient: &fakeDocker, DockerClient: &fakeDocker,
...@@ -1028,15 +1032,15 @@ func TestGetMachineStats(t *testing.T) { ...@@ -1028,15 +1032,15 @@ func TestGetMachineStats(t *testing.T) {
} }
// If the container name is an empty string, then it means the root container. // If the container name is an empty string, then it means the root container.
stats, err := kubelet.GetMachineStats() stats, err := kubelet.GetMachineStats(req)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage { if stats.StatsPercentiles.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
t.Errorf("wrong max memory usage") t.Errorf("wrong max memory usage")
} }
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t) areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t) areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
mockCadvisor.AssertExpectations(t) mockCadvisor.AssertExpectations(t)
} }
...@@ -1051,19 +1055,19 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) { ...@@ -1051,19 +1055,19 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) {
}, },
} }
stats, _ := kubelet.GetContainerStats("qux", "foo") stats, _ := kubelet.GetContainerInfo("qux", "foo", nil)
// When there's no cAdvisor, the stats should be either nil or empty // When there's no cAdvisor, the stats should be either nil or empty
if stats == nil { if stats == nil {
return return
} }
if stats.MaxMemoryUsage != 0 { if stats.StatsPercentiles.MaxMemoryUsage != 0 {
t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.MaxMemoryUsage) t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.StatsPercentiles.MaxMemoryUsage)
} }
if len(stats.CpuUsagePercentiles) > 0 { if len(stats.StatsPercentiles.CpuUsagePercentiles) > 0 {
t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.CpuUsagePercentiles) t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.CpuUsagePercentiles)
} }
if len(stats.MemoryUsagePercentiles) > 0 { if len(stats.StatsPercentiles.MemoryUsagePercentiles) > 0 {
t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.MemoryUsagePercentiles) t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.MemoryUsagePercentiles)
} }
} }
...@@ -1073,8 +1077,10 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) { ...@@ -1073,8 +1077,10 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
containerInfo := &info.ContainerInfo{} containerInfo := &info.ContainerInfo{}
mockCadvisor := &mockCadvisorClient{} mockCadvisor := &mockCadvisorClient{}
req := &info.ContainerInfoRequest{}
cadvisorReq := getCadvisorContainerInfoRequest(req)
expectedErr := fmt.Errorf("some error") expectedErr := fmt.Errorf("some error")
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, expectedErr) mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, expectedErr)
kubelet, _, fakeDocker := makeTestKubelet(t) kubelet, _, fakeDocker := makeTestKubelet(t)
kubelet.CadvisorClient = mockCadvisor kubelet.CadvisorClient = mockCadvisor
...@@ -1087,7 +1093,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) { ...@@ -1087,7 +1093,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
}, },
} }
stats, err := kubelet.GetContainerStats("qux", "foo") stats, err := kubelet.GetContainerInfo("qux", "foo", req)
if stats != nil { if stats != nil {
t.Errorf("non-nil stats on error") t.Errorf("non-nil stats on error")
} }
...@@ -1108,7 +1114,7 @@ func TestGetContainerStatsOnNonExistContainer(t *testing.T) { ...@@ -1108,7 +1114,7 @@ func TestGetContainerStatsOnNonExistContainer(t *testing.T) {
kubelet.CadvisorClient = mockCadvisor kubelet.CadvisorClient = mockCadvisor
fakeDocker.containerList = []docker.APIContainers{} fakeDocker.containerList = []docker.APIContainers{}
stats, _ := kubelet.GetContainerStats("qux", "foo") stats, _ := kubelet.GetContainerInfo("qux", "foo", nil)
if stats != nil { if stats != nil {
t.Errorf("non-nil stats on non exist container") t.Errorf("non-nil stats on non exist container")
} }
......
...@@ -5,6 +5,8 @@ TOP_PACKAGES=" ...@@ -5,6 +5,8 @@ TOP_PACKAGES="
code.google.com/p/goauth2/compute/serviceaccount code.google.com/p/goauth2/compute/serviceaccount
code.google.com/p/goauth2/oauth code.google.com/p/goauth2/oauth
code.google.com/p/google-api-go-client/compute/v1 code.google.com/p/google-api-go-client/compute/v1
github.com/google/cadvisor/info
github.com/google/cadvisor/client
" "
DEP_PACKAGES=" DEP_PACKAGES="
...@@ -13,7 +15,6 @@ DEP_PACKAGES=" ...@@ -13,7 +15,6 @@ DEP_PACKAGES="
code.google.com/p/google-api-go-client/googleapi code.google.com/p/google-api-go-client/googleapi
github.com/coreos/go-log/log github.com/coreos/go-log/log
github.com/coreos/go-systemd/journal github.com/coreos/go-systemd/journal
github.com/google/cadvisor/info
" "
PACKAGES="$TOP_PACKAGES $DEP_PACKAGES" PACKAGES="$TOP_PACKAGES $DEP_PACKAGES"
language: go language: go
go: go:
- 1.2 - 1.3
before_script: before_script:
- go get github.com/stretchr/testify/mock - go get github.com/stretchr/testify/mock
- go get github.com/kr/pretty - go get github.com/kr/pretty
- wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb
- sudo dpkg -i influxdb_latest_amd64.deb
- sudo service influxdb start
script: script:
- go test -v -race github.com/google/cadvisor/container - go test -v -race github.com/google/cadvisor/...
- go test -v github.com/google/cadvisor/info
- go test -v github.com/google/cadvisor/client
- go test -v github.com/google/cadvisor/sampling
- go test -v github.com/google/cadvisor/storage/memory
- go build github.com/google/cadvisor - go build github.com/google/cadvisor
# Changelog
## 0.1.2 (2014-07-10)
- Added Storage Driver concept (flag: storage_driver), default is the in-memory driver
- Implemented InfluxDB storage driver
- Support in REST API for specifying number of stats to return
- Allow running without lmctfy (flag: allow_lmctfy)
- Bugfixes
## 0.1.0 (2014-06-14)
- Support for container aliases
- Sampling historical usage and exporting that in the REST API
- Bugfixes for UI
## 0.0.0 (2014-06-10)
- Initial version of cAdvisor
- Web UI with auto-updating stats
- v1.0 REST API with container and machine information
- Support for Docker containers
- Support for lmctfy containers
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
# Please keep the list sorted by first name. # Please keep the list sorted by first name.
Jason Swindle <jason@swindle.me>
Johan Euphrosine <proppy@google.com>
Kamil Yurtsever <kyurtsever@google.com> Kamil Yurtsever <kyurtsever@google.com>
Nan Deng <dengnan@google.com> Nan Deng <dengnan@google.com>
Rohit Jnagal <jnagal@google.com>
Victor Marmol <vmarmol@google.com> Victor Marmol <vmarmol@google.com>
Zohaib Maya <zohaib@google.com>
// 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.
package inference
import "github.com/google/cadvisor/info"
// InterferenceDectector detects if there's a container which
// interferences with a set of containers. The detector tracks
// a set of containers and find the victims and antagonist.
type InterferenceDetector interface {
// Tracks the behavior of the container.
AddContainer(ref info.ContainerReference)
// Returns a list of possible interferences. The upper layer may take action
// based on the interference.
Detect() ([]*info.Interference, error)
// The name of the detector.
Name() string
}
...@@ -19,12 +19,13 @@ package api ...@@ -19,12 +19,13 @@ package api
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"log" "log"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time" "time"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager" "github.com/google/cadvisor/manager"
) )
...@@ -34,9 +35,11 @@ const ( ...@@ -34,9 +35,11 @@ const (
MachineApi = "machine" 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() start := time.Now()
u := r.URL
// Get API request type. // Get API request type.
requestType := u.Path[len(ApiResource):] requestType := u.Path[len(ApiResource):]
i := strings.Index(requestType, "/") i := strings.Index(requestType, "/")
...@@ -46,7 +49,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { ...@@ -46,7 +49,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
requestType = requestType[:i] requestType = requestType[:i]
} }
if requestType == MachineApi { switch {
case requestType == MachineApi:
log.Printf("Api - Machine") log.Printf("Api - Machine")
// Get the MachineInfo // Get the MachineInfo
...@@ -60,14 +64,20 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { ...@@ -60,14 +64,20 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err) fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err)
} }
w.Write(out) w.Write(out)
} else if requestType == ContainersApi { case requestType == ContainersApi:
// The container name is the path after the requestType // The container name is the path after the requestType
containerName := requestArgs containerName := requestArgs
log.Printf("Api - Container(%s)", containerName) log.Printf("Api - Container(%s)", containerName)
var query info.ContainerInfoRequest
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&query)
if err != nil && err != io.EOF {
return fmt.Errorf("unable to decode the json value: ", err)
}
// Get the container. // Get the container.
cont, err := m.GetContainerInfo(containerName) cont, err := m.GetContainerInfo(containerName, &query)
if err != nil { if err != nil {
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err) fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
return err return err
...@@ -79,7 +89,7 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error { ...@@ -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) fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err)
} }
w.Write(out) w.Write(out)
} else { default:
return fmt.Errorf("unknown API request type %q", requestType) return fmt.Errorf("unknown API request type %q", requestType)
} }
......
...@@ -27,32 +27,46 @@ import ( ...@@ -27,32 +27,46 @@ import (
"github.com/google/cadvisor/manager" "github.com/google/cadvisor/manager"
"github.com/google/cadvisor/pages" "github.com/google/cadvisor/pages"
"github.com/google/cadvisor/pages/static" "github.com/google/cadvisor/pages/static"
"github.com/google/cadvisor/storage/memory"
) )
var argPort = flag.Int("port", 8080, "port to listen") var argPort = flag.Int("port", 8080, "port to listen")
var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep") var argAllowLmctfy = flag.Bool("allow_lmctfy", true, "whether to allow lmctfy as a container handler")
var argHistoryDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep")
var argDbDriver = flag.String("storage_driver", "memory", "storage driver to use. Options are: memory (default) and influxdb")
func main() { func main() {
flag.Parse() flag.Parse()
storage := memory.New(*argSampleSize, *argHistoryDuration) storageDriver, err := NewStorageDriver(*argDbDriver)
// TODO(monnand): Add stats writer for manager if err != nil {
containerManager, err := manager.New(storage) log.Fatalf("Failed to connect to database: %s", err)
}
containerManager, err := manager.New(storageDriver)
if err != nil { if err != nil {
log.Fatalf("Failed to create a Container Manager: %s", err) log.Fatalf("Failed to create a Container Manager: %s", err)
} }
if err := lmctfy.Register("/"); err != nil { // Register lmctfy for the root if allowed and available.
log.Printf("lmctfy registration failed: %v.", err) registeredRoot := false
log.Print("Running in docker only mode.") if *argAllowLmctfy {
if err := lmctfy.Register("/"); err != nil {
log.Printf("lmctfy registration failed: %v.", err)
log.Print("Running in docker only mode.")
} else {
registeredRoot = true
}
}
// Register Docker for root if we were unable to register lmctfy.
if !registeredRoot {
if err := docker.Register(containerManager, "/"); err != nil { if err := docker.Register(containerManager, "/"); err != nil {
log.Printf("Docker registration failed: %v.", err) log.Printf("Docker registration failed: %v.", err)
log.Fatalf("Unable to continue without docker or lmctfy.") log.Fatalf("Unable to continue without root handler.")
} }
} }
// Register Docker for all Docker containers.
if err := docker.Register(containerManager, "/docker"); err != nil { if err := docker.Register(containerManager, "/docker"); err != nil {
// Ignore this error because we should work with lmctfy only // Ignore this error because we should work with lmctfy only
log.Printf("Docker registration failed: %v.", err) log.Printf("Docker registration failed: %v.", err)
...@@ -69,7 +83,7 @@ func main() { ...@@ -69,7 +83,7 @@ func main() {
// Handler for the API. // Handler for the API.
http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) { 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 { if err != nil {
fmt.Fprintf(w, "%s", err) fmt.Fprintf(w, "%s", err)
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
package cadvisor package cadvisor
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string { ...@@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string {
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) { func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
u := self.machineInfoUrl() u := self.machineInfoUrl()
ret := new(info.MachineInfo) ret := new(info.MachineInfo)
err = self.httpGetJsonData(ret, u, "machine info") err = self.httpGetJsonData(ret, nil, u, "machine info")
if err != nil { if err != nil {
return return
} }
...@@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string { ...@@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string {
return strings.Join([]string{self.baseUrl, "containers", name}, "/") return strings.Join([]string{self.baseUrl, "containers", name}, "/")
} }
func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error { func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error {
resp, err := http.Get(url) var resp *http.Response
var err error
if postData != nil {
data, err := json.Marshal(postData)
if err != nil {
return fmt.Errorf("unable to marshal data: %v", err)
}
resp, err = http.Post(url, "application/json", bytes.NewBuffer(data))
} else {
resp, err = http.Get(url)
}
if err != nil { if err != nil {
err = fmt.Errorf("unable to get %v: %v", infoName, err) err = fmt.Errorf("unable to get %v: %v", infoName, err)
return err return err
...@@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro ...@@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro
return nil return nil
} }
func (self *Client) ContainerInfo(name string) (cinfo *info.ContainerInfo, err error) { func (self *Client) ContainerInfo(
name string,
query *info.ContainerInfoRequest) (cinfo *info.ContainerInfo, err error) {
u := self.containerInfoUrl(name) u := self.containerInfoUrl(name)
ret := new(info.ContainerInfo) ret := new(info.ContainerInfo)
err = self.httpGetJsonData(ret, u, fmt.Sprintf("container info for %v", name)) err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %v", name))
if err != nil { if err != nil {
return return
} }
......
...@@ -118,7 +118,7 @@ func (self *dockerContainerHandler) isDockerContainer() bool { ...@@ -118,7 +118,7 @@ func (self *dockerContainerHandler) isDockerContainer() bool {
} }
// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API. // TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) { func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) {
dir := "/var/lib/docker/execdriver/native" dir := "/var/lib/docker/execdriver/native"
configPath := path.Join(dir, id, "container.json") configPath := path.Join(dir, id, "container.json")
f, err := os.Open(configPath) f, err := os.Open(configPath)
...@@ -127,7 +127,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) { ...@@ -127,7 +127,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
} }
defer f.Close() defer f.Close()
d := json.NewDecoder(f) d := json.NewDecoder(f)
ret := new(libcontainer.Container) ret := new(libcontainer.Config)
err = d.Decode(ret) err = d.Decode(ret)
if err != nil { if err != nil {
return return
...@@ -136,7 +136,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) { ...@@ -136,7 +136,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
return return
} }
func libcontainerConfigToContainerSpec(config *libcontainer.Container, mi *info.MachineInfo) *info.ContainerSpec { func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) *info.ContainerSpec {
spec := new(info.ContainerSpec) spec := new(info.ContainerSpec)
spec.Memory = new(info.MemorySpec) spec.Memory = new(info.MemorySpec)
spec.Memory.Limit = math.MaxUint64 spec.Memory.Limit = math.MaxUint64
...@@ -209,6 +209,12 @@ func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info. ...@@ -209,6 +209,12 @@ func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.
ret.Memory.ContainerData.Pgmajfault = v ret.Memory.ContainerData.Pgmajfault = v
ret.Memory.HierarchicalData.Pgmajfault = v ret.Memory.HierarchicalData.Pgmajfault = v
} }
if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok {
ret.Memory.WorkingSet = ret.Memory.Usage - v
if v, ok := s.MemoryStats.Stats["total_active_file"]; ok {
ret.Memory.WorkingSet -= v
}
}
return ret return ret
} }
......
// 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.
package test
import (
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/info"
"github.com/stretchr/testify/mock"
)
// This struct mocks a container handler.
type MockContainerHandler struct {
mock.Mock
Name string
Aliases []string
}
// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases.
// Otherwise, it will use the value provided by .On().Return().
func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) {
if len(self.Name) > 0 {
var aliases []string
if len(self.Aliases) > 0 {
aliases = make([]string, len(self.Aliases))
copy(aliases, self.Aliases)
}
return info.ContainerReference{
Name: self.Name,
Aliases: aliases,
}, nil
}
args := self.Called()
return args.Get(0).(info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) GetSpec() (*info.ContainerSpec, error) {
args := self.Called()
return args.Get(0).(*info.ContainerSpec), args.Error(1)
}
func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) {
args := self.Called()
return args.Get(0).(*info.ContainerStats), args.Error(1)
}
func (self *MockContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
args := self.Called(listType)
return args.Get(0).([]info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
args := self.Called(listType)
return args.Get(0).([]int), args.Error(1)
}
func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
args := self.Called(listType)
return args.Get(0).([]int), args.Error(1)
}
type FactoryForMockContainerHandler struct {
Name string
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)
}
func (self *FactoryForMockContainerHandler) String() string {
return self.Name
}
func (self *FactoryForMockContainerHandler) NewContainerHandler(name string) (container.ContainerHandler, error) {
handler := &MockContainerHandler{}
if self.PrepareContainerHandlerFunc != nil {
self.PrepareContainerHandlerFunc(name, handler)
}
return handler, nil
}
...@@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y -q --no-install-recommends pkg-config l ...@@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y -q --no-install-recommends pkg-config l
# Get the lcmtfy and cAdvisor binaries. # Get the lcmtfy and cAdvisor binaries.
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.2 /usr/bin/cadvisor
RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor
EXPOSE 8080 EXPOSE 8080
......
// 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.
package info
// This struct describes one type of relationship between containers: One
// container, antagonist, interferes the performance of other
// containers, victims.
type Interference struct {
// Absolute name of the antagonist container name. This field
// should not be empty.
Antagonist string `json:"antagonist"`
// The absolute path of the victims. This field should not be empty.
Victims []string `json:"victims"`
// The name of the detector used to detect this antagonism. This field
// should not be empty
Detector string `json:"detector"`
// Human readable description of this interference
Description string `json:"description,omitempty"`
}
...@@ -16,6 +16,7 @@ package info ...@@ -16,6 +16,7 @@ package info
import ( import (
"fmt" "fmt"
"reflect"
"sort" "sort"
"time" "time"
) )
...@@ -57,6 +58,40 @@ type ContainerReference struct { ...@@ -57,6 +58,40 @@ type ContainerReference struct {
Aliases []string `json:"aliases,omitempty"` Aliases []string `json:"aliases,omitempty"`
} }
// ContainerInfoQuery is used when users check a container info from the REST api.
// It specifies how much data users want to get about a container
type ContainerInfoRequest struct {
// Max number of stats to return.
NumStats int `json:"num_stats,omitempty"`
// Max number of samples to return.
NumSamples int `json:"num_samples,omitempty"`
// Different percentiles of CPU usage within a period. The values must be within [0, 100]
CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"`
// Different percentiles of memory usage within a period. The values must be within [0, 100]
MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"`
}
func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest {
ret := self
if ret == nil {
ret = new(ContainerInfoRequest)
}
if ret.NumStats <= 0 {
ret.NumStats = 1024
}
if ret.NumSamples <= 0 {
ret.NumSamples = 1024
}
if len(ret.CpuUsagePercentiles) == 0 {
ret.CpuUsagePercentiles = []int{50, 80, 90, 99}
}
if len(ret.MemoryUsagePercentages) == 0 {
ret.MemoryUsagePercentages = []int{50, 80, 90, 99}
}
return ret
}
type ContainerInfo struct { type ContainerInfo struct {
ContainerReference ContainerReference
...@@ -119,7 +154,7 @@ type CpuStats struct { ...@@ -119,7 +154,7 @@ type CpuStats struct {
// Per CPU/core usage of the container. // Per CPU/core usage of the container.
// Unit: nanoseconds. // Unit: nanoseconds.
PerCpu []uint64 `json:"per_cpu,omitempty"` PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
// Time spent in user space. // Time spent in user space.
// Unit: nanoseconds // Unit: nanoseconds
...@@ -165,6 +200,42 @@ type ContainerStats struct { ...@@ -165,6 +200,42 @@ type ContainerStats struct {
Memory *MemoryStats `json:"memory,omitempty"` Memory *MemoryStats `json:"memory,omitempty"`
} }
// Makes a deep copy of the ContainerStats and returns a pointer to the new
// copy. Copy() will allocate a new ContainerStats object if dst is nil.
func (self *ContainerStats) Copy(dst *ContainerStats) *ContainerStats {
if dst == nil {
dst = new(ContainerStats)
}
dst.Timestamp = self.Timestamp
if self.Cpu != nil {
if dst.Cpu == nil {
dst.Cpu = new(CpuStats)
}
// To make a deep copy of a slice, we need to copy every value
// in the slice. To make less memory allocation, we would like
// to reuse the slice in dst if possible.
percpu := dst.Cpu.Usage.PerCpu
if len(percpu) != len(self.Cpu.Usage.PerCpu) {
percpu = make([]uint64, len(self.Cpu.Usage.PerCpu))
}
dst.Cpu.Usage = self.Cpu.Usage
dst.Cpu.Load = self.Cpu.Load
copy(percpu, self.Cpu.Usage.PerCpu)
dst.Cpu.Usage.PerCpu = percpu
} else {
dst.Cpu = nil
}
if self.Memory != nil {
if dst.Memory == nil {
dst.Memory = new(MemoryStats)
}
*dst.Memory = *self.Memory
} else {
dst.Memory = nil
}
return dst
}
type ContainerStatsSample struct { type ContainerStatsSample struct {
// Timetamp of the end of the sample period // Timetamp of the end of the sample period
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
...@@ -173,6 +244,9 @@ type ContainerStatsSample struct { ...@@ -173,6 +244,9 @@ type ContainerStatsSample struct {
Cpu struct { Cpu struct {
// number of nanoseconds of CPU time used by the container // number of nanoseconds of CPU time used by the container
Usage uint64 `json:"usage"` Usage uint64 `json:"usage"`
// Per-core usage of the container. (unit: nanoseconds)
PerCpuUsage []uint64 `json:"per_cpu_usage,omitempty"`
} `json:"cpu"` } `json:"cpu"`
Memory struct { Memory struct {
// Units: Bytes. // Units: Bytes.
...@@ -180,6 +254,67 @@ type ContainerStatsSample struct { ...@@ -180,6 +254,67 @@ type ContainerStatsSample struct {
} `json:"memory"` } `json:"memory"`
} }
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
// t1 should not be later than t2
if t1.After(t2) {
t1, t2 = t2, t1
}
diff := t2.Sub(t1)
if diff <= tolerance {
return true
}
return false
}
func durationEq(a, b time.Duration, tolerance time.Duration) bool {
if a > b {
a, b = b, a
}
diff := a - b
if diff <= tolerance {
return true
}
return false
}
const (
// 10ms, i.e. 0.01s
timePrecision time.Duration = 10 * time.Millisecond
)
// This function is useful because we do not require precise time
// representation.
func (a *ContainerStats) Eq(b *ContainerStats) bool {
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
return false
}
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
return false
}
if !reflect.DeepEqual(a.Memory, b.Memory) {
return false
}
return true
}
// This function is useful because we do not require precise time
// representation.
func (a *ContainerStatsSample) Eq(b *ContainerStatsSample) bool {
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
return false
}
if !durationEq(a.Duration, b.Duration, timePrecision) {
return false
}
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
return false
}
if !reflect.DeepEqual(a.Memory, b.Memory) {
return false
}
return true
}
type Percentile struct { type Percentile struct {
Percentage int `json:"percentage"` Percentage int `json:"percentage"`
Value uint64 `json:"value"` Value uint64 `json:"value"`
...@@ -211,9 +346,28 @@ func NewSample(prev, current *ContainerStats) (*ContainerStatsSample, error) { ...@@ -211,9 +346,28 @@ func NewSample(prev, current *ContainerStats) (*ContainerStatsSample, error) {
if current.Cpu.Usage.Total < prev.Cpu.Usage.Total { if current.Cpu.Usage.Total < prev.Cpu.Usage.Total {
return nil, fmt.Errorf("current CPU usage is less than prev CPU usage (cumulative).") return nil, fmt.Errorf("current CPU usage is less than prev CPU usage (cumulative).")
} }
var percpu []uint64
if len(current.Cpu.Usage.PerCpu) > 0 {
curNumCpus := len(current.Cpu.Usage.PerCpu)
percpu = make([]uint64, curNumCpus)
for i, currUsage := range current.Cpu.Usage.PerCpu {
var prevUsage uint64 = 0
if i < len(prev.Cpu.Usage.PerCpu) {
prevUsage = prev.Cpu.Usage.PerCpu[i]
}
if currUsage < prevUsage {
return nil, fmt.Errorf("current per-core CPU usage is less than prev per-core CPU usage (cumulative).")
}
percpu[i] = currUsage - prevUsage
}
}
sample := new(ContainerStatsSample) sample := new(ContainerStatsSample)
// Calculate the diff to get the CPU usage within the time interval. // Calculate the diff to get the CPU usage within the time interval.
sample.Cpu.Usage = current.Cpu.Usage.Total - prev.Cpu.Usage.Total sample.Cpu.Usage = current.Cpu.Usage.Total - prev.Cpu.Usage.Total
sample.Cpu.PerCpuUsage = percpu
// Memory usage is current memory usage // Memory usage is current memory usage
sample.Memory.Usage = current.Memory.Usage sample.Memory.Usage = current.Memory.Usage
sample.Timestamp = current.Timestamp sample.Timestamp = current.Timestamp
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
package info package info
import ( import (
"reflect"
"testing" "testing"
"time" "time"
) )
...@@ -230,3 +231,68 @@ func TestAddSampleWrongCpuUsage(t *testing.T) { ...@@ -230,3 +231,68 @@ func TestAddSampleWrongCpuUsage(t *testing.T) {
t.Errorf("generated an unexpected sample: %+v", sample) t.Errorf("generated an unexpected sample: %+v", sample)
} }
} }
func TestAddSampleHotPluggingCpu(t *testing.T) {
cpuPrevUsage := uint64(10)
cpuCurrentUsage := uint64(15)
memCurrentUsage := uint64(200)
prevTime := time.Now()
prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime)
current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second))
current.Cpu.Usage.PerCpu = append(current.Cpu.Usage.PerCpu, 10)
sample, err := NewSample(prev, current)
if err != nil {
t.Errorf("should be able to generate a sample. but received error: %v", err)
}
if len(sample.Cpu.PerCpuUsage) != 2 {
t.Fatalf("Should have 2 cores.")
}
if sample.Cpu.PerCpuUsage[0] != cpuCurrentUsage-cpuPrevUsage {
t.Errorf("First cpu usage is %v. should be %v", sample.Cpu.PerCpuUsage[0], cpuCurrentUsage-cpuPrevUsage)
}
if sample.Cpu.PerCpuUsage[1] != 10 {
t.Errorf("Second cpu usage is %v. should be 10", sample.Cpu.PerCpuUsage[1])
}
}
func TestAddSampleHotUnpluggingCpu(t *testing.T) {
cpuPrevUsage := uint64(10)
cpuCurrentUsage := uint64(15)
memCurrentUsage := uint64(200)
prevTime := time.Now()
prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime)
current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second))
prev.Cpu.Usage.PerCpu = append(prev.Cpu.Usage.PerCpu, 10)
sample, err := NewSample(prev, current)
if err != nil {
t.Errorf("should be able to generate a sample. but received error: %v", err)
}
if len(sample.Cpu.PerCpuUsage) != 1 {
t.Fatalf("Should have 1 cores.")
}
if sample.Cpu.PerCpuUsage[0] != cpuCurrentUsage-cpuPrevUsage {
t.Errorf("First cpu usage is %v. should be %v", sample.Cpu.PerCpuUsage[0], cpuCurrentUsage-cpuPrevUsage)
}
}
func TestContainerStatsCopy(t *testing.T) {
stats := createStats(100, 101, time.Now())
shadowStats := stats.Copy(nil)
if !reflect.DeepEqual(stats, shadowStats) {
t.Errorf("Copy() returned different object")
}
stats.Cpu.Usage.PerCpu[0] = shadowStats.Cpu.Usage.PerCpu[0] + 1
stats.Cpu.Load = shadowStats.Cpu.Load + 1
stats.Memory.Usage = shadowStats.Memory.Usage + 1
if reflect.DeepEqual(stats, shadowStats) {
t.Errorf("Copy() did not deeply copy the object")
}
stats = shadowStats.Copy(stats)
if !reflect.DeepEqual(stats, shadowStats) {
t.Errorf("Copy() returned different object")
}
}
// 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.
package test
import (
"fmt"
"math"
"math/rand"
"time"
"github.com/google/cadvisor/info"
)
func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info.ContainerStats {
ret := make([]*info.ContainerStats, numStats)
perCoreUsages := make([]uint64, numCores)
currentTime := time.Now()
for i := range perCoreUsages {
perCoreUsages[i] = uint64(rand.Int63n(1000))
}
for i := 0; i < numStats; i++ {
stats := new(info.ContainerStats)
stats.Cpu = new(info.CpuStats)
stats.Memory = new(info.MemoryStats)
stats.Timestamp = currentTime
currentTime = currentTime.Add(duration)
percore := make([]uint64, numCores)
for i := range perCoreUsages {
perCoreUsages[i] += uint64(rand.Int63n(1000))
percore[i] = perCoreUsages[i]
stats.Cpu.Usage.Total += percore[i]
}
stats.Cpu.Usage.PerCpu = percore
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
}
func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
ret := &info.ContainerSpec{
Cpu: &info.CpuSpec{},
Memory: &info.MemorySpec{},
}
ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000))
ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))
n := (numCores + 63) / 64
ret.Cpu.Mask.Data = make([]uint64, n)
for i := 0; i < n; i++ {
ret.Cpu.Mask.Data[i] = math.MaxUint64
}
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
return ret
}
func GenerateRandomContainerInfo(containerName string, numCores int, query *info.ContainerInfoRequest, duration time.Duration) *info.ContainerInfo {
stats := GenerateRandomStats(query.NumStats, numCores, duration)
samples, _ := NewSamplesFromStats(stats...)
if len(samples) > query.NumSamples {
samples = samples[:query.NumSamples]
}
cpuPercentiles := make([]info.Percentile, 0, len(query.CpuUsagePercentiles))
// TODO(monnand): This will generate percentiles where 50%tile data may
// be larger than 90%tile data.
for _, p := range query.CpuUsagePercentiles {
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 := GenerateRandomContainerSpec(numCores)
ret := &info.ContainerInfo{
ContainerReference: info.ContainerReference{
Name: containerName,
},
Spec: spec,
StatsPercentiles: percentiles,
Samples: samples,
Stats: stats,
}
return ret
}
func NewSamplesFromStats(stats ...*info.ContainerStats) ([]*info.ContainerStatsSample, error) {
if len(stats) < 2 {
return nil, nil
}
samples := make([]*info.ContainerStatsSample, 0, len(stats)-1)
for i, s := range stats[1:] {
prev := stats[i]
sample, err := info.NewSample(prev, s)
if err != nil {
return nil, fmt.Errorf("Unable to generate sample from %+v and %+v: %v",
prev, s, err)
}
samples = append(samples, sample)
}
return samples, nil
}
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
package info package info
// Version of cAdvisor. // Version of cAdvisor.
const VERSION = "0.1.0" const VERSION = "0.1.2"
// 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"
"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"
"github.com/google/cadvisor/storage"
stest "github.com/google/cadvisor/storage/test"
)
func createContainerDataAndSetHandler(
driver storage.StorageDriver,
f func(*ctest.MockContainerHandler),
t *testing.T,
) *containerData {
factory := &ctest.FactoryForMockContainerHandler{
Name: "factoryForMockContainer",
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
handler.Name = name
f(handler)
},
}
container.RegisterContainerHandlerFactory("/", factory)
if driver == nil {
driver = &stest.MockStorageDriver{}
}
ret, err := NewContainerData("/container", driver)
if err != nil {
t.Fatal(err)
}
return ret
}
func TestContainerUpdateSubcontainers(t *testing.T) {
var handler *ctest.MockContainerHandler
subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"},
{Name: "/container/abcd"},
{Name: "/container/something"},
}
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return(
subcontainers,
nil,
)
handler = h
},
t,
)
err := cd.updateSubcontainers()
if err != nil {
t.Fatal(err)
}
if len(cd.info.Subcontainers) != len(subcontainers) {
t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers))
}
for _, sub := range cd.info.Subcontainers {
found := false
for _, sub2 := range subcontainers {
if sub.Name == sub2.Name {
found = true
}
}
if !found {
t.Errorf("Received unknown sub container %v", sub)
}
}
handler.AssertExpectations(t)
}
func TestContainerUpdateSubcontainersWithError(t *testing.T) {
var handler *ctest.MockContainerHandler
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return(
[]info.ContainerReference{},
fmt.Errorf("some error"),
)
handler = h
},
t,
)
err := cd.updateSubcontainers()
if err == nil {
t.Fatal("updateSubcontainers should return error")
}
if len(cd.info.Subcontainers) != 0 {
t.Errorf("Received %v subcontainers, should be 0", len(cd.info.Subcontainers))
}
handler.AssertExpectations(t)
}
func TestContainerUpdateStats(t *testing.T) {
var handler *ctest.MockContainerHandler
var ref info.ContainerReference
driver := &stest.MockStorageDriver{}
statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
stats := statsList[0]
cd := createContainerDataAndSetHandler(
driver,
func(h *ctest.MockContainerHandler) {
h.On("GetStats").Return(
stats,
nil,
)
handler = h
ref.Name = h.Name
},
t,
)
driver.On("AddStats", ref, stats).Return(nil)
err := cd.updateStats()
if err != nil {
t.Fatal(err)
}
handler.AssertExpectations(t)
}
func TestContainerUpdateSpec(t *testing.T) {
var handler *ctest.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4)
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
h.On("GetSpec").Return(
spec,
nil,
)
handler = h
},
t,
)
err := cd.updateSpec()
if err != nil {
t.Fatal(err)
}
handler.AssertExpectations(t)
}
func TestContainerGetInfo(t *testing.T) {
var handler *ctest.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4)
subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"},
{Name: "/container/abcd"},
{Name: "/container/something"},
}
aliases := []string{"a1", "a2"}
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
h.On("GetSpec").Return(
spec,
nil,
)
h.On("ListContainers", container.LIST_SELF).Return(
subcontainers,
nil,
)
h.Aliases = aliases
handler = h
},
t,
)
info, err := cd.GetInfo()
if err != nil {
t.Fatal(err)
}
handler.AssertExpectations(t)
if len(info.Subcontainers) != len(subcontainers) {
t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers))
}
for _, sub := range info.Subcontainers {
found := false
for _, sub2 := range subcontainers {
if sub.Name == sub2.Name {
found = true
}
}
if !found {
t.Errorf("Received unknown sub container %v", sub)
}
}
if !reflect.DeepEqual(spec, info.Spec) {
t.Errorf("received wrong container spec")
}
if info.Name != handler.Name {
t.Errorf("received wrong container name: received %v; should be %v", info.Name, handler.Name)
}
}
...@@ -30,7 +30,7 @@ type Manager interface { ...@@ -30,7 +30,7 @@ type Manager interface {
Start() error Start() error
// Get information about a container. // Get information about a container.
GetContainerInfo(containerName string) (*info.ContainerInfo, error) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
// Get information about the machine. // Get information about the machine.
GetMachineInfo() (*info.MachineInfo, error) GetMachineInfo() (*info.MachineInfo, error)
...@@ -106,8 +106,8 @@ func (m *manager) Start() error { ...@@ -106,8 +106,8 @@ func (m *manager) Start() error {
} }
// Get a container by name. // Get a container by name.
func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) { func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
log.Printf("Get(%s)", containerName) log.Printf("Get(%s); %+v", containerName, query)
var cont *containerData var cont *containerData
var ok bool var ok bool
func() { func() {
...@@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e ...@@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e
var percentiles *info.ContainerStatsPercentiles var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats var stats []*info.ContainerStats
// TODO(monnand): These numbers should not be hard coded query = query.FillDefaults()
percentiles, err = m.storageDriver.Percentiles( percentiles, err = m.storageDriver.Percentiles(
cinfo.Name, cinfo.Name,
[]int{50, 80, 90, 99}, query.CpuUsagePercentiles,
[]int{50, 80, 90, 99}, query.MemoryUsagePercentages,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
samples, err = m.storageDriver.Samples(cinfo.Name, 1024) samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024) stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
// 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 (
"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 TestGetContainerInfo(t *testing.T) {
containers := []string{
"/c1",
"/c2",
}
query := &info.ContainerInfoRequest{
NumStats: 256,
NumSamples: 128,
CpuUsagePercentiles: []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 {
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
}
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.CpuUsagePercentiles,
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.ContainerInfoRequest
query = query.FillDefaults()
infosMap := make(map[string]*info.ContainerInfo, len(containers))
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers))
for _, container := range containers {
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
}
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.CpuUsagePercentiles,
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)
}
}
}
...@@ -162,7 +162,11 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) ...@@ -162,7 +162,11 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
containerName := u.Path[len(ContainersPage)-1:] containerName := u.Path[len(ContainersPage)-1:]
// Get the container. // Get the container.
cont, err := m.GetContainerInfo(containerName) reqParams := info.ContainerInfoRequest{
NumStats: 60,
NumSamples: 60,
}
cont, err := m.GetContainerInfo(containerName, &reqParams)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err) return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
} }
......
...@@ -128,7 +128,7 @@ function drawCpuPerCoreUsage(elementId, machineInfo, stats) { ...@@ -128,7 +128,7 @@ function drawCpuPerCoreUsage(elementId, machineInfo, stats) {
elements.push(cur.timestamp); elements.push(cur.timestamp);
for (var j = 0; j < machineInfo.num_cores; j++) { for (var j = 0; j < machineInfo.num_cores; j++) {
// TODO(vmarmol): This assumes we sample every second, use the timestamps. // TODO(vmarmol): This assumes we sample every second, use the timestamps.
elements.push((cur.cpu.usage.per_cpu[j] - prev.cpu.usage.per_cpu[j]) / 1000000000); elements.push((cur.cpu.usage.per_cpu_usage[j] - prev.cpu.usage.per_cpu_usage[j]) / 1000000000);
} }
data.push(elements); data.push(elements);
} }
......
// 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.
package influxdb
import (
"fmt"
"testing"
"time"
"github.com/google/cadvisor/storage"
"github.com/google/cadvisor/storage/test"
"github.com/influxdb/influxdb-go"
)
func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
machineName := "machineA"
tablename := "t"
database := "cadvisor"
username := "root"
password := "root"
hostname := "localhost:8086"
percentilesDuration := 10 * time.Minute
rootConfig := &influxdb.ClientConfig{
Host: hostname,
Username: username,
Password: password,
IsSecure: false,
}
rootClient, err := influxdb.NewClient(rootConfig)
if err != nil {
t.Fatal(err)
}
// create the data base first.
rootClient.CreateDatabase(database)
config := &influxdb.ClientConfig{
Host: hostname,
Username: username,
Password: password,
Database: database,
IsSecure: false,
}
client, err := influxdb.NewClient(config)
if err != nil {
t.Fatal(err)
}
client.DisableCompression()
deleteAll := fmt.Sprintf("drop series %v", tablename)
_, err = client.Query(deleteAll)
if err != nil {
t.Fatal(err)
}
// delete all data by the end of the call
defer client.Query(deleteAll)
driver, err := New(machineName,
tablename,
database,
username,
password,
hostname,
false,
percentilesDuration)
if err != nil {
t.Fatal(err)
}
// generate another container's data on same machine.
test.StorageDriverFillRandomStatsFunc("containerOnSameMachine", 100, driver, t)
// generate another container's data on another machine.
driverForAnotherMachine, err := New("machineB",
tablename,
database,
username,
password,
hostname,
false,
percentilesDuration)
if err != nil {
t.Fatal(err)
}
defer driverForAnotherMachine.Close()
test.StorageDriverFillRandomStatsFunc("containerOnAnotherMachine", 100, driverForAnotherMachine, t)
f(driver, t)
}
func TestSampleCpuUsage(t *testing.T) {
runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
}
func TestRetrievePartialRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
}
func TestSamplesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
}
func TestRetrieveAllRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
}
func TestNoRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestNoRecentStats, t)
}
func TestNoSamples(t *testing.T) {
runStorageTest(test.StorageDriverTestNoSamples, t)
}
func TestPercentiles(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentiles, t)
}
func TestMaxMemoryUsage(t *testing.T) {
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
}
func TestPercentilesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
}
func TestPercentilesWithoutStats(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
}
...@@ -41,20 +41,7 @@ func (self *containerStorage) updatePrevStats(stats *info.ContainerStats) { ...@@ -41,20 +41,7 @@ func (self *containerStorage) updatePrevStats(stats *info.ContainerStats) {
self.prevStats = nil self.prevStats = nil
return return
} }
if self.prevStats == nil { self.prevStats = stats.Copy(self.prevStats)
self.prevStats = &info.ContainerStats{
Cpu: &info.CpuStats{},
Memory: &info.MemoryStats{},
}
}
// make a deep copy.
self.prevStats.Timestamp = stats.Timestamp
*self.prevStats.Cpu = *stats.Cpu
self.prevStats.Cpu.Usage.PerCpu = make([]uint64, len(stats.Cpu.Usage.PerCpu))
for i, perCpu := range stats.Cpu.Usage.PerCpu {
self.prevStats.Cpu.Usage.PerCpu[i] = perCpu
}
*self.prevStats.Memory = *stats.Memory
} }
func (self *containerStorage) AddStats(stats *info.ContainerStats) error { func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
...@@ -75,9 +62,9 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error { ...@@ -75,9 +62,9 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
} }
} }
if self.recentStats.Len() >= self.maxNumStats { if self.recentStats.Len() >= self.maxNumStats {
self.recentStats.Remove(self.recentStats.Front()) self.recentStats.Remove(self.recentStats.Back())
} }
self.recentStats.PushBack(stats) self.recentStats.PushFront(stats)
self.updatePrevStats(stats) self.updatePrevStats(stats)
return nil return nil
} }
...@@ -88,18 +75,25 @@ func (self *containerStorage) RecentStats(numStats int) ([]*info.ContainerStats, ...@@ -88,18 +75,25 @@ func (self *containerStorage) RecentStats(numStats int) ([]*info.ContainerStats,
if self.recentStats.Len() < numStats || numStats < 0 { if self.recentStats.Len() < numStats || numStats < 0 {
numStats = self.recentStats.Len() numStats = self.recentStats.Len()
} }
ret := make([]*info.ContainerStats, 0, numStats)
// Stats in the recentStats list are stored in reverse chronological
// order, i.e. most recent stats is in the front.
// numStats will always <= recentStats.Len() so that there will be
// always at least numStats available stats to retrieve. We traverse
// the recentStats list from its head and fill the ret slice in
// reverse order so that the returned slice will be in chronological
// order. The order of the returned slice is not specified by the
// StorageDriver interface, so it is not necessary for other storage
// drivers to return the slice in the same order.
ret := make([]*info.ContainerStats, numStats)
e := self.recentStats.Front() e := self.recentStats.Front()
for i := 0; i < numStats; i++ { for i := numStats - 1; i >= 0; i-- {
data, ok := e.Value.(*info.ContainerStats) data, ok := e.Value.(*info.ContainerStats)
if !ok { if !ok {
return nil, fmt.Errorf("The %vth element is not a ContainerStats", i) return nil, fmt.Errorf("The %vth element is not a ContainerStats", i)
} }
ret = append(ret, data) ret[i] = data
e = e.Next() e = e.Next()
if e == nil {
break
}
} }
return ret, nil return ret, nil
} }
...@@ -162,23 +156,34 @@ type InMemoryStorage struct { ...@@ -162,23 +156,34 @@ type InMemoryStorage struct {
func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
var cstore *containerStorage var cstore *containerStorage
var ok bool var ok bool
self.lock.Lock()
if cstore, ok = self.containerStorageMap[ref.Name]; !ok { func() {
cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats) self.lock.Lock()
self.containerStorageMap[ref.Name] = cstore defer self.lock.Unlock()
} if cstore, ok = self.containerStorageMap[ref.Name]; !ok {
self.lock.Unlock() cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats)
self.containerStorageMap[ref.Name] = cstore
}
}()
return cstore.AddStats(stats) return cstore.AddStats(stats)
} }
func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) { func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) {
var cstore *containerStorage var cstore *containerStorage
var ok bool var ok bool
self.lock.RLock()
if cstore, ok = self.containerStorageMap[name]; !ok { err := func() error {
return nil, fmt.Errorf("unable to find data for container %v", name) self.lock.RLock()
defer self.lock.RUnlock()
if cstore, ok = self.containerStorageMap[name]; !ok {
return fmt.Errorf("unable to find data for container %v", name)
}
return nil
}()
if err != nil {
return nil, err
} }
self.lock.RUnlock()
return cstore.Samples(numSamples) return cstore.Samples(numSamples)
} }
...@@ -186,11 +191,17 @@ func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.Conta ...@@ -186,11 +191,17 @@ func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.Conta
func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.ContainerStats, error) { func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.ContainerStats, error) {
var cstore *containerStorage var cstore *containerStorage
var ok bool var ok bool
self.lock.RLock() err := func() error {
if cstore, ok = self.containerStorageMap[name]; !ok { self.lock.RLock()
return nil, fmt.Errorf("unable to find data for container %v", name) defer self.lock.RUnlock()
if cstore, ok = self.containerStorageMap[name]; !ok {
return fmt.Errorf("unable to find data for container %v", name)
}
return nil
}()
if err != nil {
return nil, err
} }
self.lock.RUnlock()
return cstore.RecentStats(numStats) return cstore.RecentStats(numStats)
} }
...@@ -198,11 +209,17 @@ func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.Con ...@@ -198,11 +209,17 @@ func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.Con
func (self *InMemoryStorage) Percentiles(name string, cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) { func (self *InMemoryStorage) Percentiles(name string, cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) {
var cstore *containerStorage var cstore *containerStorage
var ok bool var ok bool
self.lock.RLock() err := func() error {
if cstore, ok = self.containerStorageMap[name]; !ok { self.lock.RLock()
return nil, fmt.Errorf("unable to find data for container %v", name) defer self.lock.RUnlock()
if cstore, ok = self.containerStorageMap[name]; !ok {
return fmt.Errorf("unable to find data for container %v", name)
}
return nil
}()
if err != nil {
return nil, err
} }
self.lock.RUnlock()
return cstore.Percentiles(cpuPercentiles, memPercentiles) return cstore.Percentiles(cpuPercentiles, memPercentiles)
} }
......
...@@ -44,6 +44,32 @@ func TestSamplesWithoutSample(t *testing.T) { ...@@ -44,6 +44,32 @@ func TestSamplesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t) runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
} }
func TestPercentilessWithoutSample(t *testing.T) { func TestPercentilesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t) runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
} }
func TestPercentiles(t *testing.T) {
N := 100
driver := New(N, N)
test.StorageDriverTestPercentiles(driver, t)
}
func TestRetrievePartialRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
}
func TestRetrieveAllRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
}
func TestNoRecentStats(t *testing.T) {
runStorageTest(test.StorageDriverTestNoRecentStats, t)
}
func TestNoSamples(t *testing.T) {
runStorageTest(test.StorageDriverTestNoSamples, t)
}
func TestPercentilesWithoutStats(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
}
...@@ -21,7 +21,9 @@ type StorageDriver interface { ...@@ -21,7 +21,9 @@ type StorageDriver interface {
// Read most recent stats. numStats indicates max number of stats // Read most recent stats. numStats indicates max number of stats
// returned. The returned stats must be consecutive observed stats. If // returned. The returned stats must be consecutive observed stats. If
// numStats < 0, then return all stats stored in the storage. // numStats < 0, then return all stats stored in the storage. The
// returned stats should be sorted in time increasing order, i.e. Most
// recent stats should be the last.
RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error)
// Read the specified percentiles of CPU and memory usage of the container. // Read the specified percentiles of CPU and memory usage of the container.
...@@ -31,7 +33,7 @@ type StorageDriver interface { ...@@ -31,7 +33,7 @@ type StorageDriver interface {
// Returns samples of the container stats. If numSamples < 0, then // Returns samples of the container stats. If numSamples < 0, then
// the number of returned samples is implementation defined. Otherwise, the driver // the number of returned samples is implementation defined. Otherwise, the driver
// should return at most numSamples samples. // should return at most numSamples samples.
Samples(containername string, numSamples int) ([]*info.ContainerStatsSample, error) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error)
// Close will clear the state of the storage driver. The elements // Close will clear the state of the storage driver. The elements
// stored in the underlying storage may or may not be deleted depending // stored in the underlying storage may or may not be deleted depending
......
// 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.
package test
import (
"github.com/google/cadvisor/info"
"github.com/stretchr/testify/mock"
)
type MockStorageDriver struct {
mock.Mock
MockCloseMethod bool
}
func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
args := self.Called(ref, stats)
return args.Error(0)
}
func (self *MockStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
args := self.Called(containerName, numStats)
return args.Get(0).([]*info.ContainerStats), args.Error(1)
}
func (self *MockStorageDriver) Percentiles(
containerName string,
cpuUsagePercentiles []int,
memUsagePercentiles []int,
) (*info.ContainerStatsPercentiles, error) {
args := self.Called(containerName, cpuUsagePercentiles, memUsagePercentiles)
return args.Get(0).(*info.ContainerStatsPercentiles), args.Error(1)
}
func (self *MockStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
args := self.Called(containerName, numSamples)
return args.Get(0).([]*info.ContainerStatsSample), args.Error(1)
}
func (self *MockStorageDriver) Close() error {
if self.MockCloseMethod {
args := self.Called()
return args.Error(0)
}
return nil
}
// 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.
package main
import (
"flag"
"fmt"
"os"
"time"
"github.com/google/cadvisor/storage"
"github.com/google/cadvisor/storage/influxdb"
"github.com/google/cadvisor/storage/memory"
)
var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep")
var argHistoryDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep")
var argDbUsername = flag.String("storage_driver_user", "root", "database username")
var argDbPassword = flag.String("storage_driver_password", "root", "database password")
var argDbHost = flag.String("storage_driver_host", "localhost:8086", "database host:port")
var argDbName = flag.String("storage_driver_name", "cadvisor", "database name")
var argDbIsSecure = flag.Bool("storage_driver_secure", false, "use secure connection with database")
func NewStorageDriver(driverName string) (storage.StorageDriver, error) {
var storageDriver storage.StorageDriver
var err error
switch driverName {
case "":
// empty string by default is the in memory store
fallthrough
case "memory":
storageDriver = memory.New(*argSampleSize, *argHistoryDuration)
return storageDriver, nil
case "influxdb":
var hostname string
hostname, err = os.Hostname()
if err != nil {
return nil, err
}
storageDriver, err = influxdb.New(
hostname,
"cadvisorTable",
*argDbName,
*argDbUsername,
*argDbPassword,
*argDbHost,
*argDbIsSecure,
// TODO(monnand): One hour? Or user-defined?
1*time.Hour,
)
default:
err = fmt.Errorf("Unknown database driver: %v", *argDbDriver)
}
if err != nil {
return nil, err
}
return storageDriver, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment