1r""" 2Salt Util for getting system information with the Performance Data Helper (pdh). 3Counter information is gathered from current activity or log files. 4 5Usage: 6 7.. code-block:: python 8 9 import salt.utils.win_pdh 10 11 # Get a list of Counter objects 12 salt.utils.win_pdh.list_objects() 13 14 # Get a list of ``Processor`` instances 15 salt.utils.win_pdh.list_instances('Processor') 16 17 # Get a list of ``Processor`` counters 18 salt.utils.win_pdh.list_counters('Processor') 19 20 # Get the value of a single counter 21 # \Processor(*)\% Processor Time 22 salt.utils.win_pdh.get_counter('Processor', '*', '% Processor Time') 23 24 # Get the values of multiple counters 25 counter_list = [('Processor', '*', '% Processor Time'), 26 ('System', None, 'Context Switches/sec'), 27 ('Memory', None, 'Pages/sec'), 28 ('Server Work Queues', '*', 'Queue Length')] 29 salt.utils.win_pdh.get_counters(counter_list) 30 31 # Get all counters for the Processor object 32 salt.utils.win_pdh.get_all_counters('Processor') 33""" 34 35# https://docs.microsoft.com/en-us/windows/desktop/perfctrs/using-the-pdh-functions-to-consume-counter-data 36 37# https://www.cac.cornell.edu/wiki/index.php?title=Performance_Data_Helper_in_Python_with_win32pdh 38import logging 39import time 40 41import salt.utils.platform 42from salt.exceptions import CommandExecutionError 43 44try: 45 import pywintypes 46 import win32pdh 47 48 HAS_WINDOWS_MODULES = True 49except ImportError: 50 HAS_WINDOWS_MODULES = False 51 52 53log = logging.getLogger(__file__) 54 55# Define the virtual name 56__virtualname__ = "pdh" 57 58 59def __virtual__(): 60 """ 61 Only works on Windows systems with the PyWin32 62 """ 63 if not salt.utils.platform.is_windows(): 64 return False, "salt.utils.win_pdh: Requires Windows" 65 66 if not HAS_WINDOWS_MODULES: 67 return False, "salt.utils.win_pdh: Missing required modules" 68 69 return __virtualname__ 70 71 72class Counter: 73 """ 74 Counter object 75 Has enumerations and functions for working with counters 76 """ 77 78 # The dwType field from GetCounterInfo returns the following, or'ed. 79 # These come from WinPerf.h 80 PERF_SIZE_DWORD = 0x00000000 81 PERF_SIZE_LARGE = 0x00000100 82 PERF_SIZE_ZERO = 0x00000200 # for Zero Length fields 83 PERF_SIZE_VARIABLE_LEN = 0x00000300 84 # length is in the CounterLength field of the Counter Definition structure 85 86 # select one of the following values to indicate the counter field usage 87 PERF_TYPE_NUMBER = 0x00000000 # a number (not a counter) 88 PERF_TYPE_COUNTER = 0x00000400 # an increasing numeric value 89 PERF_TYPE_TEXT = 0x00000800 # a text field 90 PERF_TYPE_ZERO = 0x00000C00 # displays a zero 91 92 # If the PERF_TYPE_NUMBER field was selected, then select one of the 93 # following to describe the Number 94 PERF_NUMBER_HEX = 0x00000000 # display as HEX value 95 PERF_NUMBER_DECIMAL = 0x00010000 # display as a decimal integer 96 PERF_NUMBER_DEC_1000 = 0x00020000 # display as a decimal/1000 97 98 # If the PERF_TYPE_COUNTER value was selected then select one of the 99 # following to indicate the type of counter 100 PERF_COUNTER_VALUE = 0x00000000 # display counter value 101 PERF_COUNTER_RATE = 0x00010000 # divide ctr / delta time 102 PERF_COUNTER_FRACTION = 0x00020000 # divide ctr / base 103 PERF_COUNTER_BASE = 0x00030000 # base value used in fractions 104 PERF_COUNTER_ELAPSED = 0x00040000 # subtract counter from current time 105 PERF_COUNTER_QUEUE_LEN = 0x00050000 # Use Queue len processing func. 106 PERF_COUNTER_HISTOGRAM = 0x00060000 # Counter begins or ends a histogram 107 108 # If the PERF_TYPE_TEXT value was selected, then select one of the 109 # following to indicate the type of TEXT data. 110 PERF_TEXT_UNICODE = 0x00000000 # type of text in text field 111 PERF_TEXT_ASCII = 0x00010000 # ASCII using the CodePage field 112 113 # Timer SubTypes 114 PERF_TIMER_TICK = 0x00000000 # use system perf. freq for base 115 PERF_TIMER_100NS = 0x00100000 # use 100 NS timer time base units 116 PERF_OBJECT_TIMER = 0x00200000 # use the object timer freq 117 118 # Any types that have calculations performed can use one or more of the 119 # following calculation modification flags listed here 120 PERF_DELTA_COUNTER = 0x00400000 # compute difference first 121 PERF_DELTA_BASE = 0x00800000 # compute base diff as well 122 PERF_INVERSE_COUNTER = 0x01000000 # show as 1.00-value (assumes: 123 PERF_MULTI_COUNTER = 0x02000000 # sum of multiple instances 124 125 # Select one of the following values to indicate the display suffix (if any) 126 PERF_DISPLAY_NO_SUFFIX = 0x00000000 # no suffix 127 PERF_DISPLAY_PER_SEC = 0x10000000 # "/sec" 128 PERF_DISPLAY_PERCENT = 0x20000000 # "%" 129 PERF_DISPLAY_SECONDS = 0x30000000 # "secs" 130 PERF_DISPLAY_NO_SHOW = 0x40000000 # value is not displayed 131 132 def build_counter(obj, instance, instance_index, counter): 133 r""" 134 Makes a fully resolved counter path. Counter names are formatted like 135 this: 136 137 ``\Processor(*)\% Processor Time`` 138 139 The above breaks down like this: 140 141 obj = 'Processor' 142 instance = '*' 143 counter = '% Processor Time' 144 145 Args: 146 147 obj (str): 148 The top level object 149 150 instance (str): 151 The instance of the object 152 153 instance_index (int): 154 The index of the instance. Can usually be 0 155 156 counter (str): 157 The name of the counter 158 159 Returns: 160 Counter: A Counter object with the path if valid 161 162 Raises: 163 CommandExecutionError: If the path is invalid 164 """ 165 path = win32pdh.MakeCounterPath( 166 (None, obj, instance, None, instance_index, counter), 0 167 ) 168 if win32pdh.ValidatePath(path) == 0: 169 return Counter(path, obj, instance, instance_index, counter) 170 raise CommandExecutionError("Invalid counter specified: {}".format(path)) 171 172 build_counter = staticmethod(build_counter) 173 174 def __init__(self, path, obj, instance, index, counter): 175 self.path = path 176 self.obj = obj 177 self.instance = instance 178 self.index = index 179 self.counter = counter 180 self.handle = None 181 self.info = None 182 self.type = None 183 184 def add_to_query(self, query): 185 """ 186 Add the current path to the query 187 188 Args: 189 query (obj): 190 The handle to the query to add the counter 191 """ 192 self.handle = win32pdh.AddCounter(query, self.path) 193 194 def get_info(self): 195 """ 196 Get information about the counter 197 198 .. note:: 199 GetCounterInfo sometimes crashes in the wrapper code. Fewer crashes 200 if this is called after sampling data. 201 """ 202 if not self.info: 203 ci = win32pdh.GetCounterInfo(self.handle, 0) 204 self.info = { 205 "type": ci[0], 206 "version": ci[1], 207 "scale": ci[2], 208 "default_scale": ci[3], 209 "user_data": ci[4], 210 "query_user_data": ci[5], 211 "full_path": ci[6], 212 "machine_name": ci[7][0], 213 "object_name": ci[7][1], 214 "instance_name": ci[7][2], 215 "parent_instance": ci[7][3], 216 "instance_index": ci[7][4], 217 "counter_name": ci[7][5], 218 "explain_text": ci[8], 219 } 220 return self.info 221 222 def value(self): 223 """ 224 Return the counter value 225 226 Returns: 227 long: The counter value 228 """ 229 (counter_type, value) = win32pdh.GetFormattedCounterValue( 230 self.handle, win32pdh.PDH_FMT_DOUBLE 231 ) 232 self.type = counter_type 233 return value 234 235 def type_string(self): 236 """ 237 Returns the names of the flags that are set in the Type field 238 239 It can be used to format the counter. 240 """ 241 type = self.get_info()["type"] 242 type_list = [] 243 for member in dir(self): 244 if member.startswith("PERF_"): 245 bit = getattr(self, member) 246 if bit and bit & type: 247 type_list.append(member[5:]) 248 return type_list 249 250 def __str__(self): 251 return self.path 252 253 254def list_objects(): 255 """ 256 Get a list of available counter objects on the system 257 258 Returns: 259 list: A list of counter objects 260 """ 261 return sorted(win32pdh.EnumObjects(None, None, -1, 0)) 262 263 264def list_counters(obj): 265 """ 266 Get a list of counters available for the object 267 268 Args: 269 obj (str): 270 The name of the counter object. You can get a list of valid names 271 using the ``list_objects`` function 272 273 Returns: 274 list: A list of counters available to the passed object 275 """ 276 return win32pdh.EnumObjectItems(None, None, obj, -1, 0)[0] 277 278 279def list_instances(obj): 280 """ 281 Get a list of instances available for the object 282 283 Args: 284 obj (str): 285 The name of the counter object. You can get a list of valid names 286 using the ``list_objects`` function 287 288 Returns: 289 list: A list of instances available to the passed object 290 """ 291 return win32pdh.EnumObjectItems(None, None, obj, -1, 0)[1] 292 293 294def build_counter_list(counter_list): 295 r""" 296 Create a list of Counter objects to be used in the pdh query 297 298 Args: 299 counter_list (list): 300 A list of tuples containing counter information. Each tuple should 301 contain the object, instance, and counter name. For example, to 302 get the ``% Processor Time`` counter for all Processors on the 303 system (``\Processor(*)\% Processor Time``) you would pass a tuple 304 like this: 305 306 ``` 307 counter_list = [('Processor', '*', '% Processor Time')] 308 ``` 309 310 If there is no ``instance`` for the counter, pass ``None`` 311 312 Multiple counters can be passed like so: 313 314 ``` 315 counter_list = [('Processor', '*', '% Processor Time'), 316 ('System', None, 'Context Switches/sec')] 317 ``` 318 319 .. note:: 320 Invalid counters are ignored 321 322 Returns: 323 list: A list of Counter objects 324 """ 325 counters = [] 326 index = 0 327 for obj, instance, counter_name in counter_list: 328 try: 329 counter = Counter.build_counter(obj, instance, index, counter_name) 330 index += 1 331 counters.append(counter) 332 except CommandExecutionError as exc: 333 # Not a valid counter 334 log.debug(exc.strerror) 335 continue 336 return counters 337 338 339def get_all_counters(obj, instance_list=None): 340 """ 341 Get the values for all counters available to a Counter object 342 343 Args: 344 345 obj (str): 346 The name of the counter object. You can get a list of valid names 347 using the ``list_objects`` function 348 349 instance_list (list): 350 A list of instances to return. Use this to narrow down the counters 351 that are returned. 352 353 .. note:: 354 ``_Total`` is returned as ``*`` 355 """ 356 counters, instances_avail = win32pdh.EnumObjectItems(None, None, obj, -1, 0) 357 358 if instance_list is None: 359 instance_list = instances_avail 360 361 if not isinstance(instance_list, list): 362 instance_list = [instance_list] 363 364 counter_list = [] 365 for counter in counters: 366 for instance in instance_list: 367 instance = "*" if instance.lower() == "_total" else instance 368 counter_list.append((obj, instance, counter)) 369 else: # pylint: disable=useless-else-on-loop 370 counter_list.append((obj, None, counter)) 371 372 return get_counters(counter_list) if counter_list else {} 373 374 375def get_counters(counter_list): 376 """ 377 Get the values for the passes list of counters 378 379 Args: 380 counter_list (list): 381 A list of counters to lookup 382 383 Returns: 384 dict: A dictionary of counters and their values 385 """ 386 if not isinstance(counter_list, list): 387 raise CommandExecutionError("counter_list must be a list of tuples") 388 389 try: 390 # Start a Query instances 391 query = win32pdh.OpenQuery() 392 393 # Build the counters 394 counters = build_counter_list(counter_list) 395 396 # Add counters to the Query 397 for counter in counters: 398 counter.add_to_query(query) 399 400 # https://docs.microsoft.com/en-us/windows/desktop/perfctrs/collecting-performance-data 401 win32pdh.CollectQueryData(query) 402 # The sleep here is required for counters that require more than 1 403 # reading 404 time.sleep(1) 405 win32pdh.CollectQueryData(query) 406 ret = {} 407 408 for counter in counters: 409 try: 410 ret.update({counter.path: counter.value()}) 411 except pywintypes.error as exc: 412 if exc.strerror == "No data to return.": 413 # Some counters are not active and will throw an error if 414 # there is no data to return 415 continue 416 else: 417 raise 418 419 except pywintypes.error as exc: 420 if exc.strerror == "No data to return.": 421 # Sometimess, win32pdh.CollectQueryData can err 422 # so just ignore it 423 return {} 424 else: 425 raise 426 427 finally: 428 win32pdh.CloseQuery(query) 429 430 return ret 431 432 433def get_counter(obj, instance, counter): 434 """ 435 Get the value of a single counter 436 437 Args: 438 439 obj (str): 440 The name of the counter object. You can get a list of valid names 441 using the ``list_objects`` function 442 443 instance (str): 444 The counter instance you wish to return. Get a list of instances 445 using the ``list_instances`` function 446 447 .. note:: 448 ``_Total`` is returned as ``*`` 449 450 counter (str): 451 The name of the counter. Get a list of counters using the 452 ``list_counters`` function 453 """ 454 return get_counters([(obj, instance, counter)]) 455