watcher.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. //go:build windows
  4. // +build windows
  5. package winperfcounters // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/winperfcounters"
  6. import (
  7. "fmt"
  8. "time"
  9. "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/winperfcounters/internal/third_party/telegraf/win_perf_counters"
  10. )
  11. const totalInstanceName = "_Total"
  12. var _ PerfCounterWatcher = (*perfCounter)(nil)
  13. // PerfCounterWatcher represents how to scrape data
  14. type PerfCounterWatcher interface {
  15. // Path returns the counter path
  16. Path() string
  17. // ScrapeData collects a measurement and returns the value(s).
  18. ScrapeData() ([]CounterValue, error)
  19. // Close all counters/handles related to the query and free all associated memory.
  20. Close() error
  21. }
  22. type CounterValue = win_perf_counters.CounterValue
  23. type perfCounter struct {
  24. path string
  25. query win_perf_counters.PerformanceQuery
  26. handle win_perf_counters.PDH_HCOUNTER
  27. }
  28. // NewWatcher creates new PerfCounterWatcher by provided parts of its path.
  29. func NewWatcher(object, instance, counterName string) (PerfCounterWatcher, error) {
  30. path := counterPath(object, instance, counterName)
  31. counter, err := newPerfCounter(path, true)
  32. if err != nil {
  33. return nil, fmt.Errorf("failed to create perf counter with path %v: %w", path, err)
  34. }
  35. return counter, nil
  36. }
  37. // NewWatcherFromPath creates new PerfCounterWatcher by provided path.
  38. func NewWatcherFromPath(path string) (PerfCounterWatcher, error) {
  39. counter, err := newPerfCounter(path, true)
  40. if err != nil {
  41. return nil, fmt.Errorf("failed to create perf counter with path %v: %w", path, err)
  42. }
  43. return counter, nil
  44. }
  45. func counterPath(object, instance, counterName string) string {
  46. if instance != "" {
  47. instance = fmt.Sprintf("(%s)", instance)
  48. }
  49. return fmt.Sprintf("\\%s%s\\%s", object, instance, counterName)
  50. }
  51. // newPerfCounter returns a new performance counter for the specified descriptor.
  52. func newPerfCounter(counterPath string, collectOnStartup bool) (*perfCounter, error) {
  53. query := &win_perf_counters.PerformanceQueryImpl{}
  54. err := query.Open()
  55. if err != nil {
  56. return nil, err
  57. }
  58. var handle win_perf_counters.PDH_HCOUNTER
  59. handle, err = query.AddEnglishCounterToQuery(counterPath)
  60. if err != nil {
  61. return nil, err
  62. }
  63. // Some perf counters (e.g. cpu) return the usage stats since the last measure.
  64. // We collect data on startup to avoid an invalid initial reading
  65. if collectOnStartup {
  66. err = query.CollectData()
  67. if err != nil {
  68. return nil, err
  69. }
  70. }
  71. counter := &perfCounter{
  72. path: counterPath,
  73. query: query,
  74. handle: handle,
  75. }
  76. return counter, nil
  77. }
  78. func (pc *perfCounter) Close() error {
  79. return pc.query.Close()
  80. }
  81. func (pc *perfCounter) Path() string {
  82. return pc.path
  83. }
  84. func (pc *perfCounter) ScrapeData() ([]CounterValue, error) {
  85. if err := pc.query.CollectData(); err != nil {
  86. pdhErr, ok := err.(*win_perf_counters.PdhError)
  87. if !ok || pdhErr.ErrorCode != win_perf_counters.PDH_CALC_NEGATIVE_DENOMINATOR {
  88. return nil, fmt.Errorf("failed to collect data for performance counter '%s': %w", pc.path, err)
  89. }
  90. // A counter rolled over, so the value is invalid
  91. // See https://support.microfocus.com/kb/doc.php?id=7010545
  92. // Wait one second and retry once
  93. time.Sleep(time.Second)
  94. if retryErr := pc.query.CollectData(); retryErr != nil {
  95. return nil, fmt.Errorf("failed retry for performance counter '%s': %w", pc.path, err)
  96. }
  97. }
  98. vals, err := pc.query.GetFormattedCounterArrayDouble(pc.handle)
  99. if err != nil {
  100. return nil, fmt.Errorf("failed to format data for performance counter '%s': %w", pc.path, err)
  101. }
  102. vals = removeTotalIfMultipleValues(vals)
  103. return vals, nil
  104. }
  105. // ExpandWildCardPath examines the local computer and returns those counter paths that match the given counter path which contains wildcard characters.
  106. func ExpandWildCardPath(counterPath string) ([]string, error) {
  107. return win_perf_counters.ExpandWildCardPath(counterPath)
  108. }
  109. func removeTotalIfMultipleValues(vals []CounterValue) []CounterValue {
  110. if len(vals) == 0 {
  111. return vals
  112. }
  113. if len(vals) == 1 {
  114. // if there is only one item & the instance name is "_Total", clear the instance name
  115. if vals[0].InstanceName == totalInstanceName {
  116. vals[0].InstanceName = ""
  117. }
  118. return vals
  119. }
  120. // if there is more than one item, remove an item that has the instance name "_Total"
  121. for i, val := range vals {
  122. if val.InstanceName == totalInstanceName {
  123. return removeItemAt(vals, i)
  124. }
  125. }
  126. return vals
  127. }
  128. func removeItemAt(vals []CounterValue, idx int) []CounterValue {
  129. vals[idx] = vals[len(vals)-1]
  130. vals[len(vals)-1] = CounterValue{}
  131. return vals[:len(vals)-1]
  132. }