Update to latest cAdvisor and use data structures directly from cAdvisor

This commit is contained in:
Nan Deng 2014-07-14 14:48:51 -07:00
parent 02ee27c133
commit 8c573ee727
36 changed files with 2456 additions and 895 deletions

View File

@ -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"`

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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
View File

@ -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"

View File

@ -0,0 +1 @@
*.swp

View File

@ -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

View 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

View File

@ -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>

View 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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}
}
}

View File

@ -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
}

View 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
}

View File

@ -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

View 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"`
}

View File

@ -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

View File

@ -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")
}
}

View 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
}

View File

@ -15,4 +15,4 @@
package info
// Version of cAdvisor.
const VERSION = "0.1.0"
const VERSION = "0.1.2"

View 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)
}
}

View File

@ -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
}

View 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)
}
}
}

View File

@ -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)
}

View File

@ -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);
}

View 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
}

View 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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View 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
}

View File

@ -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)
}
}
}

View 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
}