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                                                  &timestamp_sys_100_ns_type,
367                                                  &timestamp_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                                              &timestamp_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