github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/sinks/wavefront/wavefront.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 wavefront
    16  
    17  import (
    18  	"fmt"
    19  	"github.com/golang/glog"
    20  	"k8s.io/heapster/metrics/core"
    21  	"net"
    22  	"net/url"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  )
    28  
    29  const (
    30  	sysSubContainerName = "system.slice/"
    31  )
    32  
    33  var excludeTagList = [...]string{"namespace_id", "host_id", "pod_id", "hostname"}
    34  
    35  type wavefrontSink struct {
    36  	Conn              net.Conn
    37  	ProxyAddress      string
    38  	ClusterName       string
    39  	Prefix            string
    40  	IncludeLabels     bool
    41  	IncludeContainers bool
    42  	testMode          bool
    43  	testReceivedLines []string
    44  }
    45  
    46  func (wfSink *wavefrontSink) Name() string {
    47  	return "Wavefront Sink"
    48  }
    49  
    50  func (wfSink *wavefrontSink) Stop() {
    51  	// Do nothing.
    52  	wfSink.Conn.Close()
    53  }
    54  
    55  func (wfSink *wavefrontSink) sendLine(line string) {
    56  	if wfSink.testMode {
    57  		wfSink.testReceivedLines = append(wfSink.testReceivedLines, line)
    58  		glog.Infoln(line)
    59  		return
    60  	}
    61  	//if the connection was closed or interrupted - don't cause a panic (we'll retry at next interval)
    62  	defer func() {
    63  		if r := recover(); r != nil {
    64  			//we couldn't write the line so something is wrong with the connection
    65  			wfSink.Conn = nil
    66  		}
    67  	}()
    68  	if wfSink.Conn != nil {
    69  		wfSink.Conn.Write([]byte(line))
    70  	}
    71  }
    72  
    73  func (wfSink *wavefrontSink) sendPoint(metricName string, metricValStr string, ts string, source string, tagStr string) {
    74  	metricLine := fmt.Sprintf("%s %s %s source=\"%s\" %s\n", metricName, metricValStr, ts, source, tagStr)
    75  	wfSink.sendLine(metricLine)
    76  }
    77  
    78  func tagsToString(tags map[string]string) string {
    79  	tagStr := ""
    80  	for k, v := range tags {
    81  		// ignore tags with empty values as well so the data point doesn't fail validation
    82  		if excludeTag(k) == false && len(v) > 0 {
    83  			tagStr += k + "=\"" + v + "\" "
    84  		}
    85  	}
    86  	return tagStr
    87  }
    88  
    89  func excludeTag(a string) bool {
    90  	for _, b := range excludeTagList {
    91  		if b == a {
    92  			return true
    93  		}
    94  	}
    95  	return false
    96  }
    97  
    98  func (wfSink *wavefrontSink) cleanMetricName(metricType string, metricName string) string {
    99  	return wfSink.Prefix + metricType + "." + strings.Replace(metricName, "/", ".", -1)
   100  }
   101  
   102  func (wfSink *wavefrontSink) addLabelTags(ms *core.MetricSet, tags map[string]string) {
   103  	for _, labelName := range sortedLabelKeys(ms.Labels) {
   104  		labelValue := ms.Labels[labelName]
   105  		if labelName == "labels" {
   106  			//only parse labels if IncludeLabels == true
   107  			if wfSink.IncludeLabels {
   108  				for _, label := range strings.Split(labelValue, ",") {
   109  					//labels = app:webproxy,version:latest
   110  					tagParts := strings.SplitN(label, ":", 2)
   111  					if len(tagParts) == 2 {
   112  						tags["label."+tagParts[0]] = tagParts[1]
   113  					}
   114  				}
   115  			}
   116  		} else {
   117  			tags[labelName] = labelValue
   118  		}
   119  	}
   120  
   121  }
   122  
   123  func (wfSink *wavefrontSink) send(batch *core.DataBatch) {
   124  
   125  	metricCounter := 0
   126  
   127  	for _, key := range sortedMetricSetKeys(batch.MetricSets) {
   128  		ms := batch.MetricSets[key]
   129  
   130  		// Populate tag map
   131  		tags := make(map[string]string)
   132  		// Make sure all metrics are tagged with the cluster name
   133  		tags["cluster"] = wfSink.ClusterName
   134  		// Add pod labels as tags
   135  		wfSink.addLabelTags(ms, tags)
   136  		metricType := tags["type"]
   137  		if strings.Contains(tags["container_name"], sysSubContainerName) {
   138  			//don't send system subcontainers
   139  			continue
   140  		}
   141  		if wfSink.IncludeContainers == false && strings.Contains(metricType, "pod_container") {
   142  			// the user doesn't want to include container metrics (only pod and above)
   143  			continue
   144  		}
   145  		for _, metricName := range sortedMetricValueKeys(ms.MetricValues) {
   146  			var metricValStr string
   147  			metricValue := ms.MetricValues[metricName]
   148  			if core.ValueInt64 == metricValue.ValueType {
   149  				metricValStr = fmt.Sprintf("%d", metricValue.IntValue)
   150  			} else if core.ValueFloat == metricValue.ValueType { // W
   151  				metricValStr = fmt.Sprintf("%f", metricValue.FloatValue)
   152  			} else {
   153  				//do nothing for now
   154  				metricValStr = ""
   155  			}
   156  			if metricValStr != "" {
   157  				ts := strconv.FormatInt(batch.Timestamp.Unix(), 10)
   158  				source := ""
   159  				if metricType == "cluster" {
   160  					source = wfSink.ClusterName
   161  				} else if metricType == "ns" {
   162  					source = tags["namespace_name"] + "-ns"
   163  				} else {
   164  					source = tags["hostname"]
   165  				}
   166  				tagStr := tagsToString(tags)
   167  				wfSink.sendPoint(wfSink.cleanMetricName(metricType, metricName), metricValStr, ts, source, tagStr)
   168  				metricCounter = metricCounter + 1
   169  			}
   170  		}
   171  		for _, metric := range ms.LabeledMetrics {
   172  			metricName := wfSink.cleanMetricName(metricType, metric.Name)
   173  			metricValStr := ""
   174  			if core.ValueInt64 == metric.ValueType {
   175  				metricValStr = fmt.Sprintf("%d", metric.IntValue)
   176  			} else if core.ValueFloat == metric.ValueType { // W
   177  				metricValStr = fmt.Sprintf("%f", metric.FloatValue)
   178  			} else {
   179  				//do nothing for now
   180  				metricValStr = ""
   181  			}
   182  			if metricValStr != "" {
   183  				ts := strconv.FormatInt(batch.Timestamp.Unix(), 10)
   184  				source := tags["hostname"]
   185  				tagStr := tagsToString(tags)
   186  				for labelName, labelValue := range metric.Labels {
   187  					tagStr += labelName + "=\"" + labelValue + "\" "
   188  				}
   189  				metricCounter = metricCounter + 1
   190  				wfSink.sendPoint(metricName, metricValStr, ts, source, tagStr)
   191  			}
   192  		}
   193  	}
   194  
   195  }
   196  
   197  func (wfSink *wavefrontSink) ExportData(batch *core.DataBatch) {
   198  
   199  	if wfSink.testMode {
   200  		//clear lines from last batch
   201  		wfSink.testReceivedLines = wfSink.testReceivedLines[:0]
   202  		wfSink.send(batch)
   203  		return
   204  	}
   205  
   206  	//make sure we're Connected before sending a real batch
   207  	err := wfSink.connect()
   208  	if err != nil {
   209  		glog.Warning(err)
   210  	}
   211  
   212  	if wfSink.Conn != nil && err == nil {
   213  		wfSink.send(batch)
   214  	}
   215  }
   216  
   217  func (wfSink *wavefrontSink) connect() error {
   218  	var err error
   219  	wfSink.Conn, err = net.DialTimeout("tcp", wfSink.ProxyAddress, time.Second*10)
   220  	if err != nil {
   221  		glog.Warningf("Unable to connect to Wavefront proxy at address: %s", wfSink.ProxyAddress)
   222  		return err
   223  	} else {
   224  		glog.Infof("Connected to Wavefront proxy at address: %s", wfSink.ProxyAddress)
   225  		return nil
   226  	}
   227  }
   228  
   229  func NewWavefrontSink(uri *url.URL) (core.DataSink, error) {
   230  
   231  	storage := &wavefrontSink{
   232  		ProxyAddress:      uri.Scheme + ":" + uri.Opaque,
   233  		ClusterName:       "k8s-cluster",
   234  		Prefix:            "heapster.",
   235  		IncludeLabels:     false,
   236  		IncludeContainers: true,
   237  		testMode:          false,
   238  	}
   239  
   240  	vals := uri.Query()
   241  	if len(vals["clusterName"]) > 0 {
   242  		storage.ClusterName = vals["clusterName"][0]
   243  	}
   244  	if len(vals["prefix"]) > 0 {
   245  		storage.Prefix = vals["prefix"][0]
   246  	}
   247  	if len(vals["includeLabels"]) > 0 {
   248  		incLabels := false
   249  		incLabels, err := strconv.ParseBool(vals["includeLabels"][0])
   250  		if err != nil {
   251  			glog.Warning("Unable to parse the includeLabels argument. This argument is a boolean, please pass \"true\" or \"false\"")
   252  			return nil, err
   253  		}
   254  		storage.IncludeLabels = incLabels
   255  	}
   256  	if len(vals["includeContainers"]) > 0 {
   257  		incContainers := false
   258  		incContainers, err := strconv.ParseBool(vals["includeContainers"][0])
   259  		if err != nil {
   260  			glog.Warning("Unable to parse the includeContainers argument. This argument is a boolean, please pass \"true\" or \"false\"")
   261  			return nil, err
   262  		}
   263  		storage.IncludeContainers = incContainers
   264  	}
   265  	if len(vals["testMode"]) > 0 {
   266  		testMode := false
   267  		testMode, err := strconv.ParseBool(vals["testMode"][0])
   268  		if err != nil {
   269  			glog.Warning("Unable to parse the testMode argument. This argument is a boolean, please pass \"true\" or \"false\"")
   270  			return nil, err
   271  		}
   272  		storage.testMode = testMode
   273  	}
   274  	return storage, nil
   275  }
   276  
   277  func sortedMetricSetKeys(m map[string]*core.MetricSet) []string {
   278  	keys := make([]string, len(m))
   279  	i := 0
   280  	for k := range m {
   281  		keys[i] = k
   282  		i++
   283  	}
   284  	sort.Strings(keys)
   285  	return keys
   286  }
   287  
   288  func sortedLabelKeys(m map[string]string) []string {
   289  	keys := make([]string, len(m))
   290  	i := 0
   291  	for k := range m {
   292  		keys[i] = k
   293  		i++
   294  	}
   295  	sort.Strings(keys)
   296  	return keys
   297  }
   298  
   299  func sortedMetricValueKeys(m map[string]core.MetricValue) []string {
   300  	keys := make([]string, len(m))
   301  	i := 0
   302  	for k := range m {
   303  		keys[i] = k
   304  		i++
   305  	}
   306  	sort.Strings(keys)
   307  	return keys
   308  }