Update to latest cAdvisor and use data structures directly from cAdvisor
This commit is contained in:
parent
02ee27c133
commit
8c573ee727
@ -147,20 +147,6 @@ type Container struct {
|
||||
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
|
||||
type Event struct {
|
||||
Event string `json:"event,omitempty"`
|
||||
|
@ -48,7 +48,7 @@ const defaultChanSize = 1024
|
||||
|
||||
// CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client.
|
||||
type CadvisorInterface interface {
|
||||
ContainerInfo(name string) (*info.ContainerInfo, error)
|
||||
ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||
MachineInfo() (*info.MachineInfo, error)
|
||||
}
|
||||
|
||||
@ -828,44 +828,29 @@ func (kl *Kubelet) getDockerIDFromPodIDAndContainerName(podID, containerName str
|
||||
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
|
||||
// container. The container's absolute path refers to its hierarchy in the
|
||||
// cgroup file system. e.g. The root container, which represents the whole
|
||||
// machine, has path "/"; all docker containers have path "/docker/<docker id>"
|
||||
func (kl *Kubelet) statsFromContainerPath(containerPath string) (*api.ContainerStats, error) {
|
||||
info, err := kl.CadvisorClient.ContainerInfo(containerPath)
|
||||
|
||||
func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
cinfo, err := kl.CadvisorClient.ContainerInfo(containerPath, getCadvisorContainerInfoRequest(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When the stats data for the container is not available yet.
|
||||
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
|
||||
return cinfo, nil
|
||||
}
|
||||
|
||||
// GetContainerStats returns stats (from Cadvisor) for a container.
|
||||
func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) {
|
||||
func (kl *Kubelet) GetContainerStats(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
if kl.CadvisorClient == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@ -873,12 +858,12 @@ func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.Containe
|
||||
if err != nil || len(dockerID) == 0 {
|
||||
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.
|
||||
func (kl *Kubelet) GetMachineStats() (*api.ContainerStats, error) {
|
||||
return kl.statsFromContainerPath("/")
|
||||
func (kl *Kubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
return kl.statsFromContainerPath("/", req)
|
||||
}
|
||||
|
||||
func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (HealthCheckStatus, error) {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -28,6 +29,7 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/google/cadvisor/info"
|
||||
"gopkg.in/v1/yaml"
|
||||
)
|
||||
|
||||
@ -41,8 +43,8 @@ type KubeletServer struct {
|
||||
// kubeletInterface contains all the kubelet methods required by the server.
|
||||
// For testablitiy.
|
||||
type kubeletInterface interface {
|
||||
GetContainerStats(podID, containerName string) (*api.ContainerStats, error)
|
||||
GetMachineStats() (*api.ContainerStats, error)
|
||||
GetContainerStats(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||
GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||
GetPodInfo(name string) (api.PodInfo, error)
|
||||
}
|
||||
|
||||
@ -113,18 +115,25 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *KubeletServer) serveStats(w http.ResponseWriter, req *http.Request) {
|
||||
// /stats/<podid>/<containerName>
|
||||
components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/")
|
||||
var stats *api.ContainerStats
|
||||
var stats *info.ContainerInfo
|
||||
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) {
|
||||
case 1:
|
||||
// Machine stats
|
||||
stats, err = s.Kubelet.GetMachineStats()
|
||||
stats, err = s.Kubelet.GetMachineStats(&query)
|
||||
case 2:
|
||||
// pod stats
|
||||
// TODO(monnand) Implement this
|
||||
errors.New("pod level status currently unimplemented")
|
||||
case 3:
|
||||
stats, err = s.Kubelet.GetContainerStats(components[1], components[2])
|
||||
stats, err = s.Kubelet.GetContainerStats(components[1], components[2], &query)
|
||||
default:
|
||||
http.Error(w, "unknown resource.", http.StatusNotFound)
|
||||
return
|
||||
|
@ -29,24 +29,25 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/google/cadvisor/info"
|
||||
)
|
||||
|
||||
type fakeKubelet struct {
|
||||
infoFunc func(name string) (api.PodInfo, error)
|
||||
containerStatsFunc func(podID, containerName string) (*api.ContainerStats, error)
|
||||
machineStatsFunc func() (*api.ContainerStats, error)
|
||||
containerStatsFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||
machineStatsFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
|
||||
return fk.infoFunc(name)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) {
|
||||
return fk.containerStatsFunc(podID, containerName)
|
||||
func (fk *fakeKubelet) GetContainerStats(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
return fk.containerStatsFunc(podID, containerName, req)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetMachineStats() (*api.ContainerStats, error) {
|
||||
return fk.machineStatsFunc()
|
||||
func (fk *fakeKubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
return fk.machineStatsFunc(req)
|
||||
}
|
||||
|
||||
type serverTestFramework struct {
|
||||
@ -148,22 +149,24 @@ func TestPodInfo(t *testing.T) {
|
||||
|
||||
func TestContainerStats(t *testing.T) {
|
||||
fw := makeServerTest()
|
||||
expectedStats := &api.ContainerStats{
|
||||
MaxMemoryUsage: 1024001,
|
||||
CpuUsagePercentiles: []api.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
MemoryUsagePercentiles: []api.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
expectedStats := &info.ContainerInfo{
|
||||
StatsPercentiles: &info.ContainerStatsPercentiles{
|
||||
MaxMemoryUsage: 1024001,
|
||||
CpuUsagePercentiles: []info.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
MemoryUsagePercentiles: []info.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedPodID := "somepod"
|
||||
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 {
|
||||
return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName)
|
||||
}
|
||||
@ -175,7 +178,7 @@ func TestContainerStats(t *testing.T) {
|
||||
t.Fatalf("Got error GETing: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var receivedStats api.ContainerStats
|
||||
var receivedStats info.ContainerInfo
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err = decoder.Decode(&receivedStats)
|
||||
if err != nil {
|
||||
@ -188,20 +191,22 @@ func TestContainerStats(t *testing.T) {
|
||||
|
||||
func TestMachineStats(t *testing.T) {
|
||||
fw := makeServerTest()
|
||||
expectedStats := &api.ContainerStats{
|
||||
MaxMemoryUsage: 1024001,
|
||||
CpuUsagePercentiles: []api.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
MemoryUsagePercentiles: []api.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
expectedStats := &info.ContainerInfo{
|
||||
StatsPercentiles: &info.ContainerStatsPercentiles{
|
||||
MaxMemoryUsage: 1024001,
|
||||
CpuUsagePercentiles: []info.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
MemoryUsagePercentiles: []info.Percentile{
|
||||
{50, 150},
|
||||
{80, 180},
|
||||
{90, 190},
|
||||
},
|
||||
},
|
||||
}
|
||||
fw.fakeKubelet.machineStatsFunc = func() (*api.ContainerStats, error) {
|
||||
fw.fakeKubelet.machineStatsFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
return expectedStats, nil
|
||||
}
|
||||
|
||||
@ -210,7 +215,7 @@ func TestMachineStats(t *testing.T) {
|
||||
t.Fatalf("Got error GETing: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var receivedStats api.ContainerStats
|
||||
var receivedStats info.ContainerInfo
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err = decoder.Decode(&receivedStats)
|
||||
if err != nil {
|
||||
|
@ -911,8 +911,8 @@ type mockCadvisorClient struct {
|
||||
}
|
||||
|
||||
// ContainerInfo is a mock implementation of CadvisorInterface.ContainerInfo.
|
||||
func (c *mockCadvisorClient) ContainerInfo(name string) (*info.ContainerInfo, error) {
|
||||
args := c.Called(name)
|
||||
func (c *mockCadvisorClient) ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||
args := c.Called(name, req)
|
||||
return args.Get(0).(*info.ContainerInfo), args.Error(1)
|
||||
}
|
||||
|
||||
@ -924,7 +924,7 @@ func (c *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) {
|
||||
|
||||
func areSamePercentiles(
|
||||
cadvisorPercentiles []info.Percentile,
|
||||
kubePercentiles []api.Percentile,
|
||||
kubePercentiles []info.Percentile,
|
||||
t *testing.T,
|
||||
) {
|
||||
if len(cadvisorPercentiles) != len(kubePercentiles) {
|
||||
@ -973,7 +973,9 @@ func TestGetContainerStats(t *testing.T) {
|
||||
}
|
||||
|
||||
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.CadvisorClient = mockCadvisor
|
||||
@ -986,15 +988,15 @@ func TestGetContainerStats(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stats, err := kubelet.GetContainerStats("qux", "foo")
|
||||
stats, err := kubelet.GetContainerStats("qux", "foo", req)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@ -1018,7 +1020,9 @@ func TestGetMachineStats(t *testing.T) {
|
||||
}
|
||||
|
||||
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{
|
||||
DockerClient: &fakeDocker,
|
||||
@ -1027,15 +1031,15 @@ func TestGetMachineStats(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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")
|
||||
}
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CpuUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
|
||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@ -1050,19 +1054,19 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stats, _ := kubelet.GetContainerStats("qux", "foo")
|
||||
stats, _ := kubelet.GetContainerStats("qux", "foo", nil)
|
||||
// When there's no cAdvisor, the stats should be either nil or empty
|
||||
if stats == nil {
|
||||
return
|
||||
}
|
||||
if stats.MaxMemoryUsage != 0 {
|
||||
t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.MaxMemoryUsage)
|
||||
if stats.StatsPercentiles.MaxMemoryUsage != 0 {
|
||||
t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.StatsPercentiles.MaxMemoryUsage)
|
||||
}
|
||||
if len(stats.CpuUsagePercentiles) > 0 {
|
||||
t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.CpuUsagePercentiles)
|
||||
if len(stats.StatsPercentiles.CpuUsagePercentiles) > 0 {
|
||||
t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.CpuUsagePercentiles)
|
||||
}
|
||||
if len(stats.MemoryUsagePercentiles) > 0 {
|
||||
t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.MemoryUsagePercentiles)
|
||||
if len(stats.StatsPercentiles.MemoryUsagePercentiles) > 0 {
|
||||
t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.MemoryUsagePercentiles)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1072,8 +1076,10 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
|
||||
|
||||
containerInfo := &info.ContainerInfo{}
|
||||
mockCadvisor := &mockCadvisorClient{}
|
||||
req := &info.ContainerInfoRequest{}
|
||||
cadvisorReq := getCadvisorContainerInfoRequest(req)
|
||||
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.CadvisorClient = mockCadvisor
|
||||
@ -1086,7 +1092,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
stats, err := kubelet.GetContainerStats("qux", "foo")
|
||||
stats, err := kubelet.GetContainerStats("qux", "foo", req)
|
||||
if stats != nil {
|
||||
t.Errorf("non-nil stats on error")
|
||||
}
|
||||
@ -1107,7 +1113,7 @@ func TestGetContainerStatsOnNonExistContainer(t *testing.T) {
|
||||
kubelet.CadvisorClient = mockCadvisor
|
||||
fakeDocker.containerList = []docker.APIContainers{}
|
||||
|
||||
stats, _ := kubelet.GetContainerStats("qux", "foo")
|
||||
stats, _ := kubelet.GetContainerStats("qux", "foo", nil)
|
||||
if stats != nil {
|
||||
t.Errorf("non-nil stats on non exist container")
|
||||
}
|
||||
|
3
third_party/deps.sh
vendored
3
third_party/deps.sh
vendored
@ -5,6 +5,8 @@ TOP_PACKAGES="
|
||||
code.google.com/p/goauth2/compute/serviceaccount
|
||||
code.google.com/p/goauth2/oauth
|
||||
code.google.com/p/google-api-go-client/compute/v1
|
||||
github.com/google/cadvisor/info
|
||||
github.com/google/cadvisor/client
|
||||
"
|
||||
|
||||
DEP_PACKAGES="
|
||||
@ -13,7 +15,6 @@ DEP_PACKAGES="
|
||||
code.google.com/p/google-api-go-client/googleapi
|
||||
github.com/coreos/go-log/log
|
||||
github.com/coreos/go-systemd/journal
|
||||
github.com/google/cadvisor/info
|
||||
"
|
||||
|
||||
PACKAGES="$TOP_PACKAGES $DEP_PACKAGES"
|
||||
|
1
third_party/src/github.com/google/cadvisor/.gitignore
vendored
Normal file
1
third_party/src/github.com/google/cadvisor/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.swp
|
@ -1,13 +1,12 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
before_script:
|
||||
- go get github.com/stretchr/testify/mock
|
||||
- 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:
|
||||
- go test -v -race github.com/google/cadvisor/container
|
||||
- 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 test -v -race github.com/google/cadvisor/...
|
||||
- go build github.com/google/cadvisor
|
||||
|
20
third_party/src/github.com/google/cadvisor/CHANGELOG.md
vendored
Normal file
20
third_party/src/github.com/google/cadvisor/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# 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 @@
|
||||
|
||||
# Please keep the list sorted by first name.
|
||||
|
||||
Jason Swindle <jason@swindle.me>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Kamil Yurtsever <kyurtsever@google.com>
|
||||
Nan Deng <dengnan@google.com>
|
||||
Rohit Jnagal <jnagal@google.com>
|
||||
Victor Marmol <vmarmol@google.com>
|
||||
Zohaib Maya <zohaib@google.com>
|
||||
|
32
third_party/src/github.com/google/cadvisor/advice/interference/detector.go
vendored
Normal file
32
third_party/src/github.com/google/cadvisor/advice/interference/detector.go
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// 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
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/google/cadvisor/manager"
|
||||
)
|
||||
|
||||
@ -34,9 +35,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 +49,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 +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)
|
||||
}
|
||||
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.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.
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -27,32 +27,46 @@ import (
|
||||
"github.com/google/cadvisor/manager"
|
||||
"github.com/google/cadvisor/pages"
|
||||
"github.com/google/cadvisor/pages/static"
|
||||
"github.com/google/cadvisor/storage/memory"
|
||||
)
|
||||
|
||||
var argPort = flag.Int("port", 8080, "port to listen")
|
||||
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 argAllowLmctfy = flag.Bool("allow_lmctfy", true, "whether to allow lmctfy as a container handler")
|
||||
|
||||
var argDbDriver = flag.String("storage_driver", "memory", "storage driver to use. Options are: memory (default) and influxdb")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
storage := memory.New(*argSampleSize, *argHistoryDuration)
|
||||
// TODO(monnand): Add stats writer for manager
|
||||
containerManager, err := manager.New(storage)
|
||||
storageDriver, err := NewStorageDriver(*argDbDriver)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %s", err)
|
||||
}
|
||||
|
||||
containerManager, err := manager.New(storageDriver)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create a Container Manager: %s", err)
|
||||
}
|
||||
|
||||
if err := lmctfy.Register("/"); err != nil {
|
||||
log.Printf("lmctfy registration failed: %v.", err)
|
||||
log.Print("Running in docker only mode.")
|
||||
if err := docker.Register(containerManager, "/"); err != nil {
|
||||
log.Printf("Docker registration failed: %v.", err)
|
||||
log.Fatalf("Unable to continue without docker or lmctfy.")
|
||||
// Register lmctfy for the root if allowed and available.
|
||||
registeredRoot := false
|
||||
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 {
|
||||
log.Printf("Docker registration failed: %v.", err)
|
||||
log.Fatalf("Unable to continue without root handler.")
|
||||
}
|
||||
}
|
||||
|
||||
// Register Docker for all Docker containers.
|
||||
if err := docker.Register(containerManager, "/docker"); err != nil {
|
||||
// Ignore this error because we should work with lmctfy only
|
||||
log.Printf("Docker registration failed: %v.", err)
|
||||
@ -69,7 +83,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)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package cadvisor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string {
|
||||
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
|
||||
u := self.machineInfoUrl()
|
||||
ret := new(info.MachineInfo)
|
||||
err = self.httpGetJsonData(ret, u, "machine info")
|
||||
err = self.httpGetJsonData(ret, nil, u, "machine info")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string {
|
||||
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
|
||||
}
|
||||
|
||||
func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error {
|
||||
resp, err := http.Get(url)
|
||||
func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error {
|
||||
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 {
|
||||
err = fmt.Errorf("unable to get %v: %v", infoName, err)
|
||||
return err
|
||||
@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro
|
||||
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)
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -21,33 +21,42 @@ import (
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info"
|
||||
itest "github.com/google/cadvisor/info/test"
|
||||
"github.com/kr/pretty"
|
||||
)
|
||||
|
||||
func testGetJsonData(
|
||||
strRep string,
|
||||
emptyData interface{},
|
||||
expected interface{},
|
||||
f func() (interface{}, error),
|
||||
) error {
|
||||
err := json.Unmarshal([]byte(strRep), emptyData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid json input: %v", err)
|
||||
}
|
||||
reply, err := f()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve data: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(reply, emptyData) {
|
||||
return fmt.Errorf("retrieved wrong data: %+v != %+v", reply, emptyData)
|
||||
if !reflect.DeepEqual(reply, expected) {
|
||||
return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
||||
func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == path {
|
||||
fmt.Fprint(w, reply)
|
||||
if expectedPostObj != nil {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(expectedPostObjEmpty)
|
||||
if err != nil {
|
||||
t.Errorf("Recieved invalid object: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) {
|
||||
t.Errorf("Recieved unexpected object: %+v", expectedPostObjEmpty)
|
||||
}
|
||||
}
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.Encode(replyObj)
|
||||
} else if r.URL.Path == "/api/v1.0/machine" {
|
||||
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
|
||||
} else {
|
||||
@ -64,693 +73,69 @@ func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
||||
}
|
||||
|
||||
func TestGetMachineinfo(t *testing.T) {
|
||||
respStr := `{"num_cores":8,"memory_capacity":31625871360}`
|
||||
client, server, err := cadvisorTestClient("/api/v1.0/machine", respStr)
|
||||
minfo := &info.MachineInfo{
|
||||
NumCores: 8,
|
||||
MemoryCapacity: 31625871360,
|
||||
}
|
||||
client, server, err := cadvisorTestClient("/api/v1.0/machine", nil, nil, minfo, t)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get a client %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
err = testGetJsonData(respStr, &info.MachineInfo{}, func() (interface{}, error) {
|
||||
return client.MachineInfo()
|
||||
})
|
||||
returned, err := client.MachineInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(returned, minfo) {
|
||||
t.Fatalf("received unexpected machine info")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainerInfo(t *testing.T) {
|
||||
respStr := `
|
||||
{
|
||||
"name": "%v",
|
||||
"spec": {
|
||||
"cpu": {
|
||||
"limit": 18446744073709551000,
|
||||
"max_limit": 0,
|
||||
"mask": {
|
||||
"data": [
|
||||
18446744073709551000
|
||||
]
|
||||
}
|
||||
},
|
||||
"memory": {
|
||||
"limit": 18446744073709551000,
|
||||
"swap_limit": 18446744073709551000
|
||||
}
|
||||
},
|
||||
"stats": [
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:26.434981825Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
||||
"cpu": {
|
||||
"usage": {
|
||||
"total": 56896502,
|
||||
"per_cpu": [
|
||||
20479682,
|
||||
13579420,
|
||||
6025040,
|
||||
2255123,
|
||||
3635661,
|
||||
2489368,
|
||||
5158288,
|
||||
3273920
|
||||
],
|
||||
"user": 10000000,
|
||||
"system": 30000000
|
||||
},
|
||||
"load": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616,
|
||||
"container_data": {
|
||||
"pgfault": 2279
|
||||
},
|
||||
"hierarchical_data": {
|
||||
"pgfault": 2279
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"stats_summary": {
|
||||
"max_memory_usage": 495616,
|
||||
"samples": [
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
||||
"duration": 1103412783,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
||||
"duration": 1101907464,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
||||
"duration": 1102171008,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
||||
"duration": 1102021457,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
||||
"duration": 1102262529,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
||||
"duration": 1103456996,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
||||
"duration": 1102007798,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
||||
"duration": 1101952050,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
||||
"duration": 1101728259,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
||||
"duration": 1101683759,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
||||
"duration": 1101831451,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
||||
"duration": 1103560650,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
||||
"duration": 1101693203,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
||||
"duration": 1101785227,
|
||||
"cpu": {
|
||||
"usage": 0
|
||||
},
|
||||
"memory": {
|
||||
"usage": 495616
|
||||
}
|
||||
}
|
||||
],
|
||||
"memory_usage_percentiles": [
|
||||
{
|
||||
"percentage": 50,
|
||||
"value": 495616
|
||||
},
|
||||
{
|
||||
"percentage": 80,
|
||||
"value": 495616
|
||||
},
|
||||
{
|
||||
"percentage": 90,
|
||||
"value": 495616
|
||||
},
|
||||
{
|
||||
"percentage": 95,
|
||||
"value": 495616
|
||||
},
|
||||
{
|
||||
"percentage": 99,
|
||||
"value": 495616
|
||||
}
|
||||
],
|
||||
"cpu_usage_percentiles": [
|
||||
{
|
||||
"percentage": 50,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"percentage": 80,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"percentage": 90,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"percentage": 95,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"percentage": 99,
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
query := &info.ContainerInfoRequest{
|
||||
NumStats: 3,
|
||||
NumSamples: 2,
|
||||
CpuUsagePercentiles: []int{10, 50, 90},
|
||||
MemoryUsagePercentages: []int{10, 80, 90},
|
||||
}
|
||||
containerName := "/some/container"
|
||||
respStr = fmt.Sprintf(respStr, containerName)
|
||||
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), respStr)
|
||||
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second)
|
||||
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get a client %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
err = testGetJsonData(respStr, &info.ContainerInfo{}, func() (interface{}, error) {
|
||||
return client.ContainerInfo(containerName)
|
||||
})
|
||||
returned, err := client.ContainerInfo(containerName, query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We cannot use DeepEqual() to compare them directly,
|
||||
// because json en/decoded time may have precision issues.
|
||||
if !reflect.DeepEqual(returned.ContainerReference, cinfo.ContainerReference) {
|
||||
t.Errorf("received unexpected container ref")
|
||||
}
|
||||
if !reflect.DeepEqual(returned.Subcontainers, cinfo.Subcontainers) {
|
||||
t.Errorf("received unexpected subcontainers")
|
||||
}
|
||||
if !reflect.DeepEqual(returned.Spec, cinfo.Spec) {
|
||||
t.Errorf("received unexpected spec")
|
||||
}
|
||||
if !reflect.DeepEqual(returned.StatsPercentiles, cinfo.StatsPercentiles) {
|
||||
t.Errorf("received unexpected spec")
|
||||
}
|
||||
|
||||
for i, expectedStats := range cinfo.Stats {
|
||||
returnedStats := returned.Stats[i]
|
||||
if !expectedStats.Eq(returnedStats) {
|
||||
t.Errorf("received unexpected stats")
|
||||
}
|
||||
}
|
||||
|
||||
for i, expectedSample := range cinfo.Samples {
|
||||
returnedSample := returned.Samples[i]
|
||||
if !expectedSample.Eq(returnedSample) {
|
||||
t.Errorf("received unexpected sample")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func (self *dockerContainerHandler) isDockerContainer() bool {
|
||||
}
|
||||
|
||||
// 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"
|
||||
configPath := path.Join(dir, id, "container.json")
|
||||
f, err := os.Open(configPath)
|
||||
@ -127,7 +127,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
|
||||
}
|
||||
defer f.Close()
|
||||
d := json.NewDecoder(f)
|
||||
ret := new(libcontainer.Container)
|
||||
ret := new(libcontainer.Config)
|
||||
err = d.Decode(ret)
|
||||
if err != nil {
|
||||
return
|
||||
@ -136,7 +136,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
|
||||
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.Memory = new(info.MemorySpec)
|
||||
spec.Memory.Limit = math.MaxUint64
|
||||
@ -209,6 +209,12 @@ func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.
|
||||
ret.Memory.ContainerData.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
|
||||
}
|
||||
|
||||
|
88
third_party/src/github.com/google/cadvisor/container/test/mock.go
vendored
Normal file
88
third_party/src/github.com/google/cadvisor/container/test/mock.go
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
// 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
|
||||
# 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/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
|
||||
|
||||
EXPOSE 8080
|
||||
|
34
third_party/src/github.com/google/cadvisor/info/advice.go
vendored
Normal file
34
third_party/src/github.com/google/cadvisor/info/advice.go
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
@ -57,6 +58,40 @@ type ContainerReference struct {
|
||||
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 {
|
||||
ContainerReference
|
||||
|
||||
@ -119,7 +154,7 @@ type CpuStats struct {
|
||||
|
||||
// Per CPU/core usage of the container.
|
||||
// Unit: nanoseconds.
|
||||
PerCpu []uint64 `json:"per_cpu,omitempty"`
|
||||
PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
|
||||
|
||||
// Time spent in user space.
|
||||
// Unit: nanoseconds
|
||||
@ -165,6 +200,42 @@ type ContainerStats struct {
|
||||
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 {
|
||||
// Timetamp of the end of the sample period
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
@ -173,6 +244,9 @@ type ContainerStatsSample struct {
|
||||
Cpu struct {
|
||||
// number of nanoseconds of CPU time used by the container
|
||||
Usage uint64 `json:"usage"`
|
||||
|
||||
// Per-core usage of the container. (unit: nanoseconds)
|
||||
PerCpuUsage []uint64 `json:"per_cpu_usage,omitempty"`
|
||||
} `json:"cpu"`
|
||||
Memory struct {
|
||||
// Units: Bytes.
|
||||
@ -180,6 +254,67 @@ type ContainerStatsSample struct {
|
||||
} `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 {
|
||||
Percentage int `json:"percentage"`
|
||||
Value uint64 `json:"value"`
|
||||
@ -211,9 +346,28 @@ func NewSample(prev, current *ContainerStats) (*ContainerStatsSample, error) {
|
||||
if current.Cpu.Usage.Total < prev.Cpu.Usage.Total {
|
||||
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)
|
||||
// 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.PerCpuUsage = percpu
|
||||
// Memory usage is current memory usage
|
||||
sample.Memory.Usage = current.Memory.Usage
|
||||
sample.Timestamp = current.Timestamp
|
||||
|
@ -15,6 +15,7 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@ -230,3 +231,68 @@ func TestAddSampleWrongCpuUsage(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
127
third_party/src/github.com/google/cadvisor/info/test/datagen.go
vendored
Normal file
127
third_party/src/github.com/google/cadvisor/info/test/datagen.go
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
// 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 @@
|
||||
package info
|
||||
|
||||
// Version of cAdvisor.
|
||||
const VERSION = "0.1.0"
|
||||
const VERSION = "0.1.2"
|
||||
|
237
third_party/src/github.com/google/cadvisor/manager/container_test.go
vendored
Normal file
237
third_party/src/github.com/google/cadvisor/manager/container_test.go
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
// 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 {
|
||||
Start() error
|
||||
|
||||
// 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.
|
||||
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.ContainerInfoRequest) (*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.FillDefaults()
|
||||
percentiles, err = m.storageDriver.Percentiles(
|
||||
cinfo.Name,
|
||||
[]int{50, 80, 90, 99},
|
||||
[]int{50, 80, 90, 99},
|
||||
query.CpuUsagePercentiles,
|
||||
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
|
||||
}
|
||||
|
253
third_party/src/github.com/google/cadvisor/manager/manager_test.go
vendored
Normal file
253
third_party/src/github.com/google/cadvisor/manager/manager_test.go
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
// 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)
|
||||
containerName := u.Path[len(ContainersPage)-1:]
|
||||
|
||||
// Get the container.
|
||||
cont, err := m.GetContainerInfo(containerName)
|
||||
reqParams := info.ContainerInfoRequest{
|
||||
NumStats: 60,
|
||||
NumSamples: 60,
|
||||
}
|
||||
cont, err := m.GetContainerInfo(containerName, &reqParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ function drawCpuPerCoreUsage(elementId, machineInfo, stats) {
|
||||
elements.push(cur.timestamp);
|
||||
for (var j = 0; j < machineInfo.num_cores; j++) {
|
||||
// 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);
|
||||
}
|
||||
|
498
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go
vendored
Normal file
498
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go
vendored
Normal file
@ -0,0 +1,498 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/google/cadvisor/storage"
|
||||
"github.com/influxdb/influxdb-go"
|
||||
)
|
||||
|
||||
type influxdbStorage struct {
|
||||
client *influxdb.Client
|
||||
prevStats *info.ContainerStats
|
||||
machineName string
|
||||
tableName string
|
||||
windowLen time.Duration
|
||||
}
|
||||
|
||||
const (
|
||||
colTimestamp string = "timestamp"
|
||||
colMachineName string = "machine"
|
||||
colContainerName string = "container_name"
|
||||
colCpuCumulativeUsage string = "cpu_cumulative_usage"
|
||||
// Cumulative Cpu Usage in system mode
|
||||
colCpuCumulativeUsageSystem string = "cpu_cumulative_usage_system"
|
||||
// Cumulative Cpu Usage in user mode
|
||||
colCpuCumulativeUsageUser string = "cpu_cumulative_usage_user"
|
||||
// Memory Usage
|
||||
colMemoryUsage string = "memory_usage"
|
||||
// Working set size
|
||||
colMemoryWorkingSet string = "memory_working_set"
|
||||
// container page fault
|
||||
colMemoryContainerPgfault string = "memory_container_pgfault"
|
||||
// container major page fault
|
||||
colMemoryContainerPgmajfault string = "memory_container_pgmajfault"
|
||||
// hierarchical page fault
|
||||
colMemoryHierarchicalPgfault string = "memory_hierarchical_pgfault"
|
||||
// hierarchical major page fault
|
||||
colMemoryHierarchicalPgmajfault string = "memory_hierarchical_pgmajfault"
|
||||
// Cumulative per core usage
|
||||
colPerCoreCumulativeUsagePrefix string = "per_core_cumulative_usage_core_"
|
||||
// Optional: sample duration. Unit: Nanosecond.
|
||||
colSampleDuration string = "sample_duration"
|
||||
// Optional: Instant cpu usage
|
||||
colCpuInstantUsage string = "cpu_instant_usage"
|
||||
// Optional: Instant per core usage
|
||||
colPerCoreInstantUsagePrefix string = "per_core_instant_usage_core_"
|
||||
)
|
||||
|
||||
func (self *influxdbStorage) containerStatsToValues(
|
||||
ref info.ContainerReference,
|
||||
stats *info.ContainerStats,
|
||||
) (columns []string, values []interface{}) {
|
||||
|
||||
// Timestamp
|
||||
columns = append(columns, colTimestamp)
|
||||
values = append(values, stats.Timestamp.Format(time.RFC3339Nano))
|
||||
|
||||
// Machine name
|
||||
columns = append(columns, colMachineName)
|
||||
values = append(values, self.machineName)
|
||||
|
||||
// Container name
|
||||
columns = append(columns, colContainerName)
|
||||
values = append(values, ref.Name)
|
||||
|
||||
// Cumulative Cpu Usage
|
||||
columns = append(columns, colCpuCumulativeUsage)
|
||||
values = append(values, stats.Cpu.Usage.Total)
|
||||
|
||||
// Cumulative Cpu Usage in system mode
|
||||
columns = append(columns, colCpuCumulativeUsageSystem)
|
||||
values = append(values, stats.Cpu.Usage.System)
|
||||
|
||||
// Cumulative Cpu Usage in user mode
|
||||
columns = append(columns, colCpuCumulativeUsageUser)
|
||||
values = append(values, stats.Cpu.Usage.User)
|
||||
|
||||
// Memory Usage
|
||||
columns = append(columns, colMemoryUsage)
|
||||
values = append(values, stats.Memory.Usage)
|
||||
|
||||
// Working set size
|
||||
columns = append(columns, colMemoryWorkingSet)
|
||||
values = append(values, stats.Memory.WorkingSet)
|
||||
|
||||
// container page fault
|
||||
columns = append(columns, colMemoryContainerPgfault)
|
||||
values = append(values, stats.Memory.ContainerData.Pgfault)
|
||||
|
||||
// container major page fault
|
||||
columns = append(columns, colMemoryContainerPgmajfault)
|
||||
values = append(values, stats.Memory.ContainerData.Pgmajfault)
|
||||
|
||||
// hierarchical page fault
|
||||
columns = append(columns, colMemoryHierarchicalPgfault)
|
||||
values = append(values, stats.Memory.HierarchicalData.Pgfault)
|
||||
|
||||
// hierarchical major page fault
|
||||
columns = append(columns, colMemoryHierarchicalPgmajfault)
|
||||
values = append(values, stats.Memory.HierarchicalData.Pgmajfault)
|
||||
|
||||
// per cpu cumulative usage
|
||||
for i, u := range stats.Cpu.Usage.PerCpu {
|
||||
columns = append(columns, fmt.Sprintf("%v%v", colPerCoreCumulativeUsagePrefix, i))
|
||||
values = append(values, u)
|
||||
}
|
||||
|
||||
sample, err := info.NewSample(self.prevStats, stats)
|
||||
if err != nil || sample == nil {
|
||||
return columns, values
|
||||
}
|
||||
|
||||
// Optional: sample duration. Unit: Nanosecond.
|
||||
columns = append(columns, colSampleDuration)
|
||||
values = append(values, sample.Duration.String())
|
||||
|
||||
// Optional: Instant cpu usage
|
||||
columns = append(columns, colCpuInstantUsage)
|
||||
values = append(values, sample.Cpu.Usage)
|
||||
|
||||
// Optional: Instant per core usage
|
||||
for i, u := range sample.Cpu.PerCpuUsage {
|
||||
columns = append(columns, fmt.Sprintf("%v%v", colPerCoreInstantUsagePrefix, i))
|
||||
values = append(values, u)
|
||||
}
|
||||
|
||||
return columns, values
|
||||
}
|
||||
|
||||
func convertToUint64(v interface{}) (uint64, error) {
|
||||
if v == nil {
|
||||
return 0, nil
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case uint64:
|
||||
return x, nil
|
||||
case int:
|
||||
if x < 0 {
|
||||
return 0, fmt.Errorf("negative value: %v", x)
|
||||
}
|
||||
return uint64(x), nil
|
||||
case int32:
|
||||
if x < 0 {
|
||||
return 0, fmt.Errorf("negative value: %v", x)
|
||||
}
|
||||
return uint64(x), nil
|
||||
case int64:
|
||||
if x < 0 {
|
||||
return 0, fmt.Errorf("negative value: %v", x)
|
||||
}
|
||||
return uint64(x), nil
|
||||
case float64:
|
||||
if x < 0 {
|
||||
return 0, fmt.Errorf("negative value: %v", x)
|
||||
}
|
||||
return uint64(x), nil
|
||||
case uint32:
|
||||
return uint64(x), nil
|
||||
}
|
||||
return 0, fmt.Errorf("Unknown type")
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) valuesToContainerStats(columns []string, values []interface{}) (*info.ContainerStats, error) {
|
||||
stats := &info.ContainerStats{
|
||||
Cpu: &info.CpuStats{},
|
||||
Memory: &info.MemoryStats{},
|
||||
}
|
||||
perCoreUsage := make(map[int]uint64, 32)
|
||||
var err error
|
||||
for i, col := range columns {
|
||||
v := values[i]
|
||||
switch {
|
||||
case col == colTimestamp:
|
||||
if str, ok := v.(string); ok {
|
||||
stats.Timestamp, err = time.Parse(time.RFC3339Nano, str)
|
||||
}
|
||||
case col == colMachineName:
|
||||
if m, ok := v.(string); ok {
|
||||
if m != self.machineName {
|
||||
return nil, fmt.Errorf("different machine")
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("machine name field is not a string: %v", v)
|
||||
}
|
||||
// Cumulative Cpu Usage
|
||||
case col == colCpuCumulativeUsage:
|
||||
stats.Cpu.Usage.Total, err = convertToUint64(v)
|
||||
// Cumulative Cpu used by the system
|
||||
case col == colCpuCumulativeUsageSystem:
|
||||
stats.Cpu.Usage.System, err = convertToUint64(v)
|
||||
// Cumulative Cpu Usage in user mode
|
||||
case col == colCpuCumulativeUsageUser:
|
||||
stats.Cpu.Usage.User, err = convertToUint64(v)
|
||||
// Memory Usage
|
||||
case col == colMemoryUsage:
|
||||
stats.Memory.Usage, err = convertToUint64(v)
|
||||
// Working set size
|
||||
case col == colMemoryWorkingSet:
|
||||
stats.Memory.WorkingSet, err = convertToUint64(v)
|
||||
// container page fault
|
||||
case col == colMemoryContainerPgfault:
|
||||
stats.Memory.ContainerData.Pgfault, err = convertToUint64(v)
|
||||
// container major page fault
|
||||
case col == colMemoryContainerPgmajfault:
|
||||
stats.Memory.ContainerData.Pgmajfault, err = convertToUint64(v)
|
||||
// hierarchical page fault
|
||||
case col == colMemoryHierarchicalPgfault:
|
||||
stats.Memory.HierarchicalData.Pgfault, err = convertToUint64(v)
|
||||
// hierarchical major page fault
|
||||
case col == colMemoryHierarchicalPgmajfault:
|
||||
stats.Memory.HierarchicalData.Pgmajfault, err = convertToUint64(v)
|
||||
case strings.HasPrefix(col, colPerCoreCumulativeUsagePrefix):
|
||||
idxStr := col[len(colPerCoreCumulativeUsagePrefix):]
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
perCoreUsage[idx], err = convertToUint64(v)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
|
||||
}
|
||||
}
|
||||
stats.Cpu.Usage.PerCpu = make([]uint64, len(perCoreUsage))
|
||||
for idx, usage := range perCoreUsage {
|
||||
stats.Cpu.Usage.PerCpu[idx] = usage
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) valuesToContainerSample(columns []string, values []interface{}) (*info.ContainerStatsSample, error) {
|
||||
sample := &info.ContainerStatsSample{}
|
||||
perCoreUsage := make(map[int]uint64, 32)
|
||||
var err error
|
||||
for i, col := range columns {
|
||||
v := values[i]
|
||||
switch {
|
||||
case col == colTimestamp:
|
||||
if str, ok := v.(string); ok {
|
||||
sample.Timestamp, err = time.Parse(time.RFC3339Nano, str)
|
||||
}
|
||||
case col == colMachineName:
|
||||
if m, ok := v.(string); ok {
|
||||
if m != self.machineName {
|
||||
return nil, fmt.Errorf("different machine")
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("machine name field is not a string: %v", v)
|
||||
}
|
||||
// Memory Usage
|
||||
case col == colMemoryUsage:
|
||||
sample.Memory.Usage, err = convertToUint64(v)
|
||||
// sample duration. Unit: Nanosecond.
|
||||
case col == colSampleDuration:
|
||||
if v == nil {
|
||||
// this record does not have sample_duration, so it's the first stats.
|
||||
return nil, nil
|
||||
}
|
||||
sample.Duration, err = time.ParseDuration(v.(string))
|
||||
// Instant cpu usage
|
||||
case col == colCpuInstantUsage:
|
||||
sample.Cpu.Usage, err = convertToUint64(v)
|
||||
case strings.HasPrefix(col, colPerCoreInstantUsagePrefix):
|
||||
idxStr := col[len(colPerCoreInstantUsagePrefix):]
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
perCoreUsage[idx], err = convertToUint64(v)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
|
||||
}
|
||||
}
|
||||
sample.Cpu.PerCpuUsage = make([]uint64, len(perCoreUsage))
|
||||
for idx, usage := range perCoreUsage {
|
||||
sample.Cpu.PerCpuUsage[idx] = usage
|
||||
}
|
||||
if sample.Duration.Nanoseconds() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return sample, nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||
series := &influxdb.Series{
|
||||
Name: self.tableName,
|
||||
// There's only one point for each stats
|
||||
Points: make([][]interface{}, 1),
|
||||
}
|
||||
if stats == nil || stats.Cpu == nil || stats.Memory == nil {
|
||||
return nil
|
||||
}
|
||||
series.Columns, series.Points[0] = self.containerStatsToValues(ref, stats)
|
||||
|
||||
self.prevStats = stats.Copy(self.prevStats)
|
||||
err := self.client.WriteSeries([]*influxdb.Series{series})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
|
||||
// TODO(dengnan): select only columns that we need
|
||||
// TODO(dengnan): escape names
|
||||
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
|
||||
if numStats > 0 {
|
||||
query = fmt.Sprintf("%v limit %v", query, numStats)
|
||||
}
|
||||
series, err := self.client.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statsList := make([]*info.ContainerStats, 0, len(series))
|
||||
// By default, influxDB returns data in time descending order.
|
||||
// RecentStats() requires stats in time increasing order,
|
||||
// so we need to go through from the last one to the first one.
|
||||
for i := len(series) - 1; i >= 0; i-- {
|
||||
s := series[i]
|
||||
for j := len(s.Points) - 1; j >= 0; j-- {
|
||||
values := s.Points[j]
|
||||
stats, err := self.valuesToContainerStats(s.Columns, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stats == nil {
|
||||
continue
|
||||
}
|
||||
statsList = append(statsList, stats)
|
||||
}
|
||||
}
|
||||
return statsList, nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||
// TODO(dengnan): select only columns that we need
|
||||
// TODO(dengnan): escape names
|
||||
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
|
||||
if numSamples > 0 {
|
||||
query = fmt.Sprintf("%v limit %v", query, numSamples)
|
||||
}
|
||||
series, err := self.client.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sampleList := make([]*info.ContainerStatsSample, 0, len(series))
|
||||
for i := len(series) - 1; i >= 0; i-- {
|
||||
s := series[i]
|
||||
for j := len(s.Points) - 1; j >= 0; j-- {
|
||||
values := s.Points[j]
|
||||
sample, err := self.valuesToContainerSample(s.Columns, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sample == nil {
|
||||
continue
|
||||
}
|
||||
sampleList = append(sampleList, sample)
|
||||
}
|
||||
}
|
||||
return sampleList, nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) Close() error {
|
||||
self.client = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *influxdbStorage) Percentiles(
|
||||
containerName string,
|
||||
cpuUsagePercentiles []int,
|
||||
memUsagePercentiles []int,
|
||||
) (*info.ContainerStatsPercentiles, error) {
|
||||
selectedCol := make([]string, 0, len(cpuUsagePercentiles)+len(memUsagePercentiles)+1)
|
||||
|
||||
selectedCol = append(selectedCol, fmt.Sprintf("max(%v)", colMemoryUsage))
|
||||
for _, p := range cpuUsagePercentiles {
|
||||
selectedCol = append(selectedCol, fmt.Sprintf("percentile(%v, %v)", colCpuInstantUsage, p))
|
||||
}
|
||||
for _, p := range memUsagePercentiles {
|
||||
selectedCol = append(selectedCol, fmt.Sprintf("percentile(%v, %v)", colMemoryUsage, p))
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("select %v from %v where %v='%v' and %v='%v' and time > now() - %v",
|
||||
strings.Join(selectedCol, ","),
|
||||
self.tableName,
|
||||
colContainerName,
|
||||
containerName,
|
||||
colMachineName,
|
||||
self.machineName,
|
||||
fmt.Sprintf("%vs", self.windowLen.Seconds()),
|
||||
)
|
||||
series, err := self.client.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(series) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(series[0].Points) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
point := series[0].Points[0]
|
||||
|
||||
ret := new(info.ContainerStatsPercentiles)
|
||||
ret.MaxMemoryUsage, err = convertToUint64(point[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid max memory usage: %v", err)
|
||||
}
|
||||
retrievedCpuPercentiles := point[2 : 2+len(cpuUsagePercentiles)]
|
||||
for i, p := range cpuUsagePercentiles {
|
||||
v, err := convertToUint64(retrievedCpuPercentiles[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cpu usage: %v", err)
|
||||
}
|
||||
ret.CpuUsagePercentiles = append(
|
||||
ret.CpuUsagePercentiles,
|
||||
info.Percentile{
|
||||
Percentage: p,
|
||||
Value: v,
|
||||
},
|
||||
)
|
||||
}
|
||||
retrievedMemoryPercentiles := point[2+len(cpuUsagePercentiles):]
|
||||
for i, p := range memUsagePercentiles {
|
||||
v, err := convertToUint64(retrievedMemoryPercentiles[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid memory usage: %v", err)
|
||||
}
|
||||
ret.MemoryUsagePercentiles = append(
|
||||
ret.MemoryUsagePercentiles,
|
||||
info.Percentile{
|
||||
Percentage: p,
|
||||
Value: v,
|
||||
},
|
||||
)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// machineName: A unique identifier to identify the host that current cAdvisor
|
||||
// instance is running on.
|
||||
// influxdbHost: The host which runs influxdb.
|
||||
// percentilesDuration: Time window which will be considered when calls Percentiles()
|
||||
func New(machineName,
|
||||
tablename,
|
||||
database,
|
||||
username,
|
||||
password,
|
||||
influxdbHost string,
|
||||
isSecure bool,
|
||||
percentilesDuration time.Duration,
|
||||
) (storage.StorageDriver, error) {
|
||||
config := &influxdb.ClientConfig{
|
||||
Host: influxdbHost,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Database: database,
|
||||
IsSecure: isSecure,
|
||||
}
|
||||
client, err := influxdb.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(monnand): With go 1.3, we cannot compress data now.
|
||||
client.DisableCompression()
|
||||
if percentilesDuration.Seconds() < 1.0 {
|
||||
percentilesDuration = 5 * time.Minute
|
||||
}
|
||||
|
||||
ret := &influxdbStorage{
|
||||
client: client,
|
||||
windowLen: percentilesDuration,
|
||||
machineName: machineName,
|
||||
tableName: tablename,
|
||||
}
|
||||
return ret, nil
|
||||
}
|
136
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go
vendored
Normal file
136
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
// 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) {
|
||||
self.prevStats = nil
|
||||
return
|
||||
}
|
||||
if self.prevStats == nil {
|
||||
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
|
||||
self.prevStats = stats.Copy(self.prevStats)
|
||||
}
|
||||
|
||||
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 {
|
||||
self.recentStats.Remove(self.recentStats.Front())
|
||||
self.recentStats.Remove(self.recentStats.Back())
|
||||
}
|
||||
self.recentStats.PushBack(stats)
|
||||
self.recentStats.PushFront(stats)
|
||||
self.updatePrevStats(stats)
|
||||
return nil
|
||||
}
|
||||
@ -88,18 +75,25 @@ func (self *containerStorage) RecentStats(numStats int) ([]*info.ContainerStats,
|
||||
if self.recentStats.Len() < numStats || numStats < 0 {
|
||||
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()
|
||||
for i := 0; i < numStats; i++ {
|
||||
for i := numStats - 1; i >= 0; i-- {
|
||||
data, ok := e.Value.(*info.ContainerStats)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("The %vth element is not a ContainerStats", i)
|
||||
}
|
||||
ret = append(ret, data)
|
||||
ret[i] = data
|
||||
e = e.Next()
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@ -162,23 +156,34 @@ type InMemoryStorage struct {
|
||||
func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||
var cstore *containerStorage
|
||||
var ok bool
|
||||
self.lock.Lock()
|
||||
if cstore, ok = self.containerStorageMap[ref.Name]; !ok {
|
||||
cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats)
|
||||
self.containerStorageMap[ref.Name] = cstore
|
||||
}
|
||||
self.lock.Unlock()
|
||||
|
||||
func() {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
if cstore, ok = self.containerStorageMap[ref.Name]; !ok {
|
||||
cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats)
|
||||
self.containerStorageMap[ref.Name] = cstore
|
||||
}
|
||||
}()
|
||||
return cstore.AddStats(stats)
|
||||
}
|
||||
|
||||
func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||
var cstore *containerStorage
|
||||
var ok bool
|
||||
self.lock.RLock()
|
||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
||||
|
||||
err := func() error {
|
||||
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)
|
||||
}
|
||||
@ -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) {
|
||||
var cstore *containerStorage
|
||||
var ok bool
|
||||
self.lock.RLock()
|
||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
||||
err := func() error {
|
||||
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.RecentStats(numStats)
|
||||
}
|
||||
@ -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) {
|
||||
var cstore *containerStorage
|
||||
var ok bool
|
||||
self.lock.RLock()
|
||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
||||
err := func() error {
|
||||
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.Percentiles(cpuPercentiles, memPercentiles)
|
||||
}
|
||||
|
@ -44,6 +44,32 @@ func TestSamplesWithoutSample(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
|
||||
}
|
||||
|
||||
func TestPercentilessWithoutSample(t *testing.T) {
|
||||
func TestPercentilesWithoutSample(t *testing.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 {
|
||||
|
||||
// Read most recent stats. numStats indicates max number of stats
|
||||
// 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)
|
||||
|
||||
// Read the specified percentiles of CPU and memory usage of the container.
|
||||
@ -31,7 +33,7 @@ type StorageDriver interface {
|
||||
// Returns samples of the container stats. If numSamples < 0, then
|
||||
// the number of returned samples is implementation defined. Otherwise, the driver
|
||||
// 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
|
||||
// stored in the underlying storage may or may not be deleted depending
|
||||
|
57
third_party/src/github.com/google/cadvisor/storage/test/mock.go
vendored
Normal file
57
third_party/src/github.com/google/cadvisor/storage/test/mock.go
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// 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
|
||||
}
|
@ -16,6 +16,7 @@ package test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -43,6 +44,7 @@ func buildTrace(cpu, mem []uint64, duration time.Duration) []*info.ContainerStat
|
||||
stats.Cpu.Usage.Total = cpuTotalUsage
|
||||
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
||||
stats.Cpu.Usage.System = 0
|
||||
stats.Cpu.Usage.PerCpu = []uint64{cpuTotalUsage}
|
||||
|
||||
stats.Memory.Usage = mem[i]
|
||||
|
||||
@ -51,10 +53,133 @@ func buildTrace(cpu, mem []uint64, duration time.Duration) []*info.ContainerStat
|
||||
return ret
|
||||
}
|
||||
|
||||
// The underlying driver must be able to hold more than 10 samples.
|
||||
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 statsEq(a, b *info.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 sampleEq(a, b *info.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
|
||||
}
|
||||
|
||||
func samplesInTrace(samples []*info.ContainerStatsSample, cpuTrace, memTrace []uint64, samplePeriod time.Duration, t *testing.T) {
|
||||
for _, sample := range samples {
|
||||
if sample.Duration != samplePeriod {
|
||||
t.Errorf("sample duration is %v, not %v", sample.Duration, samplePeriod)
|
||||
}
|
||||
cpuUsage := sample.Cpu.Usage
|
||||
memUsage := sample.Memory.Usage
|
||||
found := false
|
||||
for _, u := range cpuTrace {
|
||||
if u == cpuUsage {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("unable to find cpu usage %v", cpuUsage)
|
||||
}
|
||||
found = false
|
||||
for _, u := range memTrace {
|
||||
if u == memUsage {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("unable to find mem usage %v", memUsage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function will generate random stats and write
|
||||
// them into the storage. The function will not close the driver
|
||||
func StorageDriverFillRandomStatsFunc(
|
||||
containerName string,
|
||||
N int,
|
||||
driver storage.StorageDriver,
|
||||
t *testing.T,
|
||||
) {
|
||||
cpuTrace := make([]uint64, 0, N)
|
||||
memTrace := make([]uint64, 0, N)
|
||||
|
||||
// We need N+1 observations to get N samples
|
||||
for i := 0; i < N+1; i++ {
|
||||
cpuTrace = append(cpuTrace, uint64(rand.Intn(1000)))
|
||||
memTrace = append(memTrace, uint64(rand.Intn(1000)))
|
||||
}
|
||||
|
||||
samplePeriod := 1 * time.Second
|
||||
|
||||
ref := info.ContainerReference{
|
||||
Name: containerName,
|
||||
}
|
||||
|
||||
trace := buildTrace(cpuTrace, memTrace, samplePeriod)
|
||||
|
||||
for _, stats := range trace {
|
||||
err := driver.AddStats(ref, stats)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add stats: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
N := 10
|
||||
N := 100
|
||||
cpuTrace := make([]uint64, 0, N)
|
||||
memTrace := make([]uint64, 0, N)
|
||||
|
||||
@ -73,28 +198,37 @@ func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T)
|
||||
trace := buildTrace(cpuTrace, memTrace, samplePeriod)
|
||||
|
||||
for _, stats := range trace {
|
||||
driver.AddStats(ref, stats)
|
||||
err := driver.AddStats(ref, stats)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add stats: %v", err)
|
||||
}
|
||||
// set the trace to something else. The stats stored in the
|
||||
// storage should not be affected.
|
||||
stats.Cpu.Usage.Total = 0
|
||||
stats.Cpu.Usage.System = 0
|
||||
stats.Cpu.Usage.User = 0
|
||||
}
|
||||
|
||||
samples, err := driver.Samples(ref.Name, N)
|
||||
if err != nil {
|
||||
t.Errorf("unable to sample stats: %v", err)
|
||||
}
|
||||
for _, sample := range samples {
|
||||
if sample.Duration != samplePeriod {
|
||||
t.Errorf("sample duration is %v, not %v", sample.Duration, samplePeriod)
|
||||
}
|
||||
cpuUsage := sample.Cpu.Usage
|
||||
found := false
|
||||
for _, u := range cpuTrace {
|
||||
if u == cpuUsage {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("unable to find cpu usage %v", cpuUsage)
|
||||
}
|
||||
if len(samples) == 0 {
|
||||
t.Fatal("should at least store one sample")
|
||||
}
|
||||
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||
|
||||
samples, err = driver.Samples(ref.Name, -1)
|
||||
if err != nil {
|
||||
t.Errorf("unable to sample stats: %v", err)
|
||||
}
|
||||
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||
|
||||
samples, err = driver.Samples(ref.Name, N-5)
|
||||
if err != nil {
|
||||
t.Errorf("unable to sample stats: %v", err)
|
||||
}
|
||||
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||
}
|
||||
|
||||
func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T) {
|
||||
@ -114,7 +248,16 @@ func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T)
|
||||
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||
|
||||
for _, stats := range trace {
|
||||
driver.AddStats(ref, stats)
|
||||
err := driver.AddStats(ref, stats)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add stats: %v", err)
|
||||
}
|
||||
// set the trace to something else. The stats stored in the
|
||||
// storage should not be affected.
|
||||
stats.Cpu.Usage.Total = 0
|
||||
stats.Cpu.Usage.System = 0
|
||||
stats.Cpu.Usage.User = 0
|
||||
stats.Memory.Usage = 0
|
||||
}
|
||||
|
||||
percentiles, err := driver.Percentiles(ref.Name, []int{50}, []int{50})
|
||||
@ -168,3 +311,174 @@ func StorageDriverTestPercentilesWithoutSample(driver storage.StorageDriver, t *
|
||||
t.Errorf("There should be no percentiles")
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestPercentiles(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
N := 100
|
||||
cpuTrace := make([]uint64, N)
|
||||
memTrace := make([]uint64, N)
|
||||
for i := 1; i < N+1; i++ {
|
||||
cpuTrace[i-1] = uint64(i)
|
||||
memTrace[i-1] = uint64(i)
|
||||
}
|
||||
|
||||
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||
|
||||
ref := info.ContainerReference{
|
||||
Name: "container",
|
||||
}
|
||||
for _, stats := range trace {
|
||||
driver.AddStats(ref, stats)
|
||||
}
|
||||
percentages := []int{
|
||||
80,
|
||||
90,
|
||||
50,
|
||||
}
|
||||
percentiles, err := driver.Percentiles(ref.Name, percentages, percentages)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, x := range percentiles.CpuUsagePercentiles {
|
||||
for _, y := range percentiles.CpuUsagePercentiles {
|
||||
// lower percentage, smaller value
|
||||
if x.Percentage < y.Percentage && x.Value > y.Value {
|
||||
t.Errorf("%v percent is %v; while %v percent is %v",
|
||||
x.Percentage, x.Value, y.Percentage, y.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, x := range percentiles.MemoryUsagePercentiles {
|
||||
for _, y := range percentiles.MemoryUsagePercentiles {
|
||||
if x.Percentage < y.Percentage && x.Value > y.Value {
|
||||
t.Errorf("%v percent is %v; while %v percent is %v",
|
||||
x.Percentage, x.Value, y.Percentage, y.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestRetrievePartialRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
N := 100
|
||||
memTrace := make([]uint64, N)
|
||||
cpuTrace := make([]uint64, N)
|
||||
for i := 0; i < N; i++ {
|
||||
memTrace[i] = uint64(i + 1)
|
||||
cpuTrace[i] = uint64(1)
|
||||
}
|
||||
|
||||
ref := info.ContainerReference{
|
||||
Name: "container",
|
||||
}
|
||||
|
||||
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||
|
||||
for _, stats := range trace {
|
||||
driver.AddStats(ref, stats)
|
||||
}
|
||||
|
||||
recentStats, err := driver.RecentStats(ref.Name, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(recentStats) == 0 {
|
||||
t.Fatal("should at least store one stats")
|
||||
}
|
||||
|
||||
if len(recentStats) > 10 {
|
||||
t.Fatalf("returned %v stats, not 10.", len(recentStats))
|
||||
}
|
||||
|
||||
actualRecentStats := trace[len(trace)-len(recentStats):]
|
||||
|
||||
// The returned stats should be sorted in time increasing order
|
||||
for i, s := range actualRecentStats {
|
||||
r := recentStats[i]
|
||||
if !statsEq(s, r) {
|
||||
t.Errorf("unexpected stats %+v with memory usage %v", r, r.Memory.Usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestRetrieveAllRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
N := 100
|
||||
memTrace := make([]uint64, N)
|
||||
cpuTrace := make([]uint64, N)
|
||||
for i := 0; i < N; i++ {
|
||||
memTrace[i] = uint64(i + 1)
|
||||
cpuTrace[i] = uint64(1)
|
||||
}
|
||||
|
||||
ref := info.ContainerReference{
|
||||
Name: "container",
|
||||
}
|
||||
|
||||
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||
|
||||
for _, stats := range trace {
|
||||
driver.AddStats(ref, stats)
|
||||
}
|
||||
|
||||
recentStats, err := driver.RecentStats(ref.Name, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(recentStats) == 0 {
|
||||
t.Fatal("should at least store one stats")
|
||||
}
|
||||
if len(recentStats) > N {
|
||||
t.Fatalf("returned %v stats, not 100.", len(recentStats))
|
||||
}
|
||||
|
||||
actualRecentStats := trace[len(trace)-len(recentStats):]
|
||||
|
||||
// The returned stats should be sorted in time increasing order
|
||||
for i, s := range actualRecentStats {
|
||||
r := recentStats[i]
|
||||
if !statsEq(s, r) {
|
||||
t.Errorf("unexpected stats %+v with memory usage %v", r, r.Memory.Usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestNoRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
nonExistContainer := "somerandomecontainer"
|
||||
stats, _ := driver.RecentStats(nonExistContainer, -1)
|
||||
if len(stats) > 0 {
|
||||
t.Errorf("RecentStats() returns %v stats on non exist container", len(stats))
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestNoSamples(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
nonExistContainer := "somerandomecontainer"
|
||||
samples, _ := driver.Samples(nonExistContainer, -1)
|
||||
if len(samples) > 0 {
|
||||
t.Errorf("Samples() returns %v samples on non exist container", len(samples))
|
||||
}
|
||||
}
|
||||
|
||||
func StorageDriverTestPercentilesWithoutStats(driver storage.StorageDriver, t *testing.T) {
|
||||
defer driver.Close()
|
||||
nonExistContainer := "somerandomecontainer"
|
||||
percentiles, _ := driver.Percentiles(nonExistContainer, []int{50, 80}, []int{50, 80})
|
||||
if percentiles == nil {
|
||||
return
|
||||
}
|
||||
if percentiles.MaxMemoryUsage != 0 {
|
||||
t.Errorf("Percentiles() reports max memory usage > 0 when there's no stats.")
|
||||
}
|
||||
for _, p := range percentiles.CpuUsagePercentiles {
|
||||
if p.Value != 0 {
|
||||
t.Errorf("Percentiles() reports cpu usage is %v when there's no stats.", p.Value)
|
||||
}
|
||||
}
|
||||
for _, p := range percentiles.MemoryUsagePercentiles {
|
||||
if p.Value != 0 {
|
||||
t.Errorf("Percentiles() reports memory usage is %v when there's no stats.", p.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
71
third_party/src/github.com/google/cadvisor/storagedriver.go
vendored
Normal file
71
third_party/src/github.com/google/cadvisor/storagedriver.go
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
// 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user