Update Kubernetes cadvisor to pull in @b7bbefd9b1d4c1032c6afece425a99ba17e43cdb so that the VersionInfo is not cached.

This commit is contained in:
Phillip Wittrock 2015-08-24 15:02:01 -07:00
parent 3c0a05de4a
commit dae4142d5c
26 changed files with 715 additions and 137 deletions

76
Godeps/Godeps.json generated
View File

@ -18,10 +18,6 @@
"ImportPath": "code.google.com/p/gcfg",
"Rev": "c2d3050044d05357eaf6c3547249ba57c5e235cb"
},
{
"ImportPath": "github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata",
"Rev": "e34a32f9b0ecbc0784865fb2d47f3818c09521d4"
},
{
"ImportPath": "github.com/Sirupsen/logrus",
"Comment": "v0.6.2-10-g51fe59a",
@ -269,93 +265,93 @@
},
{
"ImportPath": "github.com/google/cadvisor/api",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/cache/memory",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/collector",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/container",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/events",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/fs",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/http",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/manager",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/pages",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/storage",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/summary",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/utils",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/validate",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/cadvisor/version",
"Comment": "0.16.0-51-g78419de",
"Rev": "78419de3ea9c2d23cb04ec9d63f8899de34ebd43"
"Comment": "0.16.0-81-g27fb6d5",
"Rev": "27fb6d593c6bffe274718119659815771e79e198"
},
{
"ImportPath": "github.com/google/gofuzz",

View File

@ -372,19 +372,33 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
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)
contMetrics := make(map[string]map[string]map[string][]info.MetricValBasic, 0)
for _, cont := range conts {
metrics := map[string][]info.MetricVal{}
metrics := make(map[string]map[string][]info.MetricValBasic, 0)
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])
if contStat.HasCustomMetrics {
for name, allLabels := range contStat.CustomMetrics {
metricLabels := make(map[string][]info.MetricValBasic, 0)
for _, metric := range allLabels {
if !metric.Timestamp.IsZero() {
metVal := info.MetricValBasic{
Timestamp: metric.Timestamp,
IntValue: metric.IntValue,
FloatValue: metric.FloatValue,
}
labels := metrics[name]
if labels != nil {
values := labels[metric.Label]
values = append(values, metVal)
labels[metric.Label] = values
metrics[name] = labels
} else {
metricLabels[metric.Label] = []info.MetricValBasic{metVal}
metrics[name] = metricLabels
}
}
}
}
}
}

View File

