diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 209705f0ade..08f0431dbfb 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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", diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/api/versions.go b/Godeps/_workspace/src/github.com/google/cadvisor/api/versions.go index 8546e358de4..ab5d93eef73 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/api/versions.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/api/versions.go @@ -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 + } + } + } } } } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go index bac016b33b2..e2ccd80d512 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go index 2ffb0087e47..056e85476da 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/collector_manager_test.go @@ -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 } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go index fc1207031ef..8a3234decb6 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/config.go @@ -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"` +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/config/sample_config_prometheus.json b/Godeps/_workspace/src/github.com/google/cadvisor/collector/config/sample_config_prometheus.json new file mode 100644 index 00000000000..cc74bb727d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/config/sample_config_prometheus.json @@ -0,0 +1,6 @@ +{ + "endpoint" : "http://localhost:8080/metrics", + "polling_frequency" : 10, + "metrics_config" : [ + ] +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/fakes.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/fakes.go index 6b11acbb3ea..28606ad051e 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/fakes.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/fakes.go @@ -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 } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go index 6f724cc0230..7150057e241 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go index a1f850de229..04e9ed01f9e 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/generic_collector_test.go @@ -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) } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector.go new file mode 100644 index 00000000000..9253a06d7c1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector_test.go new file mode 100644 index 00000000000..0ead785d468 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/prometheus_collector_test.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/types.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/types.go index 60851bee2b0..50ecb1a82a9 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/types.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/collector/types.go @@ -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) diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go index 2344fc05b53..2ad1f306c1f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go index 1788d5818ff..91512eca296 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/metric.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/metric.go index f1ffd7be472..90fd9e4931a 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/metric.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/metric.go @@ -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"` diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go index e4bda16e13a..74446890cac 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go index e6084801058..0dca1b95273 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go @@ -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() { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go index 751acb97071..df360263016 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go @@ -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 } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/metrics/prometheus.go b/Godeps/_workspace/src/github.com/google/cadvisor/metrics/prometheus.go index 6e8dda66b28..f957c7c9cbb 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/metrics/prometheus.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/metrics/prometheus.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers.go index 394c5d1628c..6eab760ea18 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers.go @@ -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 { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go index abc4ea2898a..4f822bee969 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go @@ -221,6 +221,16 @@ const containersHtmlTemplate = ` {{end}} + {{if .CustomMetricsAvailable}} +