yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/monitor.go (about)

     1  // Copyright 2019 Yunion
     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 azure
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"net/url"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/errors"
    28  	"yunion.io/x/pkg/util/timeutils"
    29  	"yunion.io/x/pkg/utils"
    30  
    31  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    32  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    33  	"yunion.io/x/onecloud/pkg/util/httputils"
    34  )
    35  
    36  type ResponseMetirc struct {
    37  	// Cost - The integer value representing the cost of the query, for data case.
    38  	Cost float64 `json:"cost,omitempty"`
    39  	// Timespan - The timespan for which the data was retrieved. Its value consists of two datetimes concatenated, separated by '/'.  This may be adjusted in the future and returned back from what was originally requested.
    40  	Timespan string `json:"timespan,omitempty"`
    41  	// Interval - The interval (window size) for which the metric data was returned in.  This may be adjusted in the future and returned back from what was originally requested.  This is not present if a metadata request was made.
    42  	Interval string `json:"interval,omitempty"`
    43  	// Namespace - The namespace of the metrics been queried
    44  	Namespace string `json:"namespace,omitempty"`
    45  	// Resourceregion - The region of the resource been queried for metrics.
    46  	Resourceregion string `json:"resourceregion,omitempty"`
    47  	// Value - the value of the collection.
    48  	Value []Metric `json:"value,omitempty"`
    49  }
    50  
    51  // Metric the result data of a query.
    52  type Metric struct {
    53  	// ID - the metric Id.
    54  	ID string `json:"id,omitempty"`
    55  	// Type - the resource type of the metric resource.
    56  	Type string `json:"type,omitempty"`
    57  	// Name - the name and the display name of the metric, i.e. it is localizable string.
    58  	Name LocalizableString `json:"name,omitempty"`
    59  	// Unit - the unit of the metric. Possible values include: 'UnitCount', 'UnitBytes', 'UnitSeconds', 'UnitCountPerSecond', 'UnitBytesPerSecond', 'UnitPercent', 'UnitMilliSeconds', 'UnitByteSeconds', 'UnitUnspecified', 'UnitCores', 'UnitMilliCores', 'UnitNanoCores', 'UnitBitsPerSecond'
    60  	Unit string `json:"unit,omitempty"`
    61  	// Timeseries - the time series returned when a data query is performed.
    62  	Timeseries []TimeSeriesElement `json:"timeseries,omitempty"`
    63  }
    64  
    65  type TimeSeriesElement struct {
    66  	// Metadatavalues - the metadata values returned if $filter was specified in the call.
    67  	Metadatavalues []MetadataValue `json:"metadatavalues,omitempty"`
    68  	// Data - An array of data points representing the metric values.  This is only returned if a result type of data is specified.
    69  	Data []MetricValue `json:"data,omitempty"`
    70  }
    71  
    72  type MetadataValue struct {
    73  	// Name - the name of the metadata.
    74  	Name LocalizableString `json:"name,omitempty"`
    75  	// Value - the value of the metadata.
    76  	Value string `json:"value,omitempty"`
    77  }
    78  
    79  type LocalizableString struct {
    80  	// Value - the invariant value.
    81  	Value string `json:"value,omitempty"`
    82  	// LocalizedValue - the locale specific value.
    83  	LocalizedValue string `json:"localizedValue,omitempty"`
    84  }
    85  
    86  type MetricValue struct {
    87  	// TimeStamp - the timestamp for the metric value in ISO 8601 format.
    88  	TimeStamp time.Time `json:"timeStamp,omitempty"`
    89  	// Average - the average value in the time range.
    90  	Average float64 `json:"average,omitempty"`
    91  	// Minimum - the least value in the time range.
    92  	Minimum float64 `json:"minimum,omitempty"`
    93  	// Maximum - the greatest value in the time range.
    94  	Maximum float64 `json:"maximum,omitempty"`
    95  	// Total - the sum of all of the values in the time range.
    96  	Total float64 `json:"total,omitempty"`
    97  	// Count - the number of samples in the time range. Can be used to determine the number of values that contributed to the average value.
    98  	Count float64 `json:"count,omitempty"`
    99  }
   100  
   101  func (self MetricValue) GetValue() float64 {
   102  	if self.Average > 0 {
   103  		return self.Average
   104  	}
   105  	if self.Total > 0 {
   106  		return self.Total
   107  	}
   108  	if self.Count > 0 {
   109  		return self.Count
   110  	}
   111  	if self.Minimum > 0 {
   112  		return self.Minimum
   113  	}
   114  	return self.Maximum
   115  }
   116  
   117  const (
   118  	RDS_TYPE_SERVERS          = "servers"
   119  	RDS_TYPE_FLEXIBLE_SERVERS = "flexibleServers"
   120  )
   121  
   122  // https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported
   123  func (self *SAzureClient) GetMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   124  	switch opts.ResourceType {
   125  	case cloudprovider.METRIC_RESOURCE_TYPE_RDS:
   126  		return self.GetRdsMetrics(opts)
   127  	case cloudprovider.METRIC_RESOURCE_TYPE_SERVER:
   128  		return self.GetEcsMetrics(opts)
   129  	case cloudprovider.METRIC_RESOURCE_TYPE_REDIS:
   130  		return self.GetRedisMetrics(opts)
   131  	case cloudprovider.METRIC_RESOURCE_TYPE_LB:
   132  		return self.GetLbMetrics(opts)
   133  	case cloudprovider.METRIC_RESOURCE_TYPE_K8S:
   134  		return self.GetK8sMetrics(opts)
   135  	default:
   136  		return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "%s", opts.ResourceType)
   137  	}
   138  }
   139  
   140  type AzureTableMetricDataValue struct {
   141  	Timestamp    time.Time
   142  	Average      float64
   143  	Count        float64
   144  	CounterName  string
   145  	DeploymentId string
   146  	Host         string
   147  	Total        float64
   148  	Maximum      float64
   149  	Minimum      float64
   150  }
   151  
   152  func (self AzureTableMetricDataValue) GetValue() float64 {
   153  	if self.Average > 0 {
   154  		return self.Average
   155  	}
   156  	if self.Count > 0 {
   157  		return self.Count
   158  	}
   159  	if self.Minimum > 0 {
   160  		return self.Minimum
   161  	}
   162  	return self.Maximum
   163  }
   164  
   165  type AzureTableMetricData struct {
   166  	Value []AzureTableMetricDataValue
   167  }
   168  
   169  func (self *SAzureClient) GetEcsMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   170  	metricnamespace := "Microsoft.Compute/virtualMachines"
   171  	metricnames := "Percentage CPU,Network In Total,Network Out Total,Disk Read Bytes,Disk Write Bytes,Disk Read Operations/Sec,Disk Write Operations/Sec"
   172  	if strings.Contains(opts.ResourceId, "microsoft.classiccompute/virtualmachines") {
   173  		metricnamespace = "microsoft.classiccompute/virtualmachines"
   174  	}
   175  	ret, err := self.getMetricValues(opts.ResourceId, metricnamespace, metricnames, nil, "", opts.StartTime, opts.EndTime)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	for metricType, names := range map[cloudprovider.TMetricType][]string{
   181  		cloudprovider.VM_METRIC_TYPE_MEM_USAGE:  {"/builtin/memory/percentusedmemory", "\\Memory\\% Committed Bytes In Use"},
   182  		cloudprovider.VM_METRIC_TYPE_DISK_USAGE: {"/builtin/filesystem/percentusedspace", "\\LogicalDisk(_Total)\\% Free Space"},
   183  	} {
   184  		filters := []string{}
   185  		for _, name := range names {
   186  			filter := fmt.Sprintf("name.value eq '%s'", name)
   187  			filters = append(filters, filter)
   188  		}
   189  		metricDefinitions, err := self.getMetricDefinitions(opts.ResourceId, strings.Join(filters, " or "))
   190  		if err != nil {
   191  			log.Errorf("getMetricDefinitions error: %v", err)
   192  			continue
   193  		}
   194  		metric := cloudprovider.MetricValues{}
   195  		metric.MetricType = metricType
   196  		header := http.Header{}
   197  		header.Set("accept", "application/json;odata=minimalmetadata")
   198  		for _, definition := range metricDefinitions.Value {
   199  			for _, tables := range definition.MetricAvailabilities {
   200  				if tables.TimeGrain != "PT1M" {
   201  					continue
   202  				}
   203  				for _, table := range tables.Location.TableInfo {
   204  					if table.EndTime.Before(opts.EndTime) {
   205  						continue
   206  					}
   207  					url := fmt.Sprintf("%s%s%s", tables.Location.TableEndpoint, table.TableName, table.SasToken)
   208  					_, resp, err := httputils.JSONRequest(httputils.GetDefaultClient(), context.Background(), httputils.GET, url, header, nil, self.debug)
   209  					if err != nil {
   210  						log.Errorf("request %s error: %v", url, err)
   211  						continue
   212  					}
   213  					values := &AzureTableMetricData{}
   214  					resp.Unmarshal(values)
   215  					for _, v := range values.Value {
   216  						name := strings.ReplaceAll(v.CounterName, `\\`, `\`)
   217  						if !utils.IsInStringArray(name, names) {
   218  							continue
   219  						}
   220  						if v.Timestamp.After(opts.StartTime) && v.Timestamp.Before(opts.EndTime) {
   221  							value := v.GetValue()
   222  							if metricType == cloudprovider.VM_METRIC_TYPE_DISK_USAGE && strings.Contains(strings.ToLower(name), "free") {
   223  								value = 100 - value
   224  							}
   225  							metric.Values = append(metric.Values, cloudprovider.MetricValue{
   226  								Timestamp: v.Timestamp,
   227  								Value:     value,
   228  							})
   229  						}
   230  					}
   231  				}
   232  			}
   233  		}
   234  		if len(metric.Values) > 0 {
   235  			ret = append(ret, metric)
   236  		}
   237  	}
   238  
   239  	if len(opts.OsType) == 0 || strings.Contains(strings.ToLower(opts.OsType), "win") {
   240  		workspaces, err := self.GetLoganalyticsWorkspaces()
   241  		if err != nil {
   242  			return ret, nil
   243  		}
   244  		winmetric := cloudprovider.MetricValues{}
   245  		winmetric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_USAGE
   246  		winmetric.Values = []cloudprovider.MetricValue{}
   247  		for i := range workspaces {
   248  			data, err := self.GetInstanceDiskUsage(workspaces[i].SLoganalyticsWorkspaceProperties.CustomerId, opts.ResourceId, opts.StartTime, opts.EndTime)
   249  			if err != nil {
   250  				continue
   251  			}
   252  			for i := range data {
   253  				for j := range data[i].Rows {
   254  					if len(data[i].Rows[j]) == 5 {
   255  						date, device, free := data[i].Rows[j][0], data[i].Rows[j][1], data[i].Rows[j][2]
   256  						dataTime, err := timeutils.ParseTimeStr(date)
   257  						if err != nil {
   258  							continue
   259  						}
   260  						if dataTime.Second() > 15 {
   261  							continue
   262  						}
   263  						freeSize, err := strconv.ParseFloat(free, 64)
   264  						if err != nil {
   265  							continue
   266  						}
   267  						winmetric.Values = append(winmetric.Values, cloudprovider.MetricValue{
   268  							Timestamp: dataTime,
   269  							Value:     100 - freeSize,
   270  							Tags:      map[string]string{cloudprovider.METRIC_TAG_DEVICE: device},
   271  						})
   272  					}
   273  				}
   274  			}
   275  		}
   276  		if len(winmetric.Values) > 0 {
   277  			ret = append(ret, winmetric)
   278  		}
   279  	}
   280  
   281  	return ret, nil
   282  }
   283  
   284  func (self *SAzureClient) GetRedisMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   285  	metricnamespace := "Microsoft.Cache/redis"
   286  	metricnames := "percentProcessorTime,usedmemorypercentage,connectedclients,operationsPerSecond,alltotalkeys,expiredkeys,usedmemory"
   287  	return self.getMetricValues(opts.ResourceId, metricnamespace, metricnames, nil, "", opts.StartTime, opts.EndTime)
   288  }
   289  
   290  func (self *SAzureClient) GetLbMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   291  	metricnamespace := "Microsoft.Network/loadBalancers"
   292  	metricnames := "SnatConnectionCount,UsedSnatPorts"
   293  	return self.getMetricValues(opts.ResourceId, metricnamespace, metricnames, nil, "", opts.StartTime, opts.EndTime)
   294  }
   295  
   296  func (self *SAzureClient) GetK8sMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   297  	metricnamespace := "Microsoft.ContainerService/managedClusters"
   298  	metricnames := "node_cpu_usage_percentage,node_memory_rss_percentage,node_disk_usage_percentage,node_network_in_bytes,node_network_out_bytes"
   299  	filter := fmt.Sprintf("node eq '*'")
   300  	return self.getMetricValues(opts.ResourceId, metricnamespace, metricnames, nil, filter, opts.StartTime, opts.EndTime)
   301  }
   302  
   303  func (self *SAzureClient) GetRdsMetrics(opts *cloudprovider.MetricListOptions) ([]cloudprovider.MetricValues, error) {
   304  	ret := []cloudprovider.MetricValues{}
   305  	metricnamespace, metricnames := "", ""
   306  	rdsType := RDS_TYPE_SERVERS
   307  	if strings.Contains(opts.ResourceId, strings.ToLower(RDS_TYPE_FLEXIBLE_SERVERS)) {
   308  		rdsType = RDS_TYPE_FLEXIBLE_SERVERS
   309  	}
   310  	switch opts.Engine {
   311  	case api.DBINSTANCE_TYPE_MYSQL:
   312  		metricnamespace = fmt.Sprintf("Microsoft.DBforMySQL/%s", rdsType)
   313  		metricnames = "cpu_percent,memory_percent,storage_percent,network_bytes_ingress,network_bytes_egress,io_consumption_percent,connections_failed,active_connections"
   314  	case api.DBINSTANCE_TYPE_MARIADB:
   315  		metricnamespace = "Microsoft.DBforMariaDB/servers"
   316  		metricnames = "cpu_percent,memory_percent,storage_percent,network_bytes_ingress,network_bytes_egress,io_consumption_percent,connections_failed,active_connections"
   317  	case api.DBINSTANCE_TYPE_SQLSERVER:
   318  		metricnamespace = "Microsoft.Sql/servers/databases"
   319  		metricnames = "cpu_percent,connection_successful,sqlserver_process_memory_percent,connection_successful,connection_failed"
   320  		result := struct {
   321  			Value []SSQLServerDatabase
   322  		}{}
   323  		err := self.get(opts.ResourceId+"/databases", url.Values{}, &result)
   324  		if err != nil {
   325  			return nil, err
   326  		}
   327  		for i := range result.Value {
   328  			if result.Value[i].Name == "master" {
   329  				continue
   330  			}
   331  			metrics, err := self.getMetricValues(result.Value[i].ID, metricnamespace, metricnames, map[string]string{cloudprovider.METRIC_TAG_DATABASE: result.Value[i].Name}, "", opts.StartTime, opts.EndTime)
   332  			if err != nil {
   333  				log.Errorf("error: %v", err)
   334  				continue
   335  			}
   336  			for j := range metrics {
   337  				metrics[j].Id = opts.ResourceId
   338  				ret = append(ret, metrics[j])
   339  			}
   340  		}
   341  		return ret, nil
   342  	case api.DBINSTANCE_TYPE_POSTGRESQL:
   343  		metricnamespace = fmt.Sprintf("Microsoft.DBforPostgreSQL/%s", rdsType)
   344  		metricnames = "cpu_percent,memory_percent,storage_percent,network_bytes_ingress,network_bytes_egress,io_consumption_percent,connections_failed,active_connections"
   345  	default:
   346  		return nil, errors.Wrapf(cloudprovider.ErrNotSupported, opts.Engine)
   347  	}
   348  	return self.getMetricValues(opts.ResourceId, metricnamespace, metricnames, nil, "", opts.StartTime, opts.EndTime)
   349  }
   350  
   351  type MetrifDefinitions struct {
   352  	Id    string
   353  	Value []MetrifDefinition
   354  }
   355  
   356  type MetrifDefinition struct {
   357  	Name struct {
   358  		Value          string
   359  		LocalizedValue string
   360  	}
   361  	Category               string
   362  	Unit                   string
   363  	PrimaryAggregationType string
   364  	ResourceUri            string
   365  	ResourceId             string
   366  	MetricAvailabilities   []struct {
   367  		TimeGrain string
   368  		Retention string
   369  		Location  struct {
   370  			TableEndpoint string
   371  			TableInfo     []struct {
   372  				TableName              string
   373  				StartTime              time.Time
   374  				EndTime                time.Time
   375  				SasToken               string
   376  				SasTokenExpirationTime string
   377  			}
   378  			PartitionKey string
   379  		}
   380  	}
   381  	Id string
   382  }
   383  
   384  func (self *SAzureClient) getMetricDefinitions(resourceId, filter string) (*MetrifDefinitions, error) {
   385  	params := url.Values{}
   386  	params.Set("api-version", "2015-07-01")
   387  	resource := fmt.Sprintf("%s/providers/microsoft.insights/metricDefinitions", resourceId)
   388  	if len(filter) > 0 {
   389  		params.Set("$filter", filter)
   390  	}
   391  	result := &MetrifDefinitions{}
   392  	err := self.get(resource, params, &result)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	return result, nil
   397  }
   398  
   399  func (self *SAzureClient) getMetricValues(resourceId, metricnamespace, metricnames string, metricTag map[string]string, filter string, startTime, endTime time.Time) ([]cloudprovider.MetricValues, error) {
   400  	ret := []cloudprovider.MetricValues{}
   401  	params := url.Values{}
   402  	params.Set("interval", "PT1M")
   403  	params.Set("api-version", "2018-01-01")
   404  	params.Set("timespan", startTime.UTC().Format(time.RFC3339)+"/"+endTime.UTC().Format(time.RFC3339))
   405  	resource := fmt.Sprintf("%s/providers/microsoft.insights/metrics", resourceId)
   406  	params.Set("aggregation", "Average,Count,Maximum,Total")
   407  	params.Set("metricnamespace", metricnamespace)
   408  	params.Set("metricnames", metricnames)
   409  	if len(filter) > 0 {
   410  		params.Set("$filter", filter)
   411  	}
   412  	elements := ResponseMetirc{}
   413  	err := self.get(resource, params, &elements)
   414  	if err != nil {
   415  		return ret, err
   416  	}
   417  	for i := range elements.Value {
   418  		element := elements.Value[i]
   419  		metric := cloudprovider.MetricValues{
   420  			Unit: element.Unit,
   421  		}
   422  		switch element.Name.Value {
   423  		case "cpu_percent":
   424  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_CPU_USAGE
   425  		case "memory_percent", "sqlserver_process_memory_percent":
   426  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_MEM_USAGE
   427  		case "storage_percent":
   428  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_DISK_USAGE
   429  		case "network_bytes_ingress":
   430  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_NET_BPS_RX
   431  		case "network_bytes_egress":
   432  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_NET_BPS_TX
   433  		case "io_consumption_percent":
   434  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_DISK_IO_PERCENT
   435  		case "connections_failed", "connection_failed":
   436  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_CONN_FAILED
   437  		case "active_connections", "connection_successful":
   438  			metric.MetricType = cloudprovider.RDS_METRIC_TYPE_CONN_ACTIVE
   439  		case "Percentage CPU":
   440  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_CPU_USAGE
   441  		case "Network In Total":
   442  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_NET_BPS_RX
   443  		case "Network Out Total":
   444  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_NET_BPS_TX
   445  		case "Disk Read Bytes":
   446  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_IO_READ_BPS
   447  		case "Disk Write Bytes":
   448  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_IO_WRITE_BPS
   449  		case "Disk Read Operations/Sec":
   450  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_IO_READ_IOPS
   451  		case "Disk Write Operations/Sec":
   452  			metric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_IO_WRITE_IOPS
   453  		case "percentProcessorTime":
   454  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_CPU_USAGE
   455  		case "usedmemorypercentage":
   456  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_MEM_USAGE
   457  		case "connectedclients":
   458  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_USED_CONN
   459  		case "operationsPerSecond":
   460  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_OPT_SES
   461  		case "alltotalkeys":
   462  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_CACHE_KEYS
   463  		case "expiredkeys":
   464  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_CACHE_EXP_KEYS
   465  		case "usedmemory":
   466  			metric.MetricType = cloudprovider.REDIS_METRIC_TYPE_DATA_MEM_USAGE
   467  		case "SnatConnectionCount":
   468  			metric.MetricType = cloudprovider.LB_METRIC_TYPE_SNAT_PORT
   469  		case "UsedSnatPorts":
   470  			metric.MetricType = cloudprovider.LB_METRIC_TYPE_SNAT_CONN_COUNT
   471  		case "node_cpu_usage_percentage":
   472  			metric.MetricType = cloudprovider.K8S_NODE_METRIC_TYPE_CPU_USAGE
   473  		case "node_memory_rss_percentage":
   474  			metric.MetricType = cloudprovider.K8S_NODE_METRIC_TYPE_MEM_USAGE
   475  		case "node_disk_usage_percentage":
   476  			metric.MetricType = cloudprovider.K8S_NODE_METRIC_TYPE_DISK_USAGE
   477  		case "node_network_in_bytes":
   478  			metric.MetricType = cloudprovider.K8S_NODE_METRIC_TYPE_NET_BPS_RX
   479  		case "node_network_out_bytes":
   480  			metric.MetricType = cloudprovider.K8S_NODE_METRIC_TYPE_NET_BPS_TX
   481  		default:
   482  			log.Warningf("incognizance metric type %s", element.Name.Value)
   483  			continue
   484  		}
   485  		for _, timeserie := range element.Timeseries {
   486  			tags := map[string]string{}
   487  			for _, metadata := range timeserie.Metadatavalues {
   488  				if metadata.Name.Value == "node" { //k8s node
   489  					tags[cloudprovider.METRIC_TAG_NODE] = metadata.Value
   490  				}
   491  			}
   492  			for k, v := range metricTag {
   493  				tags[k] = v
   494  			}
   495  			for _, data := range timeserie.Data {
   496  				metric.Values = append(metric.Values, cloudprovider.MetricValue{
   497  					Timestamp: data.TimeStamp,
   498  					Value:     data.GetValue(),
   499  					Tags:      tags,
   500  				})
   501  			}
   502  		}
   503  		ret = append(ret, metric)
   504  	}
   505  	return ret, nil
   506  }