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 }