123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- //go:build windows
- // +build windows
- package winperfcounters // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/winperfcounters"
- import (
- "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 {
- pdhErr, ok := err.(*win_perf_counters.PdhError)
- if !ok || 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]
- }
|