OLD | NEW |
| (Empty) |
1 from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFU
NCTYPE, addressof, c_size_t, c_ulong | |
2 from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LARGE_INTEGER | |
3 | |
4 LPVOID = c_void_p | |
5 LPDWORD = POINTER(DWORD) | |
6 SIZE_T = c_size_t | |
7 ULONG_PTR = POINTER(c_ulong) | |
8 | |
9 # A ULONGLONG is a 64-bit unsigned integer. | |
10 # Thus there are 8 bytes in a ULONGLONG. | |
11 # XXX why not import c_ulonglong ? | |
12 ULONGLONG = BYTE * 8 | |
13 | |
14 class IO_COUNTERS(Structure): | |
15 # The IO_COUNTERS struct is 6 ULONGLONGs. | |
16 # TODO: Replace with non-dummy fields. | |
17 _fields_ = [('dummy', ULONGLONG * 6)] | |
18 | |
19 class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(Structure): | |
20 _fields_ = [('TotalUserTime', LARGE_INTEGER), | |
21 ('TotalKernelTime', LARGE_INTEGER), | |
22 ('ThisPeriodTotalUserTime', LARGE_INTEGER), | |
23 ('ThisPeriodTotalKernelTime', LARGE_INTEGER), | |
24 ('TotalPageFaultCount', DWORD), | |
25 ('TotalProcesses', DWORD), | |
26 ('ActiveProcesses', DWORD), | |
27 ('TotalTerminatedProcesses', DWORD)] | |
28 | |
29 class JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION(Structure): | |
30 _fields_ = [('BasicInfo', JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), | |
31 ('IoInfo', IO_COUNTERS)] | |
32 | |
33 # see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx | |
34 class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure): | |
35 _fields_ = [('PerProcessUserTimeLimit', LARGE_INTEGER), | |
36 ('PerJobUserTimeLimit', LARGE_INTEGER), | |
37 ('LimitFlags', DWORD), | |
38 ('MinimumWorkingSetSize', SIZE_T), | |
39 ('MaximumWorkingSetSize', SIZE_T), | |
40 ('ActiveProcessLimit', DWORD), | |
41 ('Affinity', ULONG_PTR), | |
42 ('PriorityClass', DWORD), | |
43 ('SchedulingClass', DWORD) | |
44 ] | |
45 | |
46 # see http://msdn.microsoft.com/en-us/library/ms684156%28VS.85%29.aspx | |
47 class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure): | |
48 _fields_ = [('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION), | |
49 ('IoInfo', IO_COUNTERS), | |
50 ('ProcessMemoryLimit', SIZE_T), | |
51 ('JobMemoryLimit', SIZE_T), | |
52 ('PeakProcessMemoryUsed', SIZE_T), | |
53 ('PeakJobMemoryUsed', SIZE_T)] | |
54 | |
55 # XXX Magical numbers like 8 should be documented | |
56 JobObjectBasicAndIoAccountingInformation = 8 | |
57 | |
58 # ...like magical number 9 comes from | |
59 # http://community.flexerasoftware.com/archive/index.php?t-181670.html | |
60 # I wish I had a more canonical source | |
61 JobObjectExtendedLimitInformation = 9 | |
62 | |
63 class JobObjectInfo(object): | |
64 mapping = { 'JobObjectBasicAndIoAccountingInformation': 8, | |
65 'JobObjectExtendedLimitInformation': 9 | |
66 } | |
67 structures = { 8: JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, | |
68 9: JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |
69 } | |
70 def __init__(self, _class): | |
71 if isinstance(_class, basestring): | |
72 assert _class in self.mapping, 'Class should be one of %s; you gave
%s' % (self.mapping, _class) | |
73 _class = self.mapping[_class] | |
74 assert _class in self.structures, 'Class should be one of %s; you gave %
s' % (self.structures, _class) | |
75 self.code = _class | |
76 self.info = self.structures[_class]() | |
77 | |
78 | |
79 QueryInformationJobObjectProto = WINFUNCTYPE( | |
80 BOOL, # Return type | |
81 HANDLE, # hJob | |
82 DWORD, # JobObjectInfoClass | |
83 LPVOID, # lpJobObjectInfo | |
84 DWORD, # cbJobObjectInfoLength | |
85 LPDWORD # lpReturnLength | |
86 ) | |
87 | |
88 QueryInformationJobObjectFlags = ( | |
89 (1, 'hJob'), | |
90 (1, 'JobObjectInfoClass'), | |
91 (1, 'lpJobObjectInfo'), | |
92 (1, 'cbJobObjectInfoLength'), | |
93 (1, 'lpReturnLength', None) | |
94 ) | |
95 | |
96 _QueryInformationJobObject = QueryInformationJobObjectProto( | |
97 ('QueryInformationJobObject', windll.kernel32), | |
98 QueryInformationJobObjectFlags | |
99 ) | |
100 | |
101 class SubscriptableReadOnlyStruct(object): | |
102 def __init__(self, struct): | |
103 self._struct = struct | |
104 | |
105 def _delegate(self, name): | |
106 result = getattr(self._struct, name) | |
107 if isinstance(result, Structure): | |
108 return SubscriptableReadOnlyStruct(result) | |
109 return result | |
110 | |
111 def __getitem__(self, name): | |
112 match = [fname for fname, ftype in self._struct._fields_ | |
113 if fname == name] | |
114 if match: | |
115 return self._delegate(name) | |
116 raise KeyError(name) | |
117 | |
118 def __getattr__(self, name): | |
119 return self._delegate(name) | |
120 | |
121 def QueryInformationJobObject(hJob, JobObjectInfoClass): | |
122 jobinfo = JobObjectInfo(JobObjectInfoClass) | |
123 result = _QueryInformationJobObject( | |
124 hJob=hJob, | |
125 JobObjectInfoClass=jobinfo.code, | |
126 lpJobObjectInfo=addressof(jobinfo.info), | |
127 cbJobObjectInfoLength=sizeof(jobinfo.info) | |
128 ) | |
129 if not result: | |
130 raise WinError() | |
131 return SubscriptableReadOnlyStruct(jobinfo.info) | |
132 | |
133 def test_qijo(): | |
134 from killableprocess import Popen | |
135 | |
136 popen = Popen('c:\\windows\\notepad.exe') | |
137 | |
138 try: | |
139 result = QueryInformationJobObject(0, 8) | |
140 raise AssertionError('throw should occur') | |
141 except WindowsError, e: | |
142 pass | |
143 | |
144 try: | |
145 result = QueryInformationJobObject(0, 1) | |
146 raise AssertionError('throw should occur') | |
147 except NotImplementedError, e: | |
148 pass | |
149 | |
150 result = QueryInformationJobObject(popen._job, 8) | |
151 if result['BasicInfo']['ActiveProcesses'] != 1: | |
152 raise AssertionError('expected ActiveProcesses to be 1') | |
153 popen.kill() | |
154 | |
155 result = QueryInformationJobObject(popen._job, 8) | |
156 if result.BasicInfo.ActiveProcesses != 0: | |
157 raise AssertionError('expected ActiveProcesses to be 0') | |
158 | |
159 if __name__ == '__main__': | |
160 print "testing." | |
161 test_qijo() | |
162 print "success!" | |
OLD | NEW |