github.com/timstclair/heapster@v0.20.0-alpha1/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/influxdb/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  
    38  const (
    39  	// Value Field name
    40  	valueField = "value"
    41  	// Event special tags
    42  	dbNotFoundError = "database not found"
    43  
    44  	// Maximum number of influxdb Points to be sent in one batch.
    45  	maxSendBatchSize = 10000
    46  )
    47  
    48  func (sink *influxdbSink) resetConnection() {
    49  	glog.Infof("Influxdb connection reset")
    50  	sink.dbExists = false
    51  	sink.client = nil
    52  }
    53  
    54  func (sink *influxdbSink) ExportData(dataBatch *core.DataBatch) {
    55  	sink.Lock()
    56  	defer sink.Unlock()
    57  
    58  	dataPoints := make([]influxdb.Point, 0, 0)
    59  	for _, metricSet := range dataBatch.MetricSets {
    60  		for metricName, metricValue := range metricSet.MetricValues {
    61  
    62  			var value interface{}
    63  			if core.ValueInt64 == metricValue.ValueType {
    64  				value = metricValue.IntValue
    65  			} else if core.ValueFloat == metricValue.ValueType {
    66  				value = metricValue.FloatValue
    67  			} else {
    68  				continue
    69  			}
    70  
    71  			point := influxdb.Point{
    72  				Measurement: metricName,
    73  				Tags:        metricSet.Labels,
    74  				Fields: map[string]interface{}{
    75  					"value": value,
    76  				},
    77  				Time: dataBatch.Timestamp.UTC(),
    78  			}
    79  			dataPoints = append(dataPoints, point)
    80  			if len(dataPoints) >= maxSendBatchSize {
    81  				sink.sendData(dataPoints)
    82  				dataPoints = make([]influxdb.Point, 0, 0)
    83  			}
    84  		}
    85  	}
    86  	if len(dataPoints) >= 0 {
    87  		sink.sendData(dataPoints)
    88  	}
    89  }
    90  
    91  func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) {
    92  	if err := sink.createDatabase(); err != nil {
    93  		glog.Errorf("Failed to create infuxdb: %v", err)
    94  	}
    95  	bp := influxdb.BatchPoints{
    96  		Points:          dataPoints,
    97  		Database:        sink.c.DbName,
    98  		RetentionPolicy: "default",
    99  	}
   100  
   101  	start := time.Now()
   102  	if _, err := sink.client.Write(bp); err != nil {
   103  		if strings.Contains(err.Error(), dbNotFoundError) {
   104  			sink.resetConnection()
   105  		} else if _, _, err := sink.client.Ping(); err != nil {
   106  			glog.Errorf("InfluxDB ping failed: %v", err)
   107  			sink.resetConnection()
   108  		}
   109  	}
   110  	end := time.Now()
   111  	glog.V(4).Info("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start))
   112  }
   113  
   114  func (sink *influxdbSink) Name() string {
   115  	return "InfluxDB Sink"
   116  }
   117  
   118  func (sink *influxdbSink) Stop() {
   119  	// nothing needs to be done.
   120  }
   121  
   122  func (sink *influxdbSink) createDatabase() error {
   123  	if sink.client == nil {
   124  		client, err := influxdb_common.NewClient(sink.c)
   125  		if err != nil {
   126  			return err
   127  		}
   128  		sink.client = client
   129  	}
   130  
   131  	if sink.dbExists {
   132  		return nil
   133  	}
   134  	q := influxdb.Query{
   135  		Command: fmt.Sprintf("CREATE DATABASE %s", sink.c.DbName),
   136  	}
   137  	if resp, err := sink.client.Query(q); err != nil {
   138  		if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) {
   139  			return fmt.Errorf("Database creation failed: %v", err)
   140  		}
   141  	}
   142  	sink.dbExists = true
   143  	glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host)
   144  	return nil
   145  }
   146  
   147  // Returns a thread-compatible implementation of influxdb interactions.
   148  func new(c influxdb_common.InfluxdbConfig) core.DataSink {
   149  	client, err := influxdb_common.NewClient(c)
   150  	if err != nil {
   151  		glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err)
   152  	}
   153  	return &influxdbSink{
   154  		client: client, // can be nil
   155  		c:      c,
   156  	}
   157  }
   158  
   159  func CreateInfluxdbSink(uri *url.URL) (core.DataSink, error) {
   160  	config, err := influxdb_common.BuildConfig(uri)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	sink := new(*config)
   165  	glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName)
   166  	return sink, nil
   167  }