1 /*
2  * libjingle
3  * Copyright 2008 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #ifdef WIN32
29 #include "talk/base/scoped_ptr.h"
30 #include "talk/base/win32.h"  // first because it brings in win32 stuff
31 #ifndef EXCLUDE_D3D9
32 #include <d3d9.h>
33 #endif
34 
35 #elif defined(OSX)
36 #include <ApplicationServices/ApplicationServices.h>
37 #include <CoreServices/CoreServices.h>
38 #include <sys/sysctl.h>
39 #include "talk/base/macconversion.h"
40 #elif defined(IOS)
41 #include <sys/sysctl.h>
42 #elif defined(LINUX) || defined(ANDROID)
43 #include <unistd.h>
44 #include "talk/base/linux.h"
45 #endif
46 
47 #include "talk/base/common.h"
48 #include "talk/base/cpuid.h"
49 #include "talk/base/logging.h"
50 #include "talk/base/stringutils.h"
51 #include "talk/base/systeminfo.h"
52 
53 namespace talk_base {
54 
55 // See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
56 #ifdef WIN32
57 typedef BOOL (WINAPI *LPFN_GLPI)(
58     PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
59     PDWORD);
60 
NumCores()61 static int NumCores() {
62   // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond.
63   LPFN_GLPI glpi = reinterpret_cast<LPFN_GLPI>(GetProcAddress(
64       GetModuleHandle(L"kernel32"),
65       "GetLogicalProcessorInformation"));
66   if (NULL == glpi) {
67     return -1;
68   }
69   // Determine buffer size, allocate and get processor information.
70   // Size can change between calls (unlikely), so a loop is done.
71   DWORD return_length = 0;
72   scoped_array<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> infos;
73   while (!glpi(infos.get(), &return_length)) {
74     if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
75       infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[
76           return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]);
77     } else {
78       return -1;
79     }
80   }
81   int processor_core_count = 0;
82   for (size_t i = 0;
83       i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
84     if (infos[i].Relationship == RelationProcessorCore) {
85       ++processor_core_count;
86     }
87   }
88   return processor_core_count;
89 }
90 #endif
91 
92 // Note(fbarchard):
93 // Family and model are extended family and extended model.  8 bits each.
SystemInfo()94 SystemInfo::SystemInfo()
95     : physical_cpus_(1), logical_cpus_(1),
96       cpu_family_(0), cpu_model_(0), cpu_stepping_(0),
97       cpu_speed_(0), memory_(0) {
98   // Initialize the basic information.
99 
100 #if defined(__arm__)
101   cpu_arch_ = SI_ARCH_ARM;
102 #elif defined(CPU_X86)
103   cpu_arch_ = SI_ARCH_X86;
104 #else
105   cpu_arch_ = SI_ARCH_UNKNOWN;
106 #endif
107 
108 #ifdef WIN32
109   SYSTEM_INFO si;
110   GetSystemInfo(&si);
111   logical_cpus_ = si.dwNumberOfProcessors;
112   physical_cpus_ = NumCores();
113   if (physical_cpus_ <= 0) {
114     physical_cpus_ = logical_cpus_;
115   }
116   cpu_family_ = si.wProcessorLevel;
117   cpu_model_ = si.wProcessorRevision >> 8;
118   cpu_stepping_ = si.wProcessorRevision & 0xFF;
119 #elif defined(OSX) || defined(IOS)
120   uint32_t sysctl_value;
121   size_t length = sizeof(sysctl_value);
122   if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) {
123     physical_cpus_ = static_cast<int>(sysctl_value);
124   }
125   length = sizeof(sysctl_value);
126   if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) {
127     logical_cpus_ = static_cast<int>(sysctl_value);
128   }
129   length = sizeof(sysctl_value);
130   if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) {
131     cpu_family_ = static_cast<int>(sysctl_value);
132   }
133   length = sizeof(sysctl_value);
134   if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) {
135     cpu_model_ = static_cast<int>(sysctl_value);
136   }
137   length = sizeof(sysctl_value);
138   if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) {
139     cpu_stepping_ = static_cast<int>(sysctl_value);
140   }
141 #else // LINUX || ANDROID
142   ProcCpuInfo proc_info;
143   if (proc_info.LoadFromSystem()) {
144     proc_info.GetNumCpus(&logical_cpus_);
145     proc_info.GetNumPhysicalCpus(&physical_cpus_);
146     proc_info.GetCpuFamily(&cpu_family_);
147 #if defined(CPU_X86)
148     // These values only apply to x86 systems.
149     proc_info.GetSectionIntValue(0, "model", &cpu_model_);
150     proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_);
151     proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_);
152 #endif
153   }
154 
155   // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo.
156   // But that number is a moving target which can change on-the-fly according to
157   // many factors including system workload.
158   // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors.
159   // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more
160   // accurate. We use it as our cpu speed when it is available.
161   int max_freq = talk_base::ReadCpuMaxFreq();
162   if (max_freq > 0) {
163     cpu_speed_ = max_freq;
164   }
165 #endif
166 }
167 
168 // Return the number of cpu threads available to the system.
GetMaxCpus()169 int SystemInfo::GetMaxCpus() {
170   return logical_cpus_;
171 }
172 
173 // Return the number of cpu cores available to the system.
GetMaxPhysicalCpus()174 int SystemInfo::GetMaxPhysicalCpus() {
175   return physical_cpus_;
176 }
177 
178 // Return the number of cpus available to the process.  Since affinity can be
179 // changed on the fly, do not cache this value.
180 // Can be affected by heat.
GetCurCpus()181 int SystemInfo::GetCurCpus() {
182   int cur_cpus;
183 #ifdef WIN32
184   DWORD_PTR process_mask, system_mask;
185   ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask);
186   for (cur_cpus = 0; process_mask; ++cur_cpus) {
187     // Sparse-ones algorithm. There are slightly faster methods out there but
188     // they are unintuitive and won't make a difference on a single dword.
189     process_mask &= (process_mask - 1);
190   }
191 #elif defined(OSX)
192   // Find number of _available_ cores
193   cur_cpus = MPProcessorsScheduled();
194 #elif defined(IOS)
195   uint32_t sysctl_value;
196   size_t length = sizeof(sysctl_value);
197   int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0);
198   cur_cpus = !error ? static_cast<int>(sysctl_value) : 1;
199 #else
200   // Linux, Solaris, ANDROID
201   cur_cpus = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
202 #endif
203   return cur_cpus;
204 }
205 
206 // Return the type of this CPU.
GetCpuArchitecture()207 SystemInfo::Architecture SystemInfo::GetCpuArchitecture() {
208   return cpu_arch_;
209 }
210 
211 // Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
212 // See "Intel Processor Identification and the CPUID Instruction"
213 // (Intel document number: 241618)
GetCpuVendor()214 std::string SystemInfo::GetCpuVendor() {
215   if (cpu_vendor_.empty()) {
216     cpu_vendor_ = talk_base::CpuInfo::GetCpuVendor();
217   }
218   return cpu_vendor_;
219 }
220 
221 // Return the "family" of this CPU.
GetCpuFamily()222 int SystemInfo::GetCpuFamily() {
223   return cpu_family_;
224 }
225 
226 // Return the "model" of this CPU.
GetCpuModel()227 int SystemInfo::GetCpuModel() {
228   return cpu_model_;
229 }
230 
231 // Return the "stepping" of this CPU.
GetCpuStepping()232 int SystemInfo::GetCpuStepping() {
233   return cpu_stepping_;
234 }
235 
236 // Return the clockrate of the primary processor in Mhz.  This value can be
237 // cached.  Returns -1 on error.
GetMaxCpuSpeed()238 int SystemInfo::GetMaxCpuSpeed() {
239   if (cpu_speed_) {
240     return cpu_speed_;
241   }
242 
243 #ifdef WIN32
244   HKEY key;
245   static const WCHAR keyName[] =
246       L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
247 
248   if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key)
249       == ERROR_SUCCESS) {
250     DWORD data, len;
251     len = sizeof(data);
252 
253     if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
254                         &len) == ERROR_SUCCESS) {
255       cpu_speed_ = data;
256     } else {
257       LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName
258                       << "\\~Mhz";
259       cpu_speed_ = -1;
260     }
261 
262     RegCloseKey(key);
263   } else {
264     LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName;
265     cpu_speed_ = -1;
266   }
267 #elif defined(IOS) || defined(OSX)
268   uint64_t sysctl_value;
269   size_t length = sizeof(sysctl_value);
270   int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length, NULL, 0);
271   cpu_speed_ = !error ? static_cast<int>(sysctl_value/1000000) : -1;
272 #else
273   // TODO: Implement using proc/cpuinfo
274   cpu_speed_ = 0;
275 #endif
276   return cpu_speed_;
277 }
278 
279 // Dynamically check the current clockrate, which could be reduced because of
280 // powersaving profiles.  Eventually for windows we want to query WMI for
281 // root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency
GetCurCpuSpeed()282 int SystemInfo::GetCurCpuSpeed() {
283 #ifdef WIN32
284   // TODO: Add WMI check, requires COM initialization
285   // NOTE(fbarchard): Testable on Sandy Bridge.
286   return GetMaxCpuSpeed();
287 #elif defined(IOS) || defined(OSX)
288   uint64_t sysctl_value;
289   size_t length = sizeof(sysctl_value);
290   int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0);
291   return !error ? static_cast<int>(sysctl_value/1000000) : GetMaxCpuSpeed();
292 #else // LINUX || ANDROID
293   // TODO: Use proc/cpuinfo for Cur speed on Linux.
294   return GetMaxCpuSpeed();
295 #endif
296 }
297 
298 // Returns the amount of installed physical memory in Bytes.  Cacheable.
299 // Returns -1 on error.
GetMemorySize()300 int64 SystemInfo::GetMemorySize() {
301   if (memory_) {
302     return memory_;
303   }
304 
305 #ifdef WIN32
306   MEMORYSTATUSEX status = {0};
307   status.dwLength = sizeof(status);
308 
309   if (GlobalMemoryStatusEx(&status)) {
310     memory_ = status.ullTotalPhys;
311   } else {
312     LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed.";
313     memory_ = -1;
314   }
315 
316 #elif defined(OSX) || defined(IOS)
317   size_t len = sizeof(memory_);
318   int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0);
319   if (error || memory_ == 0) {
320     memory_ = -1;
321   }
322 #else
323   memory_ = static_cast<int64>(sysconf(_SC_PHYS_PAGES)) *
324       static_cast<int64>(sysconf(_SC_PAGESIZE));
325   if (memory_ < 0) {
326     LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed."
327                     << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES)
328                     << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE);
329     memory_ = -1;
330   }
331 #endif
332 
333   return memory_;
334 }
335 
336 // Return the name of the machine model we are currently running on.
337 // This is a human readable string that consists of the name and version
338 // number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if
339 // model can not be determined. The string is cached for subsequent calls.
GetMachineModel()340 std::string SystemInfo::GetMachineModel() {
341   if (!machine_model_.empty()) {
342     return machine_model_;
343   }
344 
345 #if defined(OSX) || defined(IOS)
346   char buffer[128];
347   size_t length = sizeof(buffer);
348   int error = sysctlbyname("hw.model", buffer, &length, NULL, 0);
349   if (!error) {
350     machine_model_.assign(buffer, length - 1);
351   } else {
352     machine_model_.clear();
353   }
354 #else
355   machine_model_ = "Not available";
356 #endif
357 
358   return machine_model_;
359 }
360 
361 #ifdef OSX
362 // Helper functions to query IOKit for video hardware properties.
SearchForProperty(io_service_t port,CFStringRef name)363 static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) {
364   return IORegistryEntrySearchCFProperty(port, kIOServicePlane,
365       name, kCFAllocatorDefault,
366       kIORegistryIterateRecursively | kIORegistryIterateParents);
367 }
368 
GetProperty(io_service_t port,CFStringRef name,int * value)369 static void GetProperty(io_service_t port, CFStringRef name, int* value) {
370   if (!value) return;
371   CFTypeRef ref = SearchForProperty(port, name);
372   if (ref) {
373     CFTypeID refType = CFGetTypeID(ref);
374     if (CFNumberGetTypeID() == refType) {
375       CFNumberRef number = reinterpret_cast<CFNumberRef>(ref);
376       p_convertCFNumberToInt(number, value);
377     } else if (CFDataGetTypeID() == refType) {
378       CFDataRef data = reinterpret_cast<CFDataRef>(ref);
379       if (CFDataGetLength(data) == sizeof(UInt32)) {
380         *value = *reinterpret_cast<const UInt32*>(CFDataGetBytePtr(data));
381       }
382     }
383     CFRelease(ref);
384   }
385 }
386 
GetProperty(io_service_t port,CFStringRef name,std::string * value)387 static void GetProperty(io_service_t port, CFStringRef name,
388                         std::string* value) {
389   if (!value) return;
390   CFTypeRef ref = SearchForProperty(port, name);
391   if (ref) {
392     CFTypeID refType = CFGetTypeID(ref);
393     if (CFStringGetTypeID() == refType) {
394       CFStringRef stringRef = reinterpret_cast<CFStringRef>(ref);
395       p_convertHostCFStringRefToCPPString(stringRef, *value);
396     } else if (CFDataGetTypeID() == refType) {
397       CFDataRef dataRef = reinterpret_cast<CFDataRef>(ref);
398       *value = std::string(reinterpret_cast<const char*>(
399           CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef));
400     }
401     CFRelease(ref);
402   }
403 }
404 #endif
405 
406 // Fills a struct with information on the graphics adapater and returns true
407 // iff successful.
GetGpuInfo(GpuInfo * info)408 bool SystemInfo::GetGpuInfo(GpuInfo *info) {
409   if (!info) return false;
410 #if defined(WIN32) && !defined(EXCLUDE_D3D9)
411   D3DADAPTER_IDENTIFIER9 identifier;
412   HRESULT hr = E_FAIL;
413   HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll");
414 
415   if (d3d_lib) {
416     typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT);
417     D3DCreate9Proc d3d_create_proc = reinterpret_cast<D3DCreate9Proc>(
418         GetProcAddress(d3d_lib, "Direct3DCreate9"));
419     if (d3d_create_proc) {
420       IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION);
421       if (d3d) {
422         hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier);
423         d3d->Release();
424       }
425     }
426     FreeLibrary(d3d_lib);
427   }
428 
429   if (hr != D3D_OK) {
430     LOG(LS_ERROR) << "Failed to access Direct3D9 information.";
431     return false;
432   }
433 
434   info->device_name = identifier.DeviceName;
435   info->description = identifier.Description;
436   info->vendor_id = identifier.VendorId;
437   info->device_id = identifier.DeviceId;
438   info->driver = identifier.Driver;
439   // driver_version format: product.version.subversion.build
440   std::stringstream ss;
441   ss << HIWORD(identifier.DriverVersion.HighPart) << "."
442      << LOWORD(identifier.DriverVersion.HighPart) << "."
443      << HIWORD(identifier.DriverVersion.LowPart) << "."
444      << LOWORD(identifier.DriverVersion.LowPart);
445   info->driver_version = ss.str();
446   return true;
447 #elif defined(OSX)
448   // We'll query the IOKit for the gpu of the main display.
449   io_service_t display_service_port = CGDisplayIOServicePort(
450       kCGDirectMainDisplay);
451   GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id);
452   GetProperty(display_service_port, CFSTR("device-id"), &info->device_id);
453   GetProperty(display_service_port, CFSTR("model"), &info->description);
454   return true;
455 #else // LINUX || ANDROID
456   // TODO: Implement this on Linux
457   return false;
458 #endif
459 }
460 } // namespace talk_base
461