Home | History | Annotate | Download | only in base
      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 
     58 namespace talk_base {
     59 
     60 // See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
     61 #if defined(WIN32)
     62 typedef BOOL (WINAPI *LPFN_GLPI)(
     63     PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
     64     PDWORD);
     65 
     66 static 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__)
    105 static 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__)
    115 static 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.
    127 SystemInfo::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.
    230 int SystemInfo::GetMaxCpus() {
    231   return logical_cpus_;
    232 }
    233 
    234 // Return the number of cpu cores available to the system.
    235 int 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.
    242 int 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.
    265 SystemInfo::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)
    272 std::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 
    291 int SystemInfo::GetCpuCacheSize() {
    292   return cache_size_;
    293 }
    294 
    295 // Return the "family" of this CPU.
    296 int SystemInfo::GetCpuFamily() {
    297   return cpu_family_;
    298 }
    299 
    300 // Return the "model" of this CPU.
    301 int SystemInfo::GetCpuModel() {
    302   return cpu_model_;
    303 }
    304 
    305 // Return the "stepping" of this CPU.
    306 int 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.
    312 int 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
    356 int 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.
    374 int64 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.
    415 std::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.
    438 static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) {
    439   return IORegistryEntrySearchCFProperty(port, kIOServicePlane,
    440       name, kCFAllocatorDefault,
    441       kIORegistryIterateRecursively | kIORegistryIterateParents);
    442 }
    443 
    444 static 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 
    462 static 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.
    483 bool 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