blob: 940ee770dd9318fced370643945318167370dcb2 [file] [log] [blame]
license.botf003cfe2008-08-24 09:55:55 +09001// Copyright (c) 2006-2008 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.
initial.commit3f4a7322008-07-27 06:49:38 +09004
5#include "base/process_util.h"
6
7#include <windows.h>
8#include <winternl.h>
9#include <psapi.h>
10
jar@google.com2b50d782008-08-23 10:17:16 +090011#include "base/histogram.h"
initial.commit3f4a7322008-07-27 06:49:38 +090012#include "base/logging.h"
13#include "base/scoped_ptr.h"
14
15namespace {
16
17// System pagesize. This value remains constant on x86/64 architectures.
18const int PAGESIZE_KB = 4;
19
20// HeapSetInformation function pointer.
21typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T);
22
23} // namespace
24
25namespace process_util {
26
27int GetCurrentProcId() {
28 return ::GetCurrentProcessId();
29}
30
31// Helper for GetProcId()
32bool GetProcIdViaGetProcessId(ProcessHandle process, DWORD* id) {
33 // Dynamically get a pointer to GetProcessId().
34 typedef DWORD (WINAPI *GetProcessIdFunction)(HANDLE);
35 static GetProcessIdFunction GetProcessIdPtr = NULL;
36 static bool initialize_get_process_id = true;
37 if (initialize_get_process_id) {
38 initialize_get_process_id = false;
39 HMODULE kernel32_handle = GetModuleHandle(L"kernel32.dll");
40 if (!kernel32_handle) {
41 NOTREACHED();
42 return false;
43 }
44 GetProcessIdPtr = reinterpret_cast<GetProcessIdFunction>(GetProcAddress(
45 kernel32_handle, "GetProcessId"));
46 }
47 if (!GetProcessIdPtr)
48 return false;
49 // Ask for the process ID.
50 *id = (*GetProcessIdPtr)(process);
51 return true;
52}
53
54// Helper for GetProcId()
55bool GetProcIdViaNtQueryInformationProcess(ProcessHandle process, DWORD* id) {
56 // Dynamically get a pointer to NtQueryInformationProcess().
57 typedef NTSTATUS (WINAPI *NtQueryInformationProcessFunction)(
58 HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
59 static NtQueryInformationProcessFunction NtQueryInformationProcessPtr = NULL;
60 static bool initialize_query_information_process = true;
61 if (initialize_query_information_process) {
62 initialize_query_information_process = false;
63 // According to nsylvain, ntdll.dll is guaranteed to be loaded, even though
64 // the Windows docs seem to imply that you should LoadLibrary() it.
65 HMODULE ntdll_handle = GetModuleHandle(L"ntdll.dll");
66 if (!ntdll_handle) {
67 NOTREACHED();
68 return false;
69 }
70 NtQueryInformationProcessPtr =
71 reinterpret_cast<NtQueryInformationProcessFunction>(GetProcAddress(
72 ntdll_handle, "NtQueryInformationProcess"));
73 }
74 if (!NtQueryInformationProcessPtr)
75 return false;
76 // Ask for the process ID.
77 PROCESS_BASIC_INFORMATION info;
78 ULONG bytes_returned;
79 NTSTATUS status = (*NtQueryInformationProcessPtr)(process,
80 ProcessBasicInformation,
81 &info, sizeof info,
82 &bytes_returned);
83 if (!SUCCEEDED(status) || (bytes_returned != (sizeof info)))
84 return false;
85
86 *id = static_cast<DWORD>(info.UniqueProcessId);
87 return true;
88}
89
90int GetProcId(ProcessHandle process) {
91 // Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights.
92 HANDLE current_process = GetCurrentProcess();
93 HANDLE process_with_query_rights;
94 if (DuplicateHandle(current_process, process, current_process,
95 &process_with_query_rights, PROCESS_QUERY_INFORMATION,
96 false, 0)) {
97 // Try to use GetProcessId(), if it exists. Fall back on
98 // NtQueryInformationProcess() otherwise (< Win XP SP1).
99 DWORD id;
100 bool success =
101 GetProcIdViaGetProcessId(process_with_query_rights, &id) ||
102 GetProcIdViaNtQueryInformationProcess(process_with_query_rights, &id);
103 CloseHandle(process_with_query_rights);
104 if (success)
105 return id;
106 }
107
108 // We're screwed.
109 NOTREACHED();
110 return 0;
111}
112
113bool LaunchApp(const std::wstring& cmdline,
114 bool wait, bool start_hidden, ProcessHandle* process_handle) {
115 STARTUPINFO startup_info = {0};
116 startup_info.cb = sizeof(startup_info);
117 if (start_hidden) {
118 startup_info.dwFlags = STARTF_USESHOWWINDOW;
119 startup_info.wShowWindow = SW_HIDE;
120 }
121 PROCESS_INFORMATION process_info;
122 if (!CreateProcess(NULL,
123 const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL,
124 FALSE, 0, NULL, NULL,
125 &startup_info, &process_info))
126 return false;
127
128 // Handles must be closed or they will leak
129 CloseHandle(process_info.hThread);
130
131 if (wait)
132 WaitForSingleObject(process_info.hProcess, INFINITE);
133
134 // If the caller wants the process handle, we won't close it.
135 if (process_handle) {
136 *process_handle = process_info.hProcess;
137 } else {
138 CloseHandle(process_info.hProcess);
139 }
140 return true;
141}
142
143// Attempts to kill the process identified by the given process
144// entry structure, giving it the specified exit code.
145// Returns true if this is successful, false otherwise.
146bool KillProcess(int process_id, int exit_code, bool wait) {
147 bool result = false;
148 HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE,
149 FALSE, // Don't inherit handle
150 process_id);
151 if (process) {
152 result = !!TerminateProcess(process, exit_code);
153 if (result && wait) {
154 // The process may not end immediately due to pending I/O
155 if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000))
156 DLOG(ERROR) << "Error waiting for process exit: " << GetLastError();
157 } else {
158 DLOG(ERROR) << "Unable to terminate process: " << GetLastError();
159 }
160 CloseHandle(process);
161 }
162 return result;
163}
164
165bool DidProcessCrash(ProcessHandle handle) {
166 DWORD exitcode = 0;
167 BOOL success = ::GetExitCodeProcess(handle, &exitcode);
168 DCHECK(success);
169 DCHECK(exitcode != STILL_ACTIVE);
170
171 if (exitcode == 0 || // Normal termination.
172 exitcode == 1 || // Killed by task manager.
173 exitcode == 0xC0000354 || // STATUS_DEBUGGER_INACTIVE
174 exitcode == 0xC000013A || // Control-C/end session.
175 exitcode == 0x40010004) { // Debugger terminated process/end session.
176 return false;
177 }
178
179 // All other exit codes indicate crashes.
jar@google.com2b50d782008-08-23 10:17:16 +0900180
181 // TODO(jar): Remove histogramming code when UMA stats are consistent with
182 // other crash metrics.
183 // Histogram the low order 3 nibbles for UMA
184 const int kLeastValue = 0;
185 const int kMaxValue = 0xFFF;
186 const int kBucketCount = kMaxValue - kLeastValue + 1;
187 static LinearHistogram least_significant_histogram(L"ExitCodes.LSNibbles",
188 kLeastValue + 1, kMaxValue, kBucketCount);
189 least_significant_histogram.SetFlags(kUmaTargetedHistogramFlag |
190 LinearHistogram::kHexRangePrintingFlag);
191 least_significant_histogram.Add(exitcode & 0xFFF);
192
193 // Histogram the high order 3 nibbles
194 static LinearHistogram most_significant_histogram(L"ExitCodes.MSNibbles",
195 kLeastValue + 1, kMaxValue, kBucketCount);
196 most_significant_histogram.SetFlags(kUmaTargetedHistogramFlag |
197 LinearHistogram::kHexRangePrintingFlag);
198 // Avoid passing in negative numbers by shifting data into low end of dword.
199 most_significant_histogram.Add((exitcode >> 20) & 0xFFF);
200
201 // Histogram the middle order 2 nibbles
202 static LinearHistogram mid_significant_histogram(L"ExitCodes.MidNibbles",
203 1, 0xFF, 0x100);
204 mid_significant_histogram.SetFlags(kUmaTargetedHistogramFlag |
205 LinearHistogram::kHexRangePrintingFlag);
206 mid_significant_histogram.Add((exitcode >> 12) & 0xFF);
207
initial.commit3f4a7322008-07-27 06:49:38 +0900208 return true;
209}
210
211NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name,
212 const ProcessFilter* filter) :
213 started_iteration_(false),
214 executable_name_(executable_name),
215 filter_(filter) {
216 snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
217 }
218
219NamedProcessIterator::~NamedProcessIterator() {
220 CloseHandle(snapshot_);
221}
222
223
224const ProcessEntry* NamedProcessIterator::NextProcessEntry() {
225 bool result = false;
226 do {
227 result = CheckForNextProcess();
228 } while (result && !IncludeEntry());
229
230 if (result) {
231 return &entry_;
232 }
233
234 return NULL;
235}
236
237bool NamedProcessIterator::CheckForNextProcess() {
238 InitProcessEntry(&entry_);
239
240 if (!started_iteration_) {
241 started_iteration_ = true;
242 return !!Process32First(snapshot_, &entry_);
243 }
244
245 return !!Process32Next(snapshot_, &entry_);
246}
247
248bool NamedProcessIterator::IncludeEntry() {
249 return _wcsicmp(executable_name_.c_str(), entry_.szExeFile) == 0 &&
250 (!filter_ || filter_->Includes(entry_.th32ProcessID,
251 entry_.th32ParentProcessID));
252}
253
254void NamedProcessIterator::InitProcessEntry(ProcessEntry* entry) {
255 memset(entry, 0, sizeof(*entry));
256 entry->dwSize = sizeof(*entry);
257}
258
259int GetProcessCount(const std::wstring& executable_name,
260 const ProcessFilter* filter) {
261 int count = 0;
262
263 NamedProcessIterator iter(executable_name, filter);
264 while (iter.NextProcessEntry())
265 ++count;
266 return count;
267}
268
269bool KillProcesses(const std::wstring& executable_name, int exit_code,
270 const ProcessFilter* filter) {
271 bool result = true;
272 const ProcessEntry* entry;
273
274 NamedProcessIterator iter(executable_name, filter);
275 while (entry = iter.NextProcessEntry())
276 result = KillProcess((*entry).th32ProcessID, exit_code, true) && result;
277
278 return result;
279}
280
281bool WaitForProcessesToExit(const std::wstring& executable_name,
282 int wait_milliseconds,
283 const ProcessFilter* filter) {
284 const ProcessEntry* entry;
285 bool result = true;
286 DWORD start_time = GetTickCount();
287
288 NamedProcessIterator iter(executable_name, filter);
289 while (entry = iter.NextProcessEntry()) {
290 DWORD remaining_wait =
291 std::max(0, wait_milliseconds -
292 static_cast<int>(GetTickCount() - start_time));
293 HANDLE process = OpenProcess(SYNCHRONIZE,
294 FALSE,
295 entry->th32ProcessID);
296 DWORD wait_result = WaitForSingleObject(process, remaining_wait);
297 CloseHandle(process);
298 result = result && (wait_result == WAIT_OBJECT_0);
299 }
300
301 return result;
302}
303
304bool CleanupProcesses(const std::wstring& executable_name,
305 int wait_milliseconds,
306 int exit_code,
307 const ProcessFilter* filter) {
308 bool exited_cleanly =
309 process_util::WaitForProcessesToExit(executable_name, wait_milliseconds,
310 filter);
311 if (!exited_cleanly)
312 process_util::KillProcesses(executable_name, exit_code, filter);
313 return exited_cleanly;
314}
315
316
317///////////////////////////////////////////////////////////////////////////////
318// ProcesMetrics
319
320ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process),
321 last_time_(0),
322 last_system_time_(0) {
323 SYSTEM_INFO system_info;
324 GetSystemInfo(&system_info);
325 processor_count_ = system_info.dwNumberOfProcessors;
326}
327
328// static
329ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) {
330 return new ProcessMetrics(process);
331}
332
333ProcessMetrics::~ProcessMetrics() { }
334
335size_t ProcessMetrics::GetPagefileUsage() {
336 PROCESS_MEMORY_COUNTERS pmc;
337 if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) {
338 return pmc.PagefileUsage;
339 }
340 return 0;
341}
342
343// Returns the peak space allocated for the pagefile, in bytes.
344size_t ProcessMetrics::GetPeakPagefileUsage() {
345 PROCESS_MEMORY_COUNTERS pmc;
346 if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) {
347 return pmc.PeakPagefileUsage;
348 }
349 return 0;
350}
351
352// Returns the current working set size, in bytes.
353size_t ProcessMetrics::GetWorkingSetSize() {
354 PROCESS_MEMORY_COUNTERS pmc;
355 if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) {
356 return pmc.WorkingSetSize;
357 }
358 return 0;
359}
360
361size_t ProcessMetrics::GetPrivateBytes() {
362 // PROCESS_MEMORY_COUNTERS_EX is not supported until XP SP2.
363 // GetProcessMemoryInfo() will simply fail on prior OS. So the requested
364 // information is simply not available. Hence, we will return 0 on unsupported
365 // OSes. Unlike most Win32 API, we don't need to initialize the "cb" member.
366 PROCESS_MEMORY_COUNTERS_EX pmcx;
367 if (GetProcessMemoryInfo(process_,
368 reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmcx),
369 sizeof(pmcx))) {
370 return pmcx.PrivateUsage;
371 }
372 return 0;
373}
374
375void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) {
376 MEMORY_BASIC_INFORMATION mbi = {0};
377 size_t committed_private = 0;
378 size_t committed_mapped = 0;
379 size_t committed_image = 0;
380 void* base_address = NULL;
381 while (VirtualQueryEx(process_, base_address, &mbi,
382 sizeof(MEMORY_BASIC_INFORMATION)) ==
383 sizeof(MEMORY_BASIC_INFORMATION)) {
jar@google.com2b50d782008-08-23 10:17:16 +0900384 if (mbi.State == MEM_COMMIT) {
initial.commit3f4a7322008-07-27 06:49:38 +0900385 if (mbi.Type == MEM_PRIVATE) {
386 committed_private += mbi.RegionSize;
387 } else if (mbi.Type == MEM_MAPPED) {
388 committed_mapped += mbi.RegionSize;
389 } else if (mbi.Type == MEM_IMAGE) {
390 committed_image += mbi.RegionSize;
391 } else {
392 NOTREACHED();
393 }
394 }
395 base_address = (static_cast<BYTE*>(mbi.BaseAddress)) + mbi.RegionSize;
396 }
397 usage->image = committed_image / 1024;
398 usage->mapped = committed_mapped / 1024;
399 usage->priv = committed_private / 1024;
400}
401
402bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) {
403 size_t ws_private = 0;
404 size_t ws_shareable = 0;
405 size_t ws_shared = 0;
406
407 DCHECK(ws_usage);
408 memset(ws_usage, 0, sizeof(*ws_usage));
409
410 DWORD number_of_entries = 4096; // Just a guess.
411 PSAPI_WORKING_SET_INFORMATION* buffer = NULL;
412 int retries = 5;
jar@google.com2b50d782008-08-23 10:17:16 +0900413 for (;;) {
initial.commit3f4a7322008-07-27 06:49:38 +0900414 DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) +
415 (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK));
416
417 // if we can't expand the buffer, don't leak the previous
418 // contents or pass a NULL pointer to QueryWorkingSet
jar@google.com2b50d782008-08-23 10:17:16 +0900419 PSAPI_WORKING_SET_INFORMATION* new_buffer =
420 reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>(
421 realloc(buffer, buffer_size));
initial.commit3f4a7322008-07-27 06:49:38 +0900422 if (!new_buffer) {
423 free(buffer);
424 return false;
425 }
426 buffer = new_buffer;
427
428 // Call the function once to get number of items
429 if (QueryWorkingSet(process_, buffer, buffer_size))
430 break; // Success
431
432 if (GetLastError() != ERROR_BAD_LENGTH) {
433 free(buffer);
434 return false;
435 }
436
437 number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries);
438
439 // Maybe some entries are being added right now. Increase the buffer to
440 // take that into account.
441 number_of_entries = static_cast<DWORD>(number_of_entries * 1.25);
442
443 if (--retries == 0) {
444 free(buffer); // If we're looping, eventually fail.
445 return false;
446 }
447 }
448
449 // On windows 2000 the function returns 1 even when the buffer is too small.
450 // The number of entries that we are going to parse is the minimum between the
451 // size we allocated and the real number of entries.
452 number_of_entries =
453 std::min(number_of_entries, static_cast<DWORD>(buffer->NumberOfEntries));
454 for (unsigned int i = 0; i < number_of_entries; i++) {
455 if (buffer->WorkingSetInfo[i].Shared) {
456 ws_shareable++;
457 if (buffer->WorkingSetInfo[i].ShareCount > 1)
458 ws_shared++;
459 } else {
460 ws_private++;
461 }
462 }
463
464 ws_usage->priv = ws_private * PAGESIZE_KB;
465 ws_usage->shareable = ws_shareable * PAGESIZE_KB;
466 ws_usage->shared = ws_shared * PAGESIZE_KB;
467 free(buffer);
468 return true;
469}
470
471static uint64 FileTimeToUTC(const FILETIME& ftime) {
472 LARGE_INTEGER li;
473 li.LowPart = ftime.dwLowDateTime;
474 li.HighPart = ftime.dwHighDateTime;
475 return li.QuadPart;
476}
477
478int ProcessMetrics::GetCPUUsage() {
479 FILETIME now;
480 FILETIME creation_time;
481 FILETIME exit_time;
482 FILETIME kernel_time;
483 FILETIME user_time;
484
485 GetSystemTimeAsFileTime(&now);
486
487 if (!GetProcessTimes(process_, &creation_time, &exit_time,
488 &kernel_time, &user_time)) {
489 // We don't assert here because in some cases (such as in the Task Manager)
490 // we may call this function on a process that has just exited but we have
491 // not yet received the notification.
492 return 0;
493 }
494 int64 system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) /
495 processor_count_;
496 int64 time = FileTimeToUTC(now);
497
498 if ((last_system_time_ == 0) || (last_time_ == 0)) {
499 // First call, just set the last values.
500 last_system_time_ = system_time;
501 last_time_ = time;
502 return 0;
503 }
504
505 int64 system_time_delta = system_time - last_system_time_;
506 int64 time_delta = time - last_time_;
507 DCHECK(time_delta != 0);
508 if (time_delta == 0)
509 return 0;
510
511 // We add time_delta / 2 so the result is rounded.
512 int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) /
513 time_delta);
514
515 last_system_time_ = system_time;
516 last_time_ = time;
517
518 return cpu;
519}
520
521bool ProcessMetrics::GetIOCounters(IO_COUNTERS* io_counters) {
522 return GetProcessIoCounters(process_, io_counters) != FALSE;
523}
524
525bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) {
526 const SIZE_T kTopAdress = 0x7F000000;
527 const SIZE_T kMegabyte = 1024 * 1024;
528 SIZE_T accumulated = 0;
529
530 MEMORY_BASIC_INFORMATION largest = {0};
531 UINT_PTR scan = 0;
532 while (scan < kTopAdress) {
533 MEMORY_BASIC_INFORMATION info;
534 if (!::VirtualQueryEx(process_, reinterpret_cast<void*>(scan),
535 &info, sizeof(info)))
536 return false;
537 if (info.State == MEM_FREE) {
538 accumulated += info.RegionSize;
539 UINT_PTR end = scan + info.RegionSize;
540 if (info.RegionSize > (largest.RegionSize))
541 largest = info;
542 }
543 scan += info.RegionSize;
544 }
545 free->largest = largest.RegionSize / kMegabyte;
546 free->largest_ptr = largest.BaseAddress;
547 free->total = accumulated / kMegabyte;
548 return true;
549}
550
551bool EnableLowFragmentationHeap() {
552 HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
553 HeapSetFn heap_set = reinterpret_cast<HeapSetFn>(GetProcAddress(
554 kernel32,
555 "HeapSetInformation"));
556
557 // On Windows 2000, the function is not exported. This is not a reason to
558 // fail.
559 if (!heap_set)
560 return true;
561
562 unsigned number_heaps = GetProcessHeaps(0, NULL);
563 if (!number_heaps)
564 return false;
565
566 // Gives us some extra space in the array in case a thread is creating heaps
567 // at the same time we're querying them.
568 static const int MARGIN = 8;
569 scoped_array<HANDLE> heaps(new HANDLE[number_heaps + MARGIN]);
570 number_heaps = GetProcessHeaps(number_heaps + MARGIN, heaps.get());
571 if (!number_heaps)
572 return false;
573
574 for (unsigned i = 0; i < number_heaps; ++i) {
575 ULONG lfh_flag = 2;
576 // Don't bother with the result code. It may fails on heaps that have the
577 // HEAP_NO_SERIALIZE flag. This is expected and not a problem at all.
578 heap_set(heaps[i],
579 HeapCompatibilityInformation,
580 &lfh_flag,
581 sizeof(lfh_flag));
582 }
583 return true;
584}
585
586} // namespace process_util
license.botf003cfe2008-08-24 09:55:55 +0900587