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 }