Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

386fix #4076

Closed
wants to merge 9 commits into from
6 changes: 6 additions & 0 deletions plugins/inputs/win_perf_counters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ 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`

#### UseWinTimestamps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a more descriptive name would be UsePerfCounterTime?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. I like it. Changed


Bool, if set to `true` will request a timestamp along with the PerfCounter data.

Note: This call may not be supported on all versions of windows. If you receive an error, set this value to false to disable the feature, and searching for this call entirely.

### Object

See Entry below.
Expand Down
180 changes: 144 additions & 36 deletions plugins/inputs/win_perf_counters/pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,32 @@ package win_perf_counters
import (
"fmt"
"syscall"
"time"
"unsafe"

"golang.org/x/sys/windows"
)

// 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 (
HANDLE uintptr
)

type (
LPCTSTR uintptr
)

type (
LONGLONG int64
)

// PDH error codes, which can be returned by all Pdh* functions. Taken from mingw-w64 pdhmsg.h
const (
PDH_CSTATUS_VALID_DATA = 0x00000000 // The returned data is valid.
Expand Down Expand Up @@ -156,76 +167,89 @@ const (
PERF_DETAIL_STANDARD = 0x0000FFFF
)

const (
errnoERROR_IO_PENDING = 997
)

var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)

type (
PDH_HQUERY HANDLE // query handle
PDH_HCOUNTER HANDLE // counter handle
)

// Union specialization for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
DoubleValue float64
type SYSTEMTIME struct {
wYear uint16
wMonth uint16
wDayOfWeek uint16
wDay uint16
wHour uint16
wMinute uint16
wSecond uint16
wMilliseconds uint16
}

// Union specialization for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct {
CStatus uint32
LargeValue int64
}

// Union specialization for long values
type PDH_FMT_COUNTERVALUE_LONG struct {
CStatus uint32
LongValue int32
padding [4]byte
}

// Union specialization for double values, used by PdhGetFormattedCounterArrayDouble()
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}

// Union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge()
type PDH_FMT_COUNTERVALUE_ITEM_LARGE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LARGE
}

// Union specialization for long values, used by PdhGetFormattedCounterArrayLong()
type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_LONG
type FILETIME struct {
dwLowDateTime uint32
dwHighDateTime uint32
}

var (
// Library
libpdhDll *syscall.DLL
libkrnDll *syscall.DLL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move all the kernel32 code to a new file: kernel32.go

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Done


// Functions
pdh_ConnectMachine *syscall.Proc
pdh_AddCounterW *syscall.Proc
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
pdh_ValidatePathW *syscall.Proc

krn_FileTimeToSystemTime *syscall.Proc
krn_FileTimeToLocalFileTime *syscall.Proc
krn_LocalFileTimeToFileTime *syscall.Proc
krn_WideCharToMultiByte *syscall.Proc
)

func init() {
// Library
libpdhDll = syscall.MustLoadDLL("pdh.dll")
libkrnDll = syscall.MustLoadDLL("Kernel32.dll")

// Functions
pdh_ConnectMachine = libpdhDll.MustFindProc("PdhConnectMachineW")
pdh_AddCounterW = libpdhDll.MustFindProc("PdhAddCounterW")
pdh_AddEnglishCounterW, _ = libpdhDll.FindProc("PdhAddEnglishCounterW") // XXX: only supported on versions > Vista.
pdh_CloseQuery = libpdhDll.MustFindProc("PdhCloseQuery")
pdh_CollectQueryData = libpdhDll.MustFindProc("PdhCollectQueryData")

pdh_GetFormattedCounterValue = libpdhDll.MustFindProc("PdhGetFormattedCounterValue")
pdh_GetFormattedCounterArrayW = libpdhDll.MustFindProc("PdhGetFormattedCounterArrayW")
pdh_OpenQuery = libpdhDll.MustFindProc("PdhOpenQuery")
pdh_ValidatePathW = libpdhDll.MustFindProc("PdhValidatePathW")

krn_FileTimeToSystemTime = libkrnDll.MustFindProc("FileTimeToSystemTime")
krn_FileTimeToLocalFileTime = libkrnDll.MustFindProc("FileTimeToLocalFileTime")
krn_LocalFileTimeToFileTime = libkrnDll.MustFindProc("LocalFileTimeToFileTime")
krn_WideCharToMultiByte = libkrnDll.MustFindProc("WideCharToMultiByte")
}

func PdhUseWinTimestamps() {
pdh_CollectQueryDataWithTime = libpdhDll.MustFindProc("PdhCollectQueryDataWithTime")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do like above on AddEnglishCounterW?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn something new everyday. Much cleaner. Thank-you

}

func PdhConnectMachine(szMachineName string) uint32 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to find where this is called from, is it used?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, and I neglected to write a comment header stating this. In experimenting I had added the call, but ended up not requiring it (it was actually orders of magnitude slower doing the connect call and then querying for the counters. I've noted this in a comment header.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you instead remove all the calls that are not used?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure. I can. I just seems a shame, the call works, and may be useful in the future. Perhaps there is an alternative place to put it? I'll remove it for now. At least it will be in my git history if I need to access it again.

ptxt, _ := syscall.UTF16PtrFromString(szMachineName)
ret, _, _ := pdh_ConnectMachine.Call(uintptr(unsafe.Pointer(ptxt)))
return uint32(ret)
}

// Adds the specified counter to the query. This is the internationalized version. Preferably, use the
Expand Down Expand Up @@ -329,6 +353,37 @@ func PdhCollectQueryData(hQuery PDH_HQUERY) uint32 {
return uint32(ret)
}

// 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()
}

// 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 Expand Up @@ -414,11 +469,64 @@ func PdhValidatePath(path string) uint32 {
return uint32(ret)
}

func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}

// The windows native call for converting a 16-bit wide character string (UTF-16) to a null terminated string.
//
// Note: If you call the function and not pass in an out string, the return value will be the length of the
// input string.
// Example usage:
// cc, err := WideCharToMultiByte(65001, 0, s, -1, nil, 0)
// if err != nil {
// fmt.Println("CONVERSION ERROR: ", err)
// }
//
// fmt.Println("Length bytes: ", cc)
// n, err := WideCharToMultiByte(65001, 0, s, 1<<29, &outStr[0], 1<<29)
// if err != nil {
// fmt.Println("CONVERSION ERROR: ", err)
// }
// fmt.Println("Converted bytes: ", n)
//
func WideCharToMultiByte(codePage uint32, dwFlags uint32, wchar *uint16, nwchar int32, str *byte, nstr int32) (nwrite int32, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this called from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not. It was one of those things I used to try and fix the win32 bug without padding the structures. The call itself works good and I thought it might be useful in the future. I've noted this in a comment, but I can remove it as well.

r0, _, e1 := krn_WideCharToMultiByte.Call(
uintptr(codePage),
uintptr(dwFlags),
uintptr(unsafe.Pointer(str)),
uintptr(nstr),
uintptr(unsafe.Pointer(wchar)),
uintptr(nwchar),
)

nwrite = int32(r0)
if nwrite == 0 {
if e1 != nil {
err = errnoErr(e1.(syscall.Errno))
} else {
err = syscall.EINVAL
}
}

return nwrite, err
}

func UTF16PtrToString(s *uint16) string {
if s == nil {
return ""
}
return syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(s))[0:])

return syscall.UTF16ToString((*[20]uint16)(unsafe.Pointer(s))[:])
Copy link
Contributor

@vlastahajek vlastahajek May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, this is not right. You have to cast pointer to large enough array, otherwise you could get out of bounds.
There is 100% probability that strings with more than 20 characters go though this function .
Keeping original will not harm 32-bit support, as array size is type int, which is at least 32 bits, so [1<<29] is OK

}

func PdhFormatError(msgId uint32) string {
Expand Down
74 changes: 74 additions & 0 deletions plugins/inputs/win_perf_counters/pdh_386.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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

// Union specialization for double values
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
padding uint32 // TODO: could well be broken on amd64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the type of padding to [4]byte. Remove these comments, make sure there is a testcase to check each type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

DoubleValue float64
}

// Union specialization for 64 bit integer values
type PDH_FMT_COUNTERVALUE_LARGE struct {
CStatus uint32
padding uint32 // TODO: could well be broken on amd64
LargeValue int64
}

// Union specialization for long values
type PDH_FMT_COUNTERVALUE_LONG struct {
CStatus uint32
LongValue int32
padding [4]byte
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these all be the same size? This one is smaller than the rest by 32bits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't actually change this one. It was already written this way, so my question is yours. It just looks like I changed it because I moved it to a new file, but amd64 is the same. I don't understand why it was done this way either but there is no need to access data in the struct after the padding so I was inclined not to worry about it...


type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16
padding uint32 // TODO: could well be broken on amd64
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}

// Union specialization for 'large' values, used by PdhGetFormattedCounterArrayLarge()
type PDH_FMT_COUNTERVALUE_ITEM_LARGE struct {
SzName *uint16 // pointer to a string
padding uint32 // TODO: could well be broken on amd64
FmtValue PDH_FMT_COUNTERVALUE_LARGE
}

// Union specialization for long values, used by PdhGetFormattedCounterArrayLong()
type PDH_FMT_COUNTERVALUE_ITEM_LONG struct {
SzName *uint16 // pointer to a string
padding uint32 // TODO: could well be broken on amd64
FmtValue PDH_FMT_COUNTERVALUE_LONG
}
Loading