mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 10:43:56 +00:00
commit
a6f907e128
@ -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
|
||||
|
@ -545,7 +545,7 @@ func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.Conta
|
||||
ret := &info.ContainerInfoRequest{
|
||||
NumStats: req.NumStats,
|
||||
CpuUsagePercentiles: req.CpuUsagePercentiles,
|
||||
MemoryUsagePercentages: req.MemoryUsagePercentages,
|
||||
MemoryUsagePercentiles: req.MemoryUsagePercentiles,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
3
third_party/deps.sh
vendored
3
third_party/deps.sh
vendored
@ -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="
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
135
third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go
vendored
Normal file
135
third_party/src/github.com/google/cadvisor/container/libcontainer/helpers.go
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package libcontainer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
cgroupfs "github.com/docker/libcontainer/cgroups/fs"
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/google/cadvisor/utils/fs"
|
||||
)
|
||||
|
||||
// Get stats of the specified container
|
||||
func GetStats(config *libcontainer.Config, state *libcontainer.State) (*info.ContainerStats, error) {
|
||||
// TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready.
|
||||
libcontainerStats, err := libcontainer.GetStats(config, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toContainerStats(libcontainerStats), nil
|
||||
}
|
||||
|
||||
func GetStatsCgroupOnly(cgroup *cgroups.Cgroup) (*info.ContainerStats, error) {
|
||||
s, err := cgroupfs.GetStats(cgroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toContainerStats(&libcontainer.ContainerStats{CgroupStats: s}), nil
|
||||
}
|
||||
|
||||
// Convert libcontainer stats to info.ContainerStats.
|
||||
func toContainerStats(libcontainerStats *libcontainer.ContainerStats) *info.ContainerStats {
|
||||
s := libcontainerStats.CgroupStats
|
||||
ret := new(info.ContainerStats)
|
||||
ret.Timestamp = time.Now()
|
||||
|
||||
if s != nil {
|
||||
ret.Cpu = new(info.CpuStats)
|
||||
ret.Cpu.Usage.User = s.CpuStats.CpuUsage.UsageInUsermode
|
||||
ret.Cpu.Usage.System = s.CpuStats.CpuUsage.UsageInKernelmode
|
||||
n := len(s.CpuStats.CpuUsage.PercpuUsage)
|
||||
ret.Cpu.Usage.PerCpu = make([]uint64, n)
|
||||
|
||||
ret.Cpu.Usage.Total = 0
|
||||
for i := 0; i < n; i++ {
|
||||
ret.Cpu.Usage.PerCpu[i] = s.CpuStats.CpuUsage.PercpuUsage[i]
|
||||
ret.Cpu.Usage.Total += s.CpuStats.CpuUsage.PercpuUsage[i]
|
||||
}
|
||||
ret.Memory = new(info.MemoryStats)
|
||||
ret.Memory.Usage = s.MemoryStats.Usage
|
||||
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
|
||||
ret.Memory.ContainerData.Pgfault = v
|
||||
ret.Memory.HierarchicalData.Pgfault = v
|
||||
}
|
||||
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
|
||||
ret.Memory.ContainerData.Pgmajfault = v
|
||||
ret.Memory.HierarchicalData.Pgmajfault = v
|
||||
}
|
||||
if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok {
|
||||
ret.Memory.WorkingSet = ret.Memory.Usage - v
|
||||
if v, ok := s.MemoryStats.Stats["total_active_file"]; ok {
|
||||
ret.Memory.WorkingSet -= v
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(vishh): Perform a deep copy or alias libcontainer network stats.
|
||||
if libcontainerStats.NetworkStats != nil {
|
||||
ret.Network = (*info.NetworkStats)(libcontainerStats.NetworkStats)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Given a container name, returns the parent and name of the container to be fed to libcontainer.
|
||||
func SplitName(containerName string) (string, string, error) {
|
||||
parent, id := path.Split(containerName)
|
||||
cgroupSelf, err := fs.Open("/proc/1/cgroup")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
scanner := bufio.NewScanner(cgroupSelf)
|
||||
|
||||
// Find how nested we are. Libcontainer takes container names relative to the current process.
|
||||
subsys := []string{"memory", "cpu"}
|
||||
nestedLevels := 0
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
elems := strings.Split(line, ":")
|
||||
if len(elems) < 3 {
|
||||
continue
|
||||
}
|
||||
for _, s := range subsys {
|
||||
if elems[1] == s {
|
||||
if elems[2] == "/" {
|
||||
// We're running at root, no nesting.
|
||||
nestedLevels = 0
|
||||
} else {
|
||||
// Count how deeply nested we are.
|
||||
nestedLevels = strings.Count(elems[2], "/")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if nestedLevels > 0 {
|
||||
// we are running inside a docker container
|
||||
upperLevel := strings.Repeat("../", nestedLevels)
|
||||
parent = filepath.Join(upperLevel, parent)
|
||||
}
|
||||
|
||||
// Strip the last "/"
|
||||
if parent[len(parent)-1] == '/' {
|
||||
parent = parent[:len(parent)-1]
|
||||
}
|
||||
|
||||
return parent, id, nil
|
||||
}
|
90
third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go
vendored
Normal file
90
third_party/src/github.com/google/cadvisor/container/libcontainer/helpers_test.go
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package libcontainer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/gomock/gomock"
|
||||
|
||||
"github.com/google/cadvisor/utils/fs"
|
||||
"github.com/google/cadvisor/utils/fs/mockfs"
|
||||
)
|
||||
|
||||
var initCgroupsToParentAndID = []struct {
|
||||
InitCgroupFileContent string
|
||||
ContainerPath string
|
||||
Parent string
|
||||
Id string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
`
|
||||
11:name=systemd:/
|
||||
10:hugetlb:/
|
||||
9:perf_event:/
|
||||
8:blkio:/
|
||||
7:freezer:/
|
||||
6:devices:/
|
||||
5:memory:/
|
||||
4:cpuacct:/
|
||||
3:cpu:/
|
||||
2:cpuset:/
|
||||
`,
|
||||
"/",
|
||||
"",
|
||||
"",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
`
|
||||
11:name=systemd:/
|
||||
10:hugetlb:/
|
||||
9:perf_event:/
|
||||
8:blkio:/
|
||||
7:freezer:/
|
||||
6:devices:/
|
||||
5:memory:/docker/hash
|
||||
4:cpuacct:/
|
||||
3:cpu:/docker/hash
|
||||
2:cpuset:/
|
||||
`,
|
||||
"/parent/id",
|
||||
"../../parent",
|
||||
"id",
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
func TestSplitName(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
for _, testCase := range initCgroupsToParentAndID {
|
||||
mfs := mockfs.NewMockFileSystem(mockCtrl)
|
||||
mockfs.AddTextFile(mfs, "/proc/1/cgroup", testCase.InitCgroupFileContent)
|
||||
fs.ChangeFileSystem(mfs)
|
||||
parent, id, err := SplitName(testCase.ContainerPath)
|
||||
if testCase.Error != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("did not receive expected error.\ncontent:%v\n, path:%v\n, expected error:%v\n", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Error)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if testCase.Parent != parent || testCase.Id != id {
|
||||
t.Errorf("unexpected parent or id:\ncontent:%v\npath:%v\nexpected parent: %v; recevied parent: %v;\nexpected id: %v; received id: %v", testCase.InitCgroupFileContent, testCase.ContainerPath, testCase.Parent, parent, testCase.Id, id)
|
||||
}
|
||||
}
|
||||
}
|
91
third_party/src/github.com/google/cadvisor/container/mock.go
vendored
Normal file
91
third_party/src/github.com/google/cadvisor/container/mock.go
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// This struct mocks a container handler.
|
||||
type MockContainerHandler struct {
|
||||
mock.Mock
|
||||
Name string
|
||||
Aliases []string
|
||||
}
|
||||
|
||||
// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases.
|
||||
// Otherwise, it will use the value provided by .On().Return().
|
||||
func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) {
|
||||
if len(self.Name) > 0 {
|
||||
var aliases []string
|
||||
if len(self.Aliases) > 0 {
|
||||
aliases = make([]string, len(self.Aliases))
|
||||
copy(aliases, self.Aliases)
|
||||
}
|
||||
return info.ContainerReference{
|
||||
Name: self.Name,
|
||||
Aliases: aliases,
|
||||
}, nil
|
||||
}
|
||||
args := self.Called()
|
||||
return args.Get(0).(info.ContainerReference), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) GetSpec() (*info.ContainerSpec, error) {
|
||||
args := self.Called()
|
||||
return args.Get(0).(*info.ContainerSpec), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) {
|
||||
args := self.Called()
|
||||
return args.Get(0).(*info.ContainerStats), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) ListContainers(listType ListType) ([]info.ContainerReference, error) {
|
||||
args := self.Called(listType)
|
||||
return args.Get(0).([]info.ContainerReference), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) ListThreads(listType ListType) ([]int, error) {
|
||||
args := self.Called(listType)
|
||||
return args.Get(0).([]int), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) ListProcesses(listType ListType) ([]int, error) {
|
||||
args := self.Called(listType)
|
||||
return args.Get(0).([]int), args.Error(1)
|
||||
}
|
||||
|
||||
type FactoryForMockContainerHandler struct {
|
||||
Name string
|
||||
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)
|
||||
}
|
||||
|
||||
func (self *FactoryForMockContainerHandler) String() string {
|
||||
return self.Name
|
||||
}
|
||||
|
||||
func (self *FactoryForMockContainerHandler) NewContainerHandler(name string) (ContainerHandler, error) {
|
||||
handler := &MockContainerHandler{}
|
||||
if self.PrepareContainerHandlerFunc != nil {
|
||||
self.PrepareContainerHandlerFunc(name, handler)
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func (self *FactoryForMockContainerHandler) CanHandle(name string) bool {
|
||||
return true
|
||||
}
|
96
third_party/src/github.com/google/cadvisor/container/raw/factory.go
vendored
Normal file
96
third_party/src/github.com/google/cadvisor/container/raw/factory.go
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/google/cadvisor/container"
|
||||
"github.com/google/cadvisor/info"
|
||||
)
|
||||
|
||||
type cgroupSubsystems struct {
|
||||
// Cgroup subsystem mounts.
|
||||
mounts []cgroups.Mount
|
||||
|
||||
// Cgroup subsystem to their mount location.
|
||||
mountPoints map[string]string
|
||||
}
|
||||
|
||||
type rawFactory struct {
|
||||
// Factory for machine information.
|
||||
machineInfoFactory info.MachineInfoFactory
|
||||
cgroupSubsystems *cgroupSubsystems
|
||||
}
|
||||
|
||||
func (self *rawFactory) String() string {
|
||||
return "raw"
|
||||
}
|
||||
|
||||
func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) {
|
||||
return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory)
|
||||
}
|
||||
|
||||
// The raw factory can handle any container.
|
||||
func (self *rawFactory) CanHandle(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func Register(machineInfoFactory info.MachineInfoFactory) error {
|
||||
// Get all cgroup mounts.
|
||||
allCgroups, err := cgroups.GetCgroupMounts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(allCgroups) == 0 {
|
||||
return fmt.Errorf("failed to find cgroup mounts for the raw factory")
|
||||
}
|
||||
|
||||
// Trim the mounts to only the subsystems we care about.
|
||||
supportedCgroups := make([]cgroups.Mount, 0, len(allCgroups))
|
||||
mountPoints := make(map[string]string, len(allCgroups))
|
||||
for _, mount := range allCgroups {
|
||||
for _, subsystem := range mount.Subsystems {
|
||||
if _, ok := supportedSubsystems[subsystem]; ok {
|
||||
supportedCgroups = append(supportedCgroups, mount)
|
||||
mountPoints[subsystem] = mount.Mountpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(supportedCgroups) == 0 {
|
||||
return fmt.Errorf("failed to find supported cgroup mounts for the raw factory")
|
||||
}
|
||||
|
||||
log.Printf("Registering Raw factory")
|
||||
factory := &rawFactory{
|
||||
machineInfoFactory: machineInfoFactory,
|
||||
cgroupSubsystems: &cgroupSubsystems{
|
||||
mounts: supportedCgroups,
|
||||
mountPoints: mountPoints,
|
||||
},
|
||||
}
|
||||
container.RegisterContainerHandlerFactory(factory)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cgroup subsystems we support listing (should be the minimal set we need stats from).
|
||||
var supportedSubsystems map[string]struct{} = map[string]struct{}{
|
||||
"cpu": {},
|
||||
"cpuacct": {},
|
||||
"memory": {},
|
||||
"cpuset": {},
|
||||
}
|
206
third_party/src/github.com/google/cadvisor/container/raw/handler.go
vendored
Normal file
206
third_party/src/github.com/google/cadvisor/container/raw/handler.go
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package raw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/docker/libcontainer/cgroups/fs"
|
||||
"github.com/google/cadvisor/container"
|
||||
"github.com/google/cadvisor/container/libcontainer"
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/google/cadvisor/utils"
|
||||
)
|
||||
|
||||
type rawContainerHandler struct {
|
||||
name string
|
||||
cgroup *cgroups.Cgroup
|
||||
cgroupSubsystems *cgroupSubsystems
|
||||
machineInfoFactory info.MachineInfoFactory
|
||||
}
|
||||
|
||||
func newRawContainerHandler(name string, cgroupSubsystems *cgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) {
|
||||
parent, id, err := libcontainer.SplitName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rawContainerHandler{
|
||||
name: name,
|
||||
cgroup: &cgroups.Cgroup{
|
||||
Parent: parent,
|
||||
Name: id,
|
||||
},
|
||||
cgroupSubsystems: cgroupSubsystems,
|
||||
machineInfoFactory: machineInfoFactory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, error) {
|
||||
// We only know the container by its one name.
|
||||
return info.ContainerReference{
|
||||
Name: self.name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readString(path string, file string) string {
|
||||
cgroupFile := filepath.Join(path, file)
|
||||
|
||||
// Ignore non-existent files
|
||||
if !utils.FileExists(cgroupFile) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Read
|
||||
out, err := ioutil.ReadFile(cgroupFile)
|
||||
if err != nil {
|
||||
log.Printf("raw driver: Failed to read %q: %s", cgroupFile, err)
|
||||
return ""
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func readInt64(path string, file string) uint64 {
|
||||
out := readString(path, file)
|
||||
if out == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
val, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("raw driver: Failed to parse in %q from file %q: %s", out, filepath.Join(path, file), err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) GetSpec() (*info.ContainerSpec, error) {
|
||||
spec := new(info.ContainerSpec)
|
||||
|
||||
// The raw driver assumes unified hierarchy containers.
|
||||
|
||||
// Get machine info.
|
||||
mi, err := self.machineInfoFactory.GetMachineInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// CPU.
|
||||
cpuRoot, ok := self.cgroupSubsystems.mountPoints["cpu"]
|
||||
if ok {
|
||||
cpuRoot = filepath.Join(cpuRoot, self.name)
|
||||
if utils.FileExists(cpuRoot) {
|
||||
spec.Cpu = new(info.CpuSpec)
|
||||
spec.Cpu.Limit = readInt64(cpuRoot, "cpu.shares")
|
||||
}
|
||||
}
|
||||
|
||||
// Cpu Mask.
|
||||
// This will fail for non-unified hierarchies. We'll return the whole machine mask in that case.
|
||||
cpusetRoot, ok := self.cgroupSubsystems.mountPoints["cpuset"]
|
||||
if ok {
|
||||
if spec.Cpu == nil {
|
||||
spec.Cpu = new(info.CpuSpec)
|
||||
}
|
||||
cpusetRoot = filepath.Join(cpusetRoot, self.name)
|
||||
if utils.FileExists(cpusetRoot) {
|
||||
spec.Cpu.Mask = readString(cpusetRoot, "cpuset.cpus")
|
||||
if spec.Cpu.Mask == "" {
|
||||
spec.Cpu.Mask = fmt.Sprintf("0-%d", mi.NumCores-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memory.
|
||||
memoryRoot, ok := self.cgroupSubsystems.mountPoints["memory"]
|
||||
if ok {
|
||||
memoryRoot = filepath.Join(memoryRoot, self.name)
|
||||
if utils.FileExists(memoryRoot) {
|
||||
spec.Memory = new(info.MemorySpec)
|
||||
spec.Memory.Limit = readInt64(memoryRoot, "memory.limit_in_bytes")
|
||||
spec.Memory.SwapLimit = readInt64(memoryRoot, "memory.memsw.limit_in_bytes")
|
||||
}
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) GetStats() (stats *info.ContainerStats, err error) {
|
||||
return libcontainer.GetStatsCgroupOnly(self.cgroup)
|
||||
}
|
||||
|
||||
// Lists all directories under "path" and outputs the results as children of "parent".
|
||||
func listDirectories(path string, parent string, recursive bool, output map[string]struct{}) error {
|
||||
// Ignore if this hierarchy does not exist.
|
||||
if !utils.FileExists(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
// We only grab directories.
|
||||
if entry.IsDir() {
|
||||
name := filepath.Join(parent, entry.Name())
|
||||
output[name] = struct{}{}
|
||||
|
||||
// List subcontainers if asked to.
|
||||
if recursive {
|
||||
err := listDirectories(filepath.Join(path, entry.Name()), name, true, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
|
||||
containers := make(map[string]struct{}, 16)
|
||||
for _, subsystem := range self.cgroupSubsystems.mounts {
|
||||
err := listDirectories(filepath.Join(subsystem.Mountpoint, self.name), self.name, listType == container.LIST_RECURSIVE, containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Make into container references.
|
||||
ret := make([]info.ContainerReference, 0, len(containers))
|
||||
for cont := range containers {
|
||||
ret = append(ret, info.ContainerReference{
|
||||
Name: cont,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
|
||||
// TODO(vmarmol): Implement
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
|
||||
return fs.GetPids(self.cgroup)
|
||||
}
|
10
third_party/src/github.com/google/cadvisor/deploy/Dockerfile
vendored
Normal file
10
third_party/src/github.com/google/cadvisor/deploy/Dockerfile
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
FROM scratch
|
||||
MAINTAINER dengnan@google.com vmarmol@google.com
|
||||
|
||||
# NOTE: Run prepare.sh before building this Docker image.
|
||||
|
||||
# Grab cadvisor from the staging directory.
|
||||
ADD cadvisor /usr/bin/cadvisor
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/usr/bin/cadvisor"]
|
7
third_party/src/github.com/google/cadvisor/deploy/prepare.sh
vendored
Normal file
7
third_party/src/github.com/google/cadvisor/deploy/prepare.sh
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Statically build cAdvisor from source and stage it.
|
||||
go build --ldflags '-extldflags "-static"' github.com/google/cadvisor
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -15,4 +15,4 @@
|
||||
package info
|
||||
|
||||
// Version of cAdvisor.
|
||||
const VERSION = "0.1.3"
|
||||
const VERSION = "0.2.1"
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@ -36,6 +37,7 @@ var funcMap = template.FuncMap{
|
||||
"containerLink": containerLink,
|
||||
"printMask": printMask,
|
||||
"printCores": printCores,
|
||||
"printShares": printShares,
|
||||
"printMegabytes": printMegabytes,
|
||||
"getMemoryUsage": getMemoryUsage,
|
||||
"getMemoryUsagePercent": getMemoryUsagePercent,
|
||||
@ -57,6 +59,7 @@ type pageData struct {
|
||||
ResourcesAvailable bool
|
||||
CpuAvailable bool
|
||||
MemoryAvailable bool
|
||||
NetworkAvailable bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -87,18 +90,12 @@ func containerLink(container info.ContainerReference, basenameOnly bool, cssClas
|
||||
return template.HTML(fmt.Sprintf("<a class=\"%s\" href=\"%s%s\">%s</a>", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName))
|
||||
}
|
||||
|
||||
func printMask(mask *info.CpuSpecMask, numCores int) interface{} {
|
||||
// TODO(vmarmol): Detect this correctly.
|
||||
// TODO(vmarmol): Support more than 64 cores.
|
||||
rawMask := uint64(0)
|
||||
if len(mask.Data) > 0 {
|
||||
rawMask = mask.Data[0]
|
||||
}
|
||||
func printMask(mask string, numCores int) interface{} {
|
||||
masks := make([]string, numCores)
|
||||
for i := uint(0); i < uint(numCores); i++ {
|
||||
activeCores := getActiveCores(mask)
|
||||
for i := 0; i < numCores; i++ {
|
||||
coreClass := "inactive-cpu"
|
||||
// by default, all cores are active
|
||||
if ((0x1<<i)&rawMask) != 0 || len(mask.Data) == 0 {
|
||||
if activeCores[i] {
|
||||
coreClass = "active-cpu"
|
||||
}
|
||||
masks[i] = fmt.Sprintf("<span class=\"%s\">%d</span>", coreClass, i)
|
||||
@ -106,22 +103,49 @@ func printMask(mask *info.CpuSpecMask, numCores int) interface{} {
|
||||
return template.HTML(strings.Join(masks, " "))
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -16,145 +16,160 @@ package pages
|
||||
|
||||
const containersHtmlTemplate = `
|
||||
<html>
|
||||
<head>
|
||||
<title>cAdvisor - Container {{.ContainerName}}</title>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||
<head>
|
||||
<title>cAdvisor - Container {{.ContainerName}}</title>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
|
||||
|
||||
<link rel="stylesheet" href="/static/containers.css">
|
||||
<link rel="stylesheet" href="/static/containers.css">
|
||||
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
|
||||
|
||||
<script type="text/javascript" src="/static/containers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container theme-showcase" >
|
||||
<div class="col-sm-12" id="logo">
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h1>{{.ContainerName}}</h1>
|
||||
</div>
|
||||
<ol class="breadcrumb">
|
||||
{{range $parentContainer := .ParentContainers}}
|
||||
<li>{{containerLink $parentContainer true ""}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</div>
|
||||
{{if .Subcontainers}}
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Subcontainers</h3>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
{{range $subcontainer := .Subcontainers}}
|
||||
{{containerLink $subcontainer false "list-group-item"}}
|
||||
<script type="text/javascript" src="/static/containers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container theme-showcase" >
|
||||
<div class="col-sm-12" id="logo">
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h1>{{.ContainerName}}</h1>
|
||||
</div>
|
||||
<ol class="breadcrumb">
|
||||
{{range $parentContainer := .ParentContainers}}
|
||||
<li>{{containerLink $parentContainer true ""}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</div>
|
||||
{{if .Subcontainers}}
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Subcontainers</h3>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
{{range $subcontainer := .Subcontainers}}
|
||||
{{containerLink $subcontainer false "list-group-item"}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .ResourcesAvailable}}
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Isolation</h3>
|
||||
</div>
|
||||
{{if .CpuAvailable}}
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active isolation-title panel-title">CPU</li>
|
||||
{{if .Spec.Cpu.Limit}}
|
||||
<li class="list-group-item"><span class="stat-label">Shares</span> {{printShares .Spec.Cpu.Limit}} <span class="unit-label">shares</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Cpu.MaxLimit}}
|
||||
<li class="list-group-item"><span class="stat-label">Max Limit</span> {{printCores .Spec.Cpu.MaxLimit}} <span class="unit-label">cores</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Cpu.Mask}}
|
||||
<li class="list-group-item"><span class="stat-label">Allowed Cores</span> {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{if .MemoryAvailable}}
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active isolation-title panel-title">Memory</li>
|
||||
{{if .Spec.Memory.Reservation}}
|
||||
<li class="list-group-item"><span class="stat-label">Reservation</span> {{printMegabytes .Spec.Memory.Reservation}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Memory.Limit}}
|
||||
<li class="list-group-item"><span class="stat-label">Limit</span> {{printMegabytes .Spec.Memory.Limit}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Memory.SwapLimit}}
|
||||
<li class="list-group-item"><span class="stat-label">Swap Limit</span> {{printMegabytes .Spec.Memory.SwapLimit}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Usage</h3>
|
||||
</div>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Overview</h3>
|
||||
</div>
|
||||
<div id="usage-gauge" class="panel-body">
|
||||
</div>
|
||||
</div>
|
||||
{{if .CpuAvailable}}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">CPU</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Total Usage</h4>
|
||||
<div id="cpu-total-usage-chart"></div>
|
||||
<h4>Usage per Core</h4>
|
||||
<div id="cpu-per-core-usage-chart"></div>
|
||||
<h4>Usage Breakdown</h4>
|
||||
<div id="cpu-usage-breakdown-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .MemoryAvailable}}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Memory</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Total Usage</h4>
|
||||
<div id="memory-usage-chart"></div>
|
||||
<br/>
|
||||
<div class="row col-sm-12">
|
||||
<h4>Usage Breakdown</h4>
|
||||
<div class="col-sm-9">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-danger" style="width: {{getHotMemoryPercent .Spec .Stats .MachineInfo}}%">
|
||||
<span class="sr-only">Hot Memory</span>
|
||||
</div>
|
||||
<div class="progress-bar progress-bar-info" style="width: {{getColdMemoryPercent .Spec .Stats .MachineInfo}}%">
|
||||
<span class="sr-only">Cold Memory</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)
|
||||
</div>
|
||||
</div>
|
||||
<h4>Page Faults</h4>
|
||||
<div id="memory-page-faults-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .NetworkAvailable}}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Network</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Throughput</h4>
|
||||
<div id="network-bytes-chart"></div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Errors</h4>
|
||||
<div id="network-errors-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .ResourcesAvailable}}
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Isolation</h3>
|
||||
</div>
|
||||
{{if .CpuAvailable}}
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active isolation-title panel-title">CPU</li>
|
||||
{{if .Spec.Cpu.Limit}}
|
||||
<li class="list-group-item"><span class="stat-label">Limit</span> {{printCores .Spec.Cpu.Limit}} <span class="unit-label">cores</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Cpu.MaxLimit}}
|
||||
<li class="list-group-item"><span class="stat-label">Max Limit</span> {{printCores .Spec.Cpu.MaxLimit}} <span class="unit-label">cores</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Cpu.Mask}}
|
||||
<li class="list-group-item"><span class="stat-label">Allowed Cores</span> {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{if .MemoryAvailable}}
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item active isolation-title panel-title">Memory</li>
|
||||
{{if .Spec.Memory.Reservation}}
|
||||
<li class="list-group-item"><span class="stat-label">Reservation</span> {{printMegabytes .Spec.Memory.Reservation}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Memory.Limit}}
|
||||
<li class="list-group-item"><span class="stat-label">Limit</span> {{printMegabytes .Spec.Memory.Limit}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
{{if .Spec.Memory.SwapLimit}}
|
||||
<li class="list-group-item"><span class="stat-label">Swap Limit</span> {{printMegabytes .Spec.Memory.SwapLimit}} <span class="unit-label">MB</span></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div class="page-header">
|
||||
<h3>Usage</h3>
|
||||
</div>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Overview</h3>
|
||||
</div>
|
||||
<div id="usage-gauge" class="panel-body">
|
||||
</div>
|
||||
</div>
|
||||
{{if .CpuAvailable}}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">CPU</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Total Usage</h4>
|
||||
<div id="cpu-total-usage-chart"></div>
|
||||
<h4>Usage per Core</h4>
|
||||
<div id="cpu-per-core-usage-chart"></div>
|
||||
<h4>Usage Breakdown</h4>
|
||||
<div id="cpu-usage-breakdown-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .MemoryAvailable}}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Memory</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>Total Usage</h4>
|
||||
<div id="memory-usage-chart"></div>
|
||||
<br/>
|
||||
<div class="row col-sm-12">
|
||||
<h4>Usage Breakdown</h4>
|
||||
<div class="col-sm-9">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-danger" style="width: {{getHotMemoryPercent .Spec .Stats .MachineInfo}}%">
|
||||
<span class="sr-only">Hot Memory</span>
|
||||
</div>
|
||||
<div class="progress-bar progress-bar-info" style="width: {{getColdMemoryPercent .Spec .Stats .MachineInfo}}%">
|
||||
<span class="sr-only">Cold Memory</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)
|
||||
</div>
|
||||
</div>
|
||||
<h4>Page Faults</h4>
|
||||
<div id="memory-page-faults-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}});
|
||||
</script>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
72
third_party/src/github.com/google/cadvisor/storage/cache/memcache.go
vendored
Normal file
72
third_party/src/github.com/google/cadvisor/storage/cache/memcache.go
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/google/cadvisor/info"
|
||||
"github.com/google/cadvisor/storage"
|
||||
"github.com/google/cadvisor/storage/memory"
|
||||
)
|
||||
|
||||
type cachedStorageDriver struct {
|
||||
maxNumStatsInCache int
|
||||
maxNumSamplesInCache int
|
||||
cache storage.StorageDriver
|
||||
backend storage.StorageDriver
|
||||
}
|
||||
|
||||
func (self *cachedStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||
err := self.cache.AddStats(ref, stats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = self.backend.AddStats(ref, stats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *cachedStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
|
||||
if numStats < self.maxNumStatsInCache {
|
||||
return self.cache.RecentStats(containerName, numStats)
|
||||
}
|
||||
return self.backend.RecentStats(containerName, numStats)
|
||||
}
|
||||
|
||||
func (self *cachedStorageDriver) Percentiles(containerName string, cpuUsagePercentiles []int, memUsagePercentiles []int) (*info.ContainerStatsPercentiles, error) {
|
||||
return self.backend.Percentiles(containerName, cpuUsagePercentiles, memUsagePercentiles)
|
||||
}
|
||||
|
||||
func (self *cachedStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||
if numSamples < self.maxNumSamplesInCache {
|
||||
return self.cache.Samples(containerName, numSamples)
|
||||
}
|
||||
return self.backend.Samples(containerName, numSamples)
|
||||
}
|
||||
|
||||
func (self *cachedStorageDriver) Close() error {
|
||||
self.cache.Close()
|
||||
return self.backend.Close()
|
||||
}
|
||||
|
||||
func MemoryCache(maxNumSamplesInCache, maxNumStatsInCache int, driver storage.StorageDriver) storage.StorageDriver {
|
||||
return &cachedStorageDriver{
|
||||
maxNumStatsInCache: maxNumStatsInCache,
|
||||
maxNumSamplesInCache: maxNumSamplesInCache,
|
||||
cache: memory.New(maxNumSamplesInCache, maxNumStatsInCache),
|
||||
backend: driver,
|
||||
}
|
||||
}
|
78
third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go
vendored
Normal file
78
third_party/src/github.com/google/cadvisor/storage/cache/memcache_test.go
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cadvisor/storage"
|
||||
"github.com/google/cadvisor/storage/memory"
|
||||
"github.com/google/cadvisor/storage/test"
|
||||
)
|
||||
|
||||
func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
|
||||
maxSize := 200
|
||||
|
||||
var driver storage.StorageDriver
|
||||
for N := 10; N < maxSize; N += 10 {
|
||||
backend := memory.New(N*2, N*2)
|
||||
driver = MemoryCache(N, N, backend)
|
||||
f(driver, t)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMaxMemoryUsage(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
|
||||
}
|
||||
|
||||
func TestSampleCpuUsage(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
|
||||
}
|
||||
|
||||
func TestSamplesWithoutSample(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
|
||||
}
|
||||
|
||||
func TestPercentilesWithoutSample(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
|
||||
}
|
||||
|
||||
func TestPercentiles(t *testing.T) {
|
||||
N := 100
|
||||
backend := memory.New(N*2, N*2)
|
||||
driver := MemoryCache(N, N, backend)
|
||||
test.StorageDriverTestPercentiles(driver, t)
|
||||
}
|
||||
|
||||
func TestRetrievePartialRecentStats(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
|
||||
}
|
||||
|
||||
func TestRetrieveAllRecentStats(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
|
||||
}
|
||||
|
||||
func TestNoRecentStats(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestNoRecentStats, t)
|
||||
}
|
||||
|
||||
func TestNoSamples(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestNoSamples, t)
|
||||
}
|
||||
|
||||
func TestPercentilesWithoutStats(t *testing.T) {
|
||||
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
44
third_party/src/github.com/google/cadvisor/utils/fs/fs.go
vendored
Normal file
44
third_party/src/github.com/google/cadvisor/utils/fs/fs.go
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type osFS struct{}
|
||||
|
||||
func (osFS) Open(name string) (File, error) { return os.Open(name) }
|
||||
func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
||||
|
||||
var fs FileSystem = osFS{}
|
||||
|
||||
type FileSystem interface {
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
// Useful for tests. Not thread safe.
|
||||
func ChangeFileSystem(filesystem FileSystem) {
|
||||
fs = filesystem
|
||||
}
|
||||
|
||||
func Open(name string) (File, error) {
|
||||
return fs.Open(name)
|
||||
}
|
35
third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go
vendored
Normal file
35
third_party/src/github.com/google/cadvisor/utils/fs/mockfs/fakefile.go
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mockfs
|
||||
|
||||
import "bytes"
|
||||
|
||||
type FakeFile struct {
|
||||
bytes.Buffer
|
||||
Name string
|
||||
}
|
||||
|
||||
func (self *FakeFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddTextFile(mockfs *MockFileSystem, name, content string) *FakeFile {
|
||||
f := &FakeFile{
|
||||
Name: name,
|
||||
Buffer: *bytes.NewBufferString(content),
|
||||
}
|
||||
mockfs.EXPECT().Open(name).Return(f, nil).AnyTimes()
|
||||
return f
|
||||
}
|
55
third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go
vendored
Normal file
55
third_party/src/github.com/google/cadvisor/utils/fs/mockfs/mockfs.go
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Automatically generated by MockGen. DO NOT EDIT!
|
||||
// Source: github.com/google/cadvisor/utils/fs (interfaces: FileSystem)
|
||||
|
||||
package mockfs
|
||||
|
||||
import (
|
||||
gomock "code.google.com/p/gomock/gomock"
|
||||
fs "github.com/google/cadvisor/utils/fs"
|
||||
)
|
||||
|
||||
// Mock of FileSystem interface
|
||||
type MockFileSystem struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *_MockFileSystemRecorder
|
||||
}
|
||||
|
||||
// Recorder for MockFileSystem (not exported)
|
||||
type _MockFileSystemRecorder struct {
|
||||
mock *MockFileSystem
|
||||
}
|
||||
|
||||
func NewMockFileSystem(ctrl *gomock.Controller) *MockFileSystem {
|
||||
mock := &MockFileSystem{ctrl: ctrl}
|
||||
mock.recorder = &_MockFileSystemRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (_m *MockFileSystem) EXPECT() *_MockFileSystemRecorder {
|
||||
return _m.recorder
|
||||
}
|
||||
|
||||
func (_m *MockFileSystem) Open(_param0 string) (fs.File, error) {
|
||||
ret := _m.ctrl.Call(_m, "Open", _param0)
|
||||
ret0, _ := ret[0].(fs.File)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (_mr *_MockFileSystemRecorder) Open(arg0 interface{}) *gomock.Call {
|
||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Open", arg0)
|
||||
}
|
24
third_party/src/github.com/google/cadvisor/utils/path.go
vendored
Normal file
24
third_party/src/github.com/google/cadvisor/utils/path.go
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
func FileExists(file string) bool {
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
17
third_party/src/github.com/google/cadvisor/utils/procfs/doc.go
vendored
Normal file
17
third_party/src/github.com/google/cadvisor/utils/procfs/doc.go
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// procfs contains several low level functions to read information from /proc
|
||||
// filesystem, and also provides some utility functions like JiffiesToDuration.
|
||||
package procfs
|
33
third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go
vendored
Normal file
33
third_party/src/github.com/google/cadvisor/utils/procfs/jiffy.go
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
/*
|
||||
#include <unistd.h>
|
||||
*/
|
||||
import "C"
|
||||
import "time"
|
||||
|
||||
var userHz uint64
|
||||
|
||||
func init() {
|
||||
userHzLong := C.sysconf(C._SC_CLK_TCK)
|
||||
userHz = uint64(userHzLong)
|
||||
}
|
||||
|
||||
func JiffiesToDuration(jiffies uint64) time.Duration {
|
||||
d := jiffies * 1000000000 / userHz
|
||||
return time.Duration(d)
|
||||
}
|
87
third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go
vendored
Normal file
87
third_party/src/github.com/google/cadvisor/utils/procfs/schedstats.go
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/cadvisor/utils/fs"
|
||||
)
|
||||
|
||||
type ProcessSchedStat struct {
|
||||
// Number of processes
|
||||
NumProcesses int
|
||||
|
||||
// Total time spent on the cpu (Unit: jiffy)
|
||||
Running uint64
|
||||
|
||||
// Total time spent waiting on a runqueue (Unit: jiffy)
|
||||
RunWait uint64
|
||||
|
||||
// # of timeslices run on this cpu (Unit: jiffy)
|
||||
NumTimeSlices uint64
|
||||
}
|
||||
|
||||
func readUint64List(r io.Reader) ([]uint64, error) {
|
||||
ret := make([]uint64, 0, 4)
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Split(bufio.ScanWords)
|
||||
|
||||
for scanner.Scan() {
|
||||
str := scanner.Text()
|
||||
u, err := strconv.ParseUint(str, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, u)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Add() read the schedstat of pid, and add stat to the fields
|
||||
// in self parameters. This function is useful if one wants to read stats of
|
||||
// a group of processes.
|
||||
func (self *ProcessSchedStat) Add(pid int) error {
|
||||
if self == nil {
|
||||
return fmt.Errorf("nil stat")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/proc/%d/schedstat", pid)
|
||||
f, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
v, err := readUint64List(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(v) < 3 {
|
||||
return fmt.Errorf("only %v fields read from %v: %v", len(v), path, v)
|
||||
}
|
||||
self.Running += v[0]
|
||||
self.RunWait += v[1]
|
||||
self.NumTimeSlices += v[2]
|
||||
self.NumProcesses++
|
||||
return nil
|
||||
}
|
57
third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go
vendored
Normal file
57
third_party/src/github.com/google/cadvisor/utils/procfs/schedstats_test.go
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/gomock/gomock"
|
||||
|
||||
"github.com/google/cadvisor/utils/fs"
|
||||
"github.com/google/cadvisor/utils/fs/mockfs"
|
||||
)
|
||||
|
||||
func TestReadProcessSchedStat(t *testing.T) {
|
||||
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mfs := mockfs.NewMockFileSystem(mockCtrl)
|
||||
|
||||
pid := 10
|
||||
|
||||
stat := &ProcessSchedStat{
|
||||
NumProcesses: 1,
|
||||
Running: 100,
|
||||
RunWait: 120,
|
||||
NumTimeSlices: 130,
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/proc/%v/schedstat", pid)
|
||||
content := fmt.Sprintf("%v %v %v\n", stat.Running, stat.RunWait, stat.NumTimeSlices)
|
||||
mockfs.AddTextFile(mfs, path, content)
|
||||
fs.ChangeFileSystem(mfs)
|
||||
|
||||
receivedStat := &ProcessSchedStat{}
|
||||
err := receivedStat.Add(pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(receivedStat, stat) {
|
||||
t.Errorf("Received wrong schedstat: %+v", receivedStat)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user