github.com/jonaz/heapster@v1.3.0-beta.0.0.20170208112634-cd3c15ca3d29/metrics/sinks/monasca/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 monasca
    16  
    17  import (
    18  	"fmt"
    19  	"net/http"
    20  	"net/url"
    21  	"reflect"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/golang/glog"
    27  	"k8s.io/heapster/metrics/core"
    28  )
    29  
    30  type monascaSink struct {
    31  	client Client
    32  	sync.RWMutex
    33  	numberOfFailures int
    34  }
    35  
    36  // Pushes the specified metric measurement to the Monasca API.
    37  // The Timeseries are transformed to monasca metrics beforehand.
    38  // Timeseries that cannot be translated to monasca metrics are skipped.
    39  func (sink *monascaSink) ExportData(dataBatch *core.DataBatch) {
    40  	sink.Lock()
    41  	defer sink.Unlock()
    42  
    43  	metrics := sink.processMetrics(dataBatch)
    44  	code, response, err := sink.client.SendRequest("POST", "/metrics", metrics)
    45  	if err != nil {
    46  		glog.Errorf("%s", err)
    47  		sink.numberOfFailures++
    48  		return
    49  	}
    50  	if code != http.StatusNoContent {
    51  		glog.Error(response)
    52  		sink.numberOfFailures++
    53  	}
    54  }
    55  
    56  // a monasca metric definition
    57  type metric struct {
    58  	Name       string            `json:"name"`
    59  	Dimensions map[string]string `json:"dimensions"`
    60  	Timestamp  int64             `json:"timestamp"`
    61  	Value      float64           `json:"value"`
    62  	ValueMeta  map[string]string `json:"value_meta"`
    63  }
    64  
    65  func (sink *monascaSink) processMetrics(dataBatch *core.DataBatch) []metric {
    66  	metrics := []metric{}
    67  	for _, metricSet := range dataBatch.MetricSets {
    68  		m := sink.processMetricSet(dataBatch, metricSet)
    69  		metrics = append(metrics, m...)
    70  	}
    71  	return metrics
    72  }
    73  
    74  func (sink *monascaSink) processMetricSet(dataBatch *core.DataBatch, metricSet *core.MetricSet) []metric {
    75  	metrics := []metric{}
    76  
    77  	// process unlabeled metrics
    78  	for metricName, metricValue := range metricSet.MetricValues {
    79  		m := sink.processMetric(metricSet.Labels, metricName, dataBatch.Timestamp, metricValue.GetValue())
    80  		if nil != m {
    81  			metrics = append(metrics, *m)
    82  		}
    83  	}
    84  
    85  	// process labeled metrics
    86  	for _, metric := range metricSet.LabeledMetrics {
    87  		labels := map[string]string{}
    88  		for k, v := range metricSet.Labels {
    89  			labels[k] = v
    90  		}
    91  		for k, v := range metric.Labels {
    92  			labels[k] = v
    93  		}
    94  		m := sink.processMetric(labels, metric.Name, dataBatch.Timestamp, metric.GetValue())
    95  		if nil != m {
    96  			metrics = append(metrics, *m)
    97  		}
    98  	}
    99  	return metrics
   100  }
   101  
   102  func (sink *monascaSink) processMetric(labels map[string]string, name string, timestamp time.Time, value interface{}) *metric {
   103  	val, err := sink.convertValue(value)
   104  	if err != nil {
   105  		glog.Warningf("Metric cannot be pushed to monasca. %#v", value)
   106  		return nil
   107  	}
   108  	dims, valueMeta := sink.processLabels(labels)
   109  	m := metric{
   110  		Name:       strings.Replace(name, "/", ".", -1),
   111  		Dimensions: dims,
   112  		Timestamp:  (timestamp.UnixNano() / 1000000),
   113  		Value:      val,
   114  		ValueMeta:  valueMeta,
   115  	}
   116  	return &m
   117  }
   118  
   119  // convert the Timeseries value to a monasca value
   120  func (sink *monascaSink) convertValue(val interface{}) (float64, error) {
   121  	switch val.(type) {
   122  	case int:
   123  		return float64(val.(int)), nil
   124  	case int64:
   125  		return float64(val.(int64)), nil
   126  	case bool:
   127  		if val.(bool) {
   128  			return 1.0, nil
   129  		}
   130  		return 0.0, nil
   131  	case float32:
   132  		return float64(val.(float32)), nil
   133  	case float64:
   134  		return val.(float64), nil
   135  	}
   136  	return 0.0, fmt.Errorf("Unsupported monasca metric value type %T", reflect.TypeOf(val))
   137  }
   138  
   139  const (
   140  	emptyValue       = "none"
   141  	monascaComponent = "component"
   142  	monascaService   = "service"
   143  	monascaHostname  = "hostname"
   144  )
   145  
   146  // preprocesses heapster labels, splitting into monasca dimensions and monasca meta-values
   147  func (sink *monascaSink) processLabels(labels map[string]string) (map[string]string, map[string]string) {
   148  	dims := map[string]string{}
   149  	valueMeta := map[string]string{}
   150  
   151  	// labels to dimensions
   152  	dims[monascaComponent] = sink.processDimension(labels[core.LabelPodName.Key])
   153  	dims[monascaHostname] = sink.processDimension(labels[core.LabelHostname.Key])
   154  	dims[core.LabelContainerName.Key] = sink.processDimension(labels[core.LabelContainerName.Key])
   155  	dims[monascaService] = "kubernetes"
   156  
   157  	// labels to valueMeta
   158  	for i, v := range labels {
   159  		if i != core.LabelPodName.Key && i != core.LabelHostname.Key &&
   160  			i != core.LabelContainerName.Key && v != "" {
   161  			valueMeta[i] = strings.Replace(v, ",", " ", -1)
   162  		}
   163  	}
   164  	return dims, valueMeta
   165  }
   166  
   167  // creates a valid dimension value
   168  func (sink *monascaSink) processDimension(value string) string {
   169  	if value != "" {
   170  		v := strings.Replace(value, "/", ".", -1)
   171  		return strings.Replace(v, ",", " ", -1)
   172  	}
   173  	return emptyValue
   174  }
   175  
   176  func (sink *monascaSink) Name() string {
   177  	return "Monasca Sink"
   178  }
   179  
   180  func (sink *monascaSink) Stop() {
   181  	// Nothing needs to be done
   182  }
   183  
   184  // CreateMonascaSink creates a monasca sink that can consume the Monasca APIs to create metrics.
   185  func CreateMonascaSink(uri *url.URL) (core.DataSink, error) {
   186  	opts := uri.Query()
   187  	config := NewConfig(opts)
   188  	client, err := NewMonascaClient(config)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	monascaSink := monascaSink{client: client}
   193  	glog.Infof("Created Monasca sink. Monasca server running on: %s", client.GetURL().String())
   194  	return &monascaSink, nil
   195  }