github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/gcm/gcm.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 gcm 16 17 import ( 18 "fmt" 19 "net/url" 20 "sync" 21 "time" 22 23 gce_util "k8s.io/heapster/common/gce" 24 "k8s.io/heapster/metrics/core" 25 26 gce "cloud.google.com/go/compute/metadata" 27 "github.com/golang/glog" 28 "golang.org/x/oauth2" 29 "golang.org/x/oauth2/google" 30 gcm "google.golang.org/api/monitoring/v3" 31 ) 32 33 const ( 34 metricDomain = "kubernetes.io" 35 customApiPrefix = "custom.googleapis.com" 36 maxNumLabels = 10 37 // The largest number of timeseries we can write to per request. 38 maxTimeseriesPerRequest = 200 39 ) 40 41 type MetricFilter int8 42 43 const ( 44 metricsAll MetricFilter = iota 45 metricsOnlyAutoscaling 46 ) 47 48 type gcmSink struct { 49 sync.RWMutex 50 registered bool 51 project string 52 metricFilter MetricFilter 53 gcmService *gcm.Service 54 } 55 56 func (sink *gcmSink) Name() string { 57 return "GCM Sink" 58 } 59 60 func getReq() *gcm.CreateTimeSeriesRequest { 61 return &gcm.CreateTimeSeriesRequest{TimeSeries: make([]*gcm.TimeSeries, 0)} 62 } 63 64 func fullMetricName(project string, name string) string { 65 return fmt.Sprintf("projects/%s/metricDescriptors/%s/%s/%s", project, customApiPrefix, metricDomain, name) 66 } 67 68 func fullMetricType(name string) string { 69 return fmt.Sprintf("%s/%s/%s", customApiPrefix, metricDomain, name) 70 } 71 72 func createTimeSeries(timestamp time.Time, labels map[string]string, metric string, val core.MetricValue, createTime time.Time) *gcm.TimeSeries { 73 point := &gcm.Point{ 74 Interval: &gcm.TimeInterval{ 75 StartTime: timestamp.Format(time.RFC3339), 76 EndTime: timestamp.Format(time.RFC3339), 77 }, 78 Value: &gcm.TypedValue{}, 79 } 80 81 var valueType string 82 83 switch val.ValueType { 84 case core.ValueInt64: 85 point.Value.Int64Value = val.IntValue 86 point.Value.ForceSendFields = []string{"Int64Value"} 87 valueType = "INT64" 88 case core.ValueFloat: 89 v := float64(val.FloatValue) 90 point.Value.DoubleValue = v 91 point.Value.ForceSendFields = []string{"DoubleValue"} 92 valueType = "DOUBLE" 93 default: 94 glog.Errorf("Type not supported %v in %v", val.ValueType, metric) 95 return nil 96 } 97 // For cumulative metric use the provided start time. 98 if val.MetricType == core.MetricCumulative { 99 point.Interval.StartTime = createTime.Format(time.RFC3339) 100 } 101 102 return &gcm.TimeSeries{ 103 Points: []*gcm.Point{point}, 104 Metric: &gcm.Metric{ 105 Type: fullMetricType(metric), 106 Labels: labels, 107 }, 108 ValueType: valueType, 109 } 110 } 111 112 func (sink *gcmSink) getTimeSeries(timestamp time.Time, labels map[string]string, metric string, val core.MetricValue, createTime time.Time) *gcm.TimeSeries { 113 finalLabels := make(map[string]string) 114 if core.IsNodeAutoscalingMetric(metric) { 115 // All and autoscaling. Do not populate for other filters. 116 if sink.metricFilter != metricsAll && 117 sink.metricFilter != metricsOnlyAutoscaling { 118 return nil 119 } 120 121 finalLabels[core.LabelHostname.Key] = labels[core.LabelHostname.Key] 122 finalLabels[core.LabelGCEResourceID.Key] = labels[core.LabelHostID.Key] 123 finalLabels[core.LabelGCEResourceType.Key] = "instance" 124 } else { 125 // Only all. 126 if sink.metricFilter != metricsAll { 127 return nil 128 } 129 supportedLables := core.GcmLabels() 130 for key, value := range labels { 131 if _, ok := supportedLables[key]; ok { 132 finalLabels[key] = value 133 } 134 } 135 } 136 137 return createTimeSeries(timestamp, finalLabels, metric, val, createTime) 138 } 139 140 func (sink *gcmSink) getTimeSeriesForLabeledMetrics(timestamp time.Time, labels map[string]string, metric core.LabeledMetric, createTime time.Time) *gcm.TimeSeries { 141 // Only all. There are no autoscaling labeled metrics. 142 if sink.metricFilter != metricsAll { 143 return nil 144 } 145 146 finalLabels := make(map[string]string) 147 supportedLables := core.GcmLabels() 148 for key, value := range labels { 149 if _, ok := supportedLables[key]; ok { 150 finalLabels[key] = value 151 } 152 } 153 for key, value := range metric.Labels { 154 if _, ok := supportedLables[key]; ok { 155 finalLabels[key] = value 156 } 157 } 158 159 return createTimeSeries(timestamp, finalLabels, metric.Name, metric.MetricValue, createTime) 160 } 161 162 func fullProjectName(name string) string { 163 return fmt.Sprintf("projects/%s", name) 164 } 165 166 func (sink *gcmSink) sendRequest(req *gcm.CreateTimeSeriesRequest) { 167 _, err := sink.gcmService.Projects.TimeSeries.Create(fullProjectName(sink.project), req).Do() 168 if err != nil { 169 glog.Errorf("Error while sending request to GCM %v", err) 170 } else { 171 glog.V(4).Infof("Successfully sent %v timeserieses to GCM", len(req.TimeSeries)) 172 } 173 } 174 175 func (sink *gcmSink) ExportData(dataBatch *core.DataBatch) { 176 if err := sink.registerAllMetrics(); err != nil { 177 glog.Warningf("Error during metrics registration: %v", err) 178 return 179 } 180 181 req := getReq() 182 for _, metricSet := range dataBatch.MetricSets { 183 for metric, val := range metricSet.MetricValues { 184 point := sink.getTimeSeries(dataBatch.Timestamp, metricSet.Labels, metric, val, metricSet.CreateTime) 185 if point != nil { 186 req.TimeSeries = append(req.TimeSeries, point) 187 } 188 if len(req.TimeSeries) >= maxTimeseriesPerRequest { 189 sink.sendRequest(req) 190 req = getReq() 191 } 192 } 193 for _, metric := range metricSet.LabeledMetrics { 194 point := sink.getTimeSeriesForLabeledMetrics(dataBatch.Timestamp, metricSet.Labels, metric, metricSet.CreateTime) 195 if point != nil { 196 req.TimeSeries = append(req.TimeSeries, point) 197 } 198 if len(req.TimeSeries) >= maxTimeseriesPerRequest { 199 sink.sendRequest(req) 200 req = getReq() 201 } 202 } 203 } 204 if len(req.TimeSeries) > 0 { 205 sink.sendRequest(req) 206 } 207 } 208 209 func (sink *gcmSink) Stop() { 210 // nothing needs to be done. 211 } 212 213 func (sink *gcmSink) registerAllMetrics() error { 214 return sink.register(core.AllMetrics) 215 } 216 217 // Adds the specified metrics or updates them if they already exist. 218 func (sink *gcmSink) register(metrics []core.Metric) error { 219 sink.Lock() 220 defer sink.Unlock() 221 if sink.registered { 222 return nil 223 } 224 225 for _, metric := range metrics { 226 metricName := fullMetricName(sink.project, metric.MetricDescriptor.Name) 227 metricType := fullMetricType(metric.MetricDescriptor.Name) 228 229 if _, err := sink.gcmService.Projects.MetricDescriptors.Delete(metricName).Do(); err != nil { 230 glog.Infof("[GCM] Deleting metric %v failed: %v", metricName, err) 231 } 232 labels := make([]*gcm.LabelDescriptor, 0) 233 234 // Node autoscaling metrics have special labels. 235 if core.IsNodeAutoscalingMetric(metric.MetricDescriptor.Name) { 236 // All and autoscaling. Do not populate for other filters. 237 if sink.metricFilter != metricsAll && 238 sink.metricFilter != metricsOnlyAutoscaling { 239 continue 240 } 241 242 for _, l := range core.GcmNodeAutoscalingLabels() { 243 labels = append(labels, &gcm.LabelDescriptor{ 244 Key: l.Key, 245 Description: l.Description, 246 }) 247 } 248 } else { 249 // Only all. 250 if sink.metricFilter != metricsAll { 251 continue 252 } 253 254 for _, l := range core.GcmLabels() { 255 labels = append(labels, &gcm.LabelDescriptor{ 256 Key: l.Key, 257 Description: l.Description, 258 }) 259 } 260 } 261 262 var metricKind string 263 264 switch metric.MetricDescriptor.Type { 265 case core.MetricCumulative: 266 metricKind = "CUMULATIVE" 267 case core.MetricGauge: 268 metricKind = "GAUGE" 269 case core.MetricDelta: 270 metricKind = "DELTA" 271 } 272 273 var valueType string 274 275 switch metric.MetricDescriptor.ValueType { 276 case core.ValueInt64: 277 valueType = "INT64" 278 case core.ValueFloat: 279 valueType = "DOUBLE" 280 } 281 282 desc := &gcm.MetricDescriptor{ 283 Name: metricName, 284 Description: metric.MetricDescriptor.Description, 285 Labels: labels, 286 MetricKind: metricKind, 287 ValueType: valueType, 288 Type: metricType, 289 } 290 291 if _, err := sink.gcmService.Projects.MetricDescriptors.Create(fullProjectName(sink.project), desc).Do(); err != nil { 292 glog.Errorf("Metric registration of %v failed: %v", desc.Name, err) 293 return err 294 } 295 } 296 sink.registered = true 297 return nil 298 } 299 300 func CreateGCMSink(uri *url.URL) (core.DataSink, error) { 301 if len(uri.Scheme) > 0 { 302 return nil, fmt.Errorf("scheme should not be set for GCM sink") 303 } 304 if len(uri.Host) > 0 { 305 return nil, fmt.Errorf("host should not be set for GCM sink") 306 } 307 308 opts, err := url.ParseQuery(uri.RawQuery) 309 310 metrics := "all" 311 if len(opts["metrics"]) > 0 { 312 metrics = opts["metrics"][0] 313 } 314 var metricFilter MetricFilter = metricsAll 315 switch metrics { 316 case "all": 317 metricFilter = metricsAll 318 case "autoscaling": 319 metricFilter = metricsOnlyAutoscaling 320 default: 321 return nil, fmt.Errorf("invalid metrics parameter: %s", metrics) 322 } 323 324 if err := gce_util.EnsureOnGCE(); err != nil { 325 return nil, err 326 } 327 328 // Detect project ID 329 projectId, err := gce.ProjectID() 330 if err != nil { 331 return nil, err 332 } 333 334 // Create Google Cloud Monitoring service. 335 client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource("")) 336 gcmService, err := gcm.New(client) 337 if err != nil { 338 return nil, err 339 } 340 341 sink := &gcmSink{ 342 registered: false, 343 project: projectId, 344 gcmService: gcmService, 345 metricFilter: metricFilter, 346 } 347 glog.Infof("created GCM sink") 348 if err := sink.registerAllMetrics(); err != nil { 349 glog.Warningf("Error during metrics registration: %v", err) 350 } 351 return sink, nil 352 }