github.com/aclisp/heapster@v0.19.2-0.20160613100040-51756f899a96/metrics/sinks/gcm/gcm.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 gcm
    16  
    17  import (
    18  	"fmt"
    19  	"net/url"
    20  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	gce_util "k8s.io/heapster/common/gce"
    25  	"k8s.io/heapster/metrics/core"
    26  
    27  	"github.com/golang/glog"
    28  	"golang.org/x/oauth2"
    29  	"golang.org/x/oauth2/google"
    30  	gcm "google.golang.org/api/cloudmonitoring/v2beta2"
    31  	gce "google.golang.org/cloud/compute/metadata"
    32  )
    33  
    34  const (
    35  	metricDomain    = "kubernetes.io"
    36  	customApiPrefix = "custom.cloudmonitoring.googleapis.com"
    37  	maxNumLabels    = 10
    38  	// The largest number of timeseries we can write to per request.
    39  	maxTimeseriesPerRequest = 200
    40  )
    41  
    42  type MetricFilter int8
    43  
    44  const (
    45  	metricsAll MetricFilter = iota
    46  	metricsOnlyAutoscaling
    47  )
    48  
    49  type gcmSink struct {
    50  	sync.RWMutex
    51  	registered   bool
    52  	project      string
    53  	metricFilter MetricFilter
    54  	gcmService   *gcm.Service
    55  }
    56  
    57  func (sink *gcmSink) Name() string {
    58  	return "GCM Sink"
    59  }
    60  
    61  func getReq() *gcm.WriteTimeseriesRequest {
    62  	return &gcm.WriteTimeseriesRequest{Timeseries: make([]*gcm.TimeseriesPoint, 0)}
    63  }
    64  
    65  func fullLabelName(name string) string {
    66  	// handle correctly GCE specific labels
    67  	if !strings.Contains(name, "compute.googleapis.com") {
    68  		return fmt.Sprintf("%s/%s/label/%s", customApiPrefix, metricDomain, name)
    69  	}
    70  	return name
    71  }
    72  
    73  func fullMetricName(name string) string {
    74  	return fmt.Sprintf("%s/%s/%s", customApiPrefix, metricDomain, name)
    75  }
    76  
    77  func (sink *gcmSink) getTimeseriesPoint(timestamp time.Time, labels map[string]string, metric string, val core.MetricValue, createTime time.Time) *gcm.TimeseriesPoint {
    78  	point := &gcm.Point{
    79  		Start: timestamp.Format(time.RFC3339),
    80  		End:   timestamp.Format(time.RFC3339),
    81  	}
    82  	switch val.ValueType {
    83  	case core.ValueInt64:
    84  		point.Int64Value = &val.IntValue
    85  	case core.ValueFloat:
    86  		v := float64(val.FloatValue)
    87  		point.DoubleValue = &v
    88  	default:
    89  		glog.Errorf("Type not supported %v in %v", val.ValueType, metric)
    90  		return nil
    91  	}
    92  	// For cumulative metric use the provided start time.
    93  	if val.MetricType == core.MetricCumulative {
    94  		point.Start = createTime.Format(time.RFC3339)
    95  	}
    96  
    97  	finalLabels := make(map[string]string)
    98  	if core.IsNodeAutoscalingMetric(metric) {
    99  		// All and autoscaling. Do not populate for other filters.
   100  		if sink.metricFilter != metricsAll &&
   101  			sink.metricFilter != metricsOnlyAutoscaling {
   102  			return nil
   103  		}
   104  
   105  		finalLabels[fullLabelName(core.LabelHostname.Key)] = labels[core.LabelHostname.Key]
   106  		finalLabels[fullLabelName(core.LabelGCEResourceID.Key)] = labels[core.LabelHostID.Key]
   107  		finalLabels[fullLabelName(core.LabelGCEResourceType.Key)] = "instance"
   108  	} else {
   109  		// Only all.
   110  		if sink.metricFilter != metricsAll {
   111  			return nil
   112  		}
   113  		supportedLables := core.GcmLabels()
   114  		for key, value := range labels {
   115  			if _, ok := supportedLables[key]; ok {
   116  				finalLabels[fullLabelName(key)] = value
   117  			}
   118  		}
   119  	}
   120  	desc := &gcm.TimeseriesDescriptor{
   121  		Project: sink.project,
   122  		Labels:  finalLabels,
   123  		Metric:  fullMetricName(metric),
   124  	}
   125  
   126  	return &gcm.TimeseriesPoint{Point: point, TimeseriesDesc: desc}
   127  }
   128  
   129  func (sink *gcmSink) getTimeseriesPointForLabeledMetrics(timestamp time.Time, labels map[string]string, metric core.LabeledMetric, createTime time.Time) *gcm.TimeseriesPoint {
   130  	// Only all. There are no atuoscaling labeled metrics.
   131  	if sink.metricFilter != metricsAll {
   132  		return nil
   133  	}
   134  
   135  	point := &gcm.Point{
   136  		Start: timestamp.Format(time.RFC3339),
   137  		End:   timestamp.Format(time.RFC3339),
   138  	}
   139  	switch metric.ValueType {
   140  	case core.ValueInt64:
   141  		point.Int64Value = &metric.IntValue
   142  	case core.ValueFloat:
   143  		v := float64(metric.FloatValue)
   144  		point.DoubleValue = &v
   145  	default:
   146  		glog.Errorf("Type not supported %v in %v", metric.ValueType, metric)
   147  		return nil
   148  	}
   149  	// For cumulative metric use the provided start time.
   150  	if metric.MetricType == core.MetricCumulative {
   151  		point.Start = createTime.Format(time.RFC3339)
   152  	}
   153  
   154  	finalLabels := make(map[string]string)
   155  	supportedLables := core.GcmLabels()
   156  	for key, value := range labels {
   157  		if _, ok := supportedLables[key]; ok {
   158  			finalLabels[fullLabelName(key)] = value
   159  		}
   160  	}
   161  	for key, value := range metric.Labels {
   162  		if _, ok := supportedLables[key]; ok {
   163  			finalLabels[fullLabelName(key)] = value
   164  		}
   165  	}
   166  
   167  	desc := &gcm.TimeseriesDescriptor{
   168  		Project: sink.project,
   169  		Labels:  finalLabels,
   170  		Metric:  fullMetricName(metric.Name),
   171  	}
   172  
   173  	return &gcm.TimeseriesPoint{Point: point, TimeseriesDesc: desc}
   174  }
   175  
   176  func (sink *gcmSink) sendRequest(req *gcm.WriteTimeseriesRequest) {
   177  	_, err := sink.gcmService.Timeseries.Write(sink.project, req).Do()
   178  	if err != nil {
   179  		glog.Errorf("Error while sending request to GCM %v", err)
   180  	} else {
   181  		glog.V(4).Infof("Successfully sent %v timeserieses to GCM", len(req.Timeseries))
   182  	}
   183  }
   184  
   185  func (sink *gcmSink) ExportData(dataBatch *core.DataBatch) {
   186  	if err := sink.registerAllMetrics(); err != nil {
   187  		glog.Warningf("Error during metrics registration: %v", err)
   188  		return
   189  	}
   190  
   191  	req := getReq()
   192  	for _, metricSet := range dataBatch.MetricSets {
   193  		for metric, val := range metricSet.MetricValues {
   194  			point := sink.getTimeseriesPoint(dataBatch.Timestamp, metricSet.Labels, metric, val, metricSet.CreateTime)
   195  			if point != nil {
   196  				req.Timeseries = append(req.Timeseries, point)
   197  			}
   198  			if len(req.Timeseries) >= maxTimeseriesPerRequest {
   199  				sink.sendRequest(req)
   200  				req = getReq()
   201  			}
   202  		}
   203  		for _, metric := range metricSet.LabeledMetrics {
   204  			point := sink.getTimeseriesPointForLabeledMetrics(dataBatch.Timestamp, metricSet.Labels, metric, metricSet.CreateTime)
   205  			if point != nil {
   206  				req.Timeseries = append(req.Timeseries, point)
   207  			}
   208  			if len(req.Timeseries) >= maxTimeseriesPerRequest {
   209  				sink.sendRequest(req)
   210  				req = getReq()
   211  			}
   212  		}
   213  	}
   214  	if len(req.Timeseries) > 0 {
   215  		sink.sendRequest(req)
   216  	}
   217  }
   218  
   219  func (sink *gcmSink) Stop() {
   220  	// nothing needs to be done.
   221  }
   222  
   223  func (sink *gcmSink) registerAllMetrics() error {
   224  	return sink.register(core.AllMetrics)
   225  }
   226  
   227  // Adds the specified metrics or updates them if they already exist.
   228  func (sink *gcmSink) register(metrics []core.Metric) error {
   229  	sink.Lock()
   230  	defer sink.Unlock()
   231  	if sink.registered {
   232  		return nil
   233  	}
   234  
   235  	for _, metric := range metrics {
   236  		metricName := fullMetricName(metric.MetricDescriptor.Name)
   237  		if _, err := sink.gcmService.MetricDescriptors.Delete(sink.project, metricName).Do(); err != nil {
   238  			glog.Infof("[GCM] Deleting metric %v failed: %v", metricName, err)
   239  		}
   240  		labels := make([]*gcm.MetricDescriptorLabelDescriptor, 0)
   241  
   242  		// Node autoscaling metrics have special labels.
   243  		if core.IsNodeAutoscalingMetric(metric.MetricDescriptor.Name) {
   244  			// All and autoscaling. Do not populate for other filters.
   245  			if sink.metricFilter != metricsAll &&
   246  				sink.metricFilter != metricsOnlyAutoscaling {
   247  				continue
   248  			}
   249  
   250  			for _, l := range core.GcmNodeAutoscalingLabels() {
   251  				labels = append(labels, &gcm.MetricDescriptorLabelDescriptor{
   252  					Key:         fullLabelName(l.Key),
   253  					Description: l.Description,
   254  				})
   255  			}
   256  		} else {
   257  			// Only all.
   258  			if sink.metricFilter != metricsAll {
   259  				continue
   260  			}
   261  
   262  			for _, l := range core.GcmLabels() {
   263  				labels = append(labels, &gcm.MetricDescriptorLabelDescriptor{
   264  					Key:         fullLabelName(l.Key),
   265  					Description: l.Description,
   266  				})
   267  			}
   268  		}
   269  
   270  		t := &gcm.MetricDescriptorTypeDescriptor{
   271  			MetricType: metric.MetricDescriptor.Type.String(),
   272  			ValueType:  metric.MetricDescriptor.ValueType.String(),
   273  		}
   274  		desc := &gcm.MetricDescriptor{
   275  			Name:           metricName,
   276  			Project:        sink.project,
   277  			Description:    metric.MetricDescriptor.Description,
   278  			Labels:         labels,
   279  			TypeDescriptor: t,
   280  		}
   281  		if _, err := sink.gcmService.MetricDescriptors.Create(sink.project, desc).Do(); err != nil {
   282  			return err
   283  		}
   284  	}
   285  	sink.registered = true
   286  	return nil
   287  }
   288  
   289  func CreateGCMSink(uri *url.URL) (core.DataSink, error) {
   290  	if len(uri.Scheme) > 0 {
   291  		return nil, fmt.Errorf("scheme should not be set for GCM sink")
   292  	}
   293  	if len(uri.Host) > 0 {
   294  		return nil, fmt.Errorf("host should not be set for GCM sink")
   295  	}
   296  
   297  	opts, err := url.ParseQuery(uri.RawQuery)
   298  
   299  	metrics := "all"
   300  	if len(opts["metrics"]) > 0 {
   301  		metrics = opts["metrics"][0]
   302  	}
   303  	var metricFilter MetricFilter = metricsAll
   304  	switch metrics {
   305  	case "all":
   306  		metricFilter = metricsAll
   307  	case "autoscaling":
   308  		metricFilter = metricsOnlyAutoscaling
   309  	default:
   310  		return nil, fmt.Errorf("invalid metrics parameter: %s", metrics)
   311  	}
   312  
   313  	if err := gce_util.EnsureOnGCE(); err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	// Detect project ID
   318  	projectId, err := gce.ProjectID()
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	// Create Google Cloud Monitoring service.
   324  	client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
   325  	gcmService, err := gcm.New(client)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	sink := &gcmSink{
   331  		registered:   false,
   332  		project:      projectId,
   333  		gcmService:   gcmService,
   334  		metricFilter: metricFilter,
   335  	}
   336  	glog.Infof("created GCM sink")
   337  	if err := sink.registerAllMetrics(); err != nil {
   338  		glog.Warningf("Error during metrics registration: %v", err)
   339  	}
   340  	return sink, nil
   341  }