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