Merge pull request #713 from dchen1107/restart

Fix #707
This commit is contained in:
Victor Marmol 2014-07-31 10:33:01 -07:00
commit a6f907e128
43 changed files with 1887 additions and 642 deletions

View File

@ -62,7 +62,6 @@ func testHTTPContainerInfoGetter(
// So changing req after Get*Info would be a race. // So changing req after Get*Info would be a race.
expectedReq := req expectedReq := req
// Fill any empty fields with default value // Fill any empty fields with default value
expectedReq = expectedReq.FillDefaults()
if !reflect.DeepEqual(expectedReq, &receivedReq) { if !reflect.DeepEqual(expectedReq, &receivedReq) {
t.Errorf("received wrong request") t.Errorf("received wrong request")
} }
@ -110,10 +109,11 @@ func testHTTPContainerInfoGetter(
func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) { func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
CpuUsagePercentiles: []int{20, 30},
MemoryUsagePercentiles: []int{40, 50},
} }
req = req.FillDefaults()
cinfo := itest.GenerateRandomContainerInfo( cinfo := itest.GenerateRandomContainerInfo(
"dockerIDWhichWillNotBeChecked", // docker ID "dockerIDWhichWillNotBeChecked", // docker ID
2, // Number of cores 2, // Number of cores
@ -125,10 +125,11 @@ func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) {
func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
CpuUsagePercentiles: []int{20, 30},
MemoryUsagePercentiles: []int{40, 50},
} }
req = req.FillDefaults()
cinfo := itest.GenerateRandomContainerInfo( cinfo := itest.GenerateRandomContainerInfo(
"dockerIDWhichWillNotBeChecked", // docker ID "dockerIDWhichWillNotBeChecked", // docker ID
2, // Number of cores 2, // Number of cores
@ -140,10 +141,11 @@ func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) {
func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) { func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
CpuUsagePercentiles: []int{20, 30},
MemoryUsagePercentiles: []int{40, 50},
} }
req = req.FillDefaults()
cinfo := itest.GenerateRandomContainerInfo( cinfo := itest.GenerateRandomContainerInfo(
"dockerIDWhichWillNotBeChecked", // docker ID "dockerIDWhichWillNotBeChecked", // docker ID
2, // Number of cores 2, // Number of cores
@ -155,10 +157,11 @@ func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) {
func TestHTTPContainerInfoGetterGetRootInfoWithError(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoWithError(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
CpuUsagePercentiles: []int{20, 30},
MemoryUsagePercentiles: []int{40, 50},
} }
req = req.FillDefaults()
cinfo := itest.GenerateRandomContainerInfo( cinfo := itest.GenerateRandomContainerInfo(
"dockerIDWhichWillNotBeChecked", // docker ID "dockerIDWhichWillNotBeChecked", // docker ID
2, // Number of cores 2, // Number of cores

View File

@ -545,7 +545,7 @@ func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.Conta
ret := &info.ContainerInfoRequest{ ret := &info.ContainerInfoRequest{
NumStats: req.NumStats, NumStats: req.NumStats,
CpuUsagePercentiles: req.CpuUsagePercentiles, CpuUsagePercentiles: req.CpuUsagePercentiles,
MemoryUsagePercentages: req.MemoryUsagePercentages, MemoryUsagePercentiles: req.MemoryUsagePercentiles,
} }
return ret return ret
} }

3
third_party/deps.sh vendored
View File

@ -5,8 +5,7 @@ TOP_PACKAGES="
code.google.com/p/goauth2/compute/serviceaccount code.google.com/p/goauth2/compute/serviceaccount
code.google.com/p/goauth2/oauth code.google.com/p/goauth2/oauth
code.google.com/p/google-api-go-client/compute/v1 code.google.com/p/google-api-go-client/compute/v1
github.com/google/cadvisor/info github.com/google/cadvisor
github.com/google/cadvisor/client
" "
DEP_PACKAGES=" DEP_PACKAGES="

View File

@ -1,5 +1,20 @@
# 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.

View File

@ -1,6 +1,6 @@
# 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),
``` ```
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.

View File

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

View File

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

View File

@ -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 (
) )
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() {
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 {
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) log.Printf("Docker registration failed: %v.", err)
log.Print("Running in lmctfy only mode.") }
// Register the raw driver.
if err := raw.Register(containerManager); err != nil {
log.Fatalf("raw registration failed: %v.", err)
} }
// Handler for static content. // Handler for static content.
@ -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))
} }

