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