github.com/google/cadvisor@v0.49.1/collector/prometheus_collector.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package collector 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "io" 22 "net/http" 23 "sort" 24 "time" 25 26 rawmodel "github.com/prometheus/client_model/go" 27 "github.com/prometheus/common/expfmt" 28 "github.com/prometheus/common/model" 29 30 "github.com/google/cadvisor/container" 31 v1 "github.com/google/cadvisor/info/v1" 32 ) 33 34 type PrometheusCollector struct { 35 // name of the collector 36 name string 37 38 // rate at which metrics are collected 39 pollingFrequency time.Duration 40 41 // holds information extracted from the config file for a collector 42 configFile Prometheus 43 44 // the metrics to gather (uses a map as a set) 45 metricsSet map[string]bool 46 47 // Limit for the number of scaped metrics. If the count is higher, 48 // no metrics will be returned. 49 metricCountLimit int 50 51 // The Http client to use when connecting to metric endpoints 52 httpClient *http.Client 53 } 54 55 // Returns a new collector using the information extracted from the configfile 56 func NewPrometheusCollector(collectorName string, configFile []byte, metricCountLimit int, containerHandler container.ContainerHandler, httpClient *http.Client) (*PrometheusCollector, error) { 57 var configInJSON Prometheus 58 err := json.Unmarshal(configFile, &configInJSON) 59 if err != nil { 60 return nil, err 61 } 62 63 configInJSON.Endpoint.configure(containerHandler) 64 65 minPollingFrequency := configInJSON.PollingFrequency 66 67 // Minimum supported frequency is 1s 68 minSupportedFrequency := 1 * time.Second 69 70 if minPollingFrequency < minSupportedFrequency { 71 minPollingFrequency = minSupportedFrequency 72 } 73 74 if metricCountLimit < 0 { 75 return nil, fmt.Errorf("Metric count limit must be greater than or equal to 0") 76 } 77 78 var metricsSet map[string]bool 79 if len(configInJSON.MetricsConfig) > 0 { 80 metricsSet = make(map[string]bool, len(configInJSON.MetricsConfig)) 81 for _, name := range configInJSON.MetricsConfig { 82 metricsSet[name] = true 83 } 84 } 85 86 if len(configInJSON.MetricsConfig) > metricCountLimit { 87 return nil, fmt.Errorf("Too many metrics defined: %d limit %d", len(configInJSON.MetricsConfig), metricCountLimit) 88 } 89 90 // TODO : Add checks for validity of config file (eg : Accurate JSON fields) 91 return &PrometheusCollector{ 92 name: collectorName, 93 pollingFrequency: minPollingFrequency, 94 configFile: configInJSON, 95 metricsSet: metricsSet, 96 metricCountLimit: metricCountLimit, 97 httpClient: httpClient, 98 }, nil 99 } 100 101 // Returns name of the collector 102 func (collector *PrometheusCollector) Name() string { 103 return collector.name 104 } 105 106 func (collector *PrometheusCollector) GetSpec() []v1.MetricSpec { 107 108 response, err := collector.httpClient.Get(collector.configFile.Endpoint.URL) 109 if err != nil { 110 return nil 111 } 112 defer response.Body.Close() 113 114 if response.StatusCode != http.StatusOK { 115 return nil 116 } 117 118 dec := expfmt.NewDecoder(response.Body, expfmt.ResponseFormat(response.Header)) 119 120 var specs []v1.MetricSpec 121 122 for { 123 d := rawmodel.MetricFamily{} 124 if err = dec.Decode(&d); err != nil { 125 break 126 } 127 name := d.GetName() 128 if len(name) == 0 { 129 continue 130 } 131 // If metrics to collect is specified, skip any metrics not in the list to collect. 132 if _, ok := collector.metricsSet[name]; collector.metricsSet != nil && !ok { 133 continue 134 } 135 136 spec := v1.MetricSpec{ 137 Name: name, 138 Type: metricType(d.GetType()), 139 Format: v1.FloatType, 140 } 141 specs = append(specs, spec) 142 } 143 144 if err != nil && err != io.EOF { 145 return nil 146 } 147 148 return specs 149 } 150 151 // metricType converts Prometheus metric type to cadvisor metric type. 152 // If there is no mapping then just return the name of the Prometheus metric type. 153 func metricType(t rawmodel.MetricType) v1.MetricType { 154 switch t { 155 case rawmodel.MetricType_COUNTER: 156 return v1.MetricCumulative 157 case rawmodel.MetricType_GAUGE: 158 return v1.MetricGauge 159 default: 160 return v1.MetricType(t.String()) 161 } 162 } 163 164 type prometheusLabels []*rawmodel.LabelPair 165 166 func labelSetToLabelPairs(labels model.Metric) prometheusLabels { 167 var promLabels prometheusLabels 168 for k, v := range labels { 169 name := string(k) 170 value := string(v) 171 promLabels = append(promLabels, &rawmodel.LabelPair{Name: &name, Value: &value}) 172 } 173 return promLabels 174 } 175 176 func (s prometheusLabels) Len() int { return len(s) } 177 func (s prometheusLabels) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 178 179 // ByName implements sort.Interface by providing Less and using the Len and 180 // Swap methods of the embedded PrometheusLabels value. 181 type byName struct{ prometheusLabels } 182 183 func (s byName) Less(i, j int) bool { 184 return s.prometheusLabels[i].GetName() < s.prometheusLabels[j].GetName() 185 } 186 187 func prometheusLabelSetToCadvisorLabels(promLabels model.Metric) map[string]string { 188 labels := make(map[string]string) 189 for k, v := range promLabels { 190 if string(k) == "__name__" { 191 continue 192 } 193 labels[string(k)] = string(v) 194 } 195 return labels 196 } 197 198 func prometheusLabelSetToCadvisorLabel(promLabels model.Metric) string { 199 labels := labelSetToLabelPairs(promLabels) 200 sort.Sort(byName{labels}) 201 var b bytes.Buffer 202 203 for i, l := range labels { 204 if i > 0 { 205 b.WriteString("\xff") 206 } 207 b.WriteString(l.GetName()) 208 b.WriteString("=") 209 b.WriteString(l.GetValue()) 210 } 211 212 return b.String() 213 } 214 215 // Returns collected metrics and the next collection time of the collector 216 func (collector *PrometheusCollector) Collect(metrics map[string][]v1.MetricVal) (time.Time, map[string][]v1.MetricVal, error) { 217 currentTime := time.Now() 218 nextCollectionTime := currentTime.Add(time.Duration(collector.pollingFrequency)) 219 220 uri := collector.configFile.Endpoint.URL 221 response, err := collector.httpClient.Get(uri) 222 if err != nil { 223 return nextCollectionTime, nil, err 224 } 225 defer response.Body.Close() 226 227 if response.StatusCode != http.StatusOK { 228 return nextCollectionTime, nil, fmt.Errorf("server returned HTTP status %s", response.Status) 229 } 230 231 sdec := expfmt.SampleDecoder{ 232 Dec: expfmt.NewDecoder(response.Body, expfmt.ResponseFormat(response.Header)), 233 Opts: &expfmt.DecodeOptions{ 234 Timestamp: model.TimeFromUnixNano(currentTime.UnixNano()), 235 }, 236 } 237 238 var ( 239 // 50 is chosen as a reasonable guesstimate at a number of metrics we can 240 // expect from virtually any endpoint to try to save allocations. 241 decSamples = make(model.Vector, 0, 50) 242 newMetrics = make(map[string][]v1.MetricVal) 243 ) 244 for { 245 if err = sdec.Decode(&decSamples); err != nil { 246 break 247 } 248 249 for _, sample := range decSamples { 250 metName := string(sample.Metric[model.MetricNameLabel]) 251 if len(metName) == 0 { 252 continue 253 } 254 // If metrics to collect is specified, skip any metrics not in the list to collect. 255 if _, ok := collector.metricsSet[metName]; collector.metricsSet != nil && !ok { 256 continue 257 } 258 // TODO Handle multiple labels nicer. Prometheus metrics can have multiple 259 // labels, cadvisor only accepts a single string for the metric label. 260 label := prometheusLabelSetToCadvisorLabel(sample.Metric) 261 labels := prometheusLabelSetToCadvisorLabels(sample.Metric) 262 263 metric := v1.MetricVal{ 264 FloatValue: float64(sample.Value), 265 Timestamp: sample.Timestamp.Time(), 266 Label: label, 267 Labels: labels, 268 } 269 newMetrics[metName] = append(newMetrics[metName], metric) 270 if len(newMetrics) > collector.metricCountLimit { 271 return nextCollectionTime, nil, fmt.Errorf("too many metrics to collect") 272 } 273 } 274 decSamples = decSamples[:0] 275 } 276 277 if err != nil && err != io.EOF { 278 return nextCollectionTime, nil, err 279 } 280 281 for key, val := range newMetrics { 282 metrics[key] = append(metrics[key], val...) 283 } 284 285 return nextCollectionTime, metrics, nil 286 }