From d221a0a44913d2cb60d15ea0d70cf256144a182e Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 12 Mar 2026 17:57:16 +0500 Subject: [PATCH] feat: print hardware information in launch log Signed-off-by: Octol1ttle --- .../setup-dependencies/linux/action.yml | 2 +- launcher/Application.cpp | 2 +- launcher/CMakeLists.txt | 2 + launcher/HardwareInfo.cpp | 321 ++++++++++++++++++ launcher/HardwareInfo.h | 29 ++ launcher/SysInfo.cpp | 68 +--- launcher/SysInfo.h | 6 +- .../minecraft/launch/PrintInstanceInfo.cpp | 76 +---- .../flame/FlameInstanceCreationTask.cpp | 3 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 5 +- launcher/ui/widgets/JavaWizardWidget.cpp | 3 +- vcpkg.json | 3 +- 12 files changed, 379 insertions(+), 141 deletions(-) create mode 100644 launcher/HardwareInfo.cpp create mode 100644 launcher/HardwareInfo.h diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index fa5af702b..fe7ee2142 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -13,7 +13,7 @@ runs: dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ - libxcb-cursor-dev libtomlplusplus-dev + libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev - name: Setup AppImage tooling shell: bash diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 001795154..21bb43d3a 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -730,7 +730,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); - m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem()); + m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem()); m_settings->registerSetting("PermGen", 128); // Java Settings diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 133016978..acecf8700 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -800,6 +800,8 @@ SET(LAUNCHER_SOURCES ApplicationMessage.cpp SysInfo.h SysInfo.cpp + HardwareInfo.cpp + HardwareInfo.h # console utils console/Console.h diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp new file mode 100644 index 000000000..1d4650ba9 --- /dev/null +++ b/launcher/HardwareInfo.cpp @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2026 Octol1ttle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "HardwareInfo.h" + +#include +#include +#include + +#ifndef Q_OS_MACOS +#include +#include +#endif + +namespace { +bool vulkanInfo(QStringList& out) +{ +#ifndef Q_OS_MACOS + QVulkanInstance inst; + if (!inst.create()) { + qWarning() << "Vulkan instance creation failed, VkResult:" << inst.errorCode(); + out << "Couldn't get Vulkan device information"; + return false; + } + + QVulkanWindow window; + window.setVulkanInstance(&inst); + + for (auto device : window.availablePhysicalDevices()) { + const auto supportedVulkanVersion = QVersionNumber(VK_API_VERSION_MAJOR(device.apiVersion), VK_API_VERSION_MINOR(device.apiVersion), + VK_API_VERSION_PATCH(device.apiVersion)); + out << QString("Found Vulkan device: %1 (API version %2)").arg(device.deviceName).arg(supportedVulkanVersion.toString()); + } +#endif + + return true; +} + +bool openGlInfo(QStringList& out) +{ + QOpenGLContext ctx; + if (!ctx.create()) { + qWarning() << "OpenGL context creation failed"; + out << "Couldn't get OpenGL device information"; + return false; + } + + QOffscreenSurface surface; + surface.create(); + ctx.makeCurrent(&surface); + + auto* f = ctx.functions(); + f->initializeOpenGLFunctions(); + + auto toQString = [](const GLubyte* str) { return QString(reinterpret_cast(str)); }; + out << "OpenGL driver vendor: " + toQString(f->glGetString(GL_VENDOR)); + out << "OpenGL renderer: " + toQString(f->glGetString(GL_RENDERER)); + out << "OpenGL driver version: " + toQString(f->glGetString(GL_VERSION)); + + return true; +} +} // namespace + +#ifndef Q_OS_LINUX +QStringList HardwareInfo::gpuInfo() +{ + QStringList info; + vulkanInfo(info); + openGlInfo(info); + return info; +} +#endif + +#ifdef Q_OS_WINDOWS +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include "windows.h" + +QString HardwareInfo::cpuInfo() +{ + const QSettings registry(R"(HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0)", QSettings::NativeFormat); + return registry.value("ProcessorNameString").toString(); +} + +uint64_t HardwareInfo::totalRamMiB() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof status; + + if (GlobalMemoryStatusEx(&status) == TRUE) { + // transforming bytes -> mib + return status.ullTotalPhys / 1024 / 1024; + } + + qWarning() << "Could not get total RAM: GlobalMemoryStatusEx"; + return 0; +} + +uint64_t HardwareInfo::availableRamMiB() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof status; + + if (GlobalMemoryStatusEx(&status) == TRUE) { + // transforming bytes -> mib + return status.ullAvailPhys / 1024 / 1024; + } + + qWarning() << "Could not get available RAM: GlobalMemoryStatusEx"; + return 0; +} + +#elif defined(Q_OS_MACOS) +#include "mach/mach.h" +#include "sys/sysctl.h" + +QString HardwareInfo::cpuInfo() +{ + std::array buffer; + size_t bufferSize = buffer.size(); + if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) { + return QString(buffer.data()); + } + + qWarning() << "Could not get CPU model: sysctlbyname"; + return ""; +} + +uint64_t HardwareInfo::totalRamMiB() +{ + uint64_t memsize; + size_t memsizeSize = sizeof memsize; + if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) { + // transforming bytes -> mib + return memsize / 1024 / 1024; + } + + qWarning() << "Could not get total RAM: sysctlbyname"; + return 0; +} + +uint64_t HardwareInfo::availableRamMiB() +{ + mach_port_t host_port = mach_host_self(); + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + + vm_statistics64_data_t vm_stats; + + if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast(&vm_stats), &count) == KERN_SUCCESS) { + // transforming bytes -> mib + return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024; + } + + qWarning() << "Could not get available RAM: host_statistics64"; + return 0; +} + +#elif defined(Q_OS_LINUX) +#include + +namespace { +QString afterColon(QString& str) +{ + return str.remove(0, str.indexOf(':') + 2).trimmed(); +} +} // namespace + +QString HardwareInfo::cpuInfo() +{ + std::ifstream cpuin("/proc/cpuinfo"); + for (std::string line; std::getline(cpuin, line);) { + // model name : AMD Ryzen 7 5800X 8-Core Processor + if (QString str = QString::fromStdString(line); str.startsWith("model name")) { + return afterColon(str); + } + } + + qWarning() << "Could not get CPU model: /proc/cpuinfo"; + return "unknown"; +} + +uint64_t readMemInfo(QString searchTarget) +{ + std::ifstream memin("/proc/meminfo"); + for (std::string line; std::getline(memin, line);) { + // MemTotal: 16287480 kB + if (QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { + bool ok = false; + const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok); + if (!ok) { + qWarning() << "Could not read /proc/meminfo: failed to parse string:" << str; + return 0; + } + + // transforming kib -> mib + return total / 1024; + } + } + + qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget; + return 0; +} + +uint64_t HardwareInfo::totalRamMiB() +{ + return readMemInfo("MemTotal"); +} + +uint64_t HardwareInfo::availableRamMiB() +{ + return readMemInfo("MemAvailable"); +} + +QStringList HardwareInfo::gpuInfo() +{ + QStringList list; + const bool vulkanSuccess = vulkanInfo(list); + const bool openGlSuccess = openGlInfo(list); + if (vulkanSuccess || openGlSuccess) { + return list; + } + + std::array buffer; + FILE* lspci = popen("lspci -k", "r"); + + if (!lspci) { + return { "Could not detect GPUs: lspci is not present" }; + } + + bool readingGpuInfo = false; + QString currentModel = ""; + while (fgets(buffer.data(), 512, lspci) != nullptr) { + QString str(buffer.data()); + // clang-format off + // 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7) + // Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB + // Kernel driver in use: amdgpu + // Kernel modules: amdgpu + // clang-format on + if (str.contains("VGA compatible controller")) { + readingGpuInfo = true; + } else if (!str.startsWith('\t')) { + readingGpuInfo = false; + } + if (!readingGpuInfo) { + continue; + } + + if (str.contains("Subsystem")) { + currentModel = "Found GPU: " + afterColon(str); + } + if (str.contains("Kernel driver in use")) { + currentModel += " (using driver " + afterColon(str); + } + if (str.contains("Kernel modules")) { + currentModel += ", available drivers: " + afterColon(str) + ")"; + list.append(currentModel); + } + } + pclose(lspci); + return list; +} + +#else + +QString HardwareInfo::cpuInfo() +{ + return "unknown"; +} + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +#include + +uint64_t HardwareInfo::totalRamMiB() +{ + char buff[512]; + FILE* fp = popen("sysctl hw.physmem", "r"); + if (fp != nullptr) { + if (fgets(buff, 512, fp) != nullptr) { + std::string str(buff); + uint64_t mem = std::stoull(str.substr(12, std::string::npos)); + + // transforming kib -> mib + return mem / 1024; + } + } + + return 0; +} + +#else +uint64_t HardwareInfo::totalRamMiB() +{ + return 0; +} +#endif + +uint64_t HardwareInfo::availableRamMiB() +{ + return 0; +} + +#endif diff --git a/launcher/HardwareInfo.h b/launcher/HardwareInfo.h new file mode 100644 index 000000000..00e19f214 --- /dev/null +++ b/launcher/HardwareInfo.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2026 Octol1ttle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +namespace HardwareInfo { +QString cpuInfo(); +uint64_t totalRamMiB(); +uint64_t availableRamMiB(); +QStringList gpuInfo(); +} // namespace HardwareInfo diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index 0e2eb2ff7..d02b1d854 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -37,26 +37,13 @@ * limitations under the License. */ -#include -#include -#include -#include #include -#if defined(Q_OS_WINDOWS) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#elif defined(Q_OS_LINUX) -#include -#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) -#include -#elif defined(Q_OS_APPLE) -#include -#endif +#include "HardwareInfo.h" #ifdef Q_OS_MACOS +#include + bool rosettaDetect() { int ret = 0; @@ -98,55 +85,10 @@ QString useQTForArch() return QSysInfo::currentCpuArchitecture(); } -uint64_t getSystemRamMiB() +int defaultMaxJvmMem() { -#if defined(Q_OS_WINDOWS) - MEMORYSTATUSEX status; - status.dwLength = sizeof status; - - if (GlobalMemoryStatusEx(&status)) { - // transforming bytes -> mib - return (uint64_t)status.ullTotalPhys / 1024 / 1024; - } -#elif defined(Q_OS_LINUX) - struct sysinfo info; - - if (sysinfo(&info) != -1) { - // transforming bytes -> mib - return info.totalram / 1024 / 1024; - } -#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - char buff[512]; - FILE* fp = popen("sysctl hw.physmem", "r"); - if (fp != nullptr) { - if (fgets(buff, 512, fp) != nullptr) { - std::string str(buff); - uint64_t mem = std::stoull(str.substr(12, std::string::npos)); - - // transforming kib -> mib - return mem / 1024; - } - } -#elif defined(Q_OS_APPLE) - uint64_t memsize; - size_t memsizesize = sizeof memsize; - - if (!sysctlbyname("hw.memsize", &memsize, &memsizesize, nullptr, 0)) { - // transforming bytes -> mib - return memsize / 1024 / 1024; - } -#elif defined(__GNUC__) || defined(__clang__) -#warning getSystemRam not implemented on this platform; detecting amount of installed RAM will not work -#endif - return 0; -} - -int suitableMaxMem() -{ - int totalRAM = getSystemRamMiB(); - // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB - if (totalRAM < (4096 * 1.5)) + if (const uint64_t totalRAM = HardwareInfo::totalRamMiB(); totalRAM < (4096 * 1.5)) return totalRAM / 1.5; else return 4096; diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 83c5ab476..da23c5be1 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -8,9 +8,5 @@ namespace SysInfo { QString currentSystem(); QString useQTForArch(); QString getSupportedJavaArchitecture(); -/** - * @return Total system memory in mebibytes, or 0 if it could not be determined. - */ -uint64_t getSystemRamMiB(); -int suitableMaxMem(); +int defaultMaxJvmMem(); } // namespace SysInfo diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index e44d09839..d837f5faf 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -19,49 +19,10 @@ #include #include "PrintInstanceInfo.h" -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) +#include "HardwareInfo.h" + +#if defined(Q_OS_FREEBSD) namespace { -#if defined(Q_OS_LINUX) -void probeProcCpuinfo(QStringList& log) -{ - std::ifstream cpuin("/proc/cpuinfo"); - for (std::string line; std::getline(cpuin, line);) { - if (strncmp(line.c_str(), "model name", 10) == 0) { - log << QString::fromStdString(line.substr(13, std::string::npos)); - break; - } - } -} - -void runLspci(QStringList& log) -{ - // FIXME: fixed size buffers... - char buff[512]; - int gpuline = -1; - int cline = 0; - FILE* lspci = popen("lspci -k", "r"); - - if (!lspci) - return; - - while (fgets(buff, 512, lspci) != NULL) { - std::string str(buff); - if (str.length() < 9) - continue; - if (str.substr(8, 3) == "VGA") { - gpuline = cline; - log << QString::fromStdString(str.substr(35, std::string::npos)); - } - if (gpuline > -1 && gpuline != cline) { - if (cline - gpuline < 3) { - log << QString::fromStdString(str.substr(1, std::string::npos)); - } - } - cline++; - } - pclose(lspci); -} -#elif defined(Q_OS_FREEBSD) void runSysctlHwModel(QStringList& log) { char buff[512]; @@ -92,24 +53,6 @@ void runPciconf(QStringList& log) } pclose(pciconf); } -#endif -void runGlxinfo(QStringList& log) -{ - // FIXME: fixed size buffers... - char buff[512]; - FILE* glxinfo = popen("glxinfo", "r"); - if (!glxinfo) - return; - - while (fgets(buff, 512, glxinfo) != NULL) { - if (strncmp(buff, "OpenGL version string:", 22) == 0) { - log << QString::fromUtf8(buff); - break; - } - } - pclose(glxinfo); -} - } // namespace #endif @@ -118,15 +61,16 @@ void PrintInstanceInfo::executeTask() auto instance = m_parent->instance(); QStringList log; -#if defined(Q_OS_LINUX) - ::probeProcCpuinfo(log); - ::runLspci(log); - ::runGlxinfo(log); -#elif defined(Q_OS_FREEBSD) + log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion()); +#ifdef Q_OS_FREEBSD ::runSysctlHwModel(log); ::runPciconf(log); - ::runGlxinfo(log); +#else + log << "CPU: " + HardwareInfo::cpuInfo(); + log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB()); #endif + log.append(HardwareInfo::gpuInfo()); + log << ""; logLines(log, MessageLevel::Launcher); logLines(instance->verboseDescription(m_session, m_targetToJoin), MessageLevel::Launcher); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 66220fddd..7583eb110 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -63,6 +63,7 @@ #include #include +#include "HardwareInfo.h" #include "meta/Index.h" #include "minecraft/World.h" #include "minecraft/mod/tasks/LocalResourceParse.h" @@ -413,7 +414,7 @@ std::unique_ptr FlameCreationTask::createInstance() // only set memory if this is a fresh instance if (m_instance == nullptr && recommendedRAM > 0) { - const uint64_t sysMiB = SysInfo::getSystemRamMiB(); + const uint64_t sysMiB = HardwareInfo::totalRamMiB(); const uint64_t max = sysMiB * 0.9; if (static_cast(recommendedRAM) > max) { diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index ca51de683..4e74c610f 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -42,11 +42,12 @@ #include "Application.h" #include "BuildConfig.h" #include "FileSystem.h" +#include "HardwareInfo.h" #include "JavaCommon.h" +#include "SysInfo.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "settings/Setting.h" -#include "SysInfo.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/VersionSelectDialog.h" #include "ui/java/InstallJavaDialog.h" @@ -285,7 +286,7 @@ void JavaSettingsWidget::onJavaAutodetect() } void JavaSettingsWidget::updateThresholds() { - auto sysMiB = SysInfo::getSystemRamMiB(); + auto sysMiB = HardwareInfo::totalRamMiB(); unsigned int maxMem = m_ui->maxMemSpinBox->value(); unsigned int minMem = m_ui->minMemSpinBox->value(); diff --git a/launcher/ui/widgets/JavaWizardWidget.cpp b/launcher/ui/widgets/JavaWizardWidget.cpp index 086ca9481..bcf498b6d 100644 --- a/launcher/ui/widgets/JavaWizardWidget.cpp +++ b/launcher/ui/widgets/JavaWizardWidget.cpp @@ -27,10 +27,11 @@ #include "Application.h" #include "BuildConfig.h" +#include "HardwareInfo.h" JavaWizardWidget::JavaWizardWidget(QWidget* parent) : QWidget(parent) { - m_availableMemory = SysInfo::getSystemRamMiB(); + m_availableMemory = HardwareInfo::totalRamMiB(); goodIcon = QIcon::fromTheme("status-good"); yellowIcon = QIcon::fromTheme("status-yellow"); diff --git a/vcpkg.json b/vcpkg.json index 942e6d9e4..5fd336fff 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,6 +26,7 @@ ] }, "tomlplusplus", - "zlib" + "zlib", + "vulkan-headers" ] }