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 @@
"Rev": "0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d"
},
{
"ImportPath": "github.com/docker/go-units",
"Comment": "v0.1.0-21-g0bbddae",
"Rev": "0bbddae09c5a5419a8c6dcdd7ff90da3d450393b"
},
{
"ImportPath": "github.com/docker/spdystream",
"Rev": "c33989bcb56748d2473194d11f8ac3fc563688eb"
},
......@@ -574,93 +579,93 @@
},
{
"ImportPath": "github.com/google/cadvisor/api",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/cache/memory",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/collector",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/container",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/events",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/fs",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/http",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/manager",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/pages",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/storage",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/summary",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/utils",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/validate",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/cadvisor/version",
"Comment": "v0.20.1",
"Rev": "634965abc45557ed03c268bb193a00cfcbedbd32"
"Comment": "v0.20.4",
"Rev": "59488ce2c4197f501283739c6a4dd3169999f317"
},
{
"ImportPath": "github.com/google/gofuzz",
......@@ -759,6 +764,11 @@
"Rev": "3f504e8dabd5d562e997d19ce0200aa41973e1b2"
},
{
"ImportPath": "github.com/mistifyio/go-zfs",
"Comment": "v2.1.1-5-g1b4ae6f",
"Rev": "1b4ae6fb4e77b095934d4430860ff202060169f8"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
},
......
......@@ -29,6 +29,7 @@ github.com/daviddengcn/go-colortext | BSD?
github.com/dgrijalva/jwt-go | spdxMIT
github.com/docker/docker | Apache-2
github.com/docker/docker/pkg/symlink | spdxBSD3
github.com/docker/go-units | Apache-2
github.com/docker/spdystream | SeeFile
github.com/elazarl/go-bindata-assetfs | spdxBSD2
github.com/elazarl/goproxy | BSDWarr
......@@ -60,6 +61,7 @@ github.com/kr/pty | spdxMIT
github.com/matttproud/golang_protobuf_extensions | Apache-2
github.com/mesos/mesos-go | Apache-2
github.com/miekg/dns | spdxBSD3
github.com/mistifyio/go-zfs | Apache-2
github.com/mitchellh/mapstructure | MITname
github.com/mvdan/xurls | 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 (
"net/http"
"path"
"strconv"
"time"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2"
......@@ -32,6 +31,7 @@ const (
containersApi = "containers"
subcontainersApi = "subcontainers"
machineApi = "machine"
machineStatsApi = "machinestats"
dockerApi = "docker"
summaryApi = "summary"
statsApi = "stats"
......@@ -63,8 +63,9 @@ func getApiVersions() []ApiVersion {
v1_2 := newVersion1_2(v1_1)
v1_3 := newVersion1_3(v1_2)
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
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)
infos, err := m.GetRequestedContainersInfo(name, opt)
if err != nil {
return err
}
contStats := make(map[string][]v2.ContainerStats, 0)
for name, cont := range conts {
contStats[name] = convertStats(cont)
contStats := make(map[string][]v2.DeprecatedContainerStats, 0)
for name, cinfo := range infos {
contStats[name] = v2.DeprecatedStatsFromV1(cinfo)
}
return writeResult(contStats, w)
case customMetricsApi:
containerName := getContainerName(request)
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 {
return err
}
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)
contStats := convertStats(cont)
for _, contStat := range contStats {
if contStat.HasCustomMetrics {
for name, allLabels := range contStat.CustomMetrics {
metricLabels := make(map[string][]info.MetricValBasic, 0)
for _, metric := range allLabels {
if !metric.Timestamp.IsZero() {
metVal := info.MetricValBasic{
Timestamp: metric.Timestamp,
IntValue: metric.IntValue,
FloatValue: metric.FloatValue,
}
labels := metrics[name]
if labels != nil {
values := labels[metric.Label]
values = append(values, metVal)
labels[metric.Label] = values
metrics[name] = labels
} else {
metricLabels[metric.Label] = []info.MetricValBasic{metVal}
metrics[name] = metricLabels
}
for _, contStat := range cinfo.Stats {
if len(contStat.CustomMetrics) == 0 {
continue
}
for name, allLabels := range contStat.CustomMetrics {
metricLabels := make(map[string][]info.MetricValBasic, 0)
for _, metric := range allLabels {
if !metric.Timestamp.IsZero() {
metVal := info.MetricValBasic{
Timestamp: metric.Timestamp,
IntValue: metric.IntValue,
FloatValue: metric.FloatValue,
}
labels := metrics[name]
if labels != nil {
values := labels[metric.Label]
values = append(values, metVal)
labels[metric.Label] = values
metrics[name] = labels
} else {
metricLabels[metric.Label] = []info.MetricValBasic{metVal}
metrics[name] = metricLabels
}
}
}
......@@ -451,102 +452,54 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
}
}
func instCpuStats(last, cur *info.ContainerStats) (*v2.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
type version2_1 struct {
baseVersion *version2_0
}
func newVersion2_1(v *version2_0) *version2_1 {
return &version2_1{
baseVersion: v,
}
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 {
return nil, err
return 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])
switch requestType {
case machineStatsApi:
glog.V(4).Infof("Api - MachineStats(%v)", request)
cont, err := m.GetRequestedContainersInfo("/", opt)
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 &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
return err
}
if stat.HasDiskIo {
stat.DiskIo = val.DiskIo
return writeResult(v2.MachineStatsFromV1(cont["/"]), w)
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 {
stat.CustomMetrics = val.CustomMetrics
contStats := make(map[string][]*v2.ContainerStats, len(conts))
for name, cont := range conts {
contStats[name] = v2.ContainerStatsFromV1(&cont.Spec, cont.Stats)
}
// TODO(rjnagal): Handle load stats.
stats = append(stats, stat)
return writeResult(contStats, w)
default:
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
}
return stats
}
func getRequestOptions(r *http.Request) (v2.RequestOptions, error) {
......
......@@ -81,4 +81,8 @@ type ContainerHandler interface {
// Cleanup frees up any resources being held like fds or go routines, etc.
Cleanup()
// Start starts any necessary background goroutines - must be cleaned up in Cleanup().
// It is expected that most implementations will be a no-op.
Start()
}
......@@ -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 dockerEnvWhitelist = flag.String("docker_env_metadata_whitelist", "", "a comma-separated list of environment variable keys that needs to be collected for docker containers")
// TODO(vmarmol): Export run dir too for newer Dockers.
// Directory holding Docker container state information.
func DockerStateDir() string {
......@@ -92,6 +94,7 @@ const (
devicemapperStorageDriver storageDriver = "devicemapper"
aufsStorageDriver storageDriver = "aufs"
overlayStorageDriver storageDriver = "overlay"
zfsStorageDriver storageDriver = "zfs"
)
type dockerFactory struct {
......@@ -117,6 +120,9 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
if err != nil {
return
}
metadataEnvs := strings.Split(*dockerEnvWhitelist, ",")
handler, err = newDockerContainerHandler(
client,
name,
......@@ -125,6 +131,7 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
self.storageDriver,
&self.cgroupSubsystems,
inHostNamespace,
metadataEnvs,
)
return
}
......
......@@ -26,17 +26,19 @@ import (
type fsHandler interface {
start()
usage() uint64
usage() (uint64, uint64)
stop()
}
type realFsHandler struct {
sync.RWMutex
lastUpdate time.Time
usageBytes uint64
period time.Duration
storageDirs []string
fsInfo fs.FsInfo
lastUpdate time.Time
usageBytes uint64
baseUsageBytes uint64
period time.Duration
rootfs string
extraDir string
fsInfo fs.FsInfo
// Tells the container to stop.
stopChan chan struct{}
}
......@@ -45,14 +47,16 @@ const longDu = time.Second
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{
lastUpdate: time.Time{},
usageBytes: 0,
period: period,
storageDirs: storageDirs,
fsInfo: fsInfo,
stopChan: make(chan struct{}, 1),
lastUpdate: time.Time{},
usageBytes: 0,
baseUsageBytes: 0,
period: period,
rootfs: rootfs,
extraDir: extraDir,
fsInfo: fsInfo,
stopChan: make(chan struct{}, 1),
}
}
......@@ -61,23 +65,27 @@ func (fh *realFsHandler) needsUpdate() bool {
}
func (fh *realFsHandler) update() error {
var usage uint64
for _, dir := range fh.storageDirs {
// TODO(Vishh): Add support for external mounts.
dirUsage, err := fh.fsInfo.GetDirUsage(dir)
if err != nil {
return err
}
usage += dirUsage
// TODO(vishh): Add support for external mounts.
baseUsage, err := fh.fsInfo.GetDirUsage(fh.rootfs)
if err != nil {
return err
}
extraDirUsage, err := fh.fsInfo.GetDirUsage(fh.extraDir)
if err != nil {
return err
}
fh.Lock()
defer fh.Unlock()
fh.lastUpdate = time.Now()
fh.usageBytes = usage
fh.usageBytes = baseUsage + extraDirUsage
fh.baseUsageBytes = baseUsage
return nil
}
func (fh *realFsHandler) trackUsage() {
fh.update()
for {
select {
case <-fh.stopChan:
......@@ -89,7 +97,7 @@ func (fh *realFsHandler) trackUsage() {
}
duration := time.Since(start)
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() {
close(fh.stopChan)
}
func (fh *realFsHandler) usage() uint64 {
func (fh *realFsHandler) usage() (baseUsageBytes, totalUsageBytes uint64) {
fh.RLock()
defer fh.RUnlock()
return fh.usageBytes
return fh.baseUsageBytes, fh.usageBytes
}
......@@ -59,15 +59,16 @@ type dockerContainerHandler struct {
// Manager of this container's cgroups.
cgroupManager cgroups.Manager
storageDriver storageDriver
fsInfo fs.FsInfo
storageDirs []string
storageDriver storageDriver
fsInfo fs.FsInfo
rootfsStorageDir string
// Time at which this container was created.
creationTime time.Time
// Metadata labels associated with the container.
// Metadata associated with the container.
labels map[string]string
envs map[string]string
// The container PID used to switch namespaces as required
pid int
......@@ -93,6 +94,7 @@ func newDockerContainerHandler(
storageDriver storageDriver,
cgroupSubsystems *containerlibcontainer.CgroupSubsystems,
inHostNamespace bool,
metadataEnvs []string,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
......@@ -116,14 +118,13 @@ func newDockerContainerHandler(
id := ContainerNameToDockerId(name)
// 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 {
case aufsStorageDriver:
// Add writable layer for aufs.
storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToAufsDir, id))
rootfsStorageDir = path.Join(*dockerRootDir, pathToAufsDir, id)
case overlayStorageDriver:
storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToOverlayDir, id))
rootfsStorageDir = path.Join(*dockerRootDir, pathToOverlayDir, id)
}
handler := &dockerContainerHandler{
......@@ -136,13 +137,10 @@ func newDockerContainerHandler(
storageDriver: storageDriver,
fsInfo: fsInfo,
rootFs: rootFs,
storageDirs: storageDirs,
fsHandler: newFsHandler(time.Minute, storageDirs, fsInfo),
rootfsStorageDir: rootfsStorageDir,
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.
ctnr, err := client.InspectContainer(id)
if err != nil {
......@@ -157,9 +155,24 @@ func newDockerContainerHandler(
handler.image = ctnr.Config.Image
handler.networkMode = ctnr.HostConfig.NetworkMode
// split env vars to get metadata map.
for _, exposedEnv := range metadataEnvs {
for _, envVar := range ctnr.Config.Env {
splits := strings.SplitN(envVar, "=", 2)
if splits[0] == exposedEnv {
handler.envs[strings.ToLower(exposedEnv)] = splits[1]
}
}
}
return handler, nil
}
func (self *dockerContainerHandler) Start() {
// Start the filesystem handler.
self.fsHandler.start()
}
func (self *dockerContainerHandler) Cleanup() {
self.fsHandler.stop()
}
......@@ -239,13 +252,14 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
spec.CreationTime = self.creationTime
switch self.storageDriver {
case aufsStorageDriver, overlayStorageDriver:
case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
spec.HasFilesystem = true
default:
spec.HasFilesystem = false
}
spec.Labels = self.labels
spec.Envs = self.envs
spec.Image = self.image
spec.HasNetwork = hasNet(self.networkMode)
......@@ -254,14 +268,12 @@ func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
switch self.storageDriver {
case aufsStorageDriver, overlayStorageDriver:
case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver:
default:
return nil
}
// As of now we assume that all the storage dirs are on the same device.
// The first storage dir will be that of the image layers.
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir)
if err != nil {
return err
}
......@@ -281,7 +293,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error
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)
return nil
......
......@@ -378,6 +378,8 @@ func toContainerStats1(s *cgroups.Stats, ret *info.ContainerStats) {
func toContainerStats2(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
......
......@@ -51,6 +51,8 @@ func (self *MockContainerHandler) ContainerReference() (info.ContainerReference,
return args.Get(0).(info.ContainerReference), args.Error(1)
}
func (self *MockContainerHandler) Start() {}
func (self *MockContainerHandler) Cleanup() {}
func (self *MockContainerHandler) GetSpec() (info.ContainerSpec, error) {
......
......@@ -166,6 +166,9 @@ func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error)
return nd, nil
}
// Nothing to start up.
func (self *rawContainerHandler) Start() {}
// Nothing to clean up.
func (self *rawContainerHandler) Cleanup() {}
......
......@@ -32,6 +32,7 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/golang/glog"
zfs "github.com/mistifyio/go-zfs"
)
const (
......@@ -73,8 +74,10 @@ func NewFsInfo(context Context) (FsInfo, error) {
// all ext systems are checked through prefix.
"btrfs": true,
"xfs": true,
"zfs": true,
}
for _, mount := range mounts {
var Fstype string
if !strings.HasPrefix(mount.Fstype, "ext") && !supportedFsType[mount.Fstype] {
continue
}
......@@ -82,7 +85,11 @@ func NewFsInfo(context Context) (FsInfo, error) {
if _, ok := partitions[mount.Source]; ok {
continue
}
if mount.Fstype == "zfs" {
Fstype = mount.Fstype
}
partitions[mount.Source] = partition{
fsType: Fstype,
mountpoint: mount.Mountpoint,
major: uint(mount.Major),
minor: uint(mount.Minor),
......@@ -128,7 +135,7 @@ func getDockerImagePaths(context Context) []string {
// TODO(rjnagal): Detect docker root and graphdriver directories from docker info.
dockerRoot := context.DockerRoot
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))
}
for dockerRoot != "/" && dockerRoot != "." {
......@@ -201,6 +208,8 @@ func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, er
switch partition.fsType {
case "devicemapper":
total, free, avail, err = getDMStats(device, partition.blockSize)
case "zfs":
total, free, avail, err = getZfstats(device)
default:
total, free, avail, err = getVfsStats(partition.mountpoint)
}
......@@ -423,3 +432,15 @@ func parseDMStatus(dmStatus string) (uint64, uint64, error) {
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 {
// Metadata labels associated with this container.
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"`
Cpu CpuSpec `json:"cpu,omitempty"`
......@@ -306,6 +308,15 @@ type MemoryStats struct {
// Units: Bytes.
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,
// dirty memory, and kernel memory. Working set is <= "usage".
// Units: Bytes.
......@@ -387,6 +398,10 @@ type FsStats struct {
// Number of bytes that is consumed by the container on this filesystem.
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.
Available uint64 `json:"available"`
......
......@@ -44,6 +44,8 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
stats.Cpu.Usage.System = 0
stats.Memory.Usage = uint64(rand.Int63n(4096))
stats.Memory.Cache = uint64(rand.Int63n(4096))
stats.Memory.RSS = uint64(rand.Int63n(4096))
ret[i] = stats
}
return ret
......
......@@ -52,6 +52,14 @@ type MemorySpec struct {
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 {
// Time at which the container was created.
CreationTime time.Time `json:"creation_time,omitempty"`
......@@ -66,6 +74,8 @@ type ContainerSpec struct {
// Metadata labels associated with this container.
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"`
Cpu CpuSpec `json:"cpu,omitempty"`
......@@ -85,7 +95,7 @@ type ContainerSpec struct {
Image string `json:"image,omitempty"`
}
type ContainerStats struct {
type DeprecatedContainerStats struct {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
// CPU statistics
......@@ -114,6 +124,28 @@ type ContainerStats struct {
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 {
// Indicates whether the stats are present or not.
// If true, values below do not have any data.
......@@ -252,3 +284,11 @@ type CpuInstUsage struct {
// Unit: nanocores per second
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
import (
// TODO(rjnagal): Move structs from v1.
"time"
"github.com/google/cadvisor/info/v1"
)
......@@ -86,3 +88,97 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
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 (
"fmt"
"io/ioutil"
"math"
"math/rand"
"os/exec"
"path"
"regexp"
......@@ -36,14 +37,14 @@ import (
"github.com/google/cadvisor/summary"
"github.com/google/cadvisor/utils/cpuload"
"github.com/docker/docker/pkg/units"
units "github.com/docker/go-units"
"github.com/golang/glog"
)
// Housekeeping interval.
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 {
info.ContainerReference
......@@ -78,6 +79,17 @@ type containerData struct {
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 {
go c.housekeeping()
return nil
......@@ -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.
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.
longHousekeeping := 100 * time.Millisecond
if *HousekeepingInterval/2 < longHousekeeping {
......
......@@ -62,6 +62,9 @@ type Manager interface {
// Get information about a container.
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).
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
......@@ -375,33 +378,8 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
// Get V2 container spec from v1 container info.
func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
specV1 := self.getAdjustedSpec(cinfo)
specV2 := v2.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,
}
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
spec := self.getAdjustedSpec(cinfo)
return v2.ContainerSpecFromV1(&spec, cinfo.Aliases, cinfo.Namespace)
}
func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
......@@ -417,7 +395,6 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
return spec
}
// Get a container by name.
func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
cont, err := self.getContainerData(containerName)
if err != nil {
......@@ -426,6 +403,34 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe
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) {
// Get the info from the container.
cinfo, err := cont.GetInfo()
......@@ -741,6 +746,18 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c
// Create a container.
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)
if err != nil {
return err
......@@ -770,35 +787,15 @@ func (m *manager) createContainer(containerName string) error {
return err
}
// Add to the containers map.
alreadyExists := func() bool {
m.containersLock.Lock()
defer m.containersLock.Unlock()
namespacedName := namespacedContainerName{
Name: containerName,
}
// 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
// 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
}
glog.V(3).Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
contSpec, err := cont.handler.GetSpec()
......@@ -822,9 +819,7 @@ func (m *manager) createContainer(containerName string) error {
}
// Start the container's housekeeping.
cont.Start()
return nil
return cont.Start()
}
func (m *manager) destroyContainer(containerName string) error {
......
......@@ -16,6 +16,7 @@ package metrics
import (
"fmt"
"regexp"
"time"
info "github.com/google/cadvisor/info/v1"
......@@ -127,6 +128,20 @@ func NewPrometheusCollector(infoProvider infoProvider, f ContainerNameToLabelsFu
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",
help: "Number of memory usage hits limits",
valueType: prometheus.CounterValue,
......@@ -508,11 +523,20 @@ func (c *PrometheusCollector) collectContainersInfo(ch chan<- prometheus.Metric)
if c.containerNameToLabels != nil {
newLabels := c.containerNameToLabels(name)
for k, v := range newLabels {
baseLabels = append(baseLabels, k)
baseLabels = append(baseLabels, sanitizeLabelName(k))
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
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...)
......@@ -571,3 +595,11 @@ func specMemoryValue(v uint64) float64 {
}
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
Do()
if err != nil {
// 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
......@@ -135,14 +136,15 @@ func newStorage(
)
if err != nil {
// 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
info, code, err := client.Ping().URL(elasticHost).Do()
if err != nil {
// 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)
......
0.20.1
\ No newline at end of file
0.20.4
\ 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 (
"github.com/prometheus/common/model"
)
var KnownKubeletMetrics = map[string][]string{
var NecessaryKubeletMetrics = map[string][]string{
"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_usage_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "cpu"},
......@@ -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_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_memory_cache": {},
"container_memory_rss": {},
"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_usage_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"},
......@@ -116,15 +118,15 @@ func (m *KubeletMetrics) Equal(o KubeletMetrics) bool {
func NewKubeletMetrics() KubeletMetrics {
result := NewMetrics()
for metric := range KnownKubeletMetrics {
for metric := range NecessaryKubeletMetrics {
result[metric] = make(model.Samples, 0)
}
return KubeletMetrics(result)
}
func parseKubeletMetrics(data string, unknownMetrics sets.String) (KubeletMetrics, error) {
func parseKubeletMetrics(data string) (KubeletMetrics, error) {
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 result, nil
......
......@@ -85,7 +85,7 @@ func NewMetricsGrabber(c *client.Client, kubelets bool, scheduler bool, controll
}, 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()})
if err != nil {
return KubeletMetrics{}, err
......@@ -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)
}
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 {
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
if err != nil {
return KubeletMetrics{}, err
}
return parseKubeletMetrics(output, unknownMetrics)
return parseKubeletMetrics(output)
}
func (g *MetricsGrabber) GrabFromScheduler(unknownMetrics sets.String) (SchedulerMetrics, error) {
......@@ -173,7 +173,7 @@ func (g *MetricsGrabber) Grab(unknownMetrics sets.String) (MetricsCollection, er
} else {
for _, node := range nodes.Items {
kubeletPort := node.Status.DaemonEndpoints.KubeletEndpoint.Port
metrics, err := g.grabFromKubeletInternal(node.Name, kubeletPort, nil)
metrics, err := g.grabFromKubeletInternal(node.Name, kubeletPort)
if err != nil {
errs = append(errs, err)
}
......
......@@ -47,7 +47,7 @@ func validateLabelSet(labelSet map[string][]string, data metrics.Metrics, invali
invalidLabel = false
}
}
if invalidLabel {
if invalidLabel && invalidLabels != nil {
if _, ok := invalidLabels[metric]; !ok {
invalidLabels[metric] = sets.NewString()
}
......@@ -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) {
invalidLabels := make(map[string]sets.String)
unknownLabels := make(map[string]sets.String)
validateLabelSet(metrics.CommonMetrics, response, invalidLabels, unknownLabels)
validateLabelSet(assumedMetrics, response, invalidLabels, unknownLabels)
missingLabels := make(map[string]sets.String)
validateLabelSet(metrics.CommonMetrics, response, invalidLabels, missingLabels)
validateLabelSet(assumedMetrics, response, invalidLabels, missingLabels)
Expect(unknownLabels).To(BeEmpty())
Expect(missingLabels).To(BeEmpty())
Expect(invalidLabels).To(BeEmpty())
}
......@@ -97,12 +105,9 @@ var _ = Describe("MetricsGrabber", func() {
By("Connecting proxying to Node through the API server")
nodes := ListSchedulableNodesOrDie(c)
Expect(nodes.Items).NotTo(BeEmpty())
unknownMetrics := sets.NewString()
response, err := grabber.GrabFromKubelet(nodes.Items[0].Name, unknownMetrics)
response, err := grabber.GrabFromKubelet(nodes.Items[0].Name)
expectNoError(err)
Expect(unknownMetrics).To(BeEmpty())
checkMetrics(metrics.Metrics(response), metrics.KnownKubeletMetrics)
checkNecessaryMetrics(metrics.Metrics(response), metrics.NecessaryKubeletMetrics)
}
})
......
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