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