Commit da19f81e authored by Fabio Yeon's avatar Fabio Yeon

Merge pull request #19739 from vishh/cadvisor-v0.20.2

Updating cAdvisor deps to v0.20.4
parents 2aff6ccf 9755c06c
...@@ -393,6 +393,11 @@ ...@@ -393,6 +393,11 @@
"Rev": "0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d" "Rev": "0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d"
}, },
{ {
"ImportPath": "github.com/docker/go-units",
"Comment": "v0.1.0-21-g0bbddae",
"Rev": "0bbddae09c5a5419a8c6dcdd7ff90da3d450393b"
},
{
"ImportPath": "github.com/docker/spdystream", "ImportPath": "github.com/docker/spdystream",
"Rev": "c33989bcb56748d2473194d11f8ac3fc563688eb" "Rev": "c33989bcb56748d2473194d11f8ac3fc563688eb"
}, },
...@@ -574,93 +579,93 @@ ...@@ -574,93 +579,93 @@
}, },
{ {
"ImportPath": "github.com/google/cadvisor/api", "ImportPath": "github.com/google/cadvisor/api",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/cache/memory", "ImportPath": "github.com/google/cadvisor/cache/memory",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/collector", "ImportPath": "github.com/google/cadvisor/collector",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/container", "ImportPath": "github.com/google/cadvisor/container",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/events", "ImportPath": "github.com/google/cadvisor/events",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/fs", "ImportPath": "github.com/google/cadvisor/fs",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/healthz", "ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/http", "ImportPath": "github.com/google/cadvisor/http",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v1", "ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v2", "ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/manager", "ImportPath": "github.com/google/cadvisor/manager",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/metrics", "ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/pages", "ImportPath": "github.com/google/cadvisor/pages",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/storage", "ImportPath": "github.com/google/cadvisor/storage",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/summary", "ImportPath": "github.com/google/cadvisor/summary",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/utils", "ImportPath": "github.com/google/cadvisor/utils",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/validate", "ImportPath": "github.com/google/cadvisor/validate",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/version", "ImportPath": "github.com/google/cadvisor/version",
"Comment": "v0.20.1", "Comment": "v0.20.4",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32" "Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
}, },
{ {
"ImportPath": "github.com/google/gofuzz", "ImportPath": "github.com/google/gofuzz",
...@@ -759,6 +764,11 @@ ...@@ -759,6 +764,11 @@
"Rev": "3f504e8dabd5d562e997d19ce0200aa41973e1b2" "Rev": "3f504e8dabd5d562e997d19ce0200aa41973e1b2"
}, },
{ {
"ImportPath": "github.com/mistifyio/go-zfs",
"Comment": "v2.1.1-5-g1b4ae6f",
"Rev": "1b4ae6fb4e77b095934d4430860ff202060169f8"
},
{
"ImportPath": "github.com/mitchellh/mapstructure", "ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf" "Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
}, },
......
...@@ -29,6 +29,7 @@ github.com/daviddengcn/go-colortext | BSD? ...@@ -29,6 +29,7 @@ github.com/daviddengcn/go-colortext | BSD?
github.com/dgrijalva/jwt-go | spdxMIT github.com/dgrijalva/jwt-go | spdxMIT
github.com/docker/docker | Apache-2 github.com/docker/docker | Apache-2
github.com/docker/docker/pkg/symlink | spdxBSD3 github.com/docker/docker/pkg/symlink | spdxBSD3
github.com/docker/go-units | Apache-2
github.com/docker/spdystream | SeeFile github.com/docker/spdystream | SeeFile
github.com/elazarl/go-bindata-assetfs | spdxBSD2 github.com/elazarl/go-bindata-assetfs | spdxBSD2
github.com/elazarl/goproxy | BSDWarr github.com/elazarl/goproxy | BSDWarr
...@@ -60,6 +61,7 @@ github.com/kr/pty | spdxMIT ...@@ -60,6 +61,7 @@ github.com/kr/pty | spdxMIT
github.com/matttproud/golang_protobuf_extensions | Apache-2 github.com/matttproud/golang_protobuf_extensions | Apache-2
github.com/mesos/mesos-go | Apache-2 github.com/mesos/mesos-go | Apache-2
github.com/miekg/dns | spdxBSD3 github.com/miekg/dns | spdxBSD3
github.com/mistifyio/go-zfs | Apache-2
github.com/mitchellh/mapstructure | MITname github.com/mitchellh/mapstructure | MITname
github.com/mvdan/xurls | spdxBSD3 github.com/mvdan/xurls | spdxBSD3
github.com/mxk/go-flowrate | spdxBSD3 github.com/mxk/go-flowrate | spdxBSD3
......
# Contributing to go-units
Want to hack on go-units? Awesome! Here are instructions to get you started.
go-units is a part of the [Docker](https://www.docker.com) project, and follows
the same rules and principles. If you're already familiar with the way
Docker does things, you'll feel right at home.
Otherwise, go read Docker's
[contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md),
[issue triaging](https://github.com/docker/docker/blob/master/project/ISSUE-TRIAGE.md),
[review process](https://github.com/docker/docker/blob/master/project/REVIEWING.md) and
[branches and tags](https://github.com/docker/docker/blob/master/project/BRANCHES-AND-TAGS.md).
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
# go-connections maintainers file
#
# This file describes who runs the docker/go-connections project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"calavera",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[![GoDoc](https://godoc.org/github.com/docker/go-units?status.svg)](https://godoc.org/github.com/docker/go-units)
# Introduction
go-units is a library to transform human friendly measurements into machine friendly values.
## Usage
See the [docs in godoc](https://godoc.org/github.com/docker/go-units) for examples and documentation.
## Copyright and license
Copyright © 2015 Docker, Inc. All rights reserved, except as follows. Code
is released under the Apache 2.0 license. The README.md file, and files in the
"docs" folder are licensed under the Creative Commons Attribution 4.0
International License under the terms and conditions set forth in the file
"LICENSE.docs". You may obtain a duplicate copy of the same license, titled
CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/.
dependencies:
post:
# install golint
- go get github.com/golang/lint/golint
test:
pre:
# run analysis before tests
- go vet ./...
- test -z "$(golint ./... | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
// Package units provides helper function to parse and print size and time units
// in human-readable format.
package units
import (
"fmt"
"time"
)
// HumanDuration returns a human-readable approximation of a duration
// (eg. "About a minute", "4 hours ago", etc.).
func HumanDuration(d time.Duration) string {
if seconds := int(d.Seconds()); seconds < 1 {
return "Less than a second"
} else if seconds < 60 {
return fmt.Sprintf("%d seconds", seconds)
} else if minutes := int(d.Minutes()); minutes == 1 {
return "About a minute"
} else if minutes < 60 {
return fmt.Sprintf("%d minutes", minutes)
} else if hours := int(d.Hours()); hours == 1 {
return "About an hour"
} else if hours < 48 {
return fmt.Sprintf("%d hours", hours)
} else if hours < 24*7*2 {
return fmt.Sprintf("%d days", hours/24)
} else if hours < 24*30*3 {
return fmt.Sprintf("%d weeks", hours/24/7)
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
return fmt.Sprintf("%d years", int(d.Hours())/24/365)
}
package units
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
// Decimal
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
PB = 1000 * TB
// Binary
KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
TiB = 1024 * GiB
PiB = 1024 * TiB
)
type unitMap map[string]int64
var (
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
sizeRegex = regexp.MustCompile(`^(\d+)([kKmMgGtTpP])?[bB]?$`)
)
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
// CustomSize returns a human-readable approximation of a size
// using custom format.
func CustomSize(format string, size float64, base float64, _map []string) string {
i := 0
for size >= base {
size = size / base
i++
}
return fmt.Sprintf(format, size, _map[i])
}
// HumanSize returns a human-readable approximation of a size
// capped at 4 valid numbers (eg. "2.746 MB", "796 KB").
func HumanSize(size float64) string {
return CustomSize("%.4g %s", size, 1000.0, decimapAbbrs)
}
// BytesSize returns a human-readable size in bytes, kibibytes,
// mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB").
func BytesSize(size float64) string {
return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs)
}
// FromHumanSize returns an integer from a human-readable specification of a
// size using SI standard (eg. "44kB", "17MB").
func FromHumanSize(size string) (int64, error) {
return parseSize(size, decimalMap)
}
// RAMInBytes parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
func RAMInBytes(size string) (int64, error) {
return parseSize(size, binaryMap)
}
// Parses the human-readable size string into the amount it represents.
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
matches := sizeRegex.FindStringSubmatch(sizeStr)
if len(matches) != 3 {
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
}
size, err := strconv.ParseInt(matches[1], 10, 0)
if err != nil {
return -1, err
}
unitPrefix := strings.ToLower(matches[2])
if mul, ok := uMap[unitPrefix]; ok {
size *= mul
}
return size, nil
}
package units
import (
"fmt"
"strconv"
"strings"
)
// Ulimit is a human friendly version of Rlimit.
type Ulimit struct {
Name string
Hard int64
Soft int64
}
// Rlimit specifies the resource limits, such as max open files.
type Rlimit struct {
Type int `json:"type,omitempty"`
Hard uint64 `json:"hard,omitempty"`
Soft uint64 `json:"soft,omitempty"`
}
const (
// magic numbers for making the syscall
// some of these are defined in the syscall package, but not all.
// Also since Windows client doesn't get access to the syscall package, need to
// define these here
rlimitAs = 9
rlimitCore = 4
rlimitCPU = 0
rlimitData = 2
rlimitFsize = 1
rlimitLocks = 10
rlimitMemlock = 8
rlimitMsgqueue = 12
rlimitNice = 13
rlimitNofile = 7
rlimitNproc = 6
rlimitRss = 5
rlimitRtprio = 14
rlimitRttime = 15
rlimitSigpending = 11
rlimitStack = 3
)
var ulimitNameMapping = map[string]int{
//"as": rlimitAs, // Disabled since this doesn't seem usable with the way Docker inits a container.
"core": rlimitCore,
"cpu": rlimitCPU,
"data": rlimitData,
"fsize": rlimitFsize,
"locks": rlimitLocks,
"memlock": rlimitMemlock,
"msgqueue": rlimitMsgqueue,
"nice": rlimitNice,
"nofile": rlimitNofile,
"nproc": rlimitNproc,
"rss": rlimitRss,
"rtprio": rlimitRtprio,
"rttime": rlimitRttime,
"sigpending": rlimitSigpending,
"stack": rlimitStack,
}
// ParseUlimit parses and returns a Ulimit from the specified string.
func ParseUlimit(val string) (*Ulimit, error) {
parts := strings.SplitN(val, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid ulimit argument: %s", val)
}
if _, exists := ulimitNameMapping[parts[0]]; !exists {
return nil, fmt.Errorf("invalid ulimit type: %s", parts[0])
}
var (
soft int64
hard = &soft // default to soft in case no hard was set
temp int64
err error
)
switch limitVals := strings.Split(parts[1], ":"); len(limitVals) {
case 2:
temp, err = strconv.ParseInt(limitVals[1], 10, 64)
if err != nil {
return nil, err
}
hard = &temp
fallthrough
case 1:
soft, err = strconv.ParseInt(limitVals[0], 10, 64)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
}
if soft > *hard {
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
}
return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil
}
// GetRlimit returns the RLimit corresponding to Ulimit.
func (u *Ulimit) GetRlimit() (*Rlimit, error) {
t, exists := ulimitNameMapping[u.Name]
if !exists {
return nil, fmt.Errorf("invalid ulimit name %s", u.Name)
}
return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil
}
func (u *Ulimit) String() string {
return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard)
}
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"net/http" "net/http"
"path" "path"
"strconv" "strconv"
"time"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/info/v2"
...@@ -32,6 +31,7 @@ const ( ...@@ -32,6 +31,7 @@ const (
containersApi = "containers" containersApi = "containers"
subcontainersApi = "subcontainers" subcontainersApi = "subcontainers"
machineApi = "machine" machineApi = "machine"
machineStatsApi = "machinestats"
dockerApi = "docker" dockerApi = "docker"
summaryApi = "summary" summaryApi = "summary"
statsApi = "stats" statsApi = "stats"
...@@ -63,8 +63,9 @@ func getApiVersions() []ApiVersion { ...@@ -63,8 +63,9 @@ func getApiVersions() []ApiVersion {
v1_2 := newVersion1_2(v1_1) v1_2 := newVersion1_2(v1_1)
v1_3 := newVersion1_3(v1_2) v1_3 := newVersion1_3(v1_2)
v2_0 := newVersion2_0() v2_0 := newVersion2_0()
v2_1 := newVersion2_1(v2_0)
return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0} return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0, v2_1}
} }
...@@ -358,47 +359,47 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma ...@@ -358,47 +359,47 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
case statsApi: case statsApi:
name := getContainerName(request) name := getContainerName(request)
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt) glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
conts, err := m.GetRequestedContainersInfo(name, opt) infos, err := m.GetRequestedContainersInfo(name, opt)
if err != nil { if err != nil {
return err return err
} }
contStats := make(map[string][]v2.ContainerStats, 0) contStats := make(map[string][]v2.DeprecatedContainerStats, 0)
for name, cont := range conts { for name, cinfo := range infos {
contStats[name] = convertStats(cont) contStats[name] = v2.DeprecatedStatsFromV1(cinfo)
} }
return writeResult(contStats, w) return writeResult(contStats, w)
case customMetricsApi: case customMetricsApi:
containerName := getContainerName(request) containerName := getContainerName(request)
glog.V(4).Infof("Api - Custom Metrics: Looking for metrics for container %q, options %+v", containerName, opt) glog.V(4).Infof("Api - Custom Metrics: Looking for metrics for container %q, options %+v", containerName, opt)
conts, err := m.GetRequestedContainersInfo(containerName, opt) infos, err := m.GetContainerInfoV2(containerName, opt)
if err != nil { if err != nil {
return err return err
} }
contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0) contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0)
for _, cont := range conts { for _, cinfo := range infos {
metrics := make(map[string]map[string][]info.MetricValBasic, 0) metrics := make(map[string]map[string][]info.MetricValBasic, 0)
contStats := convertStats(cont) for _, contStat := range cinfo.Stats {
for _, contStat := range contStats { if len(contStat.CustomMetrics) == 0 {
if contStat.HasCustomMetrics { continue
for name, allLabels := range contStat.CustomMetrics { }
metricLabels := make(map[string][]info.MetricValBasic, 0) for name, allLabels := range contStat.CustomMetrics {
for _, metric := range allLabels { metricLabels := make(map[string][]info.MetricValBasic, 0)
if !metric.Timestamp.IsZero() { for _, metric := range allLabels {
metVal := info.MetricValBasic{ if !metric.Timestamp.IsZero() {
Timestamp: metric.Timestamp, metVal := info.MetricValBasic{
IntValue: metric.IntValue, Timestamp: metric.Timestamp,
FloatValue: metric.FloatValue, IntValue: metric.IntValue,
} FloatValue: metric.FloatValue,
labels := metrics[name] }
if labels != nil { labels := metrics[name]
values := labels[metric.Label] if labels != nil {
values = append(values, metVal) values := labels[metric.Label]
labels[metric.Label] = values values = append(values, metVal)
metrics[name] = labels labels[metric.Label] = values
} else { metrics[name] = labels
metricLabels[metric.Label] = []info.MetricValBasic{metVal} } else {
metrics[name] = metricLabels metricLabels[metric.Label] = []info.MetricValBasic{metVal}
} metrics[name] = metricLabels
} }
} }
} }
...@@ -451,102 +452,54 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma ...@@ -451,102 +452,54 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
} }
} }
func instCpuStats(last, cur *info.ContainerStats) (*v2.CpuInstStats, error) { type version2_1 struct {
if last == nil { baseVersion *version2_0
return nil, nil }
}
if !cur.Timestamp.After(last.Timestamp) { func newVersion2_1(v *version2_0) *version2_1 {
return nil, fmt.Errorf("container stats move backwards in time") return &version2_1{
} baseVersion: v,
if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
return nil, fmt.Errorf("different number of cpus")
}
timeDelta := cur.Timestamp.Sub(last.Timestamp)
if timeDelta <= 100*time.Millisecond {
return nil, fmt.Errorf("time delta unexpectedly small")
}
// Nanoseconds to gain precision and avoid having zero seconds if the
// difference between the timestamps is just under a second
timeDeltaNs := uint64(timeDelta.Nanoseconds())
convertToRate := func(lastValue, curValue uint64) (uint64, error) {
if curValue < lastValue {
return 0, fmt.Errorf("cumulative stats decrease")
}
valueDelta := curValue - lastValue
return (valueDelta * 1e9) / timeDeltaNs, nil
} }
total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total) }
func (self *version2_1) Version() string {
return "v2.1"
}
func (self *version2_1) SupportedRequestTypes() []string {
return self.baseVersion.SupportedRequestTypes()
}
func (self *version2_1) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
// Get the query request.
opt, err := getRequestOptions(r)
if err != nil { if err != nil {
return nil, err return err
} }
percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
for i := range percpu { switch requestType {
var err error case machineStatsApi:
percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i]) glog.V(4).Infof("Api - MachineStats(%v)", request)
cont, err := m.GetRequestedContainersInfo("/", opt)
if err != nil { if err != nil {
return nil, err return err
}
}
user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
if err != nil {
return nil, err
}
system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
if err != nil {
return nil, err
}
return &v2.CpuInstStats{
Usage: v2.CpuInstUsage{
Total: total,
PerCpu: percpu,
User: user,
System: system,
},
}, nil
}
func convertStats(cont *info.ContainerInfo) []v2.ContainerStats {
stats := make([]v2.ContainerStats, 0, len(cont.Stats))
var last *info.ContainerStats
for _, val := range cont.Stats {
stat := v2.ContainerStats{
Timestamp: val.Timestamp,
HasCpu: cont.Spec.HasCpu,
HasMemory: cont.Spec.HasMemory,
HasNetwork: cont.Spec.HasNetwork,
HasFilesystem: cont.Spec.HasFilesystem,
HasDiskIo: cont.Spec.HasDiskIo,
HasCustomMetrics: cont.Spec.HasCustomMetrics,
}
if stat.HasCpu {
stat.Cpu = val.Cpu
cpuInst, err := instCpuStats(last, val)
if err != nil {
glog.Warningf("Could not get instant cpu stats: %v", err)
} else {
stat.CpuInst = cpuInst
}
last = val
}
if stat.HasMemory {
stat.Memory = val.Memory
}
if stat.HasNetwork {
stat.Network.Interfaces = val.Network.Interfaces
}
if stat.HasFilesystem {
stat.Filesystem = val.Filesystem
} }
if stat.HasDiskIo { return writeResult(v2.MachineStatsFromV1(cont["/"]), w)
stat.DiskIo = val.DiskIo case statsApi:
name := getContainerName(request)
glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt)
conts, err := m.GetRequestedContainersInfo(name, opt)
if err != nil {
return err
} }
if stat.HasCustomMetrics { contStats := make(map[string][]*v2.ContainerStats, len(conts))
stat.CustomMetrics = val.CustomMetrics for name, cont := range conts {
contStats[name] = v2.ContainerStatsFromV1(&cont.Spec, cont.Stats)
} }
// TODO(rjnagal): Handle load stats. return writeResult(contStats, w)
stats = append(stats, stat) default:
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
} }
return stats
} }
func getRequestOptions(r *http.Request) (v2.RequestOptions, error) { func getRequestOptions(r *http.Request) (v2.RequestOptions, error) {
......
...@@ -81,4 +81,8 @@ type ContainerHandler interface { ...@@ -81,4 +81,8 @@ type ContainerHandler interface {
// Cleanup frees up any resources being held like fds or go routines, etc. // Cleanup frees up any resources being held like fds or go routines, etc.
Cleanup() Cleanup()
// Start starts any necessary background goroutines - must be cleaned up in Cleanup().
// It is expected that most implementations will be a no-op.
Start()
} }
...@@ -49,6 +49,8 @@ var dockerCgroupRegexp = regexp.MustCompile(`.+-([a-z0-9]{64})\.scope$`) ...@@ -49,6 +49,8 @@ var dockerCgroupRegexp = regexp.MustCompile(`.+-([a-z0-9]{64})\.scope$`)
var noSystemd = flag.Bool("nosystemd", false, "Explicitly disable systemd support for Docker containers") var noSystemd = flag.Bool("nosystemd", false, "Explicitly disable systemd support for Docker containers")
var dockerEnvWhitelist = flag.String("docker_env_metadata_whitelist", "", "a comma-separated list of environment variable keys that needs to be collected for docker containers")
// TODO(vmarmol): Export run dir too for newer Dockers. // TODO(vmarmol): Export run dir too for newer Dockers.
// Directory holding Docker container state information. // Directory holding Docker container state information.
func DockerStateDir() string { func DockerStateDir() string {
...@@ -92,6 +94,7 @@ const ( ...@@ -92,6 +94,7 @@ const (
devicemapperStorageDriver storageDriver = "devicemapper" devicemapperStorageDriver storageDriver = "devicemapper"
aufsStorageDriver storageDriver = "aufs" aufsStorageDriver storageDriver = "aufs"
overlayStorageDriver storageDriver = "overlay" overlayStorageDriver storageDriver = "overlay"
zfsStorageDriver storageDriver = "zfs"
) )
type dockerFactory struct { type dockerFactory struct {
...@@ -117,6 +120,9 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool ...@@ -117,6 +120,9 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
if err != nil { if err != nil {
return return
} }
metadataEnvs := strings.Split(*dockerEnvWhitelist, ",")
handler, err = newDockerContainerHandler( handler, err = newDockerContainerHandler(
client, client,
name, name,
...@@ -125,6 +131,7 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool ...@@ -125,6 +131,7 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
self.storageDriver, self.storageDriver,
&self.cgroupSubsystems, &self.cgroupSubsystems,
inHostNamespace, inHostNamespace,
metadataEnvs,
) )
return return
} }
......
...@@ -26,17 +26,19 @@ import ( ...@@ -26,17 +26,19 @@ import (
type fsHandler interface { type fsHandler interface {
start() start()
usage() uint64 usage() (uint64, uint64)
stop() stop()
} }
type realFsHandler struct { type realFsHandler struct {
sync.RWMutex sync.RWMutex
lastUpdate time.Time lastUpdate time.Time
usageBytes uint64 usageBytes uint64
period time.Duration baseUsageBytes uint64
storageDirs []string period time.Duration
fsInfo fs.FsInfo rootfs string
extraDir string
fsInfo fs.FsInfo
// Tells the container to stop. // Tells the container to stop.
stopChan chan struct{} stopChan chan struct{}
} }
...@@ -45,14 +47,16 @@ const longDu = time.Second ...@@ -45,14 +47,16 @@ const longDu = time.Second
var _ fsHandler = &realFsHandler{} var _ fsHandler = &realFsHandler{}
func newFsHandler(period time.Duration, storageDirs []string, fsInfo fs.FsInfo) fsHandler { func newFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) fsHandler {
return &realFsHandler{ return &realFsHandler{
lastUpdate: time.Time{}, lastUpdate: time.Time{},
usageBytes: 0, usageBytes: 0,
period: period, baseUsageBytes: 0,
storageDirs: storageDirs, period: period,
fsInfo: fsInfo, rootfs: rootfs,
stopChan: make(chan struct{}, 1), extraDir: extraDir,
fsInfo: fsInfo,
stopChan: make(chan struct{}, 1),
} }
} }
...@@ -61,23 +65,27 @@ func (fh *realFsHandler) needsUpdate() bool { ...@@ -61,23 +65,27 @@ func (fh *realFsHandler) needsUpdate() bool {
} }
func (fh *realFsHandler) update() error { func (fh *realFsHandler) update() error {
var usage uint64 // TODO(vishh): Add support for external mounts.
for _, dir := range fh.storageDirs { baseUsage, err := fh.fsInfo.GetDirUsage(fh.rootfs)
// TODO(Vishh): Add support for external mounts. if err != nil {
dirUsage, err := fh.fsInfo.GetDirUsage(dir) return err
if err != nil { }
return err
} extraDirUsage, err := fh.fsInfo.GetDirUsage(fh.extraDir)
usage += dirUsage if err != nil {
return err
} }
fh.Lock() fh.Lock()
defer fh.Unlock() defer fh.Unlock()
fh.lastUpdate = time.Now() fh.lastUpdate = time.Now()
fh.usageBytes = usage fh.usageBytes = baseUsage + extraDirUsage
fh.baseUsageBytes = baseUsage
return nil return nil
} }
func (fh *realFsHandler) trackUsage() { func (fh *realFsHandler) trackUsage() {
fh.update()
for { for {
select { select {
case <-fh.stopChan: case <-fh.stopChan:
...@@ -89,7 +97,7 @@ func (fh *realFsHandler) trackUsage() { ...@@ -89,7 +97,7 @@ func (fh *realFsHandler) trackUsage() {
} }
duration := time.Since(start) duration := time.Since(start)
if duration > longDu { if duration > longDu {
glog.V(3).Infof("`du` on following dirs took %v: %v", duration, fh.storageDirs) glog.V(3).Infof("`du` on following dirs took %v: %v", duration, []string{fh.rootfs, fh.extraDir})
} }
} }
} }
...@@ -103,8 +111,8 @@ func (fh *realFsHandler) stop() { ...@@ -103,8 +111,8 @@ func (fh *realFsHandler) stop() {
close(fh.stopChan) close(fh.stopChan)
} }
func (fh *realFsHandler) usage() uint64 { func (fh *realFsHandler) usage() (baseUsageBytes, totalUsageBytes uint64) {
fh.RLock() fh.RLock()
defer fh.RUnlock() defer fh.RUnlock()
return fh.usageBytes return fh.baseUsageBytes, fh.usageBytes
} }
...@@ -59,15 +59,16 @@ type dockerContainerHandler struct { ...@@ -59,15 +59,16 @@ type dockerContainerHandler struct {
// Manager of this container's cgroups. // Manager of this container's cgroups.
cgroupManager cgroups.Manager cgroupManager cgroups.Manager
storageDriver storageDriver storageDriver storageDriver
fsInfo fs.FsInfo fsInfo fs.FsInfo
storageDirs []string rootfsStorageDir string
// Time at which this container was created. // Time at which this container was created.
creationTime time.Time creationTime time.Time
// Metadata labels associated with the container. // Metadata associated with the container.
labels map[string]string labels map[string]string
envs map[string]string
// The container PID used to switch namespaces as required // The container PID used to switch namespaces as required
pid int pid int
...@@ -93,6 +94,7 @@ func newDockerContainerHandler( ...@@ -93,6 +94,7 @@ func newDockerContainerHandler(
storageDriver storageDriver, storageDriver storageDriver,
cgroupSubsystems *containerlibcontainer.CgroupSubsystems, cgroupSubsystems *containerlibcontainer.CgroupSubsystems,
inHostNamespace bool, inHostNamespace bool,
metadataEnvs []string,
) (container.ContainerHandler, error) { ) (container.ContainerHandler, error) {
// Create the cgroup paths. // Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
...@@ -116,14 +118,13 @@ func newDockerContainerHandler( ...@@ -116,14 +118,13 @@ func newDockerContainerHandler(
id := ContainerNameToDockerId(name) id := ContainerNameToDockerId(name)
// Add the Containers dir where the log files are stored. // Add the Containers dir where the log files are stored.
storageDirs := []string{path.Join(*dockerRootDir, pathToContainersDir, id)} otherStorageDir := path.Join(*dockerRootDir, pathToContainersDir, id)
var rootfsStorageDir string
switch storageDriver { switch storageDriver {
case aufsStorageDriver: case aufsStorageDriver:
// Add writable layer for aufs. rootfsStorageDir = path.Join(*dockerRootDir, pathToAufsDir, id)
storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToAufsDir, id))
case overlayStorageDriver: case overlayStorageDriver:
storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToOverlayDir, id)) rootfsStorageDir = path.Join(*dockerRootDir, pathToOverlayDir, id)
} }
handler := &dockerContainerHandler{ handler := &dockerContainerHandler{
...@@ -136,13 +137,10 @@ func newDockerContainerHandler( ...@@ -136,13 +137,10 @@ func newDockerContainerHandler(
storageDriver: storageDriver, storageDriver: storageDriver,
fsInfo: fsInfo, fsInfo: fsInfo,
rootFs: rootFs, rootFs: rootFs,
storageDirs: storageDirs, rootfsStorageDir: rootfsStorageDir,
fsHandler: newFsHandler(time.Minute, storageDirs, fsInfo), fsHandler: newFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo),
} }
// Start the filesystem handler.
handler.fsHandler.start()
// We assume that if Inspect fails then the container is not known to docker. // We assume that if Inspect fails then the container is not known to docker.
ctnr, err := client.InspectContainer(id) ctnr, err := client.InspectContainer(id)
if err != nil { if err != nil {
...@@ -157,9 +155,24 @@ func newDockerContainerHandler( ...@@ -157,9 +155,24 @@ func newDockerContainerHandler(
handler.image = ctnr.Config.Image handler.image = ctnr.Config.Image
handler.networkMode = ctnr.HostConfig.NetworkMode handler.networkMode = ctnr.HostConfig.NetworkMode
// split env vars to get metadata map.
for _, exposedEnv := range metadataEnvs {
for _, envVar := range ctnr.Config.Env {
splits := strings.SplitN(envVar, "=", 2)
if splits[0] == exposedEnv {
handler.envs[strings.ToLower(exposedEnv)] = splits[1]
}
}
}
return handler, nil return handler, nil
} }
func (self *dockerContainerHandler) Start() {
// Start the filesystem handler.
self.fsHandler.start()
}
func (self *dockerContainerHandler) Cleanup() { func (self *dockerContainerHandler) Cleanup() {
self.fsHandler.stop() self.fsHandler.stop()
} }
...@@ -239,13 +252,14 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) { ...@@ -239,13 +252,14 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
spec.CreationTime = self.creationTime spec.CreationTime = self.creationTime
switch self.storageDriver { switch self.storageDriver {
case aufsStorageDriver, overlayStorageDriver: case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
spec.HasFilesystem = true spec.HasFilesystem = true
default: default:
spec.HasFilesystem = false spec.HasFilesystem = false
} }
spec.Labels = self.labels spec.Labels = self.labels
spec.Envs = self.envs
spec.Image = self.image spec.Image = self.image
spec.HasNetwork = hasNet(self.networkMode) spec.HasNetwork = hasNet(self.networkMode)
...@@ -254,14 +268,12 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) { ...@@ -254,14 +268,12 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error { func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
switch self.storageDriver { switch self.storageDriver {
case aufsStorageDriver, overlayStorageDriver: case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
default: default:
return nil return nil
} }
// As of now we assume that all the storage dirs are on the same device. deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir)
// The first storage dir will be that of the image layers.
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
if err != nil { if err != nil {
return err return err
} }
...@@ -281,7 +293,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error ...@@ -281,7 +293,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error
fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit} fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}
fsStat.Usage = self.fsHandler.usage() fsStat.BaseUsage, fsStat.Usage = self.fsHandler.usage()
stats.Filesystem = append(stats.Filesystem, fsStat) stats.Filesystem = append(stats.Filesystem, fsStat)
return nil return nil
......
...@@ -378,6 +378,8 @@ func toContainerStats1(s *cgroups.Stats, ret *info.ContainerStats) { ...@@ -378,6 +378,8 @@ func toContainerStats1(s *cgroups.Stats, ret *info.ContainerStats) {
func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) { func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
if v, ok := s.MemoryStats.Stats["pgfault"]; ok { if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v ret.Memory.HierarchicalData.Pgfault = v
......
...@@ -51,6 +51,8 @@ func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, ...@@ -51,6 +51,8 @@ func (self *MockContainerHandler) ContainerReference() (info.ContainerReference,
return args.Get(0).(info.ContainerReference), args.Error(1) return args.Get(0).(info.ContainerReference), args.Error(1)
} }
func (self *MockContainerHandler) Start() {}
func (self *MockContainerHandler) Cleanup() {} func (self *MockContainerHandler) Cleanup() {}
func (self *MockContainerHandler) GetSpec() (info.ContainerSpec, error) { func (self *MockContainerHandler) GetSpec() (info.ContainerSpec, error) {
......
...@@ -166,6 +166,9 @@ func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error) ...@@ -166,6 +166,9 @@ func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error)
return nd, nil return nd, nil
} }
// Nothing to start up.
func (self *rawContainerHandler) Start() {}
// Nothing to clean up. // Nothing to clean up.
func (self *rawContainerHandler) Cleanup() {} func (self *rawContainerHandler) Cleanup() {}
......
...@@ -32,6 +32,7 @@ import ( ...@@ -32,6 +32,7 @@ import (
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
"github.com/golang/glog" "github.com/golang/glog"
zfs "github.com/mistifyio/go-zfs"
) )
const ( const (
...@@ -73,8 +74,10 @@ func NewFsInfo(context Context) (FsInfo, error) { ...@@ -73,8 +74,10 @@ func NewFsInfo(context Context) (FsInfo, error) {
// all ext systems are checked through prefix. // all ext systems are checked through prefix.
"btrfs": true, "btrfs": true,
"xfs": true, "xfs": true,
"zfs": true,
} }
for _, mount := range mounts { for _, mount := range mounts {
var Fstype string
if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] { if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] {
continue continue
} }
...@@ -82,7 +85,11 @@ func NewFsInfo(context Context) (FsInfo, error) { ...@@ -82,7 +85,11 @@ func NewFsInfo(context Context) (FsInfo, error) {
if _, ok := partitions[mount.Source]; ok { if _, ok := partitions[mount.Source]; ok {
continue continue
} }
if mount.Fstype == "zfs" {
Fstype = mount.Fstype
}
partitions[mount.Source] = partition{ partitions[mount.Source] = partition{
fsType: Fstype,
mountpoint: mount.Mountpoint, mountpoint: mount.Mountpoint,
major: uint(mount.Major), major: uint(mount.Major),
minor: uint(mount.Minor), minor: uint(mount.Minor),
...@@ -128,7 +135,7 @@ func getDockerImagePaths(context Context) []string { ...@@ -128,7 +135,7 @@ func getDockerImagePaths(context Context) []string {
// TODO(rjnagal): Detect docker root and graphdriver directories from docker info. // TODO(rjnagal): Detect docker root and graphdriver directories from docker info.
dockerRoot := context.DockerRoot dockerRoot := context.DockerRoot
dockerImagePaths := []string{} dockerImagePaths := []string{}
for _, dir := range []string{"devicemapper", "btrfs", "aufs", "overlay"} { for _, dir := range []string{"devicemapper", "btrfs", "aufs", "overlay", "zfs"} {
dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir)) dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir))
} }
for dockerRoot != "/" && dockerRoot != "." { for dockerRoot != "/" && dockerRoot != "." {
...@@ -201,6 +208,8 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er ...@@ -201,6 +208,8 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er
switch partition.fsType { switch partition.fsType {
case "devicemapper": case "devicemapper":
total, free, avail, err = getDMStats(device, partition.blockSize) total, free, avail, err = getDMStats(device, partition.blockSize)
case "zfs":
total, free, avail, err = getZfstats(device)
default: default:
total, free, avail, err = getVfsStats(partition.mountpoint) total, free, avail, err = getVfsStats(partition.mountpoint)
} }
...@@ -423,3 +432,15 @@ func parseDMStatus(dmStatus string) (uint64, uint64, error) { ...@@ -423,3 +432,15 @@ func parseDMStatus(dmStatus string) (uint64, uint64, error) {
return used, total, nil return used, total, nil
} }
// getZfstats returns ZFS mount stats using zfsutils
func getZfstats(poolName string) (uint64, uint64, uint64, error) {
dataset, err := zfs.GetDataset(poolName)
if err != nil {
return 0, 0, 0, err
}
total := dataset.Used + dataset.Avail + dataset.Usedbydataset
return total, dataset.Avail, dataset.Avail, nil
}
...@@ -45,6 +45,8 @@ type ContainerSpec struct { ...@@ -45,6 +45,8 @@ type ContainerSpec struct {
// Metadata labels associated with this container. // Metadata labels associated with this container.
Labels map[string]string `json:"labels,omitempty"` Labels map[string]string `json:"labels,omitempty"`
// Metadata envs associated with this container. Only whitelisted envs are added.
Envs map[string]string `json:"envs,omitempty"`
HasCpu bool `json:"has_cpu"` HasCpu bool `json:"has_cpu"`
Cpu CpuSpec `json:"cpu,omitempty"` Cpu CpuSpec `json:"cpu,omitempty"`
...@@ -306,6 +308,15 @@ type MemoryStats struct { ...@@ -306,6 +308,15 @@ type MemoryStats struct {
// Units: Bytes. // Units: Bytes.
Usage uint64 `json:"usage"` Usage uint64 `json:"usage"`
// Number of bytes of page cache memory.
// Units: Bytes.
Cache uint64 `json:"cache"`
// The amount of anonymous and swap cache memory (includes transparent
// hugepages).
// Units: Bytes.
RSS uint64 `json:"rss"`
// The amount of working set memory, this includes recently accessed memory, // The amount of working set memory, this includes recently accessed memory,
// dirty memory, and kernel memory. Working set is <= "usage". // dirty memory, and kernel memory. Working set is <= "usage".
// Units: Bytes. // Units: Bytes.
...@@ -387,6 +398,10 @@ type FsStats struct { ...@@ -387,6 +398,10 @@ type FsStats struct {
// Number of bytes that is consumed by the container on this filesystem. // Number of bytes that is consumed by the container on this filesystem.
Usage uint64 `json:"usage"` Usage uint64 `json:"usage"`
// Base Usage that is consumed by the container's writable layer.
// This field is only applicable for docker container's as of now.
BaseUsage uint64 `json:"base_usage"`
// Number of bytes available for non-root user. // Number of bytes available for non-root user.
Available uint64 `json:"available"` Available uint64 `json:"available"`
......
...@@ -44,6 +44,8 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info ...@@ -44,6 +44,8 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info
stats.Cpu.Usage.User = stats.Cpu.Usage.Total stats.Cpu.Usage.User = stats.Cpu.Usage.Total
stats.Cpu.Usage.System = 0 stats.Cpu.Usage.System = 0
stats.Memory.Usage = uint64(rand.Int63n(4096)) stats.Memory.Usage = uint64(rand.Int63n(4096))
stats.Memory.Cache = uint64(rand.Int63n(4096))
stats.Memory.RSS = uint64(rand.Int63n(4096))
ret[i] = stats ret[i] = stats
} }
return ret return ret
......
...@@ -52,6 +52,14 @@ type MemorySpec struct { ...@@ -52,6 +52,14 @@ type MemorySpec struct {
SwapLimit uint64 `json:"swap_limit,omitempty"` SwapLimit uint64 `json:"swap_limit,omitempty"`
} }
type ContainerInfo struct {
// Describes the container.
Spec ContainerSpec `json:"spec,omitempty"`
// Historical statistics gathered from the container.
Stats []*ContainerStats `json:"stats,omitempty"`
}
type ContainerSpec struct { type ContainerSpec struct {
// Time at which the container was created. // Time at which the container was created.
CreationTime time.Time `json:"creation_time,omitempty"` CreationTime time.Time `json:"creation_time,omitempty"`
...@@ -66,6 +74,8 @@ type ContainerSpec struct { ...@@ -66,6 +74,8 @@ type ContainerSpec struct {
// Metadata labels associated with this container. // Metadata labels associated with this container.
Labels map[string]string `json:"labels,omitempty"` Labels map[string]string `json:"labels,omitempty"`
// Metadata envs associated with this container. Only whitelisted envs are added.
Envs map[string]string `json:"envs,omitempty"`
HasCpu bool `json:"has_cpu"` HasCpu bool `json:"has_cpu"`
Cpu CpuSpec `json:"cpu,omitempty"` Cpu CpuSpec `json:"cpu,omitempty"`
...@@ -85,7 +95,7 @@ type ContainerSpec struct { ...@@ -85,7 +95,7 @@ type ContainerSpec struct {
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
} }
type ContainerStats struct { type DeprecatedContainerStats struct {
// The time of this stat point. // The time of this stat point.
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
// CPU statistics // CPU statistics
...@@ -114,6 +124,28 @@ type ContainerStats struct { ...@@ -114,6 +124,28 @@ type ContainerStats struct {
CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"` CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"`
} }
type ContainerStats struct {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
// CPU statistics
// In nanoseconds (aggregated)
Cpu *v1.CpuStats `json:"cpu,omitempty"`
// In nanocores per second (instantaneous)
CpuInst *CpuInstStats `json:"cpu_inst,omitempty"`
// Disk IO statistics
DiskIo *v1.DiskIoStats `json:"diskio,omitempty"`
// Memory statistics
Memory *v1.MemoryStats `json:"memory,omitempty"`
// Network statistics
Network *NetworkStats `json:"network,omitempty"`
// Filesystem statistics
Filesystem *FilesystemStats `json:"filesystem,omitempty"`
// Task load statistics
Load *v1.LoadStats `json:"load_stats,omitempty"`
// Custom Metrics
CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"`
}
type Percentiles struct { type Percentiles struct {
// Indicates whether the stats are present or not. // Indicates whether the stats are present or not.
// If true, values below do not have any data. // If true, values below do not have any data.
...@@ -252,3 +284,11 @@ type CpuInstUsage struct { ...@@ -252,3 +284,11 @@ type CpuInstUsage struct {
// Unit: nanocores per second // Unit: nanocores per second
System uint64 `json:"system"` System uint64 `json:"system"`
} }
// Filesystem usage statistics.
type FilesystemStats struct {
// Total Number of bytes consumed by container.
TotalUsageBytes *uint64 `json:"totalUsageBytes,omitempty"`
// Number of bytes consumed by a container through its root filesystem.
BaseUsageBytes *uint64 `json:"baseUsageBytes,omitempty"`
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v2
import (
"fmt"
"time"
"github.com/golang/glog"
"github.com/google/cadvisor/info/v1"
)
func machineFsStatsFromV1(fsStats []v1.FsStats) []MachineFsStats {
var result []MachineFsStats
for _, stat := range fsStats {
readDuration := time.Millisecond * time.Duration(stat.ReadTime)
writeDuration := time.Millisecond * time.Duration(stat.WriteTime)
ioDuration := time.Millisecond * time.Duration(stat.IoTime)
weightedDuration := time.Millisecond * time.Duration(stat.WeightedIoTime)
result = append(result, MachineFsStats{
Device: stat.Device,
Capacity: &stat.Limit,
Usage: &stat.Usage,
Available: &stat.Available,
DiskStats: DiskStats{
ReadsCompleted: &stat.ReadsCompleted,
ReadsMerged: &stat.ReadsMerged,
SectorsRead: &stat.SectorsRead,
ReadDuration: &readDuration,
WritesCompleted: &stat.WritesCompleted,
WritesMerged: &stat.WritesMerged,
SectorsWritten: &stat.SectorsWritten,
WriteDuration: &writeDuration,
IoInProgress: &stat.IoInProgress,
IoDuration: &ioDuration,
WeightedIoDuration: &weightedDuration,
},
})
}
return result
}
func MachineStatsFromV1(cont *v1.ContainerInfo) []MachineStats {
var stats []MachineStats
var last *v1.ContainerStats
for _, val := range cont.Stats {
stat := MachineStats{
Timestamp: val.Timestamp,
}
if cont.Spec.HasCpu {
stat.Cpu = &val.Cpu
cpuInst, err := InstCpuStats(last, val)
if err != nil {
glog.Warningf("Could not get instant cpu stats: %v", err)
} else {
stat.CpuInst = cpuInst
}
last = val
}
if cont.Spec.HasMemory {
stat.Memory = &val.Memory
}
if cont.Spec.HasNetwork {
stat.Network = &NetworkStats{
// FIXME: Use reflection instead.
Tcp: TcpStat(val.Network.Tcp),
Tcp6: TcpStat(val.Network.Tcp6),
Interfaces: val.Network.Interfaces,
}
}
if cont.Spec.HasFilesystem {
stat.Filesystem = machineFsStatsFromV1(val.Filesystem)
}
// TODO(rjnagal): Handle load stats.
stats = append(stats, stat)
}
return stats
}
func ContainerStatsFromV1(spec *v1.ContainerSpec, stats []*v1.ContainerStats) []*ContainerStats {
newStats := make([]*ContainerStats, 0, len(stats))
var last *v1.ContainerStats
for _, val := range stats {
stat := &ContainerStats{
Timestamp: val.Timestamp,
}
if spec.HasCpu {
stat.Cpu = &val.Cpu
cpuInst, err := InstCpuStats(last, val)
if err != nil {
glog.Warningf("Could not get instant cpu stats: %v", err)
} else {
stat.CpuInst = cpuInst
}
last = val
}
if spec.HasMemory {
stat.Memory = &val.Memory
}
if spec.HasNetwork {
// TODO: Handle TcpStats
stat.Network = &NetworkStats{
Interfaces: val.Network.Interfaces,
}
}
if spec.HasFilesystem {
if len(val.Filesystem) == 1 {
stat.Filesystem = &FilesystemStats{
TotalUsageBytes: &val.Filesystem[0].Usage,
BaseUsageBytes: &val.Filesystem[0].BaseUsage,
}
} else if len(val.Filesystem) > 1 {
// Cannot handle multiple devices per container.
glog.Errorf("failed to handle multiple devices for container. Skipping Filesystem stats")
}
}
if spec.HasDiskIo {
stat.DiskIo = &val.DiskIo
}
if spec.HasCustomMetrics {
stat.CustomMetrics = val.CustomMetrics
}
// TODO(rjnagal): Handle load stats.
newStats = append(newStats, stat)
}
return newStats
}
func DeprecatedStatsFromV1(cont *v1.ContainerInfo) []DeprecatedContainerStats {
stats := make([]DeprecatedContainerStats, 0, len(cont.Stats))
var last *v1.ContainerStats
for _, val := range cont.Stats {
stat := DeprecatedContainerStats{
Timestamp: val.Timestamp,
HasCpu: cont.Spec.HasCpu,
HasMemory: cont.Spec.HasMemory,
HasNetwork: cont.Spec.HasNetwork,
HasFilesystem: cont.Spec.HasFilesystem,
HasDiskIo: cont.Spec.HasDiskIo,
HasCustomMetrics: cont.Spec.HasCustomMetrics,
}
if stat.HasCpu {
stat.Cpu = val.Cpu
cpuInst, err := InstCpuStats(last, val)
if err != nil {
glog.Warningf("Could not get instant cpu stats: %v", err)
} else {
stat.CpuInst = cpuInst
}
last = val
}
if stat.HasMemory {
stat.Memory = val.Memory
}
if stat.HasNetwork {
stat.Network.Interfaces = val.Network.Interfaces
}
if stat.HasFilesystem {
stat.Filesystem = val.Filesystem
}
if stat.HasDiskIo {
stat.DiskIo = val.DiskIo
}
if stat.HasCustomMetrics {
stat.CustomMetrics = val.CustomMetrics
}
// TODO(rjnagal): Handle load stats.
stats = append(stats, stat)
}
return stats
}
func InstCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) {
if last == nil {
return nil, nil
}
if !cur.Timestamp.After(last.Timestamp) {
return nil, fmt.Errorf("container stats move backwards in time")
}
if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
return nil, fmt.Errorf("different number of cpus")
}
timeDelta := cur.Timestamp.Sub(last.Timestamp)
if timeDelta <= 100*time.Millisecond {
return nil, fmt.Errorf("time delta unexpectedly small")
}
// Nanoseconds to gain precision and avoid having zero seconds if the
// difference between the timestamps is just under a second
timeDeltaNs := uint64(timeDelta.Nanoseconds())
convertToRate := func(lastValue, curValue uint64) (uint64, error) {
if curValue < lastValue {
return 0, fmt.Errorf("cumulative stats decrease")
}
valueDelta := curValue - lastValue
return (valueDelta * 1e9) / timeDeltaNs, nil
}
total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
if err != nil {
return nil, err
}
percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
for i := range percpu {
var err error
percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
if err != nil {
return nil, err
}
}
user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
if err != nil {
return nil, err
}
system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
if err != nil {
return nil, err
}
return &CpuInstStats{
Usage: CpuInstUsage{
Total: total,
PerCpu: percpu,
User: user,
System: system,
},
}, nil
}
// Get V2 container spec from v1 container info.
func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace string) ContainerSpec {
specV2 := ContainerSpec{
CreationTime: specV1.CreationTime,
HasCpu: specV1.HasCpu,
HasMemory: specV1.HasMemory,
HasFilesystem: specV1.HasFilesystem,
HasNetwork: specV1.HasNetwork,
HasDiskIo: specV1.HasDiskIo,
HasCustomMetrics: specV1.HasCustomMetrics,
Image: specV1.Image,
Labels: specV1.Labels,
}
if specV1.HasCpu {
specV2.Cpu.Limit = specV1.Cpu.Limit
specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
specV2.Cpu.Mask = specV1.Cpu.Mask
}
if specV1.HasMemory {
specV2.Memory.Limit = specV1.Memory.Limit
specV2.Memory.Reservation = specV1.Memory.Reservation
specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
}
if specV1.HasCustomMetrics {
specV2.CustomMetrics = specV1.CustomMetrics
}
specV2.Aliases = aliases
specV2.Namespace = namespace
return specV2
}
func instCpuStats(last, cur *v1.ContainerStats) (*CpuInstStats, error) {
if last == nil {
return nil, nil
}
if !cur.Timestamp.After(last.Timestamp) {
return nil, fmt.Errorf("container stats move backwards in time")
}
if len(last.Cpu.Usage.PerCpu) != len(cur.Cpu.Usage.PerCpu) {
return nil, fmt.Errorf("different number of cpus")
}
timeDelta := cur.Timestamp.Sub(last.Timestamp)
if timeDelta <= 100*time.Millisecond {
return nil, fmt.Errorf("time delta unexpectedly small")
}
// Nanoseconds to gain precision and avoid having zero seconds if the
// difference between the timestamps is just under a second
timeDeltaNs := uint64(timeDelta.Nanoseconds())
convertToRate := func(lastValue, curValue uint64) (uint64, error) {
if curValue < lastValue {
return 0, fmt.Errorf("cumulative stats decrease")
}
valueDelta := curValue - lastValue
return (valueDelta * 1e9) / timeDeltaNs, nil
}
total, err := convertToRate(last.Cpu.Usage.Total, cur.Cpu.Usage.Total)
if err != nil {
return nil, err
}
percpu := make([]uint64, len(last.Cpu.Usage.PerCpu))
for i := range percpu {
var err error
percpu[i], err = convertToRate(last.Cpu.Usage.PerCpu[i], cur.Cpu.Usage.PerCpu[i])
if err != nil {
return nil, err
}
}
user, err := convertToRate(last.Cpu.Usage.User, cur.Cpu.Usage.User)
if err != nil {
return nil, err
}
system, err := convertToRate(last.Cpu.Usage.System, cur.Cpu.Usage.System)
if err != nil {
return nil, err
}
return &CpuInstStats{
Usage: CpuInstUsage{
Total: total,
PerCpu: percpu,
User: user,
System: system,
},
}, nil
}
...@@ -16,6 +16,8 @@ package v2 ...@@ -16,6 +16,8 @@ package v2
import ( import (
// TODO(rjnagal): Move structs from v1. // TODO(rjnagal): Move structs from v1.
"time"
"github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v1"
) )
...@@ -86,3 +88,97 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes { ...@@ -86,3 +88,97 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
InstanceType: mi.InstanceType, InstanceType: mi.InstanceType,
} }
} }
// MachineStats contains usage statistics for the entire machine.
type MachineStats struct {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
// In nanoseconds (aggregated)
Cpu *v1.CpuStats `json:"cpu,omitempty"`
// In nanocores per second (instantaneous)
CpuInst *CpuInstStats `json:"cpu_inst,omitempty"`
// Memory statistics
Memory *v1.MemoryStats `json:"memory,omitempty"`
// Network statistics
Network *NetworkStats `json:"network,omitempty"`
// Filesystem statistics
Filesystem []MachineFsStats `json:"filesystem,omitempty"`
// Task load statistics
Load *v1.LoadStats `json:"load_stats,omitempty"`
}
// MachineFsStats contains per filesystem capacity and usage information.
type MachineFsStats struct {
// The block device name associated with the filesystem.
Device string `json:"device"`
// Number of bytes that can be consumed on this filesystem.
Capacity *uint64 `json:"capacity,omitempty"`
// Number of bytes that is currently consumed on this filesystem.
Usage *uint64 `json:"usage,omitempty"`
// Number of bytes available for non-root user on this filesystem.
Available *uint64 `json:"available,omitempty"`
// DiskStats for this device.
DiskStats `json:"inline"`
}
// DiskStats contains per partition usage information.
// This information is only available at the machine level.
type DiskStats struct {
// Number of reads completed
// This is the total number of reads completed successfully.
ReadsCompleted *uint64 `json:"reads_completed,omitempty"`
// Number of reads merged
// Reads and writes which are adjacent to each other may be merged for
// efficiency. Thus two 4K reads may become one 8K read before it is
// ultimately handed to the disk, and so it will be counted (and queued)
// as only one I/O. This field lets you know how often this was done.
ReadsMerged *uint64 `json:"reads_merged,omitempty"`
// Number of sectors read
// This is the total number of sectors read successfully.
SectorsRead *uint64 `json:"sectors_read,omitempty"`
// Time spent reading
// This is the total number of milliseconds spent by all reads (as
// measured from __make_request() to end_that_request_last()).
ReadDuration *time.Duration `json:"read_duration,omitempty"`
// Number of writes completed
// This is the total number of writes completed successfully.
WritesCompleted *uint64 `json:"writes_completed,omitempty"`
// Number of writes merged
// See the description of reads merged.
WritesMerged *uint64 `json:"writes_merged,omitempty"`
// Number of sectors written
// This is the total number of sectors written successfully.
SectorsWritten *uint64 `json:"sectors_written,omitempty"`
// Time spent writing
// This is the total number of milliseconds spent by all writes (as
// measured from __make_request() to end_that_request_last()).
WriteDuration *time.Duration `json:"write_duration,omitempty"`
// Number of I/Os currently in progress
// The only field that should go to zero. Incremented as requests are
// given to appropriate struct request_queue and decremented as they finish.
IoInProgress *uint64 `json:"io_in_progress,omitempty"`
// Time spent doing I/Os
// This field increases so long as field 9 is nonzero.
IoDuration *time.Duration `json:"io_duration,omitempty"`
// weighted time spent doing I/Os
// This field is incremented at each I/O start, I/O completion, I/O
// merge, or read of these stats by the number of I/Os in progress
// (field 9) times the number of milliseconds spent doing I/O since the
// last update of this field. This can provide an easy measure of both
// I/O completion time and the backlog that may be accumulating.
WeightedIoDuration *time.Duration `json:"weighted_io_duration,omitempty"`
}
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math" "math"
"math/rand"
"os/exec" "os/exec"
"path" "path"
"regexp" "regexp"
...@@ -36,14 +37,14 @@ import ( ...@@ -36,14 +37,14 @@ import (
"github.com/google/cadvisor/summary" "github.com/google/cadvisor/summary"
"github.com/google/cadvisor/utils/cpuload" "github.com/google/cadvisor/utils/cpuload"
"github.com/docker/docker/pkg/units" units "github.com/docker/go-units"
"github.com/golang/glog" "github.com/golang/glog"
) )
// Housekeeping interval. // Housekeeping interval.
var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings") var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings")
var cgroupPathRegExp = regexp.MustCompile(`.*devices.*:(.*?)[,;$].*`) var cgroupPathRegExp = regexp.MustCompile(`devices[^:]*:(.*?)[,;$]`)
type containerInfo struct { type containerInfo struct {
info.ContainerReference info.ContainerReference
...@@ -78,6 +79,17 @@ type containerData struct { ...@@ -78,6 +79,17 @@ type containerData struct {
collectorManager collector.CollectorManager collectorManager collector.CollectorManager
} }
// jitter returns a time.Duration between duration and duration + maxFactor * duration,
// to allow clients to avoid converging on periodic behavior. If maxFactor is 0.0, a
// suggested default value will be chosen.
func jitter(duration time.Duration, maxFactor float64) time.Duration {
if maxFactor <= 0.0 {
maxFactor = 1.0
}
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))
return wait
}
func (c *containerData) Start() error { func (c *containerData) Start() error {
go c.housekeeping() go c.housekeeping()
return nil return nil
...@@ -356,11 +368,14 @@ func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Tim ...@@ -356,11 +368,14 @@ func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Tim
} }
} }
return lastHousekeeping.Add(self.housekeepingInterval) return lastHousekeeping.Add(jitter(self.housekeepingInterval, 1.0))
} }
// TODO(vmarmol): Implement stats collecting as a custom collector. // TODO(vmarmol): Implement stats collecting as a custom collector.
func (c *containerData) housekeeping() { func (c *containerData) housekeeping() {
// Start any background goroutines - must be cleaned up in c.handler.Cleanup().
c.handler.Start()
// Long housekeeping is either 100ms or half of the housekeeping interval. // Long housekeeping is either 100ms or half of the housekeeping interval.
longHousekeeping := 100 * time.Millisecond longHousekeeping := 100 * time.Millisecond
if *HousekeepingInterval/2 < longHousekeeping { if *HousekeepingInterval/2 < longHousekeeping {
......
...@@ -62,6 +62,9 @@ type Manager interface { ...@@ -62,6 +62,9 @@ type Manager interface {
// Get information about a container. // Get information about a container.
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
// Get V2 information about a container.
GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error)
// Get information about all subcontainers of the specified container (includes self). // Get information about all subcontainers of the specified container (includes self).
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
...@@ -375,33 +378,8 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp ...@@ -375,33 +378,8 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
// Get V2 container spec from v1 container info. // Get V2 container spec from v1 container info.
func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec { func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
specV1 := self.getAdjustedSpec(cinfo) spec := self.getAdjustedSpec(cinfo)
specV2 := v2.ContainerSpec{ return v2.ContainerSpecFromV1(&spec, cinfo.Aliases, cinfo.Namespace)
CreationTime: specV1.CreationTime,
HasCpu: specV1.HasCpu,
HasMemory: specV1.HasMemory,
HasFilesystem: specV1.HasFilesystem,
HasNetwork: specV1.HasNetwork,
HasDiskIo: specV1.HasDiskIo,
HasCustomMetrics: specV1.HasCustomMetrics,
Image: specV1.Image,
}
if specV1.HasCpu {
specV2.Cpu.Limit = specV1.Cpu.Limit
specV2.Cpu.MaxLimit = specV1.Cpu.MaxLimit
specV2.Cpu.Mask = specV1.Cpu.Mask
}
if specV1.HasMemory {
specV2.Memory.Limit = specV1.Memory.Limit
specV2.Memory.Reservation = specV1.Memory.Reservation
specV2.Memory.SwapLimit = specV1.Memory.SwapLimit
}
if specV1.HasCustomMetrics {
specV2.CustomMetrics = specV1.CustomMetrics
}
specV2.Aliases = cinfo.Aliases
specV2.Namespace = cinfo.Namespace
return specV2
} }
func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
...@@ -417,7 +395,6 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { ...@@ -417,7 +395,6 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
return spec return spec
} }
// Get a container by name.
func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
cont, err := self.getContainerData(containerName) cont, err := self.getContainerData(containerName)
if err != nil { if err != nil {
...@@ -426,6 +403,34 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe ...@@ -426,6 +403,34 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe
return self.containerDataToContainerInfo(cont, query) return self.containerDataToContainerInfo(cont, query)
} }
func (self *manager) GetContainerInfoV2(containerName string, options v2.RequestOptions) (map[string]v2.ContainerInfo, error) {
containers, err := self.getRequestedContainers(containerName, options)
if err != nil {
return nil, err
}
infos := make(map[string]v2.ContainerInfo, len(containers))
for name, container := range containers {
cinfo, err := container.GetInfo()
if err != nil {
return nil, err
}
var nilTime time.Time // Ignored.
stats, err := self.memoryCache.RecentStats(name, nilTime, nilTime, options.Count)
if err != nil {
return nil, err
}
infos[name] = v2.ContainerInfo{
Spec: self.getV2Spec(cinfo),
Stats: v2.ContainerStatsFromV1(&cinfo.Spec, stats),
}
}
return infos, nil
}
func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
// Get the info from the container. // Get the info from the container.
cinfo, err := cont.GetInfo() cinfo, err := cont.GetInfo()
...@@ -741,6 +746,18 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c ...@@ -741,6 +746,18 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c
// Create a container. // Create a container.
func (m *manager) createContainer(containerName string) error { func (m *manager) createContainer(containerName string) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
namespacedName := namespacedContainerName{
Name: containerName,
}
// Check that the container didn't already exist.
if _, ok := m.containers[namespacedName]; ok {
return nil
}
handler, accept, err := container.NewContainerHandler(containerName, m.inHostNamespace) handler, accept, err := container.NewContainerHandler(containerName, m.inHostNamespace)
if err != nil { if err != nil {
return err return err
...@@ -770,35 +787,15 @@ func (m *manager) createContainer(containerName string) error { ...@@ -770,35 +787,15 @@ func (m *manager) createContainer(containerName string) error {
return err return err
} }
// Add to the containers map. // Add the container name and all its aliases. The aliases must be within the namespace of the factory.
alreadyExists := func() bool { m.containers[namespacedName] = cont
m.containersLock.Lock() for _, alias := range cont.info.Aliases {
defer m.containersLock.Unlock() m.containers[namespacedContainerName{
Namespace: cont.info.Namespace,
namespacedName := namespacedContainerName{ Name: alias,
Name: containerName, }] = cont
}
// Check that the container didn't already exist.
_, ok := m.containers[namespacedName]
if ok {
return true
}
// Add the container name and all its aliases. The aliases must be within the namespace of the factory.
m.containers[namespacedName] = cont
for _, alias := range cont.info.Aliases {
m.containers[namespacedContainerName{
Namespace: cont.info.Namespace,
Name: alias,
}] = cont
}
return false
}()
if alreadyExists {
return nil
} }
glog.V(3).Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace) glog.V(3).Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
contSpec, err := cont.handler.GetSpec() contSpec, err := cont.handler.GetSpec()
...@@ -822,9 +819,7 @@ func (m *manager) createContainer(containerName string) error { ...@@ -822,9 +819,7 @@ func (m *manager) createContainer(containerName string) error {
} }
// Start the container's housekeeping. // Start the container's housekeeping.
cont.Start() return cont.Start()
return nil
} }
func (m *manager) destroyContainer(containerName string) error { func (m *manager) destroyContainer(containerName string) error {
......
...@@ -16,6 +16,7 @@ package metrics ...@@ -16,6 +16,7 @@ package metrics
import ( import (
"fmt" "fmt"
"regexp"
"time" "time"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
...@@ -127,6 +128,20 @@ func NewPrometheusCollector(infoProvider infoProvider, f ContainerNameToLabelsFu ...@@ -127,6 +128,20 @@ func NewPrometheusCollector(infoProvider infoProvider, f ContainerNameToLabelsFu
return values return values
}, },
}, { }, {
name: "container_memory_cache",
help: "Number of bytes of page cache memory.",
valueType: prometheus.GaugeValue,
getValues: func(s *info.ContainerStats) metricValues {
return metricValues{{value: float64(s.Memory.Cache)}}
},
}, {
name: "container_memory_rss",
help: "Size of RSS in bytes.",
valueType: prometheus.GaugeValue,
getValues: func(s *info.ContainerStats) metricValues {
return metricValues{{value: float64(s.Memory.RSS)}}
},
}, {
name: "container_memory_failcnt", name: "container_memory_failcnt",
help: "Number of memory usage hits limits", help: "Number of memory usage hits limits",
valueType: prometheus.CounterValue, valueType: prometheus.CounterValue,
...@@ -508,11 +523,20 @@ func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric) ...@@ -508,11 +523,20 @@ func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric)
if c.containerNameToLabels != nil { if c.containerNameToLabels != nil {
newLabels := c.containerNameToLabels(name) newLabels := c.containerNameToLabels(name)
for k, v := range newLabels { for k, v := range newLabels {
baseLabels = append(baseLabels, k) baseLabels = append(baseLabels, sanitizeLabelName(k))
baseLabelValues = append(baseLabelValues, v) baseLabelValues = append(baseLabelValues, v)
} }
} }
for k, v := range container.Spec.Labels {
baseLabels = append(baseLabels, sanitizeLabelName(k))
baseLabelValues = append(baseLabelValues, v)
}
for k, v := range container.Spec.Envs {
baseLabels = append(baseLabels, sanitizeLabelName(k))
baseLabelValues = append(baseLabelValues, v)
}
// Container spec // Container spec
desc := prometheus.NewDesc("container_start_time_seconds", "Start time of the container since unix epoch in seconds.", baseLabels, nil) desc := prometheus.NewDesc("container_start_time_seconds", "Start time of the container since unix epoch in seconds.", baseLabels, nil)
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.CreationTime.Unix()), baseLabelValues...) ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, float64(container.Spec.CreationTime.Unix()), baseLabelValues...)
...@@ -571,3 +595,11 @@ func specMemoryValue(v uint64) float64 { ...@@ -571,3 +595,11 @@ func specMemoryValue(v uint64) float64 {
} }
return float64(v) return float64(v)
} }
var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
// sanitizeLabelName replaces anything that doesn't match
// client_label.LabelNameRE with an underscore.
func sanitizeLabelName(name string) string {
return invalidLabelCharRE.ReplaceAllString(name, "_")
}
...@@ -103,7 +103,8 @@ func (self *elasticStorage) AddStats(ref info.ContainerReference, stats *info.Co ...@@ -103,7 +103,8 @@ func (self *elasticStorage) AddStats(ref info.ContainerReference, stats *info.Co
Do() Do()
if err != nil { if err != nil {
// Handle error // Handle error
panic(fmt.Errorf("failed to write stats to ElasticSearch- %s", err)) fmt.Printf("failed to write stats to ElasticSearch - %s", err)
return
} }
}() }()
return nil return nil
...@@ -135,14 +136,15 @@ func newStorage( ...@@ -135,14 +136,15 @@ func newStorage(
) )
if err != nil { if err != nil {
// Handle error // Handle error
panic(err) return nil, fmt.Errorf("failed to create the elasticsearch client - %s", err)
} }
// Ping the Elasticsearch server to get e.g. the version number // Ping the Elasticsearch server to get e.g. the version number
info, code, err := client.Ping().URL(elasticHost).Do() info, code, err := client.Ping().URL(elasticHost).Do()
if err != nil { if err != nil {
// Handle error // Handle error
panic(err) return nil, fmt.Errorf("failed to ping the elasticsearch - %s", err)
} }
fmt.Printf("Elasticsearch returned with code %d and version %s", code, info.Version.Number) fmt.Printf("Elasticsearch returned with code %d and version %s", code, info.Version.Number)
......
0.20.1 0.20.4
\ No newline at end of file \ No newline at end of file
## How to Contribute ##
We always welcome contributions to help make `go-zfs` better. Please take a moment to read this document if you would like to contribute.
### Reporting issues ###
We use [Github issues](https://github.com/mistifyio/go-zfs/issues) to track bug reports, feature requests, and submitting pull requests.
If you find a bug:
* Use the GitHub issue search to check whether the bug has already been reported.
* If the issue has been fixed, try to reproduce the issue using the latest `master` branch of the repository.
* If the issue still reproduces or has not yet been reported, try to isolate the problem before opening an issue, if possible. Also provide the steps taken to reproduce the bug.
### Pull requests ###
We welcome bug fixes, improvements, and new features. Before embarking on making significant changes, please open an issue and ask first so that you do not risk duplicating efforts or spending time working on something that may be out of scope. For minor items, just open a pull request.
[Fork the project](https://help.github.com/articles/fork-a-repo), clone your fork, and add the upstream to your remote:
$ git clone git@github.com:<your-username>/go-zfs.git
$ cd go-zfs
$ git remote add upstream https://github.com/mistifyio/go-zfs.git
If you need to pull new changes committed upstream:
$ git checkout master
$ git fetch upstream
$ git merge upstream/master
Don' work directly on master as this makes it harder to merge later. Create a feature branch for your fix or new feature:
$ git checkout -b <feature-branch-name>
Please try to commit your changes in logical chunks. Ideally, you should include the issue number in the commit message.
$ git commit -m "Issue #<issue-number> - <commit-message>"
Push your feature branch to your fork.
$ git push origin <feature-branch-name>
[Open a Pull Request](https://help.github.com/articles/using-pull-requests) against the upstream master branch. Please give your pull request a clear title and description and note which issue(s) your pull request fixes.
* All Go code should be formatted using [gofmt](http://golang.org/cmd/gofmt/).
* Every exported function should have [documentation](http://blog.golang.org/godoc-documenting-go-code) and corresponding [tests](http://golang.org/doc/code.html#Testing).
**Important:** By submitting a patch, you agree to allow the project owners to license your work under the [Apache 2.0 License](./LICENSE).
### Go Tools ###
For consistency and to catch minor issues for all of go code, please run the following:
* goimports
* go vet
* golint
* errcheck
Many editors can execute the above on save.
----
Guidelines based on http://azkaban.github.io/contributing.html
# Go Wrapper for ZFS #
Simple wrappers for ZFS command line tools.
[![GoDoc](https://godoc.org/github.com/mistifyio/go-zfs?status.svg)](https://godoc.org/github.com/mistifyio/go-zfs)
## Requirements ##
You need a working ZFS setup. To use on Ubuntu 14.04, setup ZFS:
sudo apt-get install python-software-properties
sudo apt-add-repository ppa:zfs-native/stable
sudo apt-get update
sudo apt-get install ubuntu-zfs libzfs-dev
Developed using Go 1.3, but currently there isn't anything 1.3 specific. Don't use Ubuntu packages for Go, use http://golang.org/doc/install
Generally you need root privileges to use anything zfs related.
## Status ##
This has been only been tested on Ubuntu 14.04
In the future, we hope to work directly with libzfs.
# Hacking #
The tests have decent examples for most functions.
```go
//assuming a zpool named test
//error handling ommitted
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
ok(t, err)
s, err := f.Snapshot("test", nil)
ok(t, err)
// snapshot is named "test/snapshot-test@test"
c, err := s.Clone("test/clone-test", nil)
err := c.Destroy()
err := s.Destroy()
err := f.Destroy()
```
# Contributing #
See the [contributing guidelines](./CONTRIBUTING.md)
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.ssh.forward_agent = true
config.vm.synced_folder ".", "/home/vagrant/go/src/github.com/mistifyio/go-zfs", create: true
config.vm.provision "shell", inline: <<EOF
cat << END > /etc/profile.d/go.sh
export GOPATH=\\$HOME/go
export PATH=\\$GOPATH/bin:/usr/local/go/bin:\\$PATH
END
chown -R vagrant /home/vagrant/go
apt-get update
apt-get install -y software-properties-common curl
apt-add-repository --yes ppa:zfs-native/stable
apt-get update
apt-get install -y ubuntu-zfs
cd /home/vagrant
curl -z go1.3.3.linux-amd64.tar.gz -L -O https://storage.googleapis.com/golang/go1.3.3.linux-amd64.tar.gz
tar -C /usr/local -zxf /home/vagrant/go1.3.3.linux-amd64.tar.gz
cat << END > /etc/sudoers.d/go
Defaults env_keep += "GOPATH"
END
EOF
end
package zfs
import (
"fmt"
)
// Error is an error which is returned when the `zfs` or `zpool` shell
// commands return with a non-zero exit code.
type Error struct {
Err error
Debug string
Stderr string
}
// Error returns the string representation of an Error.
func (e Error) Error() string {
return fmt.Sprintf("%s: %q => %s", e.Err, e.Debug, e.Stderr)
}
package zfs
import (
"bytes"
"fmt"
"io"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/pborman/uuid"
)
type command struct {
Command string
Stdin io.Reader
Stdout io.Writer
}
func (c *command) Run(arg ...string) ([][]string, error) {
cmd := exec.Command(c.Command, arg...)
var stdout, stderr bytes.Buffer
if c.Stdout == nil {
cmd.Stdout = &stdout
} else {
cmd.Stdout = c.Stdout
}
if c.Stdin != nil {
cmd.Stdin = c.Stdin
}
cmd.Stderr = &stderr
id := uuid.New()
joinedArgs := strings.Join(cmd.Args, " ")
logger.Log([]string{"ID:" + id, "START", joinedArgs})
err := cmd.Run()
logger.Log([]string{"ID:" + id, "FINISH"})
if err != nil {
return nil, &Error{
Err: err,
Debug: strings.Join([]string{cmd.Path, joinedArgs}, " "),
Stderr: stderr.String(),
}
}
// assume if you passed in something for stdout, that you know what to do with it
if c.Stdout != nil {
return nil, nil
}
lines := strings.Split(stdout.String(), "\n")
//last line is always blank
lines = lines[0 : len(lines)-1]
output := make([][]string, len(lines))
for i, l := range lines {
output[i] = strings.Fields(l)
}
return output, nil
}
func setString(field *string, value string) {
v := ""
if value != "-" {
v = value
}
*field = v
}
func setUint(field *uint64, value string) error {
var v uint64
if value != "-" {
var err error
v, err = strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
}
*field = v
return nil
}
func (ds *Dataset) parseLine(line []string) error {
prop := line[1]
val := line[2]
var err error
switch prop {
case "available":
err = setUint(&ds.Avail, val)
case "compression":
setString(&ds.Compression, val)
case "mountpoint":
setString(&ds.Mountpoint, val)
case "quota":
err = setUint(&ds.Quota, val)
case "type":
setString(&ds.Type, val)
case "origin":
setString(&ds.Origin, val)
case "used":
err = setUint(&ds.Used, val)
case "volsize":
err = setUint(&ds.Volsize, val)
case "written":
err = setUint(&ds.Written, val)
case "logicalused":
err = setUint(&ds.Logicalused, val)
}
return err
}
/*
* from zfs diff`s escape function:
*
* Prints a file name out a character at a time. If the character is
* not in the range of what we consider "printable" ASCII, display it
* as an escaped 3-digit octal value. ASCII values less than a space
* are all control characters and we declare the upper end as the
* DELete character. This also is the last 7-bit ASCII character.
* We choose to treat all 8-bit ASCII as not printable for this
* application.
*/
func unescapeFilepath(path string) (string, error) {
buf := make([]byte, 0, len(path))
llen := len(path)
for i := 0; i < llen; {
if path[i] == '\\' {
if llen < i+4 {
return "", fmt.Errorf("Invalid octal code: too short")
}
octalCode := path[(i + 1):(i + 4)]
val, err := strconv.ParseUint(octalCode, 8, 8)
if err != nil {
return "", fmt.Errorf("Invalid octal code: %v", err)
}
buf = append(buf, byte(val))
i += 4
} else {
buf = append(buf, path[i])
i++
}
}
return string(buf), nil
}
var changeTypeMap = map[string]ChangeType{
"-": Removed,
"+": Created,
"M": Modified,
"R": Renamed,
}
var inodeTypeMap = map[string]InodeType{
"B": BlockDevice,
"C": CharacterDevice,
"/": Directory,
">": Door,
"|": NamedPipe,
"@": SymbolicLink,
"P": EventPort,
"=": Socket,
"F": File,
}
// matches (+1) or (-1)
var referenceCountRegex = regexp.MustCompile("\\(([+-]\\d+?)\\)")
func parseReferenceCount(field string) (int, error) {
matches := referenceCountRegex.FindStringSubmatch(field)
if matches == nil {
return 0, fmt.Errorf("Regexp does not match")
}
return strconv.Atoi(matches[1])
}
func parseInodeChange(line []string) (*InodeChange, error) {
llen := len(line)
if llen < 1 {
return nil, fmt.Errorf("Empty line passed")
}
changeType := changeTypeMap[line[0]]
if changeType == 0 {
return nil, fmt.Errorf("Unknown change type '%s'", line[0])
}
switch changeType {
case Renamed:
if llen != 4 {
return nil, fmt.Errorf("Mismatching number of fields: expect 4, got: %d", llen)
}
case Modified:
if llen != 4 && llen != 3 {
return nil, fmt.Errorf("Mismatching number of fields: expect 3..4, got: %d", llen)
}
default:
if llen != 3 {
return nil, fmt.Errorf("Mismatching number of fields: expect 3, got: %d", llen)
}
}
inodeType := inodeTypeMap[line[1]]
if inodeType == 0 {
return nil, fmt.Errorf("Unknown inode type '%s'", line[1])
}
path, err := unescapeFilepath(line[2])
if err != nil {
return nil, fmt.Errorf("Failed to parse filename: %v", err)
}
var newPath string
var referenceCount int
switch changeType {
case Renamed:
newPath, err = unescapeFilepath(line[3])
if err != nil {
return nil, fmt.Errorf("Failed to parse filename: %v", err)
}
case Modified:
if llen == 4 {
referenceCount, err = parseReferenceCount(line[3])
if err != nil {
return nil, fmt.Errorf("Failed to parse reference count: %v", err)
}
}
default:
newPath = ""
}
return &InodeChange{
Change: changeType,
Type: inodeType,
Path: path,
NewPath: newPath,
ReferenceCountChange: referenceCount,
}, nil
}
// example input
//M / /testpool/bar/
//+ F /testpool/bar/hello.txt
//M / /testpool/bar/hello.txt (+1)
//M / /testpool/bar/hello-hardlink
func parseInodeChanges(lines [][]string) ([]*InodeChange, error) {
changes := make([]*InodeChange, len(lines))
for i, line := range lines {
c, err := parseInodeChange(line)
if err != nil {
return nil, fmt.Errorf("Failed to parse line %d of zfs diff: %v, got: '%s'", i, err, line)
}
changes[i] = c
}
return changes, nil
}
func listByType(t, filter string) ([]*Dataset, error) {
args := []string{"get", "-rHp", "-t", t, "all"}
if filter != "" {
args = append(args, filter)
}
out, err := zfs(args...)
if err != nil {
return nil, err
}
var datasets []*Dataset
name := ""
var ds *Dataset
for _, line := range out {
if name != line[0] {
name = line[0]
ds = &Dataset{Name: name}
datasets = append(datasets, ds)
}
if err := ds.parseLine(line); err != nil {
return nil, err
}
}
return datasets, nil
}
func propsSlice(properties map[string]string) []string {
args := make([]string, 0, len(properties)*3)
for k, v := range properties {
args = append(args, "-o")
args = append(args, fmt.Sprintf("%s=%s", k, v))
}
return args
}
func (z *Zpool) parseLine(line []string) error {
prop := line[1]
val := line[2]
var err error
switch prop {
case "health":
setString(&z.Health, val)
case "allocated":
err = setUint(&z.Allocated, val)
case "size":
err = setUint(&z.Size, val)
case "free":
err = setUint(&z.Free, val)
}
return err
}
package zfs
// ZFS zpool states, which can indicate if a pool is online, offline,
// degraded, etc. More information regarding zpool states can be found here:
// https://docs.oracle.com/cd/E19253-01/819-5461/gamno/index.html.
const (
ZpoolOnline = "ONLINE"
ZpoolDegraded = "DEGRADED"
ZpoolFaulted = "FAULTED"
ZpoolOffline = "OFFLINE"
ZpoolUnavail = "UNAVAIL"
ZpoolRemoved = "REMOVED"
)
// Zpool is a ZFS zpool. A pool is a top-level structure in ZFS, and can
// contain many descendent datasets.
type Zpool struct {
Name string
Health string
Allocated uint64
Size uint64
Free uint64
}
// zpool is a helper function to wrap typical calls to zpool.
func zpool(arg ...string) ([][]string, error) {
c := command{Command: "zpool"}
return c.Run(arg...)
}
// GetZpool retrieves a single ZFS zpool by name.
func GetZpool(name string) (*Zpool, error) {
out, err := zpool("get", "all", "-p", name)
if err != nil {
return nil, err
}
// there is no -H
out = out[1:]
z := &Zpool{Name: name}
for _, line := range out {
if err := z.parseLine(line); err != nil {
return nil, err
}
}
return z, nil
}
// Datasets returns a slice of all ZFS datasets in a zpool.
func (z *Zpool) Datasets() ([]*Dataset, error) {
return Datasets(z.Name)
}
// Snapshots returns a slice of all ZFS snapshots in a zpool.
func (z *Zpool) Snapshots() ([]*Dataset, error) {
return Snapshots(z.Name)
}
// CreateZpool creates a new ZFS zpool with the specified name, properties,
// and optional arguments.
// A full list of available ZFS properties and command-line arguments may be
// found here: https://www.freebsd.org/cgi/man.cgi?zfs(8).
func CreateZpool(name string, properties map[string]string, args ...string) (*Zpool, error) {
cli := make([]string, 1, 4)
cli[0] = "create"
if properties != nil {
cli = append(cli, propsSlice(properties)...)
}
cli = append(cli, name)
cli = append(cli, args...)
_, err := zpool(cli...)
if err != nil {
return nil, err
}
return &Zpool{Name: name}, nil
}
// Destroy destroys a ZFS zpool by name.
func (z *Zpool) Destroy() error {
_, err := zpool("destroy", z.Name)
return err
}
// ListZpools list all ZFS zpools accessible on the current system.
func ListZpools() ([]*Zpool, error) {
args := []string{"list", "-Ho", "name"}
out, err := zpool(args...)
if err != nil {
return nil, err
}
var pools []*Zpool
for _, line := range out {
z, err := GetZpool(line[0])
if err != nil {
return nil, err
}
pools = append(pools, z)
}
return pools, nil
}
...@@ -24,7 +24,7 @@ import ( ...@@ -24,7 +24,7 @@ import (
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
) )
var KnownKubeletMetrics = map[string][]string{ var NecessaryKubeletMetrics = map[string][]string{
"cadvisor_version_info": {"cadvisorRevision", "cadvisorVersion", "dockerVersion", "kernelVersion", "osVersion"}, "cadvisor_version_info": {"cadvisorRevision", "cadvisorVersion", "dockerVersion", "kernelVersion", "osVersion"},
"container_cpu_system_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_cpu_system_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
"container_cpu_usage_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "cpu"}, "container_cpu_usage_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "cpu"},
...@@ -43,6 +43,8 @@ var KnownKubeletMetrics = map[string][]string{ ...@@ -43,6 +43,8 @@ var KnownKubeletMetrics = map[string][]string{
"container_fs_writes_merged_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_fs_writes_merged_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
"container_fs_writes_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_fs_writes_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
"container_last_seen": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_last_seen": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
"container_memory_cache": {},
"container_memory_rss": {},
"container_memory_failcnt": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_memory_failcnt": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
"container_memory_failures_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "scope", "type"}, "container_memory_failures_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "scope", "type"},
"container_memory_usage_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, "container_memory_usage_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
...@@ -116,15 +118,15 @@ func (m *KubeletMetrics) Equal(o KubeletMetrics) bool { ...@@ -116,15 +118,15 @@ func (m *KubeletMetrics) Equal(o KubeletMetrics) bool {
func NewKubeletMetrics() KubeletMetrics { func NewKubeletMetrics() KubeletMetrics {
result := NewMetrics() result := NewMetrics()
for metric := range KnownKubeletMetrics { for metric := range NecessaryKubeletMetrics {
result[metric] = make(model.Samples, 0) result[metric] = make(model.Samples, 0)
} }
return KubeletMetrics(result) return KubeletMetrics(result)
} }
func parseKubeletMetrics(data string, unknownMetrics sets.String) (KubeletMetrics, error) { func parseKubeletMetrics(data string) (KubeletMetrics, error) {
result := NewKubeletMetrics() result := NewKubeletMetrics()
if err := parseMetrics(data, KnownKubeletMetrics, (*Metrics)(&result), unknownMetrics); err != nil { if err := parseMetrics(data, NecessaryKubeletMetrics, (*Metrics)(&result), nil); err != nil {
return KubeletMetrics{}, err return KubeletMetrics{}, err
} }
return result, nil return result, nil
......
...@@ -85,7 +85,7 @@ func NewMetricsGrabber(c *client.Client, kubelets bool, scheduler bool, controll ...@@ -85,7 +85,7 @@ func NewMetricsGrabber(c *client.Client, kubelets bool, scheduler bool, controll
}, nil }, nil
} }
func (g *MetricsGrabber) GrabFromKubelet(nodeName string, unknownMetrics sets.String) (KubeletMetrics, error) { func (g *MetricsGrabber) GrabFromKubelet(nodeName string) (KubeletMetrics, error) {
nodes, err := g.client.Nodes().List(api.ListOptions{FieldSelector: fields.Set{client.ObjectNameField: nodeName}.AsSelector()}) nodes, err := g.client.Nodes().List(api.ListOptions{FieldSelector: fields.Set{client.ObjectNameField: nodeName}.AsSelector()})
if err != nil { if err != nil {
return KubeletMetrics{}, err return KubeletMetrics{}, err
...@@ -94,10 +94,10 @@ func (g *MetricsGrabber) GrabFromKubelet(nodeName string, unknownMetrics sets.St ...@@ -94,10 +94,10 @@ func (g *MetricsGrabber) GrabFromKubelet(nodeName string, unknownMetrics sets.St
return KubeletMetrics{}, fmt.Errorf("Error listing nodes with name %v, got %v", nodeName, nodes.Items) return KubeletMetrics{}, fmt.Errorf("Error listing nodes with name %v, got %v", nodeName, nodes.Items)
} }
kubeletPort := nodes.Items[0].Status.DaemonEndpoints.KubeletEndpoint.Port kubeletPort := nodes.Items[0].Status.DaemonEndpoints.KubeletEndpoint.Port
return g.grabFromKubeletInternal(nodeName, kubeletPort, unknownMetrics) return g.grabFromKubeletInternal(nodeName, kubeletPort)
} }
func (g *MetricsGrabber) grabFromKubeletInternal(nodeName string, kubeletPort int, unknownMetrics sets.String) (KubeletMetrics, error) { func (g *MetricsGrabber) grabFromKubeletInternal(nodeName string, kubeletPort int) (KubeletMetrics, error) {
if kubeletPort <= 0 || kubeletPort > 65535 { if kubeletPort <= 0 || kubeletPort > 65535 {
return KubeletMetrics{}, fmt.Errorf("Invalid Kubelet port %v. Skipping Kubelet's metrics gathering.", kubeletPort) return KubeletMetrics{}, fmt.Errorf("Invalid Kubelet port %v. Skipping Kubelet's metrics gathering.", kubeletPort)
} }
...@@ -105,7 +105,7 @@ func (g *MetricsGrabber) grabFromKubeletInternal(nodeName string, kubeletPort in ...@@ -105,7 +105,7 @@ func (g *MetricsGrabber) grabFromKubeletInternal(nodeName string, kubeletPort in
if err != nil { if err != nil {
return KubeletMetrics{}, err return KubeletMetrics{}, err
} }
return parseKubeletMetrics(output, unknownMetrics) return parseKubeletMetrics(output)
} }
func (g *MetricsGrabber) GrabFromScheduler(unknownMetrics sets.String) (SchedulerMetrics, error) { func (g *MetricsGrabber) GrabFromScheduler(unknownMetrics sets.String) (SchedulerMetrics, error) {
...@@ -173,7 +173,7 @@ func (g *MetricsGrabber) Grab(unknownMetrics sets.String) (MetricsCollection, er ...@@ -173,7 +173,7 @@ func (g *MetricsGrabber) Grab(unknownMetrics sets.String) (MetricsCollection, er
} else { } else {
for _, node := range nodes.Items { for _, node := range nodes.Items {
kubeletPort := node.Status.DaemonEndpoints.KubeletEndpoint.Port kubeletPort := node.Status.DaemonEndpoints.KubeletEndpoint.Port
metrics, err := g.grabFromKubeletInternal(node.Name, kubeletPort, nil) metrics, err := g.grabFromKubeletInternal(node.Name, kubeletPort)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
......
...@@ -47,7 +47,7 @@ func validateLabelSet(labelSet map[string][]string, data metrics.Metrics, invali ...@@ -47,7 +47,7 @@ func validateLabelSet(labelSet map[string][]string, data metrics.Metrics, invali
invalidLabel = false invalidLabel = false
} }
} }
if invalidLabel { if invalidLabel && invalidLabels != nil {
if _, ok := invalidLabels[metric]; !ok { if _, ok := invalidLabels[metric]; !ok {
invalidLabels[metric] = sets.NewString() invalidLabels[metric] = sets.NewString()
} }
...@@ -59,13 +59,21 @@ func validateLabelSet(labelSet map[string][]string, data metrics.Metrics, invali ...@@ -59,13 +59,21 @@ func validateLabelSet(labelSet map[string][]string, data metrics.Metrics, invali
} }
} }
func checkNecessaryMetrics(response metrics.Metrics, necessaryMetrics map[string][]string) {
missingLabels := make(map[string]sets.String)
validateLabelSet(metrics.CommonMetrics, response, nil, missingLabels)
validateLabelSet(necessaryMetrics, response, nil, missingLabels)
Expect(missingLabels).To(BeEmpty())
}
func checkMetrics(response metrics.Metrics, assumedMetrics map[string][]string) { func checkMetrics(response metrics.Metrics, assumedMetrics map[string][]string) {
invalidLabels := make(map[string]sets.String) invalidLabels := make(map[string]sets.String)
unknownLabels := make(map[string]sets.String) missingLabels := make(map[string]sets.String)
validateLabelSet(metrics.CommonMetrics, response, invalidLabels, unknownLabels) validateLabelSet(metrics.CommonMetrics, response, invalidLabels, missingLabels)
validateLabelSet(assumedMetrics, response, invalidLabels, unknownLabels) validateLabelSet(assumedMetrics, response, invalidLabels, missingLabels)
Expect(unknownLabels).To(BeEmpty()) Expect(missingLabels).To(BeEmpty())
Expect(invalidLabels).To(BeEmpty()) Expect(invalidLabels).To(BeEmpty())
} }
...@@ -97,12 +105,9 @@ var _ = Describe("MetricsGrabber", func() { ...@@ -97,12 +105,9 @@ var _ = Describe("MetricsGrabber", func() {
By("Connecting proxying to Node through the API server") By("Connecting proxying to Node through the API server")
nodes := ListSchedulableNodesOrDie(c) nodes := ListSchedulableNodesOrDie(c)
Expect(nodes.Items).NotTo(BeEmpty()) Expect(nodes.Items).NotTo(BeEmpty())
unknownMetrics := sets.NewString() response, err := grabber.GrabFromKubelet(nodes.Items[0].Name)
response, err := grabber.GrabFromKubelet(nodes.Items[0].Name, unknownMetrics)
expectNoError(err) expectNoError(err)
Expect(unknownMetrics).To(BeEmpty()) checkNecessaryMetrics(metrics.Metrics(response), metrics.NecessaryKubeletMetrics)
checkMetrics(metrics.Metrics(response), metrics.KnownKubeletMetrics)
} }
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment