15
15
package collector
16
16
17
17
import (
18
+ "bytes"
18
19
"encoding/json"
19
20
"fmt"
20
- "io/ioutil"
21
- "math"
21
+ "io"
22
22
"net/http"
23
- "strconv"
24
- "strings"
23
+ "sort"
25
24
"time"
26
25
26
+ rawmodel "github.com/prometheus/client_model/go"
27
+ "github.com/prometheus/common/expfmt"
28
+ "github.com/prometheus/common/model"
29
+
27
30
"github.com/google/cadvisor/container"
28
31
"github.com/google/cadvisor/info/v1"
29
32
)
@@ -100,62 +103,104 @@ func (collector *PrometheusCollector) Name() string {
100
103
return collector .name
101
104
}
102
105
103
- func getMetricData (line string ) string {
104
- fields := strings .Fields (line )
105
- data := fields [3 ]
106
- if len (fields ) > 4 {
107
- for i := range fields {
108
- if i > 3 {
109
- data = data + "_" + fields [i ]
110
- }
111
- }
112
- }
113
- return strings .TrimSpace (data )
114
- }
115
-
116
106
func (collector * PrometheusCollector ) GetSpec () []v1.MetricSpec {
117
- specs := []v1.MetricSpec {}
118
107
119
108
response , err := collector .httpClient .Get (collector .configFile .Endpoint .URL )
120
109
if err != nil {
121
- return specs
110
+ return nil
122
111
}
123
112
defer response .Body .Close ()
124
113
125
- pageContent , err := ioutil .ReadAll (response .Body )
126
- if err != nil {
127
- return specs
114
+ if response .StatusCode != http .StatusOK {
115
+ return nil
128
116
}
129
117
130
- lines := strings .Split (string (pageContent ), "\n " )
131
- lineCount := len (lines )
132
- for i , line := range lines {
133
- if strings .HasPrefix (line , "# HELP" ) {
134
- if i + 2 >= lineCount {
135
- break
136
- }
118
+ dec := expfmt .NewDecoder (response .Body , expfmt .ResponseFormat (response .Header ))
137
119
138
- stopIndex := strings .IndexAny (lines [i + 2 ], "{ " )
139
- if stopIndex == - 1 {
140
- continue
141
- }
120
+ var specs []v1.MetricSpec
142
121
143
- name := strings .TrimSpace (lines [i + 2 ][0 :stopIndex ])
144
- if _ , ok := collector .metricsSet [name ]; collector .metricsSet != nil && ! ok {
145
- continue
146
- }
147
- spec := v1.MetricSpec {
148
- Name : name ,
149
- Type : v1 .MetricType (getMetricData (lines [i + 1 ])),
150
- Format : "float" ,
151
- Units : getMetricData (lines [i ]),
152
- }
153
- specs = append (specs , spec )
122
+ for {
123
+ d := rawmodel.MetricFamily {}
124
+ if err = dec .Decode (& d ); err != nil {
125
+ break
154
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 )
155
142
}
143
+
144
+ if err != nil && err != io .EOF {
145
+ return nil
146
+ }
147
+
156
148
return specs
157
149
}
158
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 prometheusLabelSetToCadvisorLabel (promLabels model.Metric ) string {
188
+ labels := labelSetToLabelPairs (promLabels )
189
+ sort .Sort (byName {labels })
190
+ var b bytes.Buffer
191
+
192
+ for i , l := range labels {
193
+ if i > 0 {
194
+ b .WriteString ("\xff " )
195
+ }
196
+ b .WriteString (l .GetName ())
197
+ b .WriteString ("=" )
198
+ b .WriteString (l .GetValue ())
199
+ }
200
+
201
+ return string (b .Bytes ())
202
+ }
203
+
159
204
//Returns collected metrics and the next collection time of the collector
160
205
func (collector * PrometheusCollector ) Collect (metrics map [string ][]v1.MetricVal ) (time.Time , map [string ][]v1.MetricVal , error ) {
161
206
currentTime := time .Now ()
@@ -168,59 +213,61 @@ func (collector *PrometheusCollector) Collect(metrics map[string][]v1.MetricVal)
168
213
}
169
214
defer response .Body .Close ()
170
215
171
- pageContent , err := ioutil .ReadAll (response .Body )
172
- if err != nil {
173
- return nextCollectionTime , nil , err
216
+ if response .StatusCode != http .StatusOK {
217
+ return nextCollectionTime , nil , fmt .Errorf ("server returned HTTP status %s" , response .Status )
174
218
}
175
219
176
- var errorSlice []error
177
- lines := strings .Split (string (pageContent ), "\n " )
178
-
179
- newMetrics := make (map [string ][]v1.MetricVal )
220
+ sdec := expfmt.SampleDecoder {
221
+ Dec : expfmt .NewDecoder (response .Body , expfmt .ResponseFormat (response .Header )),
222
+ Opts : & expfmt.DecodeOptions {
223
+ Timestamp : model .TimeFromUnixNano (currentTime .UnixNano ()),
224
+ },
225
+ }
180
226
181
- for _ , line := range lines {
182
- if line == "" {
227
+ var (
228
+ // 50 is chosen as a reasonable guesstimate at a number of metrics we can
229
+ // expect from virtually any endpoint to try to save allocations.
230
+ decSamples = make (model.Vector , 0 , 50 )
231
+ newMetrics = make (map [string ][]v1.MetricVal )
232
+ )
233
+ for {
234
+ if err = sdec .Decode (& decSamples ); err != nil {
183
235
break
184
236
}
185
- if ! strings .HasPrefix (line , "# HELP" ) && ! strings .HasPrefix (line , "# TYPE" ) {
186
- var metLabel string
187
- startLabelIndex := strings .Index (line , "{" )
188
- spaceIndex := strings .Index (line , " " )
189
- if startLabelIndex == - 1 {
190
- startLabelIndex = spaceIndex
191
- }
192
237
193
- metName := strings .TrimSpace (line [0 :startLabelIndex ])
194
- if _ , ok := collector .metricsSet [metName ]; collector .metricsSet != nil && ! ok {
238
+ for _ , sample := range decSamples {
239
+ metName := string (sample .Metric [model .MetricNameLabel ])
240
+ if len (metName ) == 0 {
195
241
continue
196
242
}
197
-
198
- if startLabelIndex + 1 <= spaceIndex - 1 {
199
- metLabel = strings .TrimSpace (line [(startLabelIndex + 1 ):(spaceIndex - 1 )])
200
- }
201
-
202
- metVal , err := strconv .ParseFloat (line [spaceIndex + 1 :], 64 )
203
- if err != nil {
204
- errorSlice = append (errorSlice , err )
205
- }
206
- if math .IsNaN (metVal ) {
207
- metVal = 0
243
+ // If metrics to collect is specified, skip any metrics not in the list to collect.
244
+ if _ , ok := collector .metricsSet [metName ]; collector .metricsSet != nil && ! ok {
245
+ continue
208
246
}
247
+ // TODO Handle multiple labels nicer. Prometheus metrics can have multiple
248
+ // labels, cadvisor only accepts a single string for the metric label.
249
+ label := prometheusLabelSetToCadvisorLabel (sample .Metric )
209
250
210
251
metric := v1.MetricVal {
211
- Label : metLabel ,
212
- FloatValue : metVal ,
213
- Timestamp : currentTime ,
252
+ FloatValue : float64 ( sample . Value ) ,
253
+ Timestamp : sample . Timestamp . Time () ,
254
+ Label : label ,
214
255
}
215
256
newMetrics [metName ] = append (newMetrics [metName ], metric )
216
257
if len (newMetrics ) > collector .metricCountLimit {
217
258
return nextCollectionTime , nil , fmt .Errorf ("too many metrics to collect" )
218
259
}
219
260
}
261
+ decSamples = decSamples [:0 ]
262
+ }
263
+
264
+ if err != nil && err != io .EOF {
265
+ return nextCollectionTime , nil , err
220
266
}
267
+
221
268
for key , val := range newMetrics {
222
269
metrics [key ] = append (metrics [key ], val ... )
223
270
}
224
271
225
- return nextCollectionTime , metrics , compileErrors ( errorSlice )
272
+ return nextCollectionTime , metrics , nil
226
273
}
0 commit comments