View File

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

View File

@ -15,52 +15,66 @@
package docker package docker
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log"
"math" "math"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs" "github.com/docker/libcontainer/cgroups/fs"
"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"
containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/info" "github.com/google/cadvisor/info"
"github.com/google/cadvisor/utils"
) )
// Basepath to all container specific information that libcontainer stores.
const dockerRootDir = "/var/lib/docker/execdriver/native"
var fileNotFound = errors.New("file not found")
type dockerContainerHandler struct { type dockerContainerHandler struct {
client *docker.Client client *docker.Client
name string name string
parent string
id string
aliases []string aliases []string
machineInfoFactory info.MachineInfoFactory machineInfoFactory info.MachineInfoFactory
useSystemd bool
} }
func newDockerContainerHandler( func newDockerContainerHandler(
client *docker.Client, client *docker.Client,
name string, name string,
machineInfoFactory info.MachineInfoFactory, machineInfoFactory info.MachineInfoFactory,
useSystemd bool,
) (container.ContainerHandler, error) { ) (container.ContainerHandler, error) {
handler := &dockerContainerHandler{ handler := &dockerContainerHandler{
client: client, client: client,
name: name, name: name,
machineInfoFactory: machineInfoFactory, machineInfoFactory: machineInfoFactory,
useSystemd: useSystemd,
} }
if !handler.isDockerContainer() { if handler.isDockerRoot() {
return handler, nil return handler, nil
} }
_, id, err := handler.splitName() parent, id, err := containerLibcontainer.SplitName(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid docker container %v: %v", name, err) return nil, fmt.Errorf("invalid docker container %v: %v", name, err)
} }
handler.parent = parent
handler.id = id
ctnr, err := client.InspectContainer(id) ctnr, err := client.InspectContainer(id)
// We assume that if Inspect fails then the container is not known to docker.
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to inspect container %v: %v", name, err) return nil, fmt.Errorf("failed to inspect container %s - %s\n", id, err)
} }
handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name)) handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
return handler, nil return handler, nil
@ -73,71 +87,66 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc
}, nil }, nil
} }
func (self *dockerContainerHandler) splitName() (string, string, error) {
parent, id := path.Split(self.name)
cgroupSelf, err := os.Open("/proc/self/cgroup")
if err != nil {
return "", "", err
}
scanner := bufio.NewScanner(cgroupSelf)
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 {
// count how many nested docker containers are there.
nestedLevels = strings.Count(elems[2], "/docker")
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
}
func (self *dockerContainerHandler) isDockerRoot() bool { func (self *dockerContainerHandler) isDockerRoot() bool {
// TODO(dengnan): Should we consider other cases?
return self.name == "/docker" return self.name == "/docker"
} }
func (self *dockerContainerHandler) isRootContainer() bool {
return self.name == "/"
}
func (self *dockerContainerHandler) isDockerContainer() bool {
return (!self.isDockerRoot()) && (!self.isRootContainer())
}
// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API. // TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) { func (self *dockerContainerHandler) readLibcontainerConfig() (config *libcontainer.Config, err error) {
dir := "/var/lib/docker/execdriver/native" configPath := path.Join(dockerRootDir, self.id, "container.json")
configPath := path.Join(dir, id, "container.json") if !utils.FileExists(configPath) {
// TODO(vishh): Return file name as well once we have a better error interface.
err = fileNotFound
return
}
f, err := os.Open(configPath) f, err := os.Open(configPath)
if err != nil { if err != nil {
return return nil, fmt.Errorf("failed to open %s - %s\n", configPath, err)
} }
defer f.Close() defer f.Close()
d := json.NewDecoder(f) d := json.NewDecoder(f)
ret := new(libcontainer.Config) retConfig := new(libcontainer.Config)
err = d.Decode(ret) err = d.Decode(retConfig)
if err != nil { if err != nil {
return return
} }
spec = ret config = retConfig
// Replace cgroup parent and name with our own since we may be running in a different context.
config.Cgroups.Parent = self.parent
config.Cgroups.Name = self.id
return
}
func (self *dockerContainerHandler) readLibcontainerState() (state *libcontainer.State, err error) {
statePath := path.Join(dockerRootDir, self.id, "state.json")
if !utils.FileExists(statePath) {
// TODO(vmarmol): Remove this once we can depend on a newer Docker.
// Libcontainer changed how its state was stored, try the old way of a "pid" file
if utils.FileExists(path.Join(dockerRootDir, self.id, "pid")) {
// We don't need the old state, return an empty state and we'll gracefully degrade.
state = new(libcontainer.State)
return
}
// TODO(vishh): Return file name as well once we have a better error interface.
err = fileNotFound
return
}
f, err := os.Open(statePath)
if err != nil {
return nil, fmt.Errorf("failed to open %s - %s\n", statePath, err)
}
defer f.Close()
d := json.NewDecoder(f)
retState := new(libcontainer.State)
err = d.Decode(retState)
if err != nil {
return
}
state = retState
return return
} }
@ -159,111 +168,59 @@ func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.Mac
if config.Cgroups.CpuShares != 0 { if config.Cgroups.CpuShares != 0 {
spec.Cpu.Limit = uint64(config.Cgroups.CpuShares) spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
} }
n := (mi.NumCores + 63) / 64 if config.Cgroups.CpusetCpus == "" {
spec.Cpu.Mask.Data = make([]uint64, n) // All cores are active.
for i := 0; i < n; i++ { spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
spec.Cpu.Mask.Data[i] = math.MaxUint64 } else {
spec.Cpu.Mask = config.Cgroups.CpusetCpus
} }
// TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus
return spec return spec
} }
func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) { func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) {
if !self.isDockerContainer() { if self.isDockerRoot() {
spec = new(info.ContainerSpec) return &info.ContainerSpec{}, nil
return
} }
mi, err := self.machineInfoFactory.GetMachineInfo() mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil { if err != nil {
return return
} }
_, id, err := self.splitName() libcontainerConfig, err := self.readLibcontainerConfig()
if err != nil {
return
}
libcontainerSpec, err := readLibcontainerSpec(id)
if err != nil { if err != nil {
return return
} }
spec = libcontainerConfigToContainerSpec(libcontainerSpec, mi) spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi)
return return
} }
func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.ContainerStats {
ret := new(info.ContainerStats)
ret.Timestamp = time.Now()
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
}
}
return ret
}
func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) { func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
if !self.isDockerContainer() { if self.isDockerRoot() {
// Return empty stats for root containers. return &info.ContainerStats{}, nil
stats = new(info.ContainerStats)
stats.Timestamp = time.Now()
return
} }
mi, err := self.machineInfoFactory.GetMachineInfo() config, err := self.readLibcontainerConfig()
if err != nil { if err != nil {
if err == fileNotFound {
log.Printf("Libcontainer config not found for container %q", self.name)
return &info.ContainerStats{}, nil
}
return return
} }
parent, id, err := self.splitName() state, err := self.readLibcontainerState()
if err != nil { if err != nil {
if err == fileNotFound {
log.Printf("Libcontainer state not found for container %q", self.name)
return &info.ContainerStats{}, nil
}
return return
} }
cg := &cgroups.Cgroup{
Parent: parent,
Name: id,
}
// TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready. return containerLibcontainer.GetStats(config, state)
// Use systemd paths if systemd is being used.
var s *cgroups.Stats
if systemd.UseSystemd() {
s, err = systemd.GetStats(cg)
} else {
s, err = fs.GetStats(cg)
}
if err != nil {
return
}
stats = libcontainerToContainerStats(s, mi)
return
} }
func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
if self.isDockerContainer() { if self.name != "/docker" {
return nil, nil return []info.ContainerReference{}, nil
}
if self.isRootContainer() && listType == container.LIST_SELF {
return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil
} }
opt := docker.ListContainersOptions{ opt := docker.ListContainersOptions{
All: true, All: true,
@ -272,22 +229,26 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// On non-systemd systems Docker containers are under /docker.
containerPrefix := "/docker"
if self.useSystemd {
containerPrefix = "/system.slice"
}
ret := make([]info.ContainerReference, 0, len(containers)+1) ret := make([]info.ContainerReference, 0, len(containers)+1)
for _, c := range containers { for _, c := range containers {
if !strings.HasPrefix(c.Status, "Up ") { if !strings.HasPrefix(c.Status, "Up ") {
continue continue
} }
path := fmt.Sprintf("/docker/%v", c.ID)
aliases := c.Names
ref := info.ContainerReference{ ref := info.ContainerReference{
Name: path, Name: filepath.Join(containerPrefix, c.ID),
Aliases: aliases, Aliases: c.Names,
} }
ret = append(ret, ref) ret = append(ret, ref)
} }
if self.isRootContainer() {
ret = append(ret, info.ContainerReference{Name: "/docker"})
}
return ret, nil return ret, nil
} }
@ -296,5 +257,9 @@ func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]
} }
func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return nil, nil c := &cgroups.Cgroup{
Parent: self.parent,
Name: self.id,
}
return fs.GetPids(c)
} }

