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