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  }