github.com/timstclair/heapster@v0.20.0-alpha1/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  	"time"
    21  
    22  	"k8s.io/heapster/metrics/core"
    23  
    24  	"github.com/golang/glog"
    25  	"golang.org/x/oauth2"
    26  	"golang.org/x/oauth2/google"
    27  	gcm "google.golang.org/api/cloudmonitoring/v2beta2"
    28  	gce "google.golang.org/cloud/compute/metadata"
    29  )
    30  
    31  const (
    32  	metricDomain    = "kubernetes.io"
    33  	customApiPrefix = "custom.cloudmonitoring.googleapis.com"
    34  	maxNumLabels    = 10
    35  	// The largest number of timeseries we can write to per request.
    36  	maxTimeseriesPerRequest = 200
    37  )
    38  
    39  type gcmSink struct {
    40  	project    string
    41  	gcmService *gcm.Service
    42  }
    43  
    44  func (sink *gcmSink) Name() string {
    45  	return "GCM Sink"
    46  }
    47  
    48  func getReq() *gcm.WriteTimeseriesRequest {
    49  	return &gcm.WriteTimeseriesRequest{Timeseries: make([]*gcm.TimeseriesPoint, 0)}
    50  }
    51  
    52  func fullLabelName(name string) string {
    53  	return fmt.Sprintf("%s/%s/label/%s", customApiPrefix, metricDomain, name)
    54  }
    55  
    56  func fullMetricName(name string) string {
    57  	return fmt.Sprintf("%s/%s/%s", customApiPrefix, metricDomain, name)
    58  }
    59  
    60  func (sink *gcmSink) getTimeseriesPoint(timestamp time.Time, labels map[string]string, metric string, val core.MetricValue) *gcm.TimeseriesPoint {
    61  	point := &gcm.Point{
    62  		Start: timestamp.Format(time.RFC3339),
    63  		End:   timestamp.Format(time.RFC3339),
    64  	}
    65  	switch val.ValueType {
    66  	case core.ValueInt64:
    67  		point.Int64Value = &val.IntValue
    68  	case core.ValueFloat:
    69  		v := float64(val.FloatValue)
    70  		point.DoubleValue = &v
    71  	default:
    72  		glog.Errorf("Type not supported %v in %v", val.ValueType, metric)
    73  		return nil
    74  	}
    75  	// For cumulative metric use the provided start time.
    76  	if val.MetricType == core.MetricCumulative {
    77  		point.Start = val.Start.Format(time.RFC3339)
    78  	}
    79  
    80  	supportedLables := core.GcmLabels()
    81  	gcmLabels := make(map[string]string, len(labels))
    82  	for key, value := range labels {
    83  		if _, ok := supportedLables[key]; ok {
    84  			gcmLabels[fullLabelName(key)] = value
    85  		}
    86  	}
    87  	desc := &gcm.TimeseriesDescriptor{
    88  		Project: sink.project,
    89  		Labels:  gcmLabels,
    90  		Metric:  fullMetricName(metric),
    91  	}
    92  
    93  	return &gcm.TimeseriesPoint{Point: point, TimeseriesDesc: desc}
    94  }
    95  
    96  func (sink *gcmSink) sendRequest(req *gcm.WriteTimeseriesRequest) {
    97  	_, err := sink.gcmService.Timeseries.Write(sink.project, req).Do()
    98  	if err != nil {
    99  		glog.Errorf("Error while sending request to GCM %v", err)
   100  	} else {
   101  		glog.V(4).Infof("Successfully sent %v timeserieses to GCM", len(req.Timeseries))
   102  	}
   103  }
   104  
   105  func (sink *gcmSink) ExportData(dataBatch *core.DataBatch) {
   106  	req := getReq()
   107  	for _, metricSet := range dataBatch.MetricSets {
   108  		for metric, val := range metricSet.MetricValues {
   109  			point := sink.getTimeseriesPoint(dataBatch.Timestamp, metricSet.Labels, metric, val)
   110  			if point != nil {
   111  				req.Timeseries = append(req.Timeseries, point)
   112  			}
   113  			if len(req.Timeseries) >= maxTimeseriesPerRequest {
   114  				sink.sendRequest(req)
   115  				req = getReq()
   116  			}
   117  		}
   118  	}
   119  	if len(req.Timeseries) > 0 {
   120  		sink.sendRequest(req)
   121  	}
   122  }
   123  
   124  func (sink *gcmSink) Stop() {
   125  	// nothing needs to be done.
   126  }
   127  
   128  // Adds the specified metrics or updates them if they already exist.
   129  func (sink *gcmSink) register(metrics []core.Metric) error {
   130  	for _, metric := range metrics {
   131  		metricName := fullMetricName(metric.MetricDescriptor.Name)
   132  		if _, err := sink.gcmService.MetricDescriptors.Delete(sink.project, metricName).Do(); err != nil {
   133  			glog.Infof("[GCM] Deleting metric %v failed: %v", metricName, err)
   134  		}
   135  		labels := make([]*gcm.MetricDescriptorLabelDescriptor, 0)
   136  		for _, l := range core.GcmLabels() {
   137  			labels = append(labels, &gcm.MetricDescriptorLabelDescriptor{
   138  				Key:         fullLabelName(l.Key),
   139  				Description: l.Description,
   140  			})
   141  		}
   142  		t := &gcm.MetricDescriptorTypeDescriptor{
   143  			MetricType: metric.MetricDescriptor.Type.String(),
   144  			ValueType:  metric.MetricDescriptor.ValueType.String(),
   145  		}
   146  		desc := &gcm.MetricDescriptor{
   147  			Name:           metricName,
   148  			Project:        sink.project,
   149  			Description:    metric.MetricDescriptor.Description,
   150  			Labels:         labels,
   151  			TypeDescriptor: t,
   152  		}
   153  		if _, err := sink.gcmService.MetricDescriptors.Create(sink.project, desc).Do(); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  func CreateGCMSink(uri *url.URL) (core.DataSink, error) {
   161  	if *uri != (url.URL{}) {
   162  		return nil, fmt.Errorf("gcm sinks don't take arguments")
   163  	}
   164  	// Detect project ID
   165  	projectId, err := gce.ProjectID()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	// Create Google Cloud Monitoring service.
   171  	client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
   172  	gcmService, err := gcm.New(client)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	sink := &gcmSink{project: projectId, gcmService: gcmService}
   178  	if err := sink.register(core.AllMetrics); err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	glog.Infof("created GCM sink")
   183  	return sink, nil
   184  }