github.com/vmware/govmomi@v0.51.0/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 performance 6 7 import ( 8 "context" 9 "fmt" 10 "sort" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/vmware/govmomi/object" 17 "github.com/vmware/govmomi/vim25" 18 "github.com/vmware/govmomi/vim25/methods" 19 "github.com/vmware/govmomi/vim25/mo" 20 "github.com/vmware/govmomi/vim25/types" 21 ) 22 23 var ( 24 // Intervals maps name to seconds for the built-in historical intervals 25 Intervals = map[string]int32{ 26 "real": 0, // 0 == default 20s interval 27 "day": 300, 28 "week": 1800, 29 "month": 7200, 30 "year": 86400, 31 } 32 ) 33 34 // Manager wraps mo.PerformanceManager. 35 type Manager struct { 36 object.Common 37 38 Sort bool 39 40 pm struct { 41 sync.Mutex 42 *mo.PerformanceManager 43 } 44 45 infoByName struct { 46 sync.Mutex 47 m map[string]*types.PerfCounterInfo 48 } 49 50 infoByKey struct { 51 sync.Mutex 52 m map[int32]*types.PerfCounterInfo 53 } 54 } 55 56 // NewManager creates a new Manager instance. 57 func NewManager(client *vim25.Client) *Manager { 58 m := Manager{ 59 Common: object.NewCommon(client, *client.ServiceContent.PerfManager), 60 } 61 62 m.pm.PerformanceManager = new(mo.PerformanceManager) 63 64 return &m 65 } 66 67 // IntervalList wraps []types.PerfInterval. 68 type IntervalList []types.PerfInterval 69 70 // Enabled returns a map with Level as the key and enabled PerfInterval.Name(s) as the value. 71 func (l IntervalList) Enabled() map[int32][]string { 72 enabled := make(map[int32][]string) 73 74 for level := int32(0); level <= 4; level++ { 75 var names []string 76 77 for _, interval := range l { 78 if interval.Enabled && interval.Level >= level { 79 names = append(names, interval.Name) 80 } 81 } 82 83 enabled[level] = names 84 } 85 86 return enabled 87 } 88 89 // HistoricalInterval gets the PerformanceManager.HistoricalInterval property and wraps as an IntervalList. 90 func (m *Manager) HistoricalInterval(ctx context.Context) (IntervalList, error) { 91 var pm mo.PerformanceManager 92 93 err := m.Properties(ctx, m.Reference(), []string{"historicalInterval"}, &pm) 94 if err != nil { 95 return nil, err 96 } 97 98 return IntervalList(pm.HistoricalInterval), nil 99 } 100 101 // CounterInfo gets the PerformanceManager.PerfCounter property. 102 // The property value is only collected once, subsequent calls return the cached value. 103 func (m *Manager) CounterInfo(ctx context.Context) ([]types.PerfCounterInfo, error) { 104 m.pm.Lock() 105 defer m.pm.Unlock() 106 107 if len(m.pm.PerfCounter) == 0 { 108 err := m.Properties(ctx, m.Reference(), []string{"perfCounter"}, m.pm.PerformanceManager) 109 if err != nil { 110 return nil, err 111 } 112 } 113 114 return m.pm.PerfCounter, nil 115 } 116 117 // CounterInfoByName converts the PerformanceManager.PerfCounter property to a map, 118 // where key is types.PerfCounterInfo.Name(). 119 func (m *Manager) CounterInfoByName(ctx context.Context) (map[string]*types.PerfCounterInfo, error) { 120 m.infoByName.Lock() 121 defer m.infoByName.Unlock() 122 123 if m.infoByName.m != nil { 124 return m.infoByName.m, nil 125 } 126 127 info, err := m.CounterInfo(ctx) 128 if err != nil { 129 return nil, err 130 } 131 132 m.infoByName.m = make(map[string]*types.PerfCounterInfo) 133 134 for i := range info { 135 c := &info[i] 136 137 m.infoByName.m[c.Name()] = c 138 } 139 140 return m.infoByName.m, nil 141 } 142 143 // CounterInfoByKey converts the PerformanceManager.PerfCounter property to a map, 144 // where key is types.PerfCounterInfo.Key. 145 func (m *Manager) CounterInfoByKey(ctx context.Context) (map[int32]*types.PerfCounterInfo, error) { 146 m.infoByKey.Lock() 147 defer m.infoByKey.Unlock() 148 149 if m.infoByKey.m != nil { 150 return m.infoByKey.m, nil 151 } 152 153 info, err := m.CounterInfo(ctx) 154 if err != nil { 155 return nil, err 156 } 157 158 m.infoByKey.m = make(map[int32]*types.PerfCounterInfo) 159 160 for i := range info { 161 c := &info[i] 162 163 m.infoByKey.m[c.Key] = c 164 } 165 166 return m.infoByKey.m, nil 167 } 168 169 // ProviderSummary wraps the QueryPerfProviderSummary method, caching the value based on entity.Type. 170 func (m *Manager) ProviderSummary(ctx context.Context, entity types.ManagedObjectReference) (*types.PerfProviderSummary, error) { 171 req := types.QueryPerfProviderSummary{ 172 This: m.Reference(), 173 Entity: entity, 174 } 175 176 res, err := methods.QueryPerfProviderSummary(ctx, m.Client(), &req) 177 if err != nil { 178 return nil, err 179 } 180 181 return &res.Returnval, nil 182 } 183 184 type groupPerfCounterInfo struct { 185 info map[int32]*types.PerfCounterInfo 186 ids []types.PerfMetricId 187 } 188 189 func (d groupPerfCounterInfo) Len() int { 190 return len(d.ids) 191 } 192 193 func (d groupPerfCounterInfo) Less(i, j int) bool { 194 ci := d.ids[i].CounterId 195 cj := d.ids[j].CounterId 196 197 giKey := "-" 198 gjKey := "-" 199 200 if gi, ok := d.info[ci]; ok { 201 giKey = gi.GroupInfo.GetElementDescription().Key 202 } 203 if gj, ok := d.info[cj]; ok { 204 gjKey = gj.GroupInfo.GetElementDescription().Key 205 } 206 207 return giKey < gjKey 208 } 209 210 func (d groupPerfCounterInfo) Swap(i, j int) { 211 d.ids[i], d.ids[j] = d.ids[j], d.ids[i] 212 } 213 214 // MetricList wraps []types.PerfMetricId 215 type MetricList []types.PerfMetricId 216 217 // ByKey converts MetricList to map, where key is types.PerfMetricId.CounterId / types.PerfCounterInfo.Key 218 func (l MetricList) ByKey() map[int32][]*types.PerfMetricId { 219 ids := make(map[int32][]*types.PerfMetricId, len(l)) 220 221 for i := range l { 222 id := &l[i] 223 ids[id.CounterId] = append(ids[id.CounterId], id) 224 } 225 226 return ids 227 } 228 229 // AvailableMetric wraps the QueryAvailablePerfMetric method. 230 // The MetricList is sorted by PerfCounterInfo.GroupInfo.Key if Manager.Sort == true. 231 func (m *Manager) AvailableMetric(ctx context.Context, entity types.ManagedObjectReference, interval int32) (MetricList, error) { 232 req := types.QueryAvailablePerfMetric{ 233 This: m.Reference(), 234 Entity: entity.Reference(), 235 IntervalId: interval, 236 } 237 238 res, err := methods.QueryAvailablePerfMetric(ctx, m.Client(), &req) 239 if err != nil { 240 return nil, err 241 } 242 243 if m.Sort { 244 info, err := m.CounterInfoByKey(ctx) 245 if err != nil { 246 return nil, err 247 } 248 249 sort.Sort(groupPerfCounterInfo{info, res.Returnval}) 250 } 251 252 return MetricList(res.Returnval), nil 253 } 254 255 // Query wraps the QueryPerf method. 256 func (m *Manager) Query(ctx context.Context, spec []types.PerfQuerySpec) ([]types.BasePerfEntityMetricBase, error) { 257 req := types.QueryPerf{ 258 This: m.Reference(), 259 QuerySpec: spec, 260 } 261 262 res, err := methods.QueryPerf(ctx, m.Client(), &req) 263 if err != nil { 264 return nil, err 265 } 266 267 return res.Returnval, nil 268 } 269 270 // QueryCounter wraps the QueryPerfCounter method. 271 func (m *Manager) QueryCounter(ctx context.Context, ids []int32) ([]types.PerfCounterInfo, error) { 272 req := types.QueryPerfCounter{ 273 This: m.Reference(), 274 CounterId: ids, 275 } 276 277 res, err := methods.QueryPerfCounter(ctx, m.Client(), &req) 278 if err != nil { 279 return nil, err 280 } 281 282 return res.Returnval, nil 283 } 284 285 // SampleByName uses the spec param as a template, constructing a []types.PerfQuerySpec for the given metrics and entities 286 // and invoking the Query method. 287 // The spec template can specify instances using the MetricId.Instance field, by default all instances are collected. 288 // The spec template MaxSample defaults to 1. 289 // If the spec template IntervalId is a historical interval and StartTime is not specified, 290 // the StartTime is set to the current time - (IntervalId * MaxSample). 291 func (m *Manager) SampleByName(ctx context.Context, spec types.PerfQuerySpec, metrics []string, entity []types.ManagedObjectReference) ([]types.BasePerfEntityMetricBase, error) { 292 info, err := m.CounterInfoByName(ctx) 293 if err != nil { 294 return nil, err 295 } 296 297 var ids []types.PerfMetricId 298 299 instances := spec.MetricId 300 if len(instances) == 0 { 301 // Default to all instances 302 instances = []types.PerfMetricId{{Instance: "*"}} 303 } 304 305 for _, name := range metrics { 306 counter, ok := info[name] 307 if !ok { 308 return nil, fmt.Errorf("counter %q not found", name) 309 } 310 311 for _, i := range instances { 312 ids = append(ids, types.PerfMetricId{CounterId: counter.Key, Instance: i.Instance}) 313 } 314 } 315 316 spec.MetricId = ids 317 318 if spec.MaxSample == 0 { 319 spec.MaxSample = 1 320 } 321 322 truncate := false 323 324 if spec.IntervalId >= 60 && spec.StartTime == nil { 325 truncate = true 326 // Need a StartTime to make use of history 327 now, err := methods.GetCurrentTime(ctx, m.Client()) 328 if err != nil { 329 return nil, err 330 } 331 332 // Go back in time 333 x := spec.IntervalId * -1 * (spec.MaxSample * 2) 334 t := now.Add(time.Duration(x) * time.Second) 335 spec.StartTime = &t 336 } 337 338 var query []types.PerfQuerySpec 339 340 for _, e := range entity { 341 spec.Entity = e.Reference() 342 query = append(query, spec) 343 } 344 345 series, err := m.Query(ctx, query) 346 if err != nil { 347 return nil, err 348 } 349 350 if truncate { 351 // Going back in time with IntervalId * MaxSample isn't always far enough, 352 // depending on when the historical data is saved in vCenter. 353 // So we go back twice as far and truncate here if needed. 354 for i := range series { 355 s, ok := series[i].(*types.PerfEntityMetric) 356 if !ok { 357 break 358 } 359 360 n := len(s.SampleInfo) 361 diff := n - int(spec.MaxSample) 362 if diff > 0 { 363 s.SampleInfo = s.SampleInfo[diff:] 364 } 365 366 for j := range s.Value { 367 v, ok := s.Value[j].(*types.PerfMetricIntSeries) 368 if !ok { 369 break 370 } 371 372 n = len(v.Value) 373 diff = n - int(spec.MaxSample) 374 if diff > 0 { 375 v.Value = v.Value[diff:] 376 } 377 } 378 } 379 } 380 381 return series, nil 382 } 383 384 // MetricSeries contains the same data as types.PerfMetricIntSeries, but with the CounterId converted to Name. 385 type MetricSeries struct { 386 Name string `json:"name"` 387 Unit string `json:"unit"` 388 Instance string `json:"instance"` 389 Value []int64 `json:"value"` 390 } 391 392 func (s *MetricSeries) Format(val int64) string { 393 switch types.PerformanceManagerUnit(s.Unit) { 394 case types.PerformanceManagerUnitPercent: 395 return strconv.FormatFloat(float64(val)/100.0, 'f', 2, 64) 396 default: 397 return strconv.FormatInt(val, 10) 398 } 399 } 400 401 // ValueCSV converts the Value field to a CSV string 402 func (s *MetricSeries) ValueCSV() string { 403 vals := make([]string, len(s.Value)) 404 405 for i := range s.Value { 406 vals[i] = s.Format(s.Value[i]) 407 } 408 409 return strings.Join(vals, ",") 410 } 411 412 // EntityMetric contains the same data as types.PerfEntityMetric, but with MetricSeries type for the Value field. 413 type EntityMetric struct { 414 Entity types.ManagedObjectReference `json:"entity"` 415 416 SampleInfo []types.PerfSampleInfo `json:"sampleInfo"` 417 Value []MetricSeries `json:"value"` 418 } 419 420 // SampleInfoCSV converts the SampleInfo field to a CSV string 421 func (m *EntityMetric) SampleInfoCSV() string { 422 vals := make([]string, len(m.SampleInfo)*2) 423 424 i := 0 425 426 for _, s := range m.SampleInfo { 427 vals[i] = s.Timestamp.Format(time.RFC3339) 428 i++ 429 vals[i] = strconv.Itoa(int(s.Interval)) 430 i++ 431 } 432 433 return strings.Join(vals, ",") 434 } 435 436 // ToMetricSeries converts []BasePerfEntityMetricBase to []EntityMetric 437 func (m *Manager) ToMetricSeries(ctx context.Context, series []types.BasePerfEntityMetricBase) ([]EntityMetric, error) { 438 counters, err := m.CounterInfoByKey(ctx) 439 if err != nil { 440 return nil, err 441 } 442 443 var result []EntityMetric 444 445 for i := range series { 446 var values []MetricSeries 447 s, ok := series[i].(*types.PerfEntityMetric) 448 if !ok { 449 panic(fmt.Errorf("expected type %T, got: %T", s, series[i])) 450 } 451 452 for j := range s.Value { 453 v := s.Value[j].(*types.PerfMetricIntSeries) 454 info, ok := counters[v.Id.CounterId] 455 if !ok { 456 continue 457 } 458 459 values = append(values, MetricSeries{ 460 Name: info.Name(), 461 Unit: info.UnitInfo.GetElementDescription().Key, 462 Instance: v.Id.Instance, 463 Value: v.Value, 464 }) 465 } 466 467 result = append(result, EntityMetric{ 468 Entity: s.Entity, 469 SampleInfo: s.SampleInfo, 470 Value: values, 471 }) 472 } 473 474 return result, nil 475 }