1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "cpu_win.h"
12
13 #define _WIN32_DCOM
14
15 #include <assert.h>
16 #include <iostream>
17 #include <Wbemidl.h>
18
19 #pragma comment(lib, "wbemuuid.lib")
20
21 #include "condition_variable_wrapper.h"
22 #include "critical_section_wrapper.h"
23 #include "event_wrapper.h"
24 #include "thread_wrapper.h"
25
26 namespace webrtc {
CpuUsage()27 WebRtc_Word32 CpuWindows::CpuUsage()
28 {
29 if (!has_initialized_)
30 {
31 return -1;
32 }
33 // Last element is the average
34 return cpu_usage_[number_of_objects_ - 1];
35 }
36
CpuUsageMultiCore(WebRtc_UWord32 & num_cores,WebRtc_UWord32 * & cpu_usage)37 WebRtc_Word32 CpuWindows::CpuUsageMultiCore(WebRtc_UWord32& num_cores,
38 WebRtc_UWord32*& cpu_usage)
39 {
40 if (has_terminated_) {
41 num_cores = 0;
42 cpu_usage = NULL;
43 return -1;
44 }
45 if (!has_initialized_)
46 {
47 num_cores = 0;
48 cpu_usage = NULL;
49 return -1;
50 }
51 num_cores = number_of_objects_ - 1;
52 cpu_usage = cpu_usage_;
53 return cpu_usage_[number_of_objects_-1];
54 }
55
CpuWindows()56 CpuWindows::CpuWindows()
57 : cpu_polling_thread(NULL),
58 initialize_(true),
59 has_initialized_(false),
60 terminate_(false),
61 has_terminated_(false),
62 cpu_usage_(NULL),
63 wbem_enum_access_(NULL),
64 number_of_objects_(0),
65 cpu_usage_handle_(0),
66 previous_processor_timestamp_(NULL),
67 timestamp_sys_100_ns_handle_(0),
68 previous_100ns_timestamp_(NULL),
69 wbem_service_(NULL),
70 wbem_service_proxy_(NULL),
71 wbem_refresher_(NULL),
72 wbem_enum_(NULL)
73 {
74 // All resources are allocated in PollingCpu().
75 if (AllocateComplexDataTypes())
76 {
77 StartPollingCpu();
78 }
79 else
80 {
81 assert(false);
82 }
83 }
84
~CpuWindows()85 CpuWindows::~CpuWindows()
86 {
87 // All resources are reclaimed in StopPollingCpu().
88 StopPollingCpu();
89 DeAllocateComplexDataTypes();
90 }
91
AllocateComplexDataTypes()92 bool CpuWindows::AllocateComplexDataTypes()
93 {
94 cpu_polling_thread = ThreadWrapper::CreateThread(
95 CpuWindows::Process,
96 reinterpret_cast<void*>(this),
97 kNormalPriority,
98 "CpuWindows");
99 init_crit_ = CriticalSectionWrapper::CreateCriticalSection();
100 init_cond_ = ConditionVariableWrapper::CreateConditionVariable();
101 terminate_crit_ = CriticalSectionWrapper::CreateCriticalSection();
102 terminate_cond_ = ConditionVariableWrapper::CreateConditionVariable();
103 sleep_event = EventWrapper::Create();
104 return (cpu_polling_thread != NULL) && (init_crit_ != NULL) &&
105 (init_cond_ != NULL) && (terminate_crit_ != NULL) &&
106 (terminate_cond_ != NULL) && (sleep_event != NULL);
107 }
108
DeAllocateComplexDataTypes()109 void CpuWindows::DeAllocateComplexDataTypes()
110 {
111 if (sleep_event != NULL)
112 {
113 delete sleep_event;
114 sleep_event = NULL;
115 }
116 if (terminate_cond_ != NULL)
117 {
118 delete terminate_cond_;
119 terminate_cond_ = NULL;
120 }
121 if (terminate_crit_ != NULL)
122 {
123 delete terminate_crit_;
124 terminate_crit_ = NULL;
125 }
126 if (init_cond_ != NULL)
127 {
128 delete init_cond_;
129 init_cond_ = NULL;
130 }
131 if (init_crit_ != NULL)
132 {
133 delete init_crit_;
134 init_crit_ = NULL;
135 }
136 if (cpu_polling_thread != NULL)
137 {
138 delete cpu_polling_thread;
139 cpu_polling_thread = NULL;
140 }
141 }
142
StartPollingCpu()143 void CpuWindows::StartPollingCpu()
144 {
145 unsigned int dummy_id = 0;
146 if (!cpu_polling_thread->Start(dummy_id))
147 {
148 initialize_ = false;
149 has_terminated_ = true;
150 assert(false);
151 }
152 }
153
StopPollingCpu()154 bool CpuWindows::StopPollingCpu()
155 {
156 {
157 // If StopPollingCpu is called immediately after StartPollingCpu() it is
158 // possible that cpu_polling_thread is in the process of initializing.
159 // Let initialization finish to avoid getting into a bad state.
160 CriticalSectionScoped cs(init_crit_);
161 while(initialize_)
162 {
163 init_cond_->SleepCS(*init_crit_);
164 }
165 }
166
167 CriticalSectionScoped cs(terminate_crit_);
168 terminate_ = true;
169 sleep_event->Set();
170 while (!has_terminated_)
171 {
172 terminate_cond_->SleepCS(*terminate_crit_);
173 }
174 cpu_polling_thread->Stop();
175 delete cpu_polling_thread;
176 cpu_polling_thread = NULL;
177 return true;
178 }
179
Process(void * thread_object)180 bool CpuWindows::Process(void* thread_object)
181 {
182 return reinterpret_cast<CpuWindows*>(thread_object)->ProcessImpl();
183 }
184
ProcessImpl()185 bool CpuWindows::ProcessImpl()
186 {
187 {
188 CriticalSectionScoped cs(terminate_crit_);
189 if (terminate_)
190 {
191 Terminate();
192 terminate_cond_->WakeAll();
193 return false;
194 }
195 }
196 // Initialize on first iteration
197 if (initialize_)
198 {
199 CriticalSectionScoped cs(init_crit_);
200 initialize_ = false;
201 const bool success = Initialize();
202 init_cond_->WakeAll();
203 if (!success || !has_initialized_)
204 {
205 has_initialized_ = false;
206 terminate_ = true;
207 return true;
208 }
209 }
210 // Approximately one seconds sleep for each CPU measurement. Precision is
211 // not important. 1 second refresh rate is also used by Performance Monitor
212 // (perfmon).
213 if(kEventTimeout != sleep_event->Wait(1000))
214 {
215 // Terminating. No need to update CPU usage.
216 assert(terminate_);
217 return true;
218 }
219
220 // UpdateCpuUsage() returns false if a single (or more) CPU read(s) failed.
221 // Not a major problem if it happens.
222 UpdateCpuUsage();
223 return true;
224 }
225
CreateWmiConnection()226 bool CpuWindows::CreateWmiConnection()
227 {
228 IWbemLocator* service_locator = NULL;
229 HRESULT hr = CoCreateInstance(CLSID_WbemLocator, NULL,
230 CLSCTX_INPROC_SERVER, IID_IWbemLocator,
231 reinterpret_cast<void**> (&service_locator));
232 if (FAILED(hr))
233 {
234 return false;
235 }
236 // To get the WMI service specify the WMI namespace.
237 BSTR wmi_namespace = SysAllocString(L"\\\\.\\root\\cimv2");
238 if (wmi_namespace == NULL)
239 {
240 // This type of failure signifies running out of memory.
241 service_locator->Release();
242 return false;
243 }
244 hr = service_locator->ConnectServer(wmi_namespace, NULL, NULL, NULL, 0L,
245 NULL, NULL, &wbem_service_);
246 SysFreeString(wmi_namespace);
247 service_locator->Release();
248 return !FAILED(hr);
249 }
250
251 // Sets up WMI refresher and enum
CreatePerfOsRefresher()252 bool CpuWindows::CreatePerfOsRefresher()
253 {
254 // Create refresher.
255 HRESULT hr = CoCreateInstance(CLSID_WbemRefresher, NULL,
256 CLSCTX_INPROC_SERVER, IID_IWbemRefresher,
257 reinterpret_cast<void**> (&wbem_refresher_));
258 if (FAILED(hr))
259 {
260 return false;
261 }
262 // Create PerfOS_Processor enum.
263 IWbemConfigureRefresher* wbem_refresher_config = NULL;
264 hr = wbem_refresher_->QueryInterface(
265 IID_IWbemConfigureRefresher,
266 reinterpret_cast<void**> (&wbem_refresher_config));
267 if (FAILED(hr))
268 {
269 return false;
270 }
271
272 // Create a proxy to the IWbemServices so that a local authentication
273 // can be set up (this is needed to be able to successfully call
274 // IWbemConfigureRefresher::AddEnum). Setting authentication with
275 // CoInitializeSecurity is process-wide (which is too intrusive).
276 hr = CoCopyProxy(static_cast<IUnknown*> (wbem_service_),
277 reinterpret_cast<IUnknown**> (&wbem_service_proxy_));
278 if(FAILED(hr))
279 {
280 return false;
281 }
282 // Set local authentication.
283 // RPC_C_AUTHN_WINNT means using NTLM instead of Kerberos which is default.
284 hr = CoSetProxyBlanket(static_cast<IUnknown*> (wbem_service_proxy_),
285 RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
286 RPC_C_AUTHN_LEVEL_DEFAULT,
287 RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
288 if(FAILED(hr))
289 {
290 return false;
291 }
292
293 // Don't care about the particular id for the enum.
294 long enum_id = 0;
295 hr = wbem_refresher_config->AddEnum(wbem_service_proxy_,
296 L"Win32_PerfRawData_PerfOS_Processor",
297 0, NULL, &wbem_enum_, &enum_id);
298 wbem_refresher_config->Release();
299 wbem_refresher_config = NULL;
300 return !FAILED(hr);
301 }
302
303 // Have to pull the first round of data to be able set the handles.
CreatePerfOsCpuHandles()304 bool CpuWindows::CreatePerfOsCpuHandles()
305 {
306 // Update the refresher so that there is data available in wbem_enum_.
307 wbem_refresher_->Refresh(0L);
308
309 // The number of enumerators is the number of processor + 1 (the total).
310 // This is unknown at this point.
311 DWORD number_returned = 0;
312 HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
313 wbem_enum_access_, &number_returned);
314 // number_returned indicates the number of enumerators that are needed.
315 if (hr == WBEM_E_BUFFER_TOO_SMALL &&
316 number_returned > number_of_objects_)
317 {
318 // Allocate the number IWbemObjectAccess asked for by the
319 // GetObjects(..) function.
320 wbem_enum_access_ = new IWbemObjectAccess*[number_returned];
321 cpu_usage_ = new WebRtc_UWord32[number_returned];
322 previous_processor_timestamp_ = new unsigned __int64[number_returned];
323 previous_100ns_timestamp_ = new unsigned __int64[number_returned];
324 if ((wbem_enum_access_ == NULL) || (cpu_usage_ == NULL) ||
325 (previous_processor_timestamp_ == NULL) ||
326 (previous_100ns_timestamp_ == NULL))
327 {
328 // Out of memory.
329 return false;
330 }
331
332 SecureZeroMemory(wbem_enum_access_, number_returned *
333 sizeof(IWbemObjectAccess*));
334 memset(cpu_usage_, 0, sizeof(int) * number_returned);
335 memset(previous_processor_timestamp_, 0, sizeof(unsigned __int64) *
336 number_returned);
337 memset(previous_100ns_timestamp_, 0, sizeof(unsigned __int64) *
338 number_returned);
339
340 number_of_objects_ = number_returned;
341 // Read should be successfull now that memory has been allocated.
342 hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_,
343 &number_returned);
344 if (FAILED(hr))
345 {
346 return false;
347 }
348 }
349 else
350 {
351 // 0 enumerators should not be enough. Something has gone wrong here.
352 return false;
353 }
354
355 // Get the enumerator handles that are needed for calculating CPU usage.
356 CIMTYPE cpu_usage_type;
357 hr = wbem_enum_access_[0]->GetPropertyHandle(L"PercentProcessorTime",
358 &cpu_usage_type,
359 &cpu_usage_handle_);
360 if (FAILED(hr))
361 {
362 return false;
363 }
364 CIMTYPE timestamp_sys_100_ns_type;
365 hr = wbem_enum_access_[0]->GetPropertyHandle(L"TimeStamp_Sys100NS",
366 ×tamp_sys_100_ns_type,
367 ×tamp_sys_100_ns_handle_);
368 return !FAILED(hr);
369 }
370
Initialize()371 bool CpuWindows::Initialize()
372 {
373 if (terminate_)
374 {
375 return false;
376 }
377 // Initialize COM library.
378 HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);
379 if (FAILED(hr))
380 {
381 return false;
382 }
383 if (FAILED(hr))
384 {
385 return false;
386 }
387
388 if (!CreateWmiConnection())
389 {
390 return false;
391 }
392 if (!CreatePerfOsRefresher())
393 {
394 return false;
395 }
396 if (!CreatePerfOsCpuHandles())
397 {
398 return false;
399 }
400 has_initialized_ = true;
401 return true;
402 }
403
Terminate()404 bool CpuWindows::Terminate()
405 {
406 if (has_terminated_)
407 {
408 return false;
409 }
410 // Reverse order of Initialize().
411 // Some compilers complain about deleting NULL though it's well defined
412 if (previous_100ns_timestamp_ != NULL)
413 {
414 delete[] previous_100ns_timestamp_;
415 previous_100ns_timestamp_ = NULL;
416 }
417 if (previous_processor_timestamp_ != NULL)
418 {
419 delete[] previous_processor_timestamp_;
420 previous_processor_timestamp_ = NULL;
421 }
422 if (cpu_usage_ != NULL)
423 {
424 delete[] cpu_usage_;
425 cpu_usage_ = NULL;
426 }
427 if (wbem_enum_access_ != NULL)
428 {
429 for (DWORD i = 0; i < number_of_objects_; i++)
430 {
431 if(wbem_enum_access_[i] != NULL)
432 {
433 wbem_enum_access_[i]->Release();
434 }
435 }
436 delete[] wbem_enum_access_;
437 wbem_enum_access_ = NULL;
438 }
439 if (wbem_enum_ != NULL)
440 {
441 wbem_enum_->Release();
442 wbem_enum_ = NULL;
443 }
444 if (wbem_refresher_ != NULL)
445 {
446 wbem_refresher_->Release();
447 wbem_refresher_ = NULL;
448 }
449 if (wbem_service_proxy_ != NULL)
450 {
451 wbem_service_proxy_->Release();
452 wbem_service_proxy_ = NULL;
453 }
454 if (wbem_service_ != NULL)
455 {
456 wbem_service_->Release();
457 wbem_service_ = NULL;
458 }
459 // CoUninitialized should be called once for every CoInitializeEx.
460 // Regardless if it failed or not.
461 CoUninitialize();
462 has_terminated_ = true;
463 return true;
464 }
465
UpdateCpuUsage()466 bool CpuWindows::UpdateCpuUsage()
467 {
468 wbem_refresher_->Refresh(0L);
469 DWORD number_returned = 0;
470 HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
471 wbem_enum_access_,&number_returned);
472 if (FAILED(hr))
473 {
474 // wbem_enum_access_ has already been allocated. Unless the number of
475 // CPUs change runtime this should not happen.
476 return false;
477 }
478 unsigned __int64 cpu_usage = 0;
479 unsigned __int64 timestamp_100ns = 0;
480 bool returnValue = true;
481 for (DWORD i = 0; i < number_returned; i++)
482 {
483 hr = wbem_enum_access_[i]->ReadQWORD(cpu_usage_handle_,&cpu_usage);
484 if (FAILED(hr))
485 {
486 returnValue = false;
487 }
488 hr = wbem_enum_access_[i]->ReadQWORD(timestamp_sys_100_ns_handle_,
489 ×tamp_100ns);
490 if (FAILED(hr))
491 {
492 returnValue = false;
493 }
494 wbem_enum_access_[i]->Release();
495 wbem_enum_access_[i] = NULL;
496
497 const bool wrapparound =
498 (previous_processor_timestamp_[i] > cpu_usage) ||
499 (previous_100ns_timestamp_[i] > timestamp_100ns);
500 const bool first_time = (previous_processor_timestamp_[i] == 0) ||
501 (previous_100ns_timestamp_[i] == 0);
502 if (wrapparound || first_time)
503 {
504 previous_processor_timestamp_[i] = cpu_usage;
505 previous_100ns_timestamp_[i] = timestamp_100ns;
506 continue;
507 }
508 const unsigned __int64 processor_timestamp_delta =
509 cpu_usage - previous_processor_timestamp_[i];
510 const unsigned __int64 timestamp_100ns_delta =
511 timestamp_100ns - previous_100ns_timestamp_[i];
512
513 if (processor_timestamp_delta >= timestamp_100ns_delta)
514 {
515 cpu_usage_[i] = 0;
516 } else {
517 // Quotient must be float since the division is guaranteed to yield
518 // a value between 0 and 1 which is 0 in integer division.
519 const float delta_quotient =
520 static_cast<float>(processor_timestamp_delta) /
521 static_cast<float>(timestamp_100ns_delta);
522 cpu_usage_[i] = 100 - static_cast<WebRtc_UWord32>(delta_quotient *
523 100);
524 }
525 previous_processor_timestamp_[i] = cpu_usage;
526 previous_100ns_timestamp_[i] = timestamp_100ns;
527 }
528 return returnValue;
529 }
530 } // namespace webrtc
531