github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/opentsdb/driver.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 opentsdb
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"net/url"
    21  	"regexp"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	opentsdbclient "github.com/bluebreezecf/opentsdb-goclient/client"
    27  	opentsdbcfg "github.com/bluebreezecf/opentsdb-goclient/config"
    28  	"github.com/golang/glog"
    29  	"k8s.io/heapster/metrics/core"
    30  )
    31  
    32  const (
    33  	defaultTagName      = "defaultTagName"
    34  	defaultTagValue     = "defaultTagValue"
    35  	eventMetricName     = "events"
    36  	eventUID            = "uid"
    37  	opentsdbSinkName    = "OpenTSDB Sink"
    38  	sinkRegisterName    = "opentsdb"
    39  	defaultOpentsdbHost = "127.0.0.1:4242"
    40  	batchSize           = 1000
    41  )
    42  
    43  var (
    44  	// Matches any disallowed character in OpenTSDB names.
    45  	disallowedCharsRegexp = regexp.MustCompile("[^[:alnum:]\\-_\\./]")
    46  )
    47  
    48  // openTSDBClient defines the minimal methods which will be used to
    49  // communicate with the target OpenTSDB for current openTSDBSink instance.
    50  type openTSDBClient interface {
    51  	Ping() error
    52  	Put(datapoints []opentsdbclient.DataPoint, queryParam string) (*opentsdbclient.PutResponse, error)
    53  }
    54  
    55  type openTSDBSink struct {
    56  	client openTSDBClient
    57  	sync.RWMutex
    58  	writeFailures int
    59  	config        opentsdbcfg.OpenTSDBConfig
    60  }
    61  
    62  func (tsdbSink *openTSDBSink) ExportData(data *core.DataBatch) {
    63  	if err := tsdbSink.client.Ping(); err != nil {
    64  		glog.Warningf("Failed to ping opentsdb: %v", err)
    65  		return
    66  	}
    67  	dataPoints := make([]opentsdbclient.DataPoint, 0, batchSize)
    68  	for _, metricSet := range data.MetricSets {
    69  		for metricName, metricValue := range metricSet.MetricValues {
    70  			dataPoints = append(dataPoints, tsdbSink.metricToPoint(metricName, metricValue, data.Timestamp, metricSet.Labels))
    71  			if len(dataPoints) >= batchSize {
    72  				_, err := tsdbSink.client.Put(dataPoints, opentsdbclient.PutRespWithSummary)
    73  				if err != nil {
    74  					glog.Errorf("failed to write metrics to opentsdb - %v", err)
    75  					tsdbSink.recordWriteFailure()
    76  					return
    77  				}
    78  				dataPoints = make([]opentsdbclient.DataPoint, 0, batchSize)
    79  			}
    80  		}
    81  	}
    82  	if len(dataPoints) >= 0 {
    83  		_, err := tsdbSink.client.Put(dataPoints, opentsdbclient.PutRespWithSummary)
    84  		if err != nil {
    85  			glog.Errorf("failed to write metrics to opentsdb - %v", err)
    86  			tsdbSink.recordWriteFailure()
    87  			return
    88  		}
    89  	}
    90  }
    91  
    92  func (tsdbSink *openTSDBSink) DebugInfo() string {
    93  	buf := bytes.Buffer{}
    94  	buf.WriteString("Sink Type: OpenTSDB\n")
    95  	buf.WriteString(fmt.Sprintf("\tclient: Host %s", tsdbSink.config.OpentsdbHost))
    96  	buf.WriteString(tsdbSink.getState())
    97  	buf.WriteString("\n")
    98  	return buf.String()
    99  }
   100  
   101  func (tsdbSink *openTSDBSink) Name() string {
   102  	return opentsdbSinkName
   103  }
   104  
   105  func (tsdbSink *openTSDBSink) Stop() {
   106  	// Do nothing
   107  }
   108  
   109  // Converts the given OpenTSDB metric or tag name/value to a form that is
   110  // accepted by OpenTSDB. As the OpenTSDB documentation states:
   111  // 'Metric names, tag names and tag values have to be made of alpha numeric
   112  // characters, dash "-", underscore "_", period ".", and forward slash "/".'
   113  func toValidOpenTsdbName(name string) (validName string) {
   114  	// This takes care of some cases where dash "-" characters were
   115  	// encoded as '\\x2d' in received Timeseries Points
   116  	validName = fmt.Sprintf("%s", name)
   117  
   118  	// replace all illegal characters with '_'
   119  	return disallowedCharsRegexp.ReplaceAllLiteralString(validName, "_")
   120  }
   121  
   122  // timeSeriesToPoint transfers the contents holding in the given pointer of sink_api.Timeseries
   123  // into the instance of opentsdbclient.DataPoint
   124  func (tsdbSink *openTSDBSink) metricToPoint(name string, value core.MetricValue, timestamp time.Time, labels map[string]string) opentsdbclient.DataPoint {
   125  	seriesName := strings.Replace(toValidOpenTsdbName(name), "/", "_", -1)
   126  
   127  	if value.MetricType.String() != "" {
   128  		seriesName = fmt.Sprintf("%s_%s", seriesName, value.MetricType.String())
   129  	}
   130  
   131  	datapoint := opentsdbclient.DataPoint{
   132  		Metric:    seriesName,
   133  		Tags:      make(map[string]string, len(labels)),
   134  		Timestamp: timestamp.Unix(),
   135  	}
   136  	if value.ValueType == core.ValueInt64 {
   137  		datapoint.Value = value.IntValue
   138  	} else {
   139  		datapoint.Value = value.FloatValue
   140  	}
   141  
   142  	for key, value := range labels {
   143  		key = toValidOpenTsdbName(key)
   144  		value = toValidOpenTsdbName(value)
   145  
   146  		if value != "" {
   147  			datapoint.Tags[key] = value
   148  		}
   149  	}
   150  
   151  	tsdbSink.secureTags(&datapoint)
   152  	return datapoint
   153  }
   154  
   155  // secureTags just fills in the default key-value pair for the tags, if there is no tags for
   156  // current datapoint. Otherwise, the opentsdb will return error and the operation of putting
   157  // datapoint will be failed.
   158  func (tsdbSink *openTSDBSink) secureTags(datapoint *opentsdbclient.DataPoint) {
   159  
   160  	glog.Warningf("%#v", datapoint)
   161  	if len(datapoint.Tags) == 0 {
   162  		datapoint.Tags[defaultTagName] = defaultTagValue
   163  	}
   164  }
   165  
   166  func (tsdbSink *openTSDBSink) recordWriteFailure() {
   167  	tsdbSink.Lock()
   168  	defer tsdbSink.Unlock()
   169  	tsdbSink.writeFailures++
   170  }
   171  
   172  func (tsdbSink *openTSDBSink) getState() string {
   173  	tsdbSink.RLock()
   174  	defer tsdbSink.RUnlock()
   175  	return fmt.Sprintf("\tNumber of write failures: %d\n", tsdbSink.writeFailures)
   176  }
   177  
   178  func (tsdbSink *openTSDBSink) ping() error {
   179  	return tsdbSink.client.Ping()
   180  }
   181  
   182  func (tsdbSink *openTSDBSink) setupClient() error {
   183  	return nil
   184  }
   185  
   186  func new(opentsdbHost string) (*openTSDBSink, error) {
   187  	cfg := opentsdbcfg.OpenTSDBConfig{OpentsdbHost: opentsdbHost}
   188  	opentsdbClient, err := opentsdbclient.NewClient(cfg)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	return &openTSDBSink{
   193  		client: opentsdbClient,
   194  		config: cfg,
   195  	}, nil
   196  }
   197  
   198  func CreateOpenTSDBSink(uri *url.URL) (core.DataSink, error) {
   199  	host := defaultOpentsdbHost
   200  	if len(uri.Host) > 0 {
   201  		host = uri.Host
   202  	}
   203  	tsdbSink, err := new(host)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	glog.Infof("created opentsdb sink with host: %v", host)
   208  	return tsdbSink, nil
   209  }