Index: pylib/mozrunner/qijo.py |
=================================================================== |
--- pylib/mozrunner/qijo.py (revision 0) |
+++ pylib/mozrunner/qijo.py (revision 0) |
@@ -0,0 +1,162 @@ |
+from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE, addressof, c_size_t, c_ulong |
+from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LARGE_INTEGER |
+ |
+LPVOID = c_void_p |
+LPDWORD = POINTER(DWORD) |
+SIZE_T = c_size_t |
+ULONG_PTR = POINTER(c_ulong) |
+ |
+# A ULONGLONG is a 64-bit unsigned integer. |
+# Thus there are 8 bytes in a ULONGLONG. |
+# XXX why not import c_ulonglong ? |
+ULONGLONG = BYTE * 8 |
+ |
+class IO_COUNTERS(Structure): |
+ # The IO_COUNTERS struct is 6 ULONGLONGs. |
+ # TODO: Replace with non-dummy fields. |
+ _fields_ = [('dummy', ULONGLONG * 6)] |
+ |
+class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(Structure): |
+ _fields_ = [('TotalUserTime', LARGE_INTEGER), |
+ ('TotalKernelTime', LARGE_INTEGER), |
+ ('ThisPeriodTotalUserTime', LARGE_INTEGER), |
+ ('ThisPeriodTotalKernelTime', LARGE_INTEGER), |
+ ('TotalPageFaultCount', DWORD), |
+ ('TotalProcesses', DWORD), |
+ ('ActiveProcesses', DWORD), |
+ ('TotalTerminatedProcesses', DWORD)] |
+ |
+class JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION(Structure): |
+ _fields_ = [('BasicInfo', JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), |
+ ('IoInfo', IO_COUNTERS)] |
+ |
+# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx |
+class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure): |
+ _fields_ = [('PerProcessUserTimeLimit', LARGE_INTEGER), |
+ ('PerJobUserTimeLimit', LARGE_INTEGER), |
+ ('LimitFlags', DWORD), |
+ ('MinimumWorkingSetSize', SIZE_T), |
+ ('MaximumWorkingSetSize', SIZE_T), |
+ ('ActiveProcessLimit', DWORD), |
+ ('Affinity', ULONG_PTR), |
+ ('PriorityClass', DWORD), |
+ ('SchedulingClass', DWORD) |
+ ] |
+ |
+# see http://msdn.microsoft.com/en-us/library/ms684156%28VS.85%29.aspx |
+class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure): |
+ _fields_ = [('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION), |
+ ('IoInfo', IO_COUNTERS), |
+ ('ProcessMemoryLimit', SIZE_T), |
+ ('JobMemoryLimit', SIZE_T), |
+ ('PeakProcessMemoryUsed', SIZE_T), |
+ ('PeakJobMemoryUsed', SIZE_T)] |
+ |
+# XXX Magical numbers like 8 should be documented |
+JobObjectBasicAndIoAccountingInformation = 8 |
+ |
+# ...like magical number 9 comes from |
+# http://community.flexerasoftware.com/archive/index.php?t-181670.html |
+# I wish I had a more canonical source |
+JobObjectExtendedLimitInformation = 9 |
+ |
+class JobObjectInfo(object): |
+ mapping = { 'JobObjectBasicAndIoAccountingInformation': 8, |
+ 'JobObjectExtendedLimitInformation': 9 |
+ } |
+ structures = { 8: JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, |
+ 9: JOBOBJECT_EXTENDED_LIMIT_INFORMATION |
+ } |
+ def __init__(self, _class): |
+ if isinstance(_class, basestring): |
+ assert _class in self.mapping, 'Class should be one of %s; you gave %s' % (self.mapping, _class) |
+ _class = self.mapping[_class] |
+ assert _class in self.structures, 'Class should be one of %s; you gave %s' % (self.structures, _class) |
+ self.code = _class |
+ self.info = self.structures[_class]() |
+ |
+ |
+QueryInformationJobObjectProto = WINFUNCTYPE( |
+ BOOL, # Return type |
+ HANDLE, # hJob |
+ DWORD, # JobObjectInfoClass |
+ LPVOID, # lpJobObjectInfo |
+ DWORD, # cbJobObjectInfoLength |
+ LPDWORD # lpReturnLength |
+ ) |
+ |
+QueryInformationJobObjectFlags = ( |
+ (1, 'hJob'), |
+ (1, 'JobObjectInfoClass'), |
+ (1, 'lpJobObjectInfo'), |
+ (1, 'cbJobObjectInfoLength'), |
+ (1, 'lpReturnLength', None) |
+ ) |
+ |
+_QueryInformationJobObject = QueryInformationJobObjectProto( |
+ ('QueryInformationJobObject', windll.kernel32), |
+ QueryInformationJobObjectFlags |
+ ) |
+ |
+class SubscriptableReadOnlyStruct(object): |
+ def __init__(self, struct): |
+ self._struct = struct |
+ |
+ def _delegate(self, name): |
+ result = getattr(self._struct, name) |
+ if isinstance(result, Structure): |
+ return SubscriptableReadOnlyStruct(result) |
+ return result |
+ |
+ def __getitem__(self, name): |
+ match = [fname for fname, ftype in self._struct._fields_ |
+ if fname == name] |
+ if match: |
+ return self._delegate(name) |
+ raise KeyError(name) |
+ |
+ def __getattr__(self, name): |
+ return self._delegate(name) |
+ |
+def QueryInformationJobObject(hJob, JobObjectInfoClass): |
+ jobinfo = JobObjectInfo(JobObjectInfoClass) |
+ result = _QueryInformationJobObject( |
+ hJob=hJob, |
+ JobObjectInfoClass=jobinfo.code, |
+ lpJobObjectInfo=addressof(jobinfo.info), |
+ cbJobObjectInfoLength=sizeof(jobinfo.info) |
+ ) |
+ if not result: |
+ raise WinError() |
+ return SubscriptableReadOnlyStruct(jobinfo.info) |
+ |
+def test_qijo(): |
+ from killableprocess import Popen |
+ |
+ popen = Popen('c:\\windows\\notepad.exe') |
+ |
+ try: |
+ result = QueryInformationJobObject(0, 8) |
+ raise AssertionError('throw should occur') |
+ except WindowsError, e: |
+ pass |
+ |
+ try: |
+ result = QueryInformationJobObject(0, 1) |
+ raise AssertionError('throw should occur') |
+ except NotImplementedError, e: |
+ pass |
+ |
+ result = QueryInformationJobObject(popen._job, 8) |
+ if result['BasicInfo']['ActiveProcesses'] != 1: |
+ raise AssertionError('expected ActiveProcesses to be 1') |
+ popen.kill() |
+ |
+ result = QueryInformationJobObject(popen._job, 8) |
+ if result.BasicInfo.ActiveProcesses != 0: |
+ raise AssertionError('expected ActiveProcesses to be 0') |
+ |
+if __name__ == '__main__': |
+ print "testing." |
+ test_qijo() |
+ print "success!" |