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