|
1 // Copyright (c) 2009 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 // We need extended process and thread attribute support |
|
6 #undef _WIN32_WINNT |
|
7 #define _WIN32_WINNT 0x0600 |
|
8 |
|
9 #include "base/process_util.h" |
|
10 |
|
11 #include <windows.h> |
|
12 #include <winternl.h> |
|
13 #include <psapi.h> |
|
14 |
|
15 #include "base/debug_util.h" |
|
16 #include "base/histogram.h" |
|
17 #include "base/logging.h" |
|
18 #include "base/scoped_handle_win.h" |
|
19 #include "base/scoped_ptr.h" |
|
20 #include "base/win_util.h" |
|
21 |
|
22 #include <algorithm> |
|
23 |
|
24 namespace { |
|
25 |
|
26 // System pagesize. This value remains constant on x86/64 architectures. |
|
27 const int PAGESIZE_KB = 4; |
|
28 |
|
29 // HeapSetInformation function pointer. |
|
30 typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); |
|
31 |
|
32 typedef BOOL (WINAPI * InitializeProcThreadAttributeListFn)( |
|
33 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, |
|
34 DWORD dwAttributeCount, |
|
35 DWORD dwFlags, |
|
36 PSIZE_T lpSize |
|
37 ); |
|
38 |
|
39 typedef BOOL (WINAPI * DeleteProcThreadAttributeListFn)( |
|
40 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList |
|
41 ); |
|
42 |
|
43 typedef BOOL (WINAPI * UpdateProcThreadAttributeFn)( |
|
44 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, |
|
45 DWORD dwFlags, |
|
46 DWORD_PTR Attribute, |
|
47 PVOID lpValue, |
|
48 SIZE_T cbSize, |
|
49 PVOID lpPreviousValue, |
|
50 PSIZE_T lpReturnSize |
|
51 ); |
|
52 |
|
53 static InitializeProcThreadAttributeListFn InitializeProcThreadAttributeListPtr; |
|
54 static DeleteProcThreadAttributeListFn DeleteProcThreadAttributeListPtr; |
|
55 static UpdateProcThreadAttributeFn UpdateProcThreadAttributePtr; |
|
56 |
|
57 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); |
|
58 |
|
59 } // namespace |
|
60 |
|
61 namespace base { |
|
62 |
|
63 ProcessId GetCurrentProcId() { |
|
64 return ::GetCurrentProcessId(); |
|
65 } |
|
66 |
|
67 ProcessHandle GetCurrentProcessHandle() { |
|
68 return ::GetCurrentProcess(); |
|
69 } |
|
70 |
|
71 bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { |
|
72 // TODO(phajdan.jr): Take even more permissions out of this list. |
|
73 ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | |
|
74 PROCESS_TERMINATE | |
|
75 PROCESS_QUERY_INFORMATION | |
|
76 SYNCHRONIZE, |
|
77 FALSE, pid); |
|
78 |
|
79 if (result == INVALID_HANDLE_VALUE) |
|
80 return false; |
|
81 |
|
82 *handle = result; |
|
83 return true; |
|
84 } |
|
85 |
|
86 bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { |
|
87 ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | |
|
88 PROCESS_TERMINATE | |
|
89 PROCESS_QUERY_INFORMATION | |
|
90 PROCESS_VM_READ | |
|
91 SYNCHRONIZE, |
|
92 FALSE, pid); |
|
93 |
|
94 if (result == INVALID_HANDLE_VALUE) |
|
95 return false; |
|
96 |
|
97 *handle = result; |
|
98 return true; |
|
99 } |
|
100 |
|
101 void CloseProcessHandle(ProcessHandle process) { |
|
102 // closing a handle twice on Windows can be catastrophic - after the first |
|
103 // close the handle value may be reused, so the second close will kill that |
|
104 // other new handle. |
|
105 BOOL ok = CloseHandle(process); |
|
106 DCHECK(ok); |
|
107 } |
|
108 |
|
109 // Helper for GetProcId() |
|
110 bool GetProcIdViaGetProcessId(ProcessHandle process, DWORD* id) { |
|
111 // Dynamically get a pointer to GetProcessId(). |
|
112 typedef DWORD (WINAPI *GetProcessIdFunction)(HANDLE); |
|
113 static GetProcessIdFunction GetProcessIdPtr = NULL; |
|
114 static bool initialize_get_process_id = true; |
|
115 if (initialize_get_process_id) { |
|
116 initialize_get_process_id = false; |
|
117 HMODULE kernel32_handle = GetModuleHandle(L"kernel32.dll"); |
|
118 if (!kernel32_handle) { |
|
119 NOTREACHED(); |
|
120 return false; |
|
121 } |
|
122 GetProcessIdPtr = reinterpret_cast<GetProcessIdFunction>(GetProcAddress( |
|
123 kernel32_handle, "GetProcessId")); |
|
124 } |
|
125 if (!GetProcessIdPtr) |
|
126 return false; |
|
127 // Ask for the process ID. |
|
128 *id = (*GetProcessIdPtr)(process); |
|
129 return true; |
|
130 } |
|
131 |
|
132 // Helper for GetProcId() |
|
133 bool GetProcIdViaNtQueryInformationProcess(ProcessHandle process, DWORD* id) { |
|
134 // Dynamically get a pointer to NtQueryInformationProcess(). |
|
135 typedef NTSTATUS (WINAPI *NtQueryInformationProcessFunction)( |
|
136 HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); |
|
137 static NtQueryInformationProcessFunction NtQueryInformationProcessPtr = NULL; |
|
138 static bool initialize_query_information_process = true; |
|
139 if (initialize_query_information_process) { |
|
140 initialize_query_information_process = false; |
|
141 // According to nsylvain, ntdll.dll is guaranteed to be loaded, even though |
|
142 // the Windows docs seem to imply that you should LoadLibrary() it. |
|
143 HMODULE ntdll_handle = GetModuleHandle(L"ntdll.dll"); |
|
144 if (!ntdll_handle) { |
|
145 NOTREACHED(); |
|
146 return false; |
|
147 } |
|
148 NtQueryInformationProcessPtr = |
|
149 reinterpret_cast<NtQueryInformationProcessFunction>(GetProcAddress( |
|
150 ntdll_handle, "NtQueryInformationProcess")); |
|
151 } |
|
152 if (!NtQueryInformationProcessPtr) |
|
153 return false; |
|
154 // Ask for the process ID. |
|
155 PROCESS_BASIC_INFORMATION info; |
|
156 ULONG bytes_returned; |
|
157 NTSTATUS status = (*NtQueryInformationProcessPtr)(process, |
|
158 ProcessBasicInformation, |
|
159 &info, sizeof info, |
|
160 &bytes_returned); |
|
161 if (!SUCCEEDED(status) || (bytes_returned != (sizeof info))) |
|
162 return false; |
|
163 |
|
164 *id = static_cast<DWORD>(info.UniqueProcessId); |
|
165 return true; |
|
166 } |
|
167 |
|
168 ProcessId GetProcId(ProcessHandle process) { |
|
169 // Get a handle to |process| that has PROCESS_QUERY_INFORMATION rights. |
|
170 HANDLE current_process = GetCurrentProcess(); |
|
171 HANDLE process_with_query_rights; |
|
172 if (DuplicateHandle(current_process, process, current_process, |
|
173 &process_with_query_rights, PROCESS_QUERY_INFORMATION, |
|
174 false, 0)) { |
|
175 // Try to use GetProcessId(), if it exists. Fall back on |
|
176 // NtQueryInformationProcess() otherwise (< Win XP SP1). |
|
177 DWORD id; |
|
178 bool success = |
|
179 GetProcIdViaGetProcessId(process_with_query_rights, &id) || |
|
180 GetProcIdViaNtQueryInformationProcess(process_with_query_rights, &id); |
|
181 CloseHandle(process_with_query_rights); |
|
182 if (success) |
|
183 return id; |
|
184 } |
|
185 |
|
186 // We're screwed. |
|
187 NOTREACHED(); |
|
188 return 0; |
|
189 } |
|
190 |
|
191 // from sandbox_policy_base.cc in a later version of the chromium ipc code... |
|
192 bool IsInheritableHandle(HANDLE handle) { |
|
193 if (!handle) |
|
194 return false; |
|
195 if (handle == INVALID_HANDLE_VALUE) |
|
196 return false; |
|
197 // File handles (FILE_TYPE_DISK) and pipe handles are known to be |
|
198 // inheritable. Console handles (FILE_TYPE_CHAR) are not |
|
199 // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. |
|
200 DWORD handle_type = GetFileType(handle); |
|
201 return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; |
|
202 } |
|
203 |
|
204 void LoadThreadAttributeFunctions() { |
|
205 HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); |
|
206 InitializeProcThreadAttributeListPtr = |
|
207 reinterpret_cast<InitializeProcThreadAttributeListFn> |
|
208 (GetProcAddress(kernel32, "InitializeProcThreadAttributeList")); |
|
209 DeleteProcThreadAttributeListPtr = |
|
210 reinterpret_cast<DeleteProcThreadAttributeListFn> |
|
211 (GetProcAddress(kernel32, "DeleteProcThreadAttributeList")); |
|
212 UpdateProcThreadAttributePtr = |
|
213 reinterpret_cast<UpdateProcThreadAttributeFn> |
|
214 (GetProcAddress(kernel32, "UpdateProcThreadAttribute")); |
|
215 } |
|
216 |
|
217 // Creates and returns a "thread attribute list" to pass to the child process. |
|
218 // On return, is a pointer to a THREAD_ATTRIBUTE_LIST or NULL if either the |
|
219 // functions we need aren't available (eg, XP or earlier) or the functions we |
|
220 // need failed. |
|
221 // The result of this function must be passed to FreeThreadAttributeList. |
|
222 // Note that the pointer to the HANDLE array ends up embedded in the result of |
|
223 // this function and must stay alive until FreeThreadAttributeList is called, |
|
224 // hence it is passed in so the owner is the caller of this function. |
|
225 LPPROC_THREAD_ATTRIBUTE_LIST CreateThreadAttributeList(HANDLE *handlesToInherit, |
|
226 int handleCount) { |
|
227 if (!InitializeProcThreadAttributeListPtr || |
|
228 !DeleteProcThreadAttributeListPtr || |
|
229 !UpdateProcThreadAttributePtr) |
|
230 LoadThreadAttributeFunctions(); |
|
231 // shouldn't happen as we are only called for Vista+, but better safe than sorry... |
|
232 if (!InitializeProcThreadAttributeListPtr || |
|
233 !DeleteProcThreadAttributeListPtr || |
|
234 !UpdateProcThreadAttributePtr) |
|
235 return NULL; |
|
236 |
|
237 SIZE_T threadAttrSize; |
|
238 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; |
|
239 |
|
240 if (!(*InitializeProcThreadAttributeListPtr)(NULL, 1, 0, &threadAttrSize) && |
|
241 GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
|
242 goto fail; |
|
243 lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST> |
|
244 (malloc(threadAttrSize)); |
|
245 if (!lpAttributeList || |
|
246 !(*InitializeProcThreadAttributeListPtr)(lpAttributeList, 1, 0, &threadAttrSize)) |
|
247 goto fail; |
|
248 |
|
249 if (!(*UpdateProcThreadAttributePtr)(lpAttributeList, |
|
250 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
|
251 handlesToInherit, |
|
252 sizeof(handlesToInherit[0]) * handleCount, |
|
253 NULL, NULL)) { |
|
254 (*DeleteProcThreadAttributeListPtr)(lpAttributeList); |
|
255 goto fail; |
|
256 } |
|
257 return lpAttributeList; |
|
258 |
|
259 fail: |
|
260 if (lpAttributeList) |
|
261 free(lpAttributeList); |
|
262 return NULL; |
|
263 } |
|
264 |
|
265 // Frees the data returned by CreateThreadAttributeList. |
|
266 void FreeThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList) { |
|
267 // must be impossible to get a NULL DeleteProcThreadAttributeListPtr, as |
|
268 // we already checked it existed when creating the data we are now freeing. |
|
269 (*DeleteProcThreadAttributeListPtr)(lpAttributeList); |
|
270 free(lpAttributeList); |
|
271 } |
|
272 |
|
273 bool LaunchApp(const std::wstring& cmdline, |
|
274 bool wait, bool start_hidden, ProcessHandle* process_handle) { |
|
275 |
|
276 // We want to inherit the std handles so dump() statements and assertion |
|
277 // messages in the child process can be seen - but we *do not* want to |
|
278 // blindly have all handles inherited. Vista and later has a technique |
|
279 // where only specified handles are inherited - so we use this technique if |
|
280 // we can. If that technique isn't available (or it fails), we just don't |
|
281 // inherit anything. This means that dump() etc isn't going to be seen on |
|
282 // XP release builds, but that's OK (developers who really care can run a |
|
283 // debug build on XP, where the processes are marked as "console" apps, so |
|
284 // things work without these hoops) |
|
285 DWORD dwCreationFlags = 0; |
|
286 BOOL bInheritHandles = FALSE; |
|
287 // We use a STARTUPINFOEX, but if we can't do the thread attribute thing, we |
|
288 // just pass the size of a STARTUPINFO. |
|
289 STARTUPINFOEX startup_info_ex; |
|
290 ZeroMemory(&startup_info_ex, sizeof(startup_info_ex)); |
|
291 STARTUPINFO &startup_info = startup_info_ex.StartupInfo; |
|
292 startup_info.cb = sizeof(startup_info); |
|
293 startup_info.dwFlags = STARTF_USESHOWWINDOW; |
|
294 startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW; |
|
295 |
|
296 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; |
|
297 // Don't even bother trying pre-Vista... |
|
298 if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) { |
|
299 // setup our handle array first - if we end up with no handles that can |
|
300 // be inherited we can avoid trying to do the ThreadAttributeList dance... |
|
301 HANDLE handlesToInherit[2]; |
|
302 int handleCount = 0; |
|
303 HANDLE stdOut = ::GetStdHandle(STD_OUTPUT_HANDLE); |
|
304 HANDLE stdErr = ::GetStdHandle(STD_ERROR_HANDLE); |
|
305 |
|
306 if (IsInheritableHandle(stdOut)) |
|
307 handlesToInherit[handleCount++] = stdOut; |
|
308 if (stdErr != stdOut && IsInheritableHandle(stdErr)) |
|
309 handlesToInherit[handleCount++] = stdErr; |
|
310 |
|
311 if (handleCount) |
|
312 lpAttributeList = CreateThreadAttributeList(handlesToInherit, handleCount); |
|
313 } |
|
314 |
|
315 if (lpAttributeList) { |
|
316 // it's safe to inherit handles, so arrange for that... |
|
317 startup_info.cb = sizeof(startup_info_ex); |
|
318 startup_info.dwFlags |= STARTF_USESTDHANDLES; |
|
319 startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); |
|
320 startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); |
|
321 startup_info.hStdInput = INVALID_HANDLE_VALUE; |
|
322 startup_info_ex.lpAttributeList = lpAttributeList; |
|
323 dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; |
|
324 bInheritHandles = TRUE; |
|
325 } |
|
326 PROCESS_INFORMATION process_info; |
|
327 BOOL createdOK = CreateProcess(NULL, |
|
328 const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, |
|
329 bInheritHandles, dwCreationFlags, NULL, NULL, |
|
330 &startup_info, &process_info); |
|
331 if (lpAttributeList) |
|
332 FreeThreadAttributeList(lpAttributeList); |
|
333 if (!createdOK) |
|
334 return false; |
|
335 |
|
336 gProcessLog.print("==> process %d launched child process %d (%S)\n", |
|
337 GetCurrentProcId(), |
|
338 process_info.dwProcessId, |
|
339 cmdline.c_str()); |
|
340 |
|
341 // Handles must be closed or they will leak |
|
342 CloseHandle(process_info.hThread); |
|
343 |
|
344 if (wait) |
|
345 WaitForSingleObject(process_info.hProcess, INFINITE); |
|
346 |
|
347 // If the caller wants the process handle, we won't close it. |
|
348 if (process_handle) { |
|
349 *process_handle = process_info.hProcess; |
|
350 } else { |
|
351 CloseHandle(process_info.hProcess); |
|
352 } |
|
353 return true; |
|
354 } |
|
355 |
|
356 bool LaunchApp(const CommandLine& cl, |
|
357 bool wait, bool start_hidden, ProcessHandle* process_handle) { |
|
358 return LaunchApp(cl.command_line_string(), wait, |
|
359 start_hidden, process_handle); |
|
360 } |
|
361 |
|
362 bool KillProcess(ProcessHandle process, int exit_code, bool wait) { |
|
363 bool result = (TerminateProcess(process, exit_code) != FALSE); |
|
364 if (result && wait) { |
|
365 // The process may not end immediately due to pending I/O |
|
366 if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) |
|
367 DLOG(ERROR) << "Error waiting for process exit: " << GetLastError(); |
|
368 } else if (!result) { |
|
369 DLOG(ERROR) << "Unable to terminate process: " << GetLastError(); |
|
370 } |
|
371 return result; |
|
372 } |
|
373 |
|
374 bool DidProcessCrash(bool* child_exited, ProcessHandle handle) { |
|
375 DWORD exitcode = 0; |
|
376 |
|
377 if (child_exited) |
|
378 *child_exited = true; // On Windows it an error to call this function if |
|
379 // the child hasn't already exited. |
|
380 if (!::GetExitCodeProcess(handle, &exitcode)) { |
|
381 NOTREACHED(); |
|
382 return false; |
|
383 } |
|
384 if (exitcode == STILL_ACTIVE) { |
|
385 // The process is likely not dead or it used 0x103 as exit code. |
|
386 NOTREACHED(); |
|
387 return false; |
|
388 } |
|
389 |
|
390 // Warning, this is not generic code; it heavily depends on the way |
|
391 // the rest of the code kills a process. |
|
392 |
|
393 if (exitcode == PROCESS_END_NORMAL_TERMINATON || |
|
394 exitcode == PROCESS_END_KILLED_BY_USER || |
|
395 exitcode == PROCESS_END_PROCESS_WAS_HUNG || |
|
396 exitcode == 0xC0000354 || // STATUS_DEBUGGER_INACTIVE. |
|
397 exitcode == 0xC000013A || // Control-C/end session. |
|
398 exitcode == 0x40010004) { // Debugger terminated process/end session. |
|
399 return false; |
|
400 } |
|
401 |
|
402 return true; |
|
403 } |
|
404 |
|
405 void SetCurrentProcessPrivileges(ChildPrivileges privs) { |
|
406 |
|
407 } |
|
408 |
|
409 NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name, |
|
410 const ProcessFilter* filter) |
|
411 : started_iteration_(false), |
|
412 executable_name_(executable_name), |
|
413 filter_(filter) { |
|
414 snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
|
415 } |
|
416 |
|
417 NamedProcessIterator::~NamedProcessIterator() { |
|
418 CloseHandle(snapshot_); |
|
419 } |
|
420 |
|
421 |
|
422 const ProcessEntry* NamedProcessIterator::NextProcessEntry() { |
|
423 bool result = false; |
|
424 do { |
|
425 result = CheckForNextProcess(); |
|
426 } while (result && !IncludeEntry()); |
|
427 |
|
428 if (result) { |
|
429 return &entry_; |
|
430 } |
|
431 |
|
432 return NULL; |
|
433 } |
|
434 |
|
435 bool NamedProcessIterator::CheckForNextProcess() { |
|
436 InitProcessEntry(&entry_); |
|
437 |
|
438 if (!started_iteration_) { |
|
439 started_iteration_ = true; |
|
440 return !!Process32First(snapshot_, &entry_); |
|
441 } |
|
442 |
|
443 return !!Process32Next(snapshot_, &entry_); |
|
444 } |
|
445 |
|
446 bool NamedProcessIterator::IncludeEntry() { |
|
447 return _wcsicmp(executable_name_.c_str(), entry_.szExeFile) == 0 && |
|
448 (!filter_ || filter_->Includes(entry_.th32ProcessID, |
|
449 entry_.th32ParentProcessID)); |
|
450 } |
|
451 |
|
452 void NamedProcessIterator::InitProcessEntry(ProcessEntry* entry) { |
|
453 memset(entry, 0, sizeof(*entry)); |
|
454 entry->dwSize = sizeof(*entry); |
|
455 } |
|
456 |
|
457 /////////////////////////////////////////////////////////////////////////////// |
|
458 // ProcesMetrics |
|
459 |
|
460 ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process), |
|
461 last_time_(0), |
|
462 last_system_time_(0) { |
|
463 SYSTEM_INFO system_info; |
|
464 GetSystemInfo(&system_info); |
|
465 processor_count_ = system_info.dwNumberOfProcessors; |
|
466 } |
|
467 |
|
468 // static |
|
469 ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { |
|
470 return new ProcessMetrics(process); |
|
471 } |
|
472 |
|
473 ProcessMetrics::~ProcessMetrics() { } |
|
474 |
|
475 static uint64_t FileTimeToUTC(const FILETIME& ftime) { |
|
476 LARGE_INTEGER li; |
|
477 li.LowPart = ftime.dwLowDateTime; |
|
478 li.HighPart = ftime.dwHighDateTime; |
|
479 return li.QuadPart; |
|
480 } |
|
481 |
|
482 int ProcessMetrics::GetCPUUsage() { |
|
483 FILETIME now; |
|
484 FILETIME creation_time; |
|
485 FILETIME exit_time; |
|
486 FILETIME kernel_time; |
|
487 FILETIME user_time; |
|
488 |
|
489 GetSystemTimeAsFileTime(&now); |
|
490 |
|
491 if (!GetProcessTimes(process_, &creation_time, &exit_time, |
|
492 &kernel_time, &user_time)) { |
|
493 // We don't assert here because in some cases (such as in the Task Manager) |
|
494 // we may call this function on a process that has just exited but we have |
|
495 // not yet received the notification. |
|
496 return 0; |
|
497 } |
|
498 int64_t system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / |
|
499 processor_count_; |
|
500 int64_t time = FileTimeToUTC(now); |
|
501 |
|
502 if ((last_system_time_ == 0) || (last_time_ == 0)) { |
|
503 // First call, just set the last values. |
|
504 last_system_time_ = system_time; |
|
505 last_time_ = time; |
|
506 return 0; |
|
507 } |
|
508 |
|
509 int64_t system_time_delta = system_time - last_system_time_; |
|
510 int64_t time_delta = time - last_time_; |
|
511 DCHECK(time_delta != 0); |
|
512 if (time_delta == 0) |
|
513 return 0; |
|
514 |
|
515 // We add time_delta / 2 so the result is rounded. |
|
516 int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) / |
|
517 time_delta); |
|
518 |
|
519 last_system_time_ = system_time; |
|
520 last_time_ = time; |
|
521 |
|
522 return cpu; |
|
523 } |
|
524 |
|
525 } // namespace base |