github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/opentsdb/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 opentsdb 16 17 import ( 18 "bytes" 19 "fmt" 20 "net/url" 21 "regexp" 22 "strings" 23 "sync" 24 "time" 25 26 opentsdbclient "github.com/bluebreezecf/opentsdb-goclient/client" 27 opentsdbcfg "github.com/bluebreezecf/opentsdb-goclient/config" 28 "github.com/golang/glog" 29 "k8s.io/heapster/metrics/core" 30 ) 31 32 const ( 33 defaultTagName = "defaultTagName" 34 defaultTagValue = "defaultTagValue" 35 eventMetricName = "events" 36 eventUID = "uid" 37 opentsdbSinkName = "OpenTSDB Sink" 38 sinkRegisterName = "opentsdb" 39 defaultOpentsdbHost = "127.0.0.1:4242" 40 batchSize = 1000 41 ) 42 43 var ( 44 // Matches any disallowed character in OpenTSDB names. 45 disallowedCharsRegexp = regexp.MustCompile("[^[:alnum:]\\-_\\./]") 46 ) 47 48 // openTSDBClient defines the minimal methods which will be used to 49 // communicate with the target OpenTSDB for current openTSDBSink instance. 50 type openTSDBClient interface { 51 Ping() error 52 Put(datapoints []opentsdbclient.DataPoint, queryParam string) (*opentsdbclient.PutResponse, error) 53 } 54 55 type openTSDBSink struct { 56 client openTSDBClient 57 sync.RWMutex 58 writeFailures int 59 config opentsdbcfg.OpenTSDBConfig 60 } 61 62 func (tsdbSink *openTSDBSink) ExportData(data *core.DataBatch) { 63 if err := tsdbSink.client.Ping(); err != nil { 64 glog.Warningf("Failed to ping opentsdb: %v", err) 65 return 66 } 67 dataPoints := make([]opentsdbclient.DataPoint, 0, batchSize) 68 for _, metricSet := range data.MetricSets { 69 for metricName, metricValue := range metricSet.MetricValues { 70 dataPoints = append(dataPoints, tsdbSink.metricToPoint(metricName, metricValue, data.Timestamp, metricSet.Labels)) 71 if len(dataPoints) >= batchSize { 72 _, err := tsdbSink.client.Put(dataPoints, opentsdbclient.PutRespWithSummary) 73 if err != nil { 74 glog.Errorf("failed to write metrics to opentsdb - %v", err) 75 tsdbSink.recordWriteFailure() 76 return 77 } 78 dataPoints = make([]opentsdbclient.DataPoint, 0, batchSize) 79 } 80 } 81 } 82 if len(dataPoints) >= 0 { 83 _, err := tsdbSink.client.Put(dataPoints, opentsdbclient.PutRespWithSummary) 84 if err != nil { 85 glog.Errorf("failed to write metrics to opentsdb - %v", err) 86 tsdbSink.recordWriteFailure() 87 return 88 } 89 } 90 } 91 92 func (tsdbSink *openTSDBSink) DebugInfo() string { 93 buf := bytes.Buffer{} 94 buf.WriteString("Sink Type: OpenTSDB\n") 95 buf.WriteString(fmt.Sprintf("\tclient: Host %s", tsdbSink.config.OpentsdbHost)) 96 buf.WriteString(tsdbSink.getState()) 97 buf.WriteString("\n") 98 return buf.String() 99 } 100 101 func (tsdbSink *openTSDBSink) Name() string { 102 return opentsdbSinkName 103 } 104 105 func (tsdbSink *openTSDBSink) Stop() { 106 // Do nothing 107 } 108 109 // Converts the given OpenTSDB metric or tag name/value to a form that is 110 // accepted by OpenTSDB. As the OpenTSDB documentation states: 111 // 'Metric names, tag names and tag values have to be made of alpha numeric 112 // characters, dash "-", underscore "_", period ".", and forward slash "/".' 113 func toValidOpenTsdbName(name string) (validName string) { 114 // This takes care of some cases where dash "-" characters were 115 // encoded as '\\x2d' in received Timeseries Points 116 validName = fmt.Sprintf("%s", name) 117 118 // replace all illegal characters with '_' 119 return disallowedCharsRegexp.ReplaceAllLiteralString(validName, "_") 120 } 121 122 // timeSeriesToPoint transfers the contents holding in the given pointer of sink_api.Timeseries 123 // into the instance of opentsdbclient.DataPoint 124 func (tsdbSink *openTSDBSink) metricToPoint(name string, value core.MetricValue, timestamp time.Time, labels map[string]string) opentsdbclient.DataPoint { 125 seriesName := strings.Replace(toValidOpenTsdbName(name), "/", "_", -1) 126 127 if value.MetricType.String() != "" { 128 seriesName = fmt.Sprintf("%s_%s", seriesName, value.MetricType.String()) 129 } 130 131 datapoint := opentsdbclient.DataPoint{ 132 Metric: seriesName, 133 Tags: make(map[string]string, len(labels)), 134 Timestamp: timestamp.Unix(), 135 } 136 if value.ValueType == core.ValueInt64 { 137 datapoint.Value = value.IntValue 138 } else { 139 datapoint.Value = value.FloatValue 140 } 141 142 for key, value := range labels { 143 key = toValidOpenTsdbName(key) 144 value = toValidOpenTsdbName(value) 145 146 if value != "" { 147 datapoint.Tags[key] = value 148 } 149 } 150 151 tsdbSink.secureTags(&datapoint) 152 return datapoint 153 } 154 155 // secureTags just fills in the default key-value pair for the tags, if there is no tags for 156 // current datapoint. Otherwise, the opentsdb will return error and the operation of putting 157 // datapoint will be failed. 158 func (tsdbSink *openTSDBSink) secureTags(datapoint *opentsdbclient.DataPoint) { 159 160 glog.Warningf("%#v", datapoint) 161 if len(datapoint.Tags) == 0 { 162 datapoint.Tags[defaultTagName] = defaultTagValue 163 } 164 } 165 166 func (tsdbSink *openTSDBSink) recordWriteFailure() { 167 tsdbSink.Lock() 168 defer tsdbSink.Unlock() 169 tsdbSink.writeFailures++ 170 } 171 172 func (tsdbSink *openTSDBSink) getState() string { 173 tsdbSink.RLock() 174 defer tsdbSink.RUnlock() 175 return fmt.Sprintf("\tNumber of write failures: %d\n", tsdbSink.writeFailures) 176 } 177 178 func (tsdbSink *openTSDBSink) ping() error { 179 return tsdbSink.client.Ping() 180 } 181 182 func (tsdbSink *openTSDBSink) setupClient() error { 183 return nil 184 } 185 186 func new(opentsdbHost string) (*openTSDBSink, error) { 187 cfg := opentsdbcfg.OpenTSDBConfig{OpentsdbHost: opentsdbHost} 188 opentsdbClient, err := opentsdbclient.NewClient(cfg) 189 if err != nil { 190 return nil, err 191 } 192 return &openTSDBSink{ 193 client: opentsdbClient, 194 config: cfg, 195 }, nil 196 } 197 198 func CreateOpenTSDBSink(uri *url.URL) (core.DataSink, error) { 199 host := defaultOpentsdbHost 200 if len(uri.Host) > 0 { 201 host = uri.Host 202 } 203 tsdbSink, err := new(host) 204 if err != nil { 205 return nil, err 206 } 207 glog.Infof("created opentsdb sink with host: %v", host) 208 return tsdbSink, nil 209 }