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  }