Move deps from _workspace/ to vendor/

godep restore
pushd $GOPATH/src/github.com/appc/spec
git co master
popd
go get go4.org/errorutil
rm -rf Godeps
godep save ./...
git add vendor
git add -f $(git ls-files --other vendor/)
git co -- Godeps/LICENSES Godeps/.license_file_state Godeps/OWNERS
This commit is contained in:
Tim Hockin
2016-05-08 20:30:21 -07:00
parent 899f9b4e31
commit 3c0c5ed4e0
4400 changed files with 16739 additions and 376 deletions

View File

@@ -0,0 +1,61 @@
// 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.
// Unmarshal's a Containers description json file. The json file contains
// an array of ContainerHint structs, each with a container's id and networkInterface
// This allows collecting stats about network interfaces configured outside docker
// and lxc
package common
import (
"encoding/json"
"flag"
"io/ioutil"
"os"
)
var ArgContainerHints = flag.String("container_hints", "/etc/cadvisor/container_hints.json", "location of the container hints file")
type containerHints struct {
AllHosts []containerHint `json:"all_hosts,omitempty"`
}
type containerHint struct {
FullName string `json:"full_path,omitempty"`
NetworkInterface *networkInterface `json:"network_interface,omitempty"`
Mounts []Mount `json:"mounts,omitempty"`
}
type Mount struct {
HostDir string `json:"host_dir,omitempty"`
ContainerDir string `json:"container_dir,omitempty"`
}
type networkInterface struct {
VethHost string `json:"veth_host,omitempty"`
VethChild string `json:"veth_child,omitempty"`
}
func GetContainerHintsFromFile(containerHintsFile string) (containerHints, error) {
dat, err := ioutil.ReadFile(containerHintsFile)
if os.IsNotExist(err) {
return containerHints{}, nil
}
var cHints containerHints
if err == nil {
err = json.Unmarshal(dat, &cHints)
}
return cHints, err
}

View File

@@ -0,0 +1,134 @@
// Copyright 2015 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.
// Handler for Docker containers.
package common
import (
"sync"
"time"
"github.com/google/cadvisor/fs"
"github.com/golang/glog"
)
type FsHandler interface {
Start()
Usage() (baseUsageBytes uint64, totalUsageBytes uint64)
Stop()
}
type realFsHandler struct {
sync.RWMutex
lastUpdate time.Time
usageBytes uint64
baseUsageBytes uint64
period time.Duration
minPeriod time.Duration
rootfs string
extraDir string
fsInfo fs.FsInfo
// Tells the container to stop.
stopChan chan struct{}
}
const (
longDu = time.Second
duTimeout = time.Minute
maxDuBackoffFactor = 20
)
var _ FsHandler = &realFsHandler{}
func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) FsHandler {
return &realFsHandler{
lastUpdate: time.Time{},
usageBytes: 0,
baseUsageBytes: 0,
period: period,
minPeriod: period,
rootfs: rootfs,
extraDir: extraDir,
fsInfo: fsInfo,
stopChan: make(chan struct{}, 1),
}
}
func (fh *realFsHandler) update() error {
var (
baseUsage, extraDirUsage uint64
err error
)
// TODO(vishh): Add support for external mounts.
if fh.rootfs != "" {
baseUsage, err = fh.fsInfo.GetDirUsage(fh.rootfs, duTimeout)
if err != nil {
return err
}
}
if fh.extraDir != "" {
extraDirUsage, err = fh.fsInfo.GetDirUsage(fh.extraDir, duTimeout)
if err != nil {
return err
}
}
fh.Lock()
defer fh.Unlock()
fh.lastUpdate = time.Now()
fh.usageBytes = baseUsage + extraDirUsage
fh.baseUsageBytes = baseUsage
return nil
}
func (fh *realFsHandler) trackUsage() {
fh.update()
for {
select {
case <-fh.stopChan:
return
case <-time.After(fh.period):
start := time.Now()
if err := fh.update(); err != nil {
glog.Errorf("failed to collect filesystem stats - %v", err)
fh.period = fh.period * 2
if fh.period > maxDuBackoffFactor*fh.minPeriod {
fh.period = maxDuBackoffFactor * fh.minPeriod
}
} else {
fh.period = fh.minPeriod
}
duration := time.Since(start)
if duration > longDu {
glog.V(2).Infof("`du` on following dirs took %v: %v", duration, []string{fh.rootfs, fh.extraDir})
}
}
}
}
func (fh *realFsHandler) Start() {
go fh.trackUsage()
}
func (fh *realFsHandler) Stop() {
close(fh.stopChan)
}
func (fh *realFsHandler) Usage() (baseUsageBytes, totalUsageBytes uint64) {
fh.RLock()
defer fh.RUnlock()
return fh.baseUsageBytes, fh.usageBytes
}

View File

