github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/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 eventToPointWithFields(event *kube_api.Event) (*influxdb.Point, error) {
    71  	point := influxdb.Point{
    72  		Measurement: "events",
    73  		Time:        event.LastTimestamp.Time.UTC(),
    74  		Fields: map[string]interface{}{
    75  			"message": event.Message,
    76  		},
    77  		Tags: map[string]string{
    78  			eventUID: string(event.UID),
    79  		},
    80  	}
    81  	if event.InvolvedObject.Kind == "Pod" {
    82  		point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID)
    83  	}
    84  	point.Tags["object_name"] = event.InvolvedObject.Name
    85  	point.Tags["type"] = event.Type
    86  	point.Tags["kind"] = event.InvolvedObject.Kind
    87  	point.Tags["component"] = event.Source.Component
    88  	point.Tags["reason"] = event.Reason
    89  	point.Tags[metrics_core.LabelNamespaceName.Key] = event.Namespace
    90  	point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host
    91  	return &point, nil
    92  }
    93  
    94  func eventToPoint(event *kube_api.Event) (*influxdb.Point, error) {
    95  	value, err := getEventValue(event)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	point := influxdb.Point{
   101  		Measurement: eventMeasurementName,
   102  		Time:        event.LastTimestamp.Time.UTC(),
   103  		Fields: map[string]interface{}{
   104  			valueField: value,
   105  		},
   106  		Tags: map[string]string{
   107  			eventUID: string(event.UID),
   108  		},
   109  	}
   110  	if event.InvolvedObject.Kind == "Pod" {
   111  		point.Tags[metrics_core.LabelPodId.Key] = string(event.InvolvedObject.UID)
   112  		point.Tags[metrics_core.LabelPodName.Key] = event.InvolvedObject.Name
   113  	}
   114  	point.Tags[metrics_core.LabelHostname.Key] = event.Source.Host
   115  	return &point, nil
   116  }
   117  
   118  func (sink *influxdbSink) ExportEvents(eventBatch *core.EventBatch) {
   119  	sink.Lock()
   120  	defer sink.Unlock()
   121  
   122  	dataPoints := make([]influxdb.Point, 0, 10)
   123  	for _, event := range eventBatch.Events {
   124  		var point *influxdb.Point
   125  		var err error
   126  		if sink.c.WithFields {
   127  			point, err = eventToPointWithFields(event)
   128  		} else {
   129  			point, err = eventToPoint(event)
   130  		}
   131  		if err != nil {
   132  			glog.Warningf("Failed to convert event to point: %v", err)
   133  		}
   134  		dataPoints = append(dataPoints, *point)
   135  		if len(dataPoints) >= maxSendBatchSize {
   136  			sink.sendData(dataPoints)
   137  			dataPoints = make([]influxdb.Point, 0, 1)
   138  		}
   139  	}
   140  	if len(dataPoints) >= 0 {
   141  		sink.sendData(dataPoints)
   142  	}
   143  }
   144  
   145  func (sink *influxdbSink) sendData(dataPoints []influxdb.Point) {
   146  	if err := sink.createDatabase(); err != nil {
   147  		glog.Errorf("Failed to create infuxdb: %v", err)
   148  		return
   149  	}
   150  	bp := influxdb.BatchPoints{
   151  		Points:          dataPoints,
   152  		Database:        sink.c.DbName,
   153  		RetentionPolicy: "default",
   154  	}
   155  
   156  	start := time.Now()
   157  	if _, err := sink.client.Write(bp); err != nil {
   158  		if strings.Contains(err.Error(), dbNotFoundError) {
   159  			sink.resetConnection()
   160  		} else if _, _, err := sink.client.Ping(); err != nil {
   161  			glog.Errorf("InfluxDB ping failed: %v", err)
   162  			sink.resetConnection()
   163  		}
   164  	}
   165  	end := time.Now()
   166  	glog.V(4).Infof("Exported %d data to influxDB in %s", len(dataPoints), end.Sub(start))
   167  }
   168  
   169  func (sink *influxdbSink) Name() string {
   170  	return "InfluxDB Sink"
   171  }
   172  
   173  func (sink *influxdbSink) Stop() {
   174  	// nothing needs to be done.
   175  }
   176  
   177  func (sink *influxdbSink) createDatabase() error {
   178  	if sink.client == nil {
   179  		client, err := influxdb_common.NewClient(sink.c)
   180  		if err != nil {
   181  			return err
   182  		}
   183  		sink.client = client
   184  	}
   185  
   186  	if sink.dbExists {
   187  		return nil
   188  	}
   189  	q := influxdb.Query{
   190  		Command: fmt.Sprintf("CREATE DATABASE %s", sink.c.DbName),
   191  	}
   192  	if resp, err := sink.client.Query(q); err != nil {
   193  		// We want to return error only if it is not "already exists" error.
   194  		if !(resp != nil && resp.Err != nil && strings.Contains(resp.Err.Error(), "already exists")) {
   195  			return fmt.Errorf("Database creation failed: %v", err)
   196  		}
   197  	}
   198  	sink.dbExists = true
   199  	glog.Infof("Created database %q on influxDB server at %q", sink.c.DbName, sink.c.Host)
   200  	return nil
   201  }
   202  
   203  // Returns a thread-safe implementation of core.EventSink for InfluxDB.
   204  func new(c influxdb_common.InfluxdbConfig) core.EventSink {
   205  	client, err := influxdb_common.NewClient(c)
   206  	if err != nil {
   207  		glog.Errorf("issues while creating an InfluxDB sink: %v, will retry on use", err)
   208  	}
   209  	return &influxdbSink{
   210  		client: client, // can be nil
   211  		c:      c,
   212  	}
   213  }
   214  
   215  func CreateInfluxdbSink(uri *url.URL) (core.EventSink, error) {
   216  	config, err := influxdb_common.BuildConfig(uri)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	sink := new(*config)
   221  	glog.Infof("created influxdb sink with options: host:%s user:%s db:%s", config.Host, config.User, config.DbName)
   222  	return sink, nil
   223  }