Bump cAdvisor (and dependencies) godeps version

This commit is contained in:
Tim St. Clair
2016-05-20 11:43:32 -07:00
parent 4215fe57a5
commit 237f90d6ee
388 changed files with 3788 additions and 121879 deletions

View File

@@ -361,7 +361,10 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
infos, err := m.GetRequestedContainersInfo(name, opt)
if err != nil {
return err
if len(infos) == 0 {
return err
}
glog.Errorf("Error calling GetRequestedContainersInfo: %v", err)
}
contStats := make(map[string][]v2.DeprecatedContainerStats, 0)
for name, cinfo := range infos {
@@ -482,7 +485,10 @@ func (self *version2_1) HandleRequest(requestType string, request []string, m ma
glog.V(4).Infof("Api - MachineStats(%v)", request)
cont, err := m.GetRequestedContainersInfo("/", opt)
if err != nil {
return err
if len(cont) == 0 {
return err
}
glog.Errorf("Error calling GetRequestedContainersInfo: %v", err)
}
return writeResult(v2.MachineStatsFromV1(cont["/"]), w)
case statsApi:
@@ -490,7 +496,10 @@ func (self *version2_1) HandleRequest(requestType string, request []string, m ma
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
conts, err := m.GetRequestedContainersInfo(name, opt)
if err != nil {
return err
if len(conts) == 0 {
return err
}
glog.Errorf("Error calling GetRequestedContainersInfo: %v", err)
}
contStats := make(map[string]v2.ContainerInfo, len(conts))
for name, cont := range conts {

View File

@@ -23,6 +23,7 @@ import (
"strings"
"time"
"github.com/google/cadvisor/container"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils"
@@ -201,3 +202,23 @@ func CgroupExists(cgroupPaths map[string]string) bool {
}
return false
}
func ListContainers(name string, cgroupPaths map[string]string, listType container.ListType) ([]info.ContainerReference, error) {
containers := make(map[string]struct{})
for _, cgroupPath := range cgroupPaths {
err := ListDirectories(cgroupPath, name, listType == container.ListRecursive, 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
}

View File

@@ -27,23 +27,15 @@ const (
ListRecursive
)
// SubcontainerEventType indicates an addition or deletion event.
type SubcontainerEventType int
type ContainerType int
const (
SubcontainerAdd SubcontainerEventType = iota
SubcontainerDelete
ContainerTypeRaw ContainerType = iota
ContainerTypeDocker
ContainerTypeRkt
ContainerTypeSystemd
)
// SubcontainerEvent represents a
type SubcontainerEvent struct {
// The type of event that occurred.
EventType SubcontainerEventType
// The full container name of the container where the event occurred.
Name string
}
// Interface for container operation handlers.
type ContainerHandler interface {
// Returns the ContainerReference
@@ -58,18 +50,9 @@ type ContainerHandler interface {
// Returns the subcontainers of this container.
ListContainers(listType ListType) ([]info.ContainerReference, error)
// Returns the threads inside this container.
ListThreads(listType ListType) ([]int, error)
// Returns the processes inside this container.
ListProcesses(listType ListType) ([]int, error)
// Registers a channel to listen for events affecting subcontainers (recursively).
WatchSubcontainers(events chan SubcontainerEvent) error
// Stops watching for subcontainer changes.
StopWatchingSubcontainers() error
// Returns absolute cgroup path for the requested resource.
GetCgroupPath(resource string) (string, error)
@@ -85,4 +68,7 @@ type ContainerHandler interface {
// Start starts any necessary background goroutines - must be cleaned up in Cleanup().
// It is expected that most implementations will be a no-op.
Start()
// Type of handler
Type() ContainerType
}

View File

@@ -20,18 +20,18 @@ package docker
import (
"sync"
dclient "github.com/fsouza/go-dockerclient"
dclient "github.com/docker/engine-api/client"
)
var (
dockerClient *dclient.Client
dockerClientErr error
once sync.Once
dockerClient *dclient.Client
dockerClientErr error
dockerClientOnce sync.Once
)
func Client() (*dclient.Client, error) {
once.Do(func() {
dockerClient, dockerClientErr = dclient.NewClient(*ArgDockerEndpoint)
dockerClientOnce.Do(func() {
dockerClient, dockerClientErr = dclient.NewClient(*ArgDockerEndpoint, "", nil, nil)
})
return dockerClient, dockerClientErr
}

View File

@@ -0,0 +1,163 @@
// Copyright 2016 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.
// Provides global docker information.
package docker
import (
"fmt"
"strconv"
"strings"
dockertypes "github.com/docker/engine-api/types"
"golang.org/x/net/context"
"github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/machine"
)
func Status() (v1.DockerStatus, error) {
client, err := Client()
if err != nil {
return v1.DockerStatus{}, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info(context.Background())
if err != nil {
return v1.DockerStatus{}, err
}
out := v1.DockerStatus{}
out.Version = VersionString()
out.KernelVersion = machine.KernelVersion()
out.OS = dockerInfo.OperatingSystem
out.Hostname = dockerInfo.Name
out.RootDir = dockerInfo.DockerRootDir
out.Driver = dockerInfo.Driver
out.ExecDriver = dockerInfo.ExecutionDriver
out.NumImages = dockerInfo.Images
out.NumContainers = dockerInfo.Containers
out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus))
for _, v := range dockerInfo.DriverStatus {
out.DriverStatus[v[0]] = v[1]
}
return out, nil
}
func Images() ([]v1.DockerImage, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
images, err := client.ImageList(context.Background(), dockertypes.ImageListOptions{All: false})
if err != nil {
return nil, err
}
out := []v1.DockerImage{}
const unknownTag = "<none>:<none>"
for _, image := range images {
if len(image.RepoTags) == 1 && image.RepoTags[0] == unknownTag {
// images with repo or tags are uninteresting.
continue
}
di := v1.DockerImage{
ID: image.ID,
RepoTags: image.RepoTags,
Created: image.Created,
VirtualSize: image.VirtualSize,
Size: image.Size,
}
out = append(out, di)
}
return out, nil
}
// Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an
// error otherwise.
func ValidateInfo() (*dockertypes.Info, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to detect Docker info: %v", err)
}
// Fall back to version API if ServerVersion is not set in info.
if dockerInfo.ServerVersion == "" {
version, err := client.ServerVersion(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to get docker version: %v", err)
}
dockerInfo.ServerVersion = version.Version
}
version, err := parseDockerVersion(dockerInfo.ServerVersion)
if err != nil {
return nil, err
}
if version[0] < 1 {
return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion)
}
// Check that the libcontainer execdriver is used if the version is < 1.11
// (execution drivers are no longer supported as of 1.11).
if version[0] <= 1 && version[1] <= 10 &&
!strings.HasPrefix(dockerInfo.ExecutionDriver, "native") {
return nil, fmt.Errorf("docker found, but not using native exec driver")
}
if dockerInfo.Driver == "" {
return nil, fmt.Errorf("failed to find docker storage driver")
}
return &dockerInfo, nil
}
func Version() ([]int, error) {
return parseDockerVersion(VersionString())
}
func VersionString() string {
docker_version := "Unknown"
client, err := Client()
if err == nil {
version, err := client.ServerVersion(context.Background())
if err == nil {
docker_version = version.Version
}
}
return docker_version
}
// TODO: switch to a semantic versioning library.
func parseDockerVersion(full_version_string string) ([]int, error) {
matches := version_re.FindAllStringSubmatch(full_version_string, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string)
}
version_string_array := matches[0][1:]
version_array := make([]int, 3)
for index, version_string := range version_string_array {
version, err := strconv.Atoi(version_string)
if err != nil {
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_string, full_version_string)
}
version_array[index] = version
}
return version_array, nil
}

View File

@@ -19,27 +19,26 @@ import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
"sync"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/devicemapper"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"
dockerutil "github.com/google/cadvisor/utils/docker"
docker "github.com/fsouza/go-dockerclient"
docker "github.com/docker/engine-api/client"
"github.com/golang/glog"
"golang.org/x/net/context"
)
var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "docker endpoint")
// The namespace under which Docker aliases are unique.
var DockerNamespace = "docker"
// Basepath to all container specific information that libcontainer stores.
// TODO: Deprecate this flag
var dockerRootDir = flag.String("docker_root", "/var/lib/docker", "Absolute path to the Docker state root directory (default: /var/lib/docker)")
var dockerRunDir = flag.String("docker_run", "/var/run/docker", "Absolute path to the Docker run directory (default: /var/run/docker)")
const DockerNamespace = "docker"
// Regexp that identifies docker cgroups, containers started with
// --cgroup-parent have another prefix than 'docker'
@@ -47,24 +46,30 @@ var dockerCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`)
var dockerEnvWhitelist = flag.String("docker_env_metadata_whitelist", "", "a comma-separated list of environment variable keys that needs to be collected for docker containers")
// TODO(vmarmol): Export run dir too for newer Dockers.
// Directory holding Docker container state information.
func DockerStateDir() string {
return libcontainer.DockerStateDir(*dockerRootDir)
}
var (
// Basepath to all container specific information that libcontainer stores.
dockerRootDir string
const (
dockerRootDirKey = "Root Dir"
dockerRootDirFlag = flag.String("docker_root", "/var/lib/docker", "DEPRECATED: docker root is read from docker info (this is a fallback, default: /var/lib/docker)")
dockerRootDirOnce sync.Once
)
func RootDir() string {
return *dockerRootDir
dockerRootDirOnce.Do(func() {
status, err := Status()
if err == nil && status.RootDir != "" {
dockerRootDir = status.RootDir
} else {
dockerRootDir = *dockerRootDirFlag
}
})
return dockerRootDir
}
type storageDriver string
const (
// TODO: Add support for devicemapper storage usage.
devicemapperStorageDriver storageDriver = "devicemapper"
aufsStorageDriver storageDriver = "aufs"
overlayStorageDriver storageDriver = "overlay"
@@ -88,6 +93,8 @@ type dockerFactory struct {
dockerVersion []int
ignoreMetrics container.MetricSet
thinPoolWatcher *devicemapper.ThinPoolWatcher
}
func (self *dockerFactory) String() string {
@@ -114,6 +121,7 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
metadataEnvs,
self.dockerVersion,
self.ignoreMetrics,
self.thinPoolWatcher,
)
return
}
@@ -146,7 +154,7 @@ func (self *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) {
id := ContainerNameToDockerId(name)
// We assume that if Inspect fails then the container is not known to docker.
ctnr, err := self.client.InspectContainer(id)
ctnr, err := self.client.ContainerInspect(context.Background(), id)
if err != nil || !ctnr.State.Running {
return false, canAccept, fmt.Errorf("error inspecting container: %v", err)
}
@@ -163,24 +171,6 @@ var (
version_re = regexp.MustCompile(version_regexp_string)
)
// TODO: switch to a semantic versioning library.
func parseDockerVersion(full_version_string string) ([]int, error) {
matches := version_re.FindAllStringSubmatch(full_version_string, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string)
}
version_string_array := matches[0][1:]
version_array := make([]int, 3)
for index, version_string := range version_string_array {
version, err := strconv.Atoi(version_string)
if err != nil {
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_string, full_version_string)
}
version_array[index] = version
}
return version_array, nil
}
// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
client, err := Client()
@@ -196,16 +186,35 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c
// Version already validated above, assume no error here.
dockerVersion, _ := parseDockerVersion(dockerInfo.ServerVersion)
storageDir := dockerInfo.DockerRootDir
if storageDir == "" {
storageDir = *dockerRootDir
}
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
glog.Infof("Registering Docker factory")
var (
dockerStorageDriver = storageDriver(dockerInfo.Driver)
thinPoolWatcher *devicemapper.ThinPoolWatcher = nil
)
if dockerStorageDriver == devicemapperStorageDriver {
// If the storage drive is devicemapper, create and start a
// ThinPoolWatcher to monitor the size of container CoW layers with
// thin_ls.
dockerThinPoolName, err := dockerutil.DockerThinPoolName(*dockerInfo)
if err != nil {
return fmt.Errorf("couldn't find device mapper thin pool name: %v", err)
}
dockerMetadataDevice, err := dockerutil.DockerMetadataDevice(*dockerInfo)
if err != nil {
return fmt.Errorf("couldn't determine devicemapper metadata device")
}
thinPoolWatcher = devicemapper.NewThinPoolWatcher(dockerThinPoolName, dockerMetadataDevice)
go thinPoolWatcher.Start()
}
glog.Infof("registering Docker factory")
f := &dockerFactory{
cgroupSubsystems: cgroupSubsystems,
client: client,
@@ -213,10 +222,11 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(dockerInfo.Driver),
storageDir: storageDir,
storageDir: RootDir(),
ignoreMetrics: ignoreMetrics,
thinPoolWatcher: thinPoolWatcher,
}
container.RegisterContainerHandlerFactory(f)
container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}

View File

@@ -25,13 +25,18 @@ import (
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/devicemapper"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
dockerutil "github.com/google/cadvisor/utils/docker"
docker "github.com/fsouza/go-dockerclient"
docker "github.com/docker/engine-api/client"
dockercontainer "github.com/docker/engine-api/types/container"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/net/context"
)
const (
@@ -55,10 +60,18 @@ type dockerContainerHandler struct {
// Manager of this container's cgroups.
cgroupManager cgroups.Manager
// the docker storage driver
storageDriver storageDriver
fsInfo fs.FsInfo
rootfsStorageDir string
// devicemapper state
// the devicemapper poolname
poolName string
// the devicemapper device id for the container
deviceID string
// Time at which this container was created.
creationTime time.Time
@@ -76,14 +89,19 @@ type dockerContainerHandler struct {
rootFs string
// The network mode of the container
networkMode string
networkMode dockercontainer.NetworkMode
// Filesystem handler.
fsHandler common.FsHandler
ignoreMetrics container.MetricSet
// thin pool watcher
thinPoolWatcher *devicemapper.ThinPoolWatcher
}
var _ container.ContainerHandler = &dockerContainerHandler{}
func getRwLayerID(containerID, storageDir string, sd storageDriver, dockerVersion []int) (string, error) {
const (
// Docker version >=1.10.0 have a randomized ID for the root fs of a container.
@@ -101,6 +119,7 @@ func getRwLayerID(containerID, storageDir string, sd storageDriver, dockerVersio
return string(bytes), err
}
// newDockerContainerHandler returns a new container.ContainerHandler
func newDockerContainerHandler(
client *docker.Client,
name string,
@@ -113,6 +132,7 @@ func newDockerContainerHandler(
metadataEnvs []string,
dockerVersion []int,
ignoreMetrics container.MetricSet,
thinPoolWatcher *devicemapper.ThinPoolWatcher,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
@@ -144,14 +164,27 @@ func newDockerContainerHandler(
if err != nil {
return nil, err
}
var rootfsStorageDir string
// Determine the rootfs storage dir OR the pool name to determine the device
var (
rootfsStorageDir string
poolName string
)
switch storageDriver {
case aufsStorageDriver:
rootfsStorageDir = path.Join(storageDir, string(aufsStorageDriver), aufsRWLayer, rwLayerID)
case overlayStorageDriver:
rootfsStorageDir = path.Join(storageDir, string(overlayStorageDriver), rwLayerID)
case devicemapperStorageDriver:
status, err := Status()
if err != nil {
return nil, fmt.Errorf("unable to determine docker status: %v", err)
}
poolName = status.DriverStatus[dockerutil.DriverStatusPoolName]
}
// TODO: extract object mother method
handler := &dockerContainerHandler{
id: id,
client: client,
@@ -162,21 +195,24 @@ func newDockerContainerHandler(
storageDriver: storageDriver,
fsInfo: fsInfo,
rootFs: rootFs,
poolName: poolName,
rootfsStorageDir: rootfsStorageDir,
envs: make(map[string]string),
ignoreMetrics: ignoreMetrics,
}
if !ignoreMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = common.NewFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo)
thinPoolWatcher: thinPoolWatcher,
}
// We assume that if Inspect fails then the container is not known to docker.
ctnr, err := client.InspectContainer(id)
ctnr, err := client.ContainerInspect(context.Background(), id)
if err != nil {
return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
}
handler.creationTime = ctnr.Created
// Timestamp returned by Docker is in time.RFC3339Nano format.
handler.creationTime, err = time.Parse(time.RFC3339Nano, ctnr.Created)
if err != nil {
// This should not happen, report the error just in case
return nil, fmt.Errorf("failed to parse the create timestamp %q for container %q: %v", ctnr.Created, id, err)
}
handler.pid = ctnr.State.Pid
// Add the name and bare ID as aliases of the container.
@@ -184,6 +220,15 @@ func newDockerContainerHandler(
handler.labels = ctnr.Config.Labels
handler.image = ctnr.Config.Image
handler.networkMode = ctnr.HostConfig.NetworkMode
handler.deviceID = ctnr.GraphDriver.Data["DeviceId"]
if !ignoreMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = &dockerFsHandler{
fsHandler: common.NewFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo),
thinPoolWatcher: thinPoolWatcher,
deviceID: handler.deviceID,
}
}
// split env vars to get metadata map.
for _, exposedEnv := range metadataEnvs {
@@ -198,6 +243,48 @@ func newDockerContainerHandler(
return handler, nil
}
// dockerFsHandler is a composite FsHandler implementation the incorporates
// the common fs handler and a devicemapper ThinPoolWatcher.
type dockerFsHandler struct {
fsHandler common.FsHandler
// thinPoolWatcher is the devicemapper thin pool watcher
thinPoolWatcher *devicemapper.ThinPoolWatcher
// deviceID is the id of the container's fs device
deviceID string
}
var _ common.FsHandler = &dockerFsHandler{}
func (h *dockerFsHandler) Start() {
h.fsHandler.Start()
}
func (h *dockerFsHandler) Stop() {
h.fsHandler.Stop()
}
func (h *dockerFsHandler) Usage() (uint64, uint64) {
baseUsage, usage := h.fsHandler.Usage()
// When devicemapper is the storage driver, the base usage of the container comes from the thin pool.
// We still need the result of the fsHandler for any extra storage associated with the container.
// To correctly factor in the thin pool usage, we should:
// * Usage the thin pool usage as the base usage
// * Calculate the overall usage by adding the overall usage from the fs handler to the thin pool usage
if h.thinPoolWatcher != nil {
thinPoolUsage, err := h.thinPoolWatcher.GetUsage(h.deviceID)
if err != nil {
glog.Errorf("unable to get fs usage from thin pool for device %v: %v", h.deviceID, err)
} else {
baseUsage = thinPoolUsage
usage += thinPoolUsage
}
}
return baseUsage, usage
}
func (self *dockerContainerHandler) Start() {
if self.fsHandler != nil {
self.fsHandler.Start()
@@ -222,7 +309,7 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc
func (self *dockerContainerHandler) needNet() bool {
if !self.ignoreMetrics.Has(container.NetworkUsageMetrics) {
return !strings.HasPrefix(self.networkMode, "container:")
return !self.networkMode.IsContainer()
}
return false
}
@@ -242,17 +329,22 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error
if self.ignoreMetrics.Has(container.DiskUsageMetrics) {
return nil
}
var device string
switch self.storageDriver {
case devicemapperStorageDriver:
// Device has to be the pool name to correlate with the device name as
// set in the machine info filesystems.
device = self.poolName
case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir)
if err != nil {
return fmt.Errorf("unable to determine device info for dir: %v: %v", self.rootfsStorageDir, err)
}
device = deviceInfo.Device
default:
return nil
}
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir)
if err != nil {
return err
}
mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil {
return err
@@ -265,16 +357,16 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error
// Docker does not impose any filesystem limits for containers. So use capacity as limit.
for _, fs := range mi.Filesystems {
if fs.Device == deviceInfo.Device {
if fs.Device == device {
limit = fs.Capacity
fsType = fs.Type
break
}
}
fsStat := info.FsStats{Device: deviceInfo.Device, Type: fsType, Limit: limit}
fsStat := info.FsStats{Device: device, Type: fsType, Limit: limit}
fsStat.BaseUsage, fsStat.Usage = self.fsHandler.Usage()
stats.Filesystem = append(stats.Filesystem, fsStat)
return nil
@@ -316,11 +408,6 @@ func (self *dockerContainerHandler) GetCgroupPath(resource string) (string, erro
return path, nil
}
func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
// TODO(vmarmol): Implement.
return nil, nil
}
func (self *dockerContainerHandler) GetContainerLabels() map[string]string {
return self.labels
}
@@ -329,83 +416,10 @@ func (self *dockerContainerHandler) ListProcesses(listType container.ListType) (
return containerlibcontainer.GetProcesses(self.cgroupManager)
}
func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
return fmt.Errorf("watch is unimplemented in the Docker container driver")
}
func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
// No-op for Docker driver.
return nil
}
func (self *dockerContainerHandler) Exists() bool {
return common.CgroupExists(self.cgroupPaths)
}
func DockerInfo() (docker.DockerInfo, error) {
client, err := Client()
if err != nil {
return docker.DockerInfo{}, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
info, err := client.Info()
if err != nil {
return docker.DockerInfo{}, err
}
return *info, nil
}
func DockerImages() ([]docker.APIImages, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
images, err := client.ListImages(docker.ListImagesOptions{All: false})
if err != nil {
return nil, err
}
return images, nil
}
// Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an
// error otherwise.
func ValidateInfo() (*docker.DockerInfo, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info()
if err != nil {
return nil, fmt.Errorf("failed to detect Docker info: %v", err)
}
// Fall back to version API if ServerVersion is not set in info.
if dockerInfo.ServerVersion == "" {
version, err := client.Version()
if err != nil {
return nil, fmt.Errorf("unable to get docker version: %v", err)
}
dockerInfo.ServerVersion = version.Get("Version")
}
version, err := parseDockerVersion(dockerInfo.ServerVersion)
if err != nil {
return nil, err
}
if version[0] < 1 {
return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion)
}
// Check that the libcontainer execdriver is used if the version is < 1.11
// (execution drivers are no longer supported as of 1.11).
if version[0] <= 1 && version[1] <= 10 &&
!strings.HasPrefix(dockerInfo.ExecutionDriver, "native") {
return nil, fmt.Errorf("docker found, but not using native exec driver")
}
if dockerInfo.Driver == "" {
return nil, fmt.Errorf("failed to find docker storage driver")
}
return dockerInfo, nil
func (self *dockerContainerHandler) Type() container.ContainerType {
return container.ContainerTypeDocker
}

View File

@@ -18,6 +18,8 @@ import (
"fmt"
"sync"
"github.com/google/cadvisor/manager/watcher"
"github.com/golang/glog"
)
@@ -67,17 +69,19 @@ func (ms MetricSet) Add(mk MetricKind) {
// TODO(vmarmol): Consider not making this global.
// Global list of factories.
var (
factories []ContainerHandlerFactory
factories = map[watcher.ContainerWatchSource][]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) {
func RegisterContainerHandlerFactory(factory ContainerHandlerFactory, watchTypes []watcher.ContainerWatchSource) {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = append(factories, factory)
for _, watchType := range watchTypes {
factories[watchType] = append(factories[watchType], factory)
}
}
// Returns whether there are any container handler factories registered.
@@ -89,12 +93,12 @@ func HasFactories() bool {
}
// Create a new ContainerHandler for the specified container.
func NewContainerHandler(name string, inHostNamespace bool) (ContainerHandler, bool, error) {
func NewContainerHandler(name string, watchType watcher.ContainerWatchSource, inHostNamespace bool) (ContainerHandler, bool, error) {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Create the ContainerHandler with the first factory that supports it.
for _, factory := range factories {
for _, factory := range factories[watchType] {
canHandle, canAccept, err := factory.CanHandleAndAccept(name)
if err != nil {
glog.V(4).Infof("Error trying to work out if we can handle %s: %v", name, err)
@@ -120,7 +124,7 @@ func ClearContainerHandlerFactories() {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = make([]ContainerHandlerFactory, 0, 4)
factories = map[watcher.ContainerWatchSource][]ContainerHandlerFactory{}
}
func DebugInfo() map[string][]string {
@@ -129,9 +133,11 @@ func DebugInfo() map[string][]string {
// Get debug information for all factories.
out := make(map[string][]string)
for _, factory := range factories {
for k, v := range factory.DebugInfo() {
out[k] = v
for _, factoriesSlice := range factories {
for _, factory := range factoriesSlice {
for k, v := range factory.DebugInfo() {
out[k] = v
}
}
}
return out

View File

@@ -299,10 +299,6 @@ func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) {
return pids, nil
}
func DockerStateDir(dockerRoot string) string {
return path.Join(dockerRoot, "containers")
}
func DiskStatsCopy0(major, minor uint64) *info.PerDiskStats {
disk := info.PerDiskStats{
Major: major,

View File

@@ -72,26 +72,11 @@ func (self *MockContainerHandler) ListContainers(listType ListType) ([]info.Cont
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)
}
func (self *MockContainerHandler) WatchSubcontainers(events chan SubcontainerEvent) error {
args := self.Called(events)
return args.Error(0)
}
func (self *MockContainerHandler) StopWatchingSubcontainers() error {
args := self.Called()
return args.Error(0)
}
func (self *MockContainerHandler) Exists() bool {
args := self.Called()
return args.Get(0).(bool)
@@ -107,6 +92,11 @@ func (self *MockContainerHandler) GetContainerLabels() map[string]string {
return args.Get(0).(map[string]string)
}
func (self *MockContainerHandler) Type() ContainerType {
args := self.Called()
return args.Get(0).(ContainerType)
}
type FactoryForMockContainerHandler struct {
Name string
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)

View File

@@ -23,6 +23,7 @@ import (
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
watch "github.com/google/cadvisor/manager/watcher"
"github.com/golang/glog"
)
@@ -90,6 +91,6 @@ func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, igno
watcher: watcher,
ignoreMetrics: ignoreMetrics,
}
container.RegisterContainerHandlerFactory(factory)
container.RegisterContainerHandlerFactory(factory, []watch.ContainerWatchSource{watch.Raw})
return nil
}

View File

@@ -17,22 +17,18 @@ package raw
import (
"fmt"
"io/ioutil"
"path"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/machine"
"github.com/google/cadvisor/machine"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/exp/inotify"
)
type rawContainerHandler struct {
@@ -41,12 +37,6 @@ type rawContainerHandler struct {
cgroupSubsystems *libcontainer.CgroupSubsystems
machineInfoFactory info.MachineInfoFactory
// Inotify event watcher.
watcher *common.InotifyWatcher
// Signal for watcher thread to stop.
stopWatcher chan error
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
@@ -102,12 +92,10 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu
name: name,
cgroupSubsystems: cgroupSubsystems,
machineInfoFactory: machineInfoFactory,
stopWatcher: make(chan error),
cgroupPaths: cgroupPaths,
cgroupManager: cgroupManager,
fsInfo: fsInfo,
externalMounts: externalMounts,
watcher: watcher,
rootFs: rootFs,
ignoreMetrics: ignoreMetrics,
pid: pid,
@@ -269,179 +257,17 @@ func (self *rawContainerHandler) GetContainerLabels() map[string]string {
}
func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
containers := make(map[string]struct{})
for _, cgroupPath := range self.cgroupPaths {
err := common.ListDirectories(cgroupPath, self.name, listType == container.ListRecursive, 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
return common.ListContainers(self.name, self.cgroupPaths, listType)
}
func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return libcontainer.GetProcesses(self.cgroupManager)
}
// Watches the specified directory and all subdirectories. Returns whether the path was
// already being watched and an error (if any).
func (self *rawContainerHandler) watchDirectory(dir string, containerName string) (bool, error) {
alreadyWatching, err := self.watcher.AddWatch(containerName, dir)
if err != nil {
return alreadyWatching, err
}
// Remove the watch if further operations failed.
cleanup := true
defer func() {
if cleanup {
_, err := self.watcher.RemoveWatch(containerName, dir)
if err != nil {
glog.Warningf("Failed to remove inotify watch for %q: %v", dir, err)
}
}
}()
// TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime.
// Watch subdirectories as well.
entries, err := ioutil.ReadDir(dir)
if err != nil {
return alreadyWatching, err
}
for _, entry := range entries {
if entry.IsDir() {
// TODO(vmarmol): We don't have to fail here, maybe we can recover and try to get as many registrations as we can.
_, err = self.watchDirectory(path.Join(dir, entry.Name()), path.Join(containerName, entry.Name()))
if err != nil {
return alreadyWatching, err
}
}
}
cleanup = false
return alreadyWatching, nil
}
func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan container.SubcontainerEvent) error {
// Convert the inotify event type to a container create or delete.
var eventType container.SubcontainerEventType
switch {
case (event.Mask & inotify.IN_CREATE) > 0:
eventType = container.SubcontainerAdd
case (event.Mask & inotify.IN_DELETE) > 0:
eventType = container.SubcontainerDelete
case (event.Mask & inotify.IN_MOVED_FROM) > 0:
eventType = container.SubcontainerDelete
case (event.Mask & inotify.IN_MOVED_TO) > 0:
eventType = container.SubcontainerAdd
default:
// Ignore other events.
return nil
}
// Derive the container name from the path name.
var containerName string
for _, mount := range self.cgroupSubsystems.Mounts {
mountLocation := path.Clean(mount.Mountpoint) + "/"
if strings.HasPrefix(event.Name, mountLocation) {
containerName = event.Name[len(mountLocation)-1:]
break
}
}
if containerName == "" {
return fmt.Errorf("unable to detect container from watch event on directory %q", event.Name)
}
// Maintain the watch for the new or deleted container.
switch {
case eventType == container.SubcontainerAdd:
// New container was created, watch it.
alreadyWatched, err := self.watchDirectory(event.Name, containerName)
if err != nil {
return err
}
// Only report container creation once.
if alreadyWatched {
return nil
}
case eventType == container.SubcontainerDelete:
// Container was deleted, stop watching for it.
lastWatched, err := self.watcher.RemoveWatch(containerName, event.Name)
if err != nil {
return err
}
// Only report container deletion once.
if !lastWatched {
return nil
}
default:
return fmt.Errorf("unknown event type %v", eventType)
}
// Deliver the event.
events <- container.SubcontainerEvent{
EventType: eventType,
Name: containerName,
}
return nil
}
func (self *rawContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
// Watch this container (all its cgroups) and all subdirectories.
for _, cgroupPath := range self.cgroupPaths {
_, err := self.watchDirectory(cgroupPath, self.name)
if err != nil {
return err
}
}
// Process the events received from the kernel.
go func() {
for {
select {
case event := <-self.watcher.Event():
err := self.processEvent(event, events)
if err != nil {
glog.Warningf("Error while processing event (%+v): %v", event, err)
}
case err := <-self.watcher.Error():
glog.Warningf("Error while watching %q:", self.name, err)
case <-self.stopWatcher:
err := self.watcher.Close()
if err == nil {
self.stopWatcher <- err
return
}
}
}
}()
return nil
}
func (self *rawContainerHandler) StopWatchingSubcontainers() error {
// Rendezvous with the watcher thread.
self.stopWatcher <- nil
return <-self.stopWatcher
}
func (self *rawContainerHandler) Exists() bool {
return common.CgroupExists(self.cgroupPaths)
}
func (self *rawContainerHandler) Type() container.ContainerType {
return container.ContainerTypeRaw
}

View File

@@ -20,6 +20,7 @@ import (
"sync"
"time"
"github.com/blang/semver"
rktapi "github.com/coreos/rkt/api/v1alpha"
"golang.org/x/net/context"
@@ -29,6 +30,7 @@ import (
const (
defaultRktAPIServiceAddr = "localhost:15441"
timeout = 2 * time.Second
minimumRktBinVersion = "1.6.0"
)
var (
@@ -55,7 +57,25 @@ func Client() (rktapi.PublicAPIClient, error) {
return
}
rktClient = rktapi.NewPublicAPIClient(apisvcConn)
apisvc := rktapi.NewPublicAPIClient(apisvcConn)
resp, err := apisvc.GetInfo(context.Background(), &rktapi.GetInfoRequest{})
if err != nil {
rktClientErr = fmt.Errorf("rkt: GetInfo() failed: %v", err)
return
}
binVersion, err := semver.Make(resp.Info.RktVersion)
if err != nil {
rktClientErr = fmt.Errorf("rkt: couldn't parse RtVersion: %v", err)
return
}
if binVersion.LT(semver.MustParse(minimumRktBinVersion)) {
rktClientErr = fmt.Errorf("rkt: binary version is too old(%v), requires at least %v", resp.Info.RktVersion, minimumRktBinVersion)
return
}
rktClient = apisvc
})
return rktClient, rktClientErr

View File

@@ -16,12 +16,12 @@ package rkt
import (
"fmt"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"
"github.com/golang/glog"
)
@@ -58,14 +58,9 @@ func (self *rktFactory) NewContainerHandler(name string, inHostNamespace bool) (
}
func (self *rktFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// will ignore all cgroup names that don't either correspond to the machine.slice that is the pod or the containers that belong to the pod
// only works for machined rkt pods at the moment
accept, err := verifyPod(name)
if strings.HasPrefix(name, "/machine.slice/machine-rkt\\x2d") {
accept, err := verifyName(name)
return true, accept, err
}
return false, false, fmt.Errorf("%s not handled by rkt handler", name)
return accept, accept, err
}
func (self *rktFactory) DebugInfo() map[string][]string {
@@ -99,6 +94,6 @@ func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, igno
ignoreMetrics: ignoreMetrics,
rktPath: rktPath,
}
container.RegisterContainerHandlerFactory(factory)
container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Rkt})
return nil
}

View File

@@ -18,7 +18,6 @@ package rkt
import (
"fmt"
"os"
"path"
"time"
rktapi "github.com/coreos/rkt/api/v1alpha"
@@ -100,20 +99,19 @@ func newRktContainerHandler(name string, rktClient rktapi.PublicAPIClient, rktPa
})
if err != nil {
return nil, err
} else {
var annotations []*rktapi.KeyValue
if parsed.Container == "" {
pid = int(resp.Pod.Pid)
apiPod = resp.Pod
annotations = resp.Pod.Annotations
} else {
var ok bool
if annotations, ok = findAnnotations(resp.Pod.Apps, parsed.Container); !ok {
glog.Warningf("couldn't find application in Pod matching %v", parsed.Container)
}
}
labels = createLabels(annotations)
}
annotations := resp.Pod.Annotations
if parsed.Container != "" { // As not empty string, an App container
if contAnnotations, ok := findAnnotations(resp.Pod.Apps, parsed.Container); !ok {
glog.Warningf("couldn't find app %v in pod", parsed.Container)
} else {
annotations = append(annotations, contAnnotations...)
}
} else { // The Pod container
pid = int(resp.Pod.Pid)
apiPod = resp.Pod
}
labels = createLabels(annotations)
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
@@ -196,7 +194,12 @@ func (handler *rktContainerHandler) Cleanup() {
func (handler *rktContainerHandler) GetSpec() (info.ContainerSpec, error) {
hasNetwork := handler.hasNetwork && !handler.ignoreMetrics.Has(container.NetworkUsageMetrics)
hasFilesystem := !handler.ignoreMetrics.Has(container.DiskUsageMetrics)
return common.GetSpec(handler.cgroupPaths, handler.machineInfoFactory, hasNetwork, hasFilesystem)
spec, err := common.GetSpec(handler.cgroupPaths, handler.machineInfoFactory, hasNetwork, hasFilesystem)
spec.Labels = handler.labels
return spec, err
}
func (handler *rktContainerHandler) getFsStats(stats *info.ContainerStats) error {
@@ -260,68 +263,17 @@ func (handler *rktContainerHandler) GetContainerLabels() map[string]string {
}
func (handler *rktContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
containers := make(map[string]struct{})
// Rkt containers do not have subcontainers, only the "Pod" does.
if handler.isPod == false {
var ret []info.ContainerReference
return ret, nil
}
// Turn the system.slice cgroups into the Pod's subcontainers
for _, cgroupPath := range handler.cgroupPaths {
err := common.ListDirectories(path.Join(cgroupPath, "system.slice"), path.Join(handler.name, "system.slice"), listType == container.ListRecursive, containers)
if err != nil {
return nil, err
}
}
// Create the container references. for the Pod's subcontainers
ret := make([]info.ContainerReference, 0, len(handler.apiPod.Apps))
for cont := range containers {
aliases := make([]string, 1)
parsed, err := parseName(cont)
if err != nil {
return nil, fmt.Errorf("this should be impossible!, unable to parse rkt subcontainer name = %s", cont)
}
aliases = append(aliases, parsed.Pod+":"+parsed.Container)
labels := make(map[string]string)
if annotations, ok := findAnnotations(handler.apiPod.Apps, parsed.Container); !ok {
glog.Warningf("couldn't find application in Pod matching %v", parsed.Container)
} else {
labels = createLabels(annotations)
}
ret = append(ret, info.ContainerReference{
Name: cont,
Aliases: aliases,
Namespace: RktNamespace,
Labels: labels,
})
}
return ret, nil
}
func (handler *rktContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
// TODO(sjpotter): Implement? Not implemented with docker yet
return nil, nil
return common.ListContainers(handler.name, handler.cgroupPaths, listType)
}
func (handler *rktContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return libcontainer.GetProcesses(handler.cgroupManager)
}
func (handler *rktContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
return fmt.Errorf("watch is unimplemented in the Rkt container driver")
}
func (handler *rktContainerHandler) StopWatchingSubcontainers() error {
// No-op for Rkt driver.
return nil
}
func (handler *rktContainerHandler) Exists() bool {
return common.CgroupExists(handler.cgroupPaths)
}
func (handler *rktContainerHandler) Type() container.ContainerType {
return container.ContainerTypeRkt
}

View File

@@ -20,7 +20,9 @@ import (
"path"
"strings"
rktapi "github.com/coreos/rkt/api/v1alpha"
"github.com/golang/glog"
"golang.org/x/net/context"
)
type parsedName struct {
@@ -28,34 +30,80 @@ type parsedName struct {
Container string
}
func verifyName(name string) (bool, error) {
_, err := parseName(name)
return err == nil, err
func verifyPod(name string) (bool, error) {
pod, err := cgroupToPod(name)
if err != nil || pod == nil {
return false, err
}
// Anything handler can handle is also accepted.
// Accept cgroups that are sub the pod cgroup, except "system.slice"
// - "system.slice" doesn't contain any processes itself
accept := !strings.HasSuffix(name, "/system.slice")
return accept, nil
}
func cgroupToPod(name string) (*rktapi.Pod, error) {
rktClient, err := Client()
if err != nil {
return nil, fmt.Errorf("couldn't get rkt api service: %v", err)
}
resp, err := rktClient.ListPods(context.Background(), &rktapi.ListPodsRequest{
Filters: []*rktapi.PodFilter{
{
States: []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING},
PodSubCgroups: []string{name},
},
},
})
if err != nil {
return nil, fmt.Errorf("failed to list pods: %v", err)
}
if len(resp.Pods) == 0 {
return nil, nil
}
if len(resp.Pods) != 1 {
return nil, fmt.Errorf("returned %d (expected 1) pods for cgroup %v", len(resp.Pods), name)
}
return resp.Pods[0], nil
}
/* Parse cgroup name into a pod/container name struct
Example cgroup fs name
pod - /sys/fs/cgroup/cpu/machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/
container under pod - /sys/fs/cgroup/cpu/machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/system.slice/alpine-sh.service
pod - /machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/
or /system.slice/k8s-..../
container under pod - /machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/system.slice/alpine-sh.service
or /system.slice/k8s-..../system.slice/pause.service
*/
//TODO{sjpotter}: this currently only recognizes machined started pods, which actually doesn't help with k8s which uses them as systemd services, need a solution for both
func parseName(name string) (*parsedName, error) {
splits := strings.Split(name, "/")
if len(splits) == 3 || len(splits) == 5 {
parsed := &parsedName{}
pod, err := cgroupToPod(name)
if err != nil {
return nil, fmt.Errorf("parseName: couldn't convert %v to a rkt pod: %v", name, err)
}
if pod == nil {
return nil, fmt.Errorf("parseName: didn't return a pod for %v", name)
}
if splits[1] == "machine.slice" {
replacer := strings.NewReplacer("machine-rkt\\x2d", "", ".scope", "", "\\x2d", "-")
parsed.Pod = replacer.Replace(splits[2])
if len(splits) == 3 {
return parsed, nil
}
if splits[3] == "system.slice" {
parsed.Container = strings.Replace(splits[4], ".service", "", -1)
return parsed, nil
}
splits := strings.Split(name, "/")
parsed := &parsedName{}
if len(splits) == 3 || len(splits) == 5 {
parsed.Pod = pod.Id
if len(splits) == 5 {
parsed.Container = strings.Replace(splits[4], ".service", "", -1)
}
return parsed, nil
}
return nil, fmt.Errorf("%s not handled by rkt handler", name)
@@ -80,7 +128,7 @@ func getRootFs(root string, parsed *parsedName) string {
bytes, err := ioutil.ReadFile(tree)
if err != nil {
glog.Infof("ReadFile failed, couldn't read %v to get upper dir: %v", tree, err)
glog.Errorf("ReadFile failed, couldn't read %v to get upper dir: %v", tree, err)
return ""
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"
"github.com/golang/glog"
)
@@ -52,6 +53,6 @@ func (f *systemdFactory) DebugInfo() map[string][]string {
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
glog.Infof("Registering systemd factory")
factory := &systemdFactory{}
container.RegisterContainerHandlerFactory(factory)
container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}

View File

@@ -0,0 +1,56 @@
// Copyright 2016 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 devicemapper
import (
"os/exec"
"strconv"
"strings"
"github.com/golang/glog"
)
// DmsetupClient is a low-level client for interacting with devicemapper via
// the dmsetup utility.
type DmsetupClient interface {
Table(deviceName string) ([]byte, error)
Message(deviceName string, sector int, message string) ([]byte, error)
Status(deviceName string) ([]byte, error)
}
func NewDmsetupClient() DmsetupClient {
return &defaultDmsetupClient{}
}
// defaultDmsetupClient implements the standard behavior for interacting with dmsetup.
type defaultDmsetupClient struct{}
var _ DmsetupClient = &defaultDmsetupClient{}
func (c *defaultDmsetupClient) Table(deviceName string) ([]byte, error) {
return c.dmsetup("table", deviceName)
}
func (c *defaultDmsetupClient) Message(deviceName string, sector int, message string) ([]byte, error) {
return c.dmsetup("message", deviceName, strconv.Itoa(sector), message)
}
func (c *defaultDmsetupClient) Status(deviceName string) ([]byte, error) {
return c.dmsetup("status", deviceName)
}
func (*defaultDmsetupClient) dmsetup(args ...string) ([]byte, error) {
glog.V(5).Infof("running dmsetup %v", strings.Join(args, " "))
return exec.Command("dmsetup", args...).Output()
}

16
vendor/github.com/google/cadvisor/devicemapper/doc.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Copyright 2016 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 devicemapper contains code for working with devicemapper
package devicemapper

View File

@@ -0,0 +1,77 @@
// Copyright 2016 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 devicemapper
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/golang/glog"
)
// thinLsClient knows how to run a thin_ls very specific to CoW usage for containers.
type thinLsClient interface {
ThinLs(deviceName string) (map[string]uint64, error)
}
func newThinLsClient() thinLsClient {
return &defaultThinLsClient{}
}
type defaultThinLsClient struct{}
var _ thinLsClient = &defaultThinLsClient{}
func (*defaultThinLsClient) ThinLs(deviceName string) (map[string]uint64, error) {
args := []string{"--no-headers", "-m", "-o", "DEV,EXCLUSIVE_BYTES", deviceName}
glog.V(4).Infof("running command: thin_ls %v", strings.Join(args, " "))
output, err := exec.Command("thin_ls", args...).Output()
if err != nil {
return nil, fmt.Errorf("Error running command `thin_ls %v`: %v\noutput:\n\n%v", strings.Join(args, " "), err, string(output))
}
return parseThinLsOutput(output), nil
}
// parseThinLsOutput parses the output returned by thin_ls to build a map of device id -> usage.
func parseThinLsOutput(output []byte) map[string]uint64 {
cache := map[string]uint64{}
// parse output
scanner := bufio.NewScanner(bytes.NewReader(output))
for scanner.Scan() {
output := scanner.Text()
fields := strings.Fields(output)
if len(fields) != 2 {
continue
}
deviceID := fields[0]
usage, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
glog.Warning("unexpected error parsing thin_ls output: %v", err)
continue
}
cache[deviceID] = usage
}
return cache
}

View File

@@ -0,0 +1,164 @@
// Copyright 2016 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 devicemapper
import (
"fmt"
"strings"
"sync"
"time"
"github.com/golang/glog"
)
// ThinPoolWatcher maintains a cache of device name -> usage stats for a devicemapper thin-pool using thin_ls.
type ThinPoolWatcher struct {
poolName string
metadataDevice string
lock *sync.RWMutex
cache map[string]uint64
period time.Duration
stopChan chan struct{}
dmsetup DmsetupClient
thinLsClient thinLsClient
}
// NewThinPoolWatcher returns a new ThinPoolWatcher for the given devicemapper thin pool name and metadata device.
func NewThinPoolWatcher(poolName, metadataDevice string) *ThinPoolWatcher {
return &ThinPoolWatcher{poolName: poolName,
metadataDevice: metadataDevice,
lock: &sync.RWMutex{},
cache: make(map[string]uint64),
period: 15 * time.Second,
stopChan: make(chan struct{}),
dmsetup: NewDmsetupClient(),
thinLsClient: newThinLsClient(),
}
}
// Start starts the thin pool watcher.
func (w *ThinPoolWatcher) Start() {
err := w.Refresh()
if err != nil {
glog.Errorf("encountered error refreshing thin pool watcher: %v", err)
}
for {
select {
case <-w.stopChan:
return
case <-time.After(w.period):
start := time.Now()
err = w.Refresh()
if err != nil {
glog.Errorf("encountered error refreshing thin pool watcher: %v", err)
}
// print latency for refresh
duration := time.Since(start)
glog.V(3).Infof("thin_ls(%d) took %s", start.Unix(), duration)
}
}
}
func (w *ThinPoolWatcher) Stop() {
close(w.stopChan)
}
// GetUsage gets the cached usage value of the given device.
func (w *ThinPoolWatcher) GetUsage(deviceId string) (uint64, error) {
w.lock.RLock()
defer w.lock.RUnlock()
v, ok := w.cache[deviceId]
if !ok {
return 0, fmt.Errorf("no cached value for usage of device %v", deviceId)
}
return v, nil
}
const (
reserveMetadataMessage = "reserve_metadata_snap"
releaseMetadataMessage = "release_metadata_snap"
)
// Refresh performs a `thin_ls` of the pool being watched and refreshes the
// cached data with the result.
func (w *ThinPoolWatcher) Refresh() error {
w.lock.Lock()
defer w.lock.Unlock()
currentlyReserved, err := w.checkReservation(w.poolName)
if err != nil {
err = fmt.Errorf("error determining whether snapshot is reserved: %v", err)
return err
}
if currentlyReserved {
glog.V(4).Infof("metadata for %v is currently reserved; releasing", w.poolName)
_, err = w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage)
if err != nil {
err = fmt.Errorf("error releasing metadata snapshot for %v: %v", w.poolName, err)
return err
}
}
glog.Infof("reserving metadata snapshot for thin-pool %v", w.poolName)
// NOTE: "0" in the call below is for the 'sector' argument to 'dmsetup message'. It's not needed for thin pools.
if output, err := w.dmsetup.Message(w.poolName, 0, reserveMetadataMessage); err != nil {
err = fmt.Errorf("error reserving metadata for thin-pool %v: %v output: %v", w.poolName, err, string(output))
return err
} else {
glog.V(5).Infof("reserved metadata snapshot for thin-pool %v", w.poolName)
}
defer func() {
glog.V(5).Infof("releasing metadata snapshot for thin-pool %v", w.poolName)
w.dmsetup.Message(w.poolName, 0, releaseMetadataMessage)
}()
glog.V(5).Infof("running thin_ls on metadata device %v", w.metadataDevice)
newCache, err := w.thinLsClient.ThinLs(w.metadataDevice)
if err != nil {
err = fmt.Errorf("error performing thin_ls on metadata device %v: %v", w.metadataDevice, err)
return err
}
w.cache = newCache
return nil
}
const (
thinPoolDmsetupStatusTokens = 11
thinPoolDmsetupStatusHeldMetadataRoot = 6
)
// checkReservation checks to see whether the thin device is currently holding userspace metadata.
func (w *ThinPoolWatcher) checkReservation(poolName string) (bool, error) {
glog.V(5).Infof("checking whether the thin-pool is holding a metadata snapshot")
output, err := w.dmsetup.Status(poolName)
if err != nil {
return false, err
}
tokens := strings.Split(string(output), " ")
// Split returns the input as the last item in the result, adjust the number of tokens by one
if len(tokens) != thinPoolDmsetupStatusTokens+1 {
return false, fmt.Errorf("unexpected output of dmsetup status command; expected 11 fields, got %v; output: %v", len(tokens), string(output))
}
heldMetadataRoot := tokens[thinPoolDmsetupStatusHeldMetadataRoot]
currentlyReserved := heldMetadataRoot != "-"
return currentlyReserved, nil
}

View File

@@ -281,14 +281,19 @@ func (self *events) updateEventStore(e *info.Event) {
self.eventsLock.Lock()
defer self.eventsLock.Unlock()
if _, ok := self.eventStore[e.EventType]; !ok {
maxAge := self.storagePolicy.DefaultMaxAge
maxNumEvents := self.storagePolicy.DefaultMaxNumEvents
if age, ok := self.storagePolicy.PerTypeMaxAge[e.EventType]; ok {
maxAge = age
}
if numEvents, ok := self.storagePolicy.PerTypeMaxNumEvents[e.EventType]; ok {
maxNumEvents = numEvents
}
if maxNumEvents == 0 {
// Event storage is disabled for e.EventType
return
}
maxAge := self.storagePolicy.DefaultMaxAge
if age, ok := self.storagePolicy.PerTypeMaxAge[e.EventType]; ok {
maxAge = age
}
self.eventStore[e.EventType] = utils.NewTimedStore(maxAge, maxNumEvents)
}

View File

@@ -33,6 +33,8 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/golang/glog"
"github.com/google/cadvisor/devicemapper"
dockerutil "github.com/google/cadvisor/utils/docker"
zfs "github.com/mistifyio/go-zfs"
)
@@ -56,8 +58,8 @@ type RealFsInfo struct {
// Map from label to block device path.
// Labels are intent-specific tags that are auto-detected.
labels map[string]string
dmsetup dmsetupClient
// devicemapper client
dmsetup devicemapper.DmsetupClient
}
type Context struct {
@@ -80,13 +82,9 @@ func NewFsInfo(context Context) (FsInfo, error) {
fsInfo := &RealFsInfo{
partitions: make(map[string]partition, 0),
labels: make(map[string]string, 0),
dmsetup: &defaultDmsetupClient{},
dmsetup: devicemapper.NewDmsetupClient(),
}
fsInfo.addSystemRootLabel(mounts)
fsInfo.addDockerImagesLabel(context, mounts)
fsInfo.addRktImagesLabel(context, mounts)
supportedFsType := map[string]bool{
// all ext systems are checked through prefix.
"btrfs": true,
@@ -113,7 +111,13 @@ func NewFsInfo(context Context) (FsInfo, error) {
}
}
fsInfo.addRktImagesLabel(context, mounts)
// need to call this before the log line below printing out the partitions, as this function may
// add a "partition" for devicemapper to fsInfo.partitions
fsInfo.addDockerImagesLabel(context, mounts)
glog.Infof("Filesystem partitions: %+v", fsInfo.partitions)
fsInfo.addSystemRootLabel(mounts)
return fsInfo, nil
}
@@ -126,7 +130,7 @@ func (self *RealFsInfo) getDockerDeviceMapperInfo(context DockerContext) (string
return "", nil, nil
}
dataLoopFile := context.DriverStatus["Data loop file"]
dataLoopFile := context.DriverStatus[dockerutil.DriverStatusDataLoopFile]
if len(dataLoopFile) > 0 {
return "", nil, nil
}
@@ -274,6 +278,7 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er
switch partition.fsType {
case DeviceMapper.String():
fs.Capacity, fs.Free, fs.Available, err = getDMStats(device, partition.blockSize)
glog.V(5).Infof("got devicemapper fs capacity stats: capacity: %v free: %v available: %v:", fs.Capacity, fs.Free, fs.Available)
fs.Type = DeviceMapper
case ZFS.String():
fs.Capacity, fs.Free, fs.Available, err = getZfstats(device)
@@ -434,30 +439,15 @@ func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes u
return total, free, avail, inodes, inodesFree, nil
}
// dmsetupClient knows to to interact with dmsetup to retrieve information about devicemapper.
type dmsetupClient interface {
table(poolName string) ([]byte, error)
//TODO add status(poolName string) ([]byte, error) and use it in getDMStats so we can unit test
}
// defaultDmsetupClient implements the standard behavior for interacting with dmsetup.
type defaultDmsetupClient struct{}
var _ dmsetupClient = &defaultDmsetupClient{}
func (*defaultDmsetupClient) table(poolName string) ([]byte, error) {
return exec.Command("dmsetup", "table", poolName).Output()
}
// Devicemapper thin provisioning is detailed at
// https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt
func dockerDMDevice(driverStatus map[string]string, dmsetup dmsetupClient) (string, uint, uint, uint, error) {
poolName, ok := driverStatus["Pool Name"]
func dockerDMDevice(driverStatus map[string]string, dmsetup devicemapper.DmsetupClient) (string, uint, uint, uint, error) {
poolName, ok := driverStatus[dockerutil.DriverStatusPoolName]
if !ok || len(poolName) == 0 {
return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name")
}
out, err := dmsetup.table(poolName)
out, err := dmsetup.Table(poolName)
if err != nil {
return "", 0, 0, 0, err
}
@@ -470,6 +460,8 @@ func dockerDMDevice(driverStatus map[string]string, dmsetup dmsetupClient) (stri
return poolName, major, minor, dataBlkSize, nil
}
// parseDMTable parses a single line of `dmsetup table` output and returns the
// major device, minor device, block size, and an error.
func parseDMTable(dmTable string) (uint, uint, uint, error) {
dmTable = strings.Replace(dmTable, ":", " ", -1)
dmFields := strings.Fields(dmTable)

37
vendor/github.com/google/cadvisor/info/v1/docker.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2016 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.
// Types used for docker containers.
package v1
type DockerStatus struct {
Version string `json:"version"`
KernelVersion string `json:"kernel_version"`
OS string `json:"os"`
Hostname string `json:"hostname"`
RootDir string `json:"root_dir"`
Driver string `json:"driver"`
DriverStatus map[string]string `json:"driver_status"`
ExecDriver string `json:"exec_driver"`
NumImages int `json:"num_images"`
NumContainers int `json:"num_containers"`
}
type DockerImage struct {
ID string `json:"id"`
RepoTags []string `json:"repo_tags"` // repository name and tags.
Created int64 `json:"created"` // unix time since creation.
VirtualSize int64 `json:"virtual_size"`
Size int64 `json:"size"`
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package manager
package machine
import (
"bytes"
@@ -22,14 +22,11 @@ import (
"strings"
"syscall"
"github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/cloudinfo"
"github.com/google/cadvisor/utils/machine"
"github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/utils/sysinfo"
version "github.com/google/cadvisor/version"
"github.com/golang/glog"
)
@@ -51,19 +48,19 @@ func getInfoFromFiles(filePaths string) string {
return ""
}
func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.MachineInfo, error) {
func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.MachineInfo, error) {
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
cpuinfo, err := ioutil.ReadFile(filepath.Join(rootFs, "/proc/cpuinfo"))
clockSpeed, err := machine.GetClockSpeed(cpuinfo)
clockSpeed, err := GetClockSpeed(cpuinfo)
if err != nil {
return nil, err
}
memoryCapacity, err := machine.GetMachineMemoryCapacity()
memoryCapacity, err := GetMachineMemoryCapacity()
if err != nil {
return nil, err
}
@@ -83,7 +80,7 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (
glog.Errorf("Failed to get network devices: %v", err)
}
topology, numCores, err := machine.GetTopology(sysFs, string(cpuinfo))
topology, numCores, err := GetTopology(sysFs, string(cpuinfo))
if err != nil {
glog.Errorf("Failed to get topology information: %v", err)
}
@@ -120,22 +117,7 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (
return machineInfo, nil
}
func getVersionInfo() (*info.VersionInfo, error) {
kernel_version := getKernelVersion()
container_os := getContainerOsVersion()
docker_version := getDockerVersion()
return &info.VersionInfo{
KernelVersion: kernel_version,
ContainerOsVersion: container_os,
DockerVersion: docker_version,
CadvisorVersion: version.Info["version"],
CadvisorRevision: version.Info["revision"],
}, nil
}
func getContainerOsVersion() string {
func ContainerOsVersion() string {
container_os := "Unknown"
os_release, err := ioutil.ReadFile("/etc/os-release")
if err == nil {
@@ -152,19 +134,7 @@ func getContainerOsVersion() string {
return container_os
}
func getDockerVersion() string {
docker_version := "Unknown"
client, err := docker.Client()
if err == nil {
version, err := client.Version()
if err == nil {
docker_version = version.Get("Version")
}
}
return docker_version
}
func getKernelVersion() string {
func KernelVersion() string {
uname := &syscall.Utsname{}
if err := syscall.Uname(uname); err != nil {

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// The machine package contains functions that extract machine-level specs.
package machine
import (
@@ -33,8 +34,6 @@ import (
"github.com/golang/glog"
)
// The utils/machine package contains functions that extract machine-level specs.
var (
cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)
@@ -49,8 +48,8 @@ const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
func GetClockSpeed(procInfo []byte) (uint64, error) {
// s390/s390x and aarch64 changes
if true == isSystemZ() || true == isAArch64() {
// s390/s390x, aarch64 and arm32 changes
if isSystemZ() || isAArch64() || isArm32() {
return 0, nil
}
@@ -280,13 +279,20 @@ func getMachineArch() (string, error) {
return arch, nil
}
// arm32 chanes
func isArm32() bool {
arch, err := getMachineArch()
if err == nil {
return strings.Contains(arch, "arm")
}
return false
}
// aarch64 changes
func isAArch64() bool {
arch, err := getMachineArch()
if err == nil {
if true == strings.Contains(arch, "aarch64") {
return true
}
return strings.Contains(arch, "aarch64")
}
return false
}
@@ -295,9 +301,7 @@ func isAArch64() bool {
func isSystemZ() bool {
arch, err := getMachineArch()
if err == nil {
if true == strings.Contains(arch, "390") {
return true
}
return strings.Contains(arch, "390")
}
return false
}

View File

@@ -42,6 +42,7 @@ import (
)
// Housekeeping interval.
var enableLoadReader = flag.Bool("enable_load_reader", false, "Whether to enable cpu load reader")
var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings")
var cgroupPathRegExp = regexp.MustCompile(`devices[^:]*:(.*?)[,;$]`)
@@ -303,7 +304,7 @@ func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace
return processes, nil
}
func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool) (*containerData, error) {
func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, logUsage bool, collectorManager collector.CollectorManager, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool) (*containerData, error) {
if memoryCache == nil {
return nil, fmt.Errorf("nil memory storage")
}
@@ -321,7 +322,6 @@ func newContainerData(containerName string, memoryCache *memory.InMemoryCache, h
housekeepingInterval: *HousekeepingInterval,
maxHousekeepingInterval: maxHousekeepingInterval,
allowDynamicHousekeeping: allowDynamicHousekeeping,
loadReader: loadReader,
logUsage: logUsage,
loadAvg: -1.0, // negative value indicates uninitialized.
stop: make(chan bool, 1),
@@ -331,6 +331,17 @@ func newContainerData(containerName string, memoryCache *memory.InMemoryCache, h
cont.loadDecay = math.Exp(float64(-cont.housekeepingInterval.Seconds() / 10))
if *enableLoadReader {
// Create cpu load reader.
loadReader, err := cpuload.New()
if err != nil {
// TODO(rjnagal): Promote to warning once we support cpu load inside namespaces.
glog.Infof("Could not initialize cpu load reader for %q: %s", ref.Name, err)
} else {
cont.loadReader = loadReader
}
}
err = cont.updateSpec()
if err != nil {
return nil, err
@@ -375,6 +386,16 @@ func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Tim
func (c *containerData) housekeeping() {
// Start any background goroutines - must be cleaned up in c.handler.Cleanup().
c.handler.Start()
defer c.handler.Cleanup()
// Initialize cpuload reader - must be cleaned up in c.loadReader.Stop()
if c.loadReader != nil {
err := c.loadReader.Start()
if err != nil {
glog.Warningf("Could not start cpu load stat collector for %q: %s", c.info.Name, err)
}
defer c.loadReader.Stop()
}
// Long housekeeping is either 100ms or half of the housekeeping interval.
longHousekeeping := 100 * time.Millisecond
@@ -388,8 +409,6 @@ func (c *containerData) housekeeping() {
for {
select {
case <-c.stop:
// Cleanup container resources before stopping housekeeping.
c.handler.Cleanup()
// Stop housekeeping when signaled.
return
default:

View File

@@ -36,9 +36,13 @@ import (
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/utils/cpuload"
"github.com/google/cadvisor/machine"
"github.com/google/cadvisor/manager/watcher"
rawwatcher "github.com/google/cadvisor/manager/watcher/raw"
rktwatcher "github.com/google/cadvisor/manager/watcher/rkt"
"github.com/google/cadvisor/utils/oomparser"
"github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/version"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
@@ -46,7 +50,6 @@ import (
var globalHousekeepingInterval = flag.Duration("global_housekeeping_interval", 1*time.Minute, "Interval between global housekeepings")
var logCadvisorUsage = flag.Bool("log_cadvisor_usage", false, "Whether to log the usage of the cAdvisor container")
var enableLoadReader = flag.Bool("enable_load_reader", false, "Whether to enable cpu load reader")
var eventStorageAgeLimit = flag.String("event_storage_age_limit", "default=24h", "Max length of time for which to store events (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is a duration. Default is applied to all non-specified event types")
var eventStorageEventLimit = flag.String("event_storage_event_limit", "default=100000", "Max number of events to store (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is an integer. Default is applied to all non-specified event types")
var applicationMetricsCountLimit = flag.Int("application_metrics_count_limit", 100, "Max number of application metrics to store (per container)")
@@ -65,6 +68,8 @@ type Manager interface {
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
// Get V2 information about a container.
// Recursive (subcontainer) requests are best-effort, and may return a partial result alongside an
// error in the partial failure case.
GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error)
// Get information about all subcontainers of the specified container (includes self).
@@ -110,10 +115,10 @@ type Manager interface {
CloseEventChannel(watch_id int)
// Get status information about docker.
DockerInfo() (DockerStatus, error)
DockerInfo() (info.DockerStatus, error)
// Get details about interesting docker images.
DockerImages() ([]DockerImage, error)
DockerImages() ([]info.DockerImage, error)
// Returns debugging information. Map of lines per category.
DebugInfo() map[string][]string
@@ -132,7 +137,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
}
glog.Infof("cAdvisor running in container: %q", selfContainer)
dockerInfo, err := dockerInfo()
dockerStatus, err := docker.Status()
if err != nil {
glog.Warningf("Unable to connect to Docker: %v", err)
}
@@ -144,8 +149,8 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
context := fs.Context{
Docker: fs.DockerContext{
Root: docker.RootDir(),
Driver: dockerInfo.Driver,
DriverStatus: dockerInfo.DriverStatus,
Driver: dockerStatus.Driver,
DriverStatus: dockerStatus.DriverStatus,
},
RktPath: rktPath,
}
@@ -161,6 +166,9 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
inHostNamespace = true
}
// Register for new subcontainers.
eventsChannel := make(chan watcher.ContainerEvent, 16)
newManager := &manager{
containers: make(map[namespacedContainerName]*containerData),
quitChannels: make([]chan error, 0, 2),
@@ -172,9 +180,11 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
maxHousekeepingInterval: maxHousekeepingInterval,
allowDynamicHousekeeping: allowDynamicHousekeeping,
ignoreMetrics: ignoreMetricsSet,
containerWatchers: []watcher.ContainerWatcher{},
eventsChannel: eventsChannel,
}
machineInfo, err := getMachineInfo(sysfs, fsInfo, inHostNamespace)
machineInfo, err := machine.Info(sysfs, fsInfo, inHostNamespace)
if err != nil {
return nil, err
}
@@ -209,12 +219,13 @@ type manager struct {
quitChannels []chan error
cadvisorContainer string
inHostNamespace bool
loadReader cpuload.CpuLoadReader
eventHandler events.EventManager
startupTime time.Time
maxHousekeepingInterval time.Duration
allowDynamicHousekeeping bool
ignoreMetrics container.MetricSet
containerWatchers []watcher.ContainerWatcher
eventsChannel chan watcher.ContainerEvent
}
// Start the container manager.
@@ -227,6 +238,12 @@ func (self *manager) Start() error {
err = rkt.Register(self, self.fsInfo, self.ignoreMetrics)
if err != nil {
glog.Errorf("Registration of the rkt container factory failed: %v", err)
} else {
watcher, err := rktwatcher.NewRktContainerWatcher()
if err != nil {
return err
}
self.containerWatchers = append(self.containerWatchers, watcher)
}
err = systemd.Register(self, self.fsInfo, self.ignoreMetrics)
@@ -239,24 +256,11 @@ func (self *manager) Start() error {
glog.Errorf("Registration of the raw container factory failed: %v", err)
}
self.DockerInfo()
self.DockerImages()
if *enableLoadReader {
// Create cpu load reader.
cpuLoadReader, err := cpuload.New()
if err != nil {
// TODO(rjnagal): Promote to warning once we support cpu load inside namespaces.
glog.Infof("Could not initialize cpu load reader: %s", err)
} else {
err = cpuLoadReader.Start()
if err != nil {
glog.Warningf("Could not start cpu load stat collector: %s", err)
} else {
self.loadReader = cpuLoadReader
}
}
rawWatcher, err := rawwatcher.NewRawContainerWatcher()
if err != nil {
return err
}
self.containerWatchers = append(self.containerWatchers, rawWatcher)
// Watch for OOMs.
err = self.watchForNewOoms()
@@ -270,7 +274,7 @@ func (self *manager) Start() error {
}
// Create root and then recover all containers.
err = self.createContainer("/")
err = self.createContainer("/", watcher.Raw)
if err != nil {
return err
}
@@ -310,10 +314,6 @@ func (self *manager) Stop() error {
}
}
self.quitChannels = make([]chan error, 0, 2)
if self.loadReader != nil {
self.loadReader.Stop()
self.loadReader = nil
}
return nil
}
@@ -373,15 +373,16 @@ func (self *manager) GetDerivedStats(containerName string, options v2.RequestOpt
if err != nil {
return nil, err
}
var errs partialFailure
stats := make(map[string]v2.DerivedStats)
for name, cont := range conts {
d, err := cont.DerivedStats()
if err != nil {
return nil, err
errs.append(name, "DerivedStats", err)
}
stats[name] = d
}
return stats, nil
return stats, errs.OrNil()
}
func (self *manager) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) {
@@ -389,16 +390,17 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
if err != nil {
return nil, err
}
var errs partialFailure
specs := make(map[string]v2.ContainerSpec)
for name, cont := range conts {
cinfo, err := cont.GetInfo()
if err != nil {
return nil, err
errs.append(name, "GetInfo", err)
}
spec := self.getV2Spec(cinfo)
specs[name] = spec
}
return specs, nil
return specs, errs.OrNil()
}
// Get V2 container spec from v1 container info.
@@ -434,26 +436,32 @@ func (self *manager) GetContainerInfoV2(containerName string, options v2.Request
return nil, err
}
var errs partialFailure
var nilTime time.Time // Ignored.
infos := make(map[string]v2.ContainerInfo, len(containers))
for name, container := range containers {
result := v2.ContainerInfo{}
cinfo, err := container.GetInfo()
if err != nil {
return nil, err
errs.append(name, "GetInfo", err)
infos[name] = result
continue
}
result.Spec = self.getV2Spec(cinfo)
var nilTime time.Time // Ignored.
stats, err := self.memoryCache.RecentStats(name, nilTime, nilTime, options.Count)
if err != nil {
return nil, err
errs.append(name, "RecentStats", err)
infos[name] = result
continue
}
infos[name] = v2.ContainerInfo{
Spec: self.getV2Spec(cinfo),
Stats: v2.ContainerStatsFromV1(&cinfo.Spec, stats),
}
result.Stats = v2.ContainerStatsFromV1(&cinfo.Spec, stats)
infos[name] = result
}
return infos, nil
return infos, errs.OrNil()
}
func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
@@ -594,6 +602,7 @@ func (self *manager) GetRequestedContainersInfo(containerName string, options v2
if err != nil {
return nil, err
}
var errs partialFailure
containersMap := make(map[string]*info.ContainerInfo)
query := info.ContainerInfoRequest{
NumStats: options.Count,
@@ -601,12 +610,11 @@ func (self *manager) GetRequestedContainersInfo(containerName string, options v2
for name, data := range containers {
info, err := self.containerDataToContainerInfo(data, &query)
if err != nil {
// Skip containers with errors, we try to degrade gracefully.
continue
errs.append(name, "containerDataToContainerInfo", err)
}
containersMap[name] = info
}
return containersMap, nil
return containersMap, errs.OrNil()
}
func (self *manager) getRequestedContainers(containerName string, options v2.RequestOptions) (map[string]*containerData, error) {
@@ -769,8 +777,12 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c
return nil
}
// Create a container.
func (m *manager) createContainer(containerName string) error {
// Enables overwriting an existing containerData/Handler object for a given containerName.
// Can't use createContainer as it just returns if a given containerName has a handler already.
// Ex: rkt handler will want to take priority over the raw handler, but the raw handler might be created first.
// Only allow raw handler to be overridden
func (m *manager) overrideContainer(containerName string, watchSource watcher.ContainerWatchSource) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
@@ -778,12 +790,41 @@ func (m *manager) createContainer(containerName string) error {
Name: containerName,
}
if _, ok := m.containers[namespacedName]; ok {
containerData := m.containers[namespacedName]
if containerData.handler.Type() != container.ContainerTypeRaw {
return nil
}
err := m.destroyContainerLocked(containerName)
if err != nil {
return fmt.Errorf("overrideContainer: failed to destroy containerData/handler for %v: %v", containerName, err)
}
}
return m.createContainerLocked(containerName, watchSource)
}
// Create a container.
func (m *manager) createContainer(containerName string, watchSource watcher.ContainerWatchSource) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
return m.createContainerLocked(containerName, watchSource)
}
func (m *manager) createContainerLocked(containerName string, watchSource watcher.ContainerWatchSource) error {
namespacedName := namespacedContainerName{
Name: containerName,
}
// Check that the container didn't already exist.
if _, ok := m.containers[namespacedName]; ok {
return nil
}
handler, accept, err := container.NewContainerHandler(containerName, m.inHostNamespace)
handler, accept, err := container.NewContainerHandler(containerName, watchSource, m.inHostNamespace)
if err != nil {
return err
}
@@ -798,7 +839,7 @@ func (m *manager) createContainer(containerName string) error {
}
logUsage := *logCadvisorUsage && containerName == m.cadvisorContainer
cont, err := newContainerData(containerName, m.memoryCache, handler, m.loadReader, logUsage, collectorManager, m.maxHousekeepingInterval, m.allowDynamicHousekeeping)
cont, err := newContainerData(containerName, m.memoryCache, handler, logUsage, collectorManager, m.maxHousekeepingInterval, m.allowDynamicHousekeeping)
if err != nil {
return err
}
@@ -850,6 +891,10 @@ func (m *manager) destroyContainer(containerName string) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
return m.destroyContainerLocked(containerName)
}
func (m *manager) destroyContainerLocked(containerName string) error {
namespacedName := namespacedContainerName{
Name: containerName,
}
@@ -947,7 +992,7 @@ func (m *manager) detectSubcontainers(containerName string) error {
// Add the new containers.
for _, cont := range added {
err = m.createContainer(cont.Name)
err = m.createContainer(cont.Name, watcher.Raw)
if err != nil {
glog.Errorf("Failed to create existing container: %s: %s", cont.Name, err)
}
@@ -966,28 +1011,15 @@ func (m *manager) detectSubcontainers(containerName string) error {
// Watches for new containers started in the system. Runs forever unless there is a setup error.
func (self *manager) watchForNewContainers(quit chan error) error {
var root *containerData
var ok bool
func() {
self.containersLock.RLock()
defer self.containersLock.RUnlock()
root, ok = self.containers[namespacedContainerName{
Name: "/",
}]
}()
if !ok {
return fmt.Errorf("root container does not exist when watching for new containers")
}
// Register for new subcontainers.
eventsChannel := make(chan container.SubcontainerEvent, 16)
err := root.handler.WatchSubcontainers(eventsChannel)
if err != nil {
return err
for _, watcher := range self.containerWatchers {
err := watcher.Start(self.eventsChannel)
if err != nil {
return err
}
}
// There is a race between starting the watch and new container creation so we do a detection before we read new containers.
err = self.detectSubcontainers("/")
err := self.detectSubcontainers("/")
if err != nil {
return err
}
@@ -996,21 +1028,37 @@ func (self *manager) watchForNewContainers(quit chan error) error {
go func() {
for {
select {
case event := <-eventsChannel:
case event := <-self.eventsChannel:
switch {
case event.EventType == container.SubcontainerAdd:
err = self.createContainer(event.Name)
case event.EventType == container.SubcontainerDelete:
case event.EventType == watcher.ContainerAdd:
switch event.WatchSource {
// the Rkt and Raw watchers can race, and if Raw wins, we want Rkt to override and create a new handler for Rkt containers
case watcher.Rkt:
err = self.overrideContainer(event.Name, event.WatchSource)
default:
err = self.createContainer(event.Name, event.WatchSource)
}
case event.EventType == watcher.ContainerDelete:
err = self.destroyContainer(event.Name)
}
if err != nil {
glog.Warningf("Failed to process watch event: %v", err)
glog.Warningf("Failed to process watch event %+v: %v", event, err)
}
case <-quit:
var errs partialFailure
// Stop processing events if asked to quit.
err := root.handler.StopWatchingSubcontainers()
quit <- err
if err == nil {
for i, watcher := range self.containerWatchers {
err := watcher.Stop()
if err != nil {
errs.append(fmt.Sprintf("watcher %d", i), "Stop", err)
}
}
if len(errs) > 0 {
quit <- errs
} else {
quit <- nil
glog.Infof("Exiting thread watching subcontainers")
return
}
@@ -1125,79 +1173,12 @@ func parseEventsStoragePolicy() events.StoragePolicy {
return policy
}
type DockerStatus struct {
Version string `json:"version"`
KernelVersion string `json:"kernel_version"`
OS string `json:"os"`
Hostname string `json:"hostname"`
RootDir string `json:"root_dir"`
Driver string `json:"driver"`
DriverStatus map[string]string `json:"driver_status"`
ExecDriver string `json:"exec_driver"`
NumImages int `json:"num_images"`
NumContainers int `json:"num_containers"`
func (m *manager) DockerImages() ([]info.DockerImage, error) {
return docker.Images()
}
type DockerImage struct {
ID string `json:"id"`
RepoTags []string `json:"repo_tags"` // repository name and tags.
Created int64 `json:"created"` // unix time since creation.
VirtualSize int64 `json:"virtual_size"`
Size int64 `json:"size"`
}
func (m *manager) DockerImages() ([]DockerImage, error) {
images, err := docker.DockerImages()
if err != nil {
return nil, err
}
out := []DockerImage{}
const unknownTag = "<none>:<none>"
for _, image := range images {
if len(image.RepoTags) == 1 && image.RepoTags[0] == unknownTag {
// images with repo or tags are uninteresting.
continue
}
di := DockerImage{
ID: image.ID,
RepoTags: image.RepoTags,
Created: image.Created,
VirtualSize: image.VirtualSize,
Size: image.Size,
}
out = append(out, di)
}
return out, nil
}
func (m *manager) DockerInfo() (DockerStatus, error) {
return dockerInfo()
}
func dockerInfo() (DockerStatus, error) {
dockerInfo, err := docker.DockerInfo()
if err != nil {
return DockerStatus{}, err
}
versionInfo, err := getVersionInfo()
if err != nil {
return DockerStatus{}, err
}
out := DockerStatus{}
out.Version = versionInfo.DockerVersion
out.KernelVersion = dockerInfo.KernelVersion
out.OS = dockerInfo.OperatingSystem
out.Hostname = dockerInfo.Name
out.RootDir = dockerInfo.DockerRootDir
out.Driver = dockerInfo.Driver
out.ExecDriver = dockerInfo.ExecutionDriver
out.NumImages = dockerInfo.Images
out.NumContainers = dockerInfo.Containers
out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus))
for _, v := range dockerInfo.DriverStatus {
out.DriverStatus[v[0]] = v[1]
}
return out, nil
func (m *manager) DockerInfo() (info.DockerStatus, error) {
return docker.Status()
}
func (m *manager) DebugInfo() map[string][]string {
@@ -1234,3 +1215,36 @@ func (m *manager) DebugInfo() map[string][]string {
debugInfo["Managed containers"] = lines
return debugInfo
}
func getVersionInfo() (*info.VersionInfo, error) {
kernel_version := machine.KernelVersion()
container_os := machine.ContainerOsVersion()
docker_version := docker.VersionString()
return &info.VersionInfo{
KernelVersion: kernel_version,
ContainerOsVersion: container_os,
DockerVersion: docker_version,
CadvisorVersion: version.Info["version"],
CadvisorRevision: version.Info["revision"],
}, nil
}
// Helper for accumulating partial failures.
type partialFailure []string
func (f *partialFailure) append(id, operation string, err error) {
*f = append(*f, fmt.Sprintf("[%q: %s: %s]", id, operation, err))
}
func (f partialFailure) Error() string {
return fmt.Sprintf("partial failures: %s", strings.Join(f, ", "))
}
func (f partialFailure) OrNil() error {
if len(f) == 0 {
return nil
}
return f
}

View File

@@ -1,119 +0,0 @@
// 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.
// +build test
package manager
import (
"github.com/google/cadvisor/events"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/mock"
)
type ManagerMock struct {
mock.Mock
}
func (c *ManagerMock) Start() error {
args := c.Called()
return args.Error(0)
}
func (c *ManagerMock) Stop() error {
args := c.Called()
return args.Error(0)
}
func (c *ManagerMock) GetContainerInfo(name string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
args := c.Called(name, query)
return args.Get(0).(*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) {
args := c.Called(containerName, query)
return args.Get(0).([]*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) {
args := c.Called(query)
return args.Get(0).(map[string]info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) DockerContainer(name string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) {
args := c.Called(name, query)
return args.Get(0).(info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]v2.ContainerSpec), args.Error(1)
}
func (c *ManagerMock) GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]v2.DerivedStats), args.Error(1)
}
func (c *ManagerMock) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) Exists(name string) bool {
args := c.Called(name)
return args.Get(0).(bool)
}
func (c *ManagerMock) WatchForEvents(queryuest *events.Request, passedChannel chan *info.Event) error {
args := c.Called(queryuest, passedChannel)
return args.Error(0)
}
func (c *ManagerMock) GetPastEvents(queryuest *events.Request) ([]*info.Event, error) {
args := c.Called(queryuest)
return args.Get(0).([]*info.Event), args.Error(1)
}
func (c *ManagerMock) GetMachineInfo() (*info.MachineInfo, error) {
args := c.Called()
return args.Get(0).(*info.MachineInfo), args.Error(1)
}
func (c *ManagerMock) GetVersionInfo() (*info.VersionInfo, error) {
args := c.Called()
return args.Get(0).(*info.VersionInfo), args.Error(1)
}
func (c *ManagerMock) GetFsInfo() ([]v2.FsInfo, error) {
args := c.Called()
return args.Get(0).([]v2.FsInfo), args.Error(1)
}
func (c *ManagerMock) GetProcessList(name string, options v2.RequestOptions) ([]v2.ProcessInfo, error) {
args := c.Called()
return args.Get(0).([]v2.ProcessInfo), args.Error(1)
}
func (c *ManagerMock) DockerInfo() (DockerStatus, error) {
args := c.Called()
return args.Get(0).(DockerStatus), args.Error(1)
}
func (c *ManagerMock) DockerImages() ([]DockerImage, error) {
args := c.Called()
return args.Get(0).([]DockerImage), args.Error(1)
}

View File

@@ -0,0 +1,214 @@
// 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 defines types for sub-container events and also
// defines an interface for container operation handlers.
package raw
import (
"fmt"
"io/ioutil"
"path"
"strings"
"github.com/google/cadvisor/container/common"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/manager/watcher"
"github.com/golang/glog"
"golang.org/x/exp/inotify"
)
type rawContainerWatcher struct {
// Absolute path to the root of the cgroup hierarchies
cgroupPaths map[string]string
cgroupSubsystems *libcontainer.CgroupSubsystems
// Inotify event watcher.
watcher *common.InotifyWatcher
// Signal for watcher thread to stop.
stopWatcher chan error
}
func NewRawContainerWatcher() (watcher.ContainerWatcher, error) {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
if len(cgroupSubsystems.Mounts) == 0 {
return nil, fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
watcher, err := common.NewInotifyWatcher()
if err != nil {
return nil, err
}
rawWatcher := &rawContainerWatcher{
cgroupPaths: common.MakeCgroupPaths(cgroupSubsystems.MountPoints, "/"),
cgroupSubsystems: &cgroupSubsystems,
watcher: watcher,
stopWatcher: make(chan error),
}
return rawWatcher, nil
}
func (self *rawContainerWatcher) Start(events chan watcher.ContainerEvent) error {
// Watch this container (all its cgroups) and all subdirectories.
for _, cgroupPath := range self.cgroupPaths {
_, err := self.watchDirectory(cgroupPath, "/")
if err != nil {
return err
}
}
// Process the events received from the kernel.
go func() {
for {
select {
case event := <-self.watcher.Event():
err := self.processEvent(event, events)
if err != nil {
glog.Warningf("Error while processing event (%+v): %v", event, err)
}
case err := <-self.watcher.Error():
glog.Warningf("Error while watching %q:", "/", err)
case <-self.stopWatcher:
err := self.watcher.Close()
if err == nil {
self.stopWatcher <- err
return
}
}
}
}()
return nil
}
func (self *rawContainerWatcher) Stop() error {
// Rendezvous with the watcher thread.
self.stopWatcher <- nil
return <-self.stopWatcher
}
// Watches the specified directory and all subdirectories. Returns whether the path was
// already being watched and an error (if any).
func (self *rawContainerWatcher) watchDirectory(dir string, containerName string) (bool, error) {
alreadyWatching, err := self.watcher.AddWatch(containerName, dir)
if err != nil {
return alreadyWatching, err
}
// Remove the watch if further operations failed.
cleanup := true
defer func() {
if cleanup {
_, err := self.watcher.RemoveWatch(containerName, dir)
if err != nil {
glog.Warningf("Failed to remove inotify watch for %q: %v", dir, err)
}
}
}()
// TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime.
// Watch subdirectories as well.
entries, err := ioutil.ReadDir(dir)
if err != nil {
return alreadyWatching, err
}
for _, entry := range entries {
if entry.IsDir() {
// TODO(vmarmol): We don't have to fail here, maybe we can recover and try to get as many registrations as we can.
_, err = self.watchDirectory(path.Join(dir, entry.Name()), path.Join(containerName, entry.Name()))
if err != nil {
return alreadyWatching, err
}
}
}
cleanup = false
return alreadyWatching, nil
}
func (self *rawContainerWatcher) processEvent(event *inotify.Event, events chan watcher.ContainerEvent) error {
// Convert the inotify event type to a container create or delete.
var eventType watcher.ContainerEventType
switch {
case (event.Mask & inotify.IN_CREATE) > 0:
eventType = watcher.ContainerAdd
case (event.Mask & inotify.IN_DELETE) > 0:
eventType = watcher.ContainerDelete
case (event.Mask & inotify.IN_MOVED_FROM) > 0:
eventType = watcher.ContainerDelete
case (event.Mask & inotify.IN_MOVED_TO) > 0:
eventType = watcher.ContainerAdd
default:
// Ignore other events.
return nil
}
// Derive the container name from the path name.
var containerName string
for _, mount := range self.cgroupSubsystems.Mounts {
mountLocation := path.Clean(mount.Mountpoint) + "/"
if strings.HasPrefix(event.Name, mountLocation) {
containerName = event.Name[len(mountLocation)-1:]
break
}
}
if containerName == "" {
return fmt.Errorf("unable to detect container from watch event on directory %q", event.Name)
}
// Maintain the watch for the new or deleted container.
switch eventType {
case watcher.ContainerAdd:
// New container was created, watch it.
alreadyWatched, err := self.watchDirectory(event.Name, containerName)
if err != nil {
return err
}
// Only report container creation once.
if alreadyWatched {
return nil
}
case watcher.ContainerDelete:
// Container was deleted, stop watching for it.
lastWatched, err := self.watcher.RemoveWatch(containerName, event.Name)
if err != nil {
return err
}
// Only report container deletion once.
if !lastWatched {
return nil
}
default:
return fmt.Errorf("unknown event type %v", eventType)
}
// Deliver the event.
events <- watcher.ContainerEvent{
EventType: eventType,
Name: containerName,
WatchSource: watcher.Raw,
}
return nil
}

View File

@@ -0,0 +1,154 @@
// Copyright 2016 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 rkt implements the watcher interface for rkt
package rkt
import (
"path/filepath"
"time"
"github.com/google/cadvisor/container/rkt"
"github.com/google/cadvisor/manager/watcher"
rktapi "github.com/coreos/rkt/api/v1alpha"
"github.com/golang/glog"
"golang.org/x/net/context"
)
type rktContainerWatcher struct {
// Signal for watcher thread to stop.
stopWatcher chan error
}
func NewRktContainerWatcher() (watcher.ContainerWatcher, error) {
watcher := &rktContainerWatcher{
stopWatcher: make(chan error),
}
return watcher, nil
}
func (self *rktContainerWatcher) Start(events chan watcher.ContainerEvent) error {
go self.detectRktContainers(events)
return nil
}
func (self *rktContainerWatcher) Stop() error {
// Rendezvous with the watcher thread.
self.stopWatcher <- nil
return nil
}
func (self *rktContainerWatcher) detectRktContainers(events chan watcher.ContainerEvent) {
glog.Infof("starting detectRktContainers thread")
ticker := time.Tick(10 * time.Second)
curpods := make(map[string]*rktapi.Pod)
for {
select {
case <-ticker:
pods, err := listRunningPods()
if err != nil {
glog.Errorf("detectRktContainers: listRunningPods failed: %v", err)
continue
}
curpods = self.syncRunningPods(pods, events, curpods)
case <-self.stopWatcher:
glog.Infof("Exiting rktContainer Thread")
return
}
}
}
func (self *rktContainerWatcher) syncRunningPods(pods []*rktapi.Pod, events chan watcher.ContainerEvent, curpods map[string]*rktapi.Pod) map[string]*rktapi.Pod {
newpods := make(map[string]*rktapi.Pod)
for _, pod := range pods {
newpods[pod.Id] = pod
// if pods become mutable, have to handle this better
if _, ok := curpods[pod.Id]; !ok {
// should create all cgroups not including system.slice
// i.e. /system.slice/rkt-test.service and /system.slice/rkt-test.service/system.slice/pause.service
for _, cgroup := range podToCgroup(pod) {
self.sendUpdateEvent(cgroup, events)
}
}
}
for id, pod := range curpods {
if _, ok := newpods[id]; !ok {
for _, cgroup := range podToCgroup(pod) {
glog.Infof("cgroup to delete = %v", cgroup)
self.sendDestroyEvent(cgroup, events)
}
}
}
return newpods
}
func (self *rktContainerWatcher) sendUpdateEvent(cgroup string, events chan watcher.ContainerEvent) {
events <- watcher.ContainerEvent{
EventType: watcher.ContainerAdd,
Name: cgroup,
WatchSource: watcher.Rkt,
}
}
func (self *rktContainerWatcher) sendDestroyEvent(cgroup string, events chan watcher.ContainerEvent) {
events <- watcher.ContainerEvent{
EventType: watcher.ContainerDelete,
Name: cgroup,
WatchSource: watcher.Rkt,
}
}
func listRunningPods() ([]*rktapi.Pod, error) {
client, err := rkt.Client()
if err != nil {
return nil, err
}
resp, err := client.ListPods(context.Background(), &rktapi.ListPodsRequest{
// Specify the request: Fetch and print only running pods and their details.
Detail: true,
Filters: []*rktapi.PodFilter{
{
States: []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING},
},
},
})
if err != nil {
return nil, err
}
return resp.Pods, nil
}
func podToCgroup(pod *rktapi.Pod) []string {
cgroups := make([]string, 1+len(pod.Apps), 1+len(pod.Apps))
baseCgroup := pod.Cgroup
cgroups[0] = baseCgroup
for i, app := range pod.Apps {
cgroups[i+1] = filepath.Join(baseCgroup, "system.slice", app.Name+".service")
}
return cgroups
}

View File

@@ -0,0 +1,52 @@
// Copyright 2016 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 defines types for sub-container events and also
// defines an interface for container operation handlers.
package watcher
// SubcontainerEventType indicates an addition or deletion event.
type ContainerEventType int
const (
ContainerAdd ContainerEventType = iota
ContainerDelete
)
type ContainerWatchSource int
const (
Raw ContainerWatchSource = iota
Rkt
)
// ContainerEvent represents a
type ContainerEvent struct {
// The type of event that occurred.
EventType ContainerEventType
// The full container name of the container where the event occurred.
Name string
// The watcher that detected this change event
WatchSource ContainerWatchSource
}
type ContainerWatcher interface {
// Registers a channel to listen for events affecting subcontainers (recursively).
Start(events chan ContainerEvent) error
// Stops watching for subcontainer changes.
Stop() error
}

View File

@@ -232,6 +232,7 @@ func serveContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) e
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
CustomMetricsAvailable: cont.Spec.HasCustomMetrics,
SubcontainersAvailable: len(subcontainerLinks) > 0,
Root: rootDir,
}
err = pageTemplate.Execute(w, data)

View File

@@ -1,243 +0,0 @@
// 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 pages
const containersHtmlTemplate = `
<html>
<head>
<title>cAdvisor - {{.DisplayName}}</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="{{.Root}}static/bootstrap-3.1.1.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="{{.Root}}static/bootstrap-theme-3.1.1.min.css">
<link rel="stylesheet" href="{{.Root}}static/containers.css">
<!-- Latest compiled and minified JavaScript -->
<script src="{{.Root}}static/jquery-1.10.2.min.js"></script>
<script src="{{.Root}}static/bootstrap-3.1.1.min.js"></script>
<script type="text/javascript" src="{{.Root}}static/google-jsapi.js"></script>
<script type="text/javascript" src="{{.Root}}static/containers.js"></script>
</head>
<body>
<div class="container theme-showcase" >
<a href="{{.Root}}" class="col-sm-12" id="logo">
</a>
<div class="col-sm-12">
<div class="page-header">
<h1>{{.DisplayName}}</h1>
</div>
<ol class="breadcrumb">
{{range $parentContainer := .ParentContainers}}
<li><a href="{{$parentContainer.Link}}">{{$parentContainer.Text}}</a></li>
{{end}}
</ol>
</div>
{{if .IsRoot}}
<div class="col-sm-12">
<h4><a href="../docker">Docker Containers</a></h4>
</div>
{{end}}
{{if .Subcontainers}}
<div class="col-sm-12">
<div class="page-header">
<h3>Subcontainers</h3>
</div>
<div class="list-group">
{{range $subcontainer := .Subcontainers}}
<a href="{{$subcontainer.Link}}" class="list-group-item">{{$subcontainer.Text}}</a>
{{end}}
</div>
</div>
{{end}}
{{if .DockerStatus}}
<div class="col-sm-12">
<div class="page-header">
<h3>Driver Status</h3>
</div>
<ul class="list-group">
{{range $dockerstatus := .DockerStatus}}
<li class ="list-group-item"><span class="stat-label">{{$dockerstatus.Key}}</span> {{$dockerstatus.Value}}</li>
{{end}}
{{if .DockerDriverStatus}}
<li class ="list-group-item"><span class="stat-label">Storage<br></span>
<ul class="list-group">
{{range $driverstatus := .DockerDriverStatus}}
<li class="list-group-item"><span class="stat-label">{{$driverstatus.Key}}</span> {{$driverstatus.Value}}</li>
{{end}}
</ul>
</li>
</ul>
{{end}}
</div>
{{end}}
{{if .DockerImages}}
<div class="col-sm-12">
<div class="page-header">
<h3>Images</h3>
</div>
<div id="docker-images"></div>
<br><br>
</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> {{printSize .Spec.Memory.Reservation}} <span class="unit-label">{{printUnit .Spec.Memory.Reservation}}</span></li>
{{end}}
{{if .Spec.Memory.Limit}}
<li class="list-group-item"><span class="stat-label">Limit</span> {{printSize .Spec.Memory.Limit}} <span class="unit-label">{{printUnit .Spec.Memory.Limit}}</span></li>
{{end}}
{{if .Spec.Memory.SwapLimit}}
<li class="list-group-item"><span class="stat-label">Swap Limit</span> {{printSize .Spec.Memory.SwapLimit}} <span class="unit-label">{{printUnit .Spec.Memory.SwapLimit}}</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>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Processes</h3>
</div>
<div id="processes-top" 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>CPU Load Average</h4>
<div id="cpu-load-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" id="progress-hot-memory">
<span class="sr-only">Hot Memory</span>
</div>
<div class="progress-bar progress-bar-info" id="progress-cold-memory">
<span class="sr-only">Cold Memory</span>
</div>
</div>
</div>
<div class="col-sm-3" id="memory-text"></div>
</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">
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="network-selection-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span id="network-selection-text"></span>
<span class="caret"></span>
</button>
<ul id="network-selection" class="dropdown-menu" role="menu" aria-labelledby="network-selection-dropdown">
</ul>
</div>
</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}}
{{if .FsAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Filesystem</h3>
</div>
<div id="filesystem-usage" class="panel-body">
</div>
</div>
{{end}}
{{if .CustomMetricsAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Application Metrics</h3>
</div>
<div class="panel-body">
<div id="custom-metrics-chart"></div>
</div>
</div>
{{end}}
</div>
{{end}}
</div>
<script type="text/javascript">
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}}, {{.Root}}, {{.IsRoot}});
drawImages({{.DockerImages}});
</script>
</body>
</html>
`

View File

@@ -31,7 +31,7 @@ import (
const DockerPage = "/docker/"
func toStatusKV(status manager.DockerStatus) ([]keyVal, []keyVal) {
func toStatusKV(status info.DockerStatus) ([]keyVal, []keyVal) {
ds := []keyVal{
{Key: "Driver", Value: status.Driver},
}

View File

@@ -59,15 +59,17 @@ type pageData struct {
NetworkAvailable bool
FsAvailable bool
CustomMetricsAvailable bool
SubcontainersAvailable bool
Root string
DockerStatus []keyVal
DockerDriverStatus []keyVal
DockerImages []manager.DockerImage
DockerImages []info.DockerImage
}
func init() {
containersHtmlTemplate, _ := Asset("pages/assets/html/containers.html")
pageTemplate = template.New("containersTemplate").Funcs(funcMap)
_, err := pageTemplate.Parse(containersHtmlTemplate)
_, err := pageTemplate.Parse(string(containersHtmlTemplate))
if err != nil {
glog.Fatalf("Failed to parse template: %s", err)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,881 +0,0 @@
// 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 static
const containersJs = gchartsJs + `
function humanize(num, size, units) {
var unit;
for (unit = units.pop(); units.length && num >= size; unit = units.pop()) {
num /= size;
}
return [num, unit];
}
// Following the IEC naming convention
function humanizeIEC(num) {
var ret = humanize(num, 1024, ["TiB", "GiB", "MiB", "KiB", "B"]);
return ret[0].toFixed(2) + " " + ret[1];
}
// Following the Metric naming convention
function humanizeMetric(num) {
var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]);
return ret[0].toFixed(2) + " " + ret[1];
}
// Draw a table.
function drawTable(seriesTitles, titleTypes, data, elementId, numPages, sortIndex) {
var dataTable = new google.visualization.DataTable();
for (var i = 0; i < seriesTitles.length; i++) {
dataTable.addColumn(titleTypes[i], seriesTitles[i]);
}
dataTable.addRows(data);
if (!(elementId in window.charts)) {
window.charts[elementId] = new google.visualization.Table(document.getElementById(elementId));
}
var cssClassNames = {
'headerRow': '',
'tableRow': 'table-row',
'oddTableRow': 'table-row'
};
var opts = {
alternatingRowStyle: true,
page: 'enable',
pageSize: numPages,
allowHtml: true,
sortColumn: sortIndex,
sortAscending: false,
cssClassNames: cssClassNames,
};
window.charts[elementId].draw(dataTable, opts);
}
// Draw a line chart.
function drawLineChart(seriesTitles, data, elementId, unit) {
var min = Infinity;
var max = -Infinity;
for (var i = 0; i < data.length; i++) {
// Convert the first column to a Date.
if (data[i] != null) {
data[i][0] = new Date(data[i][0]);
}
// Find min, max.
for (var j = 1; j < data[i].length; j++) {
var val = data[i][j];
if (val < min) {
min = val;
}
if (val > max) {
max = val;
}
}
}
// We don't want to show any values less than 0 so cap the min value at that.
// At the same time, show 10% of the graph below the min value if we can.
var minWindow = min - (max - min) / 10;
if (minWindow < 0) {
minWindow = 0;
}
// Add the definition of each column and the necessary data.
var dataTable = new google.visualization.DataTable();
dataTable.addColumn('datetime', seriesTitles[0]);
for (var i = 1; i < seriesTitles.length; i++) {
dataTable.addColumn('number', seriesTitles[i]);
}
dataTable.addRows(data);
// Create and draw the visualization.
if (!(elementId in window.charts)) {
window.charts[elementId] = new google.visualization.LineChart(document.getElementById(elementId));
}
// TODO(vmarmol): Look into changing the view window to get a smoother animation.
var opts = {
curveType: 'function',
height: 300,
legend:{position:"none"},
focusTarget: "category",
vAxis: {
title: unit,
viewWindow: {
min: minWindow,
},
},
legend: {
position: 'bottom',
},
};
// If the whole data series has the same value, try to center it in the chart.
if ( min == max) {
opts.vAxis.viewWindow.max = 1.1 * max
opts.vAxis.viewWindow.min = 0.9 * max
}
window.charts[elementId].draw(dataTable, opts);
}
// Gets the length of the interval in nanoseconds.
function getInterval(current, previous) {
var cur = new Date(current);
var prev = new Date(previous);
// ms -> ns.
return (cur.getTime() - prev.getTime()) * 1000000;
}
// Checks if the specified stats include the specified resource.
function hasResource(stats, resource) {
return stats.stats.length > 0 && stats.stats[0][resource];
}
// Draw a set of gauges. Data is comprised of an array of arrays with two elements:
// a string label and a numeric value for the gauge.
function drawGauges(elementId, gauges) {
gauges.unshift(['Label', 'Value']);
// Create and populate the data table.
var data = google.visualization.arrayToDataTable(gauges);
// Create and draw the visualization.
var options = {
height: 100,
redFrom: 90, redTo: 100,
yellowFrom:75, yellowTo: 90,
minorTicks: 5,
animation: {
duration: 900,
easing: 'linear'
}
};
var chart = new google.visualization.Gauge(document.getElementById(elementId));
chart.draw(data, options);
}
// Get the machine info.
function getMachineInfo(rootDir, callback) {
$.getJSON(rootDir + "api/v1.0/machine", function(data) {
callback(data);
});
}
// Get ps info.
function getProcessInfo(rootDir, containerName, callback) {
$.getJSON(rootDir + "api/v2.0/ps" + containerName)
.done(function(data) {
callback(data);
})
.fail(function(jqhxr, textStatus, error) {
callback([]);
});
}
// Get the container stats for the specified container.
function getStats(rootDir, containerName, callback) {
// Request 60s of container history and no samples.
var request = JSON.stringify({
// Update main.statsRequestedByUI while updating "num_stats" here.
"num_stats": 60,
"num_samples": 0
});
$.post(rootDir + "api/v1.0/containers" + containerName, request, function(data) {
callback(data);
}, "json");
}
// Draw the graph for CPU usage.
function drawCpuTotalUsage(elementId, machineInfo, stats) {
if (stats.spec.has_cpu && !hasResource(stats, "cpu")) {
return;
}
var titles = ["Time", "Total"];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalInNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.cpu.usage.total - prev.cpu.usage.total) / intervalInNs);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Cores");
}
// Draw the graph for CPU load.
function drawCpuLoad(elementId, machineInfo, stats) {
var titles = ["Time", "Average"];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.cpu.load_average/1000);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Runnable threads");
}
// Draw the graph for per-core CPU usage.
function drawCpuPerCoreUsage(elementId, machineInfo, stats) {
if (stats.spec.has_cpu && !hasResource(stats, "cpu")) {
return;
}
// Add a title for each core.
var titles = ["Time"];
for (var i = 0; i < machineInfo.num_cores; i++) {
titles.push("Core " + i);
}
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalInNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
for (var j = 0; j < machineInfo.num_cores; j++) {
elements.push((cur.cpu.usage.per_cpu_usage[j] - prev.cpu.usage.per_cpu_usage[j]) / intervalInNs);
}
data.push(elements);
}
drawLineChart(titles, data, elementId, "Cores");
}
// Draw the graph for CPU usage breakdown.
function drawCpuUsageBreakdown(elementId, machineInfo, containerInfo) {
if (containerInfo.spec.has_cpu && !hasResource(containerInfo, "cpu")) {
return;
}
var titles = ["Time", "User", "Kernel"];
var data = [];
for (var i = 1; i < containerInfo.stats.length; i++) {
var cur = containerInfo.stats[i];
var prev = containerInfo.stats[i - 1];
var intervalInNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.cpu.usage.user - prev.cpu.usage.user) / intervalInNs);
elements.push((cur.cpu.usage.system - prev.cpu.usage.system) / intervalInNs);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Cores");
}
// Draw the gauges for overall resource usage.
function drawOverallUsage(elementId, machineInfo, containerInfo) {
var cur = containerInfo.stats[containerInfo.stats.length - 1];
var gauges = [];
var cpuUsage = 0;
if (containerInfo.spec.has_cpu && containerInfo.stats.length >= 2) {
var prev = containerInfo.stats[containerInfo.stats.length - 2];
var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total;
var intervalInNs = getInterval(cur.timestamp, prev.timestamp);
// Convert to millicores and take the percentage
cpuUsage = Math.round(((rawUsage / intervalInNs) / machineInfo.num_cores) * 100);
if (cpuUsage > 100) {
cpuUsage = 100;
}
gauges.push(['CPU', cpuUsage]);
}
var memoryUsage = 0;
if (containerInfo.spec.has_memory) {
// Saturate to the machine size.
var limit = containerInfo.spec.memory.limit;
if (limit > machineInfo.memory_capacity) {
limit = machineInfo.memory_capacity;
}
memoryUsage = Math.round((cur.memory.usage / limit) * 100);
gauges.push(['Memory', memoryUsage]);
}
var numGauges = gauges.length;
if (cur.filesystem) {
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
var totalUsage = Math.floor((data.usage * 100.0) / data.capacity);
var els = window.cadvisor.fsUsage.elements[data.device];
// Update the gauges in the right order.
gauges[numGauges + els.index] = ['FS #' + (els.index + 1), totalUsage];
}
// Limit the number of filesystem gauges displayed to 5.
// 'Filesystem details' section still shows information for all filesystems.
var max_gauges = numGauges + 5;
if (gauges.length > max_gauges) {
gauges = gauges.slice(0, max_gauges);
}
}
drawGauges(elementId, gauges);
}
var oneMegabyte = 1024 * 1024;
var oneGigabyte = 1024 * oneMegabyte;
function drawMemoryUsage(elementId, machineInfo, containerInfo) {
if (containerInfo.spec.has_memory && !hasResource(containerInfo, "memory")) {
return;
}
var titles = ["Time", "Total", "Hot"];
var data = [];
for (var i = 0; i < containerInfo.stats.length; i++) {
var cur = containerInfo.stats[i];
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.memory.usage / oneMegabyte);
elements.push(cur.memory.working_set / oneMegabyte);
data.push(elements);
}
// Get the memory limit, saturate to the machine size.
var memory_limit = machineInfo.memory_capacity;
if (containerInfo.spec.memory.limit && (containerInfo.spec.memory.limit < memory_limit)) {
memory_limit = containerInfo.spec.memory.limit;
}
// Updating the progress bar.
var cur = containerInfo.stats[containerInfo.stats.length-1];
var hotMemory = Math.floor((cur.memory.working_set * 100.0) / memory_limit);
var totalMemory = Math.floor((cur.memory.usage * 100.0) / memory_limit);
var coldMemory = totalMemory - hotMemory;
$("#progress-hot-memory").width(hotMemory + "%");
$("#progress-cold-memory").width(coldMemory + "%");
$("#memory-text").text(humanizeIEC(cur.memory.usage) + " / " + humanizeIEC(memory_limit) + " ("+ totalMemory +"%)");
drawLineChart(titles, data, elementId, "Megabytes");
}
// Get the index of the interface with the specified name.
function getNetworkInterfaceIndex(interfaceName, interfaces) {
for (var i = 0; i < interfaces.length; i++) {
if (interfaces[i].name == interfaceName) {
return i;
}
}
return -1;
}
// Draw the graph for network tx/rx bytes.
function drawNetworkBytes(elementId, machineInfo, stats) {
if (stats.spec.has_network && !hasResource(stats, "network")) {
return;
}
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log("Unable to find interface\"", interfaceName, "\" in ", stats.stats.network);
return;
}
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];
var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000;
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.network.interfaces[interfaceIndex].tx_bytes - prev.network.interfaces[interfaceIndex].tx_bytes) / intervalInSec);
elements.push((cur.network.interfaces[interfaceIndex].rx_bytes - prev.network.interfaces[interfaceIndex].rx_bytes) / intervalInSec);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Bytes per second");
}
// Draw the graph for network errors
function drawNetworkErrors(elementId, machineInfo, stats) {
if (stats.spec.has_network && !hasResource(stats, "network")) {
return;
}
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log("Unable to find interface\"", interfaceName, "\" in ", stats.stats.network);
return;
}
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];
var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000;
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.network.interfaces[interfaceIndex].tx_errors - prev.network.interfaces[interfaceIndex].tx_errors) / intervalInSec);
elements.push((cur.network.interfaces[interfaceIndex].rx_errors - prev.network.interfaces[interfaceIndex].rx_errors) / intervalInSec);
data.push(elements);
}
drawLineChart(titles, data, elementId, "Errors per second");
}
// Update the filesystem usage values.
function drawFileSystemUsage(machineInfo, stats) {
var cur = stats.stats[stats.stats.length - 1];
if (!cur.filesystem) {
return;
}
var el = $("<div>");
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
var totalUsage = Math.floor((data.usage * 100.0) / data.capacity);
// Update DOM elements.
var els = window.cadvisor.fsUsage.elements[data.device];
els.progressElement.width(totalUsage + "%");
els.textElement.text(humanizeMetric(data.usage) + " / " + humanizeMetric(data.capacity)+ " (" + totalUsage + "%)");
}
}
function drawImages(images) {
if (images == null || images.length == 0) {
return;
}
window.charts = {};
var titles = ["Repository", "Tags", "ID", "Virtual Size", "Creation Time"];
var titleTypes = ['string', 'string', 'string', 'number', 'number'];
var sortIndex = 0;
var data = [];
for (var i = 0; i < images.length; i++) {
var elements = [];
var tags = [];
var repos = images[i].repo_tags[0].split(":");
repos.splice(-1,1)
for (var j = 0; j < images[i].repo_tags.length; j++) {
var splits = images[i].repo_tags[j].split(":")
if (splits.length > 1) {
tags.push(splits[splits.length - 1])
}
}
elements.push(repos.join(":"));
elements.push(tags.join(", "));
elements.push(images[i].id.substr(0,24));
elements.push({v: images[i].virtual_size, f: humanizeIEC(images[i].virtual_size)});
var d = new Date(images[i].created * 1000);
elements.push({v: images[i].created, f: d.toLocaleString()});
data.push(elements);
}
drawTable(titles, titleTypes, data, "docker-images", 30, sortIndex);
}
function drawProcesses(isRoot, rootDir, processInfo) {
if (processInfo.length == 0) {
$("#processes-top").text("No processes found");
return;
}
var titles = ["User", "PID", "PPID", "Start Time", "CPU %", "MEM %", "RSS", "Virtual Size", "Status", "Running Time", "Command"];
var titleTypes = ['string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string'];
var sortIndex = 4
if (isRoot) {
titles.push("Container");
titleTypes.push('string');
}
var data = []
for (var i = 0; i < processInfo.length; i++) {
var elements = [];
elements.push(processInfo[i].user);
elements.push(processInfo[i].pid);
elements.push(processInfo[i].parent_pid);
elements.push(processInfo[i].start_time);
elements.push({ v:processInfo[i].percent_cpu, f:processInfo[i].percent_cpu.toFixed(2)});
elements.push({ v:processInfo[i].percent_mem, f:processInfo[i].percent_mem.toFixed(2)});
elements.push({ v:processInfo[i].rss, f:humanizeIEC(processInfo[i].rss)});
elements.push({ v:processInfo[i].virtual_size, f:humanizeIEC(processInfo[i].virtual_size)});
elements.push(processInfo[i].status);
elements.push(processInfo[i].running_time);
elements.push(processInfo[i].cmd);
if (isRoot) {
var cgroup = processInfo[i].cgroup_path
// Use the raw cgroup link as it works for all containers.
var cgroupLink = '<a href="' + rootDir + 'containers/' + cgroup +'">' + cgroup.substr(0,30) + ' </a>';
elements.push({v:cgroup, f:cgroupLink});
}
data.push(elements);
}
drawTable(titles, titleTypes, data, "processes-top", 25, sortIndex);
}
// Draw the filesystem usage nodes.
function startFileSystemUsage(elementId, machineInfo, stats) {
window.cadvisor.fsUsage = {};
// A map of device name to DOM elements.
window.cadvisor.fsUsage.elements = {};
var cur = stats.stats[stats.stats.length - 1];
var el = $("<div>");
if (!cur.filesystem) {
return;
}
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
el.append($("<div>")
.addClass("row col-sm-12")
.append($("<h4>")
.text("FS #" + (i + 1) + ": " + data.device)));
var progressElement = $("<div>").addClass("progress-bar progress-bar-danger");
el.append($("<div>")
.addClass("col-sm-9")
.append($("<div>")
.addClass("progress")
.append(progressElement)));
var textElement = $("<div>").addClass("col-sm-3");
el.append(textElement);
window.cadvisor.fsUsage.elements[data.device] = {
'progressElement': progressElement,
'textElement': textElement,
'index': i,
};
}
$("#" + elementId).empty().append(el);
drawFileSystemUsage(machineInfo, stats);
}
// 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) {
// No steps, stop.
if (steps.length == 0) {
return;
}
// Get a step and execute it.
var step = steps.shift();
step();
// Schedule the next step.
setTimeout(function() {
stepExecute(steps);
}, 0);
}
// Draw all the charts on the page.
function drawCharts(machineInfo, containerInfo) {
var steps = [];
if (containerInfo.spec.has_cpu || containerInfo.spec.has_memory) {
steps.push(function() {
drawOverallUsage("usage-gauge", machineInfo, containerInfo)
});
}
// CPU.
if (containerInfo.spec.has_cpu) {
steps.push(function() {
drawCpuTotalUsage("cpu-total-usage-chart", machineInfo, containerInfo);
});
// TODO(rjnagal): Re-enable CPU Load after understanding resource usage.
// steps.push(function() {
// drawCpuLoad("cpu-load-chart", machineInfo, containerInfo);
// });
steps.push(function() {
drawCpuPerCoreUsage("cpu-per-core-usage-chart", machineInfo, containerInfo);
});
steps.push(function() {
drawCpuUsageBreakdown("cpu-usage-breakdown-chart", machineInfo, containerInfo);
});
}
// Memory.
if (containerInfo.spec.has_memory) {
steps.push(function() {
drawMemoryUsage("memory-usage-chart", machineInfo, containerInfo);
});
}
// Network.
if (containerInfo.spec.has_network) {
steps.push(function() {
drawNetworkBytes("network-bytes-chart", machineInfo, containerInfo);
});
steps.push(function() {
drawNetworkErrors("network-errors-chart", machineInfo, containerInfo);
});
}
// Filesystem.
if (containerInfo.spec.has_filesystem) {
steps.push(function() {
drawFileSystemUsage(machineInfo, containerInfo);
});
}
// Custom Metrics
if (containerInfo.spec.has_custom_metrics) {
steps.push(function() {
getCustomMetrics(window.cadvisor.rootDir, window.cadvisor.containerName, function(metricsInfo) {
drawCustomMetrics("custom-metrics-chart", containerInfo, metricsInfo)
});
});
}
stepExecute(steps);
}
function setNetwork(interfaceName) {
$("#network-selection-text")
.empty()
.append($("<span>").text("Interface: "))
.append($("<b>").text(interfaceName));
window.cadvisor.network.interface = interfaceName;
// Draw the new stats.
refreshStats();
}
// Creates the network selection dropdown.
function startNetwork(selectionElement, containerInfo) {
if (!hasResource(containerInfo, "network") || containerInfo.stats.length == 0
|| !containerInfo.stats[0].network.interfaces || containerInfo.stats[0].network.interfaces.length == 0) {
return;
}
window.cadvisor.network = {};
window.cadvisor.network.interface = "";
// Add all interfaces to the dropdown.
var el = $("#" + selectionElement);
for (var i = 0; i < containerInfo.stats[0].network.interfaces.length; i++) {
var interfaceName = containerInfo.stats[0].network.interfaces[i].name;
el.append($("<li>")
.attr("role", "presentation")
.append($("<a>")
.attr("role", "menuitem")
.attr("tabindex", -1)
.click(setNetwork.bind(null, interfaceName))
.text(interfaceName)));
}
setNetwork(containerInfo.stats[0].network.interfaces[0].name);
}
// Refresh the stats on the page.
function refreshStats() {
var machineInfo = window.cadvisor.machineInfo;
getStats(window.cadvisor.rootDir, window.cadvisor.containerName, function(containerInfo){
if (window.cadvisor.firstRun) {
window.cadvisor.firstRun = false;
if (containerInfo.spec.has_filesystem) {
startFileSystemUsage("filesystem-usage", machineInfo, containerInfo);
}
if (containerInfo.spec.has_network) {
startNetwork("network-selection", containerInfo);
}
if (containerInfo.spec.has_custom_metrics) {
startCustomMetrics("custom-metrics-chart", containerInfo);
}
}
drawCharts(machineInfo, containerInfo);
});
}
function addAllLabels(containerInfo, metricsInfo) {
if (metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i=0; i<metricSpec.length; i++) {
metricName = metricSpec[i].name;
metricLabelVal = container[metricName];
firstLabel = true;
for (var label in metricLabelVal) {
if (label == "") {
$('#button-'+metricName).hide();
}
$("#"+metricName+"_labels").append($("<li>")
.attr("role", "presentation")
.append($("<a>")
.attr("role", "menuitem")
.click(setLabel.bind(null, metricName, label))
.text(label)));
if (firstLabel) {
firstLabel = false;
setLabel(metricName, label);
}
}
}
}
}
function getMetricIndex(metricName) {
for (i = 0; i<window.cadvisor.metricLabelPair.length; ++i) {
if (window.cadvisor.metricLabelPair[i][0] == metricName)
return i;
}
return -1;
}
function setLabel(metric, label) {
$("#"+metric+"-selection-text")
.empty()
.append($("<span>").text("Label: "))
.append($("<b>").text(label))
index = getMetricIndex(metric);
if (index == -1) {
window.cadvisor.metricLabelPair.push([metric, label]);
} else {
window.cadvisor.metricLabelPair[index][1] = label;
}
refreshStats();
}
function getSelectedLabel(metricName) {
index = getMetricIndex(metricName);
if (index == -1)
return "";
return window.cadvisor.metricLabelPair[index][1];
}
function startCustomMetrics(elementId, containerInfo) {
var metricSpec = containerInfo.spec.custom_metrics;
var metricStats = containerInfo.stats.custom_metrics;
var el=$("<div>");
if (metricSpec.length < window.cadvisor.maxCustomMetrics)
window.cadvisor.maxCustomMetrics = metricSpec.length
for (i = 0; i<window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
var divText = "<div class='dropdown'> <button class='btn btn-default dropdown-toggle' type='button' id='button-"+metricName;
divText += "' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>";
divText += "<span id='"+metricName+"-selection-text'></span> <span class='caret'></span> </button>";
divText += "<ul id='"+metricName+"_labels' class='dropdown-menu' role='menu' aria-labelledby='button-"+metricName+ "'> </ul> </div>";
divText += "<div id='"+elementId+"-"+metricName+"'> </div>";
el.append($(divText));
}
el.append($("</div>"));
$("#"+elementId).append(el);
}
function getCustomMetrics(rootDir, containerName, callback) {
$.getJSON(rootDir + "api/v2.0/appmetrics/" + containerName)
.done(function(data) {
callback(data);
})
.fail(function(jqhxr, textStatus, error) {
callback([]);
});
}
function drawCustomMetrics(elementId, containerInfo, metricsInfo) {
if(metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i=0; i<window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
metricUnits = metricSpec[i].units;
var titles = ["Time", metricName];
metricLabelVal = container[metricName];
if (window.cadvisor.firstCustomCollection) {
window.cadvisor.firstCustomCollection = false;
addAllLabels(containerInfo, metricsInfo);
}
var data = [];
selectedLabel = getSelectedLabel(metricName);
metricVal = metricLabelVal[selectedLabel];
for (var index in metricVal) {
metric = metricVal[index];
var elements = [];
for (var attribute in metric) {
value = metric[attribute];
elements.push(value);
}
if (elements.length<2) {
elements.push(0);
}
data.push(elements);
}
drawLineChart(titles, data, elementId+"-"+metricName, metricUnits);
}
}
}
// Executed when the page finishes loading.
function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
// Don't fetch data if we don't have any resource.
if (!hasCpu && !hasMemory) {
return;
}
window.charts = {};
window.cadvisor = {};
window.cadvisor.firstRun = true;
window.cadvisor.rootDir = rootDir;
window.cadvisor.containerName = containerName;
window.cadvisor.firstCustomCollection = true;
window.cadvisor.metricLabelPair = [];
window.cadvisor.maxCustomMetrics = 10;
// Draw process information at start and refresh every 60s.
getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(isRoot, rootDir, processInfo)
});
setInterval(function() {
getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(isRoot, rootDir, processInfo)
});
}, 60000);
// Get machine info, then get the stats every 1s.
getMachineInfo(rootDir, function(machineInfo) {
window.cadvisor.machineInfo = machineInfo;
setInterval(function() {
refreshStats();
}, 1000);
});
}
`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -26,14 +26,25 @@ import (
const StaticResource = "/static/"
var staticFiles = map[string]string{
var bootstrapJs, _ = Asset("pages/assets/js/bootstrap-3.1.1.min.js")
var containersJs, _ = Asset("pages/assets/js/containers.js")
var gchartsJs, _ = Asset("pages/assets/js/gcharts.js")
var googleJsapiJs, _ = Asset("pages/assets/js/google-jsapi.js")
var jqueryJs, _ = Asset("pages/assets/js/jquery-1.10.2.min.js")
var bootstrapCss, _ = Asset("pages/assets/styles/bootstrap-3.1.1.min.css")
var bootstrapThemeCss, _ = Asset("pages/assets/styles/bootstrap-theme-3.1.1.min.css")
var containersCss, _ = Asset("pages/assets/styles/containers.css")
var staticFiles = map[string][]byte{
"bootstrap-3.1.1.min.css": bootstrapCss,
"bootstrap-3.1.1.min.js": bootstrapJs,
"bootstrap-theme-3.1.1.min.css": bootstrapThemeCss,
"containers.css": containersCss,
"containers.js": containersJs,
"bootstrap-3.1.1.min.css": bootstrapCss,
"bootstrap-theme-3.1.1.min.css": bootstrapThemeCss,
"jquery-1.10.2.min.js": jqueryJs,
"bootstrap-3.1.1.min.js": bootstrapJs,
"gcharts.js": gchartsJs,
"google-jsapi.js": googleJsapiJs,
"jquery-1.10.2.min.js": jqueryJs,
}
func HandleRequest(w http.ResponseWriter, u *url.URL) error {
@@ -54,6 +65,6 @@ func HandleRequest(w http.ResponseWriter, u *url.URL) error {
w.Header().Set("Content-Type", contentType)
}
_, err := w.Write([]byte(content))
_, err := w.Write(content)
return err
}

256
vendor/github.com/google/cadvisor/pages/templates.go generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -16,6 +16,7 @@ package storage
import (
"fmt"
"sort"
info "github.com/google/cadvisor/info/v1"
)
@@ -47,3 +48,12 @@ func New(name string) (StorageDriver, error) {
}
return f()
}
func ListDrivers() []string {
drivers := make([]string, 0, len(registeredPlugins))
for name := range registeredPlugins {
drivers = append(drivers, name)
}
sort.Strings(drivers)
return drivers
}

View File

@@ -15,24 +15,26 @@
package cloudinfo
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"io/ioutil"
"strings"
info "github.com/google/cadvisor/info/v1"
)
const (
ProductVerFileName = "/sys/class/dmi/id/product_version"
Amazon = "amazon"
)
func onAWS() bool {
// the default client behavior retried the operation multiple times with a 5s timeout per attempt.
// if you were not on aws, you would block for 20s when invoking this operation.
// we reduce retries to 0 and set the timeout to 2s to reduce the time this blocks when not on aws.
client := ec2metadata.New(session.New(&aws.Config{MaxRetries: aws.Int(0)}))
if client.Config.HTTPClient != nil {
client.Config.HTTPClient.Timeout = time.Duration(2 * time.Second)
data, err := ioutil.ReadFile(ProductVerFileName)
if err != nil {
return false
}
return client.Available()
return strings.Contains(string(data), Amazon)
}
func getAwsMetadata(name string) string {

View File

@@ -41,6 +41,6 @@ func New() (CpuLoadReader, error) {
if err != nil {
return nil, fmt.Errorf("failed to create a netlink based cpuload reader: %v", err)
}
glog.Info("Using a netlink-based load reader")
glog.V(3).Info("Using a netlink-based load reader")
return reader, nil
}

View File

@@ -0,0 +1,58 @@
// Copyright 2016 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 docker
import (
"fmt"
"strings"
dockertypes "github.com/docker/engine-api/types"
)
const (
DockerInfoDriver = "Driver"
DockerInfoDriverStatus = "DriverStatus"
DriverStatusPoolName = "Pool Name"
DriverStatusDataLoopFile = "Data loop file"
DriverStatusMetadataFile = "Metadata file"
)
func DriverStatusValue(status [][2]string, target string) string {
for _, v := range status {
if strings.EqualFold(v[0], target) {
return v[1]
}
}
return ""
}
func DockerThinPoolName(info dockertypes.Info) (string, error) {
poolName := DriverStatusValue(info.DriverStatus, DriverStatusPoolName)
if len(poolName) == 0 {
return "", fmt.Errorf("Could not get devicemapper pool name")
}
return poolName, nil
}
func DockerMetadataDevice(info dockertypes.Info) (string, error) {
metadataDevice := DriverStatusValue(info.DriverStatus, DriverStatusMetadataFile)
if len(metadataDevice) == 0 {
return "", fmt.Errorf("Could not get the devicemapper metadata device")
}
return metadataDevice, nil
}

View File

@@ -18,7 +18,6 @@ import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"path"
"regexp"
@@ -26,6 +25,7 @@ import (
"time"
"github.com/google/cadvisor/utils"
"github.com/google/cadvisor/utils/tail"
"github.com/golang/glog"
)
@@ -105,27 +105,29 @@ func checkIfStartOfOomMessages(line string) bool {
// Should prevent EOF errors that occur when lines are read before being fully
// written to the log. It reads line by line splitting on
// the "\n" character.
func readLinesFromFile(lineChannel chan string, ioreader *bufio.Reader) {
func readLinesFromFile(lineChannel chan string, ioreader *bufio.Reader) error {
linefragment := ""
var line string
var err error
for true {
line, err = ioreader.ReadString('\n')
if err == io.EOF {
if line != "" {
linefragment += line
}
time.Sleep(100 * time.Millisecond)
} else if err == nil {
if linefragment != "" {
line = linefragment + line
linefragment = ""
}
lineChannel <- line
} else if err != nil && err != io.EOF {
if err != nil && err != io.EOF {
glog.Errorf("exiting analyzeLinesHelper with error %v", err)
close(lineChannel)
break
}
if line == "" {
time.Sleep(100 * time.Millisecond)
continue
}
if err == nil {
lineChannel <- linefragment + line
linefragment = ""
} else { // err == io.EOF
linefragment += line
}
}
return err
}
// Calls goroutine for readLinesFromFile, which feeds it complete lines.
@@ -144,22 +146,23 @@ func (self *OomParser) StreamOoms(outStream chan *OomInstance) {
oomCurrentInstance := &OomInstance{
ContainerName: "/",
}
finished := false
for !finished {
for line := range lineChannel {
err := getContainerName(line, oomCurrentInstance)
if err != nil {
glog.Errorf("%v", err)
}
finished, err = getProcessNamePid(line, oomCurrentInstance)
finished, err := getProcessNamePid(line, oomCurrentInstance)
if err != nil {
glog.Errorf("%v", err)
}
line = <-lineChannel
if finished {
break
}
}
outStream <- oomCurrentInstance
}
}
glog.Infof("exiting analyzeLines")
glog.Infof("exiting analyzeLines. OOM events will not be reported.")
}
func callJournalctl() (io.ReadCloser, error) {
@@ -183,7 +186,6 @@ func trySystemd() (*OomParser, error) {
return &OomParser{
ioreader: bufio.NewReader(readcloser),
}, nil
}
// List of possible kernel log files. These are prioritized in order so that
@@ -192,7 +194,7 @@ var kernelLogFiles = []string{"/var/log/kern.log", "/var/log/messages", "/var/lo
// looks for system files that contain kernel messages and if one is found, sets
// the systemFile attribute of the OomParser object
func getSystemFile() (string, error) {
func getLogFile() (string, error) {
for _, logFile := range kernelLogFiles {
if utils.FileExists(logFile) {
glog.Infof("OOM parser using kernel log file: %q", logFile)
@@ -202,18 +204,29 @@ func getSystemFile() (string, error) {
return "", fmt.Errorf("unable to find any kernel log file available from our set: %v", kernelLogFiles)
}
// initializes an OomParser object and calls getSystemFile to set the systemFile
// attribute. Returns and OomParser object and an error
func New() (*OomParser, error) {
systemFile, err := getSystemFile()
func tryLogFile() (*OomParser, error) {
logFile, err := getLogFile()
if err != nil {
return trySystemd()
return nil, err
}
file, err := os.Open(systemFile)
tail, err := tail.NewTail(logFile)
if err != nil {
return trySystemd()
return nil, err
}
return &OomParser{
ioreader: bufio.NewReader(file),
ioreader: bufio.NewReader(tail),
}, nil
}
// initializes an OomParser object. Returns an OomParser object and an error.
func New() (*OomParser, error) {
parser, err := trySystemd()
if err == nil {
return parser, nil
}
parser, err = tryLogFile()
if err == nil {
return parser, nil
}
return nil, err
}

146
vendor/github.com/google/cadvisor/utils/tail/tail.go generated vendored Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2016 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 tail implements "tail -F" functionality following rotated logs
package tail
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"time"
"github.com/golang/glog"
"golang.org/x/exp/inotify"
)
type Tail struct {
reader *bufio.Reader
readerErr error
readerLock sync.RWMutex
filename string
file *os.File
stop chan bool
watcher *inotify.Watcher
}
const (
defaultRetryInterval = 100 * time.Millisecond
maxRetryInterval = 30 * time.Second
)
// NewTail starts opens the given file and watches it for deletion/rotation
func NewTail(filename string) (*Tail, error) {
t := &Tail{
filename: filename,
}
var err error
t.stop = make(chan bool)
t.watcher, err = inotify.NewWatcher()
if err != nil {
return nil, fmt.Errorf("inotify init failed on %s: %v", t.filename, err)
}
go t.watchLoop()
return t, nil
}
// Read implements the io.Reader interface for Tail
func (t *Tail) Read(p []byte) (int, error) {
t.readerLock.RLock()
defer t.readerLock.RUnlock()
if t.reader == nil || t.readerErr != nil {
return 0, t.readerErr
}
return t.reader.Read(p)
}
var _ io.Reader = &Tail{}
// Close stops watching and closes the file
func (t *Tail) Close() {
close(t.stop)
}
func (t *Tail) attemptOpen() error {
t.readerLock.Lock()
defer t.readerLock.Unlock()
t.reader = nil
t.readerErr = nil
attempt := 0
for interval := defaultRetryInterval; ; interval *= 2 {
attempt++
glog.V(4).Infof("Opening %s (attempt %d)", t.filename, attempt)
var err error
t.file, err = os.Open(t.filename)
if err == nil {
// TODO: not interested in old events?
//t.file.Seek(0, os.SEEK_END)
t.reader = bufio.NewReader(t.file)
return nil
}
if interval >= maxRetryInterval {
break
}
select {
case <-time.After(interval):
case <-t.stop:
t.readerErr = io.EOF
return fmt.Errorf("watch was cancelled")
}
}
err := fmt.Errorf("can't open log file %s", t.filename)
t.readerErr = err
return err
}
func (t *Tail) watchLoop() {
for {
err := t.watchFile()
if err != nil {
glog.Errorf("Tail failed on %s: %v", t.filename, err)
break
}
}
}
func (t *Tail) watchFile() error {
err := t.attemptOpen()
if err != nil {
return err
}
defer t.file.Close()
watchDir := filepath.Dir(t.filename)
err = t.watcher.AddWatch(watchDir, inotify.IN_MOVED_FROM|inotify.IN_DELETE)
if err != nil {
return fmt.Errorf("Failed to add watch to directory %s: %v", watchDir, err)
}
defer t.watcher.RemoveWatch(watchDir)
for {
select {
case event := <-t.watcher.Event:
eventPath := filepath.Clean(event.Name) // Directory events have an extra '/'
if eventPath == t.filename {
glog.V(4).Infof("Log file %s moved/deleted", t.filename)
return nil
}
case <-t.stop:
return fmt.Errorf("watch was cancelled")
}
}
}

View File

@@ -58,19 +58,23 @@ func NewTimedStore(age time.Duration, maxItems int) *TimedStore {
// Adds an element to the start of the buffer (removing one from the end if necessary).
func (self *TimedStore) Add(timestamp time.Time, item interface{}) {
// Remove any elements if over our max size.
if self.maxItems >= 0 && (len(self.buffer)+1) > self.maxItems {
startIndex := len(self.buffer) + 1 - self.maxItems
self.buffer = self.buffer[startIndex:]
}
// Add the new element first and sort. We can then remove an expired element, if required.
copied := item
self.buffer = append(self.buffer, timedStoreData{
data := timedStoreData{
timestamp: timestamp,
data: copied,
})
data: item,
}
// Common case: data is added in order.
if len(self.buffer) == 0 || !timestamp.Before(self.buffer[len(self.buffer)-1].timestamp) {
self.buffer = append(self.buffer, data)
} else {
// Data is out of order; insert it in the correct position.
index := sort.Search(len(self.buffer), func(index int) bool {
return self.buffer[index].timestamp.After(timestamp)
})
self.buffer = append(self.buffer, timedStoreData{}) // Make room to shift the elements
copy(self.buffer[index+1:], self.buffer[index:]) // Shift the elements over
self.buffer[index] = data
}
sort.Sort(self.buffer)
// Remove any elements before eviction time.
// TODO(rjnagal): This is assuming that the added entry has timestamp close to now.
evictTime := timestamp.Add(-self.age)
@@ -81,6 +85,11 @@ func (self *TimedStore) Add(timestamp time.Time, item interface{}) {
self.buffer = self.buffer[index:]
}
// Remove any elements if over our max size.
if self.maxItems >= 0 && len(self.buffer) > self.maxItems {
startIndex := len(self.buffer) - self.maxItems
self.buffer = self.buffer[startIndex:]
}
}
// Returns up to maxResult elements in the specified time period (inclusive).

View File

@@ -191,12 +191,6 @@ func validateDockerInfo() (string, string) {
}
desc := fmt.Sprintf("Docker exec driver is %s. Storage driver is %s.\n", info.ExecutionDriver, info.Driver)
stateFile := docker.DockerStateDir()
if !utils.FileExists(stateFile) {
desc += fmt.Sprintf("\tDocker container state directory %q is not accessible.\n", stateFile)
return Unsupported, desc
}
desc += fmt.Sprintf("\tDocker container state directory is at %q and is accessible.\n", stateFile)
return Recommended, desc
}

View File

@@ -1 +1 @@
0.23.0
0.23.2