@@ -0,0 +1,203 @@
// 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 common
import (
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"time"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils"
"github.com/golang/glog"
)
func DebugInfo(watches map[string][]string) map[string][]string {
out := make(map[string][]string)
lines := make([]string, 0, len(watches))
for containerName, cgroupWatches := range watches {
lines = append(lines, fmt.Sprintf("%s:", containerName))
for _, cg := range cgroupWatches {
lines = append(lines, fmt.Sprintf("\t%s", cg))
}
}
out["Inotify watches"] = lines
return out
}
func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem bool) (info.ContainerSpec, error) {
var spec info.ContainerSpec
// Assume unified hierarchy containers.
// Get the lowest creation time from all hierarchies as the container creation time.
now := time.Now()
lowestTime := now
for _, cgroupPath := range cgroupPaths {
// The modified time of the cgroup directory changes whenever a subcontainer is created.
// eg. /docker will have creation time matching the creation of latest docker container.
// Use clone_children as a workaround as it isn't usually modified. It is only likely changed
// immediately after creating a container.
cgroupPath = path.Join(cgroupPath, "cgroup.clone_children")
fi, err := os.Stat(cgroupPath)
if err == nil && fi.ModTime().Before(lowestTime) {
lowestTime = fi.ModTime()
}
}
if lowestTime != now {
spec.CreationTime = lowestTime
}
// Get machine info.
mi, err := machineInfoFactory.GetMachineInfo()
if err != nil {
return spec, err
}
// CPU.
cpuRoot, ok := cgroupPaths["cpu"]
if ok {
if utils.FileExists(cpuRoot) {
spec.HasCpu = true
spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares")
spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us")
quota := readString(cpuRoot, "cpu.cfs_quota_us")
if quota != "" && quota != "-1" {
val, err := strconv.ParseUint(quota, 10, 64)
if err != nil {
glog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err)
}
spec.Cpu.Quota = val
}
}
}
// Cpu Mask.
// This will fail for non-unified hierarchies. We'll return the whole machine mask in that case.
cpusetRoot, ok := cgroupPaths["cpuset"]
if ok {
if utils.FileExists(cpusetRoot) {
spec.HasCpu = true
mask := readString(cpusetRoot, "cpuset.cpus")
spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores)
}
}
// Memory
memoryRoot, ok := cgroupPaths["memory"]
if ok {
if utils.FileExists(memoryRoot) {
spec.HasMemory = true
spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes")
spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes")
}
}
spec.HasNetwork = hasNetwork
spec.HasFilesystem = hasFilesystem
if blkioRoot, ok := cgroupPaths["blkio"]; ok && utils.FileExists(blkioRoot) {
spec.HasDiskIo = true
}
return spec, nil
}
func readString(dirpath string, file string) string {
cgroupFile := path.Join(dirpath, file)
// Ignore non-existent files
if !utils.FileExists(cgroupFile) {
return ""
}
// Read
out, err := ioutil.ReadFile(cgroupFile)
if err != nil {
glog.Errorf("readString: Failed to read %q: %s", cgroupFile, err)
return ""
}
return strings.TrimSpace(string(out))
}
func readUInt64(dirpath string, file string) uint64 {
out := readString(dirpath, file)
if out == "" {
return 0
}
val, err := strconv.ParseUint(out, 10, 64)
if err != nil {
glog.Errorf("readUInt64: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err)
return 0
}
return val
}
// Lists all directories under "path" and outputs the results as children of "parent".
func ListDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error {
// Ignore if this hierarchy does not exist.
if !utils.FileExists(dirpath) {
return nil
}
entries, err := ioutil.ReadDir(dirpath)
if err != nil {
return err
}
for _, entry := range entries {
// We only grab directories.
if entry.IsDir() {
name := path.Join(parent, entry.Name())
output[name] = struct{}{}
// List subcontainers if asked to.
if recursive {
err := ListDirectories(path.Join(dirpath, entry.Name()), name, true, output)
if err != nil {
return err
}
}
}
}
return nil
}
func MakeCgroupPaths(mountPoints map[string]string, name string) map[string]string {
cgroupPaths := make(map[string]string, len(mountPoints))
for key, val := range mountPoints {
cgroupPaths[key] = path.Join(val, name)
}
return cgroupPaths
}
func CgroupExists(cgroupPaths map[string]string) bool {
// If any cgroup exists, the container is still alive.
for _, cgroupPath := range cgroupPaths {
if utils.FileExists(cgroupPath) {
return true
}
}
return false
}

View File

@@ -0,0 +1,135 @@
// Copyright 2015 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 common
import (
"sync"
"golang.org/x/exp/inotify"
)
// Watcher for container-related inotify events in the cgroup hierarchy.
//
// Implementation is thread-safe.
type InotifyWatcher struct {
// Underlying inotify watcher.
watcher *inotify.Watcher
// Map of containers being watched to cgroup paths watched for that container.
containersWatched map[string]map[string]bool
// Lock for all datastructure access.
lock sync.Mutex
}
func NewInotifyWatcher() (*InotifyWatcher, error) {
w, err := inotify.NewWatcher()
if err != nil {
return nil, err
}
return &InotifyWatcher{
watcher: w,
containersWatched: make(map[string]map[string]bool),
}, nil
}
// Add a watch to the specified directory. Returns if the container was already being watched.
func (iw *InotifyWatcher) AddWatch(containerName, dir string) (bool, error) {
iw.lock.Lock()
defer iw.lock.Unlock()
cgroupsWatched, alreadyWatched := iw.containersWatched[containerName]
// Register an inotify notification.
if !cgroupsWatched[dir] {
err := iw.watcher.AddWatch(dir, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE)
if err != nil {
return alreadyWatched, err
}
if cgroupsWatched == nil {
cgroupsWatched = make(map[string]bool)
}
cgroupsWatched[dir] = true
}
// Record our watching of the container.
if !alreadyWatched {
iw.containersWatched[containerName] = cgroupsWatched
}
return alreadyWatched, nil
}
// Remove watch from the specified directory. Returns if this was the last watch on the specified container.
func (iw *InotifyWatcher) RemoveWatch(containerName, dir string) (bool, error) {
iw.lock.Lock()
defer iw.lock.Unlock()
// If we don't have a watch registed for this, just return.
cgroupsWatched, ok := iw.containersWatched[containerName]
if !ok {
return false, nil
}
// Remove the inotify watch if it exists.
if cgroupsWatched[dir] {
err := iw.watcher.RemoveWatch(dir)
if err != nil {
return false, nil
}
delete(cgroupsWatched, dir)
}
// Remove the record if this is the last watch.
if len(cgroupsWatched) == 0 {
delete(iw.containersWatched, containerName)
return true, nil
}
return false, nil
}
// Errors are returned on this channel.
func (iw *InotifyWatcher) Error() chan error {
return iw.watcher.Error
}
// Events are returned on this channel.
func (iw *InotifyWatcher) Event() chan *inotify.Event {
return iw.watcher.Event
}
// Closes the inotify watcher.
func (iw *InotifyWatcher) Close() error {
return iw.watcher.Close()
}
// Returns a map of containers to the cgroup paths being watched.
func (iw *InotifyWatcher) GetWatches() map[string][]string {
out := make(map[string][]string, len(iw.containersWatched))
for k, v := range iw.containersWatched {
out[k] = mapToSlice(v)
}
return out
}
func mapToSlice(m map[string]bool) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}

View File

@@ -0,0 +1,40 @@
{
"name": "Container Hints",
"description": "Container hints file",
"all_hosts": [
{
"network_interface": {
"veth_child": "eth1",
"veth_host": "veth24031eth1"
},
"mounts": [
{
"host_dir": "/var/run/nm-sdc1",
"container_dir": "/var/run/nm-sdc1",
"permission": "rw"
},
{
"host_dir": "/var/run/nm-sdb3",
"container_dir": "/var/run/nm-sdb3",
"permission": "rw"
},
{
"host_dir": "/var/run/nm-sda3",
"container_dir": "/var/run/nm-sda3",
"permission": "rw"
},
{
"host_dir": "/var/run/netns/root",
"container_dir": "/var/run/netns/root",
"permission": "ro"
},
{
"host_dir": "/var/run/openvswitch/db.sock",
"container_dir": "/var/run/openvswitch/db.sock",
"permission": "rw"
}
],
"full_path": "18a4585950db428e4d5a65c216a5d708d241254709626f4cb300ee963fb4b144"
}
]
}

View File

@@ -0,0 +1,88 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package container defines types for sub-container events and also
// defines an interface for container operation handlers.
package container
import info "github.com/google/cadvisor/info/v1"
// ListType describes whether listing should be just for a
// specific container or performed recursively.
type ListType int
const (
ListSelf ListType = iota
ListRecursive
)
// SubcontainerEventType indicates an addition or deletion event.
type SubcontainerEventType int
const (
SubcontainerAdd SubcontainerEventType = iota
SubcontainerDelete
)
// 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
ContainerReference() (info.ContainerReference, error)
// Returns container's isolation spec.
GetSpec() (info.ContainerSpec, error)
// Returns the current stats values of the container.
GetStats() (*info.ContainerStats, error)
// 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)
// Returns container labels, if available.
GetContainerLabels() map[string]string
// Returns whether the container still exists.
Exists() bool
// Cleanup frees up any resources being held like fds or go routines, etc.
Cleanup()
// Start starts any necessary background goroutines - must be cleaned up in Cleanup().
// It is expected that most implementations will be a no-op.
Start()
}

View File

@@ -0,0 +1,37 @@
// Copyright 2015 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.
// Handler for /validate content.
// Validates cadvisor dependencies - kernel, os, docker setup.
package docker
import (
"sync"
dclient "github.com/fsouza/go-dockerclient"
)
var (
dockerClient *dclient.Client
dockerClientErr error
once sync.Once
)
func Client() (*dclient.Client, error) {
once.Do(func() {
dockerClient, dockerClientErr = dclient.NewClient(*ArgDockerEndpoint)
})
return dockerClient, dockerClientErr
}

