Source code

Revision control

Copy as Markdown

Other Tools

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import struct
from ctypes import (
Structure,
Union,
byref,
c_double,
c_longlong,
create_string_buffer,
memmove,
pointer,
windll,
)
from ctypes.wintypes import DWORD, HANDLE, LONG, LPCSTR, LPCWSTR, LPSTR
import six
from talos.cmanager_base import CounterManager
from talos.utils import TalosError
pdh = windll.pdh
_LONGLONG = c_longlong
class _PDH_COUNTER_PATH_ELEMENTS_A(Structure):
_fields_ = [
("szMachineName", LPSTR),
("szObjectName", LPSTR),
("szInstanceName", LPSTR),
("szParentInstance", LPSTR),
("dwInstanceIndex", DWORD),
("szCounterName", LPSTR),
]
_PDH_MORE_DATA = -2147481646 # the need more space error
def _getExpandedCounterPaths(processName, counterName):
"""
Get list of expanded counter paths given a counter name. Returns a
list of strings or None, if no counter paths can be created
"""
pcchPathListLength = DWORD(0)
szWildCardPath = LPSTR("\\process(%s)\\%s" % (processName, counterName))
if (
pdh.PdhExpandCounterPathA(
szWildCardPath, LPSTR(None), pointer(pcchPathListLength)
)
!= _PDH_MORE_DATA
):
return []
pathListLength = pcchPathListLength.value
szExpandedPathList = LPCSTR("\0" * pathListLength)
if (
pdh.PdhExpandCounterPathA(
szWildCardPath, szExpandedPathList, pointer(pcchPathListLength)
)
!= 0
):
return []
buffer = create_string_buffer(pcchPathListLength.value)
memmove(buffer, szExpandedPathList, pcchPathListLength.value)
paths = []
i = 0
path = ""
for j in six.moves.range(0, pcchPathListLength.value):
c = struct.unpack_from("c", buffer, offset=j)[0]
if c == "\0":
if j == i:
# double null: we're done
break
paths.append(path)
path = ""
i = j + 1
else:
path += c
return paths
class _PDH_Counter_Union(Union):
_fields_ = [
("longValue", LONG),
("doubleValue", c_double),
("largeValue", _LONGLONG),
("AnsiStringValue", LPCSTR),
("WideStringValue", LPCWSTR),
]
class _PDH_FMT_COUNTERVALUE(Structure):
_fields_ = [("CStatus", DWORD), ("union", _PDH_Counter_Union)]
_PDH_FMT_LONG = 0x00000100
class WinCounterManager(CounterManager):
def __init__(
self, process_name, process, counters, childProcess="plugin-container"
):
CounterManager.__init__(self)
self.childProcess = childProcess
self.registeredCounters = {}
self.registerCounters(counters)
# PDH might need to be "refreshed" if it has been queried while the
# browser is closed
pdh.PdhEnumObjectsA(None, None, 0, 1, 0, True)
for counter in self.registeredCounters:
try:
# Add the counter path for the default process.
self._addCounter(process_name, "process", counter)
except TalosError:
# Assume that this is a memory counter for the system,
# not a process counter
# If we got an error that has nothing to do with that,
# the exception will almost certainly be re-raised
self._addCounter(process_name, "Memory", counter)
self._updateCounterPathsForChildProcesses(counter)
def _addCounter(self, processName, counterType, counterName):
pCounterPathElements = _PDH_COUNTER_PATH_ELEMENTS_A(
LPSTR(None),
LPSTR(counterType),
LPSTR(processName),
LPSTR(None),
DWORD(-1),
LPSTR(counterName),
)
pcchbufferSize = DWORD(0)
# First run we just try to get the buffer size so we can allocate a
# string big enough to fill it
if (
pdh.PdhMakeCounterPathA(
pointer(pCounterPathElements),
LPCSTR(0),
pointer(pcchbufferSize),
DWORD(0),
)
!= _PDH_MORE_DATA
):
raise TalosError(
"Could not create counter path for counter %s for %s"
% (counterName, processName)
)
szFullPathBuffer = LPCSTR("\0" * pcchbufferSize.value)
# Then we run to get the actual value
if (
pdh.PdhMakeCounterPathA(
pointer(pCounterPathElements),
szFullPathBuffer,
pointer(pcchbufferSize),
DWORD(0),
)
!= 0
):
raise TalosError(
"Could not create counter path for counter %s for %s"
% (counterName, processName)
)
path = szFullPathBuffer.value
hq = HANDLE()
if pdh.PdhOpenQuery(None, None, byref(hq)) != 0:
raise TalosError("Could not open win32 counter query")
hc = HANDLE()
if pdh.PdhAddCounterA(hq, path, 0, byref(hc)) != 0:
raise TalosError("Could not add win32 counter %s" % path)
self.registeredCounters[counterName] = [hq, [(hc, path)]]
def registerCounters(self, counters):
# self.registeredCounters[counter][0] is a counter query handle
# self.registeredCounters[counter][1] is a list of tuples, the first
# member of which is a counter handle, the second a counter path
for counter in counters:
# Main_RSS is collected inside of pageloader
if counter.strip() == "Main_RSS":
continue
# mainthread_io is collected from the browser via environment
# variables
if counter.strip() == "mainthread_io":
continue
self.registeredCounters[counter] = []
def _updateCounterPathsForChildProcesses(self, counter):
# Create a counter path for each instance of the child process that
# is running. If any of these paths are not in our counter list,
# add them to our counter query and append them to the counter list,
# so that we'll begin tracking their statistics. We don't need to
# worry about removing invalid paths from the list, as
# getCounterValue() will generate a value of 0 for those.
hq = self.registeredCounters[counter][0]
oldCounterListLength = len(self.registeredCounters[counter][1])
pdh.PdhEnumObjectsA(None, None, 0, 1, 0, True)
expandedPaths = _getExpandedCounterPaths(self.childProcess, counter)
if not expandedPaths:
return
for expandedPath in expandedPaths:
alreadyInCounterList = False
for singleCounter in self.registeredCounters[counter][1]:
if expandedPath == singleCounter[1]:
alreadyInCounterList = True
if not alreadyInCounterList:
try:
newhc = HANDLE()
if pdh.PdhAddCounterA(hq, expandedPath, 0, byref(newhc)) != 0:
raise TalosError(
"Could not add expanded win32 counter %s" % expandedPath
)
self.registeredCounters[counter][1].append((newhc, expandedPath))
except Exception:
continue
if oldCounterListLength != len(self.registeredCounters[counter][1]):
pdh.PdhCollectQueryData(hq)
def getCounterValue(self, counter):
# Update counter paths, to catch any new child processes that might
# have been launched since last call. Then iterate through all
# counter paths for this counter, and return a combined value.
if counter not in self.registeredCounters:
return None
if self.registeredCounters[counter] == []:
return None
self._updateCounterPathsForChildProcesses(counter)
hq = self.registeredCounters[counter][0]
# we'll just ignore the return value here, in case no counters
# are valid anymore
pdh.PdhCollectQueryData(hq)
aggregateValue = 0
for singleCounter in self.registeredCounters[counter][1]:
hc = singleCounter[0]
dwType = DWORD(0)
value = _PDH_FMT_COUNTERVALUE()
# if we can't get a value, just assume a value of 0
if (
pdh.PdhGetFormattedCounterValue(
hc, _PDH_FMT_LONG, byref(dwType), byref(value)
)
== 0
):
aggregateValue += value.union.longValue
return aggregateValue