github.com/timstclair/heapster@v0.20.0-alpha1/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 "time" 21 22 "k8s.io/heapster/metrics/core" 23 24 "github.com/golang/glog" 25 "golang.org/x/oauth2" 26 "golang.org/x/oauth2/google" 27 gcm "google.golang.org/api/cloudmonitoring/v2beta2" 28 gce "google.golang.org/cloud/compute/metadata" 29 ) 30 31 const ( 32 metricDomain = "kubernetes.io" 33 customApiPrefix = "custom.cloudmonitoring.googleapis.com" 34 maxNumLabels = 10 35 // The largest number of timeseries we can write to per request. 36 maxTimeseriesPerRequest = 200 37 ) 38 39 type gcmSink struct { 40 project string 41 gcmService *gcm.Service 42 } 43 44 func (sink *gcmSink) Name() string { 45 return "GCM Sink" 46 } 47 48 func getReq() *gcm.WriteTimeseriesRequest { 49 return &gcm.WriteTimeseriesRequest{Timeseries: make([]*gcm.TimeseriesPoint, 0)} 50 } 51 52 func fullLabelName(name string) string { 53 return fmt.Sprintf("%s/%s/label/%s", customApiPrefix, metricDomain, name) 54 } 55 56 func fullMetricName(name string) string { 57 return fmt.Sprintf("%s/%s/%s", customApiPrefix, metricDomain, name) 58 } 59 60 func (sink *gcmSink) getTimeseriesPoint(timestamp time.Time, labels map[string]string, metric string, val core.MetricValue) *gcm.TimeseriesPoint { 61 point := &gcm.Point{ 62 Start: timestamp.Format(time.RFC3339), 63 End: timestamp.Format(time.RFC3339), 64 } 65 switch val.ValueType { 66 case core.ValueInt64: 67 point.Int64Value = &val.IntValue 68 case core.ValueFloat: 69 v := float64(val.FloatValue) 70 point.DoubleValue = &v 71 default: 72 glog.Errorf("Type not supported %v in %v", val.ValueType, metric) 73 return nil 74 } 75 // For cumulative metric use the provided start time. 76 if val.MetricType == core.MetricCumulative { 77 point.Start = val.Start.Format(time.RFC3339) 78 } 79 80 supportedLables := core.GcmLabels() 81 gcmLabels := make(map[string]string, len(labels)) 82 for key, value := range labels { 83 if _, ok := supportedLables[key]; ok { 84 gcmLabels[fullLabelName(key)] = value 85 } 86 } 87 desc := &gcm.TimeseriesDescriptor{ 88 Project: sink.project, 89 Labels: gcmLabels, 90 Metric: fullMetricName(metric), 91 } 92 93 return &gcm.TimeseriesPoint{Point: point, TimeseriesDesc: desc} 94 } 95 96 func (sink *gcmSink) sendRequest(req *gcm.WriteTimeseriesRequest) { 97 _, err := sink.gcmService.Timeseries.Write(sink.project, req).Do() 98 if err != nil { 99 glog.Errorf("Error while sending request to GCM %v", err) 100 } else { 101 glog.V(4).Infof("Successfully sent %v timeserieses to GCM", len(req.Timeseries)) 102 } 103 } 104 105 func (sink *gcmSink) ExportData(dataBatch *core.DataBatch) { 106 req := getReq() 107 for _, metricSet := range dataBatch.MetricSets { 108 for metric, val := range metricSet.MetricValues { 109 point := sink.getTimeseriesPoint(dataBatch.Timestamp, metricSet.Labels, metric, val) 110 if point != nil { 111 req.Timeseries = append(req.Timeseries, point) 112 } 113 if len(req.Timeseries) >= maxTimeseriesPerRequest { 114 sink.sendRequest(req) 115 req = getReq() 116 } 117 } 118 } 119 if len(req.Timeseries) > 0 { 120 sink.sendRequest(req) 121 } 122 } 123 124 func (sink *gcmSink) Stop() { 125 // nothing needs to be done. 126 } 127 128 // Adds the specified metrics or updates them if they already exist. 129 func (sink *gcmSink) register(metrics []core.Metric) error { 130 for _, metric := range metrics { 131 metricName := fullMetricName(metric.MetricDescriptor.Name) 132 if _, err := sink.gcmService.MetricDescriptors.Delete(sink.project, metricName).Do(); err != nil { 133 glog.Infof("[GCM] Deleting metric %v failed: %v", metricName, err) 134 } 135 labels := make([]*gcm.MetricDescriptorLabelDescriptor, 0) 136 for _, l := range core.GcmLabels() { 137 labels = append(labels, &gcm.MetricDescriptorLabelDescriptor{ 138 Key: fullLabelName(l.Key), 139 Description: l.Description, 140 }) 141 } 142 t := &gcm.MetricDescriptorTypeDescriptor{ 143 MetricType: metric.MetricDescriptor.Type.String(), 144 ValueType: metric.MetricDescriptor.ValueType.String(), 145 } 146 desc := &gcm.MetricDescriptor{ 147 Name: metricName, 148 Project: sink.project, 149 Description: metric.MetricDescriptor.Description, 150 Labels: labels, 151 TypeDescriptor: t, 152 } 153 if _, err := sink.gcmService.MetricDescriptors.Create(sink.project, desc).Do(); err != nil { 154 return err 155 } 156 } 157 return nil 158 } 159 160 func CreateGCMSink(uri *url.URL) (core.DataSink, error) { 161 if *uri != (url.URL{}) { 162 return nil, fmt.Errorf("gcm sinks don't take arguments") 163 } 164 // Detect project ID 165 projectId, err := gce.ProjectID() 166 if err != nil { 167 return nil, err 168 } 169 170 // Create Google Cloud Monitoring service. 171 client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource("")) 172 gcmService, err := gcm.New(client) 173 if err != nil { 174 return nil, err 175 } 176 177 sink := &gcmSink{project: projectId, gcmService: gcmService} 178 if err := sink.register(core.AllMetrics); err != nil { 179 return nil, err 180 } 181 182 glog.Infof("created GCM sink") 183 return sink, nil 184 }