mirror of https://github.com/k3s-io/k3s
Updated cadvisor dependency
Also added a new dep: gcloud-golang/compute/metadata.pull/6/head
parent
769230e735
commit
611b5c8bf5
|
@ -34,6 +34,10 @@
|
|||
"Comment": "release-96",
|
||||
"Rev": "98c78185197025f935947caac56a7b6d022f89d2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata",
|
||||
"Rev": "e34a32f9b0ecbc0784865fb2d47f3818c09521d4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.6.2-10-g51fe59a",
|
||||
|
@ -246,93 +250,93 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/api",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/cache/memory",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/collector",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/container",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/events",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/fs",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/healthz",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/http",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/info/v1",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/info/v2",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/manager",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/metrics",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/pages",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/storage",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/summary",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/utils",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/validate",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/cadvisor/version",
|
||||
"Comment": "0.15.1",
|
||||
"Rev": "ec588def40e1bb59f28f5a293b279f6762d13d44"
|
||||
"Comment": "0.16.0-51-g78419de",
|
||||
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/google/go-github/github",
|
||||
|
@ -425,10 +429,10 @@
|
|||
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mxk/go-flowrate/flowrate",
|
||||
"Rev": "cca7078d478f8520f85629ad7c68962d31ed7682"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mxk/go-flowrate/flowrate",
|
||||
"Rev": "cca7078d478f8520f85629ad7c68962d31ed7682"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo",
|
||||
"Comment": "v1.2.0-6-gd981d36",
|
||||
|
|
279
Godeps/_workspace/src/github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata/metadata.go
generated
vendored
Normal file
279
Godeps/_workspace/src/github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata/metadata.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/cloud/internal"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var metaClient = &http.Client{
|
||||
Transport: &internal.Transport{
|
||||
Base: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 750 * time.Millisecond,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 750 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func Get(suffix string) (string, error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv("GCE_METADATA_HOST")
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = "169.254.169.254"
|
||||
}
|
||||
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
res, err := metaClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(all), nil
|
||||
}
|
||||
|
||||
func getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cachedValue) get() (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var onGCE struct {
|
||||
sync.Mutex
|
||||
set bool
|
||||
v bool
|
||||
}
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
defer onGCE.Unlock()
|
||||
onGCE.Lock()
|
||||
if onGCE.set {
|
||||
return onGCE.v
|
||||
}
|
||||
onGCE.set = true
|
||||
|
||||
// We use the DNS name of the metadata service here instead of the IP address
|
||||
// because we expect that to fail faster in the not-on-GCE case.
|
||||
res, err := metaClient.Get("http://metadata.google.internal")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
onGCE.v = res.Header.Get("Metadata-Flavor") == "Google"
|
||||
return onGCE.v
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return projID.get() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return projNum.get() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) {
|
||||
return getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) {
|
||||
return instID.get()
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) {
|
||||
host, err := Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) {
|
||||
zone, err := getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||
|
||||
func lines(suffix string) ([]string, error) {
|
||||
j, err := Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
|
@ -39,6 +39,7 @@ const (
|
|||
attributesApi = "attributes"
|
||||
versionApi = "version"
|
||||
psApi = "ps"
|
||||
customMetricsApi = "appmetrics"
|
||||
)
|
||||
|
||||
// Interface for a cAdvisor API version
|
||||
|
@ -305,7 +306,7 @@ func (self *version2_0) Version() string {
|
|||
}
|
||||
|
||||
func (self *version2_0) SupportedRequestTypes() []string {
|
||||
return []string{versionApi, attributesApi, eventsApi, machineApi, summaryApi, statsApi, specApi, storageApi, psApi}
|
||||
return []string{versionApi, attributesApi, eventsApi, machineApi, summaryApi, statsApi, specApi, storageApi, psApi, customMetricsApi}
|
||||
}
|
||||
|
||||
func (self *version2_0) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
|
||||
|
@ -364,6 +365,32 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
|
|||
contStats[name] = convertStats(cont)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
specs, err := m.GetContainerSpec(containerName, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contMetrics := make(map[string]map[string][]info.MetricVal, 0)
|
||||
for _, cont := range conts {
|
||||
metrics := map[string][]info.MetricVal{}
|
||||
contStats := convertStats(cont)
|
||||
spec := specs[cont.Name]
|
||||
for _, contStat := range contStats {
|
||||
for _, ms := range spec.CustomMetrics {
|
||||
if contStat.HasCustomMetrics && !contStat.CustomMetrics[ms.Name].Timestamp.IsZero() {
|
||||
metrics[ms.Name] = append(metrics[ms.Name], contStat.CustomMetrics[ms.Name])
|
||||
}
|
||||
}
|
||||
}
|
||||
contMetrics[containerName] = metrics
|
||||
}
|
||||
return writeResult(contMetrics, w)
|
||||
case specApi:
|
||||
containerName := getContainerName(request)
|
||||
glog.V(4).Infof("Api - Spec for container %q, options %+v", containerName, opt)
|
||||
|
@ -412,12 +439,13 @@ func convertStats(cont *info.ContainerInfo) []v2.ContainerStats {
|
|||
stats := []v2.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,
|
||||
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
|
||||
|
@ -434,6 +462,9 @@ func convertStats(cont *info.ContainerInfo) []v2.ContainerStats {
|
|||
if stat.HasDiskIo {
|
||||
stat.DiskIo = val.DiskIo
|
||||
}
|
||||
if stat.HasCustomMetrics {
|
||||
stat.CustomMetrics = val.CustomMetrics
|
||||
}
|
||||
// TODO(rjnagal): Handle load stats.
|
||||
stats = append(stats, stat)
|
||||
}
|
||||
|
|
54
Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go
generated
vendored
54
Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go
generated
vendored
|
@ -19,14 +19,15 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info/v2"
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
)
|
||||
|
||||
type collectorManager struct {
|
||||
collectors []*collectorData
|
||||
}
|
||||
const metricLabelPrefix = "io.cadvisor.metric."
|
||||
|
||||
var _ CollectorManager = &collectorManager{}
|
||||
type GenericCollectorManager struct {
|
||||
Collectors []*collectorData
|
||||
NextCollectionTime time.Time
|
||||
}
|
||||
|
||||
type collectorData struct {
|
||||
collector Collector
|
||||
|
@ -35,33 +36,54 @@ type collectorData struct {
|
|||
|
||||
// Returns a new CollectorManager that is thread-compatible.
|
||||
func NewCollectorManager() (CollectorManager, error) {
|
||||
return &collectorManager{
|
||||
collectors: []*collectorData{},
|
||||
return &GenericCollectorManager{
|
||||
Collectors: []*collectorData{},
|
||||
NextCollectionTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cm *collectorManager) RegisterCollector(collector Collector) error {
|
||||
cm.collectors = append(cm.collectors, &collectorData{
|
||||
func GetCollectorConfigs(labels map[string]string) map[string]string {
|
||||
configs := map[string]string{}
|
||||
for k, v := range labels {
|
||||
if strings.HasPrefix(k, metricLabelPrefix) {
|
||||
name := strings.TrimPrefix(k, metricLabelPrefix)
|
||||
configs[name] = v
|
||||
}
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
func (cm *GenericCollectorManager) RegisterCollector(collector Collector) error {
|
||||
cm.Collectors = append(cm.Collectors, &collectorData{
|
||||
collector: collector,
|
||||
nextCollectionTime: time.Now(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *collectorManager) Collect() (time.Time, []v2.Metric, error) {
|
||||
func (cm *GenericCollectorManager) GetSpec() ([]v1.MetricSpec, error) {
|
||||
metricSpec := []v1.MetricSpec{}
|
||||
for _, c := range cm.Collectors {
|
||||
specs := c.collector.GetSpec()
|
||||
metricSpec = append(metricSpec, specs...)
|
||||
}
|
||||
|
||||
return metricSpec, nil
|
||||
}
|
||||
|
||||
func (cm *GenericCollectorManager) Collect() (time.Time, map[string]v1.MetricVal, error) {
|
||||
var errors []error
|
||||
|
||||
// Collect from all collectors that are ready.
|
||||
var next time.Time
|
||||
var metrics []v2.Metric
|
||||
for _, c := range cm.collectors {
|
||||
metrics := map[string]v1.MetricVal{}
|
||||
for _, c := range cm.Collectors {
|
||||
if c.nextCollectionTime.Before(time.Now()) {
|
||||
nextCollection, newMetrics, err := c.collector.Collect()
|
||||
var err error
|
||||
c.nextCollectionTime, metrics, err = c.collector.Collect(metrics)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
metrics = append(metrics, newMetrics...)
|
||||
c.nextCollectionTime = nextCollection
|
||||
}
|
||||
|
||||
// Keep track of the next collector that will be ready.
|
||||
|
@ -69,7 +91,7 @@ func (cm *collectorManager) Collect() (time.Time, []v2.Metric, error) {
|
|||
next = c.nextCollectionTime
|
||||
}
|
||||
}
|
||||
|
||||
cm.NextCollectionTime = next
|
||||
return next, metrics, compileErrors(errors)
|
||||
}
|
||||
|
||||
|
|
12
Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go
generated
vendored
12
Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go
generated
vendored
|
@ -18,7 +18,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info/v2"
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -28,17 +28,21 @@ type fakeCollector struct {
|
|||
collectedFrom int
|
||||
}
|
||||
|
||||
func (fc *fakeCollector) Collect() (time.Time, []v2.Metric, error) {
|
||||
func (fc *fakeCollector) Collect(metric map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, error) {
|
||||
fc.collectedFrom++
|
||||
return fc.nextCollectionTime, []v2.Metric{}, fc.err
|
||||
return fc.nextCollectionTime, metric, fc.err
|
||||
}
|
||||
|
||||
func (fc *fakeCollector) Name() string {
|
||||
return "fake-collector"
|
||||
}
|
||||
|
||||
func (fc *fakeCollector) GetSpec() []v1.MetricSpec {
|
||||
return []v1.MetricSpec{}
|
||||
}
|
||||
|
||||
func TestCollect(t *testing.T) {
|
||||
cm := &collectorManager{}
|
||||
cm := &GenericCollectorManager{}
|
||||
|
||||
firstTime := time.Now().Add(-time.Hour)
|
||||
secondTime := time.Now().Add(time.Hour)
|
||||
|
|
50
Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
//the endpoint to hit to scrape metrics
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
//holds information about different metrics that can be collected
|
||||
MetricsConfig []MetricConfig `json:"metrics_config"`
|
||||
}
|
||||
|
||||
// metricConfig holds information extracted from the config file about a metric
|
||||
type MetricConfig struct {
|
||||
//the name of the metric
|
||||
Name string `json:"name"`
|
||||
|
||||
//enum type for the metric type
|
||||
MetricType v1.MetricType `json:"metric_type"`
|
||||
|
||||
// metric units to display on UI and in storage (eg: MB, cores)
|
||||
// this is only used for display.
|
||||
Units string `json:"units"`
|
||||
|
||||
//data type of the metric (eg: int, float)
|
||||
DataType v1.DataType `json:"data_type"`
|
||||
|
||||
//the frequency at which the metric should be collected
|
||||
PollingFrequency time.Duration `json:"polling_frequency"`
|
||||
|
||||
//the regular expression that can be used to extract the metric
|
||||
Regex string `json:"regex"`
|
||||
}
|
34
Godeps/_workspace/src/github.com/google/cadvisor/collector/config/sample_config.json
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/google/cadvisor/collector/config/sample_config.json
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"endpoint" : "http://localhost:8000/nginx_status",
|
||||
"metrics_config" : [
|
||||
{ "name" : "activeConnections",
|
||||
"metric_type" : "gauge",
|
||||
"units" : "number of active connections",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : "Active connections: ([0-9]+)"
|
||||
},
|
||||
{ "name" : "reading",
|
||||
"metric_type" : "gauge",
|
||||
"units" : "number of reading connections",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : "Reading: ([0-9]+) .*"
|
||||
},
|
||||
{ "name" : "writing",
|
||||
"metric_type" : "gauge",
|
||||
"data_type" : "int",
|
||||
"units" : "number of writing connections",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : ".*Writing: ([0-9]+).*"
|
||||
},
|
||||
{ "name" : "waiting",
|
||||
"metric_type" : "gauge",
|
||||
"units" : "number of waiting connections",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : ".*Waiting: ([0-9]+)"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
|
@ -17,7 +17,7 @@ package collector
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info/v2"
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
)
|
||||
|
||||
type FakeCollectorManager struct {
|
||||
|
@ -27,7 +27,11 @@ func (fkm *FakeCollectorManager) RegisterCollector(collector Collector) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (fkm *FakeCollectorManager) Collect() (time.Time, []v2.Metric, error) {
|
||||
var zero time.Time
|
||||
return zero, []v2.Metric{}, nil
|
||||
func (fkm *FakeCollectorManager) GetSpec() ([]v1.MetricSpec, error) {
|
||||
return []v1.MetricSpec{}, nil
|
||||
}
|
||||
|
||||
func (fkm *FakeCollectorManager) Collect(metric map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, error) {
|
||||
var zero time.Time
|
||||
return zero, metric, nil
|
||||
}
|
||||
|
|
165
Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go
generated
vendored
Normal file
165
Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go
generated
vendored
Normal file
|
@ -0,0 +1,165 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
)
|
||||
|
||||
type GenericCollector struct {
|
||||
//name of the collector
|
||||
name string
|
||||
|
||||
//holds information extracted from the config file for a collector
|
||||
configFile Config
|
||||
|
||||
//holds information necessary to extract metrics
|
||||
info *collectorInfo
|
||||
}
|
||||
|
||||
type collectorInfo struct {
|
||||
//minimum polling frequency among all metrics
|
||||
minPollingFrequency time.Duration
|
||||
|
||||
//regular expresssions for all metrics
|
||||
regexps []*regexp.Regexp
|
||||
}
|
||||
|
||||
//Returns a new collector using the information extracted from the configfile
|
||||
func NewCollector(collectorName string, configFile []byte) (*GenericCollector, error) {
|
||||
var configInJSON Config
|
||||
err := json.Unmarshal(configFile, &configInJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO : Add checks for validity of config file (eg : Accurate JSON fields)
|
||||
|
||||
if len(configInJSON.MetricsConfig) == 0 {
|
||||
return nil, fmt.Errorf("No metrics provided in config")
|
||||
}
|
||||
|
||||
minPollFrequency := time.Duration(0)
|
||||
regexprs := make([]*regexp.Regexp, len(configInJSON.MetricsConfig))
|
||||
|
||||
for ind, metricConfig := range configInJSON.MetricsConfig {
|
||||
// Find the minimum specified polling frequency in metric config.
|
||||
if metricConfig.PollingFrequency != 0 {
|
||||
if minPollFrequency == 0 || metricConfig.PollingFrequency < minPollFrequency {
|
||||
minPollFrequency = metricConfig.PollingFrequency
|
||||
}
|
||||
}
|
||||
|
||||
regexprs[ind], err = regexp.Compile(metricConfig.Regex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid regexp %v for metric %v", metricConfig.Regex, metricConfig.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum supported polling frequency is 1s.
|
||||
minSupportedFrequency := 1 * time.Second
|
||||
if minPollFrequency < minSupportedFrequency {
|
||||
minPollFrequency = minSupportedFrequency
|
||||
}
|
||||
|
||||
return &GenericCollector{
|
||||
name: collectorName,
|
||||
configFile: configInJSON,
|
||||
info: &collectorInfo{
|
||||
minPollingFrequency: minPollFrequency,
|
||||
regexps: regexprs},
|
||||
}, nil
|
||||
}
|
||||
|
||||
//Returns name of the collector
|
||||
func (collector *GenericCollector) Name() string {
|
||||
return collector.name
|
||||
}
|
||||
|
||||
func (collector *GenericCollector) configToSpec(config MetricConfig) v1.MetricSpec {
|
||||
return v1.MetricSpec{
|
||||
Name: config.Name,
|
||||
Type: config.MetricType,
|
||||
Format: config.DataType,
|
||||
Units: config.Units,
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *GenericCollector) GetSpec() []v1.MetricSpec {
|
||||
specs := []v1.MetricSpec{}
|
||||
for _, metricConfig := range collector.configFile.MetricsConfig {
|
||||
spec := collector.configToSpec(metricConfig)
|
||||
specs = append(specs, spec)
|
||||
}
|
||||
return specs
|
||||
}
|
||||
|
||||
//Returns collected metrics and the next collection time of the collector
|
||||
func (collector *GenericCollector) Collect(metrics map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, error) {
|
||||
currentTime := time.Now()
|
||||
nextCollectionTime := currentTime.Add(time.Duration(collector.info.minPollingFrequency))
|
||||
|
||||
uri := collector.configFile.Endpoint
|
||||
response, err := http.Get(uri)
|
||||
if err != nil {
|
||||
return nextCollectionTime, nil, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
pageContent, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nextCollectionTime, nil, err
|
||||
}
|
||||
|
||||
var errorSlice []error
|
||||
for ind, metricConfig := range collector.configFile.MetricsConfig {
|
||||
matchString := collector.info.regexps[ind].FindStringSubmatch(string(pageContent))
|
||||
if matchString != nil {
|
||||
if metricConfig.DataType == v1.FloatType {
|
||||
regVal, err := strconv.ParseFloat(strings.TrimSpace(matchString[1]), 64)
|
||||
if err != nil {
|
||||
errorSlice = append(errorSlice, err)
|
||||
}
|
||||
metrics[metricConfig.Name] = v1.MetricVal{
|
||||
FloatValue: regVal, Timestamp: currentTime,
|
||||
}
|
||||
} else if metricConfig.DataType == v1.IntType {
|
||||
regVal, err := strconv.ParseInt(strings.TrimSpace(matchString[1]), 10, 64)
|
||||
if err != nil {
|
||||
errorSlice = append(errorSlice, err)
|
||||
}
|
||||
metrics[metricConfig.Name] = v1.MetricVal{
|
||||
IntValue: regVal, Timestamp: currentTime,
|
||||
}
|
||||
|
||||
} else {
|
||||
errorSlice = append(errorSlice, fmt.Errorf("Unexpected value of 'data_type' for metric '%v' in config ", metricConfig.Name))
|
||||
}
|
||||
} else {
|
||||
errorSlice = append(errorSlice, fmt.Errorf("No match found for regexp: %v for metric '%v' in config", metricConfig.Regex, metricConfig.Name))
|
||||
}
|
||||
}
|
||||
return nextCollectionTime, metrics, compileErrors(errorSlice)
|
||||
}
|
167
Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go
generated
vendored
Normal file
167
Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
// 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 collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEmptyConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
emptyConfig := `
|
||||
{
|
||||
"endpoint" : "http://localhost:8000/nginx_status",
|
||||
"metrics_config" : [
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
//Create a temporary config file 'temp.json' with invalid json format
|
||||
assert.NoError(ioutil.WriteFile("temp.json", []byte(emptyConfig), 0777))
|
||||
|
||||
configFile, err := ioutil.ReadFile("temp.json")
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = NewCollector("tempCollector", configFile)
|
||||
assert.Error(err)
|
||||
|
||||
assert.NoError(os.Remove("temp.json"))
|
||||
}
|
||||
|
||||
func TestConfigWithErrors(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
//Syntax error: Missed '"' after activeConnections
|
||||
invalid := `
|
||||
{
|
||||
"endpoint" : "http://localhost:8000/nginx_status",
|
||||
"metrics_config" : [
|
||||
{
|
||||
"name" : "activeConnections,
|
||||
"metric_type" : "gauge",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : "Active connections: ([0-9]+)"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
//Create a temporary config file 'temp.json' with invalid json format
|
||||
assert.NoError(ioutil.WriteFile("temp.json", []byte(invalid), 0777))
|
||||
configFile, err := ioutil.ReadFile("temp.json")
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = NewCollector("tempCollector", configFile)
|
||||
assert.Error(err)
|
||||
|
||||
assert.NoError(os.Remove("temp.json"))
|
||||
}
|
||||
|
||||
func TestConfigWithRegexErrors(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
//Error: Missed operand for '+' in activeConnections regex
|
||||
invalid := `
|
||||
{
|
||||
"endpoint" : "host:port/nginx_status",
|
||||
"metrics_config" : [
|
||||
{
|
||||
"name" : "activeConnections",
|
||||
"metric_type" : "gauge",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : "Active connections: (+)"
|
||||
},
|
||||
{
|
||||
"name" : "reading",
|
||||
"metric_type" : "gauge",
|
||||
"data_type" : "int",
|
||||
"polling_frequency" : 10,
|
||||
"regex" : "Reading: ([0-9]+) .*"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
//Create a temporary config file 'temp.json'
|
||||
assert.NoError(ioutil.WriteFile("temp.json", []byte(invalid), 0777))
|
||||
|
||||
configFile, err := ioutil.ReadFile("temp.json")
|
||||
assert.NoError(err)
|
||||
|
||||
_, err = NewCollector("tempCollector", configFile)
|
||||
assert.Error(err)
|
||||
|
||||
assert.NoError(os.Remove("temp.json"))
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
//Create an nginx collector using the config file 'sample_config.json'
|
||||
configFile, err := ioutil.ReadFile("config/sample_config.json")
|
||||
assert.NoError(err)
|
||||
|
||||
collector, err := NewCollector("nginx", configFile)
|
||||
assert.NoError(err)
|
||||
assert.Equal(collector.name, "nginx")
|
||||
assert.Equal(collector.configFile.Endpoint, "http://localhost:8000/nginx_status")
|
||||
assert.Equal(collector.configFile.MetricsConfig[0].Name, "activeConnections")
|
||||
}
|
||||
|
||||
func TestMetricCollection(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
//Collect nginx metrics from a fake nginx endpoint
|
||||
configFile, err := ioutil.ReadFile("config/sample_config.json")
|
||||
assert.NoError(err)
|
||||
|
||||
fakeCollector, err := NewCollector("nginx", configFile)
|
||||
assert.NoError(err)
|
||||
|
||||
tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "Active connections: 3\nserver accepts handled requests")
|
||||
fmt.Fprintln(w, "5 5 32\nReading: 0 Writing: 1 Waiting: 2")
|
||||
}))
|
||||
defer tempServer.Close()
|
||||
fakeCollector.configFile.Endpoint = tempServer.URL
|
||||
|
||||
metrics := map[string]v1.MetricVal{}
|
||||
_, metrics, errMetric := fakeCollector.Collect(metrics)
|
||||
assert.NoError(errMetric)
|
||||
metricNames := []string{"activeConnections", "reading", "writing", "waiting"}
|
||||
// activeConnections = 3
|
||||
assert.Equal(metrics[metricNames[0]].IntValue, 3)
|
||||
assert.Equal(metrics[metricNames[0]].FloatValue, 0)
|
||||
// reading = 0
|
||||
assert.Equal(metrics[metricNames[1]].IntValue, 0)
|
||||
assert.Equal(metrics[metricNames[1]].FloatValue, 0)
|
||||
// writing = 1
|
||||
assert.Equal(metrics[metricNames[2]].IntValue, 1)
|
||||
assert.Equal(metrics[metricNames[2]].FloatValue, 0)
|
||||
// waiting = 2
|
||||
assert.Equal(metrics[metricNames[3]].IntValue, 2)
|
||||
assert.Equal(metrics[metricNames[3]].FloatValue, 0)
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"github.com/google/cadvisor/info/v2"
|
||||
"github.com/google/cadvisor/info/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -27,7 +27,10 @@ type Collector interface {
|
|||
// Returns the next time this collector should be collected from.
|
||||
// Next collection time is always returned, even when an error occurs.
|
||||
// A collection time of zero means no more collection.
|
||||
Collect() (time.Time, []v2.Metric, error)
|
||||
Collect(map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, error)
|
||||
|
||||
// Return spec for all metrics associated with this collector
|
||||
GetSpec() []v1.MetricSpec
|
||||
|
||||
// Name of this collector.
|
||||
Name() string
|
||||
|
@ -42,5 +45,8 @@ type CollectorManager interface {
|
|||
// at which a collector will be ready to collect from.
|
||||
// Next collection time is always returned, even when an error occurs.
|
||||
// A collection time of zero means no more collection.
|
||||
Collect() (time.Time, []v2.Metric, error)
|
||||
Collect() (time.Time, map[string]v1.MetricVal, error)
|
||||
|
||||
// Get metric spec from all registered collectors.
|
||||
GetSpec() ([]v1.MetricSpec, error)
|
||||
}
|
||||
|
|
|
@ -73,6 +73,9 @@ type ContainerHandler interface {
|
|||
// Returns absolute cgroup path for the requested resource.
|
||||
GetCgroupPath(resource string) (string, error)
|
||||
|
||||
// Returns container labels, if available.
|
||||
GetContainerLabels() map[string]string
|
||||
|
||||
// Returns whether the container still exists.
|
||||
Exists() bool
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ func libcontainerConfigToContainerSpec(config *libcontainerConfigs.Config, mi *i
|
|||
}
|
||||
spec.Cpu.Mask = utils.FixCpuMask(config.Cgroups.CpusetCpus, mi.NumCores)
|
||||
|
||||
spec.HasNetwork = true
|
||||
spec.HasNetwork = len(config.Networks) > 0
|
||||
spec.HasDiskIo = true
|
||||
|
||||
return spec
|
||||
|
@ -276,7 +276,7 @@ func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
|
|||
}
|
||||
|
||||
func convertInterfaceStats(stats *info.InterfaceStats) {
|
||||
net := stats
|
||||
net := *stats
|
||||
|
||||
// Ingress for host veth is from the container.
|
||||
// Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
|
||||
|
@ -332,6 +332,10 @@ func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *dockerContainerHandler) GetContainerLabels() map[string]string {
|
||||
return self.labels
|
||||
}
|
||||
|
||||
func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
|
||||
return containerLibcontainer.GetProcesses(self.cgroupManager)
|
||||
}
|
||||
|
|
4
Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/helpers.go
generated
vendored
4
Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/helpers.go
generated
vendored
|
@ -93,7 +93,7 @@ func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info.
|
|||
}
|
||||
stats.Network.Interfaces[i] = interfaceStats
|
||||
}
|
||||
// For backwards compatability.
|
||||
// For backwards compatibility.
|
||||
if len(networkInterfaces) > 0 {
|
||||
stats.Network.InterfaceStats = stats.Network.Interfaces[0]
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func toContainerStats3(libcontainerStats *libcontainer.Stats, ret *info.Containe
|
|||
}
|
||||
}
|
||||
|
||||
// Add to base struct for backwards compatability.
|
||||
// Add to base struct for backwards compatibility.
|
||||
if len(ret.Network.Interfaces) > 0 {
|
||||
ret.Network.InterfaceStats = ret.Network.Interfaces[0]
|
||||
}
|
||||
|
|
|
@ -95,6 +95,11 @@ func (self *MockContainerHandler) GetCgroupPath(path string) (string, error) {
|
|||
return args.Get(0).(string), args.Error(1)
|
||||
}
|
||||
|
||||
func (self *MockContainerHandler) GetContainerLabels() map[string]string {
|
||||
args := self.Called()
|
||||
return args.Get(0).(map[string]string)
|
||||
}
|
||||
|
||||
type FactoryForMockContainerHandler struct {
|
||||
Name string
|
||||
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/google/cadvisor/fs"
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
"github.com/google/cadvisor/utils"
|
||||
"github.com/google/cadvisor/utils/machine"
|
||||
"golang.org/x/exp/inotify"
|
||||
)
|
||||
|
||||
|
@ -210,13 +211,33 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Memory.
|
||||
memoryRoot, ok := self.cgroupPaths["memory"]
|
||||
if ok {
|
||||
if utils.FileExists(memoryRoot) {
|
||||
// Memory
|
||||
if self.name == "/" {
|
||||
// Get memory and swap limits of the running machine
|
||||
memLimit, err := machine.GetMachineMemoryCapacity()
|
||||
if err != nil {
|
||||
glog.Warningf("failed to obtain memory limit for machine container")
|
||||
spec.HasMemory = false
|
||||
} else {
|
||||
spec.Memory.Limit = uint64(memLimit)
|
||||
// Spec is marked to have memory only if the memory limit is set
|
||||
spec.HasMemory = true
|
||||
spec.Memory.Limit = readInt64(memoryRoot, "memory.limit_in_bytes")
|
||||
spec.Memory.SwapLimit = readInt64(memoryRoot, "memory.memsw.limit_in_bytes")
|
||||
}
|
||||
|
||||
swapLimit, err := machine.GetMachineSwapCapacity()
|
||||
if err != nil {
|
||||
glog.Warningf("failed to obtain swap limit for machine container")
|
||||
} else {
|
||||
spec.Memory.SwapLimit = uint64(swapLimit)
|
||||
}
|
||||
} else {
|
||||
memoryRoot, ok := self.cgroupPaths["memory"]
|
||||
if ok {
|
||||
if utils.FileExists(memoryRoot) {
|
||||
spec.HasMemory = true
|
||||
spec.Memory.Limit = readInt64(memoryRoot, "memory.limit_in_bytes")
|
||||
spec.Memory.SwapLimit = readInt64(memoryRoot, "memory.memsw.limit_in_bytes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,6 +356,10 @@ func (self *rawContainerHandler) GetCgroupPath(resource string) (string, error)
|
|||
return path, nil
|
||||
}
|
||||
|
||||
func (self *rawContainerHandler) GetContainerLabels() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// Lists all directories under "path" and outputs the results as children of "parent".
|
||||
func listDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error {
|
||||
// Ignore if this hierarchy does not exist.
|
||||
|
|
|
@ -58,6 +58,9 @@ type ContainerSpec struct {
|
|||
|
||||
// HasDiskIo when true, indicates that DiskIo stats will be available.
|
||||
HasDiskIo bool `json:"has_diskio"`
|
||||
|
||||
HasCustomMetrics bool `json:"has_custom_metrics"`
|
||||
CustomMetrics []MetricSpec `json:"custom_metrics,omitempty"`
|
||||
}
|
||||
|
||||
// Container reference contains enough information to uniquely identify a container
|
||||
|
@ -190,6 +193,9 @@ func (self *ContainerSpec) Eq(b *ContainerSpec) bool {
|
|||
if self.HasDiskIo != b.HasDiskIo {
|
||||
return false
|
||||
}
|
||||
if self.HasCustomMetrics != b.HasCustomMetrics {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -419,6 +425,9 @@ type ContainerStats struct {
|
|||
|
||||
// Task load stats
|
||||
TaskStats LoadStats `json:"task_stats,omitempty"`
|
||||
|
||||
//Custom metrics from all collectors
|
||||
CustomMetrics map[string]MetricVal `json:"custom_metrics,omitempty"`
|
||||
}
|
||||
|
||||
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
|
||||
|
|
|
@ -112,6 +112,22 @@ type NetInfo struct {
|
|||
Mtu int64 `json:"mtu"`
|
||||
}
|
||||
|
||||
type CloudProvider string
|
||||
|
||||
const (
|
||||
GCE CloudProvider = "GCE"
|
||||
AWS = "AWS"
|
||||
Baremetal = "Baremetal"
|
||||
UnkownProvider = "Unknown"
|
||||
)
|
||||
|
||||
type InstanceType string
|
||||
|
||||
const (
|
||||
NoInstance InstanceType = "None"
|
||||
UnknownInstance = "Unknown"
|
||||
)
|
||||
|
||||
type MachineInfo struct {
|
||||
// The number of cores in this machine.
|
||||
NumCores int `json:"num_cores"`
|
||||
|
@ -143,6 +159,12 @@ type MachineInfo struct {
|
|||
// Machine Topology
|
||||
// Describes cpu/memory layout and hierarchy.
|
||||
Topology []Node `json:"topology"`
|
||||
|
||||
// Cloud provider the machine belongs to.
|
||||
CloudProvider CloudProvider `json:"cloud_provider"`
|
||||
|
||||
// Type of cloud instance (e.g. GCE standard) the machine is.
|
||||
InstanceType InstanceType `json:"instance_type"`
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v2
|
||||
package v1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
@ -32,38 +32,35 @@ const (
|
|||
MetricDelta = "delta"
|
||||
)
|
||||
|
||||
// An exported metric.
|
||||
type Metric struct {
|
||||
// DataType for metric being exported.
|
||||
type DataType string
|
||||
|
||||
const (
|
||||
IntType DataType = "int"
|
||||
FloatType = "float"
|
||||
)
|
||||
|
||||
// Spec for custom metric.
|
||||
type MetricSpec struct {
|
||||
// The name of the metric.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Type of the metric.
|
||||
Type MetricType `json:"type"`
|
||||
|
||||
// Metadata associated with this metric.
|
||||
Labels map[string]string
|
||||
// Data Type for the stats.
|
||||
Format DataType `json:"format"`
|
||||
|
||||
// Value of the metric. Only one of these values will be
|
||||
// available according to the output type of the metric.
|
||||
// If no values are available, there are no data points.
|
||||
IntPoints []IntPoint `json:"int_points,omitempty"`
|
||||
FloatPoints []FloatPoint `json:"float_points,omitempty"`
|
||||
// Display Units for the stats.
|
||||
Units string `json:"units"`
|
||||
}
|
||||
|
||||
// An integer metric data point.
|
||||
type IntPoint struct {
|
||||
// An exported metric.
|
||||
type MetricVal struct {
|
||||
// Time at which the metric was queried
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// The value of the metric at this point.
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
// A float metric data point.
|
||||
type FloatPoint struct {
|
||||
// Time at which the metric was queried
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// The value of the metric at this point.
|
||||
Value float64 `json:"value"`
|
||||
IntValue int64 `json:"int_value,omitempty"`
|
||||
FloatValue float64 `json:"float_value,omitempty"`
|
||||
}
|
|
@ -73,6 +73,9 @@ type ContainerSpec struct {
|
|||
HasMemory bool `json:"has_memory"`
|
||||
Memory MemorySpec `json:"memory,omitempty"`
|
||||
|
||||
HasCustomMetrics bool `json:"has_custom_metrics"`
|
||||
CustomMetrics []v1.MetricSpec `json:"custom_metrics,omitempty"`
|
||||
|
||||
// Following resources have no associated spec, but are being isolated.
|
||||
HasNetwork bool `json:"has_network"`
|
||||
HasFilesystem bool `json:"has_filesystem"`
|
||||
|
@ -100,6 +103,9 @@ type ContainerStats struct {
|
|||
// Task load statistics
|
||||
HasLoad bool `json:"has_load"`
|
||||
Load v1.LoadStats `json:"load_stats,omitempty"`
|
||||
// Custom Metrics
|
||||
HasCustomMetrics bool `json:"has_custom_metrics"`
|
||||
CustomMetrics map[string]v1.MetricVal `json:"custom_metrics,omitempty"`
|
||||
}
|
||||
|
||||
type Percentiles struct {
|
||||
|
@ -110,8 +116,12 @@ type Percentiles struct {
|
|||
Mean uint64 `json:"mean"`
|
||||
// Max seen over the collected sample.
|
||||
Max uint64 `json:"max"`
|
||||
// 50th percentile over the collected sample.
|
||||
Fifty uint64 `json:"fifty"`
|
||||
// 90th percentile over the collected sample.
|
||||
Ninety uint64 `json:"ninety"`
|
||||
// 95th percentile over the collected sample.
|
||||
NinetyFive uint64 `json:"ninetyfive"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
|
|
|
@ -59,6 +59,12 @@ type Attributes struct {
|
|||
// Machine Topology
|
||||
// Describes cpu/memory layout and hierarchy.
|
||||
Topology []v1.Node `json:"topology"`
|
||||
|
||||
// Cloud provider the machine belongs to
|
||||
CloudProvider v1.CloudProvider `json:"cloud_provider"`
|
||||
|
||||
// Type of cloud instance (e.g. GCE standard) the machine is.
|
||||
InstanceType v1.InstanceType `json:"instance_type"`
|
||||
}
|
||||
|
||||
func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
|
||||
|
@ -76,5 +82,7 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
|
|||
DiskMap: mi.DiskMap,
|
||||
NetworkDevices: mi.NetworkDevices,
|
||||
Topology: mi.Topology,
|
||||
CloudProvider: mi.CloudProvider,
|
||||
InstanceType: mi.InstanceType,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ package manager
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -39,8 +41,6 @@ import (
|
|||
|
||||
// Housekeeping interval.
|
||||
var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings")
|
||||
var maxHousekeepingInterval = flag.Duration("max_housekeeping_interval", 60*time.Second, "Largest interval to allow between container housekeepings")
|
||||
var allowDynamicHousekeeping = flag.Bool("allow_dynamic_housekeeping", true, "Whether to allow the housekeeping interval to be dynamic")
|
||||
|
||||
var cgroupPathRegExp = regexp.MustCompile(".*:devices:(.*?),.*")
|
||||
|
||||
|
@ -54,16 +54,18 @@ type containerInfo struct {
|
|||
}
|
||||
|
||||
type containerData struct {
|
||||
handler container.ContainerHandler
|
||||
info containerInfo
|
||||
memoryCache *memory.InMemoryCache
|
||||
lock sync.Mutex
|
||||
loadReader cpuload.CpuLoadReader
|
||||
summaryReader *summary.StatsSummary
|
||||
loadAvg float64 // smoothed load average seen so far.
|
||||
housekeepingInterval time.Duration
|
||||
lastUpdatedTime time.Time
|
||||
lastErrorTime time.Time
|
||||
handler container.ContainerHandler
|
||||
info containerInfo
|
||||
memoryCache *memory.InMemoryCache
|
||||
lock sync.Mutex
|
||||
loadReader cpuload.CpuLoadReader
|
||||
summaryReader *summary.StatsSummary
|
||||
loadAvg float64 // smoothed load average seen so far.
|
||||
housekeepingInterval time.Duration
|
||||
maxHousekeepingInterval time.Duration
|
||||
allowDynamicHousekeeping bool
|
||||
lastUpdatedTime time.Time
|
||||
lastErrorTime time.Time
|
||||
|
||||
// Whether to log the usage of this container when it is updated.
|
||||
logUsage bool
|
||||
|
@ -136,11 +138,32 @@ func (c *containerData) getCgroupPath(cgroups string) (string, error) {
|
|||
return string(matches[1]), nil
|
||||
}
|
||||
|
||||
func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace bool) ([]v2.ProcessInfo, error) {
|
||||
// report all processes for root.
|
||||
isRoot := c.info.Name == "/"
|
||||
// TODO(rjnagal): Take format as an option?
|
||||
format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup"
|
||||
// Returns contents of a file inside the container root.
|
||||
// Takes in a path relative to container root.
|
||||
func (c *containerData) ReadFile(filepath string, inHostNamespace bool) ([]byte, error) {
|
||||
pids, err := c.getContainerPids(inHostNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(rjnagal): Optimize by just reading container's cgroup.proc file when in host namespace.
|
||||
rootfs := "/"
|
||||
if !inHostNamespace {
|
||||
rootfs = "/rootfs"
|
||||
}
|
||||
for _, pid := range pids {
|
||||
filePath := path.Join(rootfs, "/proc", pid, "/root", filepath)
|
||||
glog.V(3).Infof("Trying path %q", filePath)
|
||||
data, err := ioutil.ReadFile(filePath)
|
||||
if err == nil {
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
// No process paths could be found. Declare config non-existent.
|
||||
return nil, fmt.Errorf("file %q does not exist.", filepath)
|
||||
}
|
||||
|
||||
// Return output for ps command in host /proc with specified format
|
||||
func (c *containerData) getPsOutput(inHostNamespace bool, format string) ([]byte, error) {
|
||||
args := []string{}
|
||||
command := "ps"
|
||||
if !inHostNamespace {
|
||||
|
@ -148,11 +171,53 @@ func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace
|
|||
args = append(args, "/rootfs", "ps")
|
||||
}
|
||||
args = append(args, "-e", "-o", format)
|
||||
expectedFields := 12
|
||||
out, err := exec.Command(command, args...).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute %q command: %v", command, err)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Get pids of processes in this container.
|
||||
// A slightly lighterweight call than GetProcessList if other details are not required.
|
||||
func (c *containerData) getContainerPids(inHostNamespace bool) ([]string, error) {
|
||||
format := "pid,cgroup"
|
||||
out, err := c.getPsOutput(inHostNamespace, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expectedFields := 2
|
||||
lines := strings.Split(string(out), "\n")
|
||||
pids := []string{}
|
||||
for _, line := range lines[1:] {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < expectedFields {
|
||||
return nil, fmt.Errorf("expected at least %d fields, found %d: output: %q", expectedFields, len(fields), line)
|
||||
}
|
||||
pid := fields[0]
|
||||
cgroup, err := c.getCgroupPath(fields[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[1], err)
|
||||
}
|
||||
if c.info.Name == cgroup {
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
}
|
||||
return pids, nil
|
||||
}
|
||||
|
||||
func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace bool) ([]v2.ProcessInfo, error) {
|
||||
// report all processes for root.
|
||||
isRoot := c.info.Name == "/"
|
||||
format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup"
|
||||
out, err := c.getPsOutput(inHostNamespace, format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expectedFields := 12
|
||||
processes := []v2.ProcessInfo{}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines[1:] {
|
||||
|
@ -183,13 +248,17 @@ func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid rss %q: %v", fields[6], err)
|
||||
}
|
||||
// convert to bytes
|
||||
rss *= 1024
|
||||
vs, err := strconv.ParseUint(fields[7], 0, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err)
|
||||
}
|
||||
// convert to bytes
|
||||
vs *= 1024
|
||||
cgroup, err := c.getCgroupPath(fields[11])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[10], err)
|
||||
return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[11], err)
|
||||
}
|
||||
// Remove the ps command we just ran from cadvisor container.
|
||||
// Not necessary, but makes the cadvisor page look cleaner.
|
||||
|
@ -221,7 +290,7 @@ func (c *containerData) GetProcessList(cadvisorContainer string, inHostNamespace
|
|||
return processes, nil
|
||||
}
|
||||
|
||||
func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager) (*containerData, error) {
|
||||
func newContainerData(containerName string, memoryCache *memory.InMemoryCache, handler container.ContainerHandler, loadReader cpuload.CpuLoadReader, logUsage bool, collectorManager collector.CollectorManager, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool) (*containerData, error) {
|
||||
if memoryCache == nil {
|
||||
return nil, fmt.Errorf("nil memory storage")
|
||||
}
|
||||
|
@ -234,14 +303,16 @@ func newContainerData(containerName string, memoryCache *memory.InMemoryCache, h
|
|||
}
|
||||
|
||||
cont := &containerData{
|
||||
handler: handler,
|
||||
memoryCache: memoryCache,
|
||||
housekeepingInterval: *HousekeepingInterval,
|
||||
loadReader: loadReader,
|
||||
logUsage: logUsage,
|
||||
loadAvg: -1.0, // negative value indicates uninitialized.
|
||||
stop: make(chan bool, 1),
|
||||
collectorManager: collectorManager,
|
||||
handler: handler,
|
||||
memoryCache: memoryCache,
|
||||
housekeepingInterval: *HousekeepingInterval,
|
||||
maxHousekeepingInterval: maxHousekeepingInterval,
|
||||
allowDynamicHousekeeping: allowDynamicHousekeeping,
|
||||
loadReader: loadReader,
|
||||
logUsage: logUsage,
|
||||
loadAvg: -1.0, // negative value indicates uninitialized.
|
||||
stop: make(chan bool, 1),
|
||||
collectorManager: collectorManager,
|
||||
}
|
||||
cont.info.ContainerReference = ref
|
||||
|
||||
|
@ -260,7 +331,7 @@ func newContainerData(containerName string, memoryCache *memory.InMemoryCache, h
|
|||
|
||||
// Determine when the next housekeeping should occur.
|
||||
func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Time {
|
||||
if *allowDynamicHousekeeping {
|
||||
if self.allowDynamicHousekeeping {
|
||||
var empty time.Time
|
||||
stats, err := self.memoryCache.RecentStats(self.info.Name, empty, empty, 2)
|
||||
if err != nil {
|
||||
|
@ -270,10 +341,10 @@ func (self *containerData) nextHousekeeping(lastHousekeeping time.Time) time.Tim
|
|||
} else if len(stats) == 2 {
|
||||
// TODO(vishnuk): Use no processes as a signal.
|
||||
// Raise the interval if usage hasn't changed in the last housekeeping.
|
||||
if stats[0].StatsEq(stats[1]) && (self.housekeepingInterval < *maxHousekeepingInterval) {
|
||||
if stats[0].StatsEq(stats[1]) && (self.housekeepingInterval < self.maxHousekeepingInterval) {
|
||||
self.housekeepingInterval *= 2
|
||||
if self.housekeepingInterval > *maxHousekeepingInterval {
|
||||
self.housekeepingInterval = *maxHousekeepingInterval
|
||||
if self.housekeepingInterval > self.maxHousekeepingInterval {
|
||||
self.housekeepingInterval = self.maxHousekeepingInterval
|
||||
}
|
||||
} else if self.housekeepingInterval != *HousekeepingInterval {
|
||||
// Lower interval back to the baseline.
|
||||
|
@ -340,19 +411,7 @@ func (c *containerData) housekeeping() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(vmarmol): Export metrics.
|
||||
// Run custom collectors.
|
||||
nextCollectionTime, _, err := c.collectorManager.Collect()
|
||||
if err != nil && c.allowErrorLogging() {
|
||||
glog.Warningf("[%s] Collection failed: %v", c.info.Name, err)
|
||||
}
|
||||
|
||||
// Next housekeeping is the first of the stats or the custom collector's housekeeping.
|
||||
nextHousekeeping := c.nextHousekeeping(lastHousekeeping)
|
||||
next := nextHousekeeping
|
||||
if !nextCollectionTime.IsZero() && nextCollectionTime.Before(nextHousekeeping) {
|
||||
next = nextCollectionTime
|
||||
}
|
||||
next := c.nextHousekeeping(lastHousekeeping)
|
||||
|
||||
// Schedule the next housekeeping. Sleep until that time.
|
||||
if time.Now().Before(next) {
|
||||
|
@ -380,6 +439,12 @@ func (c *containerData) updateSpec() error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
customMetrics, err := c.collectorManager.GetSpec()
|
||||
if len(customMetrics) > 0 {
|
||||
spec.HasCustomMetrics = true
|
||||
spec.CustomMetrics = customMetrics
|
||||
}
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.info.Spec = spec
|
||||
|
@ -432,6 +497,20 @@ func (c *containerData) updateStats() error {
|
|||
glog.V(2).Infof("Failed to add summary stats for %q: %v", c.info.Name, err)
|
||||
}
|
||||
}
|
||||
var customStatsErr error
|
||||
cm := c.collectorManager.(*collector.GenericCollectorManager)
|
||||
if len(cm.Collectors) > 0 {
|
||||
if cm.NextCollectionTime.Before(time.Now()) {
|
||||
customStats, err := c.updateCustomStats()
|
||||
if customStats != nil {
|
||||
stats.CustomMetrics = customStats
|
||||
}
|
||||
if err != nil {
|
||||
customStatsErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref, err := c.handler.ContainerReference()
|
||||
if err != nil {
|
||||
// Ignore errors if the container is dead.
|
||||
|
@ -444,7 +523,21 @@ func (c *containerData) updateStats() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return statsErr
|
||||
if statsErr != nil {
|
||||
return statsErr
|
||||
}
|
||||
return customStatsErr
|
||||
}
|
||||
|
||||
func (c *containerData) updateCustomStats() (map[string]info.MetricVal, error) {
|
||||
_, customStats, customStatsErr := c.collectorManager.Collect()
|
||||
if customStatsErr != nil {
|
||||
if !c.handler.Exists() {
|
||||
return customStats, nil
|
||||
}
|
||||
customStatsErr = fmt.Errorf("%v, continuing to push custom stats", customStatsErr)
|
||||
}
|
||||
return customStats, customStatsErr
|
||||
}
|
||||
|
||||
func (c *containerData) updateSubcontainers() error {
|
||||
|
|
|
@ -41,7 +41,7 @@ func setupContainerData(t *testing.T, spec info.ContainerSpec) (*containerData,
|
|||
nil,
|
||||
)
|
||||
memoryCache := memory.New(60, nil)
|
||||
ret, err := newContainerData(containerName, memoryCache, mockHandler, nil, false, &collector.FakeCollectorManager{})
|
||||
ret, err := newContainerData(containerName, memoryCache, mockHandler, nil, false, &collector.GenericCollectorManager{}, 60*time.Second, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@ package manager
|
|||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
|
@ -29,193 +26,16 @@ import (
|
|||
"github.com/google/cadvisor/container/docker"
|
||||
"github.com/google/cadvisor/fs"
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
"github.com/google/cadvisor/utils"
|
||||
"github.com/google/cadvisor/utils/cloudinfo"
|
||||
"github.com/google/cadvisor/utils/machine"
|
||||
"github.com/google/cadvisor/utils/sysfs"
|
||||
"github.com/google/cadvisor/utils/sysinfo"
|
||||
version "github.com/google/cadvisor/version"
|
||||
)
|
||||
|
||||
var cpuRegExp = regexp.MustCompile("processor\\t*: +([0-9]+)")
|
||||
var coreRegExp = regexp.MustCompile("core id\\t*: +([0-9]+)")
|
||||
var nodeRegExp = regexp.MustCompile("physical id\\t*: +([0-9]+)")
|
||||
var CpuClockSpeedMHz = regexp.MustCompile("cpu MHz\\t*: +([0-9]+.[0-9]+)")
|
||||
var memoryCapacityRegexp = regexp.MustCompile("MemTotal: *([0-9]+) kB")
|
||||
|
||||
var machineIdFilePath = flag.String("machine_id_file", "/etc/machine-id,/var/lib/dbus/machine-id", "Comma-separated list of files to check for machine-id. Use the first one that exists.")
|
||||
var bootIdFilePath = flag.String("boot_id_file", "/proc/sys/kernel/random/boot_id", "Comma-separated list of files to check for boot-id. Use the first one that exists.")
|
||||
|
||||
func getClockSpeed(procInfo []byte) (uint64, error) {
|
||||
// First look through sys to find a max supported cpu frequency.
|
||||
const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
|
||||
if utils.FileExists(maxFreqFile) {
|
||||
val, err := ioutil.ReadFile(maxFreqFile)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var maxFreq uint64
|
||||
n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
|
||||
if err != nil || n != 1 {
|
||||
return 0, fmt.Errorf("could not parse frequency %q", val)
|
||||
}
|
||||
return maxFreq, nil
|
||||
}
|
||||
// Fall back to /proc/cpuinfo
|
||||
matches := CpuClockSpeedMHz.FindSubmatch(procInfo)
|
||||
if len(matches) != 2 {
|
||||
//Check if we are running on Power systems which have a different format
|
||||
CpuClockSpeedMHz, _ = regexp.Compile("clock\\t*: +([0-9]+.[0-9]+)MHz")
|
||||
matches = CpuClockSpeedMHz.FindSubmatch(procInfo)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
|
||||
}
|
||||
}
|
||||
speed, err := strconv.ParseFloat(string(matches[1]), 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Convert to kHz
|
||||
return uint64(speed * 1000), nil
|
||||
}
|
||||
|
||||
func getMemoryCapacity(b []byte) (int64, error) {
|
||||
matches := memoryCapacityRegexp.FindSubmatch(b)
|
||||
if len(matches) != 2 {
|
||||
return -1, fmt.Errorf("failed to find memory capacity in output: %q", string(b))
|
||||
}
|
||||
m, err := strconv.ParseInt(string(matches[1]), 10, 64)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
return m * 1024, err
|
||||
}
|
||||
|
||||
func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
|
||||
matches := r.FindSubmatch([]byte(s))
|
||||
if len(matches) == 2 {
|
||||
val, err := strconv.ParseInt(string(matches[1]), 10, 32)
|
||||
if err != nil {
|
||||
return true, -1, err
|
||||
}
|
||||
return true, int(val), nil
|
||||
}
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
func findNode(nodes []info.Node, id int) (bool, int) {
|
||||
for i, n := range nodes {
|
||||
if n.Id == id {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func addNode(nodes *[]info.Node, id int) (int, error) {
|
||||
var idx int
|
||||
if id == -1 {
|
||||
// Some VMs don't fill topology data. Export single package.
|
||||
id = 0
|
||||
}
|
||||
|
||||
ok, idx := findNode(*nodes, id)
|
||||
if !ok {
|
||||
// New node
|
||||
node := info.Node{Id: id}
|
||||
// Add per-node memory information.
|
||||
meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id)
|
||||
out, err := ioutil.ReadFile(meminfo)
|
||||
// Ignore if per-node info is not available.
|
||||
if err == nil {
|
||||
m, err := getMemoryCapacity(out)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
node.Memory = uint64(m)
|
||||
}
|
||||
*nodes = append(*nodes, node)
|
||||
idx = len(*nodes) - 1
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func getTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
|
||||
nodes := []info.Node{}
|
||||
numCores := 0
|
||||
lastThread := -1
|
||||
lastCore := -1
|
||||
lastNode := -1
|
||||
for _, line := range strings.Split(cpuinfo, "\n") {
|
||||
ok, val, err := extractValue(line, cpuRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
thread := val
|
||||
numCores++
|
||||
if lastThread != -1 {
|
||||
// New cpu section. Save last one.
|
||||
nodeIdx, err := addNode(&nodes, lastNode)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
||||
}
|
||||
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
||||
lastCore = -1
|
||||
lastNode = -1
|
||||
}
|
||||
lastThread = thread
|
||||
}
|
||||
ok, val, err = extractValue(line, coreRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
lastCore = val
|
||||
}
|
||||
ok, val, err = extractValue(line, nodeRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
lastNode = val
|
||||
}
|
||||
}
|
||||
nodeIdx, err := addNode(&nodes, lastNode)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
||||
}
|
||||
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
||||
if numCores < 1 {
|
||||
return nil, numCores, fmt.Errorf("could not detect any cores")
|
||||
}
|
||||
for idx, node := range nodes {
|
||||
caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0])
|
||||
if err != nil {
|
||||
glog.Errorf("failed to get cache information for node %d: %v", node.Id, err)
|
||||
continue
|
||||
}
|
||||
numThreadsPerCore := len(node.Cores[0].Threads)
|
||||
numThreadsPerNode := len(node.Cores) * numThreadsPerCore
|
||||
for _, cache := range caches {
|
||||
c := info.Cache{
|
||||
Size: cache.Size,
|
||||
Level: cache.Level,
|
||||
Type: cache.Type,
|
||||
}
|
||||
if cache.Cpus == numThreadsPerNode && cache.Level > 2 {
|
||||
// Add a node-level cache.
|
||||
nodes[idx].AddNodeCache(c)
|
||||
} else if cache.Cpus == numThreadsPerCore {
|
||||
// Add to each core.
|
||||
nodes[idx].AddPerCoreCache(c)
|
||||
}
|
||||
// Ignore unknown caches.
|
||||
}
|
||||
}
|
||||
return nodes, numCores, nil
|
||||
}
|
||||
|
||||
func getInfoFromFiles(filePaths string) string {
|
||||
if len(filePaths) == 0 {
|
||||
return ""
|
||||
|
@ -232,18 +52,12 @@ func getInfoFromFiles(filePaths string) string {
|
|||
|
||||
func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo) (*info.MachineInfo, error) {
|
||||
cpuinfo, err := ioutil.ReadFile("/proc/cpuinfo")
|
||||
clockSpeed, err := getClockSpeed(cpuinfo)
|
||||
clockSpeed, err := machine.GetClockSpeed(cpuinfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the amount of usable memory from /proc/meminfo.
|
||||
out, err := ioutil.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memoryCapacity, err := getMemoryCapacity(out)
|
||||
memoryCapacity, err := machine.GetMachineMemoryCapacity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -263,7 +77,7 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo) (*info.MachineInfo, err
|
|||
glog.Errorf("Failed to get network devices: %v", err)
|
||||
}
|
||||
|
||||
topology, numCores, err := getTopology(sysFs, string(cpuinfo))
|
||||
topology, numCores, err := machine.GetTopology(sysFs, string(cpuinfo))
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to get topology information: %v", err)
|
||||
}
|
||||
|
@ -273,6 +87,10 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo) (*info.MachineInfo, err
|
|||
glog.Errorf("Failed to get system UUID: %v", err)
|
||||
}
|
||||
|
||||
realCloudInfo := cloudinfo.NewRealCloudInfo()
|
||||
cloudProvider := realCloudInfo.GetCloudProvider()
|
||||
instanceType := realCloudInfo.GetInstanceType()
|
||||
|
||||
machineInfo := &info.MachineInfo{
|
||||
NumCores: numCores,
|
||||
CpuFrequency: clockSpeed,
|
||||
|
@ -283,6 +101,8 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo) (*info.MachineInfo, err
|
|||
MachineID: getInfoFromFiles(*machineIdFilePath),
|
||||
SystemUUID: systemUUID,
|
||||
BootID: getInfoFromFiles(*bootIdFilePath),
|
||||
CloudProvider: cloudProvider,
|
||||
InstanceType: instanceType,
|
||||
}
|
||||
|
||||
for _, fs := range filesystems {
|
||||
|
|
|
@ -114,7 +114,7 @@ type Manager interface {
|
|||
}
|
||||
|
||||
// New takes a memory storage and returns a new manager.
|
||||
func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs) (Manager, error) {
|
||||
func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingInterval time.Duration, allowDynamicHousekeeping bool) (Manager, error) {
|
||||
if memoryCache == nil {
|
||||
return nil, fmt.Errorf("manager requires memory storage")
|
||||
}
|
||||
|
@ -139,13 +139,15 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs) (Manager, error)
|
|||
inHostNamespace = true
|
||||
}
|
||||
newManager := &manager{
|
||||
containers: make(map[namespacedContainerName]*containerData),
|
||||
quitChannels: make([]chan error, 0, 2),
|
||||
memoryCache: memoryCache,
|
||||
fsInfo: fsInfo,
|
||||
cadvisorContainer: selfContainer,
|
||||
inHostNamespace: inHostNamespace,
|
||||
startupTime: time.Now(),
|
||||
containers: make(map[namespacedContainerName]*containerData),
|
||||
quitChannels: make([]chan error, 0, 2),
|
||||
memoryCache: memoryCache,
|
||||
fsInfo: fsInfo,
|
||||
cadvisorContainer: selfContainer,
|
||||
inHostNamespace: inHostNamespace,
|
||||
startupTime: time.Now(),
|
||||
maxHousekeepingInterval: maxHousekeepingInterval,
|
||||
allowDynamicHousekeeping: allowDynamicHousekeeping,
|
||||
}
|
||||
|
||||
machineInfo, err := getMachineInfo(sysfs, fsInfo)
|
||||
|
@ -176,19 +178,21 @@ type namespacedContainerName struct {
|
|||
}
|
||||
|
||||
type manager struct {
|
||||
containers map[namespacedContainerName]*containerData
|
||||
containersLock sync.RWMutex
|
||||
memoryCache *memory.InMemoryCache
|
||||
fsInfo fs.FsInfo
|
||||
machineInfo info.MachineInfo
|
||||
versionInfo info.VersionInfo
|
||||
quitChannels []chan error
|
||||
cadvisorContainer string
|
||||
inHostNamespace bool
|
||||
dockerContainersRegexp *regexp.Regexp
|
||||
loadReader cpuload.CpuLoadReader
|
||||
eventHandler events.EventManager
|
||||
startupTime time.Time
|
||||
containers map[namespacedContainerName]*containerData
|
||||
containersLock sync.RWMutex
|
||||
memoryCache *memory.InMemoryCache
|
||||
fsInfo fs.FsInfo
|
||||
machineInfo info.MachineInfo
|
||||
versionInfo info.VersionInfo
|
||||
quitChannels []chan error
|
||||
cadvisorContainer string
|
||||
inHostNamespace bool
|
||||
dockerContainersRegexp *regexp.Regexp
|
||||
loadReader cpuload.CpuLoadReader
|
||||
eventHandler events.EventManager
|
||||
startupTime time.Time
|
||||
maxHousekeepingInterval time.Duration
|
||||
allowDynamicHousekeeping bool
|
||||
}
|
||||
|
||||
// Start the container manager.
|
||||
|
@ -371,12 +375,13 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp
|
|||
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,
|
||||
CreationTime: specV1.CreationTime,
|
||||
HasCpu: specV1.HasCpu,
|
||||
HasMemory: specV1.HasMemory,
|
||||
HasFilesystem: specV1.HasFilesystem,
|
||||
HasNetwork: specV1.HasNetwork,
|
||||
HasDiskIo: specV1.HasDiskIo,
|
||||
HasCustomMetrics: specV1.HasCustomMetrics,
|
||||
}
|
||||
if specV1.HasCpu {
|
||||
specV2.Cpu.Limit = specV1.Cpu.Limit
|
||||
|
@ -388,6 +393,9 @@ func (self *manager) getV2Spec(cinfo *containerInfo) v2.ContainerSpec {
|
|||
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
|
||||
|
@ -689,6 +697,28 @@ func (m *manager) GetProcessList(containerName string, options v2.RequestOptions
|
|||
return ps, nil
|
||||
}
|
||||
|
||||
func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *containerData) error {
|
||||
for k, v := range collectorConfigs {
|
||||
configFile, err := cont.ReadFile(v, m.inHostNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config file %q for config %q, container %q: %v", k, v, cont.info.Name, err)
|
||||
}
|
||||
glog.V(3).Infof("Got config from %q: %q", v, configFile)
|
||||
|
||||
newCollector, err := collector.NewCollector(k, configFile)
|
||||
if err != nil {
|
||||
glog.Infof("failed to create collector for container %q, config %q: %v", cont.info.Name, k, err)
|
||||
return err
|
||||
}
|
||||
err = cont.collectorManager.RegisterCollector(newCollector)
|
||||
if err != nil {
|
||||
glog.Infof("failed to register collector for container %q, config %q: %v", cont.info.Name, k, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a container.
|
||||
func (m *manager) createContainer(containerName string) error {
|
||||
handler, accept, err := container.NewContainerHandler(containerName)
|
||||
|
@ -700,17 +730,26 @@ func (m *manager) createContainer(containerName string) error {
|
|||
glog.V(4).Infof("ignoring container %q", containerName)
|
||||
return nil
|
||||
}
|
||||
// TODO(vmarmol): Register collectors.
|
||||
collectorManager, err := collector.NewCollectorManager()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logUsage := *logCadvisorUsage && containerName == m.cadvisorContainer
|
||||
cont, err := newContainerData(containerName, m.memoryCache, handler, m.loadReader, logUsage, collectorManager)
|
||||
cont, err := newContainerData(containerName, m.memoryCache, handler, m.loadReader, logUsage, collectorManager, m.maxHousekeepingInterval, m.allowDynamicHousekeeping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add collectors
|
||||
labels := handler.GetContainerLabels()
|
||||
collectorConfigs := collector.GetCollectorConfigs(labels)
|
||||
err = m.registerCollectors(collectorConfigs, cont)
|
||||
if err != nil {
|
||||
glog.Infof("failed to register collectors for %q: %v", containerName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add to the containers map.
|
||||
alreadyExists := func() bool {
|
||||
m.containersLock.Lock()
|
||||
|
|
|
@ -53,7 +53,7 @@ func createManagerAndAddContainers(
|
|||
spec,
|
||||
nil,
|
||||
).Once()
|
||||
cont, err := newContainerData(name, memoryCache, mockHandler, nil, false, &collector.FakeCollectorManager{})
|
||||
cont, err := newContainerData(name, memoryCache, mockHandler, nil, false, &collector.GenericCollectorManager{}, 60*time.Second, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ func TestDockerContainersInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewNilManager(t *testing.T) {
|
||||
_, err := New(nil, nil)
|
||||
_, err := New(nil, nil, 60*time.Second, true)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected nil manager to return error")
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package pages
|
|||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
@ -149,15 +148,19 @@ func toMegabytes(bytes uint64) float64 {
|
|||
return float64(bytes) / (1 << 20)
|
||||
}
|
||||
|
||||
// Size after which we consider memory to be "unlimited". This is not
|
||||
// MaxInt64 due to rounding by the kernel.
|
||||
const maxMemorySize = uint64(1 << 62)
|
||||
|
||||
func printSize(bytes uint64) string {
|
||||
if bytes >= math.MaxInt64 {
|
||||
if bytes >= maxMemorySize {
|
||||
return "unlimited"
|
||||
}
|
||||
return ByteSize(bytes).Size()
|
||||
}
|
||||
|
||||
func printUnit(bytes uint64) string {
|
||||
if bytes >= math.MaxInt64 {
|
||||
if bytes >= maxMemorySize {
|
||||
return ""
|
||||
}
|
||||
return ByteSize(bytes).Unit()
|
||||
|
@ -229,7 +232,7 @@ func serveContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) e
|
|||
|
||||
data := &pageData{
|
||||
DisplayName: displayName,
|
||||
ContainerName: cont.Name,
|
||||
ContainerName: escapeContainerName(cont.Name),
|
||||
ParentContainers: parentContainers,
|
||||
Subcontainers: subcontainerLinks,
|
||||
Spec: cont.Spec,
|
||||
|
|
|
@ -130,7 +130,7 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error
|
|||
}
|
||||
data = &pageData{
|
||||
DisplayName: displayName,
|
||||
ContainerName: cont.Name,
|
||||
ContainerName: escapeContainerName(cont.Name),
|
||||
ParentContainers: parentContainers,
|
||||
Spec: cont.Spec,
|
||||
Stats: cont.Stats,
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
auth "github.com/abbot/go-http-auth"
|
||||
|
@ -159,3 +160,12 @@ func getContainerDisplayName(cont info.ContainerReference) string {
|
|||
|
||||
return displayName
|
||||
}
|
||||
|
||||
// Escape the non-path characters on a container name.
|
||||
func escapeContainerName(containerName string) string {
|
||||
parts := strings.Split(containerName, "/")
|
||||
for i := range parts {
|
||||
parts[i] = url.QueryEscape(parts[i])
|
||||
}
|
||||
return strings.Join(parts, "/")
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ func (self *redisStorage) defaultReadyToFlush() bool {
|
|||
return time.Since(self.lastWrite) >= self.bufferDuration
|
||||
}
|
||||
|
||||
//We must add some defaut params (for example: MachineName,ContainerName...)because containerStats do not include them
|
||||
func (self *redisStorage) containerStatsAndDefautValues(ref info.ContainerReference, stats *info.ContainerStats) *detailSpec {
|
||||
//We must add some default params (for example: MachineName,ContainerName...)because containerStats do not include them
|
||||
func (self *redisStorage) containerStatsAndDefaultValues(ref info.ContainerReference, stats *info.ContainerStats) *detailSpec {
|
||||
timestamp := stats.Timestamp.UnixNano() / 1E3
|
||||
var containerName string
|
||||
if len(ref.Aliases) > 0 {
|
||||
|
@ -72,8 +72,8 @@ func (self *redisStorage) AddStats(ref info.ContainerReference, stats *info.Cont
|
|||
// AddStats will be invoked simultaneously from multiple threads and only one of them will perform a write.
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
// Add some defaut params based on containerStats
|
||||
detail := self.containerStatsAndDefautValues(ref, stats)
|
||||
// Add some default params based on containerStats
|
||||
detail := self.containerStatsAndDefaultValues(ref, stats)
|
||||
//To json
|
||||
b, _ := json.Marshal(detail)
|
||||
if self.readyToFlush() {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package statsd
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -22,8 +22,9 @@ import (
|
|||
)
|
||||
|
||||
type Client struct {
|
||||
HostPort string
|
||||
conn net.Conn
|
||||
HostPort string
|
||||
Namespace string
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func (self *Client) Open() error {
|
||||
|
@ -36,38 +37,28 @@ func (self *Client) Open() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *Client) Close() {
|
||||
func (self *Client) Close() error {
|
||||
self.conn.Close()
|
||||
self.conn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Client) UpdateGauge(name, value string) error {
|
||||
stats := make(map[string]string)
|
||||
val := fmt.Sprintf("%s|g", value)
|
||||
stats[name] = val
|
||||
if err := self.send(stats); err != nil {
|
||||
// Simple send to statsd daemon without sampling.
|
||||
func (self *Client) Send(namespace, containerName, key string, value uint64) error {
|
||||
// only send counter value
|
||||
formatted := fmt.Sprintf("%s.%s.%s:%d|g", namespace, containerName, key, value)
|
||||
_, err := fmt.Fprintf(self.conn, formatted)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("failed to send data %q: %v", formatted, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Simple send to statsd daemon without sampling.
|
||||
func (self *Client) send(data map[string]string) error {
|
||||
for k, v := range data {
|
||||
formatted := fmt.Sprintf("%s:%s", k, v)
|
||||
_, err := fmt.Fprintf(self.conn, formatted)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("failed to send data %q: %v", formatted, err)
|
||||
// return on first error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(hostPort string) (*Client, error) {
|
||||
client := Client{HostPort: hostPort}
|
||||
if err := client.Open(); err != nil {
|
||||
Client := Client{HostPort: hostPort}
|
||||
if err := Client.Open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &client, nil
|
||||
return &Client, nil
|
||||
}
|
127
Godeps/_workspace/src/github.com/google/cadvisor/storage/statsd/statsd.go
generated
vendored
Normal file
127
Godeps/_workspace/src/github.com/google/cadvisor/storage/statsd/statsd.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
// 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 statsd
|
||||
|
||||
import (
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
client "github.com/google/cadvisor/storage/statsd/client"
|
||||
)
|
||||
|
||||
type statsdStorage struct {
|
||||
client *client.Client
|
||||
Namespace string
|
||||
}
|
||||
|
||||
const (
|
||||
colCpuCumulativeUsage string = "cpu_cumulative_usage"
|
||||
// Memory Usage
|
||||
colMemoryUsage string = "memory_usage"
|
||||
// Working set size
|
||||
colMemoryWorkingSet string = "memory_working_set"
|
||||
// Cumulative count of bytes received.
|
||||
colRxBytes string = "rx_bytes"
|
||||
// Cumulative count of receive errors encountered.
|
||||
colRxErrors string = "rx_errors"
|
||||
// Cumulative count of bytes transmitted.
|
||||
colTxBytes string = "tx_bytes"
|
||||
// Cumulative count of transmit errors encountered.
|
||||
colTxErrors string = "tx_errors"
|
||||
// Filesystem summary
|
||||
colFsSummary = "fs_summary"
|
||||
// Filesystem limit.
|
||||
colFsLimit = "fs_limit"
|
||||
// Filesystem usage.
|
||||
colFsUsage = "fs_usage"
|
||||
)
|
||||
|
||||
func (self *statsdStorage) containerStatsToValues(
|
||||
stats *info.ContainerStats,
|
||||
) (series map[string]uint64) {
|
||||
series = make(map[string]uint64)
|
||||
|
||||
// Cumulative Cpu Usage
|
||||
series[colCpuCumulativeUsage] = stats.Cpu.Usage.Total
|
||||
|
||||
// Memory Usage
|
||||
series[colMemoryUsage] = stats.Memory.Usage
|
||||
|
||||
// Working set size
|
||||
series[colMemoryWorkingSet] = stats.Memory.WorkingSet
|
||||
|
||||
// Network stats.
|
||||
series[colRxBytes] = stats.Network.RxBytes
|
||||
series[colRxErrors] = stats.Network.RxErrors
|
||||
series[colTxBytes] = stats.Network.TxBytes
|
||||
series[colTxErrors] = stats.Network.TxErrors
|
||||
|
||||
return series
|
||||
}
|
||||
|
||||
func (self *statsdStorage) containerFsStatsToValues(
|
||||
series *map[string]uint64,
|
||||
stats *info.ContainerStats,
|
||||
) {
|
||||
for _, fsStat := range stats.Filesystem {
|
||||
// Summary stats.
|
||||
(*series)[colFsSummary+"."+colFsLimit] += fsStat.Limit
|
||||
(*series)[colFsSummary+"."+colFsUsage] += fsStat.Usage
|
||||
|
||||
// Per device stats.
|
||||
(*series)[fsStat.Device+"."+colFsLimit] = fsStat.Limit
|
||||
(*series)[fsStat.Device+"."+colFsUsage] = fsStat.Usage
|
||||
}
|
||||
}
|
||||
|
||||
//Push the data into redis
|
||||
func (self *statsdStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||
if stats == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var containerName string
|
||||
if len(ref.Aliases) > 0 {
|
||||
containerName = ref.Aliases[0]
|
||||
} else {
|
||||
containerName = ref.Name
|
||||
}
|
||||
|
||||
series := self.containerStatsToValues(stats)
|
||||
self.containerFsStatsToValues(&series, stats)
|
||||
for key, value := range series {
|
||||
err := self.client.Send(self.Namespace, containerName, key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *statsdStorage) Close() error {
|
||||
self.client.Close()
|
||||
self.client = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(namespace, hostPort string) (*statsdStorage, error) {
|
||||
statsdClient, err := client.New(hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statsdStorage := &statsdStorage{
|
||||
client: statsdClient,
|
||||
Namespace: namespace,
|
||||
}
|
||||
return statsdStorage, nil
|
||||
}
|
|
@ -28,20 +28,23 @@ const secondsToMilliSeconds = 1000
|
|||
const milliSecondsToNanoSeconds = 1000000
|
||||
const secondsToNanoSeconds = secondsToMilliSeconds * milliSecondsToNanoSeconds
|
||||
|
||||
type uint64Slice []uint64
|
||||
type Uint64Slice []uint64
|
||||
|
||||
func (a uint64Slice) Len() int { return len(a) }
|
||||
func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] }
|
||||
func (a Uint64Slice) Len() int { return len(a) }
|
||||
func (a Uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Uint64Slice) Less(i, j int) bool { return a[i] < a[j] }
|
||||
|
||||
// Get 90th percentile of the provided samples. Round to integer.
|
||||
func (self uint64Slice) Get90Percentile() uint64 {
|
||||
// Get percentile of the provided samples. Round to integer.
|
||||
func (self Uint64Slice) GetPercentile(d float64) uint64 {
|
||||
if d < 0.0 || d > 1.0 {
|
||||
return 0
|
||||
}
|
||||
count := self.Len()
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
sort.Sort(self)
|
||||
n := float64(0.9 * (float64(count) + 1))
|
||||
n := float64(d * (float64(count) + 1))
|
||||
idx, frac := math.Modf(n)
|
||||
index := int(idx)
|
||||
percentile := float64(self[index-1])
|
||||
|
@ -71,7 +74,7 @@ func (self *mean) Add(value uint64) {
|
|||
|
||||
type resource struct {
|
||||
// list of samples being tracked.
|
||||
samples uint64Slice
|
||||
samples Uint64Slice
|
||||
// average from existing samples.
|
||||
mean mean
|
||||
// maximum value seen so far in the added samples.
|
||||
|
@ -94,27 +97,31 @@ func (self *resource) Add(p info.Percentiles) {
|
|||
// Add a single sample. Internally, we convert it to a fake percentile sample.
|
||||
func (self *resource) AddSample(val uint64) {
|
||||
sample := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: val,
|
||||
Max: val,
|
||||
Ninety: val,
|
||||
Present: true,
|
||||
Mean: val,
|
||||
Max: val,
|
||||
Fifty: val,
|
||||
Ninety: val,
|
||||
NinetyFive: val,
|
||||
}
|
||||
self.Add(sample)
|
||||
}
|
||||
|
||||
// Get max, average, and 90p from existing samples.
|
||||
func (self *resource) GetPercentile() info.Percentiles {
|
||||
func (self *resource) GetAllPercentiles() info.Percentiles {
|
||||
p := info.Percentiles{}
|
||||
p.Mean = uint64(self.mean.Mean)
|
||||
p.Max = self.max
|
||||
p.Ninety = self.samples.Get90Percentile()
|
||||
p.Fifty = self.samples.GetPercentile(0.5)
|
||||
p.Ninety = self.samples.GetPercentile(0.9)
|
||||
p.NinetyFive = self.samples.GetPercentile(0.95)
|
||||
p.Present = true
|
||||
return p
|
||||
}
|
||||
|
||||
func NewResource(size int) *resource {
|
||||
return &resource{
|
||||
samples: make(uint64Slice, 0, size),
|
||||
samples: make(Uint64Slice, 0, size),
|
||||
mean: mean{count: 0, Mean: 0},
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +135,8 @@ func GetDerivedPercentiles(stats []*info.Usage) info.Usage {
|
|||
memory.Add(stat.Memory)
|
||||
}
|
||||
usage := info.Usage{}
|
||||
usage.Cpu = cpu.GetPercentile()
|
||||
usage.Memory = memory.GetPercentile()
|
||||
usage.Cpu = cpu.GetAllPercentiles()
|
||||
usage.Memory = memory.GetAllPercentiles()
|
||||
return usage
|
||||
}
|
||||
|
||||
|
@ -183,7 +190,7 @@ func GetMinutePercentiles(stats []*secondSample) info.Usage {
|
|||
percent := getPercentComplete(stats)
|
||||
return info.Usage{
|
||||
PercentComplete: percent,
|
||||
Cpu: cpu.GetPercentile(),
|
||||
Memory: memory.GetPercentile(),
|
||||
Cpu: cpu.GetAllPercentiles(),
|
||||
Memory: memory.GetAllPercentiles(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,25 +23,29 @@ import (
|
|||
|
||||
const Nanosecond = 1000000000
|
||||
|
||||
func Test90Percentile(t *testing.T) {
|
||||
func assertPercentile(t *testing.T, s Uint64Slice, f float64, want uint64) {
|
||||
if got := s.GetPercentile(f); got != want {
|
||||
t.Errorf("GetPercentile(%f) is %d, should be %d.", f, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPercentile(t *testing.T) {
|
||||
N := 100
|
||||
stats := make(uint64Slice, 0, N)
|
||||
s := make(Uint64Slice, 0, N)
|
||||
for i := N; i > 0; i-- {
|
||||
stats = append(stats, uint64(i))
|
||||
s = append(s, uint64(i))
|
||||
}
|
||||
p := stats.Get90Percentile()
|
||||
if p != 90 {
|
||||
t.Errorf("90th percentile is %d, should be 90.", p)
|
||||
}
|
||||
// 90p should be between 94 and 95. Promoted to 95.
|
||||
assertPercentile(t, s, 0.2, 20)
|
||||
assertPercentile(t, s, 0.7, 70)
|
||||
assertPercentile(t, s, 0.9, 90)
|
||||
N = 105
|
||||
for i := 101; i <= N; i++ {
|
||||
stats = append(stats, uint64(i))
|
||||
}
|
||||
p = stats.Get90Percentile()
|
||||
if p != 95 {
|
||||
t.Errorf("90th percentile is %d, should be 95.", p)
|
||||
s = append(s, uint64(i))
|
||||
}
|
||||
// 90p should be between 94 and 95. Promoted to 95.
|
||||
assertPercentile(t, s, 0.2, 21)
|
||||
assertPercentile(t, s, 0.7, 74)
|
||||
assertPercentile(t, s, 0.9, 95)
|
||||
}
|
||||
|
||||
func TestMean(t *testing.T) {
|
||||
|
@ -74,19 +78,23 @@ func TestAggregates(t *testing.T) {
|
|||
usage := GetMinutePercentiles(stats)
|
||||
// Cpu mean, max, and 90p should all be 1000 ms/s.
|
||||
cpuExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 1000,
|
||||
Max: 1000,
|
||||
Ninety: 1000,
|
||||
Present: true,
|
||||
Mean: 1000,
|
||||
Max: 1000,
|
||||
Fifty: 1000,
|
||||
Ninety: 1000,
|
||||
NinetyFive: 1000,
|
||||
}
|
||||
if usage.Cpu != cpuExpected {
|
||||
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
|
||||
}
|
||||
memExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Fifty: 50 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
NinetyFive: 95 * 1024,
|
||||
}
|
||||
if usage.Memory != memExpected {
|
||||
t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)
|
||||
|
@ -119,19 +127,23 @@ func TestSamplesCloseInTimeIgnored(t *testing.T) {
|
|||
usage := GetMinutePercentiles(stats)
|
||||
// Cpu mean, max, and 90p should all be 1000 ms/s. All high-value samples are discarded.
|
||||
cpuExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 1000,
|
||||
Max: 1000,
|
||||
Ninety: 1000,
|
||||
Present: true,
|
||||
Mean: 1000,
|
||||
Max: 1000,
|
||||
Fifty: 1000,
|
||||
Ninety: 1000,
|
||||
NinetyFive: 1000,
|
||||
}
|
||||
if usage.Cpu != cpuExpected {
|
||||
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
|
||||
}
|
||||
memExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Fifty: 50 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
NinetyFive: 95 * 1024,
|
||||
}
|
||||
if usage.Memory != memExpected {
|
||||
t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)
|
||||
|
@ -146,35 +158,43 @@ func TestDerivedStats(t *testing.T) {
|
|||
s := &info.Usage{
|
||||
PercentComplete: 100,
|
||||
Cpu: info.Percentiles{
|
||||
Present: true,
|
||||
Mean: i * Nanosecond,
|
||||
Max: i * Nanosecond,
|
||||
Ninety: i * Nanosecond,
|
||||
Present: true,
|
||||
Mean: i * Nanosecond,
|
||||
Max: i * Nanosecond,
|
||||
Fifty: i * Nanosecond,
|
||||
Ninety: i * Nanosecond,
|
||||
NinetyFive: i * Nanosecond,
|
||||
},
|
||||
Memory: info.Percentiles{
|
||||
Present: true,
|
||||
Mean: i * 1024,
|
||||
Max: i * 1024,
|
||||
Ninety: i * 1024,
|
||||
Present: true,
|
||||
Mean: i * 1024,
|
||||
Max: i * 1024,
|
||||
Fifty: i * 1024,
|
||||
Ninety: i * 1024,
|
||||
NinetyFive: i * 1024,
|
||||
},
|
||||
}
|
||||
stats = append(stats, s)
|
||||
}
|
||||
usage := GetDerivedPercentiles(stats)
|
||||
cpuExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 50 * Nanosecond,
|
||||
Max: 99 * Nanosecond,
|
||||
Ninety: 90 * Nanosecond,
|
||||
Present: true,
|
||||
Mean: 50 * Nanosecond,
|
||||
Max: 99 * Nanosecond,
|
||||
Fifty: 50 * Nanosecond,
|
||||
Ninety: 90 * Nanosecond,
|
||||
NinetyFive: 95 * Nanosecond,
|
||||
}
|
||||
if usage.Cpu != cpuExpected {
|
||||
t.Errorf("cpu stats are %+v. Expected %+v", usage.Cpu, cpuExpected)
|
||||
}
|
||||
memExpected := info.Percentiles{
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
Present: true,
|
||||
Mean: 50 * 1024,
|
||||
Max: 99 * 1024,
|
||||
Fifty: 50 * 1024,
|
||||
Ninety: 90 * 1024,
|
||||
NinetyFive: 95 * 1024,
|
||||
}
|
||||
if usage.Memory != memExpected {
|
||||
t.Errorf("memory stats are mean %+v. Expected %+v", usage.Memory, memExpected)
|
||||
|
|
87
Godeps/_workspace/src/github.com/google/cadvisor/utils/cloudinfo/cloudinfo.go
generated
vendored
Normal file
87
Godeps/_workspace/src/github.com/google/cadvisor/utils/cloudinfo/cloudinfo.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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.
|
||||
|
||||
// Get information about the cloud provider (if any) cAdvisor is running on.
|
||||
|
||||
package cloudinfo
|
||||
|
||||
import (
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
)
|
||||
|
||||
type CloudInfo interface {
|
||||
GetCloudProvider() info.CloudProvider
|
||||
GetInstanceType() info.InstanceType
|
||||
}
|
||||
|
||||
type realCloudInfo struct {
|
||||
cloudProvider info.CloudProvider
|
||||
instanceType info.InstanceType
|
||||
}
|
||||
|
||||
func NewRealCloudInfo() CloudInfo {
|
||||
cloudProvider := detectCloudProvider()
|
||||
instanceType := detectInstanceType(cloudProvider)
|
||||
return &realCloudInfo{
|
||||
cloudProvider: cloudProvider,
|
||||
instanceType: instanceType,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *realCloudInfo) GetCloudProvider() info.CloudProvider {
|
||||
return self.cloudProvider
|
||||
}
|
||||
|
||||
func (self *realCloudInfo) GetInstanceType() info.InstanceType {
|
||||
return self.instanceType
|
||||
}
|
||||
|
||||
func detectCloudProvider() info.CloudProvider {
|
||||
switch {
|
||||
case onGCE():
|
||||
return info.GCE
|
||||
case onAWS():
|
||||
return info.AWS
|
||||
case onBaremetal():
|
||||
return info.Baremetal
|
||||
}
|
||||
return info.UnkownProvider
|
||||
}
|
||||
|
||||
func detectInstanceType(cloudProvider info.CloudProvider) info.InstanceType {
|
||||
switch cloudProvider {
|
||||
case info.GCE:
|
||||
return getGceInstanceType()
|
||||
case info.AWS:
|
||||
return getAwsInstanceType()
|
||||
case info.Baremetal:
|
||||
return info.NoInstance
|
||||
}
|
||||
return info.UnknownInstance
|
||||
}
|
||||
|
||||
//TODO: Implement method.
|
||||
func onAWS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//TODO: Implement method.
|
||||
func getAwsInstanceType() info.InstanceType {
|
||||
return info.UnknownInstance
|
||||
}
|
||||
|
||||
//TODO: Implement method.
|
||||
func onBaremetal() bool {
|
||||
return false
|
||||
}
|
36
Godeps/_workspace/src/github.com/google/cadvisor/utils/cloudinfo/gce.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/google/cadvisor/utils/cloudinfo/gce.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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 cloudinfo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata"
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
)
|
||||
|
||||
func onGCE() bool {
|
||||
return metadata.OnGCE()
|
||||
}
|
||||
|
||||
func getGceInstanceType() info.InstanceType {
|
||||
machineType, err := metadata.Get("instance/machine-type")
|
||||
if err != nil {
|
||||
return info.UnknownInstance
|
||||
}
|
||||
|
||||
responseParts := strings.Split(machineType, "/") // Extract the instance name from the machine type.
|
||||
return info.InstanceType(responseParts[len(responseParts)-1])
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
package mockfs
|
||||
|
||||
import (
|
||||
gomock "code.google.com/p/gomock/gomock"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
fs "github.com/google/cadvisor/utils/fs"
|
||||
)
|
||||
|
||||
|
|
243
Godeps/_workspace/src/github.com/google/cadvisor/utils/machine/machine.go
generated
vendored
Normal file
243
Godeps/_workspace/src/github.com/google/cadvisor/utils/machine/machine.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
// 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 machine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
"github.com/google/cadvisor/utils"
|
||||
"github.com/google/cadvisor/utils/sysfs"
|
||||
"github.com/google/cadvisor/utils/sysinfo"
|
||||
)
|
||||
|
||||
// The utils/machine package contains functions that extract machine-level specs.
|
||||
|
||||
var cpuRegExp = regexp.MustCompile("processor\\t*: +([0-9]+)")
|
||||
var coreRegExp = regexp.MustCompile("core id\\t*: +([0-9]+)")
|
||||
var nodeRegExp = regexp.MustCompile("physical id\\t*: +([0-9]+)")
|
||||
var CpuClockSpeedMHz = regexp.MustCompile("cpu MHz\\t*: +([0-9]+.[0-9]+)")
|
||||
var memoryCapacityRegexp = regexp.MustCompile("MemTotal: *([0-9]+) kB")
|
||||
var swapCapacityRegexp = regexp.MustCompile("SwapTotal: *([0-9]+) kB")
|
||||
|
||||
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
|
||||
func GetClockSpeed(procInfo []byte) (uint64, error) {
|
||||
// First look through sys to find a max supported cpu frequency.
|
||||
const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
|
||||
if utils.FileExists(maxFreqFile) {
|
||||
val, err := ioutil.ReadFile(maxFreqFile)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var maxFreq uint64
|
||||
n, err := fmt.Sscanf(string(val), "%d", &maxFreq)
|
||||
if err != nil || n != 1 {
|
||||
return 0, fmt.Errorf("could not parse frequency %q", val)
|
||||
}
|
||||
return maxFreq, nil
|
||||
}
|
||||
// Fall back to /proc/cpuinfo
|
||||
matches := CpuClockSpeedMHz.FindSubmatch(procInfo)
|
||||
if len(matches) != 2 {
|
||||
//Check if we are running on Power systems which have a different format
|
||||
CpuClockSpeedMHz, _ = regexp.Compile("clock\\t*: +([0-9]+.[0-9]+)MHz")
|
||||
matches = CpuClockSpeedMHz.FindSubmatch(procInfo)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("could not detect clock speed from output: %q", string(procInfo))
|
||||
}
|
||||
}
|
||||
speed, err := strconv.ParseFloat(string(matches[1]), 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Convert to kHz
|
||||
return uint64(speed * 1000), nil
|
||||
}
|
||||
|
||||
// GetMachineMemoryCapacity returns the machine's total memory from /proc/meminfo.
|
||||
// Returns the total memory capacity as an int64 (number of bytes).
|
||||
func GetMachineMemoryCapacity() (int64, error) {
|
||||
out, err := ioutil.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
memoryCapacity, err := parseCapacity(out, memoryCapacityRegexp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return memoryCapacity, err
|
||||
}
|
||||
|
||||
// GetMachineSwapCapacity returns the machine's total swap from /proc/meminfo.
|
||||
// Returns the total swap capacity as an int64 (number of bytes).
|
||||
func GetMachineSwapCapacity() (int64, error) {
|
||||
out, err := ioutil.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
swapCapacity, err := parseCapacity(out, swapCapacityRegexp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return swapCapacity, err
|
||||
}
|
||||
|
||||
// parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
|
||||
// Assumes that the value matched by the Regexp is in KB.
|
||||
func parseCapacity(b []byte, r *regexp.Regexp) (int64, error) {
|
||||
matches := r.FindSubmatch(b)
|
||||
if len(matches) != 2 {
|
||||
return -1, fmt.Errorf("failed to match regexp in output: %q", string(b))
|
||||
}
|
||||
m, err := strconv.ParseInt(string(matches[1]), 10, 64)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
return m * 1024, err
|
||||
}
|
||||
|
||||
func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
|
||||
nodes := []info.Node{}
|
||||
numCores := 0
|
||||
lastThread := -1
|
||||
lastCore := -1
|
||||
lastNode := -1
|
||||
for _, line := range strings.Split(cpuinfo, "\n") {
|
||||
ok, val, err := extractValue(line, cpuRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse cpu info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
thread := val
|
||||
numCores++
|
||||
if lastThread != -1 {
|
||||
// New cpu section. Save last one.
|
||||
nodeIdx, err := addNode(&nodes, lastNode)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
||||
}
|
||||
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
||||
lastCore = -1
|
||||
lastNode = -1
|
||||
}
|
||||
lastThread = thread
|
||||
}
|
||||
ok, val, err = extractValue(line, coreRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
lastCore = val
|
||||
}
|
||||
ok, val, err = extractValue(line, nodeRegExp)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
|
||||
}
|
||||
if ok {
|
||||
lastNode = val
|
||||
}
|
||||
}
|
||||
nodeIdx, err := addNode(&nodes, lastNode)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
|
||||
}
|
||||
nodes[nodeIdx].AddThread(lastThread, lastCore)
|
||||
if numCores < 1 {
|
||||
return nil, numCores, fmt.Errorf("could not detect any cores")
|
||||
}
|
||||
for idx, node := range nodes {
|
||||
caches, err := sysinfo.GetCacheInfo(sysFs, node.Cores[0].Threads[0])
|
||||
if err != nil {
|
||||
glog.Errorf("failed to get cache information for node %d: %v", node.Id, err)
|
||||
continue
|
||||
}
|
||||
numThreadsPerCore := len(node.Cores[0].Threads)
|
||||
numThreadsPerNode := len(node.Cores) * numThreadsPerCore
|
||||
for _, cache := range caches {
|
||||
c := info.Cache{
|
||||
Size: cache.Size,
|
||||
Level: cache.Level,
|
||||
Type: cache.Type,
|
||||
}
|
||||
if cache.Cpus == numThreadsPerNode && cache.Level > 2 {
|
||||
// Add a node-level cache.
|
||||
nodes[idx].AddNodeCache(c)
|
||||
} else if cache.Cpus == numThreadsPerCore {
|
||||
// Add to each core.
|
||||
nodes[idx].AddPerCoreCache(c)
|
||||
}
|
||||
// Ignore unknown caches.
|
||||
}
|
||||
}
|
||||
return nodes, numCores, nil
|
||||
}
|
||||
|
||||
func extractValue(s string, r *regexp.Regexp) (bool, int, error) {
|
||||
matches := r.FindSubmatch([]byte(s))
|
||||
if len(matches) == 2 {
|
||||
val, err := strconv.ParseInt(string(matches[1]), 10, 32)
|
||||
if err != nil {
|
||||
return true, -1, err
|
||||
}
|
||||
return true, int(val), nil
|
||||
}
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
func findNode(nodes []info.Node, id int) (bool, int) {
|
||||
for i, n := range nodes {
|
||||
if n.Id == id {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func addNode(nodes *[]info.Node, id int) (int, error) {
|
||||
var idx int
|
||||
if id == -1 {
|
||||
// Some VMs don't fill topology data. Export single package.
|
||||
id = 0
|
||||
}
|
||||
|
||||
ok, idx := findNode(*nodes, id)
|
||||
if !ok {
|
||||
// New node
|
||||
node := info.Node{Id: id}
|
||||
// Add per-node memory information.
|
||||
meminfo := fmt.Sprintf("/sys/devices/system/node/node%d/meminfo", id)
|
||||
out, err := ioutil.ReadFile(meminfo)
|
||||
// Ignore if per-node info is not available.
|
||||
if err == nil {
|
||||
m, err := parseCapacity(out, memoryCapacityRegexp)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
node.Memory = uint64(m)
|
||||
}
|
||||
*nodes = append(*nodes, node)
|
||||
idx = len(*nodes) - 1
|
||||
}
|
||||
return idx, nil
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package manager
|
||||
package machine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
@ -38,7 +38,7 @@ func TestTopology(t *testing.T) {
|
|||
Cpus: 2,
|
||||
}
|
||||
sysFs.SetCacheInfo(c)
|
||||
topology, numCores, err := getTopology(sysFs, string(testcpuinfo))
|
||||
topology, numCores, err := GetTopology(sysFs, string(testcpuinfo))
|
||||
if err != nil {
|
||||
t.Errorf("failed to get topology for sample cpuinfo %s", string(testcpuinfo))
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func TestTopologyWithSimpleCpuinfo(t *testing.T) {
|
|||
Cpus: 1,
|
||||
}
|
||||
sysFs.SetCacheInfo(c)
|
||||
topology, numCores, err := getTopology(sysFs, "processor\t: 0\n")
|
||||
topology, numCores, err := GetTopology(sysFs, "processor\t: 0\n")
|
||||
if err != nil {
|
||||
t.Errorf("Expected cpuinfo with no topology data to succeed.")
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ func TestTopologyWithSimpleCpuinfo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTopologyEmptyCpuinfo(t *testing.T) {
|
||||
_, _, err := getTopology(&fakesysfs.FakeSysFs{}, "")
|
||||
_, _, err := GetTopology(&fakesysfs.FakeSysFs{}, "")
|
||||
if err == nil {
|
||||
t.Errorf("Expected empty cpuinfo to fail.")
|
||||
}
|
|
@ -15,4 +15,4 @@
|
|||
package version
|
||||
|
||||
// Version of cAdvisor.
|
||||
const VERSION = "0.15.1"
|
||||
const VERSION = "0.16.0"
|
||||
|
|
Loading…
Reference in New Issue