github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/monasca/driver.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 monasca 16 17 import ( 18 "fmt" 19 "net/http" 20 "net/url" 21 "reflect" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/golang/glog" 27 "k8s.io/heapster/metrics/core" 28 ) 29 30 type monascaSink struct { 31 client Client 32 sync.RWMutex 33 numberOfFailures int 34 } 35 36 // Pushes the specified metric measurement to the Monasca API. 37 // The Timeseries are transformed to monasca metrics beforehand. 38 // Timeseries that cannot be translated to monasca metrics are skipped. 39 func (sink *monascaSink) ExportData(dataBatch *core.DataBatch) { 40 sink.Lock() 41 defer sink.Unlock() 42 43 metrics := sink.processMetrics(dataBatch) 44 code, response, err := sink.client.SendRequest("POST", "/metrics", metrics) 45 if err != nil { 46 glog.Errorf("%s", err) 47 sink.numberOfFailures++ 48 return 49 } 50 if code != http.StatusNoContent { 51 glog.Error(response) 52 sink.numberOfFailures++ 53 } 54 } 55 56 // a monasca metric definition 57 type metric struct { 58 Name string `json:"name"` 59 Dimensions map[string]string `json:"dimensions"` 60 Timestamp int64 `json:"timestamp"` 61 Value float64 `json:"value"` 62 ValueMeta map[string]string `json:"value_meta"` 63 } 64 65 func (sink *monascaSink) processMetrics(dataBatch *core.DataBatch) []metric { 66 metrics := []metric{} 67 for _, metricSet := range dataBatch.MetricSets { 68 m := sink.processMetricSet(dataBatch, metricSet) 69 metrics = append(metrics, m...) 70 } 71 return metrics 72 } 73 74 func (sink *monascaSink) processMetricSet(dataBatch *core.DataBatch, metricSet *core.MetricSet) []metric { 75 metrics := []metric{} 76 77 // process unlabeled metrics 78 for metricName, metricValue := range metricSet.MetricValues { 79 m := sink.processMetric(metricSet.Labels, metricName, dataBatch.Timestamp, metricValue.GetValue()) 80 if nil != m { 81 metrics = append(metrics, *m) 82 } 83 } 84 85 // process labeled metrics 86 for _, metric := range metricSet.LabeledMetrics { 87 labels := map[string]string{} 88 for k, v := range metricSet.Labels { 89 labels[k] = v 90 } 91 for k, v := range metric.Labels { 92 labels[k] = v 93 } 94 m := sink.processMetric(labels, metric.Name, dataBatch.Timestamp, metric.GetValue()) 95 if nil != m { 96 metrics = append(metrics, *m) 97 } 98 } 99 return metrics 100 } 101 102 func (sink *monascaSink) processMetric(labels map[string]string, name string, timestamp time.Time, value interface{}) *metric { 103 val, err := sink.convertValue(value) 104 if err != nil { 105 glog.Warningf("Metric cannot be pushed to monasca. %#v", value) 106 return nil 107 } 108 dims, valueMeta := sink.processLabels(labels) 109 m := metric{ 110 Name: strings.Replace(name, "/", ".", -1), 111 Dimensions: dims, 112 Timestamp: (timestamp.UnixNano() / 1000000), 113 Value: val, 114 ValueMeta: valueMeta, 115 } 116 return &m 117 } 118 119 // convert the Timeseries value to a monasca value 120 func (sink *monascaSink) convertValue(val interface{}) (float64, error) { 121 switch val.(type) { 122 case int: 123 return float64(val.(int)), nil 124 case int64: 125 return float64(val.(int64)), nil 126 case bool: 127 if val.(bool) { 128 return 1.0, nil 129 } 130 return 0.0, nil 131 case float32: 132 return float64(val.(float32)), nil 133 case float64: 134 return val.(float64), nil 135 } 136 return 0.0, fmt.Errorf("Unsupported monasca metric value type %T", reflect.TypeOf(val)) 137 } 138 139 const ( 140 emptyValue = "none" 141 monascaComponent = "component" 142 monascaService = "service" 143 monascaHostname = "hostname" 144 ) 145 146 // preprocesses heapster labels, splitting into monasca dimensions and monasca meta-values 147 func (sink *monascaSink) processLabels(labels map[string]string) (map[string]string, map[string]string) { 148 dims := map[string]string{} 149 valueMeta := map[string]string{} 150 151 // labels to dimensions 152 dims[monascaComponent] = sink.processDimension(labels[core.LabelPodName.Key]) 153 dims[monascaHostname] = sink.processDimension(labels[core.LabelHostname.Key]) 154 dims[core.LabelContainerName.Key] = sink.processDimension(labels[core.LabelContainerName.Key]) 155 dims[monascaService] = "kubernetes" 156 157 // labels to valueMeta 158 for i, v := range labels { 159 if i != core.LabelPodName.Key && i != core.LabelHostname.Key && 160 i != core.LabelContainerName.Key && v != "" { 161 valueMeta[i] = strings.Replace(v, ",", " ", -1) 162 } 163 } 164 return dims, valueMeta 165 } 166 167 // creates a valid dimension value 168 func (sink *monascaSink) processDimension(value string) string { 169 if value != "" { 170 v := strings.Replace(value, "/", ".", -1) 171 return strings.Replace(v, ",", " ", -1) 172 } 173 return emptyValue 174 } 175 176 func (sink *monascaSink) Name() string { 177 return "Monasca Sink" 178 } 179 180 func (sink *monascaSink) Stop() { 181 // Nothing needs to be done 182 } 183 184 // CreateMonascaSink creates a monasca sink that can consume the Monasca APIs to create metrics. 185 func CreateMonascaSink(uri *url.URL) (core.DataSink, error) { 186 opts := uri.Query() 187 config := NewConfig(opts) 188 client, err := NewMonascaClient(config) 189 if err != nil { 190 return nil, err 191 } 192 monascaSink := monascaSink{client: client} 193 glog.Infof("Created Monasca sink. Monasca server running on: %s", client.GetURL().String()) 194 return &monascaSink, nil 195 }