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