blob: 22146bf7aa79d0996b90bbb6496191a7045475eb [file] [log] [blame]
niklase@google.comf0779a22011-05-30 11:39:38 +00001/*
2 * Copyright (c) 2011 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_windows.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
26namespace webrtc {
27WebRtc_Word32 CpuWindows::CpuUsage()
28{
hellner@google.com79ba0c72011-05-30 14:39:57 +000029 if (!has_initialized_)
niklase@google.comf0779a22011-05-30 11:39:38 +000030 {
31 return -1;
32 }
33 // Last element is the average
34 return cpu_usage_[number_of_objects_ - 1];
35}
36
37WebRtc_Word32 CpuWindows::CpuUsageMultiCore(WebRtc_UWord32& num_cores,
38 WebRtc_UWord32*& cpu_usage)
39{
hellner@google.com79ba0c72011-05-30 14:39:57 +000040 if (!has_initialized_)
niklase@google.comf0779a22011-05-30 11:39:38 +000041 {
42 return -1;
43 }
44 num_cores = number_of_objects_ - 1;
45 cpu_usage = cpu_usage_;
46 return cpu_usage_[number_of_objects_-1];
47}
48
49CpuWindows::CpuWindows()
50 : cpu_polling_thread(NULL),
51 initialize_(true),
hellner@google.com79ba0c72011-05-30 14:39:57 +000052 has_initialized_(false),
niklase@google.comf0779a22011-05-30 11:39:38 +000053 terminate_(false),
hellner@google.com79ba0c72011-05-30 14:39:57 +000054 has_terminated_(false),
niklase@google.comf0779a22011-05-30 11:39:38 +000055 cpu_usage_(NULL),
56 wbem_enum_access_(NULL),
57 number_of_objects_(0),
58 cpu_usage_handle_(0),
59 previous_processor_timestamp_(NULL),
60 timestamp_sys_100_ns_handle_(0),
61 previous_100ns_timestamp_(NULL),
62 wbem_service_(NULL),
hellner@google.com79ba0c72011-05-30 14:39:57 +000063 wbem_service_proxy_(NULL),
niklase@google.comf0779a22011-05-30 11:39:38 +000064 wbem_refresher_(NULL),
65 wbem_enum_(NULL)
66{
67 // All resources are allocated in PollingCpu().
68 if (AllocateComplexDataTypes())
69 {
70 const bool success = StartPollingCpu();
71 assert(success);
72 }
73 else
74 {
75 assert(false);
76 }
77}
78
79CpuWindows::~CpuWindows()
80{
81 // All resources are reclaimed in StopPollingCpu().
82 const bool success = StopPollingCpu();
83 assert(success);
84 DeAllocateComplexDataTypes();
85}
86
87bool CpuWindows::AllocateComplexDataTypes()
88{
89 cpu_polling_thread = ThreadWrapper::CreateThread(
90 CpuWindows::Process,
91 reinterpret_cast<void*>(this),
92 kNormalPriority,
93 "CpuWindows");
94 init_crit_ = CriticalSectionWrapper::CreateCriticalSection();
95 init_cond_ = ConditionVariableWrapper::CreateConditionVariable();
96 terminate_crit_ = CriticalSectionWrapper::CreateCriticalSection();
97 terminate_cond_ = ConditionVariableWrapper::CreateConditionVariable();
98 sleep_event = EventWrapper::Create();
99 return (cpu_polling_thread != NULL) && (init_crit_ != NULL) &&
100 (init_cond_ != NULL) && (terminate_crit_ != NULL) &&
101 (terminate_cond_ != NULL) && (sleep_event != NULL);
102}
103
104void CpuWindows::DeAllocateComplexDataTypes()
105{
106 if (sleep_event != NULL)
107 {
108 delete sleep_event;
109 sleep_event = NULL;
110 }
111 if (terminate_cond_ != NULL)
112 {
113 delete terminate_cond_;
114 terminate_cond_ = NULL;
115 }
116 if (terminate_crit_ != NULL)
117 {
118 delete terminate_crit_;
119 terminate_crit_ = NULL;
120 }
121 if (init_cond_ != NULL)
122 {
123 delete init_cond_;
124 init_cond_ = NULL;
125 }
126 if (init_crit_ != NULL)
127 {
128 delete init_crit_;
129 init_crit_ = NULL;
130 }
131 if (cpu_polling_thread != NULL)
132 {
133 delete cpu_polling_thread;
134 cpu_polling_thread = NULL;
135 }
136}
137
138bool CpuWindows::StartPollingCpu()
139{
140 unsigned int dummy_id = 0;
141 if (!cpu_polling_thread->Start(dummy_id))
142 {
143 return false;
144 }
145 {
146 CriticalSectionScoped cs(*init_crit_);
147 while(initialize_)
148 {
149 init_cond_->SleepCS(*init_crit_);
150 }
151 }
hellner@google.com79ba0c72011-05-30 14:39:57 +0000152 if (!has_initialized_)
niklase@google.comf0779a22011-05-30 11:39:38 +0000153 {
154 cpu_polling_thread->Stop();
155 return false;
156 }
hellner@google.com79ba0c72011-05-30 14:39:57 +0000157 return has_initialized_;
niklase@google.comf0779a22011-05-30 11:39:38 +0000158}
159
160bool CpuWindows::StopPollingCpu()
161{
hellner@google.com79ba0c72011-05-30 14:39:57 +0000162 if (!has_initialized_)
niklase@google.comf0779a22011-05-30 11:39:38 +0000163 {
164 return false;
165 }
166 CriticalSectionScoped cs(*terminate_crit_);
167 terminate_ = true;
168 sleep_event->Set();
hellner@google.com79ba0c72011-05-30 14:39:57 +0000169 while (!has_terminated_)
niklase@google.comf0779a22011-05-30 11:39:38 +0000170 {
171 terminate_cond_->SleepCS(*terminate_crit_);
172 }
173 cpu_polling_thread->Stop();
174 delete cpu_polling_thread;
175 cpu_polling_thread = NULL;
176 return true;
177}
178
179bool CpuWindows::Process(void* thread_object)
180{
181 return reinterpret_cast<CpuWindows*>(thread_object)->ProcessImpl();
182}
183
184bool CpuWindows::ProcessImpl()
185{
186 {
187 CriticalSectionScoped cs(*terminate_crit_);
188 if (terminate_)
189 {
190 const bool success = Terminate();
191 assert(success);
niklase@google.comf0779a22011-05-30 11:39:38 +0000192 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();
hellner@google.com79ba0c72011-05-30 14:39:57 +0000203 if (!success || !has_initialized_)
niklase@google.comf0779a22011-05-30 11:39:38 +0000204 {
hellner@google.com79ba0c72011-05-30 14:39:57 +0000205 has_initialized_ = false;
niklase@google.comf0779a22011-05-30 11:39:38 +0000206 terminate_ = true;
207 return false;
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 but make sure it doesnt trigger in
222 // debug.
223 const bool success = UpdateCpuUsage();
224 assert(success);
225 return true;
226}
227
228bool CpuWindows::CreateWmiConnection()
229{
230 IWbemLocator* service_locator = NULL;
231 HRESULT hr = CoCreateInstance(CLSID_WbemLocator, NULL,
232 CLSCTX_INPROC_SERVER, IID_IWbemLocator,
233 reinterpret_cast<void**> (&service_locator));
234 if (FAILED(hr))
235 {
236 return false;
237 }
238 // To get the WMI service specify the WMI namespace.
239 BSTR wmi_namespace = SysAllocString(L"\\\\.\\root\\cimv2");
240 if (wmi_namespace == NULL)
241 {
242 // This type of failure signifies running out of memory.
243 service_locator->Release();
244 return false;
245 }
246 hr = service_locator->ConnectServer(wmi_namespace, NULL, NULL, NULL, 0L,
247 NULL, NULL, &wbem_service_);
248 SysFreeString(wmi_namespace);
249 service_locator->Release();
250 return !FAILED(hr);
251}
252
253// Sets up WMI refresher and enum
254bool CpuWindows::CreatePerfOsRefresher()
255{
256 // Create refresher.
257 HRESULT hr = CoCreateInstance(CLSID_WbemRefresher, NULL,
258 CLSCTX_INPROC_SERVER, IID_IWbemRefresher,
259 reinterpret_cast<void**> (&wbem_refresher_));
260 if (FAILED(hr))
261 {
262 return false;
263 }
264 // Create PerfOS_Processor enum.
265 IWbemConfigureRefresher* wbem_refresher_config = NULL;
266 hr = wbem_refresher_->QueryInterface(
267 IID_IWbemConfigureRefresher,
268 reinterpret_cast<void**> (&wbem_refresher_config));
269 if (FAILED(hr))
270 {
271 return false;
272 }
hellner@google.com79ba0c72011-05-30 14:39:57 +0000273
274 // Create a proxy to the IWbemServices so that a local authentication
275 // can be set up (this is needed to be able to successfully call
276 // IWbemConfigureRefresher::AddEnum). Setting authentication with
277 // CoInitializeSecurity is process-wide (which is too intrusive).
278 hr = CoCopyProxy(static_cast<IUnknown*> (wbem_service_),
279 reinterpret_cast<IUnknown**> (&wbem_service_proxy_));
280 if(FAILED(hr))
281 {
282 return false;
283 }
284 // Set local authentication.
285 // RPC_C_AUTHN_WINNT means using NTLM instead of Kerberos which is default.
286 hr = CoSetProxyBlanket(static_cast<IUnknown*> (wbem_service_proxy_),
287 RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
288 RPC_C_AUTHN_LEVEL_DEFAULT,
289 RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
290 if(FAILED(hr))
291 {
292 return false;
293 }
294
niklase@google.comf0779a22011-05-30 11:39:38 +0000295 // Don't care about the particular id for the enum.
296 long enum_id = 0;
hellner@google.com79ba0c72011-05-30 14:39:57 +0000297 hr = wbem_refresher_config->AddEnum(wbem_service_proxy_,
niklase@google.comf0779a22011-05-30 11:39:38 +0000298 L"Win32_PerfRawData_PerfOS_Processor",
299 0, NULL, &wbem_enum_, &enum_id);
300 wbem_refresher_config->Release();
301 wbem_refresher_config = NULL;
302 return !FAILED(hr);
303}
304
305// Have to pull the first round of data to be able set the handles.
306bool CpuWindows::CreatePerfOsCpuHandles()
307{
308 // Update the refresher so that there is data available in wbem_enum_.
309 wbem_refresher_->Refresh(0L);
310
311 // The number of enumerators is the number of processor + 1 (the total).
312 // This is unknown at this point.
313 DWORD number_returned = 0;
314 HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
315 wbem_enum_access_, &number_returned);
316 // number_returned indicates the number of enumerators that are needed.
317 if (hr == WBEM_E_BUFFER_TOO_SMALL &&
318 number_returned > number_of_objects_)
319 {
320 // Allocate the number IWbemObjectAccess asked for by the
321 // GetObjects(..) function.
322 wbem_enum_access_ = new IWbemObjectAccess*[number_returned];
323 cpu_usage_ = new WebRtc_UWord32[number_returned];
324 previous_processor_timestamp_ = new unsigned __int64[number_returned];
325 previous_100ns_timestamp_ = new unsigned __int64[number_returned];
326 if ((wbem_enum_access_ == NULL) || (cpu_usage_ == NULL) ||
327 (previous_processor_timestamp_ == NULL) ||
328 (previous_100ns_timestamp_ == NULL))
329 {
330 // Out of memory.
331 return false;
332 }
333
334 SecureZeroMemory(wbem_enum_access_, number_returned *
335 sizeof(IWbemObjectAccess*));
336 memset(cpu_usage_, 0, sizeof(int) * number_returned);
337 memset(previous_processor_timestamp_, 0, sizeof(unsigned __int64) *
338 number_returned);
339 memset(previous_100ns_timestamp_, 0, sizeof(unsigned __int64) *
340 number_returned);
341
342 number_of_objects_ = number_returned;
343 // Read should be successfull now that memory has been allocated.
344 hr = wbem_enum_->GetObjects(0L, number_of_objects_, wbem_enum_access_,
345 &number_returned);
346 if (FAILED(hr))
347 {
348 return false;
349 }
350 }
351 else
352 {
353 // 0 enumerators should not be enough. Something has gone wrong here.
354 return false;
355 }
356
357 // Get the enumerator handles that are needed for calculating CPU usage.
358 CIMTYPE cpu_usage_type;
359 hr = wbem_enum_access_[0]->GetPropertyHandle(L"PercentProcessorTime",
360 &cpu_usage_type,
361 &cpu_usage_handle_);
362 if (FAILED(hr))
363 {
364 return false;
365 }
366 CIMTYPE timestamp_sys_100_ns_type;
367 hr = wbem_enum_access_[0]->GetPropertyHandle(L"TimeStamp_Sys100NS",
368 &timestamp_sys_100_ns_type,
369 &timestamp_sys_100_ns_handle_);
370 return !FAILED(hr);
371}
372
373bool CpuWindows::Initialize()
374{
375 if (terminate_)
376 {
377 return false;
378 }
379 // Initialize COM library.
380 HRESULT hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);
381 if (FAILED(hr))
382 {
383 return false;
384 }
niklase@google.comf0779a22011-05-30 11:39:38 +0000385 if (FAILED(hr))
386 {
387 return false;
388 }
389
390 if (!CreateWmiConnection())
391 {
392 return false;
393 }
394 if (!CreatePerfOsRefresher())
395 {
396 return false;
397 }
398 if (!CreatePerfOsCpuHandles())
399 {
400 return false;
401 }
hellner@google.com79ba0c72011-05-30 14:39:57 +0000402 has_initialized_ = true;
niklase@google.comf0779a22011-05-30 11:39:38 +0000403 return true;
404}
405
406bool CpuWindows::Terminate()
407{
hellner@google.com79ba0c72011-05-30 14:39:57 +0000408 if (has_terminated_)
409 {
410 return false;
411 }
niklase@google.comf0779a22011-05-30 11:39:38 +0000412 // Reverse order of Initialize().
413 // Some compilers complain about deleting NULL though it's well defined
414 if (previous_100ns_timestamp_ != NULL)
415 {
416 delete[] previous_100ns_timestamp_;
417 previous_100ns_timestamp_ = NULL;
418 }
419 if (previous_processor_timestamp_ != NULL)
420 {
421 delete[] previous_processor_timestamp_;
422 previous_processor_timestamp_ = NULL;
423 }
424 if (cpu_usage_ != NULL)
425 {
426 delete[] cpu_usage_;
427 cpu_usage_ = NULL;
428 }
429 if (wbem_enum_access_ != NULL)
430 {
431 for (DWORD i = 0; i < number_of_objects_; i++)
432 {
433 if(wbem_enum_access_[i] != NULL)
434 {
435 wbem_enum_access_[i]->Release();
436 }
437 }
438 delete[] wbem_enum_access_;
439 wbem_enum_access_ = NULL;
440 }
441 if (wbem_enum_ != NULL)
442 {
443 wbem_enum_->Release();
444 wbem_enum_ = NULL;
hellner@google.com79ba0c72011-05-30 14:39:57 +0000445 }
niklase@google.comf0779a22011-05-30 11:39:38 +0000446 if (wbem_refresher_ != NULL)
447 {
448 wbem_refresher_->Release();
449 wbem_refresher_ = NULL;
450 }
hellner@google.com79ba0c72011-05-30 14:39:57 +0000451 if (wbem_service_proxy_ != NULL)
452 {
453 wbem_service_proxy_->Release();
454 wbem_service_proxy_ = NULL;
455 }
niklase@google.comf0779a22011-05-30 11:39:38 +0000456 if (wbem_service_ != NULL)
457 {
458 wbem_service_->Release();
459 wbem_service_ = NULL;
460 }
461 // CoUninitialized should be called once for every CoInitializeEx.
462 // Regardless if it failed or not.
463 CoUninitialize();
hellner@google.com79ba0c72011-05-30 14:39:57 +0000464 has_terminated_ = true;
465 return true;
niklase@google.comf0779a22011-05-30 11:39:38 +0000466}
467
468bool CpuWindows::UpdateCpuUsage()
469{
470 wbem_refresher_->Refresh(0L);
471 DWORD number_returned = 0;
472 HRESULT hr = wbem_enum_->GetObjects(0L, number_of_objects_,
473 wbem_enum_access_,&number_returned);
474 if (FAILED(hr))
475 {
476 // wbem_enum_access_ has already been allocated. Unless the number of
477 // CPUs change runtime this should not happen.
478 return false;
479 }
480 unsigned __int64 cpu_usage = 0;
481 unsigned __int64 timestamp_100ns = 0;
482 bool returnValue = true;
483 for (DWORD i = 0; i < number_returned; i++)
484 {
485 hr = wbem_enum_access_[i]->ReadQWORD(cpu_usage_handle_,&cpu_usage);
486 if (FAILED(hr))
487 {
488 returnValue = false;
489 }
490 hr = wbem_enum_access_[i]->ReadQWORD(timestamp_sys_100_ns_handle_,
491 &timestamp_100ns);
492 if (FAILED(hr))
493 {
494 returnValue = false;
495 }
496 wbem_enum_access_[i]->Release();
497 wbem_enum_access_[i] = NULL;
498
499 const bool wrapparound =
500 (previous_processor_timestamp_[i] > cpu_usage) ||
501 (previous_100ns_timestamp_[i] > timestamp_100ns);
502 const bool first_time = (previous_processor_timestamp_[i] == 0) ||
503 (previous_100ns_timestamp_[i] == 0);
504 if (wrapparound || first_time)
505 {
506 previous_processor_timestamp_[i] = cpu_usage;
507 previous_100ns_timestamp_[i] = timestamp_100ns;
508 continue;
509 }
510 const unsigned __int64 processor_timestamp_delta =
511 cpu_usage - previous_processor_timestamp_[i];
512 const unsigned __int64 timestamp_100ns_delta =
513 timestamp_100ns - previous_100ns_timestamp_[i];
514
515 if (processor_timestamp_delta >= timestamp_100ns_delta)
516 {
517 cpu_usage_[i] = 0;
518 } else {
519 // Quotient must be float since the division is guaranteed to yield
520 // a value between 0 and 1 which is 0 in integer division.
521 const float delta_quotient =
522 static_cast<float>(processor_timestamp_delta) /
523 static_cast<float>(timestamp_100ns_delta);
524 cpu_usage_[i] = 100 - static_cast<WebRtc_UWord32>(delta_quotient *
525 100);
526 }
527 previous_processor_timestamp_[i] = cpu_usage;
528 previous_100ns_timestamp_[i] = timestamp_100ns;
529 }
530 return returnValue;
531}
532} // namespace webrtc