Commit 76ec7acf authored by Dawn Chen's avatar Dawn Chen

bump(github.com/google/cadvisor): bdd574b728e8a1e5eb08649d631197620dd09650

parent 2dc94e4a
...@@ -289,7 +289,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v ...@@ -289,7 +289,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v
ExposedPorts: exposedPorts, ExposedPorts: exposedPorts,
Hostname: container.Name, Hostname: container.Name,
Image: container.Image, Image: container.Image,
Memory: int64(container.Memory), Memory: uint64(container.Memory),
CpuShares: int64(milliCPUToShares(container.CPU)), CpuShares: int64(milliCPUToShares(container.CPU)),
Volumes: volumes, Volumes: volumes,
WorkingDir: container.WorkingDir, WorkingDir: container.WorkingDir,
......
# Changelog # Changelog
## 0.2.1 (2014-07-25)
- Handle old Docker versions.
- UI fixes and other bugfixes.
## 0.2.0 (2014-07-24)
- Added network stats to the UI.
- Added support for CoreOS and RHEL.
- Bugfixes and reliability fixes.
## 0.1.4 (2014-07-22)
- Add network statistics to REST API.
- Add "raw" driver to handle non-Docker containers.
- Remove lmctfy in favor of the raw driver.
- Bugfixes for Docker containers and logging.
## 0.1.3 (2014-07-14) ## 0.1.3 (2014-07-14)
- Add support for systemd systems. - Add support for systemd systems.
- Fixes for UI with InfluxDB storage driver. - Fixes for UI with InfluxDB storage driver.
......
# cAdvisor # cAdvisor
cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers. Specifically, for each container it keeps resource isolation parameters, historical resource usage, and histograms of complete historical resource usage. This data is exported by container and machine-wide. cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers. Specifically, for each container it keeps resource isolation parameters, historical resource usage, histograms of complete historical resource usage and network statistics. This data is exported by container and machine-wide.
cAdvisor currently supports lmctfy containers as well as Docker containers (those that use the default libcontainer execdriver). Other container backends can also be added. cAdvisor's container abstraction is based on lmctfy's so containers are inherently nested hierarchically. cAdvisor currently supports lmctfy containers as well as Docker containers (those that use the default libcontainer execdriver). Other container backends can also be added. cAdvisor's container abstraction is based on lmctfy's so containers are inherently nested hierarchically.
...@@ -13,11 +13,12 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above), ...@@ -13,11 +13,12 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above),
``` ```
sudo docker run \ sudo docker run \
--volume=/var/run:/var/run:rw \ --volume=/var/run:/var/run:rw \
--volume=/sys/fs/cgroup/:/sys/fs/cgroup:ro \ --volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \ --publish=8080:8080 \
--detach=true \ --detach=true \
google/cadvisor --name=cadvisor \
google/cadvisor:latest
``` ```
cAdvisor is now running (in the background) on `http://localhost:8080`. The setup includes directories with Docker state cAdvisor needs to observe. cAdvisor is now running (in the background) on `http://localhost:8080`. The setup includes directories with Docker state cAdvisor needs to observe.
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package inference package interference
import "github.com/google/cadvisor/info" import "github.com/google/cadvisor/info"
......
...@@ -71,6 +71,11 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er ...@@ -71,6 +71,11 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er
log.Printf("Api - Container(%s)", containerName) log.Printf("Api - Container(%s)", containerName)
var query info.ContainerInfoRequest var query info.ContainerInfoRequest
// If a user does not specify number of stats/samples he wants,
// it's 64 by default
query.NumStats = 64
query.NumSamples = 64
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&query) err := decoder.Decode(&query)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
......
...@@ -22,7 +22,7 @@ import ( ...@@ -22,7 +22,7 @@ import (
"github.com/google/cadvisor/api" "github.com/google/cadvisor/api"
"github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/container/lmctfy" "github.com/google/cadvisor/container/raw"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/manager" "github.com/google/cadvisor/manager"
"github.com/google/cadvisor/pages" "github.com/google/cadvisor/pages"
...@@ -30,7 +30,6 @@ import ( ...@@ -30,7 +30,6 @@ import (
) )
var argPort = flag.Int("port", 8080, "port to listen") var argPort = flag.Int("port", 8080, "port to listen")
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") var argDbDriver = flag.String("storage_driver", "memory", "storage driver to use. Options are: memory (default) and influxdb")
...@@ -47,30 +46,14 @@ func main() { ...@@ -47,30 +46,14 @@ func main() {
log.Fatalf("Failed to create a Container Manager: %s", err) log.Fatalf("Failed to create a Container Manager: %s", err)
} }
// Register lmctfy for the root if allowed and available. // Register Docker.
registeredRoot := false if err := docker.Register(containerManager); err != nil {
if *argAllowLmctfy { log.Printf("Docker registration failed: %v.", err)
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. // Register the raw driver.
if err := docker.Register(containerManager, "/docker"); err != nil { if err := raw.Register(containerManager); err != nil {
// Ignore this error because we should work with lmctfy only log.Fatalf("raw registration failed: %v.", err)
log.Printf("Docker registration failed: %v.", err)
log.Print("Running in lmctfy only mode.")
} }
// Handler for static content. // Handler for static content.
...@@ -100,11 +83,14 @@ func main() { ...@@ -100,11 +83,14 @@ func main() {
} }
}) })
go containerManager.Start() go func() {
log.Fatal(containerManager.Start())
}()
log.Printf("Starting cAdvisor version: %q", info.VERSION) log.Printf("Starting cAdvisor version: %q", info.VERSION)
log.Print("About to serve on port ", *argPort) log.Print("About to serve on port ", *argPort)
addr := fmt.Sprintf(":%v", *argPort) addr := fmt.Sprintf(":%v", *argPort)
log.Fatal(http.ListenAndServe(addr, nil)) log.Fatal(http.ListenAndServe(addr, nil))
} }
...@@ -17,11 +17,15 @@ package docker ...@@ -17,11 +17,15 @@ package docker
import ( import (
"flag" "flag"
"fmt" "fmt"
"log"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
) )
...@@ -29,6 +33,11 @@ var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "do ...@@ -29,6 +33,11 @@ var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "do
type dockerFactory struct { type dockerFactory struct {
machineInfoFactory info.MachineInfoFactory machineInfoFactory info.MachineInfoFactory
// Whether this system is using systemd.
useSystemd bool
client *docker.Client
} }
func (self *dockerFactory) String() string { func (self *dockerFactory) String() string {
...@@ -44,10 +53,42 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C ...@@ -44,10 +53,42 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C
client, client,
name, name,
self.machineInfoFactory, self.machineInfoFactory,
self.useSystemd,
) )
return return
} }
// Docker handles all containers under /docker
// TODO(vishh): Change the CanHandle interface to be able to return errors.
func (self *dockerFactory) CanHandle(name string) bool {
// In systemd systems the containers are: /system.slice/docker-{ID}
if self.useSystemd {
if !strings.HasPrefix(name, "/system.slice/docker-") {
return false
}
} else if name == "/" {
return false
} else if name == "/docker" {
// We need the docker driver to handle /docker. Otherwise the aggregation at the API level will break.
return true
} else if !strings.HasPrefix(name, "/docker/") {
return false
}
// Check if the container is known to docker and it is active.
_, id, err := libcontainer.SplitName(name)
if err != nil {
return false
}
ctnr, err := self.client.InspectContainer(id)
// We assume that if Inspect fails then the container is not known to docker.
// TODO(vishh): Detect lxc containers and avoid handling them.
if err != nil || !ctnr.State.Running {
return false
}
return true
}
func parseDockerVersion(full_version_string string) ([]int, error) { func parseDockerVersion(full_version_string string) ([]int, error) {
version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)" version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)"
version_re := regexp.MustCompile(version_regexp_string) version_re := regexp.MustCompile(version_regexp_string)
...@@ -68,7 +109,7 @@ func parseDockerVersion(full_version_string string) ([]int, error) { ...@@ -68,7 +109,7 @@ func parseDockerVersion(full_version_string string) ([]int, error) {
} }
// Register root container before running this function! // Register root container before running this function!
func Register(factory info.MachineInfoFactory, paths ...string) error { func Register(factory info.MachineInfoFactory) error {
client, err := docker.NewClient(*ArgDockerEndpoint) client, err := docker.NewClient(*ArgDockerEndpoint)
if err != nil { if err != nil {
return fmt.Errorf("unable to communicate with docker daemon: %v", err) return fmt.Errorf("unable to communicate with docker daemon: %v", err)
...@@ -92,12 +133,13 @@ func Register(factory info.MachineInfoFactory, paths ...string) error { ...@@ -92,12 +133,13 @@ func Register(factory info.MachineInfoFactory, paths ...string) error {
} }
f := &dockerFactory{ f := &dockerFactory{
machineInfoFactory: factory, machineInfoFactory: factory,
useSystemd: systemd.UseSystemd(),
client: client,
} }
for _, p := range paths { if f.useSystemd {
if p != "/" && p != "/docker" { log.Printf("System is using systemd")
return fmt.Errorf("%v cannot be managed by docker", p)
}
container.RegisterContainerHandlerFactory(p, f)
} }
log.Printf("Registering Docker factory")
container.RegisterContainerHandlerFactory(f)
return nil return nil
} }
...@@ -17,113 +17,56 @@ package container ...@@ -17,113 +17,56 @@ package container
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"sync" "sync"
) )
type ContainerHandlerFactory interface { type ContainerHandlerFactory interface {
// Create a new ContainerHandler using this factory. CanHandle() must have returned true.
NewContainerHandler(name string) (ContainerHandler, error) NewContainerHandler(name string) (ContainerHandler, error)
// for testability // Returns whether this factory can handle the specified container.
String() string CanHandle(name string) bool
}
type factoryTreeNode struct {
defaultFactory ContainerHandlerFactory
children map[string]*factoryTreeNode
}
func (self *factoryTreeNode) find(elems ...string) ContainerHandlerFactory { // Name of the factory.
node := self String() string
for _, elem := range elems {
if len(node.children) == 0 {
break
}
if child, ok := node.children[elem]; ok {
node = child
} else {
return node.defaultFactory
}
}
return node.defaultFactory
} }
func (self *factoryTreeNode) add(factory ContainerHandlerFactory, elems ...string) { // TODO(vmarmol): Consider not making this global.
node := self // Global list of factories.
for _, elem := range elems { var (
if node.children == nil { factories []ContainerHandlerFactory
node.children = make(map[string]*factoryTreeNode, 16) factoriesLock sync.RWMutex
} )
child, ok := self.children[elem]
if !ok {
child = &factoryTreeNode{
defaultFactory: node.defaultFactory,
children: make(map[string]*factoryTreeNode, 16),
}
node.children[elem] = child
}
node = child
}
node.defaultFactory = factory
}
type factoryManager struct { // Register a ContainerHandlerFactory. These should be registered from least general to most general
root *factoryTreeNode // as they will be asked in order whether they can handle a particular container.
lock sync.RWMutex func RegisterContainerHandlerFactory(factory ContainerHandlerFactory) {
} factoriesLock.Lock()
defer factoriesLock.Unlock()
func dropEmptyString(elems ...string) []string { factories = append(factories, factory)
ret := make([]string, 0, len(elems))
for _, e := range elems {
if len(e) > 0 {
ret = append(ret, e)
}
}
return ret
} }
// Must register factory for root container! // Create a new ContainerHandler for the specified container.
func (self *factoryManager) Register(path string, factory ContainerHandlerFactory) { func NewContainerHandler(name string) (ContainerHandler, error) {
self.lock.Lock() factoriesLock.RLock()
defer self.lock.Unlock() defer factoriesLock.RUnlock()
if self.root == nil { // Create the ContainerHandler with the first factory that supports it.
self.root = &factoryTreeNode{ for _, factory := range factories {
defaultFactory: nil, if factory.CanHandle(name) {
children: make(map[string]*factoryTreeNode, 10), log.Printf("Using factory %q for container %q", factory.String(), name)
return factory.NewContainerHandler(name)
} }
} }
elems := dropEmptyString(strings.Split(path, "/")...) return nil, fmt.Errorf("no known factory can handle creation of container")
self.root.add(factory, elems...)
}
func (self *factoryManager) NewContainerHandler(path string) (ContainerHandler, error) {
self.lock.RLock()
defer self.lock.RUnlock()
if self.root == nil {
err := fmt.Errorf("nil factory for container %v: no factory registered", path)
return nil, err
}
elems := dropEmptyString(strings.Split(path, "/")...)
factory := self.root.find(elems...)
if factory == nil {
err := fmt.Errorf("nil factory for container %v", path)
return nil, err
}
log.Printf("Container handler factory for %v is %v\n", path, factory)
return factory.NewContainerHandler(path)
} }
var globalFactoryManager factoryManager // Clear the known factories.
func ClearContainerHandlerFactories() {
func RegisterContainerHandlerFactory(path string, factory ContainerHandlerFactory) { factoriesLock.Lock()
globalFactoryManager.Register(path, factory) defer factoriesLock.Unlock()
}
func NewContainerHandler(path string) (ContainerHandler, error) { factories = make([]ContainerHandlerFactory, 0, 4)
return globalFactoryManager.NewContainerHandler(path)
} }
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
package container package container
import ( import (
"strings"
"testing" "testing"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
...@@ -23,54 +22,101 @@ import ( ...@@ -23,54 +22,101 @@ import (
type mockContainerHandlerFactory struct { type mockContainerHandlerFactory struct {
mock.Mock mock.Mock
Name string Name string
CanHandleValue bool
} }
func (self *mockContainerHandlerFactory) String() string { func (self *mockContainerHandlerFactory) String() string {
return self.Name return self.Name
} }
func (self *mockContainerHandlerFactory) CanHandle(name string) bool {
return self.CanHandleValue
}
func (self *mockContainerHandlerFactory) NewContainerHandler(name string) (ContainerHandler, error) { func (self *mockContainerHandlerFactory) NewContainerHandler(name string) (ContainerHandler, error) {
args := self.Called(name) args := self.Called(name)
return args.Get(0).(ContainerHandler), args.Error(1) return args.Get(0).(ContainerHandler), args.Error(1)
} }
func testExpectedFactory(root *factoryTreeNode, path, expectedFactory string, t *testing.T) { const testContainerName = "/test"
elems := dropEmptyString(strings.Split(path, "/")...)
factory := root.find(elems...) var mockFactory FactoryForMockContainerHandler
if factory.String() != expectedFactory {
t.Errorf("factory %v should be used to create container %v. but %v is selected", func TestNewContainerHandler_FirstMatches(t *testing.T) {
expectedFactory, ClearContainerHandlerFactories()
path,
factory) // Register one allways yes factory.
allwaysYes := &mockContainerHandlerFactory{
Name: "yes",
CanHandleValue: true,
}
RegisterContainerHandlerFactory(allwaysYes)
// The yes factory should be asked to create the ContainerHandler.
mockContainer, err := mockFactory.NewContainerHandler(testContainerName)
if err != nil {
t.Error(err)
}
allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil)
cont, err := NewContainerHandler(testContainerName)
if err != nil {
t.Error(err)
}
if cont == nil {
t.Error("Expected container to not be nil")
} }
} }
func testAddFactory(root *factoryTreeNode, path string) *factoryTreeNode { func TestNewContainerHandler_SecondMatches(t *testing.T) {
elems := dropEmptyString(strings.Split(path, "/")...) ClearContainerHandlerFactories()
if root == nil {
root = &factoryTreeNode{ // Register one allways no and one always yes factory.
defaultFactory: nil, allwaysNo := &mockContainerHandlerFactory{
} Name: "no",
CanHandleValue: false,
} }
f := &mockContainerHandlerFactory{ RegisterContainerHandlerFactory(allwaysNo)
Name: path, allwaysYes := &mockContainerHandlerFactory{
Name: "yes",
CanHandleValue: true,
}
RegisterContainerHandlerFactory(allwaysYes)
// The yes factory should be asked to create the ContainerHandler.
mockContainer, err := mockFactory.NewContainerHandler(testContainerName)
if err != nil {
t.Error(err)
}
allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil)
cont, err := NewContainerHandler(testContainerName)
if err != nil {
t.Error(err)
}
if cont == nil {
t.Error("Expected container to not be nil")
} }
root.add(f, elems...)
return root
} }
func TestFactoryTree(t *testing.T) { func TestNewContainerHandler_NoneMatch(t *testing.T) {
root := testAddFactory(nil, "/") ClearContainerHandlerFactories()
root = testAddFactory(root, "/docker")
root = testAddFactory(root, "/user") // Register two allways no factories.
root = testAddFactory(root, "/user/special/containers") allwaysNo1 := &mockContainerHandlerFactory{
Name: "no",
testExpectedFactory(root, "/docker/container", "/docker", t) CanHandleValue: false,
testExpectedFactory(root, "/docker", "/docker", t) }
testExpectedFactory(root, "/", "/", t) RegisterContainerHandlerFactory(allwaysNo1)
testExpectedFactory(root, "/user/deep/level/container", "/user", t) allwaysNo2 := &mockContainerHandlerFactory{
testExpectedFactory(root, "/user/special/containers", "/user/special/containers", t) Name: "no",
testExpectedFactory(root, "/user/special/containers/container", "/user/special/containers", t) CanHandleValue: false,
testExpectedFactory(root, "/other", "/", t) }
RegisterContainerHandlerFactory(allwaysNo2)
_, err := NewContainerHandler(testContainerName)
if err == nil {
t.Error("Expected NewContainerHandler to fail")
}
} }
...@@ -60,9 +60,9 @@ func TestWhiteListContainerFilter(t *testing.T) { ...@@ -60,9 +60,9 @@ func TestWhiteListContainerFilter(t *testing.T) {
mockc := &mockContainerHandler{} mockc := &mockContainerHandler{}
mockc.On("ListContainers", LIST_RECURSIVE).Return( mockc.On("ListContainers", LIST_RECURSIVE).Return(
[]info.ContainerReference{ []info.ContainerReference{
info.ContainerReference{Name: "/docker/ee0103"}, {Name: "/docker/ee0103"},
info.ContainerReference{Name: "/container/created/by/lmctfy"}, {Name: "/container/created/by/lmctfy"},
info.ContainerReference{Name: "/user/something"}, {Name: "/user/something"},
}, },
nil, nil,
) )
...@@ -95,9 +95,9 @@ func TestBlackListContainerFilter(t *testing.T) { ...@@ -95,9 +95,9 @@ func TestBlackListContainerFilter(t *testing.T) {
mockc := &mockContainerHandler{} mockc := &mockContainerHandler{}
mockc.On("ListContainers", LIST_RECURSIVE).Return( mockc.On("ListContainers", LIST_RECURSIVE).Return(
[]info.ContainerReference{ []info.ContainerReference{
info.ContainerReference{Name: "/docker/ee0103"}, {Name: "/docker/ee0103"},
info.ContainerReference{Name: "/container/created/by/lmctfy"}, {Name: "/container/created/by/lmctfy"},
info.ContainerReference{Name: "/user/something"}, {Name: "/user/something"},
}, },
nil, nil,
) )
......
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package libcontainer
import (
"bufio"
"path"
"path/filepath"
"strings"
"time"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups"
cgroupfs "github.com/docker/libcontainer/cgroups/fs"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/utils/fs"
)
// Get stats of the specified container
func GetStats(config *libcontainer.Config, state *libcontainer.State) (*info.ContainerStats, error) {
// TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready.
libcontainerStats, err := libcontainer.GetStats(config, state)
if err != nil {
return nil, err
}
return toContainerStats(libcontainerStats), nil
}
func GetStatsCgroupOnly(cgroup *cgroups.Cgroup) (*info.ContainerStats, error) {
s, err := cgroupfs.GetStats(cgroup)
if err != nil {
return nil, err
}
return toContainerStats(&libcontainer.ContainerStats{CgroupStats: s}), nil
}
// Convert libcontainer stats to info.ContainerStats.
func toContainerStats(libcontainerStats *libcontainer.ContainerStats) *info.ContainerStats {
s := libcontainerStats.CgroupStats
ret := new(info.ContainerStats)
ret.Timestamp = time.Now()
if s != nil {
ret.Cpu = new(info.CpuStats)
ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
n := len(s.CpuStats.CpuUsage.PercpuUsage)
ret.Cpu.Usage.PerCpu = make([]uint64, n)
ret.Cpu.Usage.Total = 0
for i := 0; i < n; i++ {
ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i]
ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i]
}
ret.Memory = new(info.MemoryStats)
ret.Memory.Usage = s.MemoryStats.Usage
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
}
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
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
}
}
}
// TODO(vishh): Perform a deep copy or alias libcontainer network stats.
if libcontainerStats.NetworkStats != nil {
ret.Network = (*info.NetworkStats)(libcontainerStats.NetworkStats)
}
return ret
}
// Given a container name, returns the parent and name of the container to be fed to libcontainer.
func SplitName(containerName string) (string, string, error) {
parent, id := path.Split(containerName)
cgroupSelf, err := fs.Open("/proc/1/cgroup")
if err != nil {
return "", "", err
}
scanner := bufio.NewScanner(cgroupSelf)
// Find how nested we are. Libcontainer takes container names relative to the current process.
subsys := []string{"memory", "cpu"}
nestedLevels := 0
for scanner.Scan() {
line := scanner.Text()
elems := strings.Split(line, ":")
if len(elems) < 3 {
continue
}
for _, s := range subsys {
if elems[1] == s {
if elems[2] == "/" {
// We're running at root, no nesting.
nestedLevels = 0
} else {
// Count how deeply nested we are.
nestedLevels = strings.Count(elems[2], "/")
}
break
}
}
}
if nestedLevels > 0 {
// we are running inside a docker container
upperLevel := strings.Repeat("../", nestedLevels)
parent = filepath.Join(upperLevel, parent)
}
// Strip the last "/"
if parent[len(parent)-1] == '/' {
parent = parent[:len(parent)-1]
}
return parent, id, nil
}
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package libcontainer
import (
"testing"
"code.google.com/p/gomock/gomock"
"github.com/google/cadvisor/utils/fs"
"github.com/google/cadvisor/utils/fs/mockfs"
)
var initCgroupsToParentAndID = []struct {
InitCgroupFileContent string
ContainerPath string
Parent string
Id string
Error error
}{
{
`
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/
4:cpuacct:/
3:cpu:/
2:cpuset:/
`,
"/",
"",
"",
nil,
},
{
`
11:name=systemd:/
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:freezer:/
6:devices:/
5:memory:/docker/hash
4:cpuacct:/
3:cpu:/docker/hash
2:cpuset:/
`,
"/parent/id",
"../../parent",
"id",
nil,
},
}
func TestSplitName(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
for _, testCase := range initCgroupsToParentAndID {
mfs := mockfs.NewMockFileSystem(mockCtrl)
mockfs.AddTextFile(mfs, "/proc/1/cgroup", testCase.InitCgroupFileContent)
fs.ChangeFileSystem(mfs)
parent, id, err := SplitName(testCase.ContainerPath)
if testCase.Error != nil {
if err == nil {
t.Fatalf("did not receive expected error.\ncontent:%v\n, path:%v\n, expected error:%v\n", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Error)
}
continue
}
if testCase.Parent != parent || testCase.Id != id {
t.Errorf("unexpected parent or id:\ncontent:%v\npath:%v\nexpected parent: %v; recevied parent: %v;\nexpected id: %v; received id: %v", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Parent, parent, testCase.Id, id)
}
}
}
// 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 container
import (
"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 ListType) ([]info.ContainerReference, error) {
args := self.Called(listType)
return args.Get(0).([]info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) ListThreads(listType ListType) ([]int, error) {
args := self.Called(listType)
return args.Get(0).([]int), args.Error(1)
}
func (self *MockContainerHandler) ListProcesses(listType 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) (ContainerHandler, error) {
handler := &MockContainerHandler{}
if self.PrepareContainerHandlerFunc != nil {
self.PrepareContainerHandlerFunc(name, handler)
}
return handler, nil
}
func (self *FactoryForMockContainerHandler) CanHandle(name string) bool {
return true
}
// 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 raw
import (
"fmt"
"log"
"github.com/docker/libcontainer/cgroups"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/info"
)
type cgroupSubsystems struct {
// Cgroup subsystem mounts.
mounts []cgroups.Mount
// Cgroup subsystem to their mount location.
mountPoints map[string]string
}
type rawFactory struct {
// Factory for machine information.
machineInfoFactory info.MachineInfoFactory
cgroupSubsystems *cgroupSubsystems
}
func (self *rawFactory) String() string {
return "raw"
}
func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) {
return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory)
}
// The raw factory can handle any container.
func (self *rawFactory) CanHandle(name string) bool {
return true
}
func Register(machineInfoFactory info.MachineInfoFactory) error {
// Get all cgroup mounts.
allCgroups, err := cgroups.GetCgroupMounts()
if err != nil {
return err
}
if len(allCgroups) == 0 {
return fmt.Errorf("failed to find cgroup mounts for the raw factory")
}
// Trim the mounts to only the subsystems we care about.
supportedCgroups := make([]cgroups.Mount, 0, len(allCgroups))
mountPoints := make(map[string]string, len(allCgroups))
for _, mount := range allCgroups {
for _, subsystem := range mount.Subsystems {
if _, ok := supportedSubsystems[subsystem]; ok {
supportedCgroups = append(supportedCgroups, mount)
mountPoints[subsystem] = mount.Mountpoint
}
}
}
if len(supportedCgroups) == 0 {
return fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
log.Printf("Registering Raw factory")
factory := &rawFactory{
machineInfoFactory: machineInfoFactory,
cgroupSubsystems: &cgroupSubsystems{
mounts: supportedCgroups,
mountPoints: mountPoints,
},
}
container.RegisterContainerHandlerFactory(factory)
return nil
}
// Cgroup subsystems we support listing (should be the minimal set we need stats from).
var supportedSubsystems map[string]struct{} = map[string]struct{}{
"cpu": {},
"cpuacct": {},
"memory": {},
"cpuset": {},
}
// 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 raw
import (
"fmt"
"io/ioutil"
"log"
"path/filepath"
"strconv"
"strings"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/utils"
)
type rawContainerHandler struct {
name string
cgroup *cgroups.Cgroup
cgroupSubsystems *cgroupSubsystems
machineInfoFactory info.MachineInfoFactory
}
func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) {
parent, id, err := libcontainer.SplitName(name)
if err != nil {
return nil, err
}
return &rawContainerHandler{
name: name,
cgroup: &cgroups.Cgroup{
Parent: parent,
Name: id,
},
cgroupSubsystems: cgroupSubsystems,
machineInfoFactory: machineInfoFactory,
}, nil
}
func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, error) {
// We only know the container by its one name.
return info.ContainerReference{
Name: self.name,
}, nil
}
func readString(path string, file string) string {
cgroupFile := filepath.Join(path, file)
// Ignore non-existent files
if !utils.FileExists(cgroupFile) {
return ""
}
// Read
out, err := ioutil.ReadFile(cgroupFile)
if err != nil {
log.Printf("raw driver: Failed to read %q: %s", cgroupFile, err)
return ""
}
return string(out)
}
func readInt64(path string, file string) uint64 {
out := readString(path, file)
if out == "" {
return 0
}
val, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
if err != nil {
log.Printf("raw driver: Failed to parse in %q from file %q: %s", out, filepath.Join(path, file), err)
return 0
}
return val
}
func (self *rawContainerHandler) GetSpec() (*info.ContainerSpec, error) {
spec := new(info.ContainerSpec)
// The raw driver assumes unified hierarchy containers.
// Get machine info.
mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil {
return nil, err
}
// CPU.
cpuRoot, ok := self.cgroupSubsystems.mountPoints["cpu"]
if ok {
cpuRoot = filepath.Join(cpuRoot, self.name)
if utils.FileExists(cpuRoot) {
spec.Cpu = new(info.CpuSpec)
spec.Cpu.Limit = readInt64(cpuRoot, "cpu.shares")
}
}
// Cpu Mask.
// This will fail for non-unified hierarchies. We'll return the whole machine mask in that case.
cpusetRoot, ok := self.cgroupSubsystems.mountPoints["cpuset"]
if ok {
if spec.Cpu == nil {
spec.Cpu = new(info.CpuSpec)
}
cpusetRoot = filepath.Join(cpusetRoot, self.name)
if utils.FileExists(cpusetRoot) {
spec.Cpu.Mask = readString(cpusetRoot, "cpuset.cpus")
if spec.Cpu.Mask == "" {
spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
}
}
}
// Memory.
memoryRoot, ok := self.cgroupSubsystems.mountPoints["memory"]
if ok {
memoryRoot = filepath.Join(memoryRoot, self.name)
if utils.FileExists(memoryRoot) {
spec.Memory = new(info.MemorySpec)
spec.Memory.Limit = readInt64(memoryRoot, "memory.limit_in_bytes")
spec.Memory.SwapLimit = readInt64(memoryRoot, "memory.memsw.limit_in_bytes")
}
}
return spec, nil
}
func (self *rawContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
return libcontainer.GetStatsCgroupOnly(self.cgroup)
}
// Lists all directories under "path" and outputs the results as children of "parent".
func listDirectories(path string, parent string, recursive bool, output map[string]struct{}) error {
// Ignore if this hierarchy does not exist.
if !utils.FileExists(path) {
return nil
}
entries, err := ioutil.ReadDir(path)
if err != nil {
return err
}
for _, entry := range entries {
// We only grab directories.
if entry.IsDir() {
name := filepath.Join(parent, entry.Name())
output[name] = struct{}{}
// List subcontainers if asked to.
if recursive {
err := listDirectories(filepath.Join(path, entry.Name()), name, true, output)
if err != nil {
return err
}
}
}
}
return nil
}
func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
containers := make(map[string]struct{}, 16)
for _, subsystem := range self.cgroupSubsystems.mounts {
err := listDirectories(filepath.Join(subsystem.Mountpoint, self.name), self.name, listType == container.LIST_RECURSIVE, containers)
if err != nil {
return nil, err
}
}
// Make into container references.
ret := make([]info.ContainerReference, 0, len(containers))
for cont := range containers {
ret = append(ret, info.ContainerReference{
Name: cont,
})
}
return ret, nil
}
func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
// TODO(vmarmol): Implement
return nil, nil
}
func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return fs.GetPids(self.cgroup)
}
FROM scratch
MAINTAINER dengnan@google.com vmarmol@google.com
# NOTE: Run prepare.sh before building this Docker image.
# Grab cadvisor from the staging directory.
ADD cadvisor /usr/bin/cadvisor
EXPOSE 8080
ENTRYPOINT ["/usr/bin/cadvisor"]
#!/bin/bash
set -e
set -x
# Statically build cAdvisor from source and stage it.
go build --ldflags '-extldflags "-static"' github.com/google/cadvisor
...@@ -21,14 +21,10 @@ import ( ...@@ -21,14 +21,10 @@ import (
"time" "time"
) )
type CpuSpecMask struct {
Data []uint64 `json:"data,omitempty"`
}
type CpuSpec struct { type CpuSpec struct {
Limit uint64 `json:"limit"` Limit uint64 `json:"limit"`
MaxLimit uint64 `json:"max_limit"` MaxLimit uint64 `json:"max_limit"`
Mask CpuSpecMask `json:"mask,omitempty"` Mask string `json:"mask,omitempty"`
} }
type MemorySpec struct { type MemorySpec struct {
...@@ -69,27 +65,7 @@ type ContainerInfoRequest struct { ...@@ -69,27 +65,7 @@ type ContainerInfoRequest struct {
// Different percentiles of CPU usage within a period. The values must be within [0, 100] // Different percentiles of CPU usage within a period. The values must be within [0, 100]
CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"` CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"`
// Different percentiles of memory usage within a period. The values must be within [0, 100] // Different percentiles of memory usage within a period. The values must be within [0, 100]
MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"` MemoryUsagePercentiles []int `json:"memory_usage_percentiles,omitempty"`
}
func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest {
ret := self
if ret == nil {
ret = new(ContainerInfoRequest)
}
if ret.NumStats <= 0 {
ret.NumStats = 1024
}
if ret.NumSamples <= 0 {
ret.NumSamples = 1024
}
if len(ret.CpuUsagePercentiles) == 0 {
ret.CpuUsagePercentiles = []int{50, 80, 90, 99}
}
if len(ret.MemoryUsagePercentages) == 0 {
ret.MemoryUsagePercentages = []int{50, 80, 90, 99}
}
return ret
} }
type ContainerInfo struct { type ContainerInfo struct {
...@@ -239,11 +215,31 @@ type MemoryStatsMemoryData struct { ...@@ -239,11 +215,31 @@ type MemoryStatsMemoryData struct {
Pgmajfault uint64 `json:"pgmajfault,omitempty"` Pgmajfault uint64 `json:"pgmajfault,omitempty"`
} }
type NetworkStats struct {
// Cumulative count of bytes received.
RxBytes uint64 `json:"rx_bytes"`
// Cumulative count of packets received.
RxPackets uint64 `json:"rx_packets"`
// Cumulative count of receive errors encountered.
RxErrors uint64 `json:"rx_errors"`
// Cumulative count of packets dropped while receiving.
RxDropped uint64 `json:"rx_dropped"`
// Cumulative count of bytes transmitted.
TxBytes uint64 `json:"tx_bytes"`
// Cumulative count of packets transmitted.
TxPackets uint64 `json:"tx_packets"`
// Cumulative count of transmit errors encountered.
TxErrors uint64 `json:"tx_errors"`
// Cumulative count of packets dropped while transmitting.
TxDropped uint64 `json:"tx_dropped"`
}
type ContainerStats struct { type ContainerStats struct {
// The time of this stat point. // The time of this stat point.
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
Cpu *CpuStats `json:"cpu,omitempty"` Cpu *CpuStats `json:"cpu,omitempty"`
Memory *MemoryStats `json:"memory,omitempty"` Memory *MemoryStats `json:"memory,omitempty"`
Network *NetworkStats `json:"network,omitempty"`
} }
// Makes a deep copy of the ContainerStats and returns a pointer to the new // Makes a deep copy of the ContainerStats and returns a pointer to the new
......
...@@ -16,7 +16,6 @@ package test ...@@ -16,7 +16,6 @@ package test
import ( import (
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"time" "time"
...@@ -59,12 +58,7 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec { ...@@ -59,12 +58,7 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
} }
ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000))
ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))
n := (numCores + 63) / 64 ret.Cpu.Mask = fmt.Sprintf("0-%d", numCores-1)
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)) ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
return ret return ret
} }
...@@ -83,8 +77,8 @@ func GenerateRandomContainerInfo(containerName string, numCores int, query *info ...@@ -83,8 +77,8 @@ func GenerateRandomContainerInfo(containerName string, numCores int, query *info
percentile := info.Percentile{p, uint64(rand.Int63n(1000))} percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
cpuPercentiles = append(cpuPercentiles, percentile) cpuPercentiles = append(cpuPercentiles, percentile)
} }
memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages)) memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentiles))
for _, p := range query.MemoryUsagePercentages { for _, p := range query.MemoryUsagePercentiles {
percentile := info.Percentile{p, uint64(rand.Int63n(1000))} percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
memPercentiles = append(memPercentiles, percentile) memPercentiles = append(memPercentiles, percentile)
} }
......
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
package info package info
// Version of cAdvisor. // Version of cAdvisor.
const VERSION = "0.1.3" const VERSION = "0.2.1"
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
ctest "github.com/google/cadvisor/container/test"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test" itest "github.com/google/cadvisor/info/test"
"github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage"
...@@ -32,17 +31,18 @@ import ( ...@@ -32,17 +31,18 @@ import (
func createContainerDataAndSetHandler( func createContainerDataAndSetHandler(
driver storage.StorageDriver, driver storage.StorageDriver,
f func(*ctest.MockContainerHandler), f func(*container.MockContainerHandler),
t *testing.T, t *testing.T,
) *containerData { ) *containerData {
factory := &ctest.FactoryForMockContainerHandler{ factory := &container.FactoryForMockContainerHandler{
Name: "factoryForMockContainer", Name: "factoryForMockContainer",
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) {
handler.Name = name handler.Name = name
f(handler) f(handler)
}, },
} }
container.RegisterContainerHandlerFactory("/", factory) container.ClearContainerHandlerFactories()
container.RegisterContainerHandlerFactory(factory)
if driver == nil { if driver == nil {
driver = &stest.MockStorageDriver{} driver = &stest.MockStorageDriver{}
...@@ -56,7 +56,7 @@ func createContainerDataAndSetHandler( ...@@ -56,7 +56,7 @@ func createContainerDataAndSetHandler(
} }
func TestContainerUpdateSubcontainers(t *testing.T) { func TestContainerUpdateSubcontainers(t *testing.T) {
var handler *ctest.MockContainerHandler var handler *container.MockContainerHandler
subcontainers := []info.ContainerReference{ subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"}, {Name: "/container/ee0103"},
{Name: "/container/abcd"}, {Name: "/container/abcd"},
...@@ -64,7 +64,7 @@ func TestContainerUpdateSubcontainers(t *testing.T) { ...@@ -64,7 +64,7 @@ func TestContainerUpdateSubcontainers(t *testing.T) {
} }
cd := createContainerDataAndSetHandler( cd := createContainerDataAndSetHandler(
nil, nil,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return( h.On("ListContainers", container.LIST_SELF).Return(
subcontainers, subcontainers,
nil, nil,
...@@ -99,10 +99,10 @@ func TestContainerUpdateSubcontainers(t *testing.T) { ...@@ -99,10 +99,10 @@ func TestContainerUpdateSubcontainers(t *testing.T) {
} }
func TestContainerUpdateSubcontainersWithError(t *testing.T) { func TestContainerUpdateSubcontainersWithError(t *testing.T) {
var handler *ctest.MockContainerHandler var handler *container.MockContainerHandler
cd := createContainerDataAndSetHandler( cd := createContainerDataAndSetHandler(
nil, nil,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return( h.On("ListContainers", container.LIST_SELF).Return(
[]info.ContainerReference{}, []info.ContainerReference{},
fmt.Errorf("some error"), fmt.Errorf("some error"),
...@@ -124,7 +124,7 @@ func TestContainerUpdateSubcontainersWithError(t *testing.T) { ...@@ -124,7 +124,7 @@ func TestContainerUpdateSubcontainersWithError(t *testing.T) {
} }
func TestContainerUpdateStats(t *testing.T) { func TestContainerUpdateStats(t *testing.T) {
var handler *ctest.MockContainerHandler var handler *container.MockContainerHandler
var ref info.ContainerReference var ref info.ContainerReference
driver := &stest.MockStorageDriver{} driver := &stest.MockStorageDriver{}
...@@ -134,7 +134,7 @@ func TestContainerUpdateStats(t *testing.T) { ...@@ -134,7 +134,7 @@ func TestContainerUpdateStats(t *testing.T) {
cd := createContainerDataAndSetHandler( cd := createContainerDataAndSetHandler(
driver, driver,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
h.On("GetStats").Return( h.On("GetStats").Return(
stats, stats,
nil, nil,
...@@ -156,11 +156,11 @@ func TestContainerUpdateStats(t *testing.T) { ...@@ -156,11 +156,11 @@ func TestContainerUpdateStats(t *testing.T) {
} }
func TestContainerUpdateSpec(t *testing.T) { func TestContainerUpdateSpec(t *testing.T) {
var handler *ctest.MockContainerHandler var handler *container.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4) spec := itest.GenerateRandomContainerSpec(4)
cd := createContainerDataAndSetHandler( cd := createContainerDataAndSetHandler(
nil, nil,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
h.On("GetSpec").Return( h.On("GetSpec").Return(
spec, spec,
nil, nil,
...@@ -179,7 +179,7 @@ func TestContainerUpdateSpec(t *testing.T) { ...@@ -179,7 +179,7 @@ func TestContainerUpdateSpec(t *testing.T) {
} }
func TestContainerGetInfo(t *testing.T) { func TestContainerGetInfo(t *testing.T) {
var handler *ctest.MockContainerHandler var handler *container.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4) spec := itest.GenerateRandomContainerSpec(4)
subcontainers := []info.ContainerReference{ subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"}, {Name: "/container/ee0103"},
...@@ -189,7 +189,7 @@ func TestContainerGetInfo(t *testing.T) { ...@@ -189,7 +189,7 @@ func TestContainerGetInfo(t *testing.T) {
aliases := []string{"a1", "a2"} aliases := []string{"a1", "a2"}
cd := createContainerDataAndSetHandler( cd := createContainerDataAndSetHandler(
nil, nil,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
h.On("GetSpec").Return( h.On("GetSpec").Return(
spec, spec,
nil, nil,
......
...@@ -130,11 +130,10 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn ...@@ -130,11 +130,10 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
var percentiles *info.ContainerStatsPercentiles var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats var stats []*info.ContainerStats
query = query.FillDefaults()
percentiles, err = m.storageDriver.Percentiles( percentiles, err = m.storageDriver.Percentiles(
cinfo.Name, cinfo.Name,
query.CpuUsagePercentiles, query.CpuUsagePercentiles,
query.MemoryUsagePercentages, query.MemoryUsagePercentiles,
) )
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -281,18 +280,18 @@ func (m *manager) detectContainers() error { ...@@ -281,18 +280,18 @@ func (m *manager) detectContainers() error {
} }
// Add the new containers. // Add the new containers.
for _, container := range added { for _, cont := range added {
_, err = m.createContainer(container.Name) _, err = m.createContainer(cont.Name)
if err != nil { if err != nil {
return fmt.Errorf("Failed to create existing container: %s: %s", container.Name, err) log.Printf("failed to create existing container: %s: %s", cont.Name, err)
} }
} }
// Remove the old containers. // Remove the old containers.
for _, container := range removed { for _, cont := range removed {
err = m.destroyContainer(container.Name) err = m.destroyContainer(cont.Name)
if err != nil { if err != nil {
return fmt.Errorf("Failed to destroy existing container: %s: %s", container.Name, err) log.Printf("failed to destroy existing container: %s: %s", cont.Name, err)
} }
} }
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"time" "time"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
ctest "github.com/google/cadvisor/container/test"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test" itest "github.com/google/cadvisor/info/test"
stest "github.com/google/cadvisor/storage/test" stest "github.com/google/cadvisor/storage/test"
...@@ -31,15 +30,15 @@ import ( ...@@ -31,15 +30,15 @@ import (
func createManagerAndAddContainers( func createManagerAndAddContainers(
driver *stest.MockStorageDriver, driver *stest.MockStorageDriver,
containers []string, containers []string,
f func(*ctest.MockContainerHandler), f func(*container.MockContainerHandler),
t *testing.T, t *testing.T,
) *manager { ) *manager {
if driver == nil { if driver == nil {
driver = &stest.MockStorageDriver{} driver = &stest.MockStorageDriver{}
} }
factory := &ctest.FactoryForMockContainerHandler{ factory := &container.FactoryForMockContainerHandler{
Name: "factoryForManager", Name: "factoryForManager",
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) {
handler.Name = name handler.Name = name
found := false found := false
for _, c := range containers { for _, c := range containers {
...@@ -53,7 +52,8 @@ func createManagerAndAddContainers( ...@@ -53,7 +52,8 @@ func createManagerAndAddContainers(
f(handler) f(handler)
}, },
} }
container.RegisterContainerHandlerFactory("/", factory) container.ClearContainerHandlerFactories()
container.RegisterContainerHandlerFactory(factory)
mif, err := New(driver) mif, err := New(driver)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -81,11 +81,11 @@ func TestGetContainerInfo(t *testing.T) { ...@@ -81,11 +81,11 @@ func TestGetContainerInfo(t *testing.T) {
NumStats: 256, NumStats: 256,
NumSamples: 128, NumSamples: 128,
CpuUsagePercentiles: []int{10, 50, 90}, CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentages: []int{10, 80, 90}, MemoryUsagePercentiles: []int{10, 80, 90},
} }
infosMap := make(map[string]*info.ContainerInfo, len(containers)) infosMap := make(map[string]*info.ContainerInfo, len(containers))
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers)) handlerMap := make(map[string]*container.MockContainerHandler, len(containers))
for _, container := range containers { for _, container := range containers {
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
...@@ -95,7 +95,7 @@ func TestGetContainerInfo(t *testing.T) { ...@@ -95,7 +95,7 @@ func TestGetContainerInfo(t *testing.T) {
m := createManagerAndAddContainers( m := createManagerAndAddContainers(
driver, driver,
containers, containers,
func(h *ctest.MockContainerHandler) { func(h *container.MockContainerHandler) {
cinfo := infosMap[h.Name] cinfo := infosMap[h.Name]
stats := cinfo.Stats stats := cinfo.Stats
samples := cinfo.Samples samples := cinfo.Samples
...@@ -105,7 +105,7 @@ func TestGetContainerInfo(t *testing.T) { ...@@ -105,7 +105,7 @@ func TestGetContainerInfo(t *testing.T) {
"Percentiles", "Percentiles",
h.Name, h.Name,
query.CpuUsagePercentiles, query.CpuUsagePercentiles,
query.MemoryUsagePercentages, query.MemoryUsagePercentiles,
).Return( ).Return(
percentiles, percentiles,
nil, nil,
...@@ -162,92 +162,3 @@ func TestGetContainerInfo(t *testing.T) { ...@@ -162,92 +162,3 @@ func TestGetContainerInfo(t *testing.T) {
} }
} }
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)
}
}
}
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"math"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
...@@ -36,6 +37,7 @@ var funcMap = template.FuncMap{ ...@@ -36,6 +37,7 @@ var funcMap = template.FuncMap{
"containerLink": containerLink, "containerLink": containerLink,
"printMask": printMask, "printMask": printMask,
"printCores": printCores, "printCores": printCores,
"printShares": printShares,
"printMegabytes": printMegabytes, "printMegabytes": printMegabytes,
"getMemoryUsage": getMemoryUsage, "getMemoryUsage": getMemoryUsage,
"getMemoryUsagePercent": getMemoryUsagePercent, "getMemoryUsagePercent": getMemoryUsagePercent,
...@@ -57,6 +59,7 @@ type pageData struct { ...@@ -57,6 +59,7 @@ type pageData struct {
ResourcesAvailable bool ResourcesAvailable bool
CpuAvailable bool CpuAvailable bool
MemoryAvailable bool MemoryAvailable bool
NetworkAvailable bool
} }
func init() { func init() {
...@@ -87,18 +90,12 @@ func containerLink(container info.ContainerReference, basenameOnly bool, cssClas ...@@ -87,18 +90,12 @@ func containerLink(container info.ContainerReference, basenameOnly bool, cssClas
return template.HTML(fmt.Sprintf("<a class=\"%s\" href=\"%s%s\">%s</a>", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName)) return template.HTML(fmt.Sprintf("<a class=\"%s\" href=\"%s%s\">%s</a>", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName))
} }
func printMask(mask *info.CpuSpecMask, numCores int) interface{} { func printMask(mask string, numCores int) interface{} {
// TODO(vmarmol): Detect this correctly.
// TODO(vmarmol): Support more than 64 cores.
rawMask := uint64(0)
if len(mask.Data) > 0 {
rawMask = mask.Data[0]
}
masks := make([]string, numCores) masks := make([]string, numCores)
for i := uint(0); i < uint(numCores); i++ { activeCores := getActiveCores(mask)
for i := 0; i < numCores; i++ {
coreClass := "inactive-cpu" coreClass := "inactive-cpu"
// by default, all cores are active if activeCores[i] {
if ((0x1<<i)&rawMask) != 0 || len(mask.Data) == 0 {
coreClass = "active-cpu" coreClass = "active-cpu"
} }
masks[i] = fmt.Sprintf("<span class=\"%s\">%d</span>", coreClass, i) masks[i] = fmt.Sprintf("<span class=\"%s\">%d</span>", coreClass, i)
...@@ -106,22 +103,49 @@ func printMask(mask *info.CpuSpecMask, numCores int) interface{} { ...@@ -106,22 +103,49 @@ func printMask(mask *info.CpuSpecMask, numCores int) interface{} {
return template.HTML(strings.Join(masks, "&nbsp;")) return template.HTML(strings.Join(masks, "&nbsp;"))
} }
func printCores(millicores *uint64) string { func getActiveCores(mask string) map[int]bool {
// TODO(vmarmol): Detect this correctly activeCores := make(map[int]bool)
if *millicores > 1024*1000 { for _, corebits := range strings.Split(mask, ",") {
return "unlimited" cores := strings.Split(corebits, "-")
if len(cores) == 1 {
index, err := strconv.Atoi(cores[0])
if err != nil {
// Ignore malformed strings.
continue
}
activeCores[index] = true
} else if len(cores) == 2 {
start, err := strconv.Atoi(cores[0])
if err != nil {
continue
}
end, err := strconv.Atoi(cores[1])
if err != nil {
continue
}
for i := start; i <= end; i++ {
activeCores[i] = true
}
}
} }
return activeCores
}
func printCores(millicores *uint64) string {
cores := float64(*millicores) / 1000 cores := float64(*millicores) / 1000
return strconv.FormatFloat(cores, 'f', 3, 64) return strconv.FormatFloat(cores, 'f', 3, 64)
} }
func printShares(shares *uint64) string {
return fmt.Sprintf("%d", *shares)
}
func toMegabytes(bytes uint64) float64 { func toMegabytes(bytes uint64) float64 {
return float64(bytes) / (1 << 20) return float64(bytes) / (1 << 20)
} }
func printMegabytes(bytes uint64) string { func printMegabytes(bytes uint64) string {
// TODO(vmarmol): Detect this correctly if bytes >= math.MaxInt64 {
if bytes > (100 << 30) {
return "unlimited" return "unlimited"
} }
megabytes := toMegabytes(bytes) megabytes := toMegabytes(bytes)
...@@ -139,18 +163,30 @@ func toMemoryPercent(usage uint64, spec *info.ContainerSpec, machine *info.Machi ...@@ -139,18 +163,30 @@ func toMemoryPercent(usage uint64, spec *info.ContainerSpec, machine *info.Machi
} }
func getMemoryUsage(stats []*info.ContainerStats) string { func getMemoryUsage(stats []*info.ContainerStats) string {
if len(stats) == 0 {
return "0.0"
}
return strconv.FormatFloat(toMegabytes((stats[len(stats)-1].Memory.Usage)), 'f', 2, 64) return strconv.FormatFloat(toMegabytes((stats[len(stats)-1].Memory.Usage)), 'f', 2, 64)
} }
func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int {
if len(stats) == 0 {
return 0
}
return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec, machine) return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec, machine)
} }
func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int {
if len(stats) == 0 {
return 0
}
return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec, machine) return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec, machine)
} }
func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int {
if len(stats) == 0 {
return 0
}
latestStats := stats[len(stats)-1].Memory latestStats := stats[len(stats)-1].Memory
return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine)
} }
...@@ -204,6 +240,14 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) ...@@ -204,6 +240,14 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
} }
} }
networkStatsAvailable := false
for _, stat := range cont.Stats {
if stat.Network != nil {
networkStatsAvailable = true
break
}
}
data := &pageData{ data := &pageData{
ContainerName: displayName, ContainerName: displayName,
// TODO(vmarmol): Only use strings for this. // TODO(vmarmol): Only use strings for this.
...@@ -215,6 +259,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) ...@@ -215,6 +259,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
ResourcesAvailable: cont.Spec.Cpu != nil || cont.Spec.Memory != nil, ResourcesAvailable: cont.Spec.Cpu != nil || cont.Spec.Memory != nil,
CpuAvailable: cont.Spec.Cpu != nil, CpuAvailable: cont.Spec.Cpu != nil,
MemoryAvailable: cont.Spec.Memory != nil, MemoryAvailable: cont.Spec.Memory != nil,
NetworkAvailable: networkStatsAvailable,
} }
err = pageTemplate.Execute(w, data) err = pageTemplate.Execute(w, data)
if err != nil { if err != nil {
......
...@@ -90,9 +90,14 @@ function getMachineInfo(callback) { ...@@ -90,9 +90,14 @@ function getMachineInfo(callback) {
// Get the container stats for the specified container. // Get the container stats for the specified container.
function getStats(containerName, callback) { function getStats(containerName, callback) {
$.getJSON("/api/v1.0/containers" + containerName, function(data) { // Request 60s of container history and no samples.
callback(data); var request = JSON.stringify({
"num_stats": 60,
"num_samples": 0
}); });
$.post("/api/v1.0/containers" + containerName, request, function(data) {
callback(data);
}, "json");
} }
// Draw the graph for CPU usage. // Draw the graph for CPU usage.
...@@ -163,7 +168,7 @@ function drawOverallUsage(elementId, machineInfo, containerInfo) { ...@@ -163,7 +168,7 @@ function drawOverallUsage(elementId, machineInfo, containerInfo) {
var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total;
// Convert to millicores and take the percentage // Convert to millicores and take the percentage
cpuUsage = Math.round(((rawUsage / 1000000) / containerInfo.spec.cpu.limit) * 100); cpuUsage = Math.round(((rawUsage / 1000000) / (machineInfo.num_cores * 1000)) * 100);
if (cpuUsage > 100) { if (cpuUsage > 100) {
cpuUsage = 100; cpuUsage = 100;
} }
...@@ -219,6 +224,42 @@ function drawMemoryPageFaults(elementId, containerInfo) { ...@@ -219,6 +224,42 @@ function drawMemoryPageFaults(elementId, containerInfo) {
drawLineChart(titles, data, elementId, "Faults"); drawLineChart(titles, data, elementId, "Faults");
} }
// Draw the graph for network tx/rx bytes.
function drawNetworkBytes(elementId, machineInfo, stats) {
var titles = ["Time", "Tx bytes", "Rx bytes"];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
// TODO(vmarmol): This assumes we sample every second, use the timestamps.
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.network.tx_bytes - prev.network.tx_bytes);
elements.push(cur.network.rx_bytes - prev.network.rx_bytes);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Bytes per second");
}
// Draw the graph for network errors
function drawNetworkErrors(elementId, machineInfo, stats) {
var titles = ["Time", "Tx", "Rx"];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
// TODO(vmarmol): This assumes we sample every second, use the timestamps.
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.network.tx_errors - prev.network.tx_errors);
elements.push(cur.network.rx_errors - prev.network.rx_errors);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Errors per second");
}
// Expects an array of closures to call. After each execution the JS runtime is given control back before continuing. // Expects an array of closures to call. After each execution the JS runtime is given control back before continuing.
// This function returns asynchronously // This function returns asynchronously
function stepExecute(steps) { function stepExecute(steps) {
...@@ -264,6 +305,14 @@ function drawCharts(machineInfo, containerInfo) { ...@@ -264,6 +305,14 @@ function drawCharts(machineInfo, containerInfo) {
drawMemoryPageFaults("memory-page-faults-chart", containerInfo); drawMemoryPageFaults("memory-page-faults-chart", containerInfo);
}); });
// Network.
steps.push(function() {
drawNetworkBytes("network-bytes-chart", machineInfo, containerInfo);
});
steps.push(function() {
drawNetworkErrors("network-errors-chart", machineInfo, containerInfo);
});
stepExecute(steps); stepExecute(steps);
} }
......
// 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 cache
import (
"github.com/google/cadvisor/info"
"github.com/google/cadvisor/storage"
"github.com/google/cadvisor/storage/memory"
)
type cachedStorageDriver struct {
maxNumStatsInCache int
maxNumSamplesInCache int
cache storage.StorageDriver
backend storage.StorageDriver
}
func (self *cachedStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
err := self.cache.AddStats(ref, stats)
if err != nil {
return err
}
err = self.backend.AddStats(ref, stats)
if err != nil {
return err
}
return nil
}
func (self *cachedStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
if numStats < self.maxNumStatsInCache {
return self.cache.RecentStats(containerName, numStats)
}
return self.backend.RecentStats(containerName, numStats)
}
func (self *cachedStorageDriver) Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) {
return self.backend.Percentiles(containerName, cpuUsagePercentiles, memUsagePercentiles)
}
func (self *cachedStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
if numSamples < self.maxNumSamplesInCache {
return self.cache.Samples(containerName, numSamples)
}
return self.backend.Samples(containerName, numSamples)
}
func (self *cachedStorageDriver) Close() error {
self.cache.Close()
return self.backend.Close()
}
func MemoryCache(maxNumSamplesInCache, maxNumStatsInCache int, driver storage.StorageDriver) storage.StorageDriver {
return &cachedStorageDriver{
maxNumStatsInCache: maxNumStatsInCache,
maxNumSamplesInCache: maxNumSamplesInCache,
cache: memory.New(maxNumSamplesInCache, maxNumStatsInCache),
backend: driver,
}
}
// 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 cache
import (
"testing"
"github.com/google/cadvisor/storage"
"github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/storage/test"
)
func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
maxSize := 200
var driver storage.StorageDriver
for N := 10; N < maxSize; N += 10 {
backend := memory.New(N*2, N*2)
driver = MemoryCache(N, N, backend)
f(driver, t)
}
}
func TestMaxMemoryUsage(t *testing.T) {
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
}
func TestSampleCpuUsage(t *testing.T) {
runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
}
func TestSamplesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
}
func TestPercentilesWithoutSample(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
}
func TestPercentiles(t *testing.T) {
N := 100
backend := memory.New(N*2, N*2)
driver := MemoryCache(N, N, backend)
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)
}
...@@ -22,7 +22,7 @@ import ( ...@@ -22,7 +22,7 @@ import (
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage"
"github.com/influxdb/influxdb-go" influxdb "github.com/influxdb/influxdb/client"
) )
type influxdbStorage struct { type influxdbStorage struct {
...@@ -320,6 +320,9 @@ func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.C ...@@ -320,6 +320,9 @@ func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.C
} }
func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) { func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
if numStats == 0 {
return nil, nil
}
// TODO(dengnan): select only columns that we need // TODO(dengnan): select only columns that we need
// TODO(dengnan): escape names // TODO(dengnan): escape names
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName) query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
...@@ -352,6 +355,9 @@ func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([] ...@@ -352,6 +355,9 @@ func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]
} }
func (self *influxdbStorage) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) { func (self *influxdbStorage) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
if numSamples == 0 {
return nil, nil
}
// TODO(dengnan): select only columns that we need // TODO(dengnan): select only columns that we need
// TODO(dengnan): escape names // TODO(dengnan): escape names
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName) query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
......
...@@ -21,7 +21,7 @@ import ( ...@@ -21,7 +21,7 @@ import (
"github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage"
"github.com/google/cadvisor/storage/test" "github.com/google/cadvisor/storage/test"
"github.com/influxdb/influxdb-go" influxdb "github.com/influxdb/influxdb/client"
) )
func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
...@@ -95,42 +95,64 @@ func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { ...@@ -95,42 +95,64 @@ func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
f(driver, t) f(driver, t)
} }
// TODO(vmarmol): Don't skip these tests when Travis is fixed.
func TestSampleCpuUsage(t *testing.T) { func TestSampleCpuUsage(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestSampleCpuUsage, t) runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
} }
func TestRetrievePartialRecentStats(t *testing.T) { func TestRetrievePartialRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t) runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
} }
func TestSamplesWithoutSample(t *testing.T) { func TestSamplesWithoutSample(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t) runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
} }
func TestRetrieveAllRecentStats(t *testing.T) { func TestRetrieveAllRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t) runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
} }
func TestNoRecentStats(t *testing.T) { func TestNoRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestNoRecentStats, t) runStorageTest(test.StorageDriverTestNoRecentStats, t)
} }
func TestNoSamples(t *testing.T) { func TestNoSamples(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestNoSamples, t) runStorageTest(test.StorageDriverTestNoSamples, t)
} }
func TestPercentiles(t *testing.T) { func TestPercentiles(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestPercentiles, t) runStorageTest(test.StorageDriverTestPercentiles, t)
} }
func TestMaxMemoryUsage(t *testing.T) { func TestMaxMemoryUsage(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t) runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
} }
func TestPercentilesWithoutSample(t *testing.T) { func TestPercentilesWithoutSample(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t) runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
} }
func TestPercentilesWithoutStats(t *testing.T) { func TestPercentilesWithoutStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t) runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
} }
func TestRetrieveZeroStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrieveZeroRecentStats, t)
}
func TestRetrieveZeroSamples(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrieveZeroSamples, t)
}
...@@ -73,3 +73,11 @@ func TestNoSamples(t *testing.T) { ...@@ -73,3 +73,11 @@ func TestNoSamples(t *testing.T) {
func TestPercentilesWithoutStats(t *testing.T) { func TestPercentilesWithoutStats(t *testing.T) {
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t) runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
} }
func TestRetrieveZeroStats(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrieveZeroRecentStats, t)
}
func TestRetrieveZeroSamples(t *testing.T) {
runStorageTest(test.StorageDriverTestRetrieveZeroSamples, t)
}
...@@ -482,3 +482,61 @@ func StorageDriverTestPercentilesWithoutStats(driver storage.StorageDriver, t *t ...@@ -482,3 +482,61 @@ func StorageDriverTestPercentilesWithoutStats(driver storage.StorageDriver, t *t
} }
} }
} }
func StorageDriverTestRetrieveZeroRecentStats(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, 0)
if err != nil {
t.Fatal(err)
}
if len(recentStats) > 0 {
t.Errorf("RecentStats() returns %v stats when requests for 0 stats", len(recentStats))
}
}
func StorageDriverTestRetrieveZeroSamples(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)
}
samples, err := driver.Samples(ref.Name, 0)
if err != nil {
t.Fatal(err)
}
if len(samples) > 0 {
t.Errorf("RecentStats() returns %v stats when requests for 0 stats", len(samples))
}
}
// 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 fs
import (
"io"
"os"
)
type osFS struct{}
func (osFS) Open(name string) (File, error) { return os.Open(name) }
func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
var fs FileSystem = osFS{}
type FileSystem interface {
Open(name string) (File, error)
}
type File interface {
io.ReadWriteCloser
}
// Useful for tests. Not thread safe.
func ChangeFileSystem(filesystem FileSystem) {
fs = filesystem
}
func Open(name string) (File, error) {
return fs.Open(name)
}
// 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 mockfs
import "bytes"
type FakeFile struct {
bytes.Buffer
Name string
}
func (self *FakeFile) Close() error {
return nil
}
func AddTextFile(mockfs *MockFileSystem, name, content string) *FakeFile {
f := &FakeFile{
Name: name,
Buffer: *bytes.NewBufferString(content),
}
mockfs.EXPECT().Open(name).Return(f, nil).AnyTimes()
return f
}
// 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.
// Automatically generated by MockGen. DO NOT EDIT!
// Source: github.com/google/cadvisor/utils/fs (interfaces: FileSystem)
package mockfs
import (
gomock "code.google.com/p/gomock/gomock"
fs "github.com/google/cadvisor/utils/fs"
)
// Mock of FileSystem interface
type MockFileSystem struct {
ctrl *gomock.Controller
recorder *_MockFileSystemRecorder
}
// Recorder for MockFileSystem (not exported)
type _MockFileSystemRecorder struct {
mock *MockFileSystem
}
func NewMockFileSystem(ctrl *gomock.Controller) *MockFileSystem {
mock := &MockFileSystem{ctrl: ctrl}
mock.recorder = &_MockFileSystemRecorder{mock}
return mock
}
func (_m *MockFileSystem) EXPECT() *_MockFileSystemRecorder {
return _m.recorder
}
func (_m *MockFileSystem) Open(_param0 string) (fs.File, error) {
ret := _m.ctrl.Call(_m, "Open", _param0)
ret0, _ := ret[0].(fs.File)
ret1, _ := ret[1].(error)
return ret0, ret1
}
func (_mr *_MockFileSystemRecorder) Open(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Open", arg0)
}
// 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 utils
import "os"
func FileExists(file string) bool {
if _, err := os.Stat(file); err != nil {
return false
}
return true
}
// 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.
// procfs contains several low level functions to read information from /proc
// filesystem, and also provides some utility functions like JiffiesToDuration.
package procfs
// 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 procfs
/*
#include <unistd.h>
*/
import "C"
import "time"
var userHz uint64
func init() {
userHzLong := C.sysconf(C._SC_CLK_TCK)
userHz = uint64(userHzLong)
}
func JiffiesToDuration(jiffies uint64) time.Duration {
d := jiffies * 1000000000 / userHz
return time.Duration(d)
}
// 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 procfs
import (
"bufio"
"fmt"
"io"
"strconv"
"github.com/google/cadvisor/utils/fs"
)
type ProcessSchedStat struct {
// Number of processes
NumProcesses int
// Total time spent on the cpu (Unit: jiffy)
Running uint64
// Total time spent waiting on a runqueue (Unit: jiffy)
RunWait uint64
// # of timeslices run on this cpu (Unit: jiffy)
NumTimeSlices uint64
}
func readUint64List(r io.Reader) ([]uint64, error) {
ret := make([]uint64, 0, 4)
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
str := scanner.Text()
u, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return nil, err
}
ret = append(ret, u)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return ret, nil
}
// Add() read the schedstat of pid, and add stat to the fields
// in self parameters. This function is useful if one wants to read stats of
// a group of processes.
func (self *ProcessSchedStat) Add(pid int) error {
if self == nil {
return fmt.Errorf("nil stat")
}
path := fmt.Sprintf("/proc/%d/schedstat", pid)
f, err := fs.Open(path)
if err != nil {
return err
}
defer f.Close()
v, err := readUint64List(f)
if err != nil {
return err
}
if len(v) < 3 {
return fmt.Errorf("only %v fields read from %v: %v", len(v), path, v)
}
self.Running += v[0]
self.RunWait += v[1]
self.NumTimeSlices += v[2]
self.NumProcesses++
return nil
}
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package procfs
import (
"fmt"
"reflect"
"testing"
"code.google.com/p/gomock/gomock"
"github.com/google/cadvisor/utils/fs"
"github.com/google/cadvisor/utils/fs/mockfs"
)
func TestReadProcessSchedStat(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mfs := mockfs.NewMockFileSystem(mockCtrl)
pid := 10
stat := &ProcessSchedStat{
NumProcesses: 1,
Running: 100,
RunWait: 120,
NumTimeSlices: 130,
}
path := fmt.Sprintf("/proc/%v/schedstat", pid)
content := fmt.Sprintf("%v %v %v\n", stat.Running, stat.RunWait, stat.NumTimeSlices)
mockfs.AddTextFile(mfs, path, content)
fs.ChangeFileSystem(mfs)
receivedStat := &ProcessSchedStat{}
err := receivedStat.Add(pid)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(receivedStat, stat) {
t.Errorf("Received wrong schedstat: %+v", receivedStat)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment