salt/utils/win_pdh.py
# -*- coding: utf-8 -*-
r'''
Salt Util for getting system information with the Performance Data Helper (pdh).
Counter information is gathered from current activity or log files.
Usage:
.. code-block:: python
import salt.utils.win_pdh
# Get a list of Counter objects
salt.utils.win_pdh.list_objects()
# Get a list of ``Processor`` instances
salt.utils.win_pdh.list_instances('Processor')
# Get a list of ``Processor`` counters
salt.utils.win_pdh.list_counters('Processor')
# Get the value of a single counter
# \Processor(*)\% Processor Time
salt.utils.win_pdh.get_counter('Processor', '*', '% Processor Time')
# Get the values of multiple counters
counter_list = [('Processor', '*', '% Processor Time'),
('System', None, 'Context Switches/sec'),
('Memory', None, 'Pages/sec'),
('Server Work Queues', '*', 'Queue Length')]
salt.utils.win_pdh.get_counters(counter_list)
# Get all counters for the Processor object
salt.utils.win_pdh.get_all_counters('Processor')
'''
# https://www.cac.cornell.edu/wiki/index.php?title=Performance_Data_Helper_in_Python_with_win32pdh
# https://docs.microsoft.com/en-us/windows/desktop/perfctrs/using-the-pdh-functions-to-consume-counter-data
# Import python libs
from __future__ import absolute_import, unicode_literals
import logging
import time
# Import 3rd party libs
try:
import pywintypes
import win32pdh
HAS_WINDOWS_MODULES = True
except ImportError:
HAS_WINDOWS_MODULES = False
# Import salt libs
import salt.utils.platform
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__file__)
# Define the virtual name
__virtualname__ = 'pdh'
def __virtual__():
'''
Only works on Windows systems with the PyWin32
'''
if not salt.utils.platform.is_windows():
return False, 'salt.utils.win_pdh: Requires Windows'
if not HAS_WINDOWS_MODULES:
return False, 'salt.utils.win_pdh: Missing required modules'
return __virtualname__
class Counter(object):
'''
Counter object
Has enumerations and functions for working with counters
'''
# The dwType field from GetCounterInfo returns the following, or'ed.
# These come from WinPerf.h
PERF_SIZE_DWORD = 0x00000000
PERF_SIZE_LARGE = 0x00000100
PERF_SIZE_ZERO = 0x00000200 # for Zero Length fields
PERF_SIZE_VARIABLE_LEN = 0x00000300
# length is in the CounterLength field of the Counter Definition structure
# select one of the following values to indicate the counter field usage
PERF_TYPE_NUMBER = 0x00000000 # a number (not a counter)
PERF_TYPE_COUNTER = 0x00000400 # an increasing numeric value
PERF_TYPE_TEXT = 0x00000800 # a text field
PERF_TYPE_ZERO = 0x00000C00 # displays a zero
# If the PERF_TYPE_NUMBER field was selected, then select one of the
# following to describe the Number
PERF_NUMBER_HEX = 0x00000000 # display as HEX value
PERF_NUMBER_DECIMAL = 0x00010000 # display as a decimal integer
PERF_NUMBER_DEC_1000 = 0x00020000 # display as a decimal/1000
# If the PERF_TYPE_COUNTER value was selected then select one of the
# following to indicate the type of counter
PERF_COUNTER_VALUE = 0x00000000 # display counter value
PERF_COUNTER_RATE = 0x00010000 # divide ctr / delta time
PERF_COUNTER_FRACTION = 0x00020000 # divide ctr / base
PERF_COUNTER_BASE = 0x00030000 # base value used in fractions
PERF_COUNTER_ELAPSED = 0x00040000 # subtract counter from current time
PERF_COUNTER_QUEUE_LEN = 0x00050000 # Use Queue len processing func.
PERF_COUNTER_HISTOGRAM = 0x00060000 # Counter begins or ends a histogram
# If the PERF_TYPE_TEXT value was selected, then select one of the
# following to indicate the type of TEXT data.
PERF_TEXT_UNICODE = 0x00000000 # type of text in text field
PERF_TEXT_ASCII = 0x00010000 # ASCII using the CodePage field
# Timer SubTypes
PERF_TIMER_TICK = 0x00000000 # use system perf. freq for base
PERF_TIMER_100NS = 0x00100000 # use 100 NS timer time base units
PERF_OBJECT_TIMER = 0x00200000 # use the object timer freq
# Any types that have calculations performed can use one or more of the
# following calculation modification flags listed here
PERF_DELTA_COUNTER = 0x00400000 # compute difference first
PERF_DELTA_BASE = 0x00800000 # compute base diff as well
PERF_INVERSE_COUNTER = 0x01000000 # show as 1.00-value (assumes:
PERF_MULTI_COUNTER = 0x02000000 # sum of multiple instances
# Select one of the following values to indicate the display suffix (if any)
PERF_DISPLAY_NO_SUFFIX = 0x00000000 # no suffix
PERF_DISPLAY_PER_SEC = 0x10000000 # "/sec"
PERF_DISPLAY_PERCENT = 0x20000000 # "%"
PERF_DISPLAY_SECONDS = 0x30000000 # "secs"
PERF_DISPLAY_NO_SHOW = 0x40000000 # value is not displayed
def build_counter(obj, instance, instance_index, counter):
r'''
Makes a fully resolved counter path. Counter names are formatted like
this:
``\Processor(*)\% Processor Time``
The above breaks down like this:
obj = 'Processor'
instance = '*'
counter = '% Processor Time'
Args:
obj (str):
The top level object
instance (str):
The instance of the object
instance_index (int):
The index of the instance. Can usually be 0
counter (str):
The name of the counter
Returns:
Counter: A Counter object with the path if valid
Raises:
CommandExecutionError: If the path is invalid
'''
path = win32pdh.MakeCounterPath(
(None, obj, instance, None, instance_index, counter), 0)
if win32pdh.ValidatePath(path) is 0:
return Counter(path, obj, instance, instance_index, counter)
raise CommandExecutionError('Invalid counter specified: {0}'.format(path))
build_counter = staticmethod(build_counter)
def __init__(self, path, obj, instance, index, counter):
self.path = path
self.obj = obj
self.instance = instance
self.index = index
self.counter = counter
self.handle = None
self.info = None
self.type = None
def add_to_query(self, query):
'''
Add the current path to the query
Args:
query (obj):
The handle to the query to add the counter
'''
self.handle = win32pdh.AddCounter(query, self.path)
def get_info(self):
'''
Get information about the counter
.. note::
GetCounterInfo sometimes crashes in the wrapper code. Fewer crashes
if this is called after sampling data.
'''
if not self.info:
ci = win32pdh.GetCounterInfo(self.handle, 0)
self.info = {
'type': ci[0],
'version': ci[1],
'scale': ci[2],
'default_scale': ci[3],
'user_data': ci[4],
'query_user_data': ci[5],
'full_path': ci[6],
'machine_name': ci[7][0],
'object_name': ci[7][1],
'instance_name': ci[7][2],
'parent_instance': ci[7][3],
'instance_index': ci[7][4],
'counter_name': ci[7][5],
'explain_text': ci[8]
}
return self.info
def value(self):
'''
Return the counter value
Returns:
long: The counter value
'''
(counter_type, value) = win32pdh.GetFormattedCounterValue(
self.handle, win32pdh.PDH_FMT_DOUBLE)
self.type = counter_type
return value
def type_string(self):
'''
Returns the names of the flags that are set in the Type field
It can be used to format the counter.
'''
type = self.get_info()['type']
type_list = []
for member in dir(self):
if member.startswith("PERF_"):
bit = getattr(self, member)
if bit and bit & type:
type_list.append(member[5:])
return type_list
def __str__(self):
return self.path
def list_objects():
'''
Get a list of available counter objects on the system
Returns:
list: A list of counter objects
'''
return sorted(win32pdh.EnumObjects(None, None, -1, 0))
def list_counters(obj):
'''
Get a list of counters available for the object
Args:
obj (str):
The name of the counter object. You can get a list of valid names
using the ``list_objects`` function
Returns:
list: A list of counters available to the passed object
'''
return win32pdh.EnumObjectItems(None, None, obj, -1, 0)[0]
def list_instances(obj):
'''
Get a list of instances available for the object
Args:
obj (str):
The name of the counter object. You can get a list of valid names
using the ``list_objects`` function
Returns:
list: A list of instances available to the passed object
'''
return win32pdh.EnumObjectItems(None, None, obj, -1, 0)[1]
def build_counter_list(counter_list):
r'''
Create a list of Counter objects to be used in the pdh query
Args:
counter_list (list):
A list of tuples containing counter information. Each tuple should
contain the object, instance, and counter name. For example, to
get the ``% Processor Time`` counter for all Processors on the
system (``\Processor(*)\% Processor Time``) you would pass a tuple
like this:
```
counter_list = [('Processor', '*', '% Processor Time')]
```
If there is no ``instance`` for the counter, pass ``None``
Multiple counters can be passed like so:
```
counter_list = [('Processor', '*', '% Processor Time'),
('System', None, 'Context Switches/sec')]
```
.. note::
Invalid counters are ignored
Returns:
list: A list of Counter objects
'''
counters = []
index = 0
for obj, instance, counter_name in counter_list:
try:
counter = Counter.build_counter(obj, instance, index, counter_name)
index += 1
counters.append(counter)
except CommandExecutionError as exc:
# Not a valid counter
log.debug(exc.strerror)
continue
return counters
def get_all_counters(obj, instance_list=None):
'''
Get the values for all counters available to a Counter object
Args:
obj (str):
The name of the counter object. You can get a list of valid names
using the ``list_objects`` function
instance_list (list):
A list of instances to return. Use this to narrow down the counters
that are returned.
.. note::
``_Total`` is returned as ``*``
'''
counters, instances_avail = win32pdh.EnumObjectItems(None, None, obj, -1, 0)
if instance_list is None:
instance_list = instances_avail
if not isinstance(instance_list, list):
instance_list = [instance_list]
counter_list = []
for counter in counters:
for instance in instance_list:
instance = '*' if instance.lower() == '_total' else instance
counter_list.append((obj, instance, counter))
else: # pylint: disable=useless-else-on-loop
counter_list.append((obj, None, counter))
return get_counters(counter_list) if counter_list else {}
def get_counters(counter_list):
'''
Get the values for the passes list of counters
Args:
counter_list (list):
A list of counters to lookup
Returns:
dict: A dictionary of counters and their values
'''
if not isinstance(counter_list, list):
raise CommandExecutionError('counter_list must be a list of tuples')
try:
# Start a Query instances
query = win32pdh.OpenQuery()
# Build the counters
counters = build_counter_list(counter_list)
# Add counters to the Query
for counter in counters:
counter.add_to_query(query)
# https://docs.microsoft.com/en-us/windows/desktop/perfctrs/collecting-performance-data
win32pdh.CollectQueryData(query)
# The sleep here is required for counters that require more than 1
# reading
time.sleep(1)
win32pdh.CollectQueryData(query)
ret = {}
for counter in counters:
try:
ret.update({counter.path: counter.value()})
except pywintypes.error as exc:
if exc.strerror == 'No data to return.':
# Some counters are not active and will throw an error if
# there is no data to return
continue
else:
raise
finally:
win32pdh.CloseQuery(query)
return ret
def get_counter(obj, instance, counter):
'''
Get the value of a single counter
Args:
obj (str):
The name of the counter object. You can get a list of valid names
using the ``list_objects`` function
instance (str):
The counter instance you wish to return. Get a list of instances
using the ``list_instances`` function
.. note::
``_Total`` is returned as ``*``
counter (str):
The name of the counter. Get a list of counters using the
``list_counters`` function
'''
return get_counters([(obj, instance, counter)])