From 76ec7acf919dd3b6cac183a74af1216cd5011e21 Mon Sep 17 00:00:00 2001 From: Dawn Chen Date: Wed, 30 Jul 2014 15:38:03 -0700 Subject: [PATCH 1/2] bump(github.com/google/cadvisor): bdd574b728e8a1e5eb08649d631197620dd09650 --- pkg/kubelet/kubelet.go | 2 +- .../github.com/google/cadvisor/CHANGELOG.md | 15 + .../src/github.com/google/cadvisor/README.md | 7 +- .../cadvisor/advice/interference/detector.go | 2 +- .../github.com/google/cadvisor/api/handler.go | 5 + .../github.com/google/cadvisor/cadvisor.go | 38 +-- .../cadvisor/container/docker/factory.go | 54 +++- .../cadvisor/container/docker/handler.go | 249 +++++++-------- .../google/cadvisor/container/factory.go | 129 +++----- .../google/cadvisor/container/factory_test.go | 110 +++++-- .../google/cadvisor/container/filter_test.go | 12 +- .../container/libcontainer/helpers.go | 135 +++++++++ .../container/libcontainer/helpers_test.go | 90 ++++++ .../google/cadvisor/container/mock.go | 91 ++++++ .../google/cadvisor/container/raw/factory.go | 96 ++++++ .../google/cadvisor/container/raw/handler.go | 206 +++++++++++++ .../google/cadvisor/deploy/Dockerfile | 10 + .../google/cadvisor/deploy/prepare.sh | 7 + .../google/cadvisor/info/container.go | 58 ++-- .../google/cadvisor/info/test/datagen.go | 12 +- .../google/cadvisor/info/version.go | 2 +- .../google/cadvisor/manager/container_test.go | 30 +- .../google/cadvisor/manager/manager.go | 15 +- .../google/cadvisor/manager/manager_test.go | 107 +------ .../google/cadvisor/pages/containers.go | 77 ++++- .../google/cadvisor/pages/containers_html.go | 283 +++++++++--------- .../cadvisor/pages/static/containers_js.go | 55 +++- .../google/cadvisor/storage/cache/memcache.go | 72 +++++ .../cadvisor/storage/cache/memcache_test.go | 78 +++++ .../cadvisor/storage/influxdb/influxdb.go | 8 +- .../storage/influxdb/influxdb_test.go | 24 +- .../cadvisor/storage/memory/memory_test.go | 8 + .../cadvisor/storage/test/storagetests.go | 58 ++++ .../github.com/google/cadvisor/utils/fs/fs.go | 44 +++ .../cadvisor/utils/fs/mockfs/fakefile.go | 35 +++ .../google/cadvisor/utils/fs/mockfs/mockfs.go | 55 ++++ .../github.com/google/cadvisor/utils/path.go | 24 ++ .../google/cadvisor/utils/procfs/doc.go | 17 ++ .../google/cadvisor/utils/procfs/jiffy.go | 33 ++ .../cadvisor/utils/procfs/schedstats.go | 87 ++++++ .../cadvisor/utils/procfs/schedstats_test.go | 57 ++++ 41 files changed, 1870 insertions(+), 627 deletions(-) create mode 100644 third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go create mode 100644 third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go create mode 100644 third_party/src/github.com/google/cadvisor/container/mock.go create mode 100644 third_party/src/github.com/google/cadvisor/container/raw/factory.go create mode 100644 third_party/src/github.com/google/cadvisor/container/raw/handler.go create mode 100644 third_party/src/github.com/google/cadvisor/deploy/Dockerfile create mode 100644 third_party/src/github.com/google/cadvisor/deploy/prepare.sh create mode 100644 third_party/src/github.com/google/cadvisor/storage/cache/memcache.go create mode 100644 third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/fs/fs.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/path.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/procfs/doc.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go create mode 100644 third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index abd2ace18cf..a756b8a1b95 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -289,7 +289,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v ExposedPorts: exposedPorts, Hostname: container.Name, Image: container.Image, - Memory: int64(container.Memory), + Memory: uint64(container.Memory), CpuShares: int64(milliCPUToShares(container.CPU)), Volumes: volumes, WorkingDir: container.WorkingDir, diff --git a/third_party/src/github.com/google/cadvisor/CHANGELOG.md b/third_party/src/github.com/google/cadvisor/CHANGELOG.md index 423ee7823df..6261bed254a 100644 --- a/third_party/src/github.com/google/cadvisor/CHANGELOG.md +++ b/third_party/src/github.com/google/cadvisor/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.2.1 (2014-07-25) +- Handle old Docker versions. +- UI fixes and other bugfixes. + +## 0.2.0 (2014-07-24) +- Added network stats to the UI. +- Added support for CoreOS and RHEL. +- Bugfixes and reliability fixes. + +## 0.1.4 (2014-07-22) +- Add network statistics to REST API. +- Add "raw" driver to handle non-Docker containers. +- Remove lmctfy in favor of the raw driver. +- Bugfixes for Docker containers and logging. + ## 0.1.3 (2014-07-14) - Add support for systemd systems. - Fixes for UI with InfluxDB storage driver. diff --git a/third_party/src/github.com/google/cadvisor/README.md b/third_party/src/github.com/google/cadvisor/README.md index 5e4e65a828e..663e9d60ea9 100644 --- a/third_party/src/github.com/google/cadvisor/README.md +++ b/third_party/src/github.com/google/cadvisor/README.md @@ -1,6 +1,6 @@ # cAdvisor -cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers. Specifically, for each container it keeps resource isolation parameters, historical resource usage, and histograms of complete historical resource usage. This data is exported by container and machine-wide. +cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers. Specifically, for each container it keeps resource isolation parameters, historical resource usage, histograms of complete historical resource usage and network statistics. This data is exported by container and machine-wide. cAdvisor currently supports lmctfy containers as well as Docker containers (those that use the default libcontainer execdriver). Other container backends can also be added. cAdvisor's container abstraction is based on lmctfy's so containers are inherently nested hierarchically. @@ -13,11 +13,12 @@ To quickly tryout cAdvisor on your machine with Docker (version 0.11 or above), ``` sudo docker run \ --volume=/var/run:/var/run:rw \ - --volume=/sys/fs/cgroup/:/sys/fs/cgroup:ro \ + --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --publish=8080:8080 \ --detach=true \ - google/cadvisor + --name=cadvisor \ + google/cadvisor:latest ``` cAdvisor is now running (in the background) on `http://localhost:8080`. The setup includes directories with Docker state cAdvisor needs to observe. diff --git a/third_party/src/github.com/google/cadvisor/advice/interference/detector.go b/third_party/src/github.com/google/cadvisor/advice/interference/detector.go index 8c29176311c..33261439bd7 100644 --- a/third_party/src/github.com/google/cadvisor/advice/interference/detector.go +++ b/third_party/src/github.com/google/cadvisor/advice/interference/detector.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package inference +package interference import "github.com/google/cadvisor/info" diff --git a/third_party/src/github.com/google/cadvisor/api/handler.go b/third_party/src/github.com/google/cadvisor/api/handler.go index 393705bde04..5a723ef2cd5 100644 --- a/third_party/src/github.com/google/cadvisor/api/handler.go +++ b/third_party/src/github.com/google/cadvisor/api/handler.go @@ -71,6 +71,11 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er log.Printf("Api - Container(%s)", containerName) var query info.ContainerInfoRequest + + // If a user does not specify number of stats/samples he wants, + // it's 64 by default + query.NumStats = 64 + query.NumSamples = 64 decoder := json.NewDecoder(r.Body) err := decoder.Decode(&query) if err != nil && err != io.EOF { diff --git a/third_party/src/github.com/google/cadvisor/cadvisor.go b/third_party/src/github.com/google/cadvisor/cadvisor.go index 5df6e27d24d..ccbfddea810 100644 --- a/third_party/src/github.com/google/cadvisor/cadvisor.go +++ b/third_party/src/github.com/google/cadvisor/cadvisor.go @@ -22,7 +22,7 @@ import ( "github.com/google/cadvisor/api" "github.com/google/cadvisor/container/docker" - "github.com/google/cadvisor/container/lmctfy" + "github.com/google/cadvisor/container/raw" "github.com/google/cadvisor/info" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/pages" @@ -30,7 +30,6 @@ import ( ) var argPort = flag.Int("port", 8080, "port to listen") -var argAllowLmctfy = flag.Bool("allow_lmctfy", true, "whether to allow lmctfy as a container handler") var argDbDriver = flag.String("storage_driver", "memory", "storage driver to use. Options are: memory (default) and influxdb") @@ -47,30 +46,14 @@ func main() { log.Fatalf("Failed to create a Container Manager: %s", err) } - // Register lmctfy for the root if allowed and available. - registeredRoot := false - if *argAllowLmctfy { - if err := lmctfy.Register("/"); err != nil { - log.Printf("lmctfy registration failed: %v.", err) - log.Print("Running in docker only mode.") - } else { - registeredRoot = true - } - } - - // Register Docker for root if we were unable to register lmctfy. - if !registeredRoot { - if err := docker.Register(containerManager, "/"); err != nil { - log.Printf("Docker registration failed: %v.", err) - log.Fatalf("Unable to continue without root handler.") - } - } - - // Register Docker for all Docker containers. - if err := docker.Register(containerManager, "/docker"); err != nil { - // Ignore this error because we should work with lmctfy only + // Register Docker. + if err := docker.Register(containerManager); err != nil { log.Printf("Docker registration failed: %v.", err) - log.Print("Running in lmctfy only mode.") + } + + // Register the raw driver. + if err := raw.Register(containerManager); err != nil { + log.Fatalf("raw registration failed: %v.", err) } // Handler for static content. @@ -100,11 +83,14 @@ func main() { } }) - go containerManager.Start() + go func() { + log.Fatal(containerManager.Start()) + }() log.Printf("Starting cAdvisor version: %q", info.VERSION) log.Print("About to serve on port ", *argPort) addr := fmt.Sprintf(":%v", *argPort) + log.Fatal(http.ListenAndServe(addr, nil)) } diff --git a/third_party/src/github.com/google/cadvisor/container/docker/factory.go b/third_party/src/github.com/google/cadvisor/container/docker/factory.go index 4d8165ab551..8382aec0645 100644 --- a/third_party/src/github.com/google/cadvisor/container/docker/factory.go +++ b/third_party/src/github.com/google/cadvisor/container/docker/factory.go @@ -17,11 +17,15 @@ package docker import ( "flag" "fmt" + "log" "regexp" "strconv" + "strings" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/fsouza/go-dockerclient" "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/info" ) @@ -29,6 +33,11 @@ var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "do type dockerFactory struct { machineInfoFactory info.MachineInfoFactory + + // Whether this system is using systemd. + useSystemd bool + + client *docker.Client } func (self *dockerFactory) String() string { @@ -44,10 +53,42 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C client, name, self.machineInfoFactory, + self.useSystemd, ) return } +// Docker handles all containers under /docker +// TODO(vishh): Change the CanHandle interface to be able to return errors. +func (self *dockerFactory) CanHandle(name string) bool { + // In systemd systems the containers are: /system.slice/docker-{ID} + if self.useSystemd { + if !strings.HasPrefix(name, "/system.slice/docker-") { + return false + } + } else if name == "/" { + return false + } else if name == "/docker" { + // We need the docker driver to handle /docker. Otherwise the aggregation at the API level will break. + return true + } else if !strings.HasPrefix(name, "/docker/") { + return false + } + // Check if the container is known to docker and it is active. + _, id, err := libcontainer.SplitName(name) + if err != nil { + return false + } + ctnr, err := self.client.InspectContainer(id) + // We assume that if Inspect fails then the container is not known to docker. + // TODO(vishh): Detect lxc containers and avoid handling them. + if err != nil || !ctnr.State.Running { + return false + } + + return true +} + func parseDockerVersion(full_version_string string) ([]int, error) { version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)" version_re := regexp.MustCompile(version_regexp_string) @@ -68,7 +109,7 @@ func parseDockerVersion(full_version_string string) ([]int, error) { } // Register root container before running this function! -func Register(factory info.MachineInfoFactory, paths ...string) error { +func Register(factory info.MachineInfoFactory) error { client, err := docker.NewClient(*ArgDockerEndpoint) if err != nil { return fmt.Errorf("unable to communicate with docker daemon: %v", err) @@ -92,12 +133,13 @@ func Register(factory info.MachineInfoFactory, paths ...string) error { } f := &dockerFactory{ machineInfoFactory: factory, + useSystemd: systemd.UseSystemd(), + client: client, } - for _, p := range paths { - if p != "/" && p != "/docker" { - return fmt.Errorf("%v cannot be managed by docker", p) - } - container.RegisterContainerHandlerFactory(p, f) + if f.useSystemd { + log.Printf("System is using systemd") } + log.Printf("Registering Docker factory") + container.RegisterContainerHandlerFactory(f) return nil } diff --git a/third_party/src/github.com/google/cadvisor/container/docker/handler.go b/third_party/src/github.com/google/cadvisor/container/docker/handler.go index fd170d67eec..040f74a7e6d 100644 --- a/third_party/src/github.com/google/cadvisor/container/docker/handler.go +++ b/third_party/src/github.com/google/cadvisor/container/docker/handler.go @@ -15,52 +15,66 @@ package docker import ( - "bufio" "encoding/json" + "errors" "fmt" + "log" "math" "os" "path" "path/filepath" "strings" - "time" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups/fs" - "github.com/docker/libcontainer/cgroups/systemd" "github.com/fsouza/go-dockerclient" "github.com/google/cadvisor/container" + containerLibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/info" + "github.com/google/cadvisor/utils" ) +// Basepath to all container specific information that libcontainer stores. +const dockerRootDir = "/var/lib/docker/execdriver/native" + +var fileNotFound = errors.New("file not found") + type dockerContainerHandler struct { client *docker.Client name string + parent string + id string aliases []string machineInfoFactory info.MachineInfoFactory + useSystemd bool } func newDockerContainerHandler( client *docker.Client, name string, machineInfoFactory info.MachineInfoFactory, + useSystemd bool, ) (container.ContainerHandler, error) { handler := &dockerContainerHandler{ client: client, name: name, machineInfoFactory: machineInfoFactory, + useSystemd: useSystemd, } - if !handler.isDockerContainer() { + if handler.isDockerRoot() { return handler, nil } - _, id, err := handler.splitName() + parent, id, err := containerLibcontainer.SplitName(name) if err != nil { return nil, fmt.Errorf("invalid docker container %v: %v", name, err) } + handler.parent = parent + handler.id = id ctnr, err := client.InspectContainer(id) + // We assume that if Inspect fails then the container is not known to docker. if err != nil { - return nil, fmt.Errorf("unable to inspect container %v: %v", name, err) + return nil, fmt.Errorf("failed to inspect container %s - %s\n", id, err) } handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name)) return handler, nil @@ -73,71 +87,66 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc }, nil } -func (self *dockerContainerHandler) splitName() (string, string, error) { - parent, id := path.Split(self.name) - cgroupSelf, err := os.Open("/proc/self/cgroup") - if err != nil { - return "", "", err - } - scanner := bufio.NewScanner(cgroupSelf) - - subsys := []string{"memory", "cpu"} - nestedLevels := 0 - for scanner.Scan() { - line := scanner.Text() - elems := strings.Split(line, ":") - if len(elems) < 3 { - continue - } - for _, s := range subsys { - if elems[1] == s { - // count how many nested docker containers are there. - nestedLevels = strings.Count(elems[2], "/docker") - break - } - } - } - if nestedLevels > 0 { - // we are running inside a docker container - upperLevel := strings.Repeat("../../", nestedLevels) - parent = filepath.Join(upperLevel, parent) - } - // Strip the last "/" - if parent[len(parent)-1] == '/' { - parent = parent[:len(parent)-1] - } - return parent, id, nil -} - func (self *dockerContainerHandler) isDockerRoot() bool { - // TODO(dengnan): Should we consider other cases? return self.name == "/docker" } -func (self *dockerContainerHandler) isRootContainer() bool { - return self.name == "/" -} - -func (self *dockerContainerHandler) isDockerContainer() bool { - return (!self.isDockerRoot()) && (!self.isRootContainer()) -} - // TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API. -func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) { - dir := "/var/lib/docker/execdriver/native" - configPath := path.Join(dir, id, "container.json") +func (self *dockerContainerHandler) readLibcontainerConfig() (config *libcontainer.Config, err error) { + configPath := path.Join(dockerRootDir, self.id, "container.json") + if !utils.FileExists(configPath) { + // TODO(vishh): Return file name as well once we have a better error interface. + err = fileNotFound + return + } f, err := os.Open(configPath) if err != nil { - return + return nil, fmt.Errorf("failed to open %s - %s\n", configPath, err) } defer f.Close() d := json.NewDecoder(f) - ret := new(libcontainer.Config) - err = d.Decode(ret) + retConfig := new(libcontainer.Config) + err = d.Decode(retConfig) if err != nil { return } - spec = ret + config = retConfig + + // Replace cgroup parent and name with our own since we may be running in a different context. + config.Cgroups.Parent = self.parent + config.Cgroups.Name = self.id + + return +} + +func (self *dockerContainerHandler) readLibcontainerState() (state *libcontainer.State, err error) { + statePath := path.Join(dockerRootDir, self.id, "state.json") + if !utils.FileExists(statePath) { + // TODO(vmarmol): Remove this once we can depend on a newer Docker. + // Libcontainer changed how its state was stored, try the old way of a "pid" file + if utils.FileExists(path.Join(dockerRootDir, self.id, "pid")) { + // We don't need the old state, return an empty state and we'll gracefully degrade. + state = new(libcontainer.State) + return + } + + // TODO(vishh): Return file name as well once we have a better error interface. + err = fileNotFound + return + } + f, err := os.Open(statePath) + if err != nil { + return nil, fmt.Errorf("failed to open %s - %s\n", statePath, err) + } + defer f.Close() + d := json.NewDecoder(f) + retState := new(libcontainer.State) + err = d.Decode(retState) + if err != nil { + return + } + state = retState + return } @@ -159,111 +168,59 @@ func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.Mac if config.Cgroups.CpuShares != 0 { spec.Cpu.Limit = uint64(config.Cgroups.CpuShares) } - n := (mi.NumCores + 63) / 64 - spec.Cpu.Mask.Data = make([]uint64, n) - for i := 0; i < n; i++ { - spec.Cpu.Mask.Data[i] = math.MaxUint64 + if config.Cgroups.CpusetCpus == "" { + // All cores are active. + spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1) + } else { + spec.Cpu.Mask = config.Cgroups.CpusetCpus } - // TODO(vmarmol): Get CPUs from config.Cgroups.CpusetCpus return spec } func (self *dockerContainerHandler) GetSpec() (spec *info.ContainerSpec, err error) { - if !self.isDockerContainer() { - spec = new(info.ContainerSpec) - return + if self.isDockerRoot() { + return &info.ContainerSpec{}, nil } mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { return } - _, id, err := self.splitName() - if err != nil { - return - } - libcontainerSpec, err := readLibcontainerSpec(id) + libcontainerConfig, err := self.readLibcontainerConfig() if err != nil { return } - spec = libcontainerConfigToContainerSpec(libcontainerSpec, mi) + spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi) return } -func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.ContainerStats { - ret := new(info.ContainerStats) - ret.Timestamp = time.Now() - ret.Cpu = new(info.CpuStats) - ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode - ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode - n := len(s.CpuStats.CpuUsage.PercpuUsage) - ret.Cpu.Usage.PerCpu = make([]uint64, n) - - ret.Cpu.Usage.Total = 0 - for i := 0; i < n; i++ { - ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i] - ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i] - } - ret.Memory = new(info.MemoryStats) - ret.Memory.Usage = s.MemoryStats.Usage - if v, ok := s.MemoryStats.Stats["pgfault"]; ok { - ret.Memory.ContainerData.Pgfault = v - ret.Memory.HierarchicalData.Pgfault = v - } - if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok { - ret.Memory.ContainerData.Pgmajfault = v - ret.Memory.HierarchicalData.Pgmajfault = v - } - if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok { - ret.Memory.WorkingSet = ret.Memory.Usage - v - if v, ok := s.MemoryStats.Stats["total_active_file"]; ok { - ret.Memory.WorkingSet -= v - } - } - return ret -} - func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err error) { - if !self.isDockerContainer() { - // Return empty stats for root containers. - stats = new(info.ContainerStats) - stats.Timestamp = time.Now() - return + if self.isDockerRoot() { + return &info.ContainerStats{}, nil } - mi, err := self.machineInfoFactory.GetMachineInfo() + config, err := self.readLibcontainerConfig() if err != nil { + if err == fileNotFound { + log.Printf("Libcontainer config not found for container %q", self.name) + return &info.ContainerStats{}, nil + } return } - parent, id, err := self.splitName() + state, err := self.readLibcontainerState() if err != nil { + if err == fileNotFound { + log.Printf("Libcontainer state not found for container %q", self.name) + return &info.ContainerStats{}, nil + } return } - cg := &cgroups.Cgroup{ - Parent: parent, - Name: id, - } - // TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready. - // Use systemd paths if systemd is being used. - var s *cgroups.Stats - if systemd.UseSystemd() { - s, err = systemd.GetStats(cg) - } else { - s, err = fs.GetStats(cg) - } - if err != nil { - return - } - stats = libcontainerToContainerStats(s, mi) - return + return containerLibcontainer.GetStats(config, state) } func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { - if self.isDockerContainer() { - return nil, nil - } - if self.isRootContainer() && listType == container.LIST_SELF { - return []info.ContainerReference{info.ContainerReference{Name: "/docker"}}, nil + if self.name != "/docker" { + return []info.ContainerReference{}, nil } opt := docker.ListContainersOptions{ All: true, @@ -272,22 +229,26 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType) if err != nil { return nil, err } + + // On non-systemd systems Docker containers are under /docker. + containerPrefix := "/docker" + if self.useSystemd { + containerPrefix = "/system.slice" + } + ret := make([]info.ContainerReference, 0, len(containers)+1) for _, c := range containers { if !strings.HasPrefix(c.Status, "Up ") { continue } - path := fmt.Sprintf("/docker/%v", c.ID) - aliases := c.Names + ref := info.ContainerReference{ - Name: path, - Aliases: aliases, + Name: filepath.Join(containerPrefix, c.ID), + Aliases: c.Names, } ret = append(ret, ref) } - if self.isRootContainer() { - ret = append(ret, info.ContainerReference{Name: "/docker"}) - } + return ret, nil } @@ -296,5 +257,9 @@ func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([] } func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { - return nil, nil + c := &cgroups.Cgroup{ + Parent: self.parent, + Name: self.id, + } + return fs.GetPids(c) } diff --git a/third_party/src/github.com/google/cadvisor/container/factory.go b/third_party/src/github.com/google/cadvisor/container/factory.go index 24f82ed1b52..369925d6378 100644 --- a/third_party/src/github.com/google/cadvisor/container/factory.go +++ b/third_party/src/github.com/google/cadvisor/container/factory.go @@ -17,113 +17,56 @@ package container import ( "fmt" "log" - "strings" "sync" ) type ContainerHandlerFactory interface { + // Create a new ContainerHandler using this factory. CanHandle() must have returned true. NewContainerHandler(name string) (ContainerHandler, error) - // for testability + // Returns whether this factory can handle the specified container. + CanHandle(name string) bool + + // Name of the factory. String() string } -type factoryTreeNode struct { - defaultFactory ContainerHandlerFactory - children map[string]*factoryTreeNode +// TODO(vmarmol): Consider not making this global. +// Global list of factories. +var ( + factories []ContainerHandlerFactory + factoriesLock sync.RWMutex +) + +// Register a ContainerHandlerFactory. These should be registered from least general to most general +// as they will be asked in order whether they can handle a particular container. +func RegisterContainerHandlerFactory(factory ContainerHandlerFactory) { + factoriesLock.Lock() + defer factoriesLock.Unlock() + + factories = append(factories, factory) } -func (self *factoryTreeNode) find(elems ...string) ContainerHandlerFactory { - node := self - for _, elem := range elems { - if len(node.children) == 0 { - break - } - if child, ok := node.children[elem]; ok { - node = child - } else { - return node.defaultFactory +// Create a new ContainerHandler for the specified container. +func NewContainerHandler(name string) (ContainerHandler, error) { + factoriesLock.RLock() + defer factoriesLock.RUnlock() + + // Create the ContainerHandler with the first factory that supports it. + for _, factory := range factories { + if factory.CanHandle(name) { + log.Printf("Using factory %q for container %q", factory.String(), name) + return factory.NewContainerHandler(name) } } - return node.defaultFactory + return nil, fmt.Errorf("no known factory can handle creation of container") } -func (self *factoryTreeNode) add(factory ContainerHandlerFactory, elems ...string) { - node := self - for _, elem := range elems { - if node.children == nil { - node.children = make(map[string]*factoryTreeNode, 16) - } - child, ok := self.children[elem] - if !ok { - child = &factoryTreeNode{ - defaultFactory: node.defaultFactory, - children: make(map[string]*factoryTreeNode, 16), - } - node.children[elem] = child - } - node = child - } - node.defaultFactory = factory -} - -type factoryManager struct { - root *factoryTreeNode - lock sync.RWMutex -} - -func dropEmptyString(elems ...string) []string { - ret := make([]string, 0, len(elems)) - for _, e := range elems { - if len(e) > 0 { - ret = append(ret, e) - } - } - return ret -} - -// Must register factory for root container! -func (self *factoryManager) Register(path string, factory ContainerHandlerFactory) { - self.lock.Lock() - defer self.lock.Unlock() - - if self.root == nil { - self.root = &factoryTreeNode{ - defaultFactory: nil, - children: make(map[string]*factoryTreeNode, 10), - } - } - - elems := dropEmptyString(strings.Split(path, "/")...) - self.root.add(factory, elems...) -} - -func (self *factoryManager) NewContainerHandler(path string) (ContainerHandler, error) { - self.lock.RLock() - defer self.lock.RUnlock() - - if self.root == nil { - err := fmt.Errorf("nil factory for container %v: no factory registered", path) - return nil, err - } - - elems := dropEmptyString(strings.Split(path, "/")...) - factory := self.root.find(elems...) - if factory == nil { - err := fmt.Errorf("nil factory for container %v", path) - return nil, err - } - log.Printf("Container handler factory for %v is %v\n", path, factory) - return factory.NewContainerHandler(path) -} - -var globalFactoryManager factoryManager - -func RegisterContainerHandlerFactory(path string, factory ContainerHandlerFactory) { - globalFactoryManager.Register(path, factory) -} - -func NewContainerHandler(path string) (ContainerHandler, error) { - return globalFactoryManager.NewContainerHandler(path) +// Clear the known factories. +func ClearContainerHandlerFactories() { + factoriesLock.Lock() + defer factoriesLock.Unlock() + + factories = make([]ContainerHandlerFactory, 0, 4) } diff --git a/third_party/src/github.com/google/cadvisor/container/factory_test.go b/third_party/src/github.com/google/cadvisor/container/factory_test.go index 1a688417417..b96caab5fca 100644 --- a/third_party/src/github.com/google/cadvisor/container/factory_test.go +++ b/third_party/src/github.com/google/cadvisor/container/factory_test.go @@ -15,7 +15,6 @@ package container import ( - "strings" "testing" "github.com/stretchr/testify/mock" @@ -23,54 +22,101 @@ import ( type mockContainerHandlerFactory struct { mock.Mock - Name string + Name string + CanHandleValue bool } func (self *mockContainerHandlerFactory) String() string { return self.Name } +func (self *mockContainerHandlerFactory) CanHandle(name string) bool { + return self.CanHandleValue +} + func (self *mockContainerHandlerFactory) NewContainerHandler(name string) (ContainerHandler, error) { args := self.Called(name) return args.Get(0).(ContainerHandler), args.Error(1) } -func testExpectedFactory(root *factoryTreeNode, path, expectedFactory string, t *testing.T) { - elems := dropEmptyString(strings.Split(path, "/")...) - factory := root.find(elems...) - if factory.String() != expectedFactory { - t.Errorf("factory %v should be used to create container %v. but %v is selected", - expectedFactory, - path, - factory) +const testContainerName = "/test" + +var mockFactory FactoryForMockContainerHandler + +func TestNewContainerHandler_FirstMatches(t *testing.T) { + ClearContainerHandlerFactories() + + // Register one allways yes factory. + allwaysYes := &mockContainerHandlerFactory{ + Name: "yes", + CanHandleValue: true, + } + RegisterContainerHandlerFactory(allwaysYes) + + // The yes factory should be asked to create the ContainerHandler. + mockContainer, err := mockFactory.NewContainerHandler(testContainerName) + if err != nil { + t.Error(err) + } + allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil) + + cont, err := NewContainerHandler(testContainerName) + if err != nil { + t.Error(err) + } + if cont == nil { + t.Error("Expected container to not be nil") } } -func testAddFactory(root *factoryTreeNode, path string) *factoryTreeNode { - elems := dropEmptyString(strings.Split(path, "/")...) - if root == nil { - root = &factoryTreeNode{ - defaultFactory: nil, - } +func TestNewContainerHandler_SecondMatches(t *testing.T) { + ClearContainerHandlerFactories() + + // Register one allways no and one always yes factory. + allwaysNo := &mockContainerHandlerFactory{ + Name: "no", + CanHandleValue: false, } - f := &mockContainerHandlerFactory{ - Name: path, + RegisterContainerHandlerFactory(allwaysNo) + allwaysYes := &mockContainerHandlerFactory{ + Name: "yes", + CanHandleValue: true, + } + RegisterContainerHandlerFactory(allwaysYes) + + // The yes factory should be asked to create the ContainerHandler. + mockContainer, err := mockFactory.NewContainerHandler(testContainerName) + if err != nil { + t.Error(err) + } + allwaysYes.On("NewContainerHandler", testContainerName).Return(mockContainer, nil) + + cont, err := NewContainerHandler(testContainerName) + if err != nil { + t.Error(err) + } + if cont == nil { + t.Error("Expected container to not be nil") } - root.add(f, elems...) - return root } -func TestFactoryTree(t *testing.T) { - root := testAddFactory(nil, "/") - root = testAddFactory(root, "/docker") - root = testAddFactory(root, "/user") - root = testAddFactory(root, "/user/special/containers") +func TestNewContainerHandler_NoneMatch(t *testing.T) { + ClearContainerHandlerFactories() - testExpectedFactory(root, "/docker/container", "/docker", t) - testExpectedFactory(root, "/docker", "/docker", t) - testExpectedFactory(root, "/", "/", t) - testExpectedFactory(root, "/user/deep/level/container", "/user", t) - testExpectedFactory(root, "/user/special/containers", "/user/special/containers", t) - testExpectedFactory(root, "/user/special/containers/container", "/user/special/containers", t) - testExpectedFactory(root, "/other", "/", t) + // Register two allways no factories. + allwaysNo1 := &mockContainerHandlerFactory{ + Name: "no", + CanHandleValue: false, + } + RegisterContainerHandlerFactory(allwaysNo1) + allwaysNo2 := &mockContainerHandlerFactory{ + Name: "no", + CanHandleValue: false, + } + RegisterContainerHandlerFactory(allwaysNo2) + + _, err := NewContainerHandler(testContainerName) + if err == nil { + t.Error("Expected NewContainerHandler to fail") + } } diff --git a/third_party/src/github.com/google/cadvisor/container/filter_test.go b/third_party/src/github.com/google/cadvisor/container/filter_test.go index 5e8ef538d1e..0c6e9efa105 100644 --- a/third_party/src/github.com/google/cadvisor/container/filter_test.go +++ b/third_party/src/github.com/google/cadvisor/container/filter_test.go @@ -60,9 +60,9 @@ func TestWhiteListContainerFilter(t *testing.T) { mockc := &mockContainerHandler{} mockc.On("ListContainers", LIST_RECURSIVE).Return( []info.ContainerReference{ - info.ContainerReference{Name: "/docker/ee0103"}, - info.ContainerReference{Name: "/container/created/by/lmctfy"}, - info.ContainerReference{Name: "/user/something"}, + {Name: "/docker/ee0103"}, + {Name: "/container/created/by/lmctfy"}, + {Name: "/user/something"}, }, nil, ) @@ -95,9 +95,9 @@ func TestBlackListContainerFilter(t *testing.T) { mockc := &mockContainerHandler{} mockc.On("ListContainers", LIST_RECURSIVE).Return( []info.ContainerReference{ - info.ContainerReference{Name: "/docker/ee0103"}, - info.ContainerReference{Name: "/container/created/by/lmctfy"}, - info.ContainerReference{Name: "/user/something"}, + {Name: "/docker/ee0103"}, + {Name: "/container/created/by/lmctfy"}, + {Name: "/user/something"}, }, nil, ) diff --git a/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go b/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go new file mode 100644 index 00000000000..525d5948886 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go @@ -0,0 +1,135 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcontainer + +import ( + "bufio" + "path" + "path/filepath" + "strings" + "time" + + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups" + cgroupfs "github.com/docker/libcontainer/cgroups/fs" + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/utils/fs" +) + +// Get stats of the specified container +func GetStats(config *libcontainer.Config, state *libcontainer.State) (*info.ContainerStats, error) { + // TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready. + libcontainerStats, err := libcontainer.GetStats(config, state) + if err != nil { + return nil, err + } + return toContainerStats(libcontainerStats), nil +} + +func GetStatsCgroupOnly(cgroup *cgroups.Cgroup) (*info.ContainerStats, error) { + s, err := cgroupfs.GetStats(cgroup) + if err != nil { + return nil, err + } + return toContainerStats(&libcontainer.ContainerStats{CgroupStats: s}), nil +} + +// Convert libcontainer stats to info.ContainerStats. +func toContainerStats(libcontainerStats *libcontainer.ContainerStats) *info.ContainerStats { + s := libcontainerStats.CgroupStats + ret := new(info.ContainerStats) + ret.Timestamp = time.Now() + + if s != nil { + ret.Cpu = new(info.CpuStats) + ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode + ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode + n := len(s.CpuStats.CpuUsage.PercpuUsage) + ret.Cpu.Usage.PerCpu = make([]uint64, n) + + ret.Cpu.Usage.Total = 0 + for i := 0; i < n; i++ { + ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i] + ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i] + } + ret.Memory = new(info.MemoryStats) + ret.Memory.Usage = s.MemoryStats.Usage + if v, ok := s.MemoryStats.Stats["pgfault"]; ok { + ret.Memory.ContainerData.Pgfault = v + ret.Memory.HierarchicalData.Pgfault = v + } + if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok { + ret.Memory.ContainerData.Pgmajfault = v + ret.Memory.HierarchicalData.Pgmajfault = v + } + if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok { + ret.Memory.WorkingSet = ret.Memory.Usage - v + if v, ok := s.MemoryStats.Stats["total_active_file"]; ok { + ret.Memory.WorkingSet -= v + } + } + } + // TODO(vishh): Perform a deep copy or alias libcontainer network stats. + if libcontainerStats.NetworkStats != nil { + ret.Network = (*info.NetworkStats)(libcontainerStats.NetworkStats) + } + + return ret +} + +// Given a container name, returns the parent and name of the container to be fed to libcontainer. +func SplitName(containerName string) (string, string, error) { + parent, id := path.Split(containerName) + cgroupSelf, err := fs.Open("/proc/1/cgroup") + if err != nil { + return "", "", err + } + scanner := bufio.NewScanner(cgroupSelf) + + // Find how nested we are. Libcontainer takes container names relative to the current process. + subsys := []string{"memory", "cpu"} + nestedLevels := 0 + for scanner.Scan() { + line := scanner.Text() + elems := strings.Split(line, ":") + if len(elems) < 3 { + continue + } + for _, s := range subsys { + if elems[1] == s { + if elems[2] == "/" { + // We're running at root, no nesting. + nestedLevels = 0 + } else { + // Count how deeply nested we are. + nestedLevels = strings.Count(elems[2], "/") + } + break + } + } + } + if nestedLevels > 0 { + // we are running inside a docker container + upperLevel := strings.Repeat("../", nestedLevels) + parent = filepath.Join(upperLevel, parent) + } + + // Strip the last "/" + if parent[len(parent)-1] == '/' { + parent = parent[:len(parent)-1] + } + + return parent, id, nil +} diff --git a/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go b/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go new file mode 100644 index 00000000000..36d6168b8b5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go @@ -0,0 +1,90 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcontainer + +import ( + "testing" + + "code.google.com/p/gomock/gomock" + + "github.com/google/cadvisor/utils/fs" + "github.com/google/cadvisor/utils/fs/mockfs" +) + +var initCgroupsToParentAndID = []struct { + InitCgroupFileContent string + ContainerPath string + Parent string + Id string + Error error +}{ + { + ` +11:name=systemd:/ +10:hugetlb:/ +9:perf_event:/ +8:blkio:/ +7:freezer:/ +6:devices:/ +5:memory:/ +4:cpuacct:/ +3:cpu:/ +2:cpuset:/ +`, + "/", + "", + "", + nil, + }, + { + ` +11:name=systemd:/ +10:hugetlb:/ +9:perf_event:/ +8:blkio:/ +7:freezer:/ +6:devices:/ +5:memory:/docker/hash +4:cpuacct:/ +3:cpu:/docker/hash +2:cpuset:/ +`, + "/parent/id", + "../../parent", + "id", + nil, + }, +} + +func TestSplitName(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + for _, testCase := range initCgroupsToParentAndID { + mfs := mockfs.NewMockFileSystem(mockCtrl) + mockfs.AddTextFile(mfs, "/proc/1/cgroup", testCase.InitCgroupFileContent) + fs.ChangeFileSystem(mfs) + parent, id, err := SplitName(testCase.ContainerPath) + if testCase.Error != nil { + if err == nil { + t.Fatalf("did not receive expected error.\ncontent:%v\n, path:%v\n, expected error:%v\n", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Error) + } + continue + } + if testCase.Parent != parent || testCase.Id != id { + t.Errorf("unexpected parent or id:\ncontent:%v\npath:%v\nexpected parent: %v; recevied parent: %v;\nexpected id: %v; received id: %v", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Parent, parent, testCase.Id, id) + } + } +} diff --git a/third_party/src/github.com/google/cadvisor/container/mock.go b/third_party/src/github.com/google/cadvisor/container/mock.go new file mode 100644 index 00000000000..90846c545ea --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/mock.go @@ -0,0 +1,91 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package container + +import ( + "github.com/google/cadvisor/info" + "github.com/stretchr/testify/mock" +) + +// This struct mocks a container handler. +type MockContainerHandler struct { + mock.Mock + Name string + Aliases []string +} + +// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases. +// Otherwise, it will use the value provided by .On().Return(). +func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) { + if len(self.Name) > 0 { + var aliases []string + if len(self.Aliases) > 0 { + aliases = make([]string, len(self.Aliases)) + copy(aliases, self.Aliases) + } + return info.ContainerReference{ + Name: self.Name, + Aliases: aliases, + }, nil + } + args := self.Called() + return args.Get(0).(info.ContainerReference), args.Error(1) +} + +func (self *MockContainerHandler) GetSpec() (*info.ContainerSpec, error) { + args := self.Called() + return args.Get(0).(*info.ContainerSpec), args.Error(1) +} + +func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) { + args := self.Called() + return args.Get(0).(*info.ContainerStats), args.Error(1) +} + +func (self *MockContainerHandler) ListContainers(listType ListType) ([]info.ContainerReference, error) { + args := self.Called(listType) + return args.Get(0).([]info.ContainerReference), args.Error(1) +} + +func (self *MockContainerHandler) ListThreads(listType ListType) ([]int, error) { + args := self.Called(listType) + return args.Get(0).([]int), args.Error(1) +} + +func (self *MockContainerHandler) ListProcesses(listType ListType) ([]int, error) { + args := self.Called(listType) + return args.Get(0).([]int), args.Error(1) +} + +type FactoryForMockContainerHandler struct { + Name string + PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler) +} + +func (self *FactoryForMockContainerHandler) String() string { + return self.Name +} + +func (self *FactoryForMockContainerHandler) NewContainerHandler(name string) (ContainerHandler, error) { + handler := &MockContainerHandler{} + if self.PrepareContainerHandlerFunc != nil { + self.PrepareContainerHandlerFunc(name, handler) + } + return handler, nil +} + +func (self *FactoryForMockContainerHandler) CanHandle(name string) bool { + return true +} diff --git a/third_party/src/github.com/google/cadvisor/container/raw/factory.go b/third_party/src/github.com/google/cadvisor/container/raw/factory.go new file mode 100644 index 00000000000..245fc3be547 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/raw/factory.go @@ -0,0 +1,96 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package raw + +import ( + "fmt" + "log" + + "github.com/docker/libcontainer/cgroups" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" +) + +type cgroupSubsystems struct { + // Cgroup subsystem mounts. + mounts []cgroups.Mount + + // Cgroup subsystem to their mount location. + mountPoints map[string]string +} + +type rawFactory struct { + // Factory for machine information. + machineInfoFactory info.MachineInfoFactory + cgroupSubsystems *cgroupSubsystems +} + +func (self *rawFactory) String() string { + return "raw" +} + +func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) { + return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory) +} + +// The raw factory can handle any container. +func (self *rawFactory) CanHandle(name string) bool { + return true +} + +func Register(machineInfoFactory info.MachineInfoFactory) error { + // Get all cgroup mounts. + allCgroups, err := cgroups.GetCgroupMounts() + if err != nil { + return err + } + if len(allCgroups) == 0 { + return fmt.Errorf("failed to find cgroup mounts for the raw factory") + } + + // Trim the mounts to only the subsystems we care about. + supportedCgroups := make([]cgroups.Mount, 0, len(allCgroups)) + mountPoints := make(map[string]string, len(allCgroups)) + for _, mount := range allCgroups { + for _, subsystem := range mount.Subsystems { + if _, ok := supportedSubsystems[subsystem]; ok { + supportedCgroups = append(supportedCgroups, mount) + mountPoints[subsystem] = mount.Mountpoint + } + } + } + if len(supportedCgroups) == 0 { + return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") + } + + log.Printf("Registering Raw factory") + factory := &rawFactory{ + machineInfoFactory: machineInfoFactory, + cgroupSubsystems: &cgroupSubsystems{ + mounts: supportedCgroups, + mountPoints: mountPoints, + }, + } + container.RegisterContainerHandlerFactory(factory) + return nil +} + +// Cgroup subsystems we support listing (should be the minimal set we need stats from). +var supportedSubsystems map[string]struct{} = map[string]struct{}{ + "cpu": {}, + "cpuacct": {}, + "memory": {}, + "cpuset": {}, +} diff --git a/third_party/src/github.com/google/cadvisor/container/raw/handler.go b/third_party/src/github.com/google/cadvisor/container/raw/handler.go new file mode 100644 index 00000000000..ef5404e2be5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/container/raw/handler.go @@ -0,0 +1,206 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package raw + +import ( + "fmt" + "io/ioutil" + "log" + "path/filepath" + "strconv" + "strings" + + "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/cgroups/fs" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/utils" +) + +type rawContainerHandler struct { + name string + cgroup *cgroups.Cgroup + cgroupSubsystems *cgroupSubsystems + machineInfoFactory info.MachineInfoFactory +} + +func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { + parent, id, err := libcontainer.SplitName(name) + if err != nil { + return nil, err + } + return &rawContainerHandler{ + name: name, + cgroup: &cgroups.Cgroup{ + Parent: parent, + Name: id, + }, + cgroupSubsystems: cgroupSubsystems, + machineInfoFactory: machineInfoFactory, + }, nil +} + +func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, error) { + // We only know the container by its one name. + return info.ContainerReference{ + Name: self.name, + }, nil +} + +func readString(path string, file string) string { + cgroupFile := filepath.Join(path, file) + + // Ignore non-existent files + if !utils.FileExists(cgroupFile) { + return "" + } + + // Read + out, err := ioutil.ReadFile(cgroupFile) + if err != nil { + log.Printf("raw driver: Failed to read %q: %s", cgroupFile, err) + return "" + } + return string(out) +} + +func readInt64(path string, file string) uint64 { + out := readString(path, file) + if out == "" { + return 0 + } + + val, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64) + if err != nil { + log.Printf("raw driver: Failed to parse in %q from file %q: %s", out, filepath.Join(path, file), err) + return 0 + } + + return val +} + +func (self *rawContainerHandler) GetSpec() (*info.ContainerSpec, error) { + spec := new(info.ContainerSpec) + + // The raw driver assumes unified hierarchy containers. + + // Get machine info. + mi, err := self.machineInfoFactory.GetMachineInfo() + if err != nil { + return nil, err + } + + // CPU. + cpuRoot, ok := self.cgroupSubsystems.mountPoints["cpu"] + if ok { + cpuRoot = filepath.Join(cpuRoot, self.name) + if utils.FileExists(cpuRoot) { + spec.Cpu = new(info.CpuSpec) + spec.Cpu.Limit = readInt64(cpuRoot, "cpu.shares") + } + } + + // Cpu Mask. + // This will fail for non-unified hierarchies. We'll return the whole machine mask in that case. + cpusetRoot, ok := self.cgroupSubsystems.mountPoints["cpuset"] + if ok { + if spec.Cpu == nil { + spec.Cpu = new(info.CpuSpec) + } + cpusetRoot = filepath.Join(cpusetRoot, self.name) + if utils.FileExists(cpusetRoot) { + spec.Cpu.Mask = readString(cpusetRoot, "cpuset.cpus") + if spec.Cpu.Mask == "" { + spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1) + } + } + } + + // Memory. + memoryRoot, ok := self.cgroupSubsystems.mountPoints["memory"] + if ok { + memoryRoot = filepath.Join(memoryRoot, self.name) + if utils.FileExists(memoryRoot) { + spec.Memory = new(info.MemorySpec) + spec.Memory.Limit = readInt64(memoryRoot, "memory.limit_in_bytes") + spec.Memory.SwapLimit = readInt64(memoryRoot, "memory.memsw.limit_in_bytes") + } + } + + return spec, nil +} + +func (self *rawContainerHandler) GetStats() (stats *info.ContainerStats, err error) { + return libcontainer.GetStatsCgroupOnly(self.cgroup) +} + +// Lists all directories under "path" and outputs the results as children of "parent". +func listDirectories(path string, parent string, recursive bool, output map[string]struct{}) error { + // Ignore if this hierarchy does not exist. + if !utils.FileExists(path) { + return nil + } + + entries, err := ioutil.ReadDir(path) + if err != nil { + return err + } + for _, entry := range entries { + // We only grab directories. + if entry.IsDir() { + name := filepath.Join(parent, entry.Name()) + output[name] = struct{}{} + + // List subcontainers if asked to. + if recursive { + err := listDirectories(filepath.Join(path, entry.Name()), name, true, output) + if err != nil { + return err + } + } + } + } + return nil +} + +func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { + containers := make(map[string]struct{}, 16) + for _, subsystem := range self.cgroupSubsystems.mounts { + err := listDirectories(filepath.Join(subsystem.Mountpoint, self.name), self.name, listType == container.LIST_RECURSIVE, containers) + if err != nil { + return nil, err + } + } + + // Make into container references. + ret := make([]info.ContainerReference, 0, len(containers)) + for cont := range containers { + ret = append(ret, info.ContainerReference{ + Name: cont, + }) + } + + return ret, nil +} + +func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + // TODO(vmarmol): Implement + return nil, nil +} + +func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + return fs.GetPids(self.cgroup) +} diff --git a/third_party/src/github.com/google/cadvisor/deploy/Dockerfile b/third_party/src/github.com/google/cadvisor/deploy/Dockerfile new file mode 100644 index 00000000000..15496b26bfc --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/deploy/Dockerfile @@ -0,0 +1,10 @@ +FROM scratch +MAINTAINER dengnan@google.com vmarmol@google.com + +# NOTE: Run prepare.sh before building this Docker image. + +# Grab cadvisor from the staging directory. +ADD cadvisor /usr/bin/cadvisor + +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/cadvisor"] diff --git a/third_party/src/github.com/google/cadvisor/deploy/prepare.sh b/third_party/src/github.com/google/cadvisor/deploy/prepare.sh new file mode 100644 index 00000000000..96e4a8d76ac --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/deploy/prepare.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e +set -x + +# Statically build cAdvisor from source and stage it. +go build --ldflags '-extldflags "-static"' github.com/google/cadvisor diff --git a/third_party/src/github.com/google/cadvisor/info/container.go b/third_party/src/github.com/google/cadvisor/info/container.go index 57ba814173c..58931c8353e 100644 --- a/third_party/src/github.com/google/cadvisor/info/container.go +++ b/third_party/src/github.com/google/cadvisor/info/container.go @@ -21,14 +21,10 @@ import ( "time" ) -type CpuSpecMask struct { - Data []uint64 `json:"data,omitempty"` -} - type CpuSpec struct { - Limit uint64 `json:"limit"` - MaxLimit uint64 `json:"max_limit"` - Mask CpuSpecMask `json:"mask,omitempty"` + Limit uint64 `json:"limit"` + MaxLimit uint64 `json:"max_limit"` + Mask string `json:"mask,omitempty"` } type MemorySpec struct { @@ -69,27 +65,7 @@ type ContainerInfoRequest struct { // Different percentiles of CPU usage within a period. The values must be within [0, 100] CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"` // Different percentiles of memory usage within a period. The values must be within [0, 100] - MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"` -} - -func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest { - ret := self - if ret == nil { - ret = new(ContainerInfoRequest) - } - if ret.NumStats <= 0 { - ret.NumStats = 1024 - } - if ret.NumSamples <= 0 { - ret.NumSamples = 1024 - } - if len(ret.CpuUsagePercentiles) == 0 { - ret.CpuUsagePercentiles = []int{50, 80, 90, 99} - } - if len(ret.MemoryUsagePercentages) == 0 { - ret.MemoryUsagePercentages = []int{50, 80, 90, 99} - } - return ret + MemoryUsagePercentiles []int `json:"memory_usage_percentiles,omitempty"` } type ContainerInfo struct { @@ -239,11 +215,31 @@ type MemoryStatsMemoryData struct { Pgmajfault uint64 `json:"pgmajfault,omitempty"` } +type NetworkStats struct { + // Cumulative count of bytes received. + RxBytes uint64 `json:"rx_bytes"` + // Cumulative count of packets received. + RxPackets uint64 `json:"rx_packets"` + // Cumulative count of receive errors encountered. + RxErrors uint64 `json:"rx_errors"` + // Cumulative count of packets dropped while receiving. + RxDropped uint64 `json:"rx_dropped"` + // Cumulative count of bytes transmitted. + TxBytes uint64 `json:"tx_bytes"` + // Cumulative count of packets transmitted. + TxPackets uint64 `json:"tx_packets"` + // Cumulative count of transmit errors encountered. + TxErrors uint64 `json:"tx_errors"` + // Cumulative count of packets dropped while transmitting. + TxDropped uint64 `json:"tx_dropped"` +} + type ContainerStats struct { // The time of this stat point. - Timestamp time.Time `json:"timestamp"` - Cpu *CpuStats `json:"cpu,omitempty"` - Memory *MemoryStats `json:"memory,omitempty"` + Timestamp time.Time `json:"timestamp"` + Cpu *CpuStats `json:"cpu,omitempty"` + Memory *MemoryStats `json:"memory,omitempty"` + Network *NetworkStats `json:"network,omitempty"` } // Makes a deep copy of the ContainerStats and returns a pointer to the new diff --git a/third_party/src/github.com/google/cadvisor/info/test/datagen.go b/third_party/src/github.com/google/cadvisor/info/test/datagen.go index a1c0a3565ca..d52a156a82c 100644 --- a/third_party/src/github.com/google/cadvisor/info/test/datagen.go +++ b/third_party/src/github.com/google/cadvisor/info/test/datagen.go @@ -16,7 +16,6 @@ package test import ( "fmt" - "math" "math/rand" "time" @@ -59,12 +58,7 @@ func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec { } ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000)) - n := (numCores + 63) / 64 - ret.Cpu.Mask.Data = make([]uint64, n) - for i := 0; i < n; i++ { - ret.Cpu.Mask.Data[i] = math.MaxUint64 - } - + ret.Cpu.Mask = fmt.Sprintf("0-%d", numCores-1) ret.Memory.Limit = uint64(4096 + rand.Int63n(4096)) return ret } @@ -83,8 +77,8 @@ func GenerateRandomContainerInfo(containerName string, numCores int, query *info percentile := info.Percentile{p, uint64(rand.Int63n(1000))} cpuPercentiles = append(cpuPercentiles, percentile) } - memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages)) - for _, p := range query.MemoryUsagePercentages { + memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentiles)) + for _, p := range query.MemoryUsagePercentiles { percentile := info.Percentile{p, uint64(rand.Int63n(1000))} memPercentiles = append(memPercentiles, percentile) } diff --git a/third_party/src/github.com/google/cadvisor/info/version.go b/third_party/src/github.com/google/cadvisor/info/version.go index 5440de4ff1b..c5a91de01a5 100644 --- a/third_party/src/github.com/google/cadvisor/info/version.go +++ b/third_party/src/github.com/google/cadvisor/info/version.go @@ -15,4 +15,4 @@ package info // Version of cAdvisor. -const VERSION = "0.1.3" +const VERSION = "0.2.1" diff --git a/third_party/src/github.com/google/cadvisor/manager/container_test.go b/third_party/src/github.com/google/cadvisor/manager/container_test.go index c942eb81cab..5910ed3b782 100644 --- a/third_party/src/github.com/google/cadvisor/manager/container_test.go +++ b/third_party/src/github.com/google/cadvisor/manager/container_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/google/cadvisor/container" - ctest "github.com/google/cadvisor/container/test" "github.com/google/cadvisor/info" itest "github.com/google/cadvisor/info/test" "github.com/google/cadvisor/storage" @@ -32,17 +31,18 @@ import ( func createContainerDataAndSetHandler( driver storage.StorageDriver, - f func(*ctest.MockContainerHandler), + f func(*container.MockContainerHandler), t *testing.T, ) *containerData { - factory := &ctest.FactoryForMockContainerHandler{ + factory := &container.FactoryForMockContainerHandler{ Name: "factoryForMockContainer", - PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { + PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) { handler.Name = name f(handler) }, } - container.RegisterContainerHandlerFactory("/", factory) + container.ClearContainerHandlerFactories() + container.RegisterContainerHandlerFactory(factory) if driver == nil { driver = &stest.MockStorageDriver{} @@ -56,7 +56,7 @@ func createContainerDataAndSetHandler( } func TestContainerUpdateSubcontainers(t *testing.T) { - var handler *ctest.MockContainerHandler + var handler *container.MockContainerHandler subcontainers := []info.ContainerReference{ {Name: "/container/ee0103"}, {Name: "/container/abcd"}, @@ -64,7 +64,7 @@ func TestContainerUpdateSubcontainers(t *testing.T) { } cd := createContainerDataAndSetHandler( nil, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { h.On("ListContainers", container.LIST_SELF).Return( subcontainers, nil, @@ -99,10 +99,10 @@ func TestContainerUpdateSubcontainers(t *testing.T) { } func TestContainerUpdateSubcontainersWithError(t *testing.T) { - var handler *ctest.MockContainerHandler + var handler *container.MockContainerHandler cd := createContainerDataAndSetHandler( nil, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { h.On("ListContainers", container.LIST_SELF).Return( []info.ContainerReference{}, fmt.Errorf("some error"), @@ -124,7 +124,7 @@ func TestContainerUpdateSubcontainersWithError(t *testing.T) { } func TestContainerUpdateStats(t *testing.T) { - var handler *ctest.MockContainerHandler + var handler *container.MockContainerHandler var ref info.ContainerReference driver := &stest.MockStorageDriver{} @@ -134,7 +134,7 @@ func TestContainerUpdateStats(t *testing.T) { cd := createContainerDataAndSetHandler( driver, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { h.On("GetStats").Return( stats, nil, @@ -156,11 +156,11 @@ func TestContainerUpdateStats(t *testing.T) { } func TestContainerUpdateSpec(t *testing.T) { - var handler *ctest.MockContainerHandler + var handler *container.MockContainerHandler spec := itest.GenerateRandomContainerSpec(4) cd := createContainerDataAndSetHandler( nil, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { h.On("GetSpec").Return( spec, nil, @@ -179,7 +179,7 @@ func TestContainerUpdateSpec(t *testing.T) { } func TestContainerGetInfo(t *testing.T) { - var handler *ctest.MockContainerHandler + var handler *container.MockContainerHandler spec := itest.GenerateRandomContainerSpec(4) subcontainers := []info.ContainerReference{ {Name: "/container/ee0103"}, @@ -189,7 +189,7 @@ func TestContainerGetInfo(t *testing.T) { aliases := []string{"a1", "a2"} cd := createContainerDataAndSetHandler( nil, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { h.On("GetSpec").Return( spec, nil, diff --git a/third_party/src/github.com/google/cadvisor/manager/manager.go b/third_party/src/github.com/google/cadvisor/manager/manager.go index 696e0badbdf..a472df5b9b9 100644 --- a/third_party/src/github.com/google/cadvisor/manager/manager.go +++ b/third_party/src/github.com/google/cadvisor/manager/manager.go @@ -130,11 +130,10 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn var percentiles *info.ContainerStatsPercentiles var samples []*info.ContainerStatsSample var stats []*info.ContainerStats - query = query.FillDefaults() percentiles, err = m.storageDriver.Percentiles( cinfo.Name, query.CpuUsagePercentiles, - query.MemoryUsagePercentages, + query.MemoryUsagePercentiles, ) if err != nil { return nil, err @@ -281,18 +280,18 @@ func (m *manager) detectContainers() error { } // Add the new containers. - for _, container := range added { - _, err = m.createContainer(container.Name) + for _, cont := range added { + _, err = m.createContainer(cont.Name) if err != nil { - return fmt.Errorf("Failed to create existing container: %s: %s", container.Name, err) + log.Printf("failed to create existing container: %s: %s", cont.Name, err) } } // Remove the old containers. - for _, container := range removed { - err = m.destroyContainer(container.Name) + for _, cont := range removed { + err = m.destroyContainer(cont.Name) if err != nil { - return fmt.Errorf("Failed to destroy existing container: %s: %s", container.Name, err) + log.Printf("failed to destroy existing container: %s: %s", cont.Name, err) } } diff --git a/third_party/src/github.com/google/cadvisor/manager/manager_test.go b/third_party/src/github.com/google/cadvisor/manager/manager_test.go index 99862c5db07..cf48f406c84 100644 --- a/third_party/src/github.com/google/cadvisor/manager/manager_test.go +++ b/third_party/src/github.com/google/cadvisor/manager/manager_test.go @@ -22,7 +22,6 @@ import ( "time" "github.com/google/cadvisor/container" - ctest "github.com/google/cadvisor/container/test" "github.com/google/cadvisor/info" itest "github.com/google/cadvisor/info/test" stest "github.com/google/cadvisor/storage/test" @@ -31,15 +30,15 @@ import ( func createManagerAndAddContainers( driver *stest.MockStorageDriver, containers []string, - f func(*ctest.MockContainerHandler), + f func(*container.MockContainerHandler), t *testing.T, ) *manager { if driver == nil { driver = &stest.MockStorageDriver{} } - factory := &ctest.FactoryForMockContainerHandler{ + factory := &container.FactoryForMockContainerHandler{ Name: "factoryForManager", - PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { + PrepareContainerHandlerFunc: func(name string, handler *container.MockContainerHandler) { handler.Name = name found := false for _, c := range containers { @@ -53,7 +52,8 @@ func createManagerAndAddContainers( f(handler) }, } - container.RegisterContainerHandlerFactory("/", factory) + container.ClearContainerHandlerFactories() + container.RegisterContainerHandlerFactory(factory) mif, err := New(driver) if err != nil { t.Fatal(err) @@ -81,11 +81,11 @@ func TestGetContainerInfo(t *testing.T) { NumStats: 256, NumSamples: 128, CpuUsagePercentiles: []int{10, 50, 90}, - MemoryUsagePercentages: []int{10, 80, 90}, + MemoryUsagePercentiles: []int{10, 80, 90}, } infosMap := make(map[string]*info.ContainerInfo, len(containers)) - handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers)) + handlerMap := make(map[string]*container.MockContainerHandler, len(containers)) for _, container := range containers { infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) @@ -95,7 +95,7 @@ func TestGetContainerInfo(t *testing.T) { m := createManagerAndAddContainers( driver, containers, - func(h *ctest.MockContainerHandler) { + func(h *container.MockContainerHandler) { cinfo := infosMap[h.Name] stats := cinfo.Stats samples := cinfo.Samples @@ -105,7 +105,7 @@ func TestGetContainerInfo(t *testing.T) { "Percentiles", h.Name, query.CpuUsagePercentiles, - query.MemoryUsagePercentages, + query.MemoryUsagePercentiles, ).Return( percentiles, nil, @@ -162,92 +162,3 @@ func TestGetContainerInfo(t *testing.T) { } } - -func TestGetContainerInfoWithDefaultValue(t *testing.T) { - containers := []string{ - "/c1", - "/c2", - } - - var query *info.ContainerInfoRequest - query = query.FillDefaults() - - infosMap := make(map[string]*info.ContainerInfo, len(containers)) - handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers)) - - for _, container := range containers { - infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second) - } - - driver := &stest.MockStorageDriver{} - m := createManagerAndAddContainers( - driver, - containers, - func(h *ctest.MockContainerHandler) { - cinfo := infosMap[h.Name] - stats := cinfo.Stats - samples := cinfo.Samples - percentiles := cinfo.StatsPercentiles - spec := cinfo.Spec - driver.On( - "Percentiles", - h.Name, - query.CpuUsagePercentiles, - query.MemoryUsagePercentages, - ).Return( - percentiles, - nil, - ) - - driver.On( - "Samples", - h.Name, - query.NumSamples, - ).Return( - samples, - nil, - ) - - driver.On( - "RecentStats", - h.Name, - query.NumStats, - ).Return( - stats, - nil, - ) - - h.On("ListContainers", container.LIST_SELF).Return( - []info.ContainerReference(nil), - nil, - ) - h.On("GetSpec").Return( - spec, - nil, - ) - handlerMap[h.Name] = h - }, - t, - ) - - returnedInfos := make(map[string]*info.ContainerInfo, len(containers)) - - for _, container := range containers { - // nil should give us default values - cinfo, err := m.GetContainerInfo(container, nil) - if err != nil { - t.Fatalf("Unable to get info for container %v: %v", container, err) - } - returnedInfos[container] = cinfo - } - - for container, handler := range handlerMap { - handler.AssertExpectations(t) - returned := returnedInfos[container] - expected := infosMap[container] - if !reflect.DeepEqual(returned, expected) { - t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected) - } - } - -} diff --git a/third_party/src/github.com/google/cadvisor/pages/containers.go b/third_party/src/github.com/google/cadvisor/pages/containers.go index b3302b171e6..8f4b95f2e60 100644 --- a/third_party/src/github.com/google/cadvisor/pages/containers.go +++ b/third_party/src/github.com/google/cadvisor/pages/containers.go @@ -19,6 +19,7 @@ import ( "fmt" "html/template" "log" + "math" "net/http" "net/url" "path" @@ -36,6 +37,7 @@ var funcMap = template.FuncMap{ "containerLink": containerLink, "printMask": printMask, "printCores": printCores, + "printShares": printShares, "printMegabytes": printMegabytes, "getMemoryUsage": getMemoryUsage, "getMemoryUsagePercent": getMemoryUsagePercent, @@ -57,6 +59,7 @@ type pageData struct { ResourcesAvailable bool CpuAvailable bool MemoryAvailable bool + NetworkAvailable bool } func init() { @@ -87,18 +90,12 @@ func containerLink(container info.ContainerReference, basenameOnly bool, cssClas return template.HTML(fmt.Sprintf("%s", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName)) } -func printMask(mask *info.CpuSpecMask, numCores int) interface{} { - // TODO(vmarmol): Detect this correctly. - // TODO(vmarmol): Support more than 64 cores. - rawMask := uint64(0) - if len(mask.Data) > 0 { - rawMask = mask.Data[0] - } +func printMask(mask string, numCores int) interface{} { masks := make([]string, numCores) - for i := uint(0); i < uint(numCores); i++ { + activeCores := getActiveCores(mask) + for i := 0; i < numCores; i++ { coreClass := "inactive-cpu" - // by default, all cores are active - if ((0x1<%d", coreClass, i) @@ -106,22 +103,49 @@ func printMask(mask *info.CpuSpecMask, numCores int) interface{} { return template.HTML(strings.Join(masks, " ")) } -func printCores(millicores *uint64) string { - // TODO(vmarmol): Detect this correctly - if *millicores > 1024*1000 { - return "unlimited" +func getActiveCores(mask string) map[int]bool { + activeCores := make(map[int]bool) + for _, corebits := range strings.Split(mask, ",") { + cores := strings.Split(corebits, "-") + if len(cores) == 1 { + index, err := strconv.Atoi(cores[0]) + if err != nil { + // Ignore malformed strings. + continue + } + activeCores[index] = true + } else if len(cores) == 2 { + start, err := strconv.Atoi(cores[0]) + if err != nil { + continue + } + end, err := strconv.Atoi(cores[1]) + if err != nil { + continue + } + for i := start; i <= end; i++ { + activeCores[i] = true + } + } } + return activeCores +} + +func printCores(millicores *uint64) string { cores := float64(*millicores) / 1000 return strconv.FormatFloat(cores, 'f', 3, 64) } +func printShares(shares *uint64) string { + return fmt.Sprintf("%d", *shares) +} + func toMegabytes(bytes uint64) float64 { return float64(bytes) / (1 << 20) } func printMegabytes(bytes uint64) string { - // TODO(vmarmol): Detect this correctly - if bytes > (100 << 30) { + if bytes >= math.MaxInt64 { return "unlimited" } megabytes := toMegabytes(bytes) @@ -139,18 +163,30 @@ func toMemoryPercent(usage uint64, spec *info.ContainerSpec, machine *info.Machi } func getMemoryUsage(stats []*info.ContainerStats) string { + if len(stats) == 0 { + return "0.0" + } return strconv.FormatFloat(toMegabytes((stats[len(stats)-1].Memory.Usage)), 'f', 2, 64) } func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + if len(stats) == 0 { + return 0 + } return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec, machine) } func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + if len(stats) == 0 { + return 0 + } return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec, machine) } func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + if len(stats) == 0 { + return 0 + } latestStats := stats[len(stats)-1].Memory return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) } @@ -204,6 +240,14 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) } } + networkStatsAvailable := false + for _, stat := range cont.Stats { + if stat.Network != nil { + networkStatsAvailable = true + break + } + } + data := &pageData{ ContainerName: displayName, // TODO(vmarmol): Only use strings for this. @@ -215,6 +259,7 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) ResourcesAvailable: cont.Spec.Cpu != nil || cont.Spec.Memory != nil, CpuAvailable: cont.Spec.Cpu != nil, MemoryAvailable: cont.Spec.Memory != nil, + NetworkAvailable: networkStatsAvailable, } err = pageTemplate.Execute(w, data) if err != nil { diff --git a/third_party/src/github.com/google/cadvisor/pages/containers_html.go b/third_party/src/github.com/google/cadvisor/pages/containers_html.go index 037aa3f4da6..5d311b7ddb8 100644 --- a/third_party/src/github.com/google/cadvisor/pages/containers_html.go +++ b/third_party/src/github.com/google/cadvisor/pages/containers_html.go @@ -16,145 +16,160 @@ package pages const containersHtmlTemplate = ` - - cAdvisor - Container {{.ContainerName}} - - + + cAdvisor - Container {{.ContainerName}} + + - - + + - + - - - - + + + + - - - -
- -
- - -
- {{if .Subcontainers}} -
- -
- {{range $subcontainer := .Subcontainers}} - {{containerLink $subcontainer false "list-group-item"}} + + + +
+ +
+ + +
+ {{if .Subcontainers}} +
+ +
+ {{range $subcontainer := .Subcontainers}} + {{containerLink $subcontainer false "list-group-item"}} + {{end}} +
+
+ {{end}} + {{if .ResourcesAvailable}} +
+ + {{if .CpuAvailable}} +
    +
  • CPU
  • + {{if .Spec.Cpu.Limit}} +
  • Shares {{printShares .Spec.Cpu.Limit}} shares
  • + {{end}} + {{if .Spec.Cpu.MaxLimit}} +
  • Max Limit {{printCores .Spec.Cpu.MaxLimit}} cores
  • + {{end}} + {{if .Spec.Cpu.Mask}} +
  • Allowed Cores {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}
  • + {{end}} +