View File

@ -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.
CanHandle(name string) bool
// Name of the factory.
String() string String() string
} }
type factoryTreeNode struct { // TODO(vmarmol): Consider not making this global.
defaultFactory ContainerHandlerFactory // Global list of factories.
children map[string]*factoryTreeNode var (
factories []ContainerHandlerFactory
factoriesLock sync.RWMutex
)
// Register a ContainerHandlerFactory. These should be registered from least general to most general
// as they will be asked in order whether they can handle a particular container.
func RegisterContainerHandlerFactory(factory ContainerHandlerFactory) {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = append(factories, factory)
} }
func (self *factoryTreeNode) find(elems ...string) ContainerHandlerFactory { // Create a new ContainerHandler for the specified container.
node := self func NewContainerHandler(name string) (ContainerHandler, error) {
for _, elem := range elems { factoriesLock.RLock()
if len(node.children) == 0 { defer factoriesLock.RUnlock()
break
} // Create the ContainerHandler with the first factory that supports it.
if child, ok := node.children[elem]; ok { for _, factory := range factories {
node = child if factory.CanHandle(name) {
} else { log.Printf("Using factory %q for container %q", factory.String(), name)
return node.defaultFactory return factory.NewContainerHandler(name)
} }
} }
return node.defaultFactory return nil, fmt.Errorf("no known factory can handle creation of container")
} }
func (self *factoryTreeNode) add(factory ContainerHandlerFactory, elems ...string) { // Clear the known factories.
node := self func ClearContainerHandlerFactories() {
for _, elem := range elems { factoriesLock.Lock()
if node.children == nil { defer factoriesLock.Unlock()
node.children = make(map[string]*factoryTreeNode, 16)
} factories = make([]ContainerHandlerFactory, 0, 4)
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 {
root *factoryTreeNode
lock sync.RWMutex
}
func dropEmptyString(elems ...string) []string {
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!
func (self *factoryManager) Register(path string, factory ContainerHandlerFactory) {
self.lock.Lock()
defer self.lock.Unlock()
if self.root == nil {
self.root = &factoryTreeNode{
defaultFactory: nil,
children: make(map[string]*factoryTreeNode, 10),
}
}
elems := dropEmptyString(strings.Split(path, "/")...)
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
func RegisterContainerHandlerFactory(path string, factory ContainerHandlerFactory) {
globalFactoryManager.Register(path, factory)
}
func NewContainerHandler(path string) (ContainerHandler, error) {
return globalFactoryManager.NewContainerHandler(path)
} }

View File

@ -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 (
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")
root = testAddFactory(root, "/user/special/containers")
testExpectedFactory(root, "/docker/container", "/docker", t) // Register two allways no factories.
testExpectedFactory(root, "/docker", "/docker", t) allwaysNo1 := &mockContainerHandlerFactory{
testExpectedFactory(root, "/", "/", t) Name: "no",
testExpectedFactory(root, "/user/deep/level/container", "/user", t) CanHandleValue: false,
testExpectedFactory(root, "/user/special/containers", "/user/special/containers", t) }
testExpectedFactory(root, "/user/special/containers/container", "/user/special/containers", t) RegisterContainerHandlerFactory(allwaysNo1)
testExpectedFactory(root, "/other", "/", t) allwaysNo2 := &mockContainerHandlerFactory{
Name: "no",
CanHandleValue: false,
}
RegisterContainerHandlerFactory(allwaysNo2)
_, err := NewContainerHandler(testContainerName)
if err == nil {
t.Error("Expected NewContainerHandler to fail")
}
} }

View File

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

View File

@ -0,0 +1,135 @@
// 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
}

View File

@ -0,0 +1,90 @@
// 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)
}
}
}

View File

@ -0,0 +1,91 @@
// 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
}

View File

@ -0,0 +1,96 @@
// 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": {},
}

View File

@ -0,0 +1,206 @@
// 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)
}

View File

@ -0,0 +1,10 @@
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"]

View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
set -x
# Statically build cAdvisor from source and stage it.
go build --ldflags '-extldflags "-static"' github.com/google/cadvisor

View File

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

View File

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

View File

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

View File

@ -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 (
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(
} }
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) {
} }
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) {
} }
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) {
} }
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) {
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) {
} }
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) {
} }
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) {
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,

View File

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

View File

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

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

View File

@ -16,145 +16,160 @@ package pages
const containersHtmlTemplate = ` const containersHtmlTemplate = `
<html> <html>
<head> <head>
<title>cAdvisor - Container {{.ContainerName}}</title> <title>cAdvisor - Container {{.ContainerName}}</title>
<!-- Latest compiled and minified CSS --> <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<!-- Optional theme --> <!-- Optional theme -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/containers.css"> <link rel="stylesheet" href="/static/containers.css">
<!-- Latest compiled and minified JavaScript --> <!-- Latest compiled and minified JavaScript -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="/static/containers.js"></script> <script type="text/javascript" src="/static/containers.js"></script>
</head> </head>
<body> <body>
<div class="container theme-showcase" > <div class="container theme-showcase" >
<div class="col-sm-12" id="logo"> <div class="col-sm-12" id="logo">
</div> </div>
<div class="col-sm-12"> <div class="col-sm-12">
<div class="page-header"> <div class="page-header">
<h1>{{.ContainerName}}</h1> <h1>{{.ContainerName}}</h1>
</div> </div>
<ol class="breadcrumb"> <ol class="breadcrumb">
{{range $parentContainer := .ParentContainers}} {{range $parentContainer := .ParentContainers}}
<li>{{containerLink $parentContainer true ""}}</li> <li>{{containerLink $parentContainer true ""}}</li>
{{end}} {{end}}
</ol> </ol>
</div> </div>
{{if .Subcontainers}} {{if .Subcontainers}}
<div class="col-sm-12"> <div class="col-sm-12">
<div class="page-header"> <div class="page-header">
<h3>Subcontainers</h3> <h3>Subcontainers</h3>
</div> </div>
<div class="list-group"> <div class="list-group">
{{range $subcontainer := .Subcontainers}} {{range $subcontainer := .Subcontainers}}
{{containerLink $subcontainer false "list-group-item"}} {{containerLink $subcontainer false "list-group-item"}}
{{end}}
</div>
</div>
{{end}}
{{if .ResourcesAvailable}}
<div class="col-sm-12">
<div class="page-header">
<h3>Isolation</h3>
</div>
{{if .CpuAvailable}}
<ul class="list-group">
<li class="list-group-item active isolation-title panel-title">CPU</li>
{{if .Spec.Cpu.Limit}}
<li class="list-group-item"><span class="stat-label">Shares</span> {{printShares .Spec.Cpu.Limit}} <span class="unit-label">shares</span></li>
{{end}}
{{if .Spec.Cpu.MaxLimit}}
<li class="list-group-item"><span class="stat-label">Max Limit</span> {{printCores .Spec.Cpu.MaxLimit}} <span class="unit-label">cores</span></li>
{{end}}
{{if .Spec.Cpu.Mask}}
<li class="list-group-item"><span class="stat-label">Allowed Cores</span> {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}</li>
{{end}}
</ul>
{{end}}
{{if .MemoryAvailable}}
<ul class="list-group">
<li class="list-group-item active isolation-title panel-title">Memory</li>
{{if .Spec.Memory.Reservation}}
<li class="list-group-item"><span class="stat-label">Reservation</span> {{printMegabytes .Spec.Memory.Reservation}} <span class="unit-label">MB</span></li>
{{end}}
{{if .Spec.Memory.Limit}}
<li class="list-group-item"><span class="stat-label">Limit</span> {{printMegabytes .Spec.Memory.Limit}} <span class="unit-label">MB</span></li>
{{end}}
{{if .Spec.Memory.SwapLimit}}
<li class="list-group-item"><span class="stat-label">Swap Limit</span> {{printMegabytes .Spec.Memory.SwapLimit}} <span class="unit-label">MB</span></li>
{{end}}
</ul>
{{end}}
</div>
<div class="col-sm-12">
<div class="page-header">
<h3>Usage</h3>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Overview</h3>
</div>
<div id="usage-gauge" class="panel-body">
</div>
</div>
{{if .CpuAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">CPU</h3>
</div>
<div class="panel-body">
<h4>Total Usage</h4>
<div id="cpu-total-usage-chart"></div>
<h4>Usage per Core</h4>
<div id="cpu-per-core-usage-chart"></div>
<h4>Usage Breakdown</h4>
<div id="cpu-usage-breakdown-chart"></div>
</div>
</div>
{{end}}
{{if .MemoryAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Memory</h3>
</div>
<div class="panel-body">
<h4>Total Usage</h4>
<div id="memory-usage-chart"></div>
<br/>
<div class="row col-sm-12">
<h4>Usage Breakdown</h4>
<div class="col-sm-9">
<div class="progress">
<div class="progress-bar progress-bar-danger" style="width: {{getHotMemoryPercent .Spec .Stats .MachineInfo}}%">
<span class="sr-only">Hot Memory</span>
</div>
<div class="progress-bar progress-bar-info" style="width: {{getColdMemoryPercent .Spec .Stats .MachineInfo}}%">
<span class="sr-only">Cold Memory</span>
</div>
</div>
</div>
<div class="col-sm-3">
{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)
</div>
</div>
<h4>Page Faults</h4>
<div id="memory-page-faults-chart"></div>
</div>
</div>
{{end}}
{{if .NetworkAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Network</h3>
</div>
<div class="panel-body">
<h4>Throughput</h4>
<div id="network-bytes-chart"></div>
</div>
<div class="panel-body">
<h4>Errors</h4>
<div id="network-errors-chart"></div>
</div>
</div>
{{end}}
</div>
{{end}} {{end}}
</div> </div>
</div> <script type="text/javascript">
{{end}} startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}});
{{if .ResourcesAvailable}} </script>
<div class="col-sm-12"> </body>
<div class="page-header">
<h3>Isolation</h3>
</div>
{{if .CpuAvailable}}
<ul class="list-group">
<li class="list-group-item active isolation-title panel-title">CPU</li>
{{if .Spec.Cpu.Limit}}
<li class="list-group-item"><span class="stat-label">Limit</span> {{printCores .Spec.Cpu.Limit}} <span class="unit-label">cores</span></li>
{{end}}
{{if .Spec.Cpu.MaxLimit}}
<li class="list-group-item"><span class="stat-label">Max Limit</span> {{printCores .Spec.Cpu.MaxLimit}} <span class="unit-label">cores</span></li>
{{end}}
{{if .Spec.Cpu.Mask}}
<li class="list-group-item"><span class="stat-label">Allowed Cores</span> {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}</li>
{{end}}
</ul>
{{end}}
{{if .MemoryAvailable}}
<ul class="list-group">
<li class="list-group-item active isolation-title panel-title">Memory</li>
{{if .Spec.Memory.Reservation}}
<li class="list-group-item"><span class="stat-label">Reservation</span> {{printMegabytes .Spec.Memory.Reservation}} <span class="unit-label">MB</span></li>
{{end}}
{{if .Spec.Memory.Limit}}
<li class="list-group-item"><span class="stat-label">Limit</span> {{printMegabytes .Spec.Memory.Limit}} <span class="unit-label">MB</span></li>
{{end}}
{{if .Spec.Memory.SwapLimit}}
<li class="list-group-item"><span class="stat-label">Swap Limit</span> {{printMegabytes .Spec.Memory.SwapLimit}} <span class="unit-label">MB</span></li>
{{end}}
</ul>
{{end}}
</div>
<div class="col-sm-12">
<div class="page-header">
<h3>Usage</h3>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Overview</h3>
</div>
<div id="usage-gauge" class="panel-body">
</div>
</div>
{{if .CpuAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">CPU</h3>
</div>
<div class="panel-body">
<h4>Total Usage</h4>
<div id="cpu-total-usage-chart"></div>
<h4>Usage per Core</h4>
<div id="cpu-per-core-usage-chart"></div>
<h4>Usage Breakdown</h4>
<div id="cpu-usage-breakdown-chart"></div>
</div>
</div>
{{end}}
{{if .MemoryAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Memory</h3>
</div>
<div class="panel-body">
<h4>Total Usage</h4>
<div id="memory-usage-chart"></div>
<br/>
<div class="row col-sm-12">
<h4>Usage Breakdown</h4>
<div class="col-sm-9">
<div class="progress">
<div class="progress-bar progress-bar-danger" style="width: {{getHotMemoryPercent .Spec .Stats .MachineInfo}}%">
<span class="sr-only">Hot Memory</span>
</div>
<div class="progress-bar progress-bar-info" style="width: {{getColdMemoryPercent .Spec .Stats .MachineInfo}}%">
<span class="sr-only">Cold Memory</span>
</div>
</div>
</div>
<div class="col-sm-3">
{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)
</div>
</div>
<h4>Page Faults</h4>
<div id="memory-page-faults-chart"></div>
</div>
</div>
{{end}}
</div>
{{end}}
</div>
<script type="text/javascript">
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}});
</script>
</body>
</html> </html>
` `

View File

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

View File

@ -0,0 +1,72 @@
// 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,
}
}

View File

@ -0,0 +1,78 @@
// 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)
}

View File

@ -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
} }
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) ([]
} }
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)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,44 @@
// 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)
}

View File

@ -0,0 +1,35 @@
// 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
}

View File

@ -0,0 +1,55 @@
// 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)
}

View File

@ -0,0 +1,24 @@
// 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
}

View File

@ -0,0 +1,17 @@
// 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

View File

@ -0,0 +1,33 @@
// 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)
}

View File

@ -0,0 +1,87 @@
// 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
}

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