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  }