Merge branch 'master' into patch-2

This commit is contained in:
David Ashpole 2017-04-20 09:20:13 -07:00 committed by GitHub
commit 69e0ad3806
21 changed files with 281 additions and 25 deletions

4
.gitignore vendored
View File

@ -4,3 +4,7 @@ cadvisor
# Go test binaries
*.test
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/
*.iml

View File

@ -33,6 +33,7 @@ import (
"github.com/google/cadvisor/version"
"crypto/tls"
"github.com/golang/glog"
)
@ -60,13 +61,17 @@ var collectorKey = flag.String("collector_key", "", "Key for the collector's cer
var (
// Metrics to be ignored.
// Tcp metrics are ignored by default.
ignoreMetrics metricSetValue = metricSetValue{container.MetricSet{container.NetworkTcpUsageMetrics: struct{}{}}}
ignoreMetrics metricSetValue = metricSetValue{container.MetricSet{
container.NetworkTcpUsageMetrics: struct{}{},
container.NetworkUdpUsageMetrics: struct{}{},
}}
// List of metrics that can be ignored.
ignoreWhitelist = container.MetricSet{
container.DiskUsageMetrics: struct{}{},
container.NetworkUsageMetrics: struct{}{},
container.NetworkTcpUsageMetrics: struct{}{},
container.NetworkUdpUsageMetrics: struct{}{},
}
)
@ -98,7 +103,7 @@ func (ml *metricSetValue) Set(value string) error {
}
func init() {
flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'network', 'tcp'. Note: tcp is disabled by default due to high CPU usage.")
flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'network', 'tcp', 'udp'. Note: tcp and udp are disabled by default due to high CPU usage.")
}
func main() {

View File

@ -28,6 +28,12 @@ func TestTcpMetricsAreDisabledByDefault(t *testing.T) {
assert.True(t, ignoreMetrics.Has(container.NetworkTcpUsageMetrics))
}
func TestUdpMetricsAreDisabledByDefault(t *testing.T) {
assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics))
flag.Parse()
assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics))
}
func TestIgnoreMetrics(t *testing.T) {
tests := []struct {
value string

View File

@ -31,21 +31,35 @@ import (
"github.com/google/cadvisor/info/v1"
"github.com/golang/glog"
"time"
)
// Client represents the base URL for a cAdvisor client.
type Client struct {
baseUrl string
baseUrl string
httpClient *http.Client
}
// NewClient returns a new v1.3 client with the specified base URL.
func NewClient(url string) (*Client, error) {
return newClient(url, http.DefaultClient)
}
// NewClientWithTimeout returns a new v1.3 client with the specified base URL and http client timeout.
func NewClientWithTimeout(url string, timeout time.Duration) (*Client, error) {
return newClient(url, &http.Client{
Timeout: timeout,
})
}
func newClient(url string, client *http.Client) (*Client, error) {
if !strings.HasSuffix(url, "/") {
url += "/"
}
return &Client{
baseUrl: fmt.Sprintf("%sapi/v1.3/", url),
baseUrl: fmt.Sprintf("%sapi/v1.3/", url),
httpClient: client,
}, nil
}
@ -168,9 +182,9 @@ func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName st
if marshalErr != nil {
return fmt.Errorf("unable to marshal data: %v", marshalErr)
}
resp, err = http.Post(url, "application/json", bytes.NewBuffer(data))
resp, err = self.httpClient.Post(url, "application/json", bytes.NewBuffer(data))
} else {
resp, err = http.Get(url)
resp, err = self.httpClient.Get(url)
}
if err != nil {
return fmt.Errorf("unable to get %q from %q: %v", infoName, url, err)
@ -199,7 +213,7 @@ func (self *Client) getEventStreamingData(url string, einfo chan *v1.Event) erro
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
resp, err := self.httpClient.Do(req)
if err != nil {
return err
}

View File

@ -17,6 +17,7 @@ package docker
import (
"fmt"
"regexp"
"strconv"
"strings"
@ -39,6 +40,7 @@ func Status() (v1.DockerStatus, error) {
out := v1.DockerStatus{}
out.Version = VersionString()
out.APIVersion = APIVersionString()
out.KernelVersion = machine.KernelVersion()
out.OS = dockerInfo.OperatingSystem
out.Hostname = dockerInfo.Name
@ -105,7 +107,7 @@ func ValidateInfo() (*dockertypes.Info, error) {
}
dockerInfo.ServerVersion = version.Version
}
version, err := parseDockerVersion(dockerInfo.ServerVersion)
version, err := parseVersion(dockerInfo.ServerVersion, version_re, 3)
if err != nil {
return nil, err
}
@ -129,7 +131,11 @@ func ValidateInfo() (*dockertypes.Info, error) {
}
func Version() ([]int, error) {
return parseDockerVersion(VersionString())
return parseVersion(VersionString(), version_re, 3)
}
func APIVersion() ([]int, error) {
return parseVersion(APIVersionString(), apiversion_re, 2)
}
func VersionString() string {
@ -144,18 +150,29 @@ func VersionString() string {
return docker_version
}
// TODO: switch to a semantic versioning library.
func parseDockerVersion(full_version_string string) ([]int, error) {
matches := version_re.FindAllStringSubmatch(full_version_string, -1)
func APIVersionString() string {
docker_api_version := "Unknown"
client, err := Client()
if err == nil {
version, err := client.ServerVersion(context.Background())
if err == nil {
docker_api_version = version.APIVersion
}
}
return docker_api_version
}
func parseVersion(version_string string, regex *regexp.Regexp, length int) ([]int, error) {
matches := regex.FindAllStringSubmatch(version_string, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string)
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", version_string, regex.String())
}
version_string_array := matches[0][1:]
version_array := make([]int, 3)
for index, version_string := range version_string_array {
version, err := strconv.Atoi(version_string)
version_array := make([]int, length)
for index, version_str := range version_string_array {
version, err := strconv.Atoi(version_str)
if err != nil {
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_string, full_version_string)
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_str, version_string)
}
version_array[index] = version
}

View File

@ -0,0 +1,51 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package docker
import (
"reflect"
"regexp"
"testing"
)
func TestParseDockerAPIVersion(t *testing.T) {
tests := []struct {
version string
regex *regexp.Regexp
length int
expected []int
expectedError string
}{
{"17.03.0", version_re, 3, []int{17, 03, 0}, ""},
{"17.a3.0", version_re, 3, []int{}, `version string "17.a3.0" doesn't match expected regular expression: "(\d+)\.(\d+)\.(\d+)"`},
{"1.20", apiversion_re, 2, []int{1, 20}, ""},
{"1.a", apiversion_re, 2, []int{}, `version string "1.a" doesn't match expected regular expression: "(\d+)\.(\d+)"`},
}
for _, test := range tests {
actual, err := parseVersion(test.version, test.regex, test.length)
if err != nil {
if len(test.expectedError) == 0 {
t.Errorf("%s: expected no error, got %v", test.version, err)
} else if err.Error() != test.expectedError {
t.Errorf("%s: expected error %v, got %v", test.version, test.expectedError, err)
}
} else {
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("%s: expected array %v, got %v", test.version, test.expected, actual)
}
}
}
}

View File

@ -103,6 +103,8 @@ type dockerFactory struct {
dockerVersion []int
dockerAPIVersion []int
ignoreMetrics container.MetricSet
thinPoolWatcher *devicemapper.ThinPoolWatcher
@ -185,8 +187,10 @@ func (self *dockerFactory) DebugInfo() map[string][]string {
}
var (
version_regexp_string = `(\d+)\.(\d+)\.(\d+)`
version_re = regexp.MustCompile(version_regexp_string)
version_regexp_string = `(\d+)\.(\d+)\.(\d+)`
version_re = regexp.MustCompile(version_regexp_string)
apiversion_regexp_string = `(\d+)\.(\d+)`
apiversion_re = regexp.MustCompile(apiversion_regexp_string)
)
func startThinPoolWatcher(dockerInfo *dockertypes.Info) (*devicemapper.ThinPoolWatcher, error) {
@ -310,7 +314,9 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c
}
// Version already validated above, assume no error here.
dockerVersion, _ := parseDockerVersion(dockerInfo.ServerVersion)
dockerVersion, _ := parseVersion(dockerInfo.ServerVersion, version_re, 3)
dockerAPIVersion, _ := APIVersion()
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
@ -338,6 +344,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c
cgroupSubsystems: cgroupSubsystems,
client: client,
dockerVersion: dockerVersion,
dockerAPIVersion: dockerAPIVersion,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(dockerInfo.Driver),

View File

@ -48,6 +48,7 @@ const (
DiskUsageMetrics MetricKind = "disk"
NetworkUsageMetrics MetricKind = "network"
NetworkTcpUsageMetrics MetricKind = "tcp"
NetworkUdpUsageMetrics MetricKind = "udp"
AppMetrics MetricKind = "app"
)

View File

@ -17,6 +17,7 @@ package libcontainer
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path"
@ -118,6 +119,21 @@ func GetStats(cgroupManager cgroups.Manager, rootFs string, pid int, ignoreMetri
stats.Network.Tcp6 = t6
}
}
if !ignoreMetrics.Has(container.NetworkUdpUsageMetrics) {
u, err := udpStatsFromProc(rootFs, pid, "net/udp")
if err != nil {
glog.V(2).Infof("Unable to get udp stats from pid %d: %v", pid, err)
} else {
stats.Network.Udp = u
}
u6, err := udpStatsFromProc(rootFs, pid, "net/udp6")
if err != nil {
glog.V(2).Infof("Unable to get udp6 stats from pid %d: %v", pid, err)
} else {
stats.Network.Udp6 = u6
}
}
// For backwards compatibility.
if len(stats.Network.Interfaces) > 0 {
@ -291,6 +307,74 @@ func scanTcpStats(tcpStatsFile string) (info.TcpStat, error) {
return stats, nil
}
func udpStatsFromProc(rootFs string, pid int, file string) (info.UdpStat, error) {
var err error
var udpStats info.UdpStat
udpStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), file)
r, err := os.Open(udpStatsFile)
if err != nil {
return udpStats, fmt.Errorf("failure opening %s: %v", udpStatsFile, err)
}
udpStats, err = scanUdpStats(r)
if err != nil {
return udpStats, fmt.Errorf("couldn't read udp stats: %v", err)
}
return udpStats, nil
}
func scanUdpStats(r io.Reader) (info.UdpStat, error) {
var stats info.UdpStat
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
// Discard header line
if b := scanner.Scan(); !b {
return stats, scanner.Err()
}
listening := uint64(0)
dropped := uint64(0)
rxQueued := uint64(0)
txQueued := uint64(0)
for scanner.Scan() {
line := scanner.Text()
// Format: sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
listening++
fs := strings.Fields(line)
if len(fs) != 13 {
continue
}
rx, tx := uint64(0), uint64(0)
fmt.Sscanf(fs[4], "%X:%X", &rx, &tx)
rxQueued += rx
txQueued += tx
d, err := strconv.Atoi(string(fs[12]))
if err != nil {
continue
}
dropped += uint64(d)
}
stats = info.UdpStat{
Listen: listening,
Dropped: dropped,
RxQueued: rxQueued,
TxQueued: txQueued,
}
return stats, nil
}
func GetProcesses(cgroupManager cgroups.Manager) ([]int, error) {
pids, err := cgroupManager.GetPids()
if err != nil {

View File

@ -15,6 +15,7 @@
package libcontainer
import (
"os"
"testing"
info "github.com/google/cadvisor/info/v1"
@ -61,3 +62,27 @@ func TestScanInterfaceStats(t *testing.T) {
}
}
}
func TestScanUDPStats(t *testing.T) {
udpStatsFile := "testdata/procnetudp"
r, err := os.Open(udpStatsFile)
if err != nil {
t.Errorf("failure opening %s: %v", udpStatsFile, err)
}
stats, err := scanUdpStats(r)
if err != nil {
t.Error(err)
}
var udpstats = info.UdpStat{
Listen: 2,
Dropped: 4,
RxQueued: 10,
TxQueued: 11,
}
if stats != udpstats {
t.Errorf("Expected %#v, got %#v", udpstats, stats)
}
}

View File

@ -0,0 +1,3 @@
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
1: 00000000:07D3 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 16583 2 ffff8800b4549400 0
21: 00000000:A841 00000000:0000 07 0000000A:0000000B 00:00000000 00000000 1000 0 114299623 2 ffff880338477800 4

View File

@ -386,6 +386,10 @@ type NetworkStats struct {
Tcp TcpStat `json:"tcp"`
// TCP6 connection stats (Established, Listen...)
Tcp6 TcpStat `json:"tcp6"`
// UDP connection stats
Udp UdpStat `json:"udp"`
// UDP6 connection stats
Udp6 UdpStat `json:"udp6"`
}
type TcpStat struct {
@ -413,6 +417,20 @@ type TcpStat struct {
Closing uint64
}
type UdpStat struct {
// Count of UDP sockets in state "Listen"
Listen uint64
// Count of UDP packets dropped by the IP stack
Dropped uint64
// Count of packets Queued for Receieve
RxQueued uint64
// Count of packets Queued for Transmit
TxQueued uint64
}
type FsStats struct {
// The block device name associated with the filesystem.
Device string `json:"device,omitempty"`

View File

@ -17,6 +17,7 @@ package v1
type DockerStatus struct {
Version string `json:"version"`
APIVersion string `json:"api_version"`
KernelVersion string `json:"kernel_version"`
OS string `json:"os"`
Hostname string `json:"hostname"`

View File

@ -196,6 +196,9 @@ type VersionInfo struct {
// Docker version.
DockerVersion string `json:"docker_version"`
// Docker API Version
DockerAPIVersion string `json:"docker_api_version"`
// cAdvisor version.
CadvisorVersion string `json:"cadvisor_version"`
// cAdvisor git revision.

View File

@ -269,6 +269,10 @@ type NetworkStats struct {
Tcp TcpStat `json:"tcp"`
// TCP6 connection stats (Established, Listen...)
Tcp6 TcpStat `json:"tcp6"`
// UDP connection stats
Udp v1.UdpStat `json:"udp"`
// UDP6 connection stats
Udp6 v1.UdpStat `json:"udp6"`
}
// Instantaneous CPU stats

View File

@ -133,7 +133,7 @@ func ContainerStatsFromV1(containerName string, spec *v1.ContainerSpec, stats []
}
} else if len(val.Filesystem) > 1 && containerName != "/" {
// Cannot handle multiple devices per container.
glog.V(2).Infof("failed to handle multiple devices for container %s. Skipping Filesystem stats", containerName)
glog.V(4).Infof("failed to handle multiple devices for container %s. Skipping Filesystem stats", containerName)
}
}
if spec.HasDiskIo {
@ -259,6 +259,7 @@ func ContainerSpecFromV1(specV1 *v1.ContainerSpec, aliases []string, namespace s
HasCustomMetrics: specV1.HasCustomMetrics,
Image: specV1.Image,
Labels: specV1.Labels,
Envs: specV1.Envs,
}
if specV1.HasCpu {
specV2.Cpu.Limit = specV1.Cpu.Limit

View File

@ -26,12 +26,14 @@ import (
var (
timestamp = time.Date(1987, time.August, 10, 0, 0, 0, 0, time.UTC)
labels = map[string]string{"foo": "bar"}
envs = map[string]string{"foo": "bar"}
)
func TestContanierSpecFromV1(t *testing.T) {
v1Spec := v1.ContainerSpec{
CreationTime: timestamp,
Labels: labels,
Envs: envs,
HasCpu: true,
Cpu: v1.CpuSpec{
Limit: 2048,
@ -63,6 +65,7 @@ func TestContanierSpecFromV1(t *testing.T) {
expectedV2Spec := ContainerSpec{
CreationTime: timestamp,
Labels: labels,
Envs: envs,
HasCpu: true,
Cpu: CpuSpec{
Limit: 2048,

View File

@ -31,6 +31,9 @@ type Attributes struct {
// Docker version.
DockerVersion string `json:"docker_version"`
// Docker API version.
DockerAPIVersion string `json:"docker_api_version"`
// cAdvisor version.
CadvisorVersion string `json:"cadvisor_version"`
@ -74,6 +77,7 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
KernelVersion: vi.KernelVersion,
ContainerOsVersion: vi.ContainerOsVersion,
DockerVersion: vi.DockerVersion,
DockerAPIVersion: vi.DockerAPIVersion,
CadvisorVersion: vi.CadvisorVersion,
NumCores: mi.NumCores,
CpuFrequency: mi.CpuFrequency,

View File

@ -1255,11 +1255,13 @@ func getVersionInfo() (*info.VersionInfo, error) {
kernel_version := machine.KernelVersion()
container_os := machine.ContainerOsVersion()
docker_version := docker.VersionString()
docker_api_version := docker.APIVersionString()
return &info.VersionInfo{
KernelVersion: kernel_version,
ContainerOsVersion: container_os,
DockerVersion: docker_version,
DockerAPIVersion: docker_api_version,
CadvisorVersion: version.Info["version"],
CadvisorRevision: version.Info["revision"],
}, nil

View File

@ -130,10 +130,12 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc) *PrometheusCo
getValues: func(s *info.ContainerStats) metricValues {
values := make(metricValues, 0, len(s.Cpu.Usage.PerCpu))
for i, value := range s.Cpu.Usage.PerCpu {
values = append(values, metricValue{
value: float64(value) / float64(time.Second),
labels: []string{fmt.Sprintf("cpu%02d", i)},
})
if value > 0 {
values = append(values, metricValue{
value: float64(value) / float64(time.Second),
labels: []string{fmt.Sprintf("cpu%02d", i)},
})
}
}
return values
},

View File

@ -40,6 +40,7 @@ func toStatusKV(status info.DockerStatus) ([]keyVal, []keyVal) {
}
return []keyVal{
{Key: "Docker Version", Value: status.Version},
{Key: "Docker API Version", Value: status.APIVersion},
{Key: "Kernel Version", Value: status.KernelVersion},
{Key: "OS Version", Value: status.OS},
{Key: "Host Name", Value: status.Hostname},