diff --git a/plugins/inputs/win_perf_counters/README.md b/plugins/inputs/win_perf_counters/README.md index a89efec0f..cf2ba4d64 100644 --- a/plugins/inputs/win_perf_counters/README.md +++ b/plugins/inputs/win_perf_counters/README.md @@ -72,6 +72,15 @@ It is recommended NOT to use this on OSes starting with Vista and newer because Example for Windows Server 2003, this would be set to true: `PreVistaSupport=true` +#### UsePerfCounterTime + +Bool, if set to `true` will request a timestamp along with the PerfCounter data. +If se to `false`, current time will be used. + +Supported on Windows Vista/Windows Server 2008 and newer +Example: +`UsePerfCounterTime=true` + ### Object See Entry below. diff --git a/plugins/inputs/win_perf_counters/kernel32.go b/plugins/inputs/win_perf_counters/kernel32.go new file mode 100644 index 000000000..9cdadedc8 --- /dev/null +++ b/plugins/inputs/win_perf_counters/kernel32.go @@ -0,0 +1,73 @@ +// Copyright (c) 2010 The win Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. The names of the authors may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This is the official list of 'win' authors for copyright purposes. +// +// Alexander Neumann +// Joseph Watson +// Kevin Pors + +// +build windows + +package win_perf_counters + +import ( + "syscall" +) + +type SYSTEMTIME struct { + wYear uint16 + wMonth uint16 + wDayOfWeek uint16 + wDay uint16 + wHour uint16 + wMinute uint16 + wSecond uint16 + wMilliseconds uint16 +} + +type FILETIME struct { + dwLowDateTime uint32 + dwHighDateTime uint32 +} + +var ( + // Library + libkrnDll *syscall.DLL + + // Functions + krn_FileTimeToSystemTime *syscall.Proc + krn_FileTimeToLocalFileTime *syscall.Proc + krn_LocalFileTimeToFileTime *syscall.Proc + krn_WideCharToMultiByte *syscall.Proc +) + +func init() { + libkrnDll = syscall.MustLoadDLL("Kernel32.dll") + + krn_FileTimeToSystemTime = libkrnDll.MustFindProc("FileTimeToSystemTime") + krn_FileTimeToLocalFileTime = libkrnDll.MustFindProc("FileTimeToLocalFileTime") + krn_LocalFileTimeToFileTime = libkrnDll.MustFindProc("LocalFileTimeToFileTime") + krn_WideCharToMultiByte = libkrnDll.MustFindProc("WideCharToMultiByte") +} diff --git a/plugins/inputs/win_perf_counters/pdh.go b/plugins/inputs/win_perf_counters/pdh.go index e6f7d5196..6a8dff10b 100644 --- a/plugins/inputs/win_perf_counters/pdh.go +++ b/plugins/inputs/win_perf_counters/pdh.go @@ -38,12 +38,15 @@ import ( "unsafe" "golang.org/x/sys/windows" + "time" ) // Error codes const ( - ERROR_SUCCESS = 0 - ERROR_INVALID_FUNCTION = 1 + ERROR_SUCCESS = 0 + ERROR_FAILURE = 1 + ERROR_INVALID_FUNCTION = 1 + EPOCH_DIFFERENCE_MICROS int64 = 11644473600000000 ) type ( @@ -170,6 +173,7 @@ var ( pdh_AddEnglishCounterW *syscall.Proc pdh_CloseQuery *syscall.Proc pdh_CollectQueryData *syscall.Proc + pdh_CollectQueryDataWithTime *syscall.Proc pdh_GetFormattedCounterValue *syscall.Proc pdh_GetFormattedCounterArrayW *syscall.Proc pdh_OpenQuery *syscall.Proc @@ -187,6 +191,7 @@ func init() { pdh_AddEnglishCounterW, _ = libpdhDll.FindProc("PdhAddEnglishCounterW") // XXX: only supported on versions > Vista. pdh_CloseQuery = libpdhDll.MustFindProc("PdhCloseQuery") pdh_CollectQueryData = libpdhDll.MustFindProc("PdhCollectQueryData") + pdh_CollectQueryDataWithTime, _ = libpdhDll.FindProc("PdhCollectQueryDataWithTime") pdh_GetFormattedCounterValue = libpdhDll.MustFindProc("PdhGetFormattedCounterValue") pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW") pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery") @@ -303,6 +308,37 @@ func PdhCollectQueryData(hQuery PDH_HQUERY) uint32 { return uint32(ret) } +// PdhCollectQueryDataWithTime queries data from perfmon, retrieving the device/windows timestamp from the node it was collected on. +// Converts the filetime structure to a GO time class and returns the native time. +// +func PdhCollectQueryDataWithTime(hQuery PDH_HQUERY) (uint32, time.Time) { + var localFileTime FILETIME + ret, _, _ := pdh_CollectQueryDataWithTime.Call(uintptr(hQuery), uintptr(unsafe.Pointer(&localFileTime))) + + if ret == ERROR_SUCCESS { + var utcFileTime FILETIME + ret, _, _ := krn_LocalFileTimeToFileTime.Call( + uintptr(unsafe.Pointer(&localFileTime)), + uintptr(unsafe.Pointer(&utcFileTime))) + + if ret == 0 { + return uint32(ERROR_FAILURE), time.Now() + } + + // First convert 100-ns intervals to microseconds, then adjust for the + // epoch difference + var totalMicroSeconds int64 + totalMicroSeconds = ((int64(utcFileTime.dwHighDateTime) << 32) | int64(utcFileTime.dwLowDateTime)) / 10 + totalMicroSeconds -= EPOCH_DIFFERENCE_MICROS + + retTime := time.Unix(0, totalMicroSeconds*1000) + + return uint32(ERROR_SUCCESS), retTime + } + + return uint32(ret), time.Now() +} + // PdhGetFormattedCounterValueDouble formats the given hCounter using a 'double'. The result is set into the specialized union struct pValue. // This function does not directly translate to a Windows counterpart due to union specialization tricks. func PdhGetFormattedCounterValueDouble(hCounter PDH_HCOUNTER, lpdwType *uint32, pValue *PDH_FMT_COUNTERVALUE_DOUBLE) uint32 { diff --git a/plugins/inputs/win_perf_counters/performance_query.go b/plugins/inputs/win_perf_counters/performance_query.go index e2e2ed866..ce247a495 100644 --- a/plugins/inputs/win_perf_counters/performance_query.go +++ b/plugins/inputs/win_perf_counters/performance_query.go @@ -6,6 +6,7 @@ package win_perf_counters import ( "errors" "syscall" + "time" "unsafe" ) @@ -26,7 +27,8 @@ type PerformanceQuery interface { GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) CollectData() error - AddEnglishCounterSupported() bool + CollectDataWithTime() (time.Time, error) + IsVistaOrNewer() bool } //PdhError represents error returned from Performance Counters API @@ -61,8 +63,8 @@ func (m *PerformanceQueryImpl) Open() error { } } var handle PDH_HQUERY - ret := PdhOpenQuery(0, 0, &handle) - if ret != ERROR_SUCCESS { + + if ret := PdhOpenQuery(0, 0, &handle); ret != ERROR_SUCCESS { return NewPdhError(ret) } m.query = handle @@ -74,8 +76,8 @@ func (m *PerformanceQueryImpl) Close() error { if m.query == 0 { return errors.New("uninitialised query") } - ret := PdhCloseQuery(m.query) - if ret != ERROR_SUCCESS { + + if ret := PdhCloseQuery(m.query); ret != ERROR_SUCCESS { return NewPdhError(ret) } m.query = 0 @@ -87,8 +89,8 @@ func (m *PerformanceQueryImpl) AddCounterToQuery(counterPath string) (PDH_HCOUNT if m.query == 0 { return 0, errors.New("uninitialised query") } - ret := PdhAddCounter(m.query, counterPath, 0, &counterHandle) - if ret != ERROR_SUCCESS { + + if ret := PdhAddCounter(m.query, counterPath, 0, &counterHandle); ret != ERROR_SUCCESS { return 0, NewPdhError(ret) } return counterHandle, nil @@ -99,8 +101,7 @@ func (m *PerformanceQueryImpl) AddEnglishCounterToQuery(counterPath string) (PDH if m.query == 0 { return 0, errors.New("uninitialised query") } - ret := PdhAddEnglishCounter(m.query, counterPath, 0, &counterHandle) - if ret != ERROR_SUCCESS { + if ret := PdhAddEnglishCounter(m.query, counterPath, 0, &counterHandle); ret != ERROR_SUCCESS { return 0, NewPdhError(ret) } return counterHandle, nil @@ -110,13 +111,11 @@ func (m *PerformanceQueryImpl) AddEnglishCounterToQuery(counterPath string) (PDH func (m *PerformanceQueryImpl) GetCounterPath(counterHandle PDH_HCOUNTER) (string, error) { var bufSize uint32 var buff []byte - - ret := PdhGetCounterInfo(counterHandle, 0, &bufSize, nil) - if ret == PDH_MORE_DATA { + var ret uint32 + if ret = PdhGetCounterInfo(counterHandle, 0, &bufSize, nil); ret == PDH_MORE_DATA { buff = make([]byte, bufSize) bufSize = uint32(len(buff)) - ret = PdhGetCounterInfo(counterHandle, 0, &bufSize, &buff[0]) - if ret == ERROR_SUCCESS { + if ret = PdhGetCounterInfo(counterHandle, 0, &bufSize, &buff[0]); ret == ERROR_SUCCESS { ci := (*PDH_COUNTER_INFO)(unsafe.Pointer(&buff[0])) return UTF16PtrToString(ci.SzFullPath), nil } @@ -128,9 +127,9 @@ func (m *PerformanceQueryImpl) GetCounterPath(counterHandle PDH_HCOUNTER) (strin func (m *PerformanceQueryImpl) ExpandWildCardPath(counterPath string) ([]string, error) { var bufSize uint32 var buff []uint16 + var ret uint32 - ret := PdhExpandWildCardPath(counterPath, nil, &bufSize) - if ret == PDH_MORE_DATA { + if ret = PdhExpandWildCardPath(counterPath, nil, &bufSize); ret == PDH_MORE_DATA { buff = make([]uint16, bufSize) bufSize = uint32(len(buff)) ret = PdhExpandWildCardPath(counterPath, &buff[0], &bufSize) @@ -146,8 +145,9 @@ func (m *PerformanceQueryImpl) ExpandWildCardPath(counterPath string) ([]string, func (m *PerformanceQueryImpl) GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error) { var counterType uint32 var value PDH_FMT_COUNTERVALUE_DOUBLE - ret := PdhGetFormattedCounterValueDouble(hCounter, &counterType, &value) - if ret == ERROR_SUCCESS { + var ret uint32 + + if ret = PdhGetFormattedCounterValueDouble(hCounter, &counterType, &value); ret == ERROR_SUCCESS { if value.CStatus == PDH_CSTATUS_VALID_DATA || value.CStatus == PDH_CSTATUS_NEW_DATA { return value.DoubleValue, nil } else { @@ -161,11 +161,12 @@ func (m *PerformanceQueryImpl) GetFormattedCounterValueDouble(hCounter PDH_HCOUN func (m *PerformanceQueryImpl) GetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER) ([]CounterValue, error) { var buffSize uint32 var itemCount uint32 - ret := PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, nil) - if ret == PDH_MORE_DATA { + var ret uint32 + + if ret = PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, nil); ret == PDH_MORE_DATA { buff := make([]byte, buffSize) - ret = PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, &buff[0]) - if ret == ERROR_SUCCESS { + + if ret = PdhGetFormattedCounterArrayDouble(hCounter, &buffSize, &itemCount, &buff[0]); ret == ERROR_SUCCESS { items := (*[1 << 20]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE)(unsafe.Pointer(&buff[0]))[:itemCount] values := make([]CounterValue, 0, itemCount) for _, item := range items { @@ -181,17 +182,29 @@ func (m *PerformanceQueryImpl) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN } func (m *PerformanceQueryImpl) CollectData() error { + var ret uint32 if m.query == 0 { return errors.New("uninitialised query") } - ret := PdhCollectQueryData(m.query) - if ret != ERROR_SUCCESS { + + if ret = PdhCollectQueryData(m.query); ret != ERROR_SUCCESS { return NewPdhError(ret) } return nil } -func (m *PerformanceQueryImpl) AddEnglishCounterSupported() bool { +func (m *PerformanceQueryImpl) CollectDataWithTime() (time.Time, error) { + if m.query == 0 { + return time.Now(), errors.New("uninitialised query") + } + ret, mtime := PdhCollectQueryDataWithTime(m.query) + if ret != ERROR_SUCCESS { + return time.Now(), NewPdhError(ret) + } + return mtime, nil +} + +func (m *PerformanceQueryImpl) IsVistaOrNewer() bool { return PdhAddEnglishCounterSupported() } diff --git a/plugins/inputs/win_perf_counters/win_perf_counters.go b/plugins/inputs/win_perf_counters/win_perf_counters.go index 9ff5aefc4..d2ace5231 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters.go @@ -23,6 +23,8 @@ var sampleConfig = ` ## agent, it will not be gathered. ## Settings: # PrintValid = false # Print All matching performance counters + # Whether request a timestamp along with the PerfCounter data or just use current time + # UsePerfCounterTime=true # If UseWildcardsExpansion params is set to true, wildcards (partial wildcards in instance names and wildcards in counters names) in configured counter paths will be expanded # and in case of localized Windows, counter paths will be also localized. It also returns instance indexes in instance names. # If false, wildcards (not partial) in instance names will still be expanded, but instance indexes will not be returned in instance names. @@ -78,6 +80,7 @@ type Win_PerfCounters struct { PrintValid bool //deprecated: determined dynamically PreVistaSupport bool + UsePerfCounterTime bool Object []perfobject CountersRefreshInterval internal.Duration UseWildcardsExpansion bool @@ -107,6 +110,12 @@ type counter struct { counterHandle PDH_HCOUNTER } +type instanceGrouping struct { + name string + instance string + objectname string +} + var sanitizedChars = strings.NewReplacer("/sec", "_persec", "/Sec", "_persec", " ", "_", "%", "Percent", `\`, "") @@ -147,7 +156,7 @@ func (m *Win_PerfCounters) SampleConfig() string { func (m *Win_PerfCounters) AddItem(counterPath string, objectName string, instance string, counterName string, measurement string, includeTotal bool) error { var err error var counterHandle PDH_HCOUNTER - if !m.query.AddEnglishCounterSupported() { + if !m.query.IsVistaOrNewer() { counterHandle, err = m.query.AddCounterToQuery(counterPath) if err != nil { return err @@ -249,18 +258,15 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { m.counters = m.counters[:0] } - err = m.query.Open() - if err != nil { + if err = m.query.Open(); err != nil { return err } - err = m.ParseConfig() - if err != nil { + if err = m.ParseConfig(); err != nil { return err } //some counters need two data samples before computing a value - err = m.query.CollectData() - if err != nil { + if err = m.query.CollectData(); err != nil { return err } m.lastRefreshed = time.Now() @@ -268,37 +274,31 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { time.Sleep(time.Second) } - type InstanceGrouping struct { - name string - instance string - objectname string + var collectFields = make(map[instanceGrouping]map[string]interface{}) + + var timestamp time.Time + if m.UsePerfCounterTime && m.query.IsVistaOrNewer() { + timestamp, err = m.query.CollectDataWithTime() + if err != nil { + return err + } + } else { + timestamp = time.Now() + if err = m.query.CollectData(); err != nil { + return err + } } - var collectFields = make(map[InstanceGrouping]map[string]interface{}) - - err = m.query.CollectData() - if err != nil { - return err - } // For iterate over the known metrics and get the samples. for _, metric := range m.counters { // collect if m.UseWildcardsExpansion { value, err := m.query.GetFormattedCounterValueDouble(metric.counterHandle) if err == nil { - measurement := sanitizedChars.Replace(metric.measurement) - if measurement == "" { - measurement = "win_perf_counters" - } - - var instance = InstanceGrouping{measurement, metric.instance, metric.objectName} - if collectFields[instance] == nil { - collectFields[instance] = make(map[string]interface{}) - } - collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value) + addCounterMeasurement(metric, metric.instance, value, collectFields) } else { - //ignore invalid data from as some counters from process instances returns this sometimes - if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { + //ignore invalid data as some counters from process instances returns this sometimes + if !isKnownCounterDataError(err) { return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err) } } @@ -326,18 +326,14 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { } if add { - measurement := sanitizedChars.Replace(metric.measurement) - if measurement == "" { - measurement = "win_perf_counters" - } - var instance = InstanceGrouping{measurement, cValue.InstanceName, metric.objectName} - - if collectFields[instance] == nil { - collectFields[instance] = make(map[string]interface{}) - } - collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(cValue.Value) + addCounterMeasurement(metric, cValue.InstanceName, cValue.Value, collectFields) } } + } else { + //ignore invalid data as some counters from process instances returns this sometimes + if !isKnownCounterDataError(err) { + return fmt.Errorf("error while getting value for counter %s: %v", metric.counterPath, err) + } } } } @@ -349,12 +345,33 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error { if len(instance.instance) > 0 { tags["instance"] = instance.instance } - acc.AddFields(instance.name, fields, tags) + acc.AddFields(instance.name, fields, tags, timestamp) } return nil } +func addCounterMeasurement(metric *counter, instanceName string, value float64, collectFields map[instanceGrouping]map[string]interface{}) { + measurement := sanitizedChars.Replace(metric.measurement) + if measurement == "" { + measurement = "win_perf_counters" + } + var instance = instanceGrouping{measurement, instanceName, metric.objectName} + if collectFields[instance] == nil { + collectFields[instance] = make(map[string]interface{}) + } + collectFields[instance][sanitizedChars.Replace(metric.counter)] = float32(value) +} + +func isKnownCounterDataError(err error) bool { + if pdhErr, ok := err.(*PdhError); ok && (pdhErr.ErrorCode == PDH_INVALID_DATA || + pdhErr.ErrorCode == PDH_CALC_NEGATIVE_VALUE || + pdhErr.ErrorCode == PDH_CSTATUS_INVALID_DATA) { + return true + } + return false +} + func init() { inputs.Add("win_perf_counters", func() telegraf.Input { return &Win_PerfCounters{query: &PerformanceQueryImpl{}, CountersRefreshInterval: internal.Duration{Duration: time.Second * 60}} diff --git a/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go b/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go index c5136d30b..546dfa143 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters_integration_test.go @@ -70,6 +70,11 @@ func TestWinPerformanceQueryImpl(t *testing.T) { _, err = query.GetFormattedCounterValueDouble(hCounter) require.NoError(t, err) + now := time.Now() + mtime, err := query.CollectDataWithTime() + require.NoError(t, err) + assert.True(t, mtime.Sub(now) < time.Second) + counterPath = "\\Process(*)\\% Processor Time" paths, err := query.ExpandWildCardPath(counterPath) require.NoError(t, err) @@ -98,6 +103,10 @@ func TestWinPerformanceQueryImpl(t *testing.T) { require.NoError(t, err) arr, err := query.GetFormattedCounterArrayDouble(hCounter) + if phderr, ok := err.(*PdhError); ok && phderr.ErrorCode != PDH_INVALID_DATA && phderr.ErrorCode != PDH_CALC_NEGATIVE_VALUE { + time.Sleep(time.Second) + arr, err = query.GetFormattedCounterArrayDouble(hCounter) + } require.NoError(t, err) assert.True(t, len(arr) > 0, "Too") @@ -596,7 +605,7 @@ func TestWinPerfcountersCollect2(t *testing.T) { perfobjects[0] = PerfObject - m := Win_PerfCounters{PrintValid: false, Object: perfobjects, query: &PerformanceQueryImpl{}, UseWildcardsExpansion: true} + m := Win_PerfCounters{PrintValid: false, UsePerfCounterTime: true, Object: perfobjects, query: &PerformanceQueryImpl{}, UseWildcardsExpansion: true} var acc testutil.Accumulator err := m.Gather(&acc) require.NoError(t, err) diff --git a/plugins/inputs/win_perf_counters/win_perf_counters_test.go b/plugins/inputs/win_perf_counters/win_perf_counters_test.go index 14aec375b..07e1941a9 100644 --- a/plugins/inputs/win_perf_counters/win_perf_counters_test.go +++ b/plugins/inputs/win_perf_counters/win_perf_counters_test.go @@ -19,12 +19,14 @@ type testCounter struct { value float64 } type FakePerformanceQuery struct { - counters map[string]testCounter - addEnglishSupported bool - expandPaths map[string][]string - openCalled bool + counters map[string]testCounter + vistaAndNewer bool + expandPaths map[string][]string + openCalled bool } +var MetricTime = time.Date(2018, 5, 28, 12, 0, 0, 0, time.UTC) + func (m *testCounter) ToCounterValue() *CounterValue { _, inst, _, _ := extractObjectInstanceCounterFromQuery(m.path) if inst == "" { @@ -102,8 +104,10 @@ func (m *FakePerformanceQuery) GetFormattedCounterValueDouble(counterHandle PDH_ } else { if counter.value == 0 { return 0, NewPdhError(PDH_INVALID_DATA) - } else { + } else if counter.value == -1 { return 0, NewPdhError(PDH_CALC_NEGATIVE_VALUE) + } else { + return 0, NewPdhError(PDH_ACCESS_DENIED) } } } @@ -138,8 +142,18 @@ func (m *FakePerformanceQuery) GetFormattedCounterArrayDouble(hCounter PDH_HCOUN counters := make([]CounterValue, 0, len(e)) for _, p := range e { counter := m.findCounterByPath(p) - if counter != nil && counter.value > 0 { - counters = append(counters, *counter.ToCounterValue()) + if counter != nil { + if counter.value > 0 { + counters = append(counters, *counter.ToCounterValue()) + } else { + if counter.value == 0 { + return nil, NewPdhError(PDH_INVALID_DATA) + } else if counter.value == -1 { + return nil, NewPdhError(PDH_CALC_NEGATIVE_VALUE) + } else { + return nil, NewPdhError(PDH_ACCESS_DENIED) + } + } } else { return nil, fmt.Errorf("GetFormattedCounterArrayDouble: invalid counter : %s", p) } @@ -160,8 +174,15 @@ func (m *FakePerformanceQuery) CollectData() error { return nil } -func (m *FakePerformanceQuery) AddEnglishCounterSupported() bool { - return m.addEnglishSupported +func (m *FakePerformanceQuery) CollectDataWithTime() (time.Time, error) { + if !m.openCalled { + return time.Now(), errors.New("CollectData: uninitialised query") + } + return MetricTime, nil +} + +func (m *FakePerformanceQuery) IsVistaOrNewer() bool { + return m.vistaAndNewer } func createPerfObject(measurement string, object string, instances []string, counters []string, failOnMissing bool, includeTotal bool) []perfobject { @@ -198,7 +219,7 @@ func TestAddItemSimple(t *testing.T) { expandPaths: map[string][]string{ cps1[0]: cps1, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -216,7 +237,7 @@ func TestAddItemInvalidCountPath(t *testing.T) { expandPaths: map[string][]string{ cps1[0]: {"\\O/C"}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -238,7 +259,7 @@ func TestParseConfigBasic(t *testing.T) { cps1[2]: {cps1[2]}, cps1[3]: {cps1[3]}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -270,7 +291,7 @@ func TestParseConfigNoInstance(t *testing.T) { cps1[0]: {cps1[0]}, cps1[1]: {cps1[1]}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -303,7 +324,7 @@ func TestParseConfigInvalidCounterError(t *testing.T) { cps1[1]: {cps1[1]}, cps1[2]: {cps1[2]}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -334,7 +355,7 @@ func TestParseConfigInvalidCounterNoError(t *testing.T) { cps1[1]: {cps1[1]}, cps1[2]: {cps1[2]}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -364,7 +385,7 @@ func TestParseConfigTotalExpansion(t *testing.T) { expandPaths: map[string][]string{ "\\O(*)\\*": cps1, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -381,7 +402,7 @@ func TestParseConfigTotalExpansion(t *testing.T) { expandPaths: map[string][]string{ "\\O(*)\\*": cps1, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -401,7 +422,7 @@ func TestParseConfigExpand(t *testing.T) { expandPaths: map[string][]string{ "\\O(*)\\*": cps1, }, - addEnglishSupported: true, + vistaAndNewer: true, }} err = m.query.Open() require.NoError(t, err) @@ -425,7 +446,7 @@ func TestSimpleGather(t *testing.T) { expandPaths: map[string][]string{ cp1: {cp1}, }, - addEnglishSupported: false, + vistaAndNewer: false, }} var acc1 testutil.Accumulator err = m.Gather(&acc1) @@ -449,7 +470,65 @@ func TestSimpleGather(t *testing.T) { err = m.Gather(&acc2) require.NoError(t, err) acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) +} +func TestSimpleGatherWithTimestamp(t *testing.T) { + var err error + if testing.Short() { + t.Skip("Skipping long taking test in short mode") + } + measurement := "test" + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + cp1 := "\\O(I)\\C" + m := Win_PerfCounters{PrintValid: false, UsePerfCounterTime: true, Object: perfObjects, query: &FakePerformanceQuery{ + counters: createCounterMap([]string{cp1}, []float64{1.2}), + expandPaths: map[string][]string{ + cp1: {cp1}, + }, + vistaAndNewer: true, + }} + var acc1 testutil.Accumulator + err = m.Gather(&acc1) + require.NoError(t, err) + + fields1 := map[string]interface{}{ + "C": float32(1.2), + } + tags1 := map[string]string{ + "instance": "I", + "objectname": "O", + } + acc1.AssertContainsTaggedFields(t, measurement, fields1, tags1) + assert.True(t, acc1.HasTimestamp(measurement, MetricTime)) +} + +func TestGatherError(t *testing.T) { + var err error + if testing.Short() { + t.Skip("Skipping long taking test in short mode") + } + measurement := "test" + perfObjects := createPerfObject(measurement, "O", []string{"I"}, []string{"C"}, false, false) + cp1 := "\\O(I)\\C" + m := Win_PerfCounters{PrintValid: false, Object: perfObjects, query: &FakePerformanceQuery{ + counters: createCounterMap([]string{cp1}, []float64{-2}), + expandPaths: map[string][]string{ + cp1: {cp1}, + }, + vistaAndNewer: false, + }} + var acc1 testutil.Accumulator + err = m.Gather(&acc1) + require.Error(t, err) + + m.UseWildcardsExpansion = true + m.counters = nil + m.lastRefreshed = time.Time{} + + var acc2 testutil.Accumulator + + err = m.Gather(&acc2) + require.Error(t, err) } func TestGatherInvalidDataIgnore(t *testing.T) { @@ -467,7 +546,7 @@ func TestGatherInvalidDataIgnore(t *testing.T) { cps1[1]: {cps1[1]}, cps1[2]: {cps1[2]}, }, - addEnglishSupported: false, + vistaAndNewer: false, }} var acc1 testutil.Accumulator err = m.Gather(&acc1) @@ -506,7 +585,7 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { expandPaths: map[string][]string{ "\\O(*)\\*": cps1, }, - addEnglishSupported: true, + vistaAndNewer: true, } m := Win_PerfCounters{PrintValid: false, Object: perfObjects, UseWildcardsExpansion: true, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}} var acc1 testutil.Accumulator @@ -540,7 +619,7 @@ func TestGatherRefreshingWithExpansion(t *testing.T) { expandPaths: map[string][]string{ "\\O(*)\\*": cps2, }, - addEnglishSupported: true, + vistaAndNewer: true, } m.query = fpm fpm.Open() @@ -592,7 +671,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { "\\O(*)\\C1": {cps1[0], cps1[2]}, "\\O(*)\\C2": {cps1[1], cps1[3]}, }, - addEnglishSupported: true, + vistaAndNewer: true, } m := Win_PerfCounters{PrintValid: false, Object: perfObjects, UseWildcardsExpansion: false, query: fpm, CountersRefreshInterval: internal.Duration{Duration: time.Second * 10}} var acc1 testutil.Accumulator @@ -628,7 +707,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { "\\O(*)\\C1": {cps2[0], cps2[2], cps2[4]}, "\\O(*)\\C2": {cps2[1], cps2[3], cps2[5]}, }, - addEnglishSupported: true, + vistaAndNewer: true, } m.query = fpm fpm.Open() @@ -662,7 +741,7 @@ func TestGatherRefreshingWithoutExpansion(t *testing.T) { "\\O(*)\\C2": {cps3[1], cps3[4]}, "\\O(*)\\C3": {cps3[2], cps3[5]}, }, - addEnglishSupported: true, + vistaAndNewer: true, } m.query = fpm m.Object = perfObjects @@ -710,7 +789,7 @@ func TestGatherTotalNoExpansion(t *testing.T) { "\\O(*)\\C1": {cps1[0], cps1[2]}, "\\O(*)\\C2": {cps1[1], cps1[3]}, }, - addEnglishSupported: true, + vistaAndNewer: true, }} var acc1 testutil.Accumulator err = m.Gather(&acc1)