View File

@@ -0,0 +1,222 @@
// 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 docker
import (
"flag"
"fmt"
"path"
"regexp"
"strconv"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
)
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)")
// Regexp that identifies docker cgroups, containers started with
// --cgroup-parent have another prefix than 'docker'
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)
}
const (
dockerRootDirKey = "Root Dir"
)
func RootDir() string {
return *dockerRootDir
}
type storageDriver string
const (
// TODO: Add support for devicemapper storage usage.
devicemapperStorageDriver storageDriver = "devicemapper"
aufsStorageDriver storageDriver = "aufs"
overlayStorageDriver storageDriver = "overlay"
zfsStorageDriver storageDriver = "zfs"
)
type dockerFactory struct {
machineInfoFactory info.MachineInfoFactory
storageDriver storageDriver
storageDir string
client *docker.Client
// Information about the mounted cgroup subsystems.
cgroupSubsystems libcontainer.CgroupSubsystems
// Information about mounted filesystems.
fsInfo fs.FsInfo
dockerVersion []int
ignoreMetrics container.MetricSet
}
func (self *dockerFactory) String() string {
return DockerNamespace
}
func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
client, err := Client()
if err != nil {
return
}
metadataEnvs := strings.Split(*dockerEnvWhitelist, ",")
handler, err = newDockerContainerHandler(
client,
name,
self.machineInfoFactory,
self.fsInfo,
self.storageDriver,
self.storageDir,
&self.cgroupSubsystems,
inHostNamespace,
metadataEnvs,
self.dockerVersion,
self.ignoreMetrics,
)
return
}
// Returns the Docker ID from the full container name.
func ContainerNameToDockerId(name string) string {
id := path.Base(name)
if matches := dockerCgroupRegexp.FindStringSubmatch(id); matches != nil {
return matches[1]
}
return id
}
func isContainerName(name string) bool {
return dockerCgroupRegexp.MatchString(path.Base(name))
}
// Docker handles all containers under /docker
func (self *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// docker factory accepts all containers it can handle.
canAccept := true
if !isContainerName(name) {
return false, canAccept, fmt.Errorf("invalid container name")
}
// Check if the container is known to docker and it is active.
id := ContainerNameToDockerId(name)
// We assume that if Inspect fails then the container is not known to docker.
ctnr, err := self.client.InspectContainer(id)
if err != nil || !ctnr.State.Running {
return false, canAccept, fmt.Errorf("error inspecting container: %v", err)
}
return true, canAccept, nil
}
func (self *dockerFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
var (
version_regexp_string = `(\d+)\.(\d+)\.(\d+)`
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()
if err != nil {
return fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := ValidateInfo()
if err != nil {
return fmt.Errorf("failed to validate Docker info: %v", err)
}
// 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")
f := &dockerFactory{
cgroupSubsystems: cgroupSubsystems,
client: client,
dockerVersion: dockerVersion,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(dockerInfo.Driver),
storageDir: storageDir,
ignoreMetrics: ignoreMetrics,
}
container.RegisterContainerHandlerFactory(f)
return nil
}

View File

@@ -0,0 +1,411 @@
// 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.
// Handler for Docker containers.
package docker
import (
"fmt"
"io/ioutil"
"path"
"strings"
"time"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
docker "github.com/fsouza/go-dockerclient"
"github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
)
const (
// The read write layers exist here.
aufsRWLayer = "diff"
// Path to the directory where docker stores log files if the json logging driver is enabled.
pathToContainersDir = "containers"
)
type dockerContainerHandler struct {
client *docker.Client
name string
id string
aliases []string
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
// Manager of this container's cgroups.
cgroupManager cgroups.Manager
storageDriver storageDriver
fsInfo fs.FsInfo
rootfsStorageDir string
// Time at which this container was created.
creationTime time.Time
// Metadata associated with the container.
labels map[string]string
envs map[string]string
// The container PID used to switch namespaces as required
pid int
// Image name used for this container.
image string
// The host root FS to read
rootFs string
// The network mode of the container
networkMode string
// Filesystem handler.
fsHandler common.FsHandler
ignoreMetrics container.MetricSet
}
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.
randomizedRWLayerMinorVersion = 10
rwLayerIDFile = "mount-id"
)
if (dockerVersion[0] <= 1) && (dockerVersion[1] < randomizedRWLayerMinorVersion) {
return containerID, nil
}
bytes, err := ioutil.ReadFile(path.Join(storageDir, "image", string(sd), "layerdb", "mounts", containerID, rwLayerIDFile))
if err != nil {
return "", fmt.Errorf("failed to identify the read-write layer ID for container %q. - %v", containerID, err)
}
return string(bytes), err
}
func newDockerContainerHandler(
client *docker.Client,
name string,
machineInfoFactory info.MachineInfoFactory,
fsInfo fs.FsInfo,
storageDriver storageDriver,
storageDir string,
cgroupSubsystems *containerlibcontainer.CgroupSubsystems,
inHostNamespace bool,
metadataEnvs []string,
dockerVersion []int,
ignoreMetrics container.MetricSet,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
for key, val := range cgroupSubsystems.MountPoints {
cgroupPaths[key] = path.Join(val, name)
}
// Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{
Cgroups: &libcontainerconfigs.Cgroup{
Name: name,
},
Paths: cgroupPaths,
}
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
storageDir = path.Join(rootFs, storageDir)
}
id := ContainerNameToDockerId(name)
// Add the Containers dir where the log files are stored.
// FIXME: Give `otherStorageDir` a more descriptive name.
otherStorageDir := path.Join(storageDir, pathToContainersDir, id)
rwLayerID, err := getRwLayerID(id, storageDir, storageDriver, dockerVersion)
if err != nil {
return nil, err
}
var rootfsStorageDir string
switch storageDriver {
case aufsStorageDriver:
rootfsStorageDir = path.Join(storageDir, string(aufsStorageDriver), aufsRWLayer, rwLayerID)
case overlayStorageDriver:
rootfsStorageDir = path.Join(storageDir, string(overlayStorageDriver), rwLayerID)
}
handler := &dockerContainerHandler{
id: id,
client: client,
name: name,
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
cgroupManager: cgroupManager,
storageDriver: storageDriver,
fsInfo: fsInfo,
rootFs: rootFs,
rootfsStorageDir: rootfsStorageDir,
envs: make(map[string]string),
ignoreMetrics: ignoreMetrics,
}
if !ignoreMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = common.NewFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo)
}
// We assume that if Inspect fails then the container is not known to docker.
ctnr, err := client.InspectContainer(id)
if err != nil {
return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
}
handler.creationTime = ctnr.Created
handler.pid = ctnr.State.Pid
// Add the name and bare ID as aliases of the container.
handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/"), id)
handler.labels = ctnr.Config.Labels
handler.image = ctnr.Config.Image
handler.networkMode = ctnr.HostConfig.NetworkMode
// split env vars to get metadata map.
for _, exposedEnv := range metadataEnvs {
for _, envVar := range ctnr.Config.Env {
splits := strings.SplitN(envVar, "=", 2)
if splits[0] == exposedEnv {
handler.envs[strings.ToLower(exposedEnv)] = splits[1]
}
}
}
return handler, nil
}
func (self *dockerContainerHandler) Start() {
if self.fsHandler != nil {
self.fsHandler.Start()
}
}
func (self *dockerContainerHandler) Cleanup() {
if self.fsHandler != nil {
self.fsHandler.Stop()
}
}
func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
return info.ContainerReference{
Id: self.id,
Name: self.name,
Aliases: self.aliases,
Namespace: DockerNamespace,
Labels: self.labels,
}, nil
}
func (self *dockerContainerHandler) needNet() bool {
if !self.ignoreMetrics.Has(container.NetworkUsageMetrics) {
return !strings.HasPrefix(self.networkMode, "container:")
}
return false
}
func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
hasFilesystem := !self.ignoreMetrics.Has(container.DiskUsageMetrics)
spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, self.needNet(), hasFilesystem)
spec.Labels = self.labels
spec.Envs = self.envs
spec.Image = self.image
return spec, err
}
func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
if self.ignoreMetrics.Has(container.DiskUsageMetrics) {
return nil
}
switch self.storageDriver {
case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
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
}
var (
limit uint64
fsType string
)
// Docker does not impose any filesystem limits for containers. So use capacity as limit.
for _, fs := range mi.Filesystems {
if fs.Device == deviceInfo.Device {
limit = fs.Capacity
fsType = fs.Type
break
}
}
fsStat := info.FsStats{Device: deviceInfo.Device, Type: fsType, Limit: limit}
fsStat.BaseUsage, fsStat.Usage = self.fsHandler.Usage()
stats.Filesystem = append(stats.Filesystem, fsStat)
return nil
}
// TODO(vmarmol): Get from libcontainer API instead of cgroup manager when we don't have to support older Dockers.
func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
stats, err := containerlibcontainer.GetStats(self.cgroupManager, self.rootFs, self.pid, self.ignoreMetrics)
if err != nil {
return stats, err
}
// Clean up stats for containers that don't have their own network - this
// includes containers running in Kubernetes pods that use the network of the
// infrastructure container. This stops metrics being reported multiple times
// for each container in a pod.
if !self.needNet() {
stats.Network = info.NetworkStats{}
}
// Get filesystem stats.
err = self.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
// No-op for Docker driver.
return []info.ContainerReference{}, nil
}
func (self *dockerContainerHandler) GetCgroupPath(resource string) (string, error) {
path, ok := self.cgroupPaths[resource]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name)
}
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
}
func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
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
}

