github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/riemann/driver.go (about) 1 // Copyright 2014 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 riemann 16 17 import ( 18 "net/url" 19 "reflect" 20 "runtime" 21 "strconv" 22 "sync" 23 24 "time" 25 26 "github.com/golang/glog" 27 riemann_api "github.com/rikatz/goryman" 28 "k8s.io/heapster/metrics/core" 29 ) 30 31 // Abstracted for testing: this package works against any client that obeys the 32 // interface contract exposed by the goryman Riemann client 33 34 type riemannClient interface { 35 Connect() error 36 Close() error 37 SendEvent(e *riemann_api.Event) error 38 } 39 40 type riemannSink struct { 41 client riemannClient 42 config riemannConfig 43 sync.RWMutex 44 } 45 46 type riemannConfig struct { 47 host string 48 ttl float32 49 state string 50 tags []string 51 } 52 53 const ( 54 // Maximum number of riemann Events to be sent in one batch. 55 maxSendBatchSize = 10000 56 max_retries = 2 57 ) 58 59 func CreateRiemannSink(uri *url.URL) (core.DataSink, error) { 60 c := riemannConfig{ 61 host: "riemann-heapster:5555", 62 ttl: 60.0, 63 state: "", 64 tags: make([]string, 0), 65 } 66 if len(uri.Host) > 0 { 67 c.host = uri.Host 68 } 69 options := uri.Query() 70 if len(options["ttl"]) > 0 { 71 var ttl, err = strconv.ParseFloat(options["ttl"][0], 32) 72 if err != nil { 73 return nil, err 74 } 75 c.ttl = float32(ttl) 76 } 77 if len(options["state"]) > 0 { 78 c.state = options["state"][0] 79 } 80 if len(options["tags"]) > 0 { 81 c.tags = options["tags"] 82 } 83 84 glog.Infof("Riemann sink URI: '%+v', host: '%+v', options: '%+v', ", uri, c.host, options) 85 rs := &riemannSink{ 86 client: nil, 87 config: c, 88 } 89 90 err := rs.setupRiemannClient() 91 if err != nil { 92 glog.Warningf("Riemann sink not connected: %v", err) 93 // Warn but return the sink. 94 } 95 return rs, nil 96 } 97 98 func (rs *riemannSink) setupRiemannClient() error { 99 client := riemann_api.NewGorymanClient(rs.config.host) 100 runtime.SetFinalizer(client, func(c riemannClient) { c.Close() }) 101 err := client.Connect() 102 if err != nil { 103 return err 104 } 105 rs.client = client 106 return nil 107 } 108 109 // Return a user-friendly string describing the sink 110 func (sink *riemannSink) Name() string { 111 return "Riemann Sink" 112 } 113 114 func (sink *riemannSink) Stop() { 115 // nothing needs to be done. 116 } 117 118 // ExportData Send a collection of Timeseries to Riemann 119 func (sink *riemannSink) ExportData(dataBatch *core.DataBatch) { 120 sink.Lock() 121 defer sink.Unlock() 122 123 if sink.client == nil { 124 if err := sink.setupRiemannClient(); err != nil { 125 glog.Warningf("Riemann sink not connected: %v", err) 126 return 127 } 128 } 129 130 var dataEvents []riemann_api.Event 131 appendMetric := func(host, name string, value interface{}, labels map[string]string) { 132 event := riemann_api.Event{ 133 Time: dataBatch.Timestamp.Unix(), 134 Service: name, 135 Host: host, 136 Description: "", //no description - waste of bandwidth. 137 Attributes: labels, 138 Metric: value, 139 Ttl: sink.config.ttl, 140 State: sink.config.state, 141 Tags: sink.config.tags, 142 } 143 144 dataEvents = append(dataEvents, event) 145 if len(dataEvents) >= maxSendBatchSize { 146 sink.sendData(dataEvents) 147 dataEvents = nil 148 } 149 } 150 151 riemannValue := func(value interface{}) interface{} { 152 // Workaround for error from goryman: "Metric of invalid type (type int64)" 153 if reflect.TypeOf(value).Kind() == reflect.Int64 { 154 return int(value.(int64)) 155 } 156 return value 157 } 158 159 for _, metricSet := range dataBatch.MetricSets { 160 host := metricSet.Labels[core.LabelHostname.Key] 161 for metricName, metricValue := range metricSet.MetricValues { 162 if value := metricValue.GetValue(); value != nil { 163 appendMetric(host, metricName, riemannValue(value), metricSet.Labels) 164 } 165 } 166 for _, metric := range metricSet.LabeledMetrics { 167 if value := metric.GetValue(); value != nil { 168 labels := make(map[string]string) 169 for k, v := range metricSet.Labels { 170 labels[k] = v 171 } 172 for k, v := range metric.Labels { 173 labels[k] = v 174 } 175 appendMetric(host, metric.Name, riemannValue(value), labels) 176 } 177 } 178 } 179 180 if len(dataEvents) > 0 { 181 sink.sendData(dataEvents) 182 } 183 } 184 185 func (sink *riemannSink) sendData(dataEvents []riemann_api.Event) { 186 if sink.client == nil { 187 return 188 } 189 190 start := time.Now() 191 errors := 0 192 for _, event := range dataEvents { 193 glog.V(8).Infof("Sending event to Riemann: %+v", event) 194 var err error 195 for try := 0; try < max_retries; try++ { 196 err = sink.client.SendEvent(&event) 197 if err == nil { 198 break 199 } 200 } 201 if err != nil { 202 errors++ 203 glog.V(4).Infof("Failed to send event to Riemann: %+v: %+v", event, err) 204 } 205 } 206 end := time.Now() 207 if errors > 0 { 208 glog.V(2).Info("There were errors sending events to Riemman, forcing reconnection") 209 sink.client.Close() 210 sink.client = nil 211 } 212 glog.V(4).Infof("Exported %d events to riemann in %s", len(dataEvents)-errors, end.Sub(start)) 213 }