@ -71,12 +71,12 @@ func (cm *GenericCollectorManager) GetSpec() ([]v1.MetricSpec, error) {
return metricSpec, nil
}
func (cm *GenericCollectorManager) Collect() (time.Time, map[string]v1.MetricVal, error) {
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
metrics := map[string]v1.MetricVal{}
metrics := map[string][]v1.MetricVal{}
for _, c := range cm.Collectors {
if c.nextCollectionTime.Before(time.Now()) {
var err error

View File

@ -28,7 +28,7 @@ type fakeCollector struct {
collectedFrom int
}
func (fc *fakeCollector) Collect(metric map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, error) {
func (fc *fakeCollector) Collect(metric map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) {
fc.collectedFrom++
return fc.nextCollectionTime, metric, fc.err
}

View File

@ -48,3 +48,14 @@ type MetricConfig struct {
//the regular expression that can be used to extract the metric
Regex string `json:"regex"`
}
type Prometheus struct {
//the endpoint to hit to scrape metrics
Endpoint string `json:"endpoint"`
//the frequency at which metrics should be collected
PollingFrequency time.Duration `json:"polling_frequency"`
//holds names of different metrics that can be collected
MetricsConfig []string `json:"metrics_config"`
}

View File

@ -0,0 +1,6 @@
{
"endpoint" : "http://localhost:8080/metrics",
"polling_frequency" : 10,
"metrics_config" : [
]
}

View File

@ -31,7 +31,7 @@ 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) {
func (fkm *FakeCollectorManager) Collect(metric map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) {
var zero time.Time
return zero, metric, nil
}

View File

@ -116,7 +116,7 @@ func (collector *GenericCollector) GetSpec() []v1.MetricSpec {
}
//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) {
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))
@ -142,16 +142,16 @@ func (collector *GenericCollector) Collect(metrics map[string]v1.MetricVal) (tim
if err != nil {
errorSlice = append(errorSlice, err)
}
metrics[metricConfig.Name] = v1.MetricVal{
FloatValue: regVal, Timestamp: currentTime,
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,
metrics[metricConfig.Name] = []v1.MetricVal{
{IntValue: regVal, Timestamp: currentTime},
}
} else {

View File

@ -148,20 +148,20 @@ func TestMetricCollection(t *testing.T) {
defer tempServer.Close()
fakeCollector.configFile.Endpoint = tempServer.URL
metrics := map[string]v1.MetricVal{}
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)
assert.Equal(metrics[metricNames[0]][0].IntValue, 3)
assert.Equal(metrics[metricNames[0]][0].FloatValue, 0)
// reading = 0
assert.Equal(metrics[metricNames[1]].IntValue, 0)
assert.Equal(metrics[metricNames[1]].FloatValue, 0)
assert.Equal(metrics[metricNames[1]][0].IntValue, 0)
assert.Equal(metrics[metricNames[1]][0].FloatValue, 0)
// writing = 1
assert.Equal(metrics[metricNames[2]].IntValue, 1)
assert.Equal(metrics[metricNames[2]].FloatValue, 0)
assert.Equal(metrics[metricNames[2]][0].IntValue, 1)
assert.Equal(metrics[metricNames[2]][0].FloatValue, 0)
// waiting = 2
assert.Equal(metrics[metricNames[3]].IntValue, 2)
assert.Equal(metrics[metricNames[3]].FloatValue, 0)
assert.Equal(metrics[metricNames[3]][0].IntValue, 2)
assert.Equal(metrics[metricNames[3]][0].FloatValue, 0)
}

View File

@ -0,0 +1,170 @@
// 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"
"io/ioutil"
"math"
"net/http"
"strconv"
"strings"
"time"
"github.com/google/cadvisor/info/v1"
)
type PrometheusCollector struct {
//name of the collector
name string
//rate at which metrics are collected
pollingFrequency time.Duration
//holds information extracted from the config file for a collector
configFile Prometheus
}
//Returns a new collector using the information extracted from the configfile
func NewPrometheusCollector(collectorName string, configFile []byte) (*PrometheusCollector, error) {
var configInJSON Prometheus
err := json.Unmarshal(configFile, &configInJSON)
if err != nil {
return nil, err
}
minPollingFrequency := configInJSON.PollingFrequency
// Minimum supported frequency is 1s
minSupportedFrequency := 1 * time.Second
if minPollingFrequency < minSupportedFrequency {
minPollingFrequency = minSupportedFrequency
}
//TODO : Add checks for validity of config file (eg : Accurate JSON fields)
return &PrometheusCollector{
name: collectorName,
pollingFrequency: minPollingFrequency,
configFile: configInJSON,
}, nil
}
//Returns name of the collector
func (collector *PrometheusCollector) Name() string {
return collector.name
}
func getMetricData(line string) string {
fields := strings.Fields(line)
data := fields[3]
if len(fields) > 4 {
for i := range fields {
if i > 3 {
data = data + "_" + fields[i]
}
}
}
return strings.TrimSpace(data)
}
func (collector *PrometheusCollector) GetSpec() []v1.MetricSpec {
specs := []v1.MetricSpec{}
response, err := http.Get(collector.configFile.Endpoint)
if err != nil {
return specs
}
defer response.Body.Close()
pageContent, err := ioutil.ReadAll(response.Body)
if err != nil {
return specs
}
lines := strings.Split(string(pageContent), "\n")
for i, line := range lines {
if strings.HasPrefix(line, "# HELP") {
stopIndex := strings.Index(lines[i+2], "{")
if stopIndex == -1 {
stopIndex = strings.Index(lines[i+2], " ")
}
spec := v1.MetricSpec{
Name: strings.TrimSpace(lines[i+2][0:stopIndex]),
Type: v1.MetricType(getMetricData(lines[i+1])),
Format: "float",
Units: getMetricData(lines[i]),
}
specs = append(specs, spec)
}
}
return specs
}
//Returns collected metrics and the next collection time of the collector
func (collector *PrometheusCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) {
currentTime := time.Now()
nextCollectionTime := currentTime.Add(time.Duration(collector.pollingFrequency))
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
lines := strings.Split(string(pageContent), "\n")
for _, line := range lines {
if line == "" {
break
}
if !strings.HasPrefix(line, "# HELP") && !strings.HasPrefix(line, "# TYPE") {
var metLabel string
startLabelIndex := strings.Index(line, "{")
spaceIndex := strings.Index(line, " ")
if startLabelIndex == -1 {
startLabelIndex = spaceIndex
}
metName := strings.TrimSpace(line[0:startLabelIndex])
if startLabelIndex+1 <= spaceIndex-1 {
metLabel = strings.TrimSpace(line[(startLabelIndex + 1):(spaceIndex - 1)])
}
metVal, err := strconv.ParseFloat(line[spaceIndex+1:], 64)
if err != nil {
errorSlice = append(errorSlice, err)
}
if math.IsNaN(metVal) {
metVal = 0
}
metric := v1.MetricVal{
Label: metLabel,
FloatValue: metVal,
Timestamp: currentTime,
}
metrics[metName] = append(metrics[metName], metric)
}
}
return nextCollectionTime, metrics, compileErrors(errorSlice)
}

View File

@ -0,0 +1,64 @@
// 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"
"testing"
"github.com/google/cadvisor/info/v1"
"github.com/stretchr/testify/assert"
)
func TestPrometheus(t *testing.T) {
assert := assert.New(t)
//Create a prometheus collector using the config file 'sample_config_prometheus.json'
configFile, err := ioutil.ReadFile("config/sample_config_prometheus.json")
collector, err := NewPrometheusCollector("Prometheus", configFile)
assert.NoError(err)
assert.Equal(collector.name, "Prometheus")
assert.Equal(collector.configFile.Endpoint, "http://localhost:8080/metrics")
tempServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
text := "# HELP go_gc_duration_seconds A summary of the GC invocation durations.\n"
text += "# TYPE go_gc_duration_seconds summary\n"
text += "go_gc_duration_seconds{quantile=\"0\"} 5.8348000000000004e-05\n"
text += "go_gc_duration_seconds{quantile=\"1\"} 0.000499764\n"
text += "# HELP go_goroutines Number of goroutines that currently exist.\n"
text += "# TYPE go_goroutines gauge\n"
text += "go_goroutines 16"
fmt.Fprintln(w, text)
}))
defer tempServer.Close()
collector.configFile.Endpoint = tempServer.URL
metrics := map[string][]v1.MetricVal{}
_, metrics, errMetric := collector.Collect(metrics)
assert.NoError(errMetric)
go_gc_duration := metrics["go_gc_duration_seconds"]
assert.Equal(go_gc_duration[0].FloatValue, 5.8348000000000004e-05)
assert.Equal(go_gc_duration[1].FloatValue, 0.000499764)
goRoutines := metrics["go_goroutines"]
assert.Equal(goRoutines[0].FloatValue, 16)
}

View File

@ -27,7 +27,7 @@ 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(map[string]v1.MetricVal) (time.Time, map[string]v1.MetricVal, 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
@ -45,7 +45,7 @@ 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, map[string]v1.MetricVal, error)
Collect() (time.Time, map[string][]v1.MetricVal, error)
// Get metric spec from all registered collectors.
GetSpec() ([]v1.MetricSpec, error)

View File

@ -167,7 +167,16 @@ func libcontainerConfigToContainerSpec(config *libcontainerConfigs.Config, mi *i
}
spec.Cpu.Mask = utils.FixCpuMask(config.Cgroups.CpusetCpus, mi.NumCores)
spec.HasNetwork = len(config.Networks) > 0
// Docker reports a loop device for containers with --net=host. Ignore
// those too.
networkCount := 0
for _, n := range config.Networks {
if n.Type != "loopback" {
networkCount += 1
}
}
spec.HasNetwork = networkCount > 0
spec.HasDiskIo = true
return spec

View File

@ -427,7 +427,7 @@ type ContainerStats struct {
TaskStats LoadStats `json:"task_stats,omitempty"`
//Custom metrics from all collectors
CustomMetrics map[string]MetricVal `json:"custom_metrics,omitempty"`
CustomMetrics map[string][]MetricVal `json:"custom_metrics,omitempty"`
}
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {

View File

@ -56,7 +56,20 @@ type MetricSpec struct {
}
// An exported metric.
type MetricVal struct {
type MetricValBasic struct {
// Time at which the metric was queried
Timestamp time.Time `json:"timestamp"`
// The value of the metric at this point.
IntValue int64 `json:"int_value,omitempty"`
FloatValue float64 `json:"float_value,omitempty"`
}
// An exported metric.
type MetricVal struct {
// Label associated with a metric
Label string `json:"label,omitempty"`
// Time at which the metric was queried
Timestamp time.Time `json:"timestamp"`

View File

@ -104,8 +104,8 @@ type ContainerStats struct {
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"`
HasCustomMetrics bool `json:"has_custom_metrics"`
CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"`
}
type Percentiles struct {

View File

@ -42,7 +42,7 @@ import (
// Housekeeping interval.
var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, "Interval between container housekeepings")
var cgroupPathRegExp = regexp.MustCompile(".*:devices:(.*?),.*")
var cgroupPathRegExp = regexp.MustCompile(".*devices:(.*?)[,;$].*")
// Decay value used for load average smoothing. Interval length of 10 seconds is used.
var loadDecay = math.Exp(float64(-1 * (*HousekeepingInterval).Seconds() / 10))
@ -416,6 +416,8 @@ func (c *containerData) housekeeping() {
// Schedule the next housekeeping. Sleep until that time.
if time.Now().Before(next) {
time.Sleep(next.Sub(time.Now()))
} else {
next = time.Now()
}
lastHousekeeping = next
}
@ -529,7 +531,7 @@ func (c *containerData) updateStats() error {
return customStatsErr
}
func (c *containerData) updateCustomStats() (map[string]info.MetricVal, error) {
func (c *containerData) updateCustomStats() (map[string][]info.MetricVal, error) {
_, customStats, customStatsErr := c.collectorManager.Collect()
if customStatsErr != nil {
if !c.handler.Exists() {

View File

@ -161,8 +161,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
if err != nil {
return nil, err
}
newManager.versionInfo = *versionInfo
glog.Infof("Version: %+v", newManager.versionInfo)
glog.Infof("Version: %+v", *versionInfo)
newManager.eventHandler = events.NewEventManager(parseEventsStoragePolicy())
return newManager, nil
@ -183,7 +182,6 @@ type manager struct {
memoryCache *memory.InMemoryCache
fsInfo fs.FsInfo
machineInfo info.MachineInfo
versionInfo info.VersionInfo
quitChannels []chan error
cadvisorContainer string
inHostNamespace bool
@ -221,7 +219,7 @@ func (self *manager) Start() error {
} else {
err = cpuLoadReader.Start()
if err != nil {
glog.Warning("Could not start cpu load stat collector: %s", err)
glog.Warningf("Could not start cpu load stat collector: %s", err)
} else {
self.loadReader = cpuLoadReader
}
@ -658,7 +656,11 @@ func (m *manager) GetMachineInfo() (*info.MachineInfo, error) {
}
func (m *manager) GetVersionInfo() (*info.VersionInfo, error) {
return &m.versionInfo, nil
// TODO: Consider caching this and periodically updating. The VersionInfo may change if
// the docker daemon is started after the cAdvisor client is created. Caching the value
// would be helpful so we would be able to return the last known docker version if
// docker was down at the time of a query.
return getVersionInfo()
}
func (m *manager) Exists(containerName string) bool {
@ -705,15 +707,28 @@ func (m *manager) registerCollectors(collectorConfigs map[string]string, cont *c
}
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
if strings.HasPrefix(k, "prometheus") || strings.HasPrefix(k, "Prometheus") {
newCollector, err := collector.NewPrometheusCollector(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
}
} else {
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
@ -1136,8 +1151,12 @@ func (m *manager) DockerInfo() (DockerStatus, error) {
if err != nil {
return DockerStatus{}, err
}
versionInfo, err := m.GetVersionInfo()
if err != nil {
return DockerStatus{}, err
}
out := DockerStatus{}
out.Version = m.versionInfo.DockerVersion
out.Version = versionInfo.DockerVersion
if val, ok := info["KernelVersion"]; ok {
out.KernelVersion = val
}

View File

@ -392,7 +392,7 @@ func (c *PrometheusCollector) Collect(ch chan<- prometheus.Metric) {
containers, err := c.infoProvider.SubcontainersInfo("/", &info.ContainerInfoRequest{NumStats: 1})
if err != nil {
c.errors.Set(1)
glog.Warning("Couldn't get containers: %s", err)
glog.Warningf("Couldn't get containers: %s", err)
return
}
for _, container := range containers {

View File

@ -231,20 +231,21 @@ func serveContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) e
}
data := &pageData{
DisplayName: displayName,
ContainerName: escapeContainerName(cont.Name),
ParentContainers: parentContainers,
Subcontainers: subcontainerLinks,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
IsRoot: cont.Name == "/",
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork || cont.Spec.HasFilesystem,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
Root: rootDir,
DisplayName: displayName,
ContainerName: escapeContainerName(cont.Name),
ParentContainers: parentContainers,
Subcontainers: subcontainerLinks,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
IsRoot: cont.Name == "/",
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork || cont.Spec.HasFilesystem,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
CustomMetricsAvailable: cont.Spec.HasCustomMetrics,
Root: rootDir,
}
err = pageTemplate.Execute(w, data)
if err != nil {

View File

@ -221,6 +221,16 @@ const containersHtmlTemplate = `
</div>
</div>
{{end}}
{{if .CustomMetricsAvailable}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Application Metrics</h3>
</div>
<div class="panel-body">
<div id="custom-metrics-chart"></div>
</div>
</div>
{{end}}
</div>
{{end}}
</div>

View File

@ -129,18 +129,19 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error
return err
}
data = &pageData{
DisplayName: displayName,
ContainerName: escapeContainerName(cont.Name),
ParentContainers: parentContainers,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
Root: rootDir,
DisplayName: displayName,
ContainerName: escapeContainerName(cont.Name),
ParentContainers: parentContainers,
Spec: cont.Spec,
Stats: cont.Stats,
MachineInfo: machineInfo,
ResourcesAvailable: cont.Spec.HasCpu || cont.Spec.HasMemory || cont.Spec.HasNetwork,
CpuAvailable: cont.Spec.HasCpu,
MemoryAvailable: cont.Spec.HasMemory,
NetworkAvailable: cont.Spec.HasNetwork,
FsAvailable: cont.Spec.HasFilesystem,
CustomMetricsAvailable: cont.Spec.HasCustomMetrics,
Root: rootDir,
}
}

View File

@ -44,23 +44,24 @@ type keyVal struct {
}
type pageData struct {
DisplayName string
ContainerName string
ParentContainers []link
Subcontainers []link
Spec info.ContainerSpec
Stats []*info.ContainerStats
MachineInfo *info.MachineInfo
IsRoot bool
ResourcesAvailable bool
CpuAvailable bool
MemoryAvailable bool
NetworkAvailable bool
FsAvailable bool
Root string
DockerStatus []keyVal
DockerDriverStatus []keyVal
DockerImages []manager.DockerImage
DisplayName string
ContainerName string
ParentContainers []link
Subcontainers []link
Spec info.ContainerSpec
Stats []*info.ContainerStats
MachineInfo *info.MachineInfo
IsRoot bool
ResourcesAvailable bool
CpuAvailable bool
MemoryAvailable bool
NetworkAvailable bool
FsAvailable bool
CustomMetricsAvailable bool
Root string
DockerStatus []keyVal
DockerDriverStatus []keyVal
DockerImages []manager.DockerImage
}
func init() {

View File

@ -645,6 +645,15 @@ function drawCharts(machineInfo, containerInfo) {
});
}
// Custom Metrics
if (containerInfo.spec.has_custom_metrics) {
steps.push(function() {
getCustomMetrics(window.cadvisor.rootDir, window.cadvisor.containerName, function(metricsInfo) {
drawCustomMetrics("custom-metrics-chart", containerInfo, metricsInfo)
});
});
}
stepExecute(steps);
}
@ -696,12 +705,144 @@ function refreshStats() {
}
if (containerInfo.spec.has_network) {
startNetwork("network-selection", containerInfo);
}
if (containerInfo.spec.has_custom_metrics) {
startCustomMetrics("custom-metrics-chart", containerInfo);
}
}
drawCharts(machineInfo, containerInfo);
});
}
function addAllLabels(containerInfo, metricsInfo) {
if (metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i=0; i<metricSpec.length; i++) {
metricName = metricSpec[i].name;
metricLabelVal = container[metricName];
firstLabel = true;
for (var label in metricLabelVal) {
if (label == "") {
$('#button-'+metricName).hide();
}
$("#"+metricName+"_labels").append($("<li>")
.attr("role", "presentation")
.append($("<a>")
.attr("role", "menuitem")
.click(setLabel.bind(null, metricName, label))
.text(label)));
if (firstLabel) {
firstLabel = false;
setLabel(metricName, label);
}
}
}
}
}
function getMetricIndex(metricName) {
for (i = 0; i<window.cadvisor.metricLabelPair.length; ++i) {
if (window.cadvisor.metricLabelPair[i][0] == metricName)
return i;
}
return -1;
}
function setLabel(metric, label) {
$("#"+metric+"-selection-text")
.empty()
.append($("<span>").text("Label: "))
.append($("<b>").text(label))
index = getMetricIndex(metric);
if (index == -1) {
window.cadvisor.metricLabelPair.push([metric, label]);
} else {
window.cadvisor.metricLabelPair[index][1] = label;
}
refreshStats();
}
function getSelectedLabel(metricName) {
index = getMetricIndex(metricName);
if (index == -1)
return "";
return window.cadvisor.metricLabelPair[index][1];
}
function startCustomMetrics(elementId, containerInfo) {
var metricSpec = containerInfo.spec.custom_metrics;
var metricStats = containerInfo.stats.custom_metrics;
var el=$("<div>");
if (metricSpec.length < window.cadvisor.maxCustomMetrics)
window.cadvisor.maxCustomMetrics = metricSpec.length
for (i = 0; i<window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
var divText = "<div class='dropdown'> <button class='btn btn-default dropdown-toggle' type='button' id='button-"+metricName;
divText += "' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>";
divText += "<span id='"+metricName+"-selection-text'></span> <span class='caret'></span> </button>";
divText += "<ul id='"+metricName+"_labels' class='dropdown-menu' role='menu' aria-labelledby='button-"+metricName+ "'> </ul> </div>";
divText += "<div id='"+elementId+"-"+metricName+"'> </div>";
el.append($(divText));
}
el.append($("</div>"));
$("#"+elementId).append(el);
}
function getCustomMetrics(rootDir, containerName, callback) {
$.getJSON(rootDir + "api/v2.0/appmetrics/" + containerName)
.done(function(data) {
callback(data);
})
.fail(function(jqhxr, textStatus, error) {
callback([]);
});
}
function drawCustomMetrics(elementId, containerInfo, metricsInfo) {
if(metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i=0; i<window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
metricUnits = metricSpec[i].units;
var titles = ["Time", metricName];
metricLabelVal = container[metricName];
if (window.cadvisor.firstCustomCollection) {
window.cadvisor.firstCustomCollection = false;
addAllLabels(containerInfo, metricsInfo);
}
var data = [];
selectedLabel = getSelectedLabel(metricName);
metricVal = metricLabelVal[selectedLabel];
for (var index in metricVal) {
metric = metricVal[index];
var elements = [];
for (var attribute in metric) {
value = metric[attribute];
elements.push(value);
}
if (elements.length<2) {
elements.push(0);
}
data.push(elements);
}
drawLineChart(titles, data, elementId+"-"+metricName, metricUnits);
}
}
}
// Executed when the page finishes loading.
function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
// Don't fetch data if we don't have any resource.
@ -715,6 +856,10 @@ function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
window.cadvisor.rootDir = rootDir;
window.cadvisor.containerName = containerName;
window.cadvisor.firstCustomCollection = true;
window.cadvisor.metricLabelPair = [];
window.cadvisor.maxCustomMetrics = 10;
// Draw process information at start and refresh every 60s.
getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(isRoot, rootDir, processInfo)

View File

@ -0,0 +1,116 @@
// 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 stdout
import (
"bytes"
"fmt"
info "github.com/google/cadvisor/info/v1"
)
type stdoutStorage struct {
Namespace string
}
const (
colCpuCumulativeUsage = "cpu_cumulative_usage"
// Memory Usage
colMemoryUsage = "memory_usage"
// Working set size
colMemoryWorkingSet = "memory_working_set"
// Cumulative count of bytes received.
colRxBytes = "rx_bytes"
// Cumulative count of receive errors encountered.
colRxErrors = "rx_errors"
// Cumulative count of bytes transmitted.
colTxBytes = "tx_bytes"
// Cumulative count of transmit errors encountered.
colTxErrors = "tx_errors"
// Filesystem summary
colFsSummary = "fs_summary"
// Filesystem limit.
colFsLimit = "fs_limit"
// Filesystem usage.
colFsUsage = "fs_usage"
)
func (driver *stdoutStorage) 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 (driver *stdoutStorage) 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
}
}
func (driver *stdoutStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
if stats == nil {
return nil
}
containerName := ref.Name
if len(ref.Aliases) > 0 {
containerName = ref.Aliases[0]
}
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("cName=%s host=%s", containerName, driver.Namespace))
series := driver.containerStatsToValues(stats)
driver.containerFsStatsToValues(&series, stats)
for key, value := range series {
buffer.WriteString(fmt.Sprintf(" %s=%v", key, value))
}
_, err := fmt.Println(buffer.String())
return err
}
func (driver *stdoutStorage) Close() error {
return nil
}
func New(namespace string) (*stdoutStorage, error) {
stdoutStorage := &stdoutStorage{
Namespace: namespace,
}
return stdoutStorage, nil
}

View File

@ -17,8 +17,8 @@ package cloudinfo
import (
"strings"
"github.com/GoogleCloudPlatform/gcloud-golang/compute/metadata"
info "github.com/google/cadvisor/info/v1"
"google.golang.org/cloud/compute/metadata"
)
func onGCE() bool {