OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "stdafx.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "system_information_sampler.h" | |
10 | |
11 // From ntdef.h | |
12 typedef struct _UNICODE_STRING { | |
13 USHORT Length; | |
14 USHORT MaximumLength; | |
15 PWCH Buffer; | |
16 } UNICODE_STRING; | |
17 | |
18 // From <wdm.h> | |
19 typedef LONG KPRIORITY; | |
20 typedef LONG KWAIT_REASON; // Full definition is in wdm.h | |
21 | |
22 // From ntddk.h | |
23 typedef struct _VM_COUNTERS { | |
24 SIZE_T PeakVirtualSize; | |
25 SIZE_T VirtualSize; | |
26 ULONG PageFaultCount; | |
27 // Padding here in 64-bit | |
28 SIZE_T PeakWorkingSetSize; | |
29 SIZE_T WorkingSetSize; | |
30 SIZE_T QuotaPeakPagedPoolUsage; | |
31 SIZE_T QuotaPagedPoolUsage; | |
32 SIZE_T QuotaPeakNonPagedPoolUsage; | |
33 SIZE_T QuotaNonPagedPoolUsage; | |
34 SIZE_T PagefileUsage; | |
35 SIZE_T PeakPagefileUsage; | |
36 } VM_COUNTERS; | |
37 | |
38 // Two possibilities available from here: | |
39 // http://stackoverflow.com/questions/28858849/where-is-system-information-class -defined | |
40 | |
41 typedef enum _SYSTEM_INFORMATION_CLASS { | |
42 SystemBasicInformation = 0, | |
43 SystemPerformanceInformation = 2, | |
44 SystemTimeOfDayInformation = 3, | |
45 SystemProcessInformation = 5, // This is the number that we need | |
46 SystemProcessorPerformanceInformation = 8, | |
47 SystemInterruptInformation = 23, | |
48 SystemExceptionInformation = 33, | |
49 SystemRegistryQuotaInformation = 37, | |
50 SystemLookasideInformation = 45 | |
51 } SYSTEM_INFORMATION_CLASS; | |
52 | |
53 // https://msdn.microsoft.com/en-us/library/gg750647.aspx?f=255&MSPPError=-21472 17396 | |
54 typedef struct { | |
55 HANDLE UniqueProcess; // Actually process ID | |
56 HANDLE UniqueThread; // Actually thread ID | |
57 } CLIENT_ID; | |
58 | |
59 // From http://alax.info/blog/1182, with corrections and modifications | |
60 // Originally from | |
61 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%2 0Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html | |
62 struct SYSTEM_THREAD_INFORMATION { | |
63 ULONGLONG KernelTime; | |
64 ULONGLONG UserTime; | |
65 ULONGLONG CreateTime; | |
66 ULONG WaitTime; | |
67 // Padding here in 64-bit | |
68 PVOID StartAddress; | |
69 CLIENT_ID ClientId; | |
70 KPRIORITY Priority; | |
71 LONG BasePriority; | |
72 ULONG ContextSwitchCount; | |
73 ULONG State; | |
74 KWAIT_REASON WaitReason; | |
75 }; | |
76 #if _M_X64 | |
77 static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 80, | |
78 "Structure size mismatch"); | |
79 #else | |
80 static_assert(sizeof(SYSTEM_THREAD_INFORMATION) == 64, | |
81 "Structure size mismatch"); | |
82 #endif | |
83 | |
84 // From http://alax.info/blog/1182, with corrections and modifications | |
85 // Originally from | |
86 // http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%2 0Functions%2FSystem%20Information%2FStructures%2FSYSTEM_THREAD.html | |
87 struct SYSTEM_PROCESS_INFORMATION { | |
88 ULONG NextEntryOffset; | |
89 ULONG NumberOfThreads; | |
90 // http://processhacker.sourceforge.net/doc/struct___s_y_s_t_e_m___p_r_o_c_e_s _s___i_n_f_o_r_m_a_t_i_o_n.html | |
91 ULONGLONG WorkingSetPrivateSize; | |
92 ULONG HardFaultCount; | |
93 ULONG Reserved1; | |
94 ULONGLONG CycleTime; | |
95 ULONGLONG CreateTime; | |
96 ULONGLONG UserTime; | |
97 ULONGLONG KernelTime; | |
98 UNICODE_STRING ImageName; | |
99 KPRIORITY BasePriority; | |
100 HANDLE ProcessId; | |
101 HANDLE ParentProcessId; | |
102 ULONG HandleCount; | |
103 ULONG Reserved2[2]; | |
104 // Padding here in 64-bit | |
105 VM_COUNTERS VirtualMemoryCounters; | |
106 size_t Reserved3; | |
107 IO_COUNTERS IoCounters; | |
108 SYSTEM_THREAD_INFORMATION Threads[1]; | |
109 }; | |
110 #if _M_X64 | |
111 static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 336, | |
112 "Structure size mismatch"); | |
113 #else | |
114 static_assert(sizeof(SYSTEM_PROCESS_INFORMATION) == 248, | |
115 "Structure size mismatch"); | |
116 #endif | |
117 | |
118 // ntstatus.h conflicts with windows.h so define this locally. | |
119 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) | |
120 #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) | |
121 #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) | |
122 | |
123 typedef NTSTATUS(WINAPI* NTQUERYSYSTEMINFORMATION)( | |
124 SYSTEM_INFORMATION_CLASS SystemInformationClass, | |
125 PVOID SystemInformation, | |
126 ULONG SystemInformationLength, | |
127 PULONG ReturnLength); | |
128 | |
129 __declspec(noreturn) void oops(const char* pMessage) { | |
130 printf("%s\n", pMessage); | |
131 exit(0); | |
132 } | |
133 | |
134 // Simple memory buffer wrapper for passing the data out of | |
135 // QuerySystemProcessInformation. | |
136 class ByteBuffer { | |
137 public: | |
138 explicit ByteBuffer(size_t capacity) : size_(0), capacity_(0) { | |
139 if (capacity > 0) | |
140 grow(capacity); | |
141 } | |
142 | |
143 ~ByteBuffer() {} | |
144 | |
145 BYTE* data() { return data_.get(); } | |
146 | |
147 size_t size() { return size_; } | |
148 | |
149 void set_size(size_t new_size) { size_ = new_size; } | |
150 | |
151 size_t capacity() { return capacity_; } | |
152 | |
153 void grow(size_t new_capacity) { | |
154 capacity_ = new_capacity; | |
155 data_.reset(new BYTE[new_capacity]); | |
156 } | |
157 | |
158 private: | |
159 std::unique_ptr<BYTE[]> data_; | |
160 size_t size_; | |
161 size_t capacity_; | |
162 | |
163 ByteBuffer& operator=(const ByteBuffer&) = delete; | |
164 ByteBuffer(const ByteBuffer&) = delete; | |
165 }; | |
166 | |
167 // Wrapper for NtQuerySystemProcessInformation with buffer reallocation logic. | |
168 bool QuerySystemProcessInformation(ByteBuffer* buffer) { | |
169 typedef NTSTATUS(WINAPI * NTQUERYSYSTEMINFORMATION)( | |
170 SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, | |
171 ULONG SystemInformationLength, PULONG ReturnLength); | |
172 | |
173 HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); | |
174 if (!ntdll) { | |
175 oops("Couldn't load ntdll.dll"); | |
176 } | |
177 | |
178 NTQUERYSYSTEMINFORMATION nt_query_system_information_ptr = | |
179 reinterpret_cast<NTQUERYSYSTEMINFORMATION>( | |
180 GetProcAddress(ntdll, "NtQuerySystemInformation")); | |
181 if (!nt_query_system_information_ptr) | |
182 oops("Couldn't find NtQuerySystemInformation"); | |
183 | |
184 NTSTATUS result; | |
185 | |
186 // There is a potential race condition between growing the buffer and new | |
187 // processes being created. Try a few times before giving up. | |
188 for (int i = 0; i < 10; i++) { | |
189 ULONG data_size = 0; | |
190 ULONG buffer_size = static_cast<ULONG>(buffer->capacity()); | |
191 result = nt_query_system_information_ptr( | |
192 SystemProcessInformation, buffer->data(), buffer_size, &data_size); | |
193 | |
194 if (result == STATUS_SUCCESS) { | |
195 buffer->set_size(data_size); | |
196 break; | |
197 } | |
198 | |
199 if (result == STATUS_INFO_LENGTH_MISMATCH || | |
200 result == STATUS_BUFFER_TOO_SMALL) { | |
201 // Insufficient buffer. Grow to the returned |data_size| plus 10% extra | |
202 // to avoid frequent reallocations and try again. | |
203 buffer->grow(static_cast<ULONG>(data_size * 1.1)); | |
204 } else { | |
205 // An error other than the two above. | |
206 break; | |
207 } | |
208 } | |
209 | |
210 return result == STATUS_SUCCESS; | |
211 } | |
212 | |
213 SystemInformationSampler::SystemInformationSampler( | |
214 const wchar_t* process_name) { | |
215 lstrcpyn(target_process_name_, process_name, | |
216 sizeof(target_process_name_) / sizeof(wchar_t)); | |
217 | |
218 QueryPerformanceFrequency(&perf_frequency_); | |
219 QueryPerformanceCounter(&initial_counter_); | |
220 } | |
221 | |
222 SystemInformationSampler::~SystemInformationSampler() {} | |
223 | |
224 std::unique_ptr<ProcessDataSnapshot> SystemInformationSampler::TakeSnapshot() { | |
225 // Preallocate the buffer with the size determined on the previous call to | |
226 // QuerySystemProcessInformation. This should be sufficient most of the time. | |
227 // QuerySystemProcessInformation will grow the buffer if necessary. | |
228 ByteBuffer data_buffer(previous_buffer_size_); | |
229 | |
230 if (!QuerySystemProcessInformation(&data_buffer)) | |
231 return std::unique_ptr<ProcessDataSnapshot>(); | |
stanisc
2016/09/23 01:57:27
Should fail here rather than return an empty snaps
| |
232 | |
233 previous_buffer_size_ = data_buffer.capacity(); | |
234 | |
235 std::unique_ptr<ProcessDataSnapshot> snapshot(new ProcessDataSnapshot); | |
236 | |
237 LARGE_INTEGER perf_counter_value; | |
238 QueryPerformanceCounter(&perf_counter_value); | |
239 snapshot->timestamp = static_cast<double>( | |
240 (perf_counter_value.QuadPart - initial_counter_.QuadPart) / | |
241 perf_frequency_.QuadPart); | |
242 | |
243 for (size_t offset = 0; offset < data_buffer.size();) { | |
244 auto pi = reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*>( | |
245 data_buffer.data() + offset); | |
246 | |
247 // Validate that the offset is valid and all needed data is within | |
248 // the buffer boundary. | |
249 if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) > data_buffer.size()) | |
250 break; | |
251 if (offset + sizeof(SYSTEM_PROCESS_INFORMATION) + | |
252 (pi->NumberOfThreads - 1) * sizeof(SYSTEM_THREAD_INFORMATION) > | |
253 data_buffer.size()) | |
254 break; | |
255 | |
256 if (pi->ImageName.Buffer) { | |
257 // Validate that the image name is within the buffer boundary. | |
258 // ImageName.Length seems to be in bytes rather than characters. | |
259 size_t image_name_offset = | |
260 reinterpret_cast<BYTE*>(pi->ImageName.Buffer) - data_buffer.data(); | |
261 if (image_name_offset + pi->ImageName.Length > data_buffer.size()) | |
262 break; | |
263 | |
264 // Check if this is a chrome process. Ignore all other processes. | |
265 if (wcsncmp(target_process_name_filter(), pi->ImageName.Buffer, | |
266 lstrlen(target_process_name_filter())) == 0) { | |
267 // Collect enough data to be able to do a diff between two snapshots. | |
268 // Some threads might stop or new threads might be created between two | |
269 // snapshots. If a thread with a large number of context switches gets | |
270 // terminated the total number of context switches for the process might | |
271 // go down and the delta would be negative. | |
272 // To avoid that we need to compare thread IDs between two snapshots and | |
273 // not count context switches for threads that are missing in the most | |
274 // recent snapshot. | |
275 ProcessData process_data; | |
276 | |
277 process_data.cpu_time = pi->KernelTime + pi->UserTime; | |
278 process_data.working_set = pi->WorkingSetPrivateSize; | |
279 | |
280 // Iterate over threads and store each thread's ID and number of context | |
281 // switches. | |
282 for (ULONG thread_index = 0; thread_index < pi->NumberOfThreads; | |
283 ++thread_index) { | |
284 const SYSTEM_THREAD_INFORMATION* ti = &pi->Threads[thread_index]; | |
285 if (ti->ClientId.UniqueProcess != pi->ProcessId) | |
286 continue; | |
287 | |
288 ThreadData thread_data; | |
289 thread_data.thread_id = ti->ClientId.UniqueThread; | |
290 thread_data.context_switches = ti->ContextSwitchCount; | |
291 process_data.threads.push_back(thread_data); | |
292 } | |
293 | |
294 // Order thread data by thread ID to help diff two snapshots. | |
295 std::sort(process_data.threads.begin(), process_data.threads.end(), | |
296 [](const ThreadData& l, const ThreadData r) { | |
297 return l.thread_id < r.thread_id; | |
298 }); | |
299 | |
300 snapshot->processes.insert( | |
301 std::make_pair(pi->ProcessId, std::move(process_data))); | |
302 } | |
303 } | |
304 | |
305 // Check for end of the list. | |
306 if (!pi->NextEntryOffset) | |
307 break; | |
308 | |
309 // Jump to the next entry. | |
310 offset += pi->NextEntryOffset; | |
311 } | |
312 | |
313 return snapshot; | |
314 } | |
OLD | NEW |