+ {{end}} + {{if .MemoryAvailable}} +
    +
  • Memory
  • + {{if .Spec.Memory.Reservation}} +
  • Reservation {{printMegabytes .Spec.Memory.Reservation}} MB
  • + {{end}} + {{if .Spec.Memory.Limit}} +
  • Limit {{printMegabytes .Spec.Memory.Limit}} MB
  • + {{end}} + {{if .Spec.Memory.SwapLimit}} +
  • Swap Limit {{printMegabytes .Spec.Memory.SwapLimit}} MB
  • + {{end}} +
+ {{end}} +
+
+ +
+
+

Overview

+
+
+
+
+ {{if .CpuAvailable}} +
+
+

CPU

+
+
+

Total Usage

+
+

Usage per Core

+
+

Usage Breakdown

+
+
+
+ {{end}} + {{if .MemoryAvailable}} +
+
+

Memory

+
+
+

Total Usage

+
+
+
+

Usage Breakdown

+
+
+
+ Hot Memory +
+
+ Cold Memory +
+
+
+
+ {{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%) +
+
+

Page Faults

+
+
+
+ {{end}} + {{if .NetworkAvailable}} +
+
+

Network

+
+
+

Throughput

+
+
+
+

Errors

+
+
+
+ {{end}} +
{{end}}
-
- {{end}} - {{if .ResourcesAvailable}} -
- - {{if .CpuAvailable}} -
    -
  • CPU
  • - {{if .Spec.Cpu.Limit}} -
  • Limit {{printCores .Spec.Cpu.Limit}} cores
  • - {{end}} - {{if .Spec.Cpu.MaxLimit}} -
  • Max Limit {{printCores .Spec.Cpu.MaxLimit}} cores
  • - {{end}} - {{if .Spec.Cpu.Mask}} -
  • Allowed Cores {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}
  • - {{end}} -
- {{end}} - {{if .MemoryAvailable}} -
    -
  • Memory
  • - {{if .Spec.Memory.Reservation}} -
  • Reservation {{printMegabytes .Spec.Memory.Reservation}} MB
  • - {{end}} - {{if .Spec.Memory.Limit}} -
  • Limit {{printMegabytes .Spec.Memory.Limit}} MB
  • - {{end}} - {{if .Spec.Memory.SwapLimit}} -
  • Swap Limit {{printMegabytes .Spec.Memory.SwapLimit}} MB
  • - {{end}} -
- {{end}} -
-
- -
-
-

Overview

-
-
-
-
- {{if .CpuAvailable}} -
-
-

CPU

-
-
-

Total Usage

-
-

Usage per Core

-
-

Usage Breakdown

-
-
-
- {{end}} - {{if .MemoryAvailable}} -
-
-

Memory

-
-
-

Total Usage

-
-
-
-

Usage Breakdown

-
-
-
- Hot Memory -
-
- Cold Memory -
-
-
-
- {{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%) -
-
-

Page Faults

-
-
-
- {{end}} -
- {{end}} -
- - + + ` diff --git a/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go b/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go index 086c610b8f9..0858d5e104b 100644 --- a/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go +++ b/third_party/src/github.com/google/cadvisor/pages/static/containers_js.go @@ -90,9 +90,14 @@ function getMachineInfo(callback) { // Get the container stats for the specified container. function getStats(containerName, callback) { - $.getJSON("/api/v1.0/containers" + containerName, function(data) { - callback(data); + // Request 60s of container history and no samples. + var request = JSON.stringify({ + "num_stats": 60, + "num_samples": 0 }); + $.post("/api/v1.0/containers" + containerName, request, function(data) { + callback(data); + }, "json"); } // Draw the graph for CPU usage. @@ -163,7 +168,7 @@ function drawOverallUsage(elementId, machineInfo, containerInfo) { var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total; // Convert to millicores and take the percentage - cpuUsage = Math.round(((rawUsage / 1000000) / containerInfo.spec.cpu.limit) * 100); + cpuUsage = Math.round(((rawUsage / 1000000) / (machineInfo.num_cores * 1000)) * 100); if (cpuUsage > 100) { cpuUsage = 100; } @@ -219,6 +224,42 @@ function drawMemoryPageFaults(elementId, containerInfo) { drawLineChart(titles, data, elementId, "Faults"); } +// Draw the graph for network tx/rx bytes. +function drawNetworkBytes(elementId, machineInfo, stats) { + var titles = ["Time", "Tx bytes", "Rx bytes"]; + var data = []; + for (var i = 1; i < stats.stats.length; i++) { + var cur = stats.stats[i]; + var prev = stats.stats[i - 1]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push(cur.network.tx_bytes - prev.network.tx_bytes); + elements.push(cur.network.rx_bytes - prev.network.rx_bytes); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Bytes per second"); +} + +// Draw the graph for network errors +function drawNetworkErrors(elementId, machineInfo, stats) { + var titles = ["Time", "Tx", "Rx"]; + var data = []; + for (var i = 1; i < stats.stats.length; i++) { + var cur = stats.stats[i]; + var prev = stats.stats[i - 1]; + + // TODO(vmarmol): This assumes we sample every second, use the timestamps. + var elements = []; + elements.push(cur.timestamp); + elements.push(cur.network.tx_errors - prev.network.tx_errors); + elements.push(cur.network.rx_errors - prev.network.rx_errors); + data.push(elements); + } + drawLineChart(titles, data, elementId, "Errors per second"); +} + // Expects an array of closures to call. After each execution the JS runtime is given control back before continuing. // This function returns asynchronously function stepExecute(steps) { @@ -264,6 +305,14 @@ function drawCharts(machineInfo, containerInfo) { drawMemoryPageFaults("memory-page-faults-chart", containerInfo); }); + // Network. + steps.push(function() { + drawNetworkBytes("network-bytes-chart", machineInfo, containerInfo); + }); + steps.push(function() { + drawNetworkErrors("network-errors-chart", machineInfo, containerInfo); + }); + stepExecute(steps); } diff --git a/third_party/src/github.com/google/cadvisor/storage/cache/memcache.go b/third_party/src/github.com/google/cadvisor/storage/cache/memcache.go new file mode 100644 index 00000000000..fdc7d029695 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/cache/memcache.go @@ -0,0 +1,72 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "github.com/google/cadvisor/info" + "github.com/google/cadvisor/storage" + "github.com/google/cadvisor/storage/memory" +) + +type cachedStorageDriver struct { + maxNumStatsInCache int + maxNumSamplesInCache int + cache storage.StorageDriver + backend storage.StorageDriver +} + +func (self *cachedStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { + err := self.cache.AddStats(ref, stats) + if err != nil { + return err + } + err = self.backend.AddStats(ref, stats) + if err != nil { + return err + } + return nil +} + +func (self *cachedStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) { + if numStats < self.maxNumStatsInCache { + return self.cache.RecentStats(containerName, numStats) + } + return self.backend.RecentStats(containerName, numStats) +} + +func (self *cachedStorageDriver) Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) { + return self.backend.Percentiles(containerName, cpuUsagePercentiles, memUsagePercentiles) +} + +func (self *cachedStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) { + if numSamples < self.maxNumSamplesInCache { + return self.cache.Samples(containerName, numSamples) + } + return self.backend.Samples(containerName, numSamples) +} + +func (self *cachedStorageDriver) Close() error { + self.cache.Close() + return self.backend.Close() +} + +func MemoryCache(maxNumSamplesInCache, maxNumStatsInCache int, driver storage.StorageDriver) storage.StorageDriver { + return &cachedStorageDriver{ + maxNumStatsInCache: maxNumStatsInCache, + maxNumSamplesInCache: maxNumSamplesInCache, + cache: memory.New(maxNumSamplesInCache, maxNumStatsInCache), + backend: driver, + } +} diff --git a/third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go b/third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go new file mode 100644 index 00000000000..f77232dfed3 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go @@ -0,0 +1,78 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "testing" + + "github.com/google/cadvisor/storage" + "github.com/google/cadvisor/storage/memory" + "github.com/google/cadvisor/storage/test" +) + +func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { + maxSize := 200 + + var driver storage.StorageDriver + for N := 10; N < maxSize; N += 10 { + backend := memory.New(N*2, N*2) + driver = MemoryCache(N, N, backend) + f(driver, t) + } + +} + +func TestMaxMemoryUsage(t *testing.T) { + runStorageTest(test.StorageDriverTestMaxMemoryUsage, t) +} + +func TestSampleCpuUsage(t *testing.T) { + runStorageTest(test.StorageDriverTestSampleCpuUsage, t) +} + +func TestSamplesWithoutSample(t *testing.T) { + runStorageTest(test.StorageDriverTestSamplesWithoutSample, t) +} + +func TestPercentilesWithoutSample(t *testing.T) { + runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t) +} + +func TestPercentiles(t *testing.T) { + N := 100 + backend := memory.New(N*2, N*2) + driver := MemoryCache(N, N, backend) + test.StorageDriverTestPercentiles(driver, t) +} + +func TestRetrievePartialRecentStats(t *testing.T) { + runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t) +} + +func TestRetrieveAllRecentStats(t *testing.T) { + runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t) +} + +func TestNoRecentStats(t *testing.T) { + runStorageTest(test.StorageDriverTestNoRecentStats, t) +} + +func TestNoSamples(t *testing.T) { + runStorageTest(test.StorageDriverTestNoSamples, t) +} + +func TestPercentilesWithoutStats(t *testing.T) { + runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t) +} diff --git a/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go b/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go index 99cb92c718f..d9b38644469 100644 --- a/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go +++ b/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go @@ -22,7 +22,7 @@ import ( "github.com/google/cadvisor/info" "github.com/google/cadvisor/storage" - "github.com/influxdb/influxdb-go" + influxdb "github.com/influxdb/influxdb/client" ) type influxdbStorage struct { @@ -320,6 +320,9 @@ func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.C } func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) { + if numStats == 0 { + return nil, nil + } // TODO(dengnan): select only columns that we need // TODO(dengnan): escape names query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName) @@ -352,6 +355,9 @@ func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([] } func (self *influxdbStorage) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) { + if numSamples == 0 { + return nil, nil + } // TODO(dengnan): select only columns that we need // TODO(dengnan): escape names query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName) diff --git a/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go b/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go index c5770d35d1b..4605fa56d9c 100644 --- a/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go +++ b/third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go @@ -21,7 +21,7 @@ import ( "github.com/google/cadvisor/storage" "github.com/google/cadvisor/storage/test" - "github.com/influxdb/influxdb-go" + influxdb "github.com/influxdb/influxdb/client" ) func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { @@ -95,42 +95,64 @@ func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) { f(driver, t) } +// TODO(vmarmol): Don't skip these tests when Travis is fixed. + func TestSampleCpuUsage(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestSampleCpuUsage, t) } func TestRetrievePartialRecentStats(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t) } func TestSamplesWithoutSample(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestSamplesWithoutSample, t) } func TestRetrieveAllRecentStats(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t) } func TestNoRecentStats(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestNoRecentStats, t) } func TestNoSamples(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestNoSamples, t) } func TestPercentiles(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestPercentiles, t) } func TestMaxMemoryUsage(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestMaxMemoryUsage, t) } func TestPercentilesWithoutSample(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t) } func TestPercentilesWithoutStats(t *testing.T) { + t.SkipNow() runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t) } + +func TestRetrieveZeroStats(t *testing.T) { + t.SkipNow() + runStorageTest(test.StorageDriverTestRetrieveZeroRecentStats, t) +} + +func TestRetrieveZeroSamples(t *testing.T) { + t.SkipNow() + runStorageTest(test.StorageDriverTestRetrieveZeroSamples, t) +} diff --git a/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go b/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go index 394e48f6a5f..24b4a7319ba 100644 --- a/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go +++ b/third_party/src/github.com/google/cadvisor/storage/memory/memory_test.go @@ -73,3 +73,11 @@ func TestNoSamples(t *testing.T) { func TestPercentilesWithoutStats(t *testing.T) { runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t) } + +func TestRetrieveZeroStats(t *testing.T) { + runStorageTest(test.StorageDriverTestRetrieveZeroRecentStats, t) +} + +func TestRetrieveZeroSamples(t *testing.T) { + runStorageTest(test.StorageDriverTestRetrieveZeroSamples, t) +} diff --git a/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go b/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go index 8bad2c4d7a9..b9a66f808dc 100644 --- a/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go +++ b/third_party/src/github.com/google/cadvisor/storage/test/storagetests.go @@ -482,3 +482,61 @@ func StorageDriverTestPercentilesWithoutStats(driver storage.StorageDriver, t *t } } } + +func StorageDriverTestRetrieveZeroRecentStats(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + N := 100 + memTrace := make([]uint64, N) + cpuTrace := make([]uint64, N) + for i := 0; i < N; i++ { + memTrace[i] = uint64(i + 1) + cpuTrace[i] = uint64(1) + } + + ref := info.ContainerReference{ + Name: "container", + } + + trace := buildTrace(cpuTrace, memTrace, 1*time.Second) + + for _, stats := range trace { + driver.AddStats(ref, stats) + } + + recentStats, err := driver.RecentStats(ref.Name, 0) + if err != nil { + t.Fatal(err) + } + if len(recentStats) > 0 { + t.Errorf("RecentStats() returns %v stats when requests for 0 stats", len(recentStats)) + } +} + +func StorageDriverTestRetrieveZeroSamples(driver storage.StorageDriver, t *testing.T) { + defer driver.Close() + N := 100 + memTrace := make([]uint64, N) + cpuTrace := make([]uint64, N) + for i := 0; i < N; i++ { + memTrace[i] = uint64(i + 1) + cpuTrace[i] = uint64(1) + } + + ref := info.ContainerReference{ + Name: "container", + } + + trace := buildTrace(cpuTrace, memTrace, 1*time.Second) + + for _, stats := range trace { + driver.AddStats(ref, stats) + } + + samples, err := driver.Samples(ref.Name, 0) + if err != nil { + t.Fatal(err) + } + if len(samples) > 0 { + t.Errorf("RecentStats() returns %v stats when requests for 0 stats", len(samples)) + } +} diff --git a/third_party/src/github.com/google/cadvisor/utils/fs/fs.go b/third_party/src/github.com/google/cadvisor/utils/fs/fs.go new file mode 100644 index 00000000000..d5999a9b30b --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/fs/fs.go @@ -0,0 +1,44 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs + +import ( + "io" + "os" +) + +type osFS struct{} + +func (osFS) Open(name string) (File, error) { return os.Open(name) } +func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) } + +var fs FileSystem = osFS{} + +type FileSystem interface { + Open(name string) (File, error) +} + +type File interface { + io.ReadWriteCloser +} + +// Useful for tests. Not thread safe. +func ChangeFileSystem(filesystem FileSystem) { + fs = filesystem +} + +func Open(name string) (File, error) { + return fs.Open(name) +} diff --git a/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go b/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go new file mode 100644 index 00000000000..77a3f48734b --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go @@ -0,0 +1,35 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mockfs + +import "bytes" + +type FakeFile struct { + bytes.Buffer + Name string +} + +func (self *FakeFile) Close() error { + return nil +} + +func AddTextFile(mockfs *MockFileSystem, name, content string) *FakeFile { + f := &FakeFile{ + Name: name, + Buffer: *bytes.NewBufferString(content), + } + mockfs.EXPECT().Open(name).Return(f, nil).AnyTimes() + return f +} diff --git a/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go b/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go new file mode 100644 index 00000000000..93f08a686e4 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go @@ -0,0 +1,55 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Automatically generated by MockGen. DO NOT EDIT! +// Source: github.com/google/cadvisor/utils/fs (interfaces: FileSystem) + +package mockfs + +import ( + gomock "code.google.com/p/gomock/gomock" + fs "github.com/google/cadvisor/utils/fs" +) + +// Mock of FileSystem interface +type MockFileSystem struct { + ctrl *gomock.Controller + recorder *_MockFileSystemRecorder +} + +// Recorder for MockFileSystem (not exported) +type _MockFileSystemRecorder struct { + mock *MockFileSystem +} + +func NewMockFileSystem(ctrl *gomock.Controller) *MockFileSystem { + mock := &MockFileSystem{ctrl: ctrl} + mock.recorder = &_MockFileSystemRecorder{mock} + return mock +} + +func (_m *MockFileSystem) EXPECT() *_MockFileSystemRecorder { + return _m.recorder +} + +func (_m *MockFileSystem) Open(_param0 string) (fs.File, error) { + ret := _m.ctrl.Call(_m, "Open", _param0) + ret0, _ := ret[0].(fs.File) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockFileSystemRecorder) Open(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Open", arg0) +} diff --git a/third_party/src/github.com/google/cadvisor/utils/path.go b/third_party/src/github.com/google/cadvisor/utils/path.go new file mode 100644 index 00000000000..a7aceee6615 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/path.go @@ -0,0 +1,24 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import "os" + +func FileExists(file string) bool { + if _, err := os.Stat(file); err != nil { + return false + } + return true +} diff --git a/third_party/src/github.com/google/cadvisor/utils/procfs/doc.go b/third_party/src/github.com/google/cadvisor/utils/procfs/doc.go new file mode 100644 index 00000000000..763a556c553 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/procfs/doc.go @@ -0,0 +1,17 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// procfs contains several low level functions to read information from /proc +// filesystem, and also provides some utility functions like JiffiesToDuration. +package procfs diff --git a/third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go b/third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go new file mode 100644 index 00000000000..b36772a2525 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go @@ -0,0 +1,33 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +/* +#include +*/ +import "C" +import "time" + +var userHz uint64 + +func init() { + userHzLong := C.sysconf(C._SC_CLK_TCK) + userHz = uint64(userHzLong) +} + +func JiffiesToDuration(jiffies uint64) time.Duration { + d := jiffies * 1000000000 / userHz + return time.Duration(d) +} diff --git a/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go b/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go new file mode 100644 index 00000000000..560b0d43ae5 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go @@ -0,0 +1,87 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "bufio" + "fmt" + "io" + "strconv" + + "github.com/google/cadvisor/utils/fs" +) + +type ProcessSchedStat struct { + // Number of processes + NumProcesses int + + // Total time spent on the cpu (Unit: jiffy) + Running uint64 + + // Total time spent waiting on a runqueue (Unit: jiffy) + RunWait uint64 + + // # of timeslices run on this cpu (Unit: jiffy) + NumTimeSlices uint64 +} + +func readUint64List(r io.Reader) ([]uint64, error) { + ret := make([]uint64, 0, 4) + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanWords) + + for scanner.Scan() { + str := scanner.Text() + u, err := strconv.ParseUint(str, 10, 64) + if err != nil { + return nil, err + } + ret = append(ret, u) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return ret, nil +} + +// Add() read the schedstat of pid, and add stat to the fields +// in self parameters. This function is useful if one wants to read stats of +// a group of processes. +func (self *ProcessSchedStat) Add(pid int) error { + if self == nil { + return fmt.Errorf("nil stat") + } + + path := fmt.Sprintf("/proc/%d/schedstat", pid) + f, err := fs.Open(path) + if err != nil { + return err + } + defer f.Close() + v, err := readUint64List(f) + if err != nil { + return err + } + if len(v) < 3 { + return fmt.Errorf("only %v fields read from %v: %v", len(v), path, v) + } + self.Running += v[0] + self.RunWait += v[1] + self.NumTimeSlices += v[2] + self.NumProcesses++ + return nil +} diff --git a/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go b/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go new file mode 100644 index 00000000000..eae182ef399 --- /dev/null +++ b/third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go @@ -0,0 +1,57 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "fmt" + "reflect" + "testing" + + "code.google.com/p/gomock/gomock" + + "github.com/google/cadvisor/utils/fs" + "github.com/google/cadvisor/utils/fs/mockfs" +) + +func TestReadProcessSchedStat(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mfs := mockfs.NewMockFileSystem(mockCtrl) + + pid := 10 + + stat := &ProcessSchedStat{ + NumProcesses: 1, + Running: 100, + RunWait: 120, + NumTimeSlices: 130, + } + + path := fmt.Sprintf("/proc/%v/schedstat", pid) + content := fmt.Sprintf("%v %v %v\n", stat.Running, stat.RunWait, stat.NumTimeSlices) + mockfs.AddTextFile(mfs, path, content) + fs.ChangeFileSystem(mfs) + + receivedStat := &ProcessSchedStat{} + err := receivedStat.Add(pid) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(receivedStat, stat) { + t.Errorf("Received wrong schedstat: %+v", receivedStat) + } +} From 2e8020be8ce32670ebffbff814758fa33e3cac9a Mon Sep 17 00:00:00 2001 From: Dawn Chen Date: Wed, 30 Jul 2014 16:56:50 -0700 Subject: [PATCH 2/2] Update the entire cAdvisor package. There is a version mismatch issue which cause #707 --- pkg/client/containerinfo_test.go | 29 ++++++++++++++++------------- pkg/kubelet/kubelet.go | 4 ++-- third_party/deps.sh | 3 +-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pkg/client/containerinfo_test.go b/pkg/client/containerinfo_test.go index 4d4a04543b2..9a31b3ec19d 100644 --- a/pkg/client/containerinfo_test.go +++ b/pkg/client/containerinfo_test.go @@ -62,7 +62,6 @@ func testHTTPContainerInfoGetter( // So changing req after Get*Info would be a race. expectedReq := req // Fill any empty fields with default value - expectedReq = expectedReq.FillDefaults() if !reflect.DeepEqual(expectedReq, &receivedReq) { t.Errorf("received wrong request") } @@ -110,10 +109,11 @@ func testHTTPContainerInfoGetter( func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) { req := &info.ContainerInfoRequest{ - NumStats: 10, - NumSamples: 10, + NumStats: 10, + NumSamples: 10, + CpuUsagePercentiles: []int{20, 30}, + MemoryUsagePercentiles: []int{40, 50}, } - req = req.FillDefaults() cinfo := itest.GenerateRandomContainerInfo( "dockerIDWhichWillNotBeChecked", // docker ID 2, // Number of cores @@ -125,10 +125,11 @@ func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) { req := &info.ContainerInfoRequest{ - NumStats: 10, - NumSamples: 10, + NumStats: 10, + NumSamples: 10, + CpuUsagePercentiles: []int{20, 30}, + MemoryUsagePercentiles: []int{40, 50}, } - req = req.FillDefaults() cinfo := itest.GenerateRandomContainerInfo( "dockerIDWhichWillNotBeChecked", // docker ID 2, // Number of cores @@ -140,10 +141,11 @@ func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) { func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) { req := &info.ContainerInfoRequest{ - NumStats: 10, - NumSamples: 10, + NumStats: 10, + NumSamples: 10, + CpuUsagePercentiles: []int{20, 30}, + MemoryUsagePercentiles: []int{40, 50}, } - req = req.FillDefaults() cinfo := itest.GenerateRandomContainerInfo( "dockerIDWhichWillNotBeChecked", // docker ID 2, // Number of cores @@ -155,10 +157,11 @@ func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoWithError(t *testing.T) { req := &info.ContainerInfoRequest{ - NumStats: 10, - NumSamples: 10, + NumStats: 10, + NumSamples: 10, + CpuUsagePercentiles: []int{20, 30}, + MemoryUsagePercentiles: []int{40, 50}, } - req = req.FillDefaults() cinfo := itest.GenerateRandomContainerInfo( "dockerIDWhichWillNotBeChecked", // docker ID 2, // Number of cores diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index a756b8a1b95..70bd76f2f70 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -289,7 +289,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v ExposedPorts: exposedPorts, Hostname: container.Name, Image: container.Image, - Memory: uint64(container.Memory), + Memory: int64(container.Memory), CpuShares: int64(milliCPUToShares(container.CPU)), Volumes: volumes, WorkingDir: container.WorkingDir, @@ -545,7 +545,7 @@ func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.Conta ret := &info.ContainerInfoRequest{ NumStats: req.NumStats, CpuUsagePercentiles: req.CpuUsagePercentiles, - MemoryUsagePercentages: req.MemoryUsagePercentages, + MemoryUsagePercentiles: req.MemoryUsagePercentiles, } return ret } diff --git a/third_party/deps.sh b/third_party/deps.sh index a94467e1e79..05e7b6a7436 100755 --- a/third_party/deps.sh +++ b/third_party/deps.sh @@ -5,8 +5,7 @@ TOP_PACKAGES=" code.google.com/p/goauth2/compute/serviceaccount code.google.com/p/goauth2/oauth code.google.com/p/google-api-go-client/compute/v1 - github.com/google/cadvisor/info - github.com/google/cadvisor/client + github.com/google/cadvisor " DEP_PACKAGES="