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 }