github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/sinks/influxdb/influxdb.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 influxdb 16 17 import ( 18 "fmt" 19 "net/url" 20 "strings" 21 "sync" 22 "time" 23 24 influxdb_common "k8s.io/heapster/common/influxdb" 25 "k8s.io/heapster/metrics/core" 26 27 "github.com/golang/glog" 28 influxdb "github.com/influxdata/influxdb/client" 29 ) 30 31 type influxdbSink struct { 32 client influxdb_common.InfluxdbClient 33 sync.RWMutex 34 c influxdb_common.InfluxdbConfig 35 dbExists bool 36 37 // wg and conChan will work together to limit concurrent influxDB sink goroutines. 38 wg sync.WaitGroup 39 conChan chan struct{} 40 } 41 42 var influxdbBlacklistLabels = map[string]struct{}{ 43 core.LabelPodNamespaceUID.Key: {}, 44 core.LabelPodId.Key: {}, 45 core.LabelHostname.Key: {}, 46 core.LabelHostID.Key: {}, 47 } 48 49 const ( 50 // Value Field name 51 valueField = "value" 52 // Event special tags 53 dbNotFoundError = "database not found" 54 55 // Maximum number of influxdb Points to be sent in one batch. 56 maxSendBatchSize = 10000 57 ) 58 59 func (sink *influxdbSink) resetConnection() { 60 glog.Infof("Influxdb connection reset") 61 sink.dbExists = false 62 sink.client = nil 63 } 64 65 func (sink *influxdbSink) ExportData(dataBatch *core.DataBatch) { 66 sink.Lock() 67 defer sink.Unlock() 68 69 dataPoints := make([]influxdb.Point, 0, 0) 70 for _, metricSet := range dataBatch.MetricSets { 71 for metricName, metricValue := range metricSet.MetricValues { 72 if sink.c.DisableCounterMetrics { 73 if _, exists := core.RateMetricsMapping[metricName]; exists { 74 continue 75 } 76 } 77 78 var value interface{} 79 if core.ValueInt64 == metricValue.ValueType { 80 value = metricValue.IntValue 81 } else if core.ValueFloat == metricValue.ValueType { 82 value = float64(metricValue.FloatValue) 83 } else { 84 continue 85 } 86 87 // Prepare measurement without fields 88 fieldName := "value" 89 measurementName := metricName 90 if sink.c.WithFields { 91 // Prepare measurement and field names 92 serieName := strings.SplitN(metricName, "/", 2) 93 measurementName = serieName[0] 94 if len(serieName) > 1 { 95 fieldName = serieName[1] 96 } 97 } 98 99 point := influxdb.Point{ 100 Measurement: measurementName, 101 Tags: make(map[string]string, len(metricSet.Labels)), 102 Fields: map[string]interface{}{ 103 fieldName: value, 104 }, 105 Time: dataBatch.Timestamp.UTC(), 106 } 107 for key, value := range metricSet.Labels { 108 if _, exists := influxdbBlacklistLabels[key]; !exists { 109 if value != "" { 110 point.Tags[key] = value 111 } 112 } 113 } 114 115 point.Tags["cluster_name"] = sink.c.ClusterName 116 117 dataPoints = append(dataPoints, point) 118 if len(dataPoints) >= maxSendBatchSize { 119 sink.concurrentSendData(dataPoints) 120 dataPoints = make([]influxdb.Point, 0, 0) 121 } 122 } 123 124 for _, labeledMetric := range metricSet.LabeledMetrics { 125 if sink.c.DisableCounterMetrics { 126 if _, exists := core.RateMetricsMapping[labeledMetric.Name]; exists { 127 continue 128 } 129 } 130 131 var value interface{} 132 if core.ValueInt64 == labeledMetric.ValueType { 133 value = labeledMetric.IntValue 134 } else if core.ValueFloat == labeledMetric.ValueType { 135 value = float64(labeledMetric.FloatValue) 136 } else { 137 continue 138 } 139 140 // Prepare measurement without fields 141 fieldName := "value" 142 measurementName := labeledMetric.Name 143 if sink.c.WithFields { 144 // Prepare measurement and field names 145 serieName := strings.SplitN(labeledMetric.Name, "/", 2) 146 measurementName = serieName[0] 147 if len(serieName) > 1 { 148 fieldName = serieName[1] 149 } 150 } 151 152 point := influxdb.Point{ 153 Measurement: measurementName, 154 Tags: make(map[string]string, len(metricSet.Labels)+len(labeledMetric.Labels)), 155 Fields: map[string]interface{}{ 156 fieldName: value, 157 }, 158 Time: dataBatch.Timestamp.UTC(), 159 } 160 161 for key, value := range metricSet.Labels { 162 if _, exists := influxdbBlacklistLabels[key]; !exists { 163 if value != "" { 164 point.Tags[key] = value 165 } 166 } 167 } 168 for key, value := range labeledMetric.Labels { 169 if _, exists := influxdbBlacklistLabels[key]; !exists { 170 if value != "" { 171 point.Tags[key] = value 172 } 173 } 174 } 175 point.Tags["cluster_name"] = sink.c.ClusterName 176 177 dataPoints = append(dataPoints, point) 178 if len(dataPoints) >= maxSendBatchSize { 179 sink.concurrentSendData(dataPoints) 180 dataPoints = make([]influxdb.Point, 0, 0) 181 } 182 } 183 } 184 if len(dataPoints) > 0 { 185 sink.concurrentSendData(dataPoints) 186 } 187 188 sink.wg.Wait() 189 } 190 191 func (sink *influxdbSink) concurrentSendData(dataPoints []influxdb.Point) { 192 sink.wg.Add(1) 193 // use the channel to block until there's less than the maximum number of concurrent requests running 194 sink.conChan <- struct{}{} 195 go func(dataPoints []influxdb.Point) { 196 sink.sendData(dataPoints) 197 }(dataPoints) 198 } 199 200 func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) { 201 defer func() { 202 // empty an item from the channel so the next waiting request can run 203 <-sink.conChan 204 sink.wg.Done() 205 }() 206 207 if err := sink.createDatabase(); err != nil { 208 glog.Errorf("Failed to create influxdb: %v", err) 209 return 210 } 211 bp := influxdb.BatchPoints{ 212 Points: dataPoints, 213 Database: sink.c.DbName, 214 RetentionPolicy: "default", 215 } 216 217 start := time.Now() 218 if _, err := sink.client.Write(bp); err != nil { 219 glog.Errorf("InfluxDB write failed: %v", err) 220 if strings.Contains(err.Error(), dbNotFoundError) { 221 sink.resetConnection() 222 } else if _, _, err := sink.client.Ping(); err != nil { 223 glog.Errorf("InfluxDB ping failed: %v", err) 224 sink.resetConnection() 225 } 226 return 227 } 228 end := time.Now() 229 glog.V(4).Infof("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start)) 230 } 231 232 func (sink *influxdbSink) Name() string { 233 return "InfluxDB Sink" 234 } 235 236 func (sink *influxdbSink) Stop() { 237 // nothing needs to be done. 238 } 239 240 func (sink *influxdbSink) ensureClient() error { 241 if sink.client == nil { 242 client, err := influxdb_common.NewClient(sink.c) 243 if err != nil { 244 return err 245 } 246 sink.client = client 247 } 248 249 return nil 250 } 251 252 func (sink *influxdbSink) createDatabase() error { 253 if err := sink.ensureClient(); err != nil { 254 return err 255 } 256 257 if sink.dbExists { 258 return nil 259 } 260 q := influxdb.Query{ 261 Command: fmt.Sprintf(`CREATE DATABASE %s WITH NAME "default"`, sink.c.DbName), 262 } 263 264 if resp, err := sink.client.Query(q); err != nil { 265 if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) { 266 err := sink.createRetentionPolicy() 267 if err != nil { 268 return err 269 } 270 } 271 } 272 273 sink.dbExists = true 274 glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host) 275 return nil 276 } 277 278 func (sink *influxdbSink) createRetentionPolicy() error { 279 q := influxdb.Query{ 280 Command: fmt.Sprintf(`CREATE RETENTION POLICY "default" ON %s DURATION %s REPLICATION 1 DEFAULT`, sink.c.DbName, sink.c.RetentionPolicy), 281 } 282 283 if resp, err := sink.client.Query(q); err != nil { 284 if !(resp != nil && resp.Err != nil) { 285 return fmt.Errorf("Retention Policy creation failed: %v", err) 286 } 287 } 288 289 glog.Infof("Created retention policy 'default' in database %q on influxDB server at %q", sink.c.DbName, sink.c.Host) 290 return nil 291 } 292 293 // Returns a thread-compatible implementation of influxdb interactions. 294 func newSink(c influxdb_common.InfluxdbConfig) core.DataSink { 295 client, err := influxdb_common.NewClient(c) 296 if err != nil { 297 glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err) 298 } 299 return &influxdbSink{ 300 client: client, // can be nil 301 c: c, 302 conChan: make(chan struct{}, c.Concurrency), 303 } 304 } 305 306 func CreateInfluxdbSink(uri *url.URL) (core.DataSink, error) { 307 config, err := influxdb_common.BuildConfig(uri) 308 if err != nil { 309 return nil, err 310 } 311 sink := newSink(*config) 312 glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName) 313 return sink, nil 314 }