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#include "talk/base/systeminfo.h"
29
30#if defined(WIN32)
31#include <winsock2.h>
32#ifndef EXCLUDE_D3D9
33#include <d3d9.h>
34#endif
35#include <intrin.h>  // for __cpuid()
36#elif defined(OSX)
37#include <ApplicationServices/ApplicationServices.h>
38#include <CoreServices/CoreServices.h>
39#elif defined(LINUX) || defined(ANDROID)
40#include <unistd.h>
41#endif
42#if defined(OSX) || defined(IOS)
43#include <sys/sysctl.h>
44#endif
45
46#if defined(WIN32)
47#include "talk/base/scoped_ptr.h"
48#include "talk/base/win32.h"
49#elif defined(OSX)
50#include "talk/base/macconversion.h"
51#elif defined(LINUX) || defined(ANDROID)
52#include "talk/base/linux.h"
53#endif
54#include "talk/base/common.h"
55#include "talk/base/logging.h"
56#include "talk/base/stringutils.h"
57
58namespace talk_base {
59
60// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
61#if defined(WIN32)
62typedef BOOL (WINAPI *LPFN_GLPI)(
63    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
64    PDWORD);
65
66static void GetProcessorInformation(int* physical_cpus, int* cache_size) {
67  // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond.
68  LPFN_GLPI glpi = reinterpret_cast<LPFN_GLPI>(GetProcAddress(
69      GetModuleHandle(L"kernel32"),
70      "GetLogicalProcessorInformation"));
71  if (NULL == glpi) {
72    return;
73  }
74  // Determine buffer size, allocate and get processor information.
75  // Size can change between calls (unlikely), so a loop is done.
76  DWORD return_length = 0;
77  scoped_ptr<SYSTEM_LOGICAL_PROCESSOR_INFORMATION[]> infos;
78  while (!glpi(infos.get(), &return_length)) {
79    if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
80      infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[
81          return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]);
82    } else {
83      return;
84    }
85  }
86  *physical_cpus = 0;
87  *cache_size = 0;
88  for (size_t i = 0;
89      i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
90    if (infos[i].Relationship == RelationProcessorCore) {
91      ++*physical_cpus;
92    } else if (infos[i].Relationship == RelationCache) {
93      int next_cache_size = static_cast<int>(infos[i].Cache.Size);
94      if (next_cache_size >= *cache_size) {
95        *cache_size = next_cache_size;
96      }
97    }
98  }
99  return;
100}
101#else
102// TODO(fbarchard): Use gcc 4.4 provided cpuid intrinsic
103// 32 bit fpic requires ebx be preserved
104#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__)
105static inline void __cpuid(int cpu_info[4], int info_type) {
106  __asm__ volatile (  // NOLINT
107    "mov %%ebx, %%edi\n"
108    "cpuid\n"
109    "xchg %%edi, %%ebx\n"
110    : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
111    : "a"(info_type)
112  );  // NOLINT
113}
114#elif defined(__i386__) || defined(__x86_64__)
115static inline void __cpuid(int cpu_info[4], int info_type) {
116  __asm__ volatile (  // NOLINT
117    "cpuid\n"
118    : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
119    : "a"(info_type)
120  );  // NOLINT
121}
122#endif
123#endif  // WIN32
124
125// Note(fbarchard):
126// Family and model are extended family and extended model.  8 bits each.
127SystemInfo::SystemInfo()
128    : physical_cpus_(1), logical_cpus_(1), cache_size_(0),
129      cpu_family_(0), cpu_model_(0), cpu_stepping_(0),
130      cpu_speed_(0), memory_(0) {
131  // Initialize the basic information.
132#if defined(__arm__) || defined(_M_ARM)
133  cpu_arch_ = SI_ARCH_ARM;
134#elif defined(__x86_64__) || defined(_M_X64)
135  cpu_arch_ = SI_ARCH_X64;
136#elif defined(__i386__) || defined(_M_IX86)
137  cpu_arch_ = SI_ARCH_X86;
138#else
139  cpu_arch_ = SI_ARCH_UNKNOWN;
140#endif
141
142#if defined(WIN32)
143  SYSTEM_INFO si;
144  GetSystemInfo(&si);
145  logical_cpus_ = si.dwNumberOfProcessors;
146  GetProcessorInformation(&physical_cpus_, &cache_size_);
147  if (physical_cpus_ <= 0) {
148    physical_cpus_ = logical_cpus_;
149  }
150  cpu_family_ = si.wProcessorLevel;
151  cpu_model_ = si.wProcessorRevision >> 8;
152  cpu_stepping_ = si.wProcessorRevision & 0xFF;
153#elif defined(OSX) || defined(IOS)
154  uint32_t sysctl_value;
155  size_t length = sizeof(sysctl_value);
156  if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) {
157    physical_cpus_ = static_cast<int>(sysctl_value);
158  }
159  length = sizeof(sysctl_value);
160  if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) {
161    logical_cpus_ = static_cast<int>(sysctl_value);
162  }
163  uint64_t sysctl_value64;
164  length = sizeof(sysctl_value64);
165  if (!sysctlbyname("hw.l3cachesize", &sysctl_value64, &length, NULL, 0)) {
166    cache_size_ = static_cast<int>(sysctl_value64);
167  }
168  if (!cache_size_) {
169    length = sizeof(sysctl_value64);
170    if (!sysctlbyname("hw.l2cachesize", &sysctl_value64, &length, NULL, 0)) {
171      cache_size_ = static_cast<int>(sysctl_value64);
172    }
173  }
174  length = sizeof(sysctl_value);
175  if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) {
176    cpu_family_ = static_cast<int>(sysctl_value);
177  }
178  length = sizeof(sysctl_value);
179  if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) {
180    cpu_model_ = static_cast<int>(sysctl_value);
181  }
182  length = sizeof(sysctl_value);
183  if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) {
184    cpu_stepping_ = static_cast<int>(sysctl_value);
185  }
186#elif defined(__native_client__)
187  // TODO(ryanpetrie): Implement this via PPAPI when it's available.
188#else  // LINUX || ANDROID
189  ProcCpuInfo proc_info;
190  if (proc_info.LoadFromSystem()) {
191    proc_info.GetNumCpus(&logical_cpus_);
192    proc_info.GetNumPhysicalCpus(&physical_cpus_);
193    proc_info.GetCpuFamily(&cpu_family_);
194#if defined(CPU_X86)
195    // These values only apply to x86 systems.
196    proc_info.GetSectionIntValue(0, "model", &cpu_model_);
197    proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_);
198    proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_);
199    proc_info.GetSectionIntValue(0, "cache size", &cache_size_);
200    cache_size_ *= 1024;
201#endif
202  }
203  // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo.
204  // But that number is a moving target which can change on-the-fly according to
205  // many factors including system workload.
206  // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors.
207  // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more
208  // accurate. We use it as our cpu speed when it is available.
209  // cpuinfo_max_freq is measured in KHz and requires conversion to MHz.
210  int max_freq = talk_base::ReadCpuMaxFreq();
211  if (max_freq > 0) {
212    cpu_speed_ = max_freq / 1000;
213  }
214#endif
215// For L2 CacheSize see also
216// http://www.flounder.com/cpuid_explorer2.htm#CPUID(0x800000006)
217#ifdef CPU_X86
218  if (cache_size_ == 0) {
219    int cpu_info[4];
220    __cpuid(cpu_info, 0x80000000);  // query maximum extended cpuid function.
221    if (static_cast<uint32>(cpu_info[0]) >= 0x80000006) {
222      __cpuid(cpu_info, 0x80000006);
223      cache_size_ = (cpu_info[2] >> 16) * 1024;
224    }
225  }
226#endif
227}
228
229// Return the number of cpu threads available to the system.
230int SystemInfo::GetMaxCpus() {
231  return logical_cpus_;
232}
233
234// Return the number of cpu cores available to the system.
235int SystemInfo::GetMaxPhysicalCpus() {
236  return physical_cpus_;
237}
238
239// Return the number of cpus available to the process.  Since affinity can be
240// changed on the fly, do not cache this value.
241// Can be affected by heat.
242int SystemInfo::GetCurCpus() {
243  int cur_cpus;
244#if defined(WIN32)
245  DWORD_PTR process_mask, system_mask;
246  ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask);
247  for (cur_cpus = 0; process_mask; ++cur_cpus) {
248    // Sparse-ones algorithm. There are slightly faster methods out there but
249    // they are unintuitive and won't make a difference on a single dword.
250    process_mask &= (process_mask - 1);
251  }
252#elif defined(OSX) || defined(IOS)
253  uint32_t sysctl_value;
254  size_t length = sizeof(sysctl_value);
255  int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0);
256  cur_cpus = !error ? static_cast<int>(sysctl_value) : 1;
257#else
258  // Linux, Solaris, ANDROID
259  cur_cpus = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
260#endif
261  return cur_cpus;
262}
263
264// Return the type of this CPU.
265SystemInfo::Architecture SystemInfo::GetCpuArchitecture() {
266  return cpu_arch_;
267}
268
269// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
270// See "Intel Processor Identification and the CPUID Instruction"
271// (Intel document number: 241618)
272std::string SystemInfo::GetCpuVendor() {
273  if (cpu_vendor_.empty()) {
274#if defined(CPU_X86)
275    int cpu_info[4];
276    __cpuid(cpu_info, 0);
277    cpu_info[0] = cpu_info[1];  // Reorder output
278    cpu_info[1] = cpu_info[3];
279    cpu_info[2] = cpu_info[2];
280    cpu_info[3] = 0;
281    cpu_vendor_ = std::string(reinterpret_cast<char*>(&cpu_info[0]));
282#elif defined(CPU_ARM)
283    cpu_vendor_ = std::string("ARM");
284#else
285    cpu_vendor_ = std::string("Undefined");
286#endif
287  }
288  return cpu_vendor_;
289}
290
291int SystemInfo::GetCpuCacheSize() {
292  return cache_size_;
293}
294
295// Return the "family" of this CPU.
296int SystemInfo::GetCpuFamily() {
297  return cpu_family_;
298}
299
300// Return the "model" of this CPU.
301int SystemInfo::GetCpuModel() {
302  return cpu_model_;
303}
304
305// Return the "stepping" of this CPU.
306int SystemInfo::GetCpuStepping() {
307  return cpu_stepping_;
308}
309
310// Return the clockrate of the primary processor in Mhz.  This value can be
311// cached.  Returns -1 on error.
312int SystemInfo::GetMaxCpuSpeed() {
313  if (cpu_speed_) {
314    return cpu_speed_;
315  }
316#if defined(WIN32)
317  HKEY key;
318  static const WCHAR keyName[] =
319      L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
320
321  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key)
322      == ERROR_SUCCESS) {
323    DWORD data, len;
324    len = sizeof(data);
325
326    if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
327                        &len) == ERROR_SUCCESS) {
328      cpu_speed_ = data;
329    } else {
330      LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName
331                      << "\\~Mhz";
332      cpu_speed_ = -1;
333    }
334
335    RegCloseKey(key);
336  } else {
337    LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName;
338    cpu_speed_ = -1;
339  }
340#elif defined(IOS) || defined(OSX)
341  uint64_t sysctl_value;
342  size_t length = sizeof(sysctl_value);
343  int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length,
344                           NULL, 0);
345  cpu_speed_ = !error ? static_cast<int>(sysctl_value/1000000) : -1;
346#else
347  // TODO(fbarchard): Implement using proc/cpuinfo
348  cpu_speed_ = 0;
349#endif
350  return cpu_speed_;
351}
352
353// Dynamically check the current clockrate, which could be reduced because of
354// powersaving profiles.  Eventually for windows we want to query WMI for
355// root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency
356int SystemInfo::GetCurCpuSpeed() {
357#if defined(WIN32)
358  // TODO(fbarchard): Add WMI check, requires COM initialization
359  // NOTE(fbarchard): Testable on Sandy Bridge.
360  return GetMaxCpuSpeed();
361#elif defined(IOS) || defined(OSX)
362  uint64_t sysctl_value;
363  size_t length = sizeof(sysctl_value);
364  int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0);
365  return !error ? static_cast<int>(sysctl_value/1000000) : GetMaxCpuSpeed();
366#else  // LINUX || ANDROID
367  // TODO(fbarchard): Use proc/cpuinfo for Cur speed on Linux.
368  return GetMaxCpuSpeed();
369#endif
370}
371
372// Returns the amount of installed physical memory in Bytes.  Cacheable.
373// Returns -1 on error.
374int64 SystemInfo::GetMemorySize() {
375  if (memory_) {
376    return memory_;
377  }
378
379#if defined(WIN32)
380  MEMORYSTATUSEX status = {0};
381  status.dwLength = sizeof(status);
382
383  if (GlobalMemoryStatusEx(&status)) {
384    memory_ = status.ullTotalPhys;
385  } else {
386    LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed.";
387    memory_ = -1;
388  }
389
390#elif defined(OSX) || defined(IOS)
391  size_t len = sizeof(memory_);
392  int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0);
393  if (error || memory_ == 0) {
394    memory_ = -1;
395  }
396#else  // LINUX || ANDROID
397  memory_ = static_cast<int64>(sysconf(_SC_PHYS_PAGES)) *
398      static_cast<int64>(sysconf(_SC_PAGESIZE));
399  if (memory_ < 0) {
400    LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed."
401                    << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES)
402                    << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE);
403    memory_ = -1;
404  }
405#endif
406
407  return memory_;
408}
409
410
411// Return the name of the machine model we are currently running on.
412// This is a human readable string that consists of the name and version
413// number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if
414// model can not be determined. The string is cached for subsequent calls.
415std::string SystemInfo::GetMachineModel() {
416  if (!machine_model_.empty()) {
417    return machine_model_;
418  }
419
420#if defined(OSX) || defined(IOS)
421  char buffer[128];
422  size_t length = sizeof(buffer);
423  int error = sysctlbyname("hw.model", buffer, &length, NULL, 0);
424  if (!error) {
425    machine_model_.assign(buffer, length - 1);
426  } else {
427    machine_model_.clear();
428  }
429#else
430  machine_model_ = "Not available";
431#endif
432
433  return machine_model_;
434}
435
436#ifdef OSX
437// Helper functions to query IOKit for video hardware properties.
438static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) {
439  return IORegistryEntrySearchCFProperty(port, kIOServicePlane,
440      name, kCFAllocatorDefault,
441      kIORegistryIterateRecursively | kIORegistryIterateParents);
442}
443
444static void GetProperty(io_service_t port, CFStringRef name, int* value) {
445  if (!value) return;
446  CFTypeRef ref = SearchForProperty(port, name);
447  if (ref) {
448    CFTypeID refType = CFGetTypeID(ref);
449    if (CFNumberGetTypeID() == refType) {
450      CFNumberRef number = reinterpret_cast<CFNumberRef>(ref);
451      p_convertCFNumberToInt(number, value);
452    } else if (CFDataGetTypeID() == refType) {
453      CFDataRef data = reinterpret_cast<CFDataRef>(ref);
454      if (CFDataGetLength(data) == sizeof(UInt32)) {
455        *value = *reinterpret_cast<const UInt32*>(CFDataGetBytePtr(data));
456      }
457    }
458    CFRelease(ref);
459  }
460}
461
462static void GetProperty(io_service_t port, CFStringRef name,
463                        std::string* value) {
464  if (!value) return;
465  CFTypeRef ref = SearchForProperty(port, name);
466  if (ref) {
467    CFTypeID refType = CFGetTypeID(ref);
468    if (CFStringGetTypeID() == refType) {
469      CFStringRef stringRef = reinterpret_cast<CFStringRef>(ref);
470      p_convertHostCFStringRefToCPPString(stringRef, *value);
471    } else if (CFDataGetTypeID() == refType) {
472      CFDataRef dataRef = reinterpret_cast<CFDataRef>(ref);
473      *value = std::string(reinterpret_cast<const char*>(
474          CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef));
475    }
476    CFRelease(ref);
477  }
478}
479#endif
480
481// Fills a struct with information on the graphics adapater and returns true
482// iff successful.
483bool SystemInfo::GetGpuInfo(GpuInfo *info) {
484  if (!info) return false;
485#if defined(WIN32) && !defined(EXCLUDE_D3D9)
486  D3DADAPTER_IDENTIFIER9 identifier;
487  HRESULT hr = E_FAIL;
488  HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll");
489
490  if (d3d_lib) {
491    typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT);
492    D3DCreate9Proc d3d_create_proc = reinterpret_cast<D3DCreate9Proc>(
493        GetProcAddress(d3d_lib, "Direct3DCreate9"));
494    if (d3d_create_proc) {
495      IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION);
496      if (d3d) {
497        hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier);
498        d3d->Release();
499      }
500    }
501    FreeLibrary(d3d_lib);
502  }
503
504  if (hr != D3D_OK) {
505    LOG(LS_ERROR) << "Failed to access Direct3D9 information.";
506    return false;
507  }
508
509  info->device_name = identifier.DeviceName;
510  info->description = identifier.Description;
511  info->vendor_id = identifier.VendorId;
512  info->device_id = identifier.DeviceId;
513  info->driver = identifier.Driver;
514  // driver_version format: product.version.subversion.build
515  std::stringstream ss;
516  ss << HIWORD(identifier.DriverVersion.HighPart) << "."
517     << LOWORD(identifier.DriverVersion.HighPart) << "."
518     << HIWORD(identifier.DriverVersion.LowPart) << "."
519     << LOWORD(identifier.DriverVersion.LowPart);
520  info->driver_version = ss.str();
521  return true;
522#elif defined(OSX)
523  // We'll query the IOKit for the gpu of the main display.
524  io_service_t display_service_port = CGDisplayIOServicePort(
525      kCGDirectMainDisplay);
526  GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id);
527  GetProperty(display_service_port, CFSTR("device-id"), &info->device_id);
528  GetProperty(display_service_port, CFSTR("model"), &info->description);
529  return true;
530#else  // LINUX || ANDROID
531  // TODO(fbarchard): Implement this on Linux
532  return false;
533#endif
534}
535}  // namespace talk_base
536