manager.go 12 KB

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