addon-sdk/source/python-lib/mozrunner/qijo.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/python-lib/mozrunner/qijo.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,166 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE, addressof, c_size_t, c_ulong
     1.9 +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LARGE_INTEGER
    1.10 +
    1.11 +LPVOID = c_void_p
    1.12 +LPDWORD = POINTER(DWORD)
    1.13 +SIZE_T = c_size_t
    1.14 +ULONG_PTR = POINTER(c_ulong)
    1.15 +
    1.16 +# A ULONGLONG is a 64-bit unsigned integer.
    1.17 +# Thus there are 8 bytes in a ULONGLONG.
    1.18 +# XXX why not import c_ulonglong ?
    1.19 +ULONGLONG = BYTE * 8
    1.20 +
    1.21 +class IO_COUNTERS(Structure):
    1.22 +    # The IO_COUNTERS struct is 6 ULONGLONGs.
    1.23 +    # TODO: Replace with non-dummy fields.
    1.24 +    _fields_ = [('dummy', ULONGLONG * 6)]
    1.25 +
    1.26 +class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(Structure):
    1.27 +    _fields_ = [('TotalUserTime', LARGE_INTEGER),
    1.28 +                ('TotalKernelTime', LARGE_INTEGER),
    1.29 +                ('ThisPeriodTotalUserTime', LARGE_INTEGER),
    1.30 +                ('ThisPeriodTotalKernelTime', LARGE_INTEGER),
    1.31 +                ('TotalPageFaultCount', DWORD),
    1.32 +                ('TotalProcesses', DWORD),
    1.33 +                ('ActiveProcesses', DWORD),
    1.34 +                ('TotalTerminatedProcesses', DWORD)]
    1.35 +
    1.36 +class JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION(Structure):
    1.37 +    _fields_ = [('BasicInfo', JOBOBJECT_BASIC_ACCOUNTING_INFORMATION),
    1.38 +                ('IoInfo', IO_COUNTERS)]
    1.39 +
    1.40 +# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx
    1.41 +class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure):
    1.42 +    _fields_ = [('PerProcessUserTimeLimit', LARGE_INTEGER),
    1.43 +                ('PerJobUserTimeLimit', LARGE_INTEGER),
    1.44 +                ('LimitFlags', DWORD),
    1.45 +                ('MinimumWorkingSetSize', SIZE_T),
    1.46 +                ('MaximumWorkingSetSize', SIZE_T),
    1.47 +                ('ActiveProcessLimit', DWORD),
    1.48 +                ('Affinity', ULONG_PTR),
    1.49 +                ('PriorityClass', DWORD),
    1.50 +                ('SchedulingClass', DWORD)
    1.51 +                ]
    1.52 +
    1.53 +# see http://msdn.microsoft.com/en-us/library/ms684156%28VS.85%29.aspx
    1.54 +class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure):
    1.55 +    _fields_ = [('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION),
    1.56 +                ('IoInfo', IO_COUNTERS),
    1.57 +                ('ProcessMemoryLimit', SIZE_T),
    1.58 +                ('JobMemoryLimit', SIZE_T),
    1.59 +                ('PeakProcessMemoryUsed', SIZE_T),
    1.60 +                ('PeakJobMemoryUsed', SIZE_T)]
    1.61 +
    1.62 +# XXX Magical numbers like 8 should be documented
    1.63 +JobObjectBasicAndIoAccountingInformation = 8
    1.64 +
    1.65 +# ...like magical number 9 comes from
    1.66 +# http://community.flexerasoftware.com/archive/index.php?t-181670.html
    1.67 +# I wish I had a more canonical source
    1.68 +JobObjectExtendedLimitInformation = 9
    1.69 +
    1.70 +class JobObjectInfo(object):
    1.71 +    mapping = { 'JobObjectBasicAndIoAccountingInformation': 8,
    1.72 +                'JobObjectExtendedLimitInformation': 9
    1.73 +                }
    1.74 +    structures = { 8: JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION,
    1.75 +                   9: JOBOBJECT_EXTENDED_LIMIT_INFORMATION
    1.76 +                   }
    1.77 +    def __init__(self, _class):
    1.78 +        if isinstance(_class, basestring):
    1.79 +            assert _class in self.mapping, 'Class should be one of %s; you gave %s' % (self.mapping, _class)
    1.80 +            _class = self.mapping[_class]
    1.81 +        assert _class in self.structures, 'Class should be one of %s; you gave %s' % (self.structures, _class)
    1.82 +        self.code = _class
    1.83 +        self.info = self.structures[_class]()
    1.84 +    
    1.85 +
    1.86 +QueryInformationJobObjectProto = WINFUNCTYPE(
    1.87 +    BOOL,        # Return type
    1.88 +    HANDLE,      # hJob
    1.89 +    DWORD,       # JobObjectInfoClass
    1.90 +    LPVOID,      # lpJobObjectInfo
    1.91 +    DWORD,       # cbJobObjectInfoLength
    1.92 +    LPDWORD      # lpReturnLength
    1.93 +    )
    1.94 +
    1.95 +QueryInformationJobObjectFlags = (
    1.96 +    (1, 'hJob'),
    1.97 +    (1, 'JobObjectInfoClass'),
    1.98 +    (1, 'lpJobObjectInfo'),
    1.99 +    (1, 'cbJobObjectInfoLength'),
   1.100 +    (1, 'lpReturnLength', None)
   1.101 +    )
   1.102 +
   1.103 +_QueryInformationJobObject = QueryInformationJobObjectProto(
   1.104 +    ('QueryInformationJobObject', windll.kernel32),
   1.105 +    QueryInformationJobObjectFlags
   1.106 +    )
   1.107 +
   1.108 +class SubscriptableReadOnlyStruct(object):
   1.109 +    def __init__(self, struct):
   1.110 +        self._struct = struct
   1.111 +
   1.112 +    def _delegate(self, name):
   1.113 +        result = getattr(self._struct, name)
   1.114 +        if isinstance(result, Structure):
   1.115 +            return SubscriptableReadOnlyStruct(result)
   1.116 +        return result
   1.117 +
   1.118 +    def __getitem__(self, name):
   1.119 +        match = [fname for fname, ftype in self._struct._fields_
   1.120 +                 if fname == name]
   1.121 +        if match:
   1.122 +            return self._delegate(name)
   1.123 +        raise KeyError(name)
   1.124 +
   1.125 +    def __getattr__(self, name):
   1.126 +        return self._delegate(name)
   1.127 +
   1.128 +def QueryInformationJobObject(hJob, JobObjectInfoClass):
   1.129 +    jobinfo = JobObjectInfo(JobObjectInfoClass)
   1.130 +    result = _QueryInformationJobObject(
   1.131 +        hJob=hJob,
   1.132 +        JobObjectInfoClass=jobinfo.code,
   1.133 +        lpJobObjectInfo=addressof(jobinfo.info),
   1.134 +        cbJobObjectInfoLength=sizeof(jobinfo.info)
   1.135 +        )
   1.136 +    if not result:
   1.137 +        raise WinError()
   1.138 +    return SubscriptableReadOnlyStruct(jobinfo.info)
   1.139 +
   1.140 +def test_qijo():
   1.141 +    from killableprocess import Popen
   1.142 +
   1.143 +    popen = Popen('c:\\windows\\notepad.exe')
   1.144 +
   1.145 +    try:
   1.146 +        result = QueryInformationJobObject(0, 8)
   1.147 +        raise AssertionError('throw should occur')
   1.148 +    except WindowsError, e:
   1.149 +        pass
   1.150 +
   1.151 +    try:
   1.152 +        result = QueryInformationJobObject(0, 1)
   1.153 +        raise AssertionError('throw should occur')
   1.154 +    except NotImplementedError, e:
   1.155 +        pass
   1.156 +
   1.157 +    result = QueryInformationJobObject(popen._job, 8)
   1.158 +    if result['BasicInfo']['ActiveProcesses'] != 1:
   1.159 +        raise AssertionError('expected ActiveProcesses to be 1')
   1.160 +    popen.kill()
   1.161 +
   1.162 +    result = QueryInformationJobObject(popen._job, 8)
   1.163 +    if result.BasicInfo.ActiveProcesses != 0:
   1.164 +        raise AssertionError('expected ActiveProcesses to be 0')
   1.165 +
   1.166 +if __name__ == '__main__':
   1.167 +    print "testing."
   1.168 +    test_qijo()
   1.169 +    print "success!"

mercurial