From 7e0f45265a88de9cd3f1ba3bb31d0dbff4cb6d9b Mon Sep 17 00:00:00 2001 From: Alexey Diyan Date: Fri, 7 Jun 2013 20:22:04 +0300 Subject: [PATCH] Gmond module for collect Windows Perfomance Counters data. Proof of concept --- win_perfcounter/README.mkdn | 32 ++++ win_perfcounter/conf.d/win_perfcounter.pyconf | 22 +++ .../python_modules/win_perfcounter.py | 157 ++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 win_perfcounter/README.mkdn create mode 100644 win_perfcounter/conf.d/win_perfcounter.pyconf create mode 100644 win_perfcounter/python_modules/win_perfcounter.py diff --git a/win_perfcounter/README.mkdn b/win_perfcounter/README.mkdn new file mode 100644 index 00000000..3ee814b9 --- /dev/null +++ b/win_perfcounter/README.mkdn @@ -0,0 +1,32 @@ +win_perfcounter +=============== + +python module for ganglia 3.1. + +WARNING: This module is just proof-of-concept, it does not work. + +"win_perfcounter" uses ctypes and Windows API's Performance Data Helper functions in order to get perfomance counter data. + +http://msdn.microsoft.com/en-us/library/windows/desktop/aa373214%28v=vs.85%29.aspx + +Module is self-contained and does not require any third-party Python packages such as PyWin32 or WMI. + +What was done: +- Implementation were tested on Windows Server 2008 R2 Standard 64 bit +- Gmond/Cygwin 3.1.7 build for Windows was downloaded from http://tapir.sajinet.com.pe/ganglia/ganglia-3.1.7-bin.zip +- Since Gmond build were linked with Python 2.5 module were developed for v2.5 +- Cygwin Python 2.5 was downloaded from http://mirror.mcs.anl.gov/cygwin/release/python/python-2.5.5-1.tar.bz2 +- Windows Stats were used as baseline (https://bitbucket.org/mixmastamyk/winstats) +- Windows Stats did not work in Cygwin environment, so ctypes was monkey patched in order to do that +- ctypes monkey patch approach was taken from pyglet library (https://github.com/adamlwgriffiths/Pyglet/blob/master/pyglet/__init__.py#L221) +- gmond are able to load this module on Windows server +- Python module are able to read perfomance data from two different counters + +TODO: +- metric_init method should return descriptors structure +- basically we have to implement all the glue code between win_perfcounter.pyconf and gmond API +- write a set of tests + +## AUTHOR + +Alexey Diyan \ No newline at end of file diff --git a/win_perfcounter/conf.d/win_perfcounter.pyconf b/win_perfcounter/conf.d/win_perfcounter.pyconf new file mode 100644 index 00000000..d8891b68 --- /dev/null +++ b/win_perfcounter/conf.d/win_perfcounter.pyconf @@ -0,0 +1,22 @@ +modules { + module { + name = "win_perfcounter" + language = "python" + + # Which metric group should these metrics be put into + param metric_group { + value = "win_perfcounter" + } + } +} + +collection_group { + collect_every = 30 + time_threshold = 90 + + metric { + name = "win_perf_couner_metric" + title = "Windows Perfomance Counter Metric" + value_threshold = 0 + } +} diff --git a/win_perfcounter/python_modules/win_perfcounter.py b/win_perfcounter/python_modules/win_perfcounter.py new file mode 100644 index 00000000..a9dc7b8e --- /dev/null +++ b/win_perfcounter/python_modules/win_perfcounter.py @@ -0,0 +1,157 @@ +from __future__ import absolute_import +# TODO update winstats source code and return to the upstream project +# https://bitbucket.org/mixmastamyk/winstats/src/fa7a0b568a5c/winstats.py +import sys +import types + +# TODO remove hardcoded True value +if True or sys.platform == 'cygwin': + # This hack pretends that the posix-like ctypes provides windows + # functionality. COM does not work with this hack. + import ctypes + ctypes.windll = ctypes.cdll + ctypes.oledll = ctypes.cdll + ctypes.WINFUNCTYPE = ctypes.CFUNCTYPE + ctypes.HRESULT = ctypes.c_long + + # http://epydoc.sourceforge.net/stdlib/ctypes.wintypes-module.html + wintypes_module = types.ModuleType('wintypes'.encode('ascii')) + wintypes_module.HANDLE = ctypes.c_ulong + wintypes_module.LONG = ctypes.c_long + wintypes_module.LPCSTR = ctypes.c_char_p + wintypes_module.LPCWSTR = ctypes.c_wchar_p + wintypes_module.DWORD = ctypes.c_ulong + sys.modules['ctypes.wintypes'] = wintypes_module + + +from ctypes import byref +from ctypes import Structure, Union +from ctypes.wintypes import HANDLE, LONG, LPCSTR, LPCWSTR, DWORD + + +# PerfMon -------------------------------------------------------------------- +HQUERY = HCOUNTER = HANDLE +pdh = ctypes.windll.pdh +PDH_FMT_RAW = 16L +PDH_FMT_ANSI = 32L +PDH_FMT_UNICODE = 64L +PDH_FMT_LONG = 256L +PDH_FMT_DOUBLE = 512L +PDH_FMT_LARGE = 1024L +PDH_FMT_1000 = 8192L +PDH_FMT_NODATA = 16384L +PDH_FMT_NOSCALE = 4096L + +#~ dwType = DWORD(0) +_pdh_errcodes = { + 0x00000000: 'PDH_CSTATUS_VALID_DATA', + 0x800007d0: 'PDH_CSTATUS_NO_MACHINE', + 0x800007d2: 'PDH_MORE_DATA', + 0x800007d5: 'PDH_NO_DATA', + 0xc0000bb8: 'PDH_CSTATUS_NO_OBJECT', + 0xc0000bb9: 'PDH_CSTATUS_NO_COUNTER', + 0xc0000bbb: 'PDH_MEMORY_ALLOCATION_FAILURE', + 0xc0000bbc: 'PDH_INVALID_HANDLE', + 0xc0000bbd: 'PDH_INVALID_ARGUMENT', + 0xc0000bc0: 'PDH_CSTATUS_BAD_COUNTERNAME', + 0xc0000bc2: 'PDH_INSUFFICIENT_BUFFER', + 0xc0000bc6: 'PDH_INVALID_DATA', + 0xc0000bd3: 'PDH_NOT_IMPLEMENTED', + 0xc0000bd4: 'PDH_STRING_NOT_FOUND', +} + + +class PDH_Counter_Union(Union): + _fields_ = [ + ('longValue', LONG), + ('doubleValue', ctypes.c_double), + ('largeValue', ctypes.c_longlong), + ('ansiValue', LPCSTR), # aka AnsiString... + ('unicodeValue', LPCWSTR) # aka WideString.. + ] + + +class PDH_FMT_COUNTERVALUE(Structure): + _fields_ = [ + ('CStatus', DWORD), + ('union', PDH_Counter_Union), + ] + + +def get_pdherr(code): + """Convert a PDH error code.""" + code &= 2 ** 32 - 1 # signed to unsigned :/ + return _pdh_errcodes.get(code, code) + + +def get_perfdata(counter_name, fmt='long', delay=0): + """ Wrap up PerfMon's low-level API. + + Arguments: + counter_name Windows PerfMon counter name. + fmt One of 'long', 'double', 'large', 'ansi', 'unicode' + delay Some metrics need a delay to acquire (as int ms). + Returns: + requested Value + Raises: + WindowsError + """ + counter_name = unicode(counter_name) + FMT = globals().get('PDH_FMT_' + fmt.upper(), PDH_FMT_LONG) + hQuery = HQUERY() + hCounter = HCOUNTER() + value = PDH_FMT_COUNTERVALUE() + + # Open Sie, bitte + errs = pdh.PdhOpenQueryW(None, 0, byref(hQuery)) + if errs: + raise OSError('PdhOpenQueryW failed: %s' % get_pdherr(errs)) + + # Add Counter + errs = pdh.PdhAddCounterW(hQuery, counter_name, 0, byref(hCounter)) + if errs: + raise OSError('PdhAddCounterW failed: %s' % get_pdherr(errs)) + + # Collect + errs = pdh.PdhCollectQueryData(hQuery) + if errs: + raise OSError('PdhCollectQueryData failed: %s' % get_pdherr(errs)) + if delay: + ctypes.windll.kernel32.Sleep(delay) + errs = pdh.PdhCollectQueryData(hQuery) + if errs: + raise OSError(('PdhCollectQueryData failed: %s' % + get_pdherr(errs))) + + # Format # byref(dwType), is optional + errs = pdh.PdhGetFormattedCounterValue(hCounter, FMT, None, byref(value)) + if errs: + raise OSError(('PdhGetFormattedCounterValue failed: %s' % + get_pdherr(errs))) + + # Close + errs = pdh.PdhCloseQuery(hQuery) + if errs: + raise OSError('PdhCloseQuery failed: %s' % get_pdherr(errs)) + + return getattr(value.union, fmt + 'Value') + + +def metric_handler(name): + print('metric_handler_name: ' + name) + + +def metric_init(params): + cpu_usage = get_perfdata('\Processor(_Total)\% Processor Time', + fmt='double', delay=100) + print('cpu usage: ' + str(cpu_usage)) + + disk_queue = get_perfdata('\LogicalDisk(_Total)\Current Disk Queue Length', + fmt='long', delay=100) + print('disk_queue: ' + str(disk_queue)) + + print('metric_init_params ' + str(params)) + + +def metric_cleanup(): + pass \ No newline at end of file