Skip to content

Commit

Permalink
Trying to fix influxdata#4250 - added possibility to use timestamp fr…
Browse files Browse the repository at this point in the history
…om perf counters
  • Loading branch information
vlastahajek committed Jun 11, 2018
1 parent 87f711a commit f518141
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 28 deletions.
9 changes: 9 additions & 0 deletions plugins/inputs/win_perf_counters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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.
Expand Down
73 changes: 73 additions & 0 deletions plugins/inputs/win_perf_counters/kernel32.go
Original file line number Diff line number Diff line change
@@ -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 <an2048@googlemail.com>
// Joseph Watson <jtwatson@linux-consulting.us>
// Kevin Pors <krpors@gmail.com>

// +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")
}
40 changes: 38 additions & 2 deletions plugins/inputs/win_perf_counters/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions plugins/inputs/win_perf_counters/performance_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package win_perf_counters
import (
"errors"
"syscall"
"time"
"unsafe"
)

Expand All @@ -19,7 +20,8 @@ type PerformanceQuery interface {
ExpandWildCardPath(counterPath string) ([]string, error)
GetFormattedCounterValueDouble(hCounter PDH_HCOUNTER) (float64, error)
CollectData() error
AddEnglishCounterSupported() bool
CollectDataWithTime() (time.Time, error)
IsVistaOrNewer() bool
}

//PdhError represents error returned from Performance Counters API
Expand Down Expand Up @@ -162,7 +164,18 @@ func (m *PerformanceQueryImpl) CollectData() error {
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()
}

Expand Down
23 changes: 18 additions & 5 deletions plugins/inputs/win_perf_counters/win_perf_counters.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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
# Period after which counters will be reread from configuration and wildcards in counter paths expanded
CountersRefreshInterval="1m"
Expand Down Expand Up @@ -73,6 +75,7 @@ type Win_PerfCounters struct {
PrintValid bool
//deprecated: determined dynamically
PreVistaSupport bool
UsePerfCounterTime bool
Object []perfobject
CountersRefreshInterval internal.Duration

Expand Down Expand Up @@ -138,7 +141,7 @@ func (m *Win_PerfCounters) SampleConfig() string {
}

func (m *Win_PerfCounters) AddItem(counterPath string, instance string, measurement string, includeTotal bool) error {
if !m.query.AddEnglishCounterSupported() {
if !m.query.IsVistaOrNewer() {
_, err := m.query.AddCounterToQuery(counterPath)
if err != nil {
return err
Expand Down Expand Up @@ -254,10 +257,20 @@ func (m *Win_PerfCounters) Gather(acc telegraf.Accumulator) error {

var collectFields = make(map[InstanceGrouping]map[string]interface{})

err = m.query.CollectData()
if err != nil {
return err
var timestamp time.Time
if m.UsePerfCounterTime && m.query.IsVistaOrNewer() {
timestamp, err = m.query.CollectDataWithTime()
if err != nil {
return err
}
} else {
timestamp = time.Now()
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
Expand Down Expand Up @@ -288,7 +301,7 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit f518141

Please sign in to comment.