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

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