github.com/timstclair/heapster@v0.20.0-alpha1/events/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  	"encoding/json"
    19  	"fmt"
    20  	"net/url"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	influxdb_common "k8s.io/heapster/common/influxdb"
    26  	"k8s.io/heapster/events/core"
    27  	metrics_core "k8s.io/heapster/metrics/core"
    28  	kube_api "k8s.io/kubernetes/pkg/api"
    29  
    30  	"github.com/golang/glog"
    31  	influxdb "github.com/influxdb/influxdb/client"
    32  )
    33  
    34  type influxdbSink struct {
    35  	client influxdb_common.InfluxdbClient
    36  	sync.RWMutex
    37  	c        influxdb_common.InfluxdbConfig
    38  	dbExists bool
    39  }
    40  
    41  const (
    42  	eventMeasurementName = "log/events"
    43  	// Event special tags
    44  	eventUID = "uid"
    45  	// Value Field name
    46  	valueField = "value"
    47  	// Event special tags
    48  	dbNotFoundError = "database not found"
    49  
    50  	// Maximum number of influxdb Points to be sent in one batch.
    51  	maxSendBatchSize = 1000
    52  )
    53  
    54  func (sink *influxdbSink) resetConnection() {
    55  	glog.Infof("Influxdb connection reset")
    56  	sink.dbExists = false
    57  	sink.client = nil
    58  }
    59  
    60  // Generate point value for event
    61  func getEventValue(event *kube_api.Event) (string, error) {
    62  	// TODO: check whether indenting is required.
    63  	bytes, err := json.MarshalIndent(event, "", " ")
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  	return string(bytes), nil
    68  }
    69  
    70  func eventToPoint(event *kube_api.Event) (*influxdb.Point, error) {
    71  	value, err := getEventValue(event)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	point := influxdb.Point{
    77  		Measurement: eventMeasurementName,
    78  		Time:        event.LastTimestamp.Time.UTC(),
    79  		Fields: map[string]interface{}{
    80  			valueField: value,
    81  		},
    82  		Tags: map[string]string{
    83  			eventUID: string(event.UID),
    84  		},
    85  	}
    86  	if event.InvolvedObject.Kind == "Pod" {
    87  		point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID)
    88  		point.Tags[metrics_core.LabelPodName.Key] = event.InvolvedObject.Name
    89  	}
    90  	point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host
    91  	return &point, nil
    92  }
    93  
    94  func (sink *influxdbSink) ExportEvents(eventBatch *core.EventBatch) {
    95  	sink.Lock()
    96  	defer sink.Unlock()
    97  
    98  	dataPoints := make([]influxdb.Point, 0, 10)
    99  	for _, event := range eventBatch.Events {
   100  		point, err := eventToPoint(event)
   101  		if err != nil {
   102  			glog.Warningf("Failed to convert event to point: %v", err)
   103  		}
   104  		dataPoints = append(dataPoints, *point)
   105  		if len(dataPoints) >= maxSendBatchSize {
   106  			sink.sendData(dataPoints)
   107  			dataPoints = make([]influxdb.Point, 0, 1)
   108  		}
   109  	}
   110  	if len(dataPoints) >= 0 {
   111  		sink.sendData(dataPoints)
   112  	}
   113  }
   114  
   115  func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) {
   116  	if err := sink.createDatabase(); err != nil {
   117  		glog.Errorf("Failed to create infuxdb: %v", err)
   118  	}
   119  	bp := influxdb.BatchPoints{
   120  		Points:          dataPoints,
   121  		Database:        sink.c.DbName,
   122  		RetentionPolicy: "default",
   123  	}
   124  
   125  	start := time.Now()
   126  	if _, err := sink.client.Write(bp); err != nil {
   127  		if strings.Contains(err.Error(), dbNotFoundError) {
   128  			sink.resetConnection()
   129  		} else if _, _, err := sink.client.Ping(); err != nil {
   130  			glog.Errorf("InfluxDB ping failed: %v", err)
   131  			sink.resetConnection()
   132  		}
   133  	}
   134  	end := time.Now()
   135  	glog.V(4).Info("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start))
   136  }
   137  
   138  func (sink *influxdbSink) Name() string {
   139  	return "InfluxDB Sink"
   140  }
   141  
   142  func (sink *influxdbSink) Stop() {
   143  	// nothing needs to be done.
   144  }
   145  
   146  func (sink *influxdbSink) createDatabase() error {
   147  	if sink.client == nil {
   148  		client, err := influxdb_common.NewClient(sink.c)
   149  		if err != nil {
   150  			return err
   151  		}
   152  		sink.client = client
   153  	}
   154  
   155  	if sink.dbExists {
   156  		return nil
   157  	}
   158  	q := influxdb.Query{
   159  		Command: fmt.Sprintf("CREATE DATABASE %s", sink.c.DbName),
   160  	}
   161  	if resp, err := sink.client.Query(q); err != nil {
   162  		// We want to return error only if it is not "already exists" error.
   163  		if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) {
   164  			return fmt.Errorf("Database creation failed: %v", err)
   165  		}
   166  	}
   167  	sink.dbExists = true
   168  	glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host)
   169  	return nil
   170  }
   171  
   172  // Returns a thread-safe implementation of core.EventSink for InfluxDB.
   173  func new(c influxdb_common.InfluxdbConfig) core.EventSink {
   174  	client, err := influxdb_common.NewClient(c)
   175  	if err != nil {
   176  		glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err)
   177  	}
   178  	return &influxdbSink{
   179  		client: client, // can be nil
   180  		c:      c,
   181  	}
   182  }
   183  
   184  func CreateInfluxdbSink(uri *url.URL) (core.EventSink, error) {
   185  	config, err := influxdb_common.BuildConfig(uri)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	sink := new(*config)
   190  	glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName)
   191  	return sink, nil
   192  }