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