138
vendor/github.com/google/cadvisor/container/factory.go generated vendored Normal file
View File

@@ -0,0 +1,138 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container
import (
"fmt"
"sync"
"github.com/golang/glog"
)
type ContainerHandlerFactory interface {
// Create a new ContainerHandler using this factory. CanHandleAndAccept() must have returned true.
NewContainerHandler(name string, inHostNamespace bool) (c ContainerHandler, err error)
// Returns whether this factory can handle and accept the specified container.
CanHandleAndAccept(name string) (handle bool, accept bool, err error)
// Name of the factory.
String() string
// Returns debugging information. Map of lines per category.
DebugInfo() map[string][]string
}
// MetricKind represents the kind of metrics that cAdvisor exposes.
type MetricKind string
const (
CpuUsageMetrics MetricKind = "cpu"
MemoryUsageMetrics MetricKind = "memory"
CpuLoadMetrics MetricKind = "cpuLoad"
DiskIOMetrics MetricKind = "diskIO"
DiskUsageMetrics MetricKind = "disk"
NetworkUsageMetrics MetricKind = "network"
NetworkTcpUsageMetrics MetricKind = "tcp"
AppMetrics MetricKind = "app"
)
func (mk MetricKind) String() string {
return string(mk)
}
type MetricSet map[MetricKind]struct{}
func (ms MetricSet) Has(mk MetricKind) bool {
_, exists := ms[mk]
return exists
}
func (ms MetricSet) Add(mk MetricKind) {
ms[mk] = struct{}{}
}
// TODO(vmarmol): Consider not making this global.
// Global list of factories.
var (
factories []ContainerHandlerFactory
factoriesLock sync.RWMutex
)
// Register a ContainerHandlerFactory. These should be registered from least general to most general
// as they will be asked in order whether they can handle a particular container.
func RegisterContainerHandlerFactory(factory ContainerHandlerFactory) {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = append(factories, factory)
}
// Returns whether there are any container handler factories registered.
func HasFactories() bool {
factoriesLock.Lock()
defer factoriesLock.Unlock()
return len(factories) != 0
}
// Create a new ContainerHandler for the specified container.
func NewContainerHandler(name string, inHostNamespace bool) (ContainerHandler, bool, error) {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Create the ContainerHandler with the first factory that supports it.
for _, factory := range factories {
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)
}
if canHandle {
if !canAccept {
glog.V(3).Infof("Factory %q can handle container %q, but ignoring.", factory, name)
return nil, false, nil
}
glog.V(3).Infof("Using factory %q for container %q", factory, name)
handle, err := factory.NewContainerHandler(name, inHostNamespace)
return handle, canAccept, err
} else {
glog.V(4).Infof("Factory %q was unable to handle container %q", factory, name)
}
}
return nil, false, fmt.Errorf("no known factory can handle creation of container")
}
// Clear the known factories.
func ClearContainerHandlerFactories() {
factoriesLock.Lock()
defer factoriesLock.Unlock()
factories = make([]ContainerHandlerFactory, 0, 4)
}
func DebugInfo() map[string][]string {
factoriesLock.RLock()
defer factoriesLock.RUnlock()
// Get debug information for all factories.
out := make(map[string][]string)
for _, factory := range factories {
for k, v := range factory.DebugInfo() {
out[k] = v
}
}
return out
}

View File

