github.com/vmware/govmomi@v0.43.0/simulator/performance_manager.go (about)

     1  /*
     2  Copyright (c) 2017 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package simulator
    18  
    19  import (
    20  	"math/rand"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/vmware/govmomi/simulator/esx"
    26  	"github.com/vmware/govmomi/simulator/vpx"
    27  	"github.com/vmware/govmomi/vim25/methods"
    28  	"github.com/vmware/govmomi/vim25/mo"
    29  	"github.com/vmware/govmomi/vim25/soap"
    30  	"github.com/vmware/govmomi/vim25/types"
    31  )
    32  
    33  var realtimeProviderSummary = types.PerfProviderSummary{
    34  	CurrentSupported: true,
    35  	SummarySupported: true,
    36  	RefreshRate:      20,
    37  }
    38  
    39  var historicProviderSummary = types.PerfProviderSummary{
    40  	CurrentSupported: false,
    41  	SummarySupported: true,
    42  	RefreshRate:      -1,
    43  }
    44  
    45  type PerformanceManager struct {
    46  	mo.PerformanceManager
    47  	vmMetrics         []types.PerfMetricId
    48  	hostMetrics       []types.PerfMetricId
    49  	rpMetrics         []types.PerfMetricId
    50  	clusterMetrics    []types.PerfMetricId
    51  	datastoreMetrics  []types.PerfMetricId
    52  	datacenterMetrics []types.PerfMetricId
    53  	perfCounterIndex  map[int32]types.PerfCounterInfo
    54  	metricData        map[string]map[int32][]int64
    55  }
    56  
    57  func (m *PerformanceManager) init(r *Registry) {
    58  	if r.IsESX() {
    59  		m.PerfCounter = esx.PerfCounter
    60  		m.HistoricalInterval = esx.HistoricalInterval
    61  		m.hostMetrics = esx.HostMetrics
    62  		m.vmMetrics = esx.VmMetrics
    63  		m.rpMetrics = esx.ResourcePoolMetrics
    64  		m.metricData = esx.MetricData
    65  	} else {
    66  		m.PerfCounter = vpx.PerfCounter
    67  		m.HistoricalInterval = vpx.HistoricalInterval
    68  		m.hostMetrics = vpx.HostMetrics
    69  		m.vmMetrics = vpx.VmMetrics
    70  		m.rpMetrics = vpx.ResourcePoolMetrics
    71  		m.clusterMetrics = vpx.ClusterMetrics
    72  		m.datastoreMetrics = vpx.DatastoreMetrics
    73  		m.datacenterMetrics = vpx.DatacenterMetrics
    74  		m.metricData = vpx.MetricData
    75  	}
    76  	m.perfCounterIndex = make(map[int32]types.PerfCounterInfo, len(m.PerfCounter))
    77  	for _, p := range m.PerfCounter {
    78  		m.perfCounterIndex[p.Key] = p
    79  	}
    80  }
    81  
    82  func (p *PerformanceManager) QueryPerfCounter(ctx *Context, req *types.QueryPerfCounter) soap.HasFault {
    83  	body := new(methods.QueryPerfCounterBody)
    84  	body.Res = new(types.QueryPerfCounterResponse)
    85  	body.Res.Returnval = make([]types.PerfCounterInfo, len(req.CounterId))
    86  	for i, id := range req.CounterId {
    87  		body.Res.Returnval[i] = p.perfCounterIndex[id]
    88  	}
    89  	return body
    90  }
    91  
    92  func (p *PerformanceManager) QueryPerfProviderSummary(ctx *Context, req *types.QueryPerfProviderSummary) soap.HasFault {
    93  	body := new(methods.QueryPerfProviderSummaryBody)
    94  	body.Res = new(types.QueryPerfProviderSummaryResponse)
    95  
    96  	// The entity must exist
    97  	if ctx.Map.Get(req.Entity) == nil {
    98  		body.Fault_ = Fault("", &types.InvalidArgument{
    99  			InvalidProperty: "Entity",
   100  		})
   101  		return body
   102  	}
   103  
   104  	switch req.Entity.Type {
   105  	case "VirtualMachine", "HostSystem", "ResourcePool":
   106  		body.Res.Returnval = realtimeProviderSummary
   107  	default:
   108  		body.Res.Returnval = historicProviderSummary
   109  	}
   110  	body.Res.Returnval.Entity = req.Entity
   111  	return body
   112  }
   113  
   114  func (p *PerformanceManager) buildAvailablePerfMetricsQueryResponse(ids []types.PerfMetricId, numCPU int, datastoreURL string) *types.QueryAvailablePerfMetricResponse {
   115  	r := new(types.QueryAvailablePerfMetricResponse)
   116  	r.Returnval = make([]types.PerfMetricId, 0, len(ids))
   117  	for _, id := range ids {
   118  		switch id.Instance {
   119  		case "$cpu":
   120  			for i := 0; i < numCPU; i++ {
   121  				r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: strconv.Itoa(i)})
   122  			}
   123  		case "$physDisk":
   124  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: datastoreURL})
   125  		case "$file":
   126  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "DISKFILE"})
   127  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "DELTAFILE"})
   128  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "SWAPFILE"})
   129  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "OTHERFILE"})
   130  		default:
   131  			r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: id.Instance})
   132  		}
   133  	}
   134  	// Add a CounterId without a corresponding PerfCounterInfo entry. See issue #2835
   135  	r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: 10042})
   136  	return r
   137  }
   138  
   139  func (p *PerformanceManager) queryAvailablePerfMetric(entity types.ManagedObjectReference, interval int32) *types.QueryAvailablePerfMetricResponse {
   140  	switch entity.Type {
   141  	case "VirtualMachine":
   142  		vm := Map.Get(entity).(*VirtualMachine)
   143  		return p.buildAvailablePerfMetricsQueryResponse(p.vmMetrics, int(vm.Summary.Config.NumCpu), vm.Datastore[0].Value)
   144  	case "HostSystem":
   145  		host := Map.Get(entity).(*HostSystem)
   146  		return p.buildAvailablePerfMetricsQueryResponse(p.hostMetrics, int(host.Hardware.CpuInfo.NumCpuThreads), host.Datastore[0].Value)
   147  	case "ResourcePool":
   148  		return p.buildAvailablePerfMetricsQueryResponse(p.rpMetrics, 0, "")
   149  	case "ClusterComputeResource":
   150  		if interval != 20 {
   151  			return p.buildAvailablePerfMetricsQueryResponse(p.clusterMetrics, 0, "")
   152  		}
   153  	case "Datastore":
   154  		if interval != 20 {
   155  			return p.buildAvailablePerfMetricsQueryResponse(p.datastoreMetrics, 0, "")
   156  		}
   157  	case "Datacenter":
   158  		if interval != 20 {
   159  			return p.buildAvailablePerfMetricsQueryResponse(p.datacenterMetrics, 0, "")
   160  		}
   161  	}
   162  
   163  	// Don't know how to handle this. Return empty response.
   164  	return new(types.QueryAvailablePerfMetricResponse)
   165  }
   166  
   167  func (p *PerformanceManager) QueryAvailablePerfMetric(ctx *Context, req *types.QueryAvailablePerfMetric) soap.HasFault {
   168  	body := new(methods.QueryAvailablePerfMetricBody)
   169  	body.Res = p.queryAvailablePerfMetric(req.Entity, req.IntervalId)
   170  
   171  	return body
   172  }
   173  
   174  func (p *PerformanceManager) QueryPerf(ctx *Context, req *types.QueryPerf) soap.HasFault {
   175  	body := new(methods.QueryPerfBody)
   176  	body.Res = new(types.QueryPerfResponse)
   177  	body.Res.Returnval = make([]types.BasePerfEntityMetricBase, len(req.QuerySpec))
   178  
   179  	for i, qs := range req.QuerySpec {
   180  		// Get metric data for this entity type
   181  		metricData, ok := p.metricData[qs.Entity.Type]
   182  		if !ok {
   183  			body.Fault_ = Fault("", &types.InvalidArgument{
   184  				InvalidProperty: "Entity",
   185  			})
   186  		}
   187  		var start, end time.Time
   188  		if qs.StartTime == nil {
   189  			start = time.Now().Add(time.Duration(-365*24) * time.Hour) // Assume we have data for a year
   190  		} else {
   191  			start = *qs.StartTime
   192  		}
   193  		if qs.EndTime == nil {
   194  			end = time.Now()
   195  		} else {
   196  			end = *qs.EndTime
   197  		}
   198  
   199  		// Generate metric series. Divide into n buckets of interval seconds
   200  		interval := qs.IntervalId
   201  		if interval == -1 || interval == 0 {
   202  			interval = 20 // TODO: Determine from entity type
   203  		}
   204  		n := 1 + int32(end.Sub(start).Seconds())/interval
   205  		if qs.MaxSample > 0 && n > qs.MaxSample {
   206  			n = qs.MaxSample
   207  		}
   208  
   209  		metrics := new(types.PerfEntityMetric)
   210  		metrics.Entity = qs.Entity
   211  
   212  		// Loop through each interval "tick"
   213  		metrics.SampleInfo = make([]types.PerfSampleInfo, n)
   214  		metrics.Value = make([]types.BasePerfMetricSeries, len(qs.MetricId))
   215  		for tick := int32(0); tick < n; tick++ {
   216  			metrics.SampleInfo[tick] = types.PerfSampleInfo{Timestamp: end.Add(time.Duration(-interval*tick) * time.Second), Interval: interval}
   217  		}
   218  
   219  		series := make([]*types.PerfMetricIntSeries, len(qs.MetricId))
   220  		for j, mid := range qs.MetricId {
   221  			// Create list of metrics for this tick
   222  			series[j] = &types.PerfMetricIntSeries{Value: make([]int64, n)}
   223  			series[j].Id = mid
   224  			points := metricData[mid.CounterId]
   225  			offset := int64(start.Unix()) / int64(interval)
   226  
   227  			for tick := int32(0); tick < n; tick++ {
   228  				var p int64
   229  
   230  				// Use sample data if we have it. Otherwise, just send 0.
   231  				if len(points) > 0 {
   232  					p = points[(offset+int64(tick))%int64(len(points))]
   233  					scale := p / 5
   234  					if scale > 0 {
   235  						// Add some gaussian noise to make the data look more "real"
   236  						p += int64(rand.NormFloat64() * float64(scale))
   237  						if p < 0 {
   238  							p = 0
   239  						}
   240  					}
   241  				} else {
   242  					p = 0
   243  				}
   244  				series[j].Value[tick] = p
   245  			}
   246  			metrics.Value[j] = series[j]
   247  		}
   248  
   249  		if qs.Format == string(types.PerfFormatCsv) {
   250  			metricsCsv := new(types.PerfEntityMetricCSV)
   251  			metricsCsv.Entity = qs.Entity
   252  
   253  			//PerfSampleInfo encoded in the following CSV format: [interval1], [date1], [interval2], [date2], and so on.
   254  			metricsCsv.SampleInfoCSV = sampleInfoCSV(metrics)
   255  			metricsCsv.Value = make([]types.PerfMetricSeriesCSV, len(qs.MetricId))
   256  
   257  			for j, mid := range qs.MetricId {
   258  				seriesCsv := &types.PerfMetricSeriesCSV{Value: ""}
   259  				seriesCsv.Id = mid
   260  				seriesCsv.Value = valueCSV(series[j])
   261  				metricsCsv.Value[j] = *seriesCsv
   262  			}
   263  
   264  			body.Res.Returnval[i] = metricsCsv
   265  		} else {
   266  			body.Res.Returnval[i] = metrics
   267  		}
   268  	}
   269  	return body
   270  }
   271  
   272  // sampleInfoCSV converts the SampleInfo field to a CSV string
   273  func sampleInfoCSV(m *types.PerfEntityMetric) string {
   274  	values := make([]string, len(m.SampleInfo)*2)
   275  
   276  	i := 0
   277  	for _, s := range m.SampleInfo {
   278  		values[i] = strconv.Itoa(int(s.Interval))
   279  		i++
   280  		values[i] = s.Timestamp.Format(time.RFC3339)
   281  		i++
   282  	}
   283  
   284  	return strings.Join(values, ",")
   285  }
   286  
   287  // valueCSV converts the Value field to a CSV string
   288  func valueCSV(s *types.PerfMetricIntSeries) string {
   289  	values := make([]string, len(s.Value))
   290  
   291  	for i := range s.Value {
   292  		values[i] = strconv.FormatInt(s.Value[i], 10)
   293  	}
   294  
   295  	return strings.Join(values, ",")
   296  }