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