system-info-posix.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. #include "system-info.hpp"
  2. #include "util/dstr.hpp"
  3. #include "util/platform.h"
  4. #include <sys/utsname.h>
  5. #include <unistd.h>
  6. #include <string>
  7. #include <fstream>
  8. #include <regex>
  9. using namespace std;
  10. extern "C" {
  11. #include "pci/pci.h"
  12. }
  13. // Anonymous namespace ensures internal linkage
  14. namespace {
  15. struct drm_card_info {
  16. uint16_t vendor_id;
  17. uint16_t device_id;
  18. uint16_t subsystem_vendor;
  19. uint16_t subsystem_device;
  20. /* match_count is the number of matches between
  21. * the tokenized GPU identification string and
  22. * the device_name and vendor_name supplied by
  23. * libpci. It is used to associate the GPU that
  24. * OBS is using and the cards found in a bus scan
  25. * using libpci.
  26. */
  27. uint16_t match_count;
  28. /* The following 2 fields are easily
  29. * obtained for AMD GPUs. Reporting
  30. * for NVIDIA GPUs is not working at
  31. * the moment.
  32. */
  33. uint64_t dedicated_vram_total;
  34. uint64_t shared_system_memory_total;
  35. // PCI domain:bus:slot:function
  36. std::string dbsf;
  37. std::string device_name;
  38. std::string vendor_name;
  39. };
  40. constexpr std::string_view WHITE_SPACE = " \f\n\r\t\v";
  41. // trim_ws() will remove leading and trailing
  42. // white space from the string.
  43. void trim_ws(std::string &s)
  44. {
  45. // Trim leading whitespace
  46. size_t pos = s.find_first_not_of(WHITE_SPACE);
  47. if (pos != std::string::npos)
  48. s = s.substr(pos);
  49. // Trim trailing whitespace
  50. pos = s.find_last_not_of(WHITE_SPACE);
  51. if (pos != std::string::npos)
  52. s = s.substr(0, pos + 1);
  53. }
  54. bool compare_match_strength(const drm_card_info &a, const drm_card_info &b)
  55. {
  56. return a.match_count > b.match_count;
  57. }
  58. void adjust_gpu_model(std::string &model)
  59. {
  60. /* Use the sub-string between the [] brackets. For example,
  61. * the NVIDIA Quadro P4000 model string from PCI ID database
  62. * is "GP104GL [Quadro P4000]", and we only want the "Quadro
  63. * P4000" sub-string.
  64. */
  65. size_t first = model.find('[');
  66. size_t last = model.find_last_of(']');
  67. if ((last - first) > 1) {
  68. std::string adjusted_model = model.substr(first + 1, last - first - 1);
  69. model.assign(adjusted_model);
  70. }
  71. }
  72. bool get_distribution_info(std::string &distro, std::string &version)
  73. {
  74. ifstream file;
  75. std::string line;
  76. const std::string flatpak_file = "/.flatpak-info";
  77. const std::string systemd_file = "/etc/os-release";
  78. distro = "";
  79. version = "";
  80. if (std::filesystem::exists(flatpak_file)) {
  81. /* The .flatpak-info file has a line of the form:
  82. *
  83. * runtime=runtime/org.kde.Platform/x86_64/6.6
  84. *
  85. * Parse the line into tokens to identify the name and
  86. * version, which are "org.kde.Platform" and "6.6" respectively,
  87. * in the example above.
  88. */
  89. file.open(flatpak_file);
  90. if (file.is_open()) {
  91. while (getline(file, line)) {
  92. if (line.compare(0, 16, "runtime=runtime/") == 0) {
  93. size_t pos = line.find('/');
  94. if (pos != string::npos && line.at(pos + 1) != '\0') {
  95. line.erase(0, pos + 1);
  96. /* Split the string into tokens with a regex
  97. * of one or more '/' characters'.
  98. */
  99. std::regex fp_reg("[/]+");
  100. vector<std::string> fp_tokens(
  101. sregex_token_iterator(line.begin(), line.end(), fp_reg, -1),
  102. sregex_token_iterator());
  103. if (fp_tokens.size() >= 2) {
  104. auto token = begin(fp_tokens);
  105. distro = "Flatpak " + *token;
  106. token = next(fp_tokens.end(), -1);
  107. version = *token;
  108. } else {
  109. distro = "Flatpak unknown";
  110. version = "0";
  111. blog(LOG_DEBUG,
  112. "%s: Format of 'runtime' entry unrecognized in file %s",
  113. __FUNCTION__, flatpak_file.c_str());
  114. }
  115. break;
  116. }
  117. }
  118. }
  119. file.close();
  120. }
  121. } else if (std::filesystem::exists(systemd_file)) {
  122. /* systemd-based distributions use /etc/os-release to identify
  123. * the OS. For example, the Ubuntu 24.04.1 variant looks like:
  124. *
  125. * PRETTY_NAME="Ubuntu 24.04.1 LTS"
  126. * NAME="Ubuntu"
  127. * VERSION_ID="24.04"
  128. * VERSION="24.04.1 LTS (Noble Numbat)"
  129. * VERSION_CODENAME=noble
  130. * ID=ubuntu
  131. * ID_LIKE=debian
  132. * HOME_URL="https://www.ubuntu.com/"
  133. * SUPPORT_URL="https://help.ubuntu.com/"
  134. * BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
  135. * PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
  136. * UBUNTU_CODENAME=noble
  137. * LOGO=ubuntu-logo
  138. *
  139. * Parse the file looking for the NAME and VERSION_ID fields.
  140. * Note that some distributions wrap the entry in '"' characters
  141. * while others do not, so we need to remove those characters.
  142. */
  143. file.open(systemd_file);
  144. if (file.is_open()) {
  145. while (getline(file, line)) {
  146. if (line.compare(0, 4, "NAME") == 0) {
  147. size_t pos = line.find('=');
  148. if (pos != std::string::npos && line.at(pos + 1) != '\0') {
  149. distro = line.substr(pos + 1);
  150. // Remove the '"' characters from the string, if any.
  151. distro.erase(std::remove(distro.begin(), distro.end(), '"'),
  152. distro.end());
  153. trim_ws(distro);
  154. continue;
  155. }
  156. }
  157. if (line.compare(0, 10, "VERSION_ID") == 0) {
  158. size_t pos = line.find('=');
  159. if (pos != std::string::npos && line.at(pos + 1) != '\0') {
  160. version = line.substr(pos + 1);
  161. // Remove the '"' characters from the string, if any.
  162. version.erase(std::remove(version.begin(), version.end(), '"'),
  163. version.end());
  164. trim_ws(version);
  165. continue;
  166. }
  167. }
  168. }
  169. file.close();
  170. }
  171. } else {
  172. blog(LOG_DEBUG, "%s: Failed to find host OS or flatpak info", __FUNCTION__);
  173. return false;
  174. }
  175. return true;
  176. }
  177. bool get_cpu_name(optional<std::string> &proc_name)
  178. {
  179. ifstream file("/proc/cpuinfo");
  180. std::string line;
  181. int physical_id = -1;
  182. bool found_name = false;
  183. /* Initialize processor name. Some ARM-based hosts do
  184. * not output the "model name" field in /proc/cpuinfo.
  185. */
  186. proc_name = "Unavailable";
  187. if (file.is_open()) {
  188. while (getline(file, line)) {
  189. if (line.compare(0, 10, "model name") == 0) {
  190. size_t pos = line.find(':');
  191. if (pos != std::string::npos && line.at(pos + 1) != '\0') {
  192. proc_name = line.substr(pos + 1);
  193. trim_ws((std::string &)proc_name);
  194. found_name = true;
  195. continue;
  196. }
  197. }
  198. if (line.compare(0, 11, "physical id") == 0) {
  199. size_t pos = line.find(':');
  200. if (pos != std::string::npos && line.at(pos + 1) != '\0') {
  201. physical_id = atoi(&line[pos + 1]);
  202. if (physical_id == 0 && found_name)
  203. break;
  204. }
  205. }
  206. }
  207. file.close();
  208. }
  209. return true;
  210. }
  211. bool get_cpu_freq(uint32_t &cpu_freq)
  212. {
  213. ifstream freq_file;
  214. std::string line;
  215. /* Look for the sysfs tree "base_frequency" first.
  216. * Intel exports "base_frequency, AMD does not.
  217. * If not found, use "cpuinfo_max_freq".
  218. */
  219. freq_file.open("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency");
  220. if (!freq_file.is_open()) {
  221. freq_file.open("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
  222. if (!freq_file.is_open())
  223. return false;
  224. }
  225. if (getline(freq_file, line)) {
  226. trim_ws(line);
  227. // Convert the CPU frequency string to an integer in MHz
  228. cpu_freq = atoi(line.c_str()) / 1000;
  229. } else {
  230. cpu_freq = 0;
  231. }
  232. freq_file.close();
  233. return true;
  234. }
  235. /* get_drm_cards() will find all render-capable cards
  236. * in /sys/class/drm and populate a vector of drm_card_info.
  237. */
  238. void get_drm_cards(std::vector<drm_card_info> &cards)
  239. {
  240. struct drm_card_info dci;
  241. char *file_str = NULL;
  242. char buf[256];
  243. int len = 0;
  244. uint val = 0;
  245. std::string base_path = "/sys/class/drm/";
  246. std::string drm_path = base_path;
  247. size_t base_len = base_path.length();
  248. size_t node_len = 0;
  249. os_dir_t *dir = os_opendir(base_path.c_str());
  250. struct os_dirent *ent;
  251. while ((ent = os_readdir(dir)) != NULL) {
  252. std::string entry_name = ent->d_name;
  253. if (ent->directory && (entry_name.find("renderD") != std::string::npos)) {
  254. dci = {0};
  255. drm_path.resize(base_len);
  256. drm_path += ent->d_name;
  257. drm_path += "/device";
  258. /* Get the PCI D:B:S:F (domain:bus:slot:function) in string form
  259. * by reading the device symlink.
  260. */
  261. if ((len = readlink(drm_path.c_str(), buf, sizeof(buf) - 1)) != -1) {
  262. // readlink() doesn't null terminate strings, so do it explicitly
  263. buf[len] = '\0';
  264. dci.dbsf = buf;
  265. /* The DBSF string is of the form: "../../../0000:65:00.0/".
  266. * Remove all the '/' characters, and
  267. * remove all the leading '.' characters.
  268. */
  269. dci.dbsf.erase(std::remove(dci.dbsf.begin(), dci.dbsf.end(), '/'), dci.dbsf.end());
  270. dci.dbsf.erase(0, dci.dbsf.find_first_not_of("."));
  271. node_len = drm_path.length();
  272. // Get the device_id
  273. drm_path += "/device";
  274. file_str = os_quick_read_utf8_file(drm_path.c_str());
  275. if (!file_str) {
  276. blog(LOG_ERROR, "Could not read from '%s'", drm_path.c_str());
  277. dci.device_id = 0;
  278. } else {
  279. // Skip over the "0x" and convert
  280. sscanf(file_str + 2, "%x", &val);
  281. dci.device_id = val;
  282. bfree(file_str);
  283. }
  284. // Get the vendor_id
  285. drm_path.resize(node_len);
  286. drm_path += "/vendor";
  287. file_str = os_quick_read_utf8_file(drm_path.c_str());
  288. if (!file_str) {
  289. blog(LOG_ERROR, "Could not read from '%s'", drm_path.c_str());
  290. dci.vendor_id = 0;
  291. } else {
  292. // Skip over the "0x" and convert
  293. sscanf(file_str + 2, "%x", &val);
  294. dci.vendor_id = val;
  295. bfree(file_str);
  296. }
  297. // Get the subsystem_vendor
  298. drm_path.resize(node_len);
  299. drm_path += "/subsystem_vendor";
  300. file_str = os_quick_read_utf8_file(drm_path.c_str());
  301. if (!file_str) {
  302. dci.subsystem_vendor = 0;
  303. } else {
  304. // Skip over the "0x" and convert
  305. sscanf(file_str + 2, "%x", &val);
  306. dci.subsystem_vendor = val;
  307. bfree(file_str);
  308. }
  309. // Get the subsystem_device
  310. drm_path.resize(node_len);
  311. drm_path += "/subsystem_device";
  312. file_str = os_quick_read_utf8_file(drm_path.c_str());
  313. if (!file_str) {
  314. dci.subsystem_device = 0;
  315. } else {
  316. // Skip over the "0x" and convert
  317. sscanf(file_str + 2, "%x", &val);
  318. dci.subsystem_device = val;
  319. bfree(file_str);
  320. }
  321. /* The amdgpu driver exports the GPU memory information
  322. * via sysfs nodes. Sadly NVIDIA doesn't have the same
  323. * information via sysfs. Read the amdgpu-based nodes
  324. * if present and get the required fields.
  325. *
  326. * Get the GPU total dedicated VRAM, if available
  327. */
  328. drm_path.resize(node_len);
  329. drm_path += "/mem_info_vram_total";
  330. file_str = os_quick_read_utf8_file(drm_path.c_str());
  331. if (!file_str) {
  332. dci.dedicated_vram_total = 0;
  333. } else {
  334. sscanf(file_str, "%lu", &dci.dedicated_vram_total);
  335. bfree(file_str);
  336. }
  337. // Get the GPU total shared system memory, if available
  338. drm_path.resize(node_len);
  339. drm_path += "/mem_info_gtt_total";
  340. file_str = os_quick_read_utf8_file(drm_path.c_str());
  341. if (!file_str) {
  342. dci.shared_system_memory_total = 0;
  343. } else {
  344. sscanf(file_str, "%lu", &dci.shared_system_memory_total);
  345. bfree(file_str);
  346. }
  347. cards.push_back(dci);
  348. blog(LOG_DEBUG,
  349. "%s: drm_adapter_info: PCI B:D:S:F: %s, device_id:0x%x,"
  350. "vendor_id:0x%x, sub_device:0x%x, sub_vendor:0x%x,"
  351. "vram_total: %lu, sys_memory: %lu",
  352. __FUNCTION__, dci.dbsf.c_str(), dci.device_id, dci.vendor_id, dci.subsystem_device,
  353. dci.subsystem_vendor, dci.dedicated_vram_total, dci.shared_system_memory_total);
  354. } else {
  355. blog(LOG_DEBUG, "%s: Failed to read symlink for %s", __FUNCTION__, drm_path.c_str());
  356. }
  357. }
  358. }
  359. os_closedir(dir);
  360. }
  361. /* system_gpu_data() returns a sorted vector of GoLiveApi::Gpu
  362. * objects needed to build the multitrack video request.
  363. *
  364. * When a single GPU is installed the case is trivial. In systems
  365. * with multiple GPUs (including the CPU iGPU), the GPU that
  366. * OBS is currently using must be placed first in the list, so
  367. * that composition_gpu_index=0 is valid. composition_gpu_index
  368. * is set to to ovi.adapter which is always 0 apparently.
  369. *
  370. * system_gpu_data() does the following:
  371. * 1. Gather information about the GPU being used by OBS
  372. * 2. Scan the PCIe bus to identify all GPUs
  373. * 3. Find a best match of the GPU in-use in the list found in 2
  374. * 4. Generate the sorted list of GoLiveAPI::Gpu entries
  375. *
  376. * Note that the PCIe device_id and vendor_id are not available
  377. * via OpenGL calls, hence the need to match the in-use GPU with
  378. * the libpci scanned results and extract the PCIe information.
  379. */
  380. std::optional<std::vector<GoLiveApi::Gpu>> system_gpu_data()
  381. {
  382. std::vector<GoLiveApi::Gpu> adapter_info;
  383. GoLiveApi::Gpu gpu;
  384. std::string gs_driver_version;
  385. std::string gs_device_renderer;
  386. uint64_t dedicated_video_memory = 0;
  387. uint64_t shared_system_memory = 0;
  388. std::vector<drm_card_info> drm_cards;
  389. struct pci_access *pacc;
  390. pacc = pci_alloc();
  391. pci_init(pacc);
  392. char slot[256];
  393. // Obtain GPU information by querying graphics API
  394. obs_enter_graphics();
  395. gs_driver_version = gs_get_driver_version();
  396. gs_device_renderer = gs_get_renderer();
  397. dedicated_video_memory = gs_get_gpu_dmem() * 1024;
  398. shared_system_memory = gs_get_gpu_smem() * 1024;
  399. obs_leave_graphics();
  400. /* Split the GPU renderer string into tokens with
  401. * a regex of one or more white-space characters.
  402. */
  403. std::regex ws_reg("[\\s]+");
  404. vector<std::string> gpu_tokens(sregex_token_iterator(gs_device_renderer.begin(), gs_device_renderer.end(),
  405. ws_reg, -1),
  406. sregex_token_iterator());
  407. // Remove extraneous characters from the tokens
  408. constexpr std::string_view EXTRA_CHARS = ",()[]{}";
  409. for (auto token = begin(gpu_tokens); token != end(gpu_tokens); ++token) {
  410. for (unsigned int i = 0; i < EXTRA_CHARS.size(); ++i) {
  411. token->erase(std::remove(token->begin(), token->end(), EXTRA_CHARS[i]), token->end());
  412. }
  413. // Convert tokens to lower-case
  414. std::transform(token->begin(), token->end(), token->begin(), ::tolower);
  415. blog(LOG_DEBUG, "%s: gpu_token: '%s'", __FUNCTION__, token->c_str());
  416. }
  417. // Scan the PCI bus once
  418. pci_scan_bus(pacc);
  419. // Discover the set of DRM render-capable GPUs
  420. get_drm_cards(drm_cards);
  421. blog(LOG_DEBUG, "Number of GPUs detected: %lu", drm_cards.size());
  422. // Iterate through drm_cards to get the device and vendor names via libpci
  423. for (auto card = begin(drm_cards); card != end(drm_cards); ++card) {
  424. struct pci_dev *pdev;
  425. struct pci_filter pfilter;
  426. char namebuf[1024];
  427. /* Get around the 'const char*' vs 'char*'
  428. * type issue with pci_filter_parse_slot().
  429. */
  430. strncpy(slot, card->dbsf.c_str(), sizeof(slot));
  431. // Validate the "slot" string according to libpci
  432. pci_filter_init(pacc, &pfilter);
  433. if (pci_filter_parse_slot(&pfilter, slot)) {
  434. blog(LOG_DEBUG, "%s: pci_filter_parse_slot() failed", __FUNCTION__);
  435. continue;
  436. }
  437. // Get the device name and vendor name from libpci
  438. for (pdev = pacc->devices; pdev; pdev = pdev->next) {
  439. if (pci_filter_match(&pfilter, pdev)) {
  440. pci_fill_info(pdev, PCI_FILL_IDENT);
  441. card->device_name =
  442. pci_lookup_name(pacc, namebuf, sizeof(namebuf),
  443. PCI_LOOKUP_DEVICE | PCI_LOOKUP_CACHE | PCI_LOOKUP_NETWORK,
  444. pdev->vendor_id, pdev->device_id);
  445. card->vendor_name =
  446. pci_lookup_name(pacc, namebuf, sizeof(namebuf),
  447. PCI_LOOKUP_VENDOR | PCI_LOOKUP_CACHE | PCI_LOOKUP_NETWORK,
  448. pdev->vendor_id, pdev->device_id);
  449. blog(LOG_DEBUG, "libpci lookup: device_name: %s, vendor_name: %s",
  450. card->device_name.c_str(), card->vendor_name.c_str());
  451. break;
  452. }
  453. }
  454. }
  455. pci_cleanup(pacc);
  456. /* Iterate through drm_cards to determine a string match count
  457. * against the GPU string tokens from the OpenGL identification.
  458. */
  459. for (auto card = begin(drm_cards); card != end(drm_cards); ++card) {
  460. card->match_count = 0;
  461. for (auto token = begin(gpu_tokens); token != end(gpu_tokens); ++token) {
  462. std::string card_device_name = card->device_name;
  463. std::string card_gpu_vendor = card->vendor_name;
  464. std::transform(card_device_name.begin(), card_device_name.end(), card_device_name.begin(),
  465. ::tolower);
  466. std::transform(card_gpu_vendor.begin(), card_gpu_vendor.end(), card_gpu_vendor.begin(),
  467. ::tolower);
  468. // Compare GPU string tokens to PCI device name
  469. std::size_t found = card_device_name.find(*token);
  470. if (found != std::string::npos) {
  471. card->match_count++;
  472. blog(LOG_DEBUG, "Found %s in PCI device name", (*token).c_str());
  473. }
  474. // Compare GPU string tokens to PCI vendor name
  475. found = card_gpu_vendor.find(*token);
  476. if (found != std::string::npos) {
  477. card->match_count++;
  478. blog(LOG_DEBUG, "Found %s in PCI vendor name", (*token).c_str());
  479. }
  480. }
  481. }
  482. /* Sort the cards based on the highest match strength.
  483. * In the case of multiple cards and the first one is not a higher
  484. * match, there is ambiguity and all we can do is log a warning.
  485. * The chance of this happening is low, but not impossible.
  486. */
  487. std::sort(drm_cards.begin(), drm_cards.end(), compare_match_strength);
  488. if ((drm_cards.size() > 1) && (std::next(begin(drm_cards))->match_count) >= begin(drm_cards)->match_count) {
  489. blog(LOG_WARNING, "%s: Ambiguous GPU association. Possible incorrect sort order.", __FUNCTION__);
  490. for (auto card = begin(drm_cards); card != end(drm_cards); ++card) {
  491. blog(LOG_DEBUG, "Total matches for card %s: %u", card->device_name.c_str(), card->match_count);
  492. }
  493. }
  494. /* Iterate through the sorted list of cards and generate
  495. * the GoLiveApi GPU list.
  496. */
  497. for (auto card = begin(drm_cards); card != end(drm_cards); ++card) {
  498. gpu.device_id = card->device_id;
  499. gpu.vendor_id = card->vendor_id;
  500. gpu.model = card->device_name;
  501. adjust_gpu_model(gpu.model);
  502. if (card == begin(drm_cards)) {
  503. /* The first card in the list corresponds to the
  504. * driver version and GPU memory information obtained
  505. * previously by OpenGL calls into the GPU.
  506. */
  507. gpu.driver_version = gs_driver_version;
  508. gpu.dedicated_video_memory = dedicated_video_memory;
  509. gpu.shared_system_memory = shared_system_memory;
  510. } else {
  511. /* The driver version for the other device(s)
  512. * is not accessible easily in a common location.
  513. */
  514. gpu.driver_version = "Unknown";
  515. /* Use the GPU memory info discovered with get_drm_card_info()
  516. * stored in drm_cards. amdgpu driver exposes the GPU memory
  517. * info via sysfs, NVIDIA does not.
  518. */
  519. gpu.dedicated_video_memory = card->dedicated_vram_total;
  520. gpu.shared_system_memory = card->shared_system_memory_total;
  521. }
  522. adapter_info.push_back(gpu);
  523. }
  524. return adapter_info;
  525. }
  526. } // namespace
  527. void system_info(GoLiveApi::Capabilities &capabilities)
  528. {
  529. // Determine the GPU capabilities
  530. capabilities.gpu = system_gpu_data();
  531. // Determine the CPU capabilities
  532. {
  533. auto &cpu_data = capabilities.cpu;
  534. cpu_data.physical_cores = os_get_physical_cores();
  535. cpu_data.logical_cores = os_get_logical_cores();
  536. if (!get_cpu_name(cpu_data.name)) {
  537. cpu_data.name = "Unknown";
  538. }
  539. uint32_t cpu_freq;
  540. if (get_cpu_freq(cpu_freq)) {
  541. cpu_data.speed = cpu_freq;
  542. } else {
  543. cpu_data.speed = 0;
  544. }
  545. }
  546. // Determine the memory capabilities
  547. {
  548. auto &memory_data = capabilities.memory;
  549. memory_data.total = os_get_sys_total_size();
  550. memory_data.free = os_get_sys_free_size();
  551. }
  552. // Reporting of gaming features not supported on Linux
  553. UNUSED_PARAMETER(capabilities.gaming_features);
  554. // Determine the system capabilities
  555. {
  556. auto &system_data = capabilities.system;
  557. if (!get_distribution_info(system_data.name, system_data.release)) {
  558. system_data.name = "Linux-based distribution";
  559. system_data.release = "unknown";
  560. }
  561. struct utsname utsinfo;
  562. static const uint16_t max_sys_data_version_sz = 128;
  563. if (uname(&utsinfo) == 0) {
  564. /* To determine if the host is 64-bit, check if the machine
  565. * name contains "64", as in "x86_64" or "aarch64".
  566. */
  567. system_data.bits = strstr(utsinfo.machine, "64") ? 64 : 32;
  568. /* To determine if the host CPU is ARM based, check if the
  569. * machine name contains "aarch".
  570. */
  571. system_data.arm = strstr(utsinfo.machine, "aarch") ? true : false;
  572. /* Send the sysname (usually "Linux"), kernel version and
  573. * release reported by utsname as the version string.
  574. * The code below will produce something like:
  575. *
  576. * "Linux 6.5.0-41-generic #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC
  577. * Mon Jun 3 11:32:55 UTC 2"
  578. */
  579. system_data.version = utsinfo.sysname;
  580. system_data.version.append(" ");
  581. system_data.version.append(utsinfo.release);
  582. system_data.version.append(" ");
  583. system_data.version.append(utsinfo.version);
  584. // Ensure system_data.version string is within the maximum size
  585. if (system_data.version.size() > max_sys_data_version_sz) {
  586. system_data.version.resize(max_sys_data_version_sz);
  587. }
  588. } else {
  589. UNUSED_PARAMETER(system_data.bits);
  590. UNUSED_PARAMETER(system_data.arm);
  591. system_data.version = "unknown";
  592. }
  593. // On Linux-based distros, there's no build or revision info
  594. UNUSED_PARAMETER(system_data.build);
  595. UNUSED_PARAMETER(system_data.revision);
  596. system_data.armEmulation = os_get_emulation_status();
  597. }
  598. }