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

View File

@ -545,7 +545,7 @@ func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.Conta
ret := &info.ContainerInfoRequest{
NumStats: req.NumStats,
CpuUsagePercentiles: req.CpuUsagePercentiles,
MemoryUsagePercentages: req.MemoryUsagePercentages,
MemoryUsagePercentiles: req.MemoryUsagePercentiles,
}
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/oauth
code.google.com/p/google-api-go-client/compute/v1
github.com/google/cadvisor/info
github.com/google/cadvisor/client
github.com/google/cadvisor
"
DEP_PACKAGES="

View File

@ -1,5 +1,20 @@
# 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)
- Add support for systemd systems.
- Fixes for UI with InfluxDB storage driver.

View File

@ -1,6 +1,6 @@
# 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.
@ -13,11 +13,12 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above),
```
sudo docker run \
--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 \
--publish=8080:8080 \
--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.

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package inference
package interference
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)
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)
err := decoder.Decode(&query)
if err != nil && err != io.EOF {

View File

@ -22,7 +22,7 @@ import (
"github.com/google/cadvisor/api"
"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/manager"
"github.com/google/cadvisor/pages"
@ -30,7 +30,6 @@ import (
)
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")
@ -47,30 +46,14 @@ func main() {
log.Fatalf("Failed to create a Container Manager: %s", err)
}
// Register lmctfy for the root if allowed and available.
registeredRoot := false
if *argAllowLmctfy {
if err := lmctfy.Register("/"); err != nil {
log.Printf("lmctfy registration failed: %v.", err)
log.Print("Running in docker only mode.")
} else {
registeredRoot = true
}
}
// Register Docker for root if we were unable to register lmctfy.
if !registeredRoot {
if err := docker.Register(containerManager, "/"); err != nil {
log.Printf("Docker registration failed: %v.", err)
log.Fatalf("Unable to continue without root handler.")
}
}
// Register Docker for all Docker containers.
if err := docker.Register(containerManager, "/docker"); err != nil {
// Ignore this error because we should work with lmctfy only
// Register Docker.
if err := docker.Register(containerManager); err != nil {
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.
@ -100,11 +83,14 @@ func main() {
}
})
go containerManager.Start()
go func() {
log.Fatal(containerManager.Start())
}()
log.Printf("Starting cAdvisor version: %q", info.VERSION)
log.Print("About to serve on port ", *argPort)
addr := fmt.Sprintf(":%v", *argPort)
log.Fatal(http.ListenAndServe(addr, nil))
}

View File

@ -17,11 +17,15 @@ package docker
import (
"flag"
"fmt"
"log"
"regexp"
"strconv"
"strings"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/fsouza/go-dockerclient"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/info"
)
@ -29,6 +33,11 @@ var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "do
type dockerFactory struct {
machineInfoFactory info.MachineInfoFactory
// Whether this system is using systemd.
useSystemd bool
client *docker.Client
}
func (self *dockerFactory) String() string {
@ -44,10 +53,42 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C
client,
name,
self.machineInfoFactory,
self.useSystemd,
)
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) {
version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)"
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!
func Register(factory info.MachineInfoFactory, paths ...string) error {
func Register(factory info.MachineInfoFactory) error {
client, err := docker.NewClient(*ArgDockerEndpoint)
if err != nil {
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{
machineInfoFactory: factory,
useSystemd: systemd.UseSystemd(),
client: client,
}
for _, p := range paths {
if p != "/" && p != "/docker" {
return fmt.Errorf("%v cannot be managed by docker", p)
}
container.RegisterContainerHandlerFactory(p, f)
if f.useSystemd {
log.Printf("System is using systemd")
}
log.Printf("Registering Docker factory")
container.RegisterContainerHandlerFactory(f)
return nil
}

View File

@ -15,52 +15,66 @@
package docker
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log"
"math"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/fsouza/go-dockerclient"
"github.com/google/cadvisor/container"
containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
"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 {
client *docker.Client
name string
parent string
id string
aliases []string
machineInfoFactory info.MachineInfoFactory
useSystemd bool
}
func newDockerContainerHandler(
client *docker.Client,
name string,
machineInfoFactory info.MachineInfoFactory,
useSystemd bool,
) (container.ContainerHandler, error) {
handler := &dockerContainerHandler{
client: client,
name: name,
machineInfoFactory: machineInfoFactory,
useSystemd: useSystemd,
}
if !handler.isDockerContainer() {
if handler.isDockerRoot() {
return handler, nil
}
_, id, err := handler.splitName()
parent, id, err := containerLibcontainer.SplitName(name)
if err != nil {
return nil, fmt.Errorf("invalid docker container %v: %v", name, err)
}
handler.parent = parent
handler.id = id
ctnr, err := client.InspectContainer(id)
// We assume that if Inspect fails then the container is not known to docker.
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))
return handler, nil
@ -73,71 +87,66 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc
}, 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 {
// TODO(dengnan): Should we consider other cases?
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.
func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) {
dir := "/var/lib/docker/execdriver/native"
configPath := path.Join(dir, id, "container.json")
func (self *dockerContainerHandler) readLibcontainerConfig() (config *libcontainer.Config, err error) {
configPath := path.Join(dockerRootDir, self.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)
if err != nil {
return
return nil, fmt.Errorf("failed to open %s - %s\n", configPath, err)
}
defer f.Close()
d := json.NewDecoder(f)
ret := new(libcontainer.Config)
err = d.Decode(ret)
retConfig := new(libcontainer.Config)
err = d.Decode(retConfig)
if err != nil {
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
}
@ -159,111 +168,59 @@ func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.Mac
if config.Cgroups.CpuShares != 0 {
spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
}
n := (mi.NumCores + 63) / 64
spec.Cpu.Mask.Data = make([]uint64, n)
for i := 0; i < n; i++ {
spec.Cpu.Mask.Data[i] = math.MaxUint64
if config.Cgroups.CpusetCpus == "" {
// All cores are active.
spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
} else {
spec.Cpu.Mask = config.Cgroups.CpusetCpus
}
// TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus
return spec
}
func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) {
if !self.isDockerContainer() {
spec = new(info.ContainerSpec)
return
if self.isDockerRoot() {
return &info.ContainerSpec{}, nil
}
mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil {
return
}
_, id, err := self.splitName()
if err != nil {
return
}
libcontainerSpec, err := readLibcontainerSpec(id)
libcontainerConfig, err := self.readLibcontainerConfig()
if err != nil {
return
}
spec = libcontainerConfigToContainerSpec(libcontainerSpec, mi)
spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi)
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) {
if !self.isDockerContainer() {
// Return empty stats for root containers.
stats = new(info.ContainerStats)
stats.Timestamp = time.Now()
return
if self.isDockerRoot() {
return &info.ContainerStats{}, nil
}
mi, err := self.machineInfoFactory.GetMachineInfo()
config, err := self.readLibcontainerConfig()
if err != nil {
if err == fileNotFound {
log.Printf("Libcontainer config not found for container %q", self.name)
return &info.ContainerStats{}, nil
}
return
}
parent, id, err := self.splitName()
state, err := self.readLibcontainerState()
if err != nil {
if err == fileNotFound {
log.Printf("Libcontainer state not found for container %q", self.name)
return &info.ContainerStats{}, nil
}
return
}
cg := &cgroups.Cgroup{
Parent: parent,
Name: id,
}
// TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready.
// 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
return containerLibcontainer.GetStats(config, state)
}
func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
if self.isDockerContainer() {
return nil, nil
}
if self.isRootContainer() && listType == container.LIST_SELF {
return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil
if self.name != "/docker" {
return []info.ContainerReference{}, nil
}
opt := docker.ListContainersOptions{
All: true,
@ -272,22 +229,26 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType)
if err != nil {
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)
for _, c := range containers {
if !strings.HasPrefix(c.Status, "Up ") {
continue
}
path := fmt.Sprintf("/docker/%v", c.ID)
aliases := c.Names
ref := info.ContainerReference{
Name: path,
Aliases: aliases,
Name: filepath.Join(containerPrefix, c.ID),
Aliases: c.Names,
}
ret = append(ret, ref)
}
if self.isRootContainer() {
ret = append(ret, info.ContainerReference{Name: "/docker"})
}
return ret, nil
}
@ -296,5 +257,9 @@ func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]
}
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 (
"fmt"
"log"
"strings"
"sync"
)
type ContainerHandlerFactory interface {
// Create a new ContainerHandler using this factory. CanHandle() must have returned true.
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
}
type factoryTreeNode struct {
defaultFactory ContainerHandlerFactory
children map[string]*factoryTreeNode
// TODO(vmarmol): Consider not making this global.
// Global list of factories.
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 {
node := self
for _, elem := range elems {
if len(node.children) == 0 {
break
}
if child, ok := node.children[elem]; ok {
node = child
} else {
return node.defaultFactory
// Create a new ContainerHandler for the specified container.
func NewContainerHandler(name string) (ContainerHandler, error) {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Create the ContainerHandler with the first factory that supports it.
for _, factory := range factories {
if factory.CanHandle(name) {
log.Printf("Using factory %q for container %q", factory.String(), name)
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) {
node := self
for _, elem := range elems {
if node.children == nil {
node.children = make(map[string]*factoryTreeNode, 16)
}
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)
// Clear the known factories.
func ClearContainerHandlerFactories() {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = make([]ContainerHandlerFactory, 0, 4)
}

View File

@ -15,7 +15,6 @@
package container
import (
"strings"
"testing"
"github.com/stretchr/testify/mock"
@ -23,54 +22,101 @@ import (
type mockContainerHandlerFactory struct {
mock.Mock
Name string
Name string
CanHandleValue bool
}
func (self *mockContainerHandlerFactory) String() string {
return self.Name
}
func (self *mockContainerHandlerFactory) CanHandle(name string) bool {
return self.CanHandleValue
}
func (self *mockContainerHandlerFactory) NewContainerHandler(name string) (ContainerHandler, error) {
args := self.Called(name)
return args.Get(0).(ContainerHandler), args.Error(1)
}
func testExpectedFactory(root *factoryTreeNode, path, expectedFactory string, t *testing.T) {
elems := dropEmptyString(strings.Split(path, "/")...)
factory := root.find(elems...)
if factory.String() != expectedFactory {
t.Errorf("factory %v should be used to create container %v. but %v is selected",
expectedFactory,
path,
factory)
const testContainerName = "/test"
var mockFactory FactoryForMockContainerHandler
func TestNewContainerHandler_FirstMatches(t *testing.T) {
ClearContainerHandlerFactories()
// 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 {
elems := dropEmptyString(strings.Split(path, "/")...)
if root == nil {
root = &factoryTreeNode{
defaultFactory: nil,
}
func TestNewContainerHandler_SecondMatches(t *testing.T) {
ClearContainerHandlerFactories()
// Register one allways no and one always yes factory.
allwaysNo := &mockContainerHandlerFactory{
Name: "no",
CanHandleValue: false,
}
f := &mockContainerHandlerFactory{
Name: path,
RegisterContainerHandlerFactory(allwaysNo)
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) {
root := testAddFactory(nil, "/")
root = testAddFactory(root, "/docker")
root = testAddFactory(root, "/user")
root = testAddFactory(root, "/user/special/containers")
func TestNewContainerHandler_NoneMatch(t *testing.T) {
ClearContainerHandlerFactories()
testExpectedFactory(root, "/docker/container", "/docker", t)
testExpectedFactory(root, "/docker", "/docker", t)
testExpectedFactory(root, "/", "/", t)
testExpectedFactory(root, "/user/deep/level/container", "/user", t)
testExpectedFactory(root, "/user/special/containers", "/user/special/containers", t)
testExpectedFactory(root, "/user/special/containers/container", "/user/special/containers", t)
testExpectedFactory(root, "/other", "/", t)
// Register two allways no factories.
allwaysNo1 := &mockContainerHandlerFactory{
Name: "no",
CanHandleValue: false,
}
RegisterContainerHandlerFactory(allwaysNo1)
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.On("ListContainers", LIST_RECURSIVE).Return(
[]info.ContainerReference{
info.ContainerReference{Name: "/docker/ee0103"},
info.ContainerReference{Name: "/container/created/by/lmctfy"},
info.ContainerReference{Name: "/user/something"},
{Name: "/docker/ee0103"},
{Name: "/container/created/by/lmctfy"},
{Name: "/user/something"},
},
nil,
)
@ -95,9 +95,9 @@ func TestBlackListContainerFilter(t *testing.T) {
mockc := &mockContainerHandler{}
mockc.On("ListContainers", LIST_RECURSIVE).Return(
[]info.ContainerReference{
info.ContainerReference{Name: "/docker/ee0103"},
info.ContainerReference{Name: "/container/created/by/lmctfy"},
info.ContainerReference{Name: "/user/something"},
{Name: "/docker/ee0103"},
{Name: "/container/created/by/lmctfy"},
{Name: "/user/something"},
},
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"
)
type CpuSpecMask struct {
Data []uint64 `json:"data,omitempty"`
}
type CpuSpec struct {
Limit uint64 `json:"limit"`
MaxLimit uint64 `json:"max_limit"`
Mask CpuSpecMask `json:"mask,omitempty"`
Limit uint64 `json:"limit"`
MaxLimit uint64 `json:"max_limit"`
Mask string `json:"mask,omitempty"`
}
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]
CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"`
// Different percentiles of memory usage within a period. The values must be within [0, 100]
MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"`
}
func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest {
ret := self
if ret == nil {
ret = new(ContainerInfoRequest)
}
if ret.NumStats <= 0 {
ret.NumStats = 1024
}
if ret.NumSamples <= 0 {
ret.NumSamples = 1024
}
if len(ret.CpuUsagePercentiles) == 0 {
ret.CpuUsagePercentiles = []int{50, 80, 90, 99}
}
if len(ret.MemoryUsagePercentages) == 0 {
ret.MemoryUsagePercentages = []int{50, 80, 90, 99}
}
return ret
MemoryUsagePercentiles []int `json:"memory_usage_percentiles,omitempty"`
}
type ContainerInfo struct {
@ -239,11 +215,31 @@ type MemoryStatsMemoryData struct {
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 {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
Cpu *CpuStats `json:"cpu,omitempty"`
Memory *MemoryStats `json:"memory,omitempty"`
Timestamp time.Time `json:"timestamp"`
Cpu *CpuStats `json:"cpu,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

View File

@ -16,7 +16,6 @@ package test
import (
"fmt"
"math"
"math/rand"
"time"
@ -59,12 +58,7 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
}
ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000))
ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))
n := (numCores + 63) / 64
ret.Cpu.Mask.Data = make([]uint64, n)
for i := 0; i < n; i++ {
ret.Cpu.Mask.Data[i] = math.MaxUint64
}
ret.Cpu.Mask = fmt.Sprintf("0-%d", numCores-1)
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
return ret
}
@ -83,8 +77,8 @@ func GenerateRandomContainerInfo(containerName string, numCores int, query *info
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
cpuPercentiles = append(cpuPercentiles, percentile)
}
memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages))
for _, p := range query.MemoryUsagePercentages {
memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentiles))
for _, p := range query.MemoryUsagePercentiles {
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
memPercentiles = append(memPercentiles, percentile)
}

View File

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

View File

@ -23,7 +23,6 @@ import (
"time"
"github.com/google/cadvisor/container"
ctest "github.com/google/cadvisor/container/test"
"github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test"
"github.com/google/cadvisor/storage"
@ -32,17 +31,18 @@ import (
func createContainerDataAndSetHandler(
driver storage.StorageDriver,
f func(*ctest.MockContainerHandler),
f func(*container.MockContainerHandler),
t *testing.T,
) *containerData {
factory := &ctest.FactoryForMockContainerHandler{
factory := &container.FactoryForMockContainerHandler{
Name: "factoryForMockContainer",
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) {
handler.Name = name
f(handler)
},
}
container.RegisterContainerHandlerFactory("/", factory)
container.ClearContainerHandlerFactories()
container.RegisterContainerHandlerFactory(factory)
if driver == nil {
driver = &stest.MockStorageDriver{}
@ -56,7 +56,7 @@ func createContainerDataAndSetHandler(
}
func TestContainerUpdateSubcontainers(t *testing.T) {
var handler *ctest.MockContainerHandler
var handler *container.MockContainerHandler
subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"},
{Name: "/container/abcd"},
@ -64,7 +64,7 @@ func TestContainerUpdateSubcontainers(t *testing.T) {
}
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return(
subcontainers,
nil,
@ -99,10 +99,10 @@ func TestContainerUpdateSubcontainers(t *testing.T) {
}
func TestContainerUpdateSubcontainersWithError(t *testing.T) {
var handler *ctest.MockContainerHandler
var handler *container.MockContainerHandler
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
h.On("ListContainers", container.LIST_SELF).Return(
[]info.ContainerReference{},
fmt.Errorf("some error"),
@ -124,7 +124,7 @@ func TestContainerUpdateSubcontainersWithError(t *testing.T) {
}
func TestContainerUpdateStats(t *testing.T) {
var handler *ctest.MockContainerHandler
var handler *container.MockContainerHandler
var ref info.ContainerReference
driver := &stest.MockStorageDriver{}
@ -134,7 +134,7 @@ func TestContainerUpdateStats(t *testing.T) {
cd := createContainerDataAndSetHandler(
driver,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
h.On("GetStats").Return(
stats,
nil,
@ -156,11 +156,11 @@ func TestContainerUpdateStats(t *testing.T) {
}
func TestContainerUpdateSpec(t *testing.T) {
var handler *ctest.MockContainerHandler
var handler *container.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4)
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
h.On("GetSpec").Return(
spec,
nil,
@ -179,7 +179,7 @@ func TestContainerUpdateSpec(t *testing.T) {
}
func TestContainerGetInfo(t *testing.T) {
var handler *ctest.MockContainerHandler
var handler *container.MockContainerHandler
spec := itest.GenerateRandomContainerSpec(4)
subcontainers := []info.ContainerReference{
{Name: "/container/ee0103"},
@ -189,7 +189,7 @@ func TestContainerGetInfo(t *testing.T) {
aliases := []string{"a1", "a2"}
cd := createContainerDataAndSetHandler(
nil,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
h.On("GetSpec").Return(
spec,
nil,

View File

@ -130,11 +130,10 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats
query = query.FillDefaults()
percentiles, err = m.storageDriver.Percentiles(
cinfo.Name,
query.CpuUsagePercentiles,
query.MemoryUsagePercentages,
query.MemoryUsagePercentiles,
)
if err != nil {
return nil, err
@ -281,18 +280,18 @@ func (m *manager) detectContainers() error {
}
// Add the new containers.
for _, container := range added {
_, err = m.createContainer(container.Name)
for _, cont := range added {
_, err = m.createContainer(cont.Name)
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.
for _, container := range removed {
err = m.destroyContainer(container.Name)
for _, cont := range removed {
err = m.destroyContainer(cont.Name)
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"
"github.com/google/cadvisor/container"
ctest "github.com/google/cadvisor/container/test"
"github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test"
stest "github.com/google/cadvisor/storage/test"
@ -31,15 +30,15 @@ import (
func createManagerAndAddContainers(
driver *stest.MockStorageDriver,
containers []string,
f func(*ctest.MockContainerHandler),
f func(*container.MockContainerHandler),
t *testing.T,
) *manager {
if driver == nil {
driver = &stest.MockStorageDriver{}
}
factory := &ctest.FactoryForMockContainerHandler{
factory := &container.FactoryForMockContainerHandler{
Name: "factoryForManager",
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) {
handler.Name = name
found := false
for _, c := range containers {
@ -53,7 +52,8 @@ func createManagerAndAddContainers(
f(handler)
},
}
container.RegisterContainerHandlerFactory("/", factory)
container.ClearContainerHandlerFactories()
container.RegisterContainerHandlerFactory(factory)
mif, err := New(driver)
if err != nil {
t.Fatal(err)
@ -81,11 +81,11 @@ func TestGetContainerInfo(t *testing.T) {
NumStats: 256,
NumSamples: 128,
CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentages: []int{10, 80, 90},
MemoryUsagePercentiles: []int{10, 80, 90},
}
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 {
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
@ -95,7 +95,7 @@ func TestGetContainerInfo(t *testing.T) {
m := createManagerAndAddContainers(
driver,
containers,
func(h *ctest.MockContainerHandler) {
func(h *container.MockContainerHandler) {
cinfo := infosMap[h.Name]
stats := cinfo.Stats
samples := cinfo.Samples
@ -105,7 +105,7 @@ func TestGetContainerInfo(t *testing.T) {
"Percentiles",
h.Name,
query.CpuUsagePercentiles,
query.MemoryUsagePercentages,
query.MemoryUsagePercentiles,
).Return(
percentiles,
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"
"html/template"
"log"
"math"
"net/http"
"net/url"
"path"
@ -36,6 +37,7 @@ var funcMap = template.FuncMap{
"containerLink": containerLink,
"printMask": printMask,
"printCores": printCores,
"printShares": printShares,
"printMegabytes": printMegabytes,
"getMemoryUsage": getMemoryUsage,
"getMemoryUsagePercent": getMemoryUsagePercent,
@ -57,6 +59,7 @@ type pageData struct {
ResourcesAvailable bool
CpuAvailable bool
MemoryAvailable bool
NetworkAvailable bool
}
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))
}
func printMask(mask *info.CpuSpecMask, 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]
}
func printMask(mask string, numCores int) interface{} {
masks := make([]string, numCores)
for i := uint(0); i < uint(numCores); i++ {
activeCores := getActiveCores(mask)
for i := 0; i < numCores; i++ {
coreClass := "inactive-cpu"
// by default, all cores are active
if ((0x1<<i)&rawMask) != 0 || len(mask.Data) == 0 {
if activeCores[i] {
coreClass = "active-cpu"
}
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;"))
}
func printCores(millicores *uint64) string {
// TODO(vmarmol): Detect this correctly
if *millicores > 1024*1000 {
return "unlimited"
func getActiveCores(mask string) map[int]bool {
activeCores := make(map[int]bool)
for _, corebits := range strings.Split(mask, ",") {
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
return strconv.FormatFloat(cores, 'f', 3, 64)
}
func printShares(shares *uint64) string {
return fmt.Sprintf("%d", *shares)
}
func toMegabytes(bytes uint64) float64 {
return float64(bytes) / (1 << 20)
}
func printMegabytes(bytes uint64) string {
// TODO(vmarmol): Detect this correctly
if bytes > (100 << 30) {
if bytes >= math.MaxInt64 {
return "unlimited"
}
megabytes := toMegabytes(bytes)
@ -139,18 +163,30 @@ func toMemoryPercent(usage uint64, spec *info.ContainerSpec, machine *info.Machi
}
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)
}
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)
}
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)
}
func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int {
if len(stats) == 0 {
return 0
}
latestStats := stats[len(stats)-1].Memory
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{
ContainerName: displayName,
// 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,
CpuAvailable: cont.Spec.Cpu != nil,
MemoryAvailable: cont.Spec.Memory != nil,
NetworkAvailable: networkStatsAvailable,
}
err = pageTemplate.Execute(w, data)
if err != nil {

View File

@ -16,145 +16,160 @@ package pages
const containersHtmlTemplate = `
<html>
<head>
<title>cAdvisor - Container {{.ContainerName}}</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<head>
<title>cAdvisor - Container {{.ContainerName}}</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<!-- Optional theme -->
<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 -->
<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 type="text/javascript" src="https://www.google.com/jsapi"></script>
<!-- Latest compiled and minified JavaScript -->
<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 type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="/static/containers.js"></script>
</head>
<body>
<div class="container theme-showcase" >
<div class="col-sm-12" id="logo">
</div>
<div class="col-sm-12">
<div class="page-header">
<h1>{{.ContainerName}}</h1>
</div>
<ol class="breadcrumb">
{{range $parentContainer := .ParentContainers}}
<li>{{containerLink $parentContainer true ""}}</li>
{{end}}
</ol>
</div>
{{if .Subcontainers}}
<div class="col-sm-12">
<div class="page-header">
<h3>Subcontainers</h3>
</div>
<div class="list-group">
{{range $subcontainer := .Subcontainers}}
{{containerLink $subcontainer false "list-group-item"}}
<script type="text/javascript" src="/static/containers.js"></script>
</head>
<body>
<div class="container theme-showcase" >
<div class="col-sm-12" id="logo">
</div>
<div class="col-sm-12">
<div class="page-header">
<h1>{{.ContainerName}}</h1>
</div>
<ol class="breadcrumb">
{{range $parentContainer := .ParentContainers}}
<li>{{containerLink $parentContainer true ""}}</li>
{{end}}
</ol>
</div>
{{if .Subcontainers}}
<div class="col-sm-12">
<div class="page-header">
<h3>Subcontainers</h3>
</div>
<div class="list-group">
{{range $subcontainer := .Subcontainers}}
{{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}}
</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">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>
<script type="text/javascript">
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}});
</script>
</body>
</html>
`

View File

@ -90,9 +90,14 @@ function getMachineInfo(callback) {
// Get the container stats for the specified container.
function getStats(containerName, callback) {
$.getJSON("/api/v1.0/containers" + containerName, function(data) {
callback(data);
// Request 60s of container history and no samples.
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.
@ -163,7 +168,7 @@ function drawOverallUsage(elementId, machineInfo, containerInfo) {
var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total;
// 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) {
cpuUsage = 100;
}
@ -219,6 +224,42 @@ function drawMemoryPageFaults(elementId, containerInfo) {
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.
// This function returns asynchronously
function stepExecute(steps) {
@ -264,6 +305,14 @@ function drawCharts(machineInfo, 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);
}

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/storage"
"github.com/influxdb/influxdb-go"
influxdb "github.com/influxdb/influxdb/client"
)
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) {
if numStats == 0 {
return nil, nil
}
// TODO(dengnan): select only columns that we need
// TODO(dengnan): escape names
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
@ -352,6 +355,9 @@ func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]
}
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): escape names
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/test"
"github.com/influxdb/influxdb-go"
influxdb "github.com/influxdb/influxdb/client"
)
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)
}
// TODO(vmarmol): Don't skip these tests when Travis is fixed.
func TestSampleCpuUsage(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
}
func TestRetrievePartialRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
}
func TestSamplesWithoutSample(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
}
func TestRetrieveAllRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
}
func TestNoRecentStats(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestNoRecentStats, t)
}
func TestNoSamples(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestNoSamples, t)
}
func TestPercentiles(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestPercentiles, t)
}
func TestMaxMemoryUsage(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
}
func TestPercentilesWithoutSample(t *testing.T) {
t.SkipNow()
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
}
func TestPercentilesWithoutStats(t *testing.T) {
t.SkipNow()
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) {
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)
}
}