@@ -0,0 +1,449 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package libcontainer
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/google/cadvisor/container"
info "github.com/google/cadvisor/info/v1"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups"
)
type CgroupSubsystems struct {
// Cgroup subsystem mounts.
// e.g.: "/sys/fs/cgroup/cpu" -> ["cpu", "cpuacct"]
Mounts []cgroups.Mount
// Cgroup subsystem to their mount location.
// e.g.: "cpu" -> "/sys/fs/cgroup/cpu"
MountPoints map[string]string
}
// Get information about the cgroup subsystems.
func GetCgroupSubsystems() (CgroupSubsystems, error) {
// Get all cgroup mounts.
allCgroups, err := cgroups.GetCgroupMounts()
if err != nil {
return CgroupSubsystems{}, err
}
if len(allCgroups) == 0 {
return CgroupSubsystems{}, fmt.Errorf("failed to find cgroup mounts")
}
// Trim the mounts to only the subsystems we care about.
supportedCgroups := make([]cgroups.Mount, 0, len(allCgroups))
mountPoints := make(map[string]string, len(allCgroups))
for _, mount := range allCgroups {
for _, subsystem := range mount.Subsystems {
if _, ok := supportedSubsystems[subsystem]; ok {
supportedCgroups = append(supportedCgroups, mount)
mountPoints[subsystem] = mount.Mountpoint
}
}
}
return CgroupSubsystems{
Mounts: supportedCgroups,
MountPoints: mountPoints,
}, nil
}
// Cgroup subsystems we support listing (should be the minimal set we need stats from).
var supportedSubsystems map[string]struct{} = map[string]struct{}{
"cpu": {},
"cpuacct": {},
"memory": {},
"cpuset": {},
"blkio": {},
}
// Get cgroup and networking stats of the specified container
func GetStats(cgroupManager cgroups.Manager, rootFs string, pid int, ignoreMetrics container.MetricSet) (*info.ContainerStats, error) {
cgroupStats, err := cgroupManager.GetStats()
if err != nil {
return nil, err
}
libcontainerStats := &libcontainer.Stats{
CgroupStats: cgroupStats,
}
stats := toContainerStats(libcontainerStats)
// If we know the pid then get network stats from /proc/<pid>/net/dev
if pid == 0 {
return stats, nil
}
if !ignoreMetrics.Has(container.NetworkUsageMetrics) {
netStats, err := networkStatsFromProc(rootFs, pid)
if err != nil {
glog.V(2).Infof("Unable to get network stats from pid %d: %v", pid, err)
} else {
stats.Network.Interfaces = append(stats.Network.Interfaces, netStats...)
}
}
if !ignoreMetrics.Has(container.NetworkTcpUsageMetrics) {
t, err := tcpStatsFromProc(rootFs, pid, "net/tcp")
if err != nil {
glog.V(2).Infof("Unable to get tcp stats from pid %d: %v", pid, err)
} else {
stats.Network.Tcp = t
}
t6, err := tcpStatsFromProc(rootFs, pid, "net/tcp6")
if err != nil {
glog.V(2).Infof("Unable to get tcp6 stats from pid %d: %v", pid, err)
} else {
stats.Network.Tcp6 = t6
}
}
// For backwards compatibility.
if len(stats.Network.Interfaces) > 0 {
stats.Network.InterfaceStats = stats.Network.Interfaces[0]
}
return stats, nil
}
func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) {
netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev")
ifaceStats, err := scanInterfaceStats(netStatsFile)
if err != nil {
return []info.InterfaceStats{}, fmt.Errorf("couldn't read network stats: %v", err)
}
return ifaceStats, nil
}
var (
ignoredDevicePrefixes = []string{"lo", "veth", "docker"}
)
func isIgnoredDevice(ifName string) bool {
for _, prefix := range ignoredDevicePrefixes {
if strings.HasPrefix(strings.ToLower(ifName), prefix) {
return true
}
}
return false
}
func scanInterfaceStats(netStatsFile string) ([]info.InterfaceStats, error) {
file, err := os.Open(netStatsFile)
if err != nil {
return nil, fmt.Errorf("failure opening %s: %v", netStatsFile, err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
// Discard header lines
for i := 0; i < 2; i++ {
if b := scanner.Scan(); !b {
return nil, scanner.Err()
}
}
stats := []info.InterfaceStats{}
for scanner.Scan() {
line := scanner.Text()
line = strings.Replace(line, ":", "", -1)
fields := strings.Fields(line)
// If the format of the line is invalid then don't trust any of the stats
// in this file.
if len(fields) != 17 {
return nil, fmt.Errorf("invalid interface stats line: %v", line)
}
devName := fields[0]
if isIgnoredDevice(devName) {
continue
}
i := info.InterfaceStats{
Name: devName,
}
statFields := append(fields[1:5], fields[9:13]...)
statPointers := []*uint64{
&i.RxBytes, &i.RxPackets, &i.RxErrors, &i.RxDropped,
&i.TxBytes, &i.TxPackets, &i.TxErrors, &i.TxDropped,
}
err := setInterfaceStatValues(statFields, statPointers)
if err != nil {
return nil, fmt.Errorf("cannot parse interface stats (%v): %v", err, line)
}
stats = append(stats, i)
}
return stats, nil
}
func setInterfaceStatValues(fields []string, pointers []*uint64) error {
for i, v := range fields {
val, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return err
}
*pointers[i] = val
}
return nil
}
func tcpStatsFromProc(rootFs string, pid int, file string) (info.TcpStat, error) {
tcpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
tcpStats, err := scanTcpStats(tcpStatsFile)
if err != nil {
return tcpStats, fmt.Errorf("couldn't read tcp stats: %v", err)
}
return tcpStats, nil
}
func scanTcpStats(tcpStatsFile string) (info.TcpStat, error) {
var stats info.TcpStat
data, err := ioutil.ReadFile(tcpStatsFile)
if err != nil {
return stats, fmt.Errorf("failure opening %s: %v", tcpStatsFile, err)
}
tcpStateMap := map[string]uint64{
"01": 0, //ESTABLISHED
"02": 0, //SYN_SENT
"03": 0, //SYN_RECV
"04": 0, //FIN_WAIT1
"05": 0, //FIN_WAIT2
"06": 0, //TIME_WAIT
"07": 0, //CLOSE
"08": 0, //CLOSE_WAIT
"09": 0, //LAST_ACK
"0A": 0, //LISTEN
"0B": 0, //CLOSING
}
reader := strings.NewReader(string(data))
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
// Discard header line
if b := scanner.Scan(); !b {
return stats, scanner.Err()
}
for scanner.Scan() {
line := scanner.Text()
state := strings.Fields(line)
// TCP state is the 4th field.
// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
tcpState := state[3]
_, ok := tcpStateMap[tcpState]
if !ok {
return stats, fmt.Errorf("invalid TCP stats line: %v", line)
}
tcpStateMap[tcpState]++
}
stats = info.TcpStat{
Established: tcpStateMap["01"],
SynSent: tcpStateMap["02"],
SynRecv: tcpStateMap["03"],
FinWait1: tcpStateMap["04"],
FinWait2: tcpStateMap["05"],
TimeWait: tcpStateMap["06"],
Close: tcpStateMap["07"],
CloseWait: tcpStateMap["08"],
LastAck: tcpStateMap["09"],
Listen: tcpStateMap["0A"],
Closing: tcpStateMap["0B"],
}
return stats, nil
}
func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) {
pids, err := cgroupManager.GetPids()
if err != nil {
return nil, err
}
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,
Minor: minor,
}
disk.Stats = make(map[string]uint64)
return &disk
}
type DiskKey struct {
Major uint64
Minor uint64
}
func DiskStatsCopy1(disk_stat map[DiskKey]*info.PerDiskStats) []info.PerDiskStats {
i := 0
stat := make([]info.PerDiskStats, len(disk_stat))
for _, disk := range disk_stat {
stat[i] = *disk
i++
}
return stat
}
func DiskStatsCopy(blkio_stats []cgroups.BlkioStatEntry) (stat []info.PerDiskStats) {
if len(blkio_stats) == 0 {
return
}
disk_stat := make(map[DiskKey]*info.PerDiskStats)
for i := range blkio_stats {
major := blkio_stats[i].Major
minor := blkio_stats[i].Minor
disk_key := DiskKey{
Major: major,
Minor: minor,
}
diskp, ok := disk_stat[disk_key]
if !ok {
diskp = DiskStatsCopy0(major, minor)
disk_stat[disk_key] = diskp
}
op := blkio_stats[i].Op
if op == "" {
op = "Count"
}
diskp.Stats[op] = blkio_stats[i].Value
}
return DiskStatsCopy1(disk_stat)
}
// Convert libcontainer stats to info.ContainerStats.
func toContainerStats0(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
n := len(s.CpuStats.CpuUsage.PercpuUsage)
ret.Cpu.Usage.PerCpu = make([]uint64, n)
ret.Cpu.Usage.Total = 0
for i := 0; i < n; i++ {
ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i]
ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i]
}
}
func toContainerStats1(s *cgroups.Stats, ret *info.ContainerStats) {
ret.DiskIo.IoServiceBytes = DiskStatsCopy(s.BlkioStats.IoServiceBytesRecursive)
ret.DiskIo.IoServiced = DiskStatsCopy(s.BlkioStats.IoServicedRecursive)
ret.DiskIo.IoQueued = DiskStatsCopy(s.BlkioStats.IoQueuedRecursive)
ret.DiskIo.Sectors = DiskStatsCopy(s.BlkioStats.SectorsRecursive)
ret.DiskIo.IoServiceTime = DiskStatsCopy(s.BlkioStats.IoServiceTimeRecursive)
ret.DiskIo.IoWaitTime = DiskStatsCopy(s.BlkioStats.IoWaitTimeRecursive)
ret.DiskIo.IoMerged = DiskStatsCopy(s.BlkioStats.IoMergedRecursive)
ret.DiskIo.IoTime = DiskStatsCopy(s.BlkioStats.IoTimeRecursive)
}
func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
}
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
ret.Memory.ContainerData.Pgmajfault = v
ret.Memory.HierarchicalData.Pgmajfault = v
}
if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok {
workingSet := ret.Memory.Usage
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
if v, ok := s.MemoryStats.Stats["total_inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
ret.Memory.WorkingSet = workingSet
}
}
func toContainerStats3(libcontainerStats *libcontainer.Stats, ret *info.ContainerStats) {
ret.Network.Interfaces = make([]info.InterfaceStats, len(libcontainerStats.Interfaces))
for i := range libcontainerStats.Interfaces {
ret.Network.Interfaces[i] = info.InterfaceStats{
Name: libcontainerStats.Interfaces[i].Name,
RxBytes: libcontainerStats.Interfaces[i].RxBytes,
RxPackets: libcontainerStats.Interfaces[i].RxPackets,
RxErrors: libcontainerStats.Interfaces[i].RxErrors,
RxDropped: libcontainerStats.Interfaces[i].RxDropped,
TxBytes: libcontainerStats.Interfaces[i].TxBytes,
TxPackets: libcontainerStats.Interfaces[i].TxPackets,
TxErrors: libcontainerStats.Interfaces[i].TxErrors,
TxDropped: libcontainerStats.Interfaces[i].TxDropped,
}
}
// Add to base struct for backwards compatibility.
if len(ret.Network.Interfaces) > 0 {
ret.Network.InterfaceStats = ret.Network.Interfaces[0]
}
}
func toContainerStats(libcontainerStats *libcontainer.Stats) *info.ContainerStats {
s := libcontainerStats.CgroupStats
ret := new(info.ContainerStats)
ret.Timestamp = time.Now()
if s != nil {
toContainerStats0(s, ret)
toContainerStats1(s, ret)
toContainerStats2(s, ret)
}
if len(libcontainerStats.Interfaces) > 0 {
toContainerStats3(libcontainerStats, ret)
}
return ret
}

129
vendor/github.com/google/cadvisor/container/mock.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
// 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 container
import (
info "github.com/google/cadvisor/info/v1"
"github.com/stretchr/testify/mock"
)
// This struct mocks a container handler.
type MockContainerHandler struct {
mock.Mock
Name string
Aliases []string
}
func NewMockContainerHandler(containerName string) *MockContainerHandler {
return &MockContainerHandler{
Name: containerName,
}
}
// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases.
// Otherwise, it will use the value provided by .On().Return().
func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) {
if len(self.Name) > 0 {
var aliases []string
if len(self.Aliases) > 0 {
aliases = make([]string, len(self.Aliases))
copy(aliases, self.Aliases)
}
return info.ContainerReference{
Name: self.Name,
Aliases: aliases,
}, nil
}
args := self.Called()
return args.Get(0).(info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) Start() {}
func (self *MockContainerHandler) Cleanup() {}
func (self *MockContainerHandler) GetSpec() (info.ContainerSpec, error) {
args := self.Called()
return args.Get(0).(info.ContainerSpec), args.Error(1)
}
func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) {
args := self.Called()
return args.Get(0).(*info.ContainerStats), args.Error(1)
}
func (self *MockContainerHandler) ListContainers(listType ListType) ([]info.ContainerReference, error) {
args := self.Called(listType)
return args.Get(0).([]info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) ListThreads(listType ListType) ([]int, error) {
args := self.Called(listType)
return args.Get(0).([]int), args.Error(1)
}
func (self *MockContainerHandler) ListProcesses(listType ListType) ([]int, error) {
args := self.Called(listType)
return args.Get(0).([]int), args.Error(1)
}
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)
}
func (self *MockContainerHandler) GetCgroupPath(path string) (string, error) {
args := self.Called(path)
return args.Get(0).(string), args.Error(1)
}
func (self *MockContainerHandler) GetContainerLabels() map[string]string {
args := self.Called()
return args.Get(0).(map[string]string)
}
type FactoryForMockContainerHandler struct {
Name string
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)
}
func (self *FactoryForMockContainerHandler) String() string {
return self.Name
}
func (self *FactoryForMockContainerHandler) NewContainerHandler(name string, inHostNamespace bool) (ContainerHandler, error) {
handler := &MockContainerHandler{}
if self.PrepareContainerHandlerFunc != nil {
self.PrepareContainerHandlerFunc(name, handler)
}
return handler, nil
}
func (self *FactoryForMockContainerHandler) CanHandle(name string) bool {
return true
}

View File

@@ -0,0 +1,95 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package raw
import (
"flag"
"fmt"
"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/golang/glog"
)
var dockerOnly = flag.Bool("docker_only", false, "Only report docker containers in addition to root stats")
type rawFactory struct {
// Factory for machine information.
machineInfoFactory info.MachineInfoFactory
// Information about the cgroup subsystems.
cgroupSubsystems *libcontainer.CgroupSubsystems
// Information about mounted filesystems.
fsInfo fs.FsInfo
// Watcher for inotify events.
watcher *common.InotifyWatcher
// List of metrics to be ignored.
ignoreMetrics map[container.MetricKind]struct{}
}
func (self *rawFactory) String() string {
return "raw"
}
func (self *rawFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) {
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, self.watcher, rootFs, self.ignoreMetrics)
}
// The raw factory can handle any container. If --docker_only is set to false, non-docker containers are ignored.
func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) {
accept := name == "/" || !*dockerOnly
return true, accept, nil
}
func (self *rawFactory) DebugInfo() map[string][]string {
return common.DebugInfo(self.watcher.GetWatches())
}
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics map[container.MetricKind]struct{}) error {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
if len(cgroupSubsystems.Mounts) == 0 {
return fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
watcher, err := common.NewInotifyWatcher()
if err != nil {
return err
}
glog.Infof("Registering Raw factory")
factory := &rawFactory{
machineInfoFactory: machineInfoFactory,
fsInfo: fsInfo,
cgroupSubsystems: &cgroupSubsystems,
watcher: watcher,
ignoreMetrics: ignoreMetrics,
}
container.RegisterContainerHandlerFactory(factory)
return nil
}

View File

@@ -0,0 +1,447 @@
// 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.
// Handler for "raw" containers.
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/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 {
// Name of the container for this handler.
name string
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
// Manager of this container's cgroups.
cgroupManager cgroups.Manager
fsInfo fs.FsInfo
externalMounts []common.Mount
rootFs string
// Metrics to be ignored.
ignoreMetrics container.MetricSet
pid int
}
func isRootCgroup(name string) bool {
return name == "/"
}
func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *common.InotifyWatcher, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) {
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
cHints, err := common.GetContainerHintsFromFile(*common.ArgContainerHints)
if err != nil {
return nil, err
}
// Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{
Cgroups: &configs.Cgroup{
Name: name,
},
Paths: cgroupPaths,
}
var externalMounts []common.Mount
for _, container := range cHints.AllHosts {
if name == container.FullName {
externalMounts = container.Mounts
break
}
}
pid := 0
if isRootCgroup(name) {
pid = 1
}
return &rawContainerHandler{
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,
}, nil
}
func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, error) {
// We only know the container by its one name.
return info.ContainerReference{
Name: self.name,
}, nil
}
func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error) {
nd := []info.NetInfo{}
if isRootCgroup(self.name) {
mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil {
return nd, err
}
return mi.NetworkDevices, nil
}
return nd, nil
}
// Nothing to start up.
func (self *rawContainerHandler) Start() {}
// Nothing to clean up.
func (self *rawContainerHandler) Cleanup() {}
func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) {
const hasNetwork = false
hasFilesystem := isRootCgroup(self.name) || len(self.externalMounts) > 0
spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, hasNetwork, hasFilesystem)
if err != nil {
return spec, err
}
if isRootCgroup(self.name) {
// Check physical network devices for root container.
nd, err := self.GetRootNetworkDevices()
if err != nil {
return spec, err
}
spec.HasNetwork = spec.HasNetwork || len(nd) != 0
// Get memory and swap limits of the running machine
memLimit, err := machine.GetMachineMemoryCapacity()
if err != nil {
glog.Warningf("failed to obtain memory limit for machine container")
spec.HasMemory = false
} else {
spec.Memory.Limit = uint64(memLimit)
// Spec is marked to have memory only if the memory limit is set
spec.HasMemory = true
}
swapLimit, err := machine.GetMachineSwapCapacity()
if err != nil {
glog.Warningf("failed to obtain swap limit for machine container")
} else {
spec.Memory.SwapLimit = uint64(swapLimit)
}
}
return spec, nil
}
func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error {
// Get Filesystem information only for the root cgroup.
if isRootCgroup(self.name) {
filesystems, err := self.fsInfo.GetGlobalFsInfo()
if err != nil {
return err
}
for _, fs := range filesystems {
stats.Filesystem = append(stats.Filesystem,
info.FsStats{
Device: fs.Device,
Type: fs.Type.String(),
Limit: fs.Capacity,
Usage: fs.Capacity - fs.Free,
Available: fs.Available,
InodesFree: fs.InodesFree,
ReadsCompleted: fs.DiskStats.ReadsCompleted,
ReadsMerged: fs.DiskStats.ReadsMerged,
SectorsRead: fs.DiskStats.SectorsRead,
ReadTime: fs.DiskStats.ReadTime,
WritesCompleted: fs.DiskStats.WritesCompleted,
WritesMerged: fs.DiskStats.WritesMerged,
SectorsWritten: fs.DiskStats.SectorsWritten,
WriteTime: fs.DiskStats.WriteTime,
IoInProgress: fs.DiskStats.IoInProgress,
IoTime: fs.DiskStats.IoTime,
WeightedIoTime: fs.DiskStats.WeightedIoTime,
})
}
} else if len(self.externalMounts) > 0 {
var mountSet map[string]struct{}
mountSet = make(map[string]struct{})
for _, mount := range self.externalMounts {
mountSet[mount.HostDir] = struct{}{}
}
filesystems, err := self.fsInfo.GetFsInfoForPath(mountSet)
if err != nil {
return err
}
for _, fs := range filesystems {
stats.Filesystem = append(stats.Filesystem,
info.FsStats{
Device: fs.Device,
Type: fs.Type.String(),
Limit: fs.Capacity,
Usage: fs.Capacity - fs.Free,
InodesFree: fs.InodesFree,
ReadsCompleted: fs.DiskStats.ReadsCompleted,
ReadsMerged: fs.DiskStats.ReadsMerged,
SectorsRead: fs.DiskStats.SectorsRead,
ReadTime: fs.DiskStats.ReadTime,
WritesCompleted: fs.DiskStats.WritesCompleted,
WritesMerged: fs.DiskStats.WritesMerged,
SectorsWritten: fs.DiskStats.SectorsWritten,
WriteTime: fs.DiskStats.WriteTime,
IoInProgress: fs.DiskStats.IoInProgress,
IoTime: fs.DiskStats.IoTime,
WeightedIoTime: fs.DiskStats.WeightedIoTime,
})
}
}
return nil
}
func (self *rawContainerHandler) GetStats() (*info.ContainerStats, error) {
stats, err := libcontainer.GetStats(self.cgroupManager, self.rootFs, self.pid, self.ignoreMetrics)
if err != nil {
return stats, err
}
// Get filesystem stats.
err = self.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (self *rawContainerHandler) GetCgroupPath(resource string) (string, error) {
path, ok := self.cgroupPaths[resource]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name)
}
return path, nil
}
func (self *rawContainerHandler) GetContainerLabels() map[string]string {
return 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
}
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)
}

View File

@@ -0,0 +1,76 @@
// 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
import (
"fmt"
"net"
"sync"
"time"
rktapi "github.com/coreos/rkt/api/v1alpha"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
defaultRktAPIServiceAddr = "localhost:15441"
timeout = 2 * time.Second
)
var (
rktClient rktapi.PublicAPIClient
rktClientErr error
once sync.Once
)
func Client() (rktapi.PublicAPIClient, error) {
once.Do(func() {
conn, err := net.DialTimeout("tcp", defaultRktAPIServiceAddr, timeout)
if err != nil {
rktClient = nil
rktClientErr = fmt.Errorf("rkt: cannot tcp Dial rkt api service: %v", err)
return
}
conn.Close()
apisvcConn, err := grpc.Dial(defaultRktAPIServiceAddr, grpc.WithInsecure(), grpc.WithTimeout(timeout))
if err != nil {
rktClient = nil
rktClientErr = fmt.Errorf("rkt: cannot grpc Dial rkt api service: %v", err)
return
}
rktClient = rktapi.NewPublicAPIClient(apisvcConn)
})
return rktClient, rktClientErr
}
func RktPath() (string, error) {
client, err := Client()
if err != nil {
return "", err
}
resp, err := client.GetInfo(context.Background(), &rktapi.GetInfoRequest{})
if err != nil {
return "", fmt.Errorf("couldn't GetInfo from rkt api service: %v", err)
}
return resp.Info.GlobalFlags.Dir, nil
}

View File

@@ -0,0 +1,104 @@
// 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
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/golang/glog"
)
const RktNamespace = "rkt"
type rktFactory struct {
machineInfoFactory info.MachineInfoFactory
cgroupSubsystems *libcontainer.CgroupSubsystems
fsInfo fs.FsInfo
ignoreMetrics container.MetricSet
rktPath string
}
func (self *rktFactory) String() string {
return "rkt"
}
func (self *rktFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) {
client, err := Client()
if err != nil {
return nil, err
}
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
return newRktContainerHandler(name, client, self.rktPath, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, rootFs, self.ignoreMetrics)
}
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
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)
}
func (self *rktFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
_, err := Client()
if err != nil {
return fmt.Errorf("unable to communicate with Rkt api service: %v", err)
}
rktPath, err := RktPath()
if err != nil {
return fmt.Errorf("unable to get the RktPath variable %v", err)
}
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
if len(cgroupSubsystems.Mounts) == 0 {
return fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
}
glog.Infof("Registering Rkt factory")
factory := &rktFactory{
machineInfoFactory: machineInfoFactory,
fsInfo: fsInfo,
cgroupSubsystems: &cgroupSubsystems,
ignoreMetrics: ignoreMetrics,
rktPath: rktPath,
}
container.RegisterContainerHandlerFactory(factory)
return nil
}

View File

@@ -0,0 +1,327 @@
// 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.
// Handler for "rkt" containers.
package rkt
import (
"fmt"
"os"
"path"
"time"
rktapi "github.com/coreos/rkt/api/v1alpha"
"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"
"golang.org/x/net/context"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/configs"
)
type rktContainerHandler struct {
rktClient rktapi.PublicAPIClient
// Name of the container for this handler.
name string
cgroupSubsystems *libcontainer.CgroupSubsystems
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
// Manager of this container's cgroups.
cgroupManager cgroups.Manager
// Whether this container has network isolation enabled.
hasNetwork bool
fsInfo fs.FsInfo
rootFs string
isPod bool
aliases []string
pid int
rootfsStorageDir string
labels map[string]string
// Filesystem handler.
fsHandler common.FsHandler
ignoreMetrics container.MetricSet
apiPod *rktapi.Pod
}
func newRktContainerHandler(name string, rktClient rktapi.PublicAPIClient, rktPath string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) {
aliases := make([]string, 1)
isPod := false
apiPod := &rktapi.Pod{}
parsed, err := parseName(name)
if err != nil {
return nil, fmt.Errorf("this should be impossible!, new handler failing, but factory allowed, name = %s", name)
}
//rktnetes uses containerID: rkt://fff40827-b994-4e3a-8f88-6427c2c8a5ac:nginx
if parsed.Container == "" {
isPod = true
aliases = append(aliases, "rkt://"+parsed.Pod)
} else {
aliases = append(aliases, "rkt://"+parsed.Pod+":"+parsed.Container)
}
pid := os.Getpid()
labels := make(map[string]string)
resp, err := rktClient.InspectPod(context.Background(), &rktapi.InspectPodRequest{
Id: parsed.Pod,
})
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)
}
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
// Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{
Cgroups: &configs.Cgroup{
Name: name,
},
Paths: cgroupPaths,
}
hasNetwork := false
if isPod {
hasNetwork = true
}
rootfsStorageDir := getRootFs(rktPath, parsed)
handler := &rktContainerHandler{
name: name,
rktClient: rktClient,
cgroupSubsystems: cgroupSubsystems,
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
cgroupManager: cgroupManager,
fsInfo: fsInfo,
hasNetwork: hasNetwork,
rootFs: rootFs,
isPod: isPod,
aliases: aliases,
pid: pid,
labels: labels,
rootfsStorageDir: rootfsStorageDir,
ignoreMetrics: ignoreMetrics,
apiPod: apiPod,
}
if !ignoreMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = common.NewFsHandler(time.Minute, rootfsStorageDir, "", fsInfo)
}
return handler, nil
}
func findAnnotations(apps []*rktapi.App, container string) ([]*rktapi.KeyValue, bool) {
for _, app := range apps {
if app.Name == container {
return app.Annotations, true
}
}
return nil, false
}
func createLabels(annotations []*rktapi.KeyValue) map[string]string {
labels := make(map[string]string)
for _, kv := range annotations {
labels[kv.Key] = kv.Value
}
return labels
}
func (handler *rktContainerHandler) ContainerReference() (info.ContainerReference, error) {
return info.ContainerReference{
Name: handler.name,
Aliases: handler.aliases,
Namespace: RktNamespace,
Labels: handler.labels,
}, nil
}
func (handler *rktContainerHandler) Start() {
handler.fsHandler.Start()
}
func (handler *rktContainerHandler) Cleanup() {
handler.fsHandler.Stop()
}
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)
}
func (handler *rktContainerHandler) getFsStats(stats *info.ContainerStats) error {
if handler.ignoreMetrics.Has(container.DiskUsageMetrics) {
return nil
}
deviceInfo, err := handler.fsInfo.GetDirFsDevice(handler.rootfsStorageDir)
if err != nil {
return err
}
mi, err := handler.machineInfoFactory.GetMachineInfo()
if err != nil {
return err
}
var limit uint64 = 0
// Use capacity as limit.
for _, fs := range mi.Filesystems {
if fs.Device == deviceInfo.Device {
limit = fs.Capacity
break
}
}
fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}
fsStat.BaseUsage, fsStat.Usage = handler.fsHandler.Usage()
stats.Filesystem = append(stats.Filesystem, fsStat)
return nil
}
func (handler *rktContainerHandler) GetStats() (*info.ContainerStats, error) {
stats, err := libcontainer.GetStats(handler.cgroupManager, handler.rootFs, handler.pid, handler.ignoreMetrics)
if err != nil {
return stats, err
}
// Get filesystem stats.
err = handler.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (handler *rktContainerHandler) GetCgroupPath(resource string) (string, error) {
path, ok := handler.cgroupPaths[resource]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, handler.name)
}
return path, nil
}
func (handler *rktContainerHandler) GetContainerLabels() map[string]string {
return handler.labels
}
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
}
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)
}

View File

@@ -0,0 +1,93 @@
// 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
import (
"fmt"
"io/ioutil"
"path"
"strings"
"github.com/golang/glog"
)
type parsedName struct {
Pod string
Container string
}
func verifyName(name string) (bool, error) {
_, err := parseName(name)
return err == nil, err
}
/* 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
*/
//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{}
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
}
}
}
return nil, fmt.Errorf("%s not handled by rkt handler", name)
}
// Gets a Rkt container's overlay upper dir
func getRootFs(root string, parsed *parsedName) string {
/* Example of where it stores the upper dir key
for container
/var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/appsinfo/alpine-sh/treeStoreID
for pod
/var/lib/rkt/pods/run/f556b64a-17a7-47d7-93ec-ef2275c3d67e/stage1TreeStoreID
*/
var tree string
if parsed.Container == "" {
tree = path.Join(root, "pods/run", parsed.Pod, "stage1TreeStoreID")
} else {
tree = path.Join(root, "pods/run", parsed.Pod, "appsinfo", parsed.Container, "treeStoreID")
}
bytes, err := ioutil.ReadFile(tree)
if err != nil {
glog.Infof("ReadFile failed, couldn't read %v to get upper dir: %v", tree, err)
return ""
}
s := string(bytes)
/* Example of where the upper dir is stored via key read above
/var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/overlay/deps-sha512-82a099e560a596662b15dec835e9adabab539cad1f41776a30195a01a8f2f22b/
*/
return path.Join(root, "pods/run", parsed.Pod, "overlay", s)
}

View File

@@ -0,0 +1,57 @@
// 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 systemd
import (
"fmt"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/golang/glog"
)
type systemdFactory struct{}
func (f *systemdFactory) String() string {
return "systemd"
}
func (f *systemdFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) {
return nil, fmt.Errorf("Not yet supported")
}
func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// on systemd using devicemapper each mount into the container has an associated cgroup that we ignore.
// for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html
if strings.HasSuffix(name, ".mount") {
return true, false, nil
}
return false, false, fmt.Errorf("%s not handled by systemd handler", name)
}
func (f *systemdFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
// Register registers the systemd container factory.
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
glog.Infof("Registering systemd factory")
factory := &systemdFactory{}
container.RegisterContainerHandlerFactory(factory)
return nil
}