Всем привет!
Предыстория (можете пропустить)
Долго думал, писать или не писать статью про мою библиотеку, которой активно пользуюсь и тестирую уже который месяц.
Но недавно обнаружил, что «очередное решение несуществующей проблемы» в виде моего личного Android порта Blender 5.3.0 Alpha, ушло в массы и начало жить своей жизнью, а люди на Reddit выкладывают видосы с тем, как они работают на своих Android планшетах. Причём, мой оригинальный пост собрал еле‑еле 3 стрелочки вверх на Reddit, а пост с видосами да и форк моего репозитория получили дикие отклики по меркам их сабреддитов.
Потому решил, таки рассказать, про самописные вещи, которые лежат в опенсорсе и которыми я активно пользуюсь.
Вступление
Гуглил я как‑то опенсорс C++ библиотеку для мониторинга, температуры и нагрузки компонентов ПК. И не нагуглил. Вот такие вот дела. Есть HWiNFO, платно и закрыто. Есть OpenHardwareMonitor — написан C# и библиотека мертва. Есть форк предыдущей библиотеки — LibreHardwareMonitor — живее всех живых, но C#, да ещё и по дефолту требует 10.NET.
Зачем нужен C++, когда можно подцепить DLL из C# напрямую? Ну вот не хочу я таскать.NET определенной версии с собой из бандла в бандл. AOT компиляция вообще не поможет, тут как раз то самое использование специфических вещей, которые AOT не компилит. Finita la comedia! А ещё есть один косяк у последней библиотеки: мака нет в списке поддержки от слова совсем.
“Одно кольцо, чтоб править всеми” Windows, Linux, MacOS — x64, arm64. Вот тут я и решил сесть и написать её. В очередной раз. Тем более что меня уволили и времени теперь вагон, можно потрогать все свои пэт проджекты. Тем более что есть референс в виде LibreHardwareMonitor.
Правда то, что он есть, хорошо и плохо одновременно. Нельзя сделать, как у них, потому что «лицензия, детка»! Мне их лицензия не нравится. Надо избавлятся, делать надо не так.
Встречайте HardwareMonitorCpp (hardware_monitor_cpp)!
Пытался я получить температуру процессора без повышения привилегий. Гайки, никак. One Ring0 to rule them all! Ну, на винде и макоси, по крайней мере, Ring0 обязателен, Linux может и без этого.
Остальное работает и без sudo. Что такое остальное? Общая информация о железе: как железка называется, сколько у неё частота, память занятая/свободная, текущая температура и текущая нагрузка. Полноценный мониторинг, одним словом. Понятно, что у процессора нет занятой свободной памяти, а у SSD нет текущей частоты, но есть количество активного времени и IO Bound.
Чем классная библиотека?
Ну, во‑первых, к ней сразу идёт Python сервер, которые все эти данные выдает в сеть! Так же есть консольная приложуха, которая выдаёт эти данные визуально в консоль (со своими собственными настройками, котрые сохраняются между запусками). Писать 100 500 утилит не стал. Я пользуюсь только консольной приложухой на винде и маке. Очень нравится, легким движением руки переключаюсь между нагрузкой и температурой (кнопка «t»), а таймскейл на колонку меняю с 1 секунды на 5 минут (кнопки «‑» и «=»). Очень удобно, особенно, чтобы прикинуть визуально, сколько ещё проекту собираться, и сколько он собирался в прошлый раз. И сколько он выжрал оперативы, например.
А ещё на Windows и MacOS наконец‑то есть внятный график и размер файла подкачки. Я сколько не смотрел в TaskManager и ActivityMonitor, так надо найти 2 числа и дальше математикой заниматься. Не круто! Тут сразу видно, что к чему.
Все написано на C++. Потому что я плюсовик и я могу! Консольное приложение в ходе разработки раздулось с 7 мб оперативы до 30 мб на винде. Да и только потому, что я решил больше снапшотов системы хранить в истории отображения.
Из плюшек библиотеки: можно запросить по всему оборудованию только нагрузку или только температуру.
Из минусов: для Windows нужно отдельно ставить PawnIO для цифер процессора, но это нормально. LibreHardwareMontior тоже драйвер таскает и запускатет от имени администратора, для того же самого. Судьба такая. HWiNFO тоже, вот, не просто так просит поднять привелегии.
Что я делать не стал: мониторить температуру материнки и обороты вентилятора. Мне самой температуры железок хватает, чтобы понять, что к чему. А писать код вслепую для 100 500 вендоров не хочется, хоть и есть куда подглядеть.
Консольное приложение и Python сервер
Вместе с самой библиотекой в комплекте идёт консольное приложение и отдельно сервер на Python, который выдаёт эту информацию в сеть.


Несмотря на то, что они могут не покрыть ваши нужды, приложение и сервер — хорошая точка старта. Потом с помощью молотка и напильника можно получить то, что бы вам хотелось. Ну и да, сборка питоновского сервера сразу оборачивает биндинги библиотеки в питоновское расширение, так что можно будет использовать бинарь и в других местах.
Билдится всё очень просто. Всё, что нужно — установленные ninja и cmake, и чтобы они были доступны в Path. Дальше достаточно запустить один из python скриптов сборки. Их три:
python build_lib.py
python build_console.py
python build_server.py
Сборки самодостаточные. После подтягивания саб модулей ничего дополнительно кроме запуска скрипта сборки делать не нужно. В README.md всё подробно описано.
Если впадлу билдить, уже есть готовые сборки релизов (но там не всё).
Примеры использования кода
Вывод температуры ядер:
#include <cstdio> #include "hardware_monitor_cpp/hardware_monitor_cpp.hpp" int main() { using namespace hardware_monitor_cpp; Monitor monitor; monitor.addPlatformSources(); monitor.open(); Snapshot snap = monitor.poll(); for (const DeviceInfo& d : snap.devices()) { if (d.id.kind != DeviceKind::Cpu) continue; std::printf("CPU: %s\n", d.name.c_str()); for (const Reading& r : snap.forDevice(d.id)) { if (r.quantity == Quantity::Temperature) std::printf(" %s: %.1f %s\n", r.channel.c_str(), r.value, unitSymbol(r.unit)); } } return 0; }
Монитор загрузки системных ресурсов:
#include <chrono> #include <cstdio> #include <string> #include <thread> #include <vector> #include "hardware_monitor_cpp/hardware_monitor_cpp.hpp" using namespace hardware_monitor_cpp; static bool isGpu(DeviceKind k) { return k == DeviceKind::GpuIntegrated || k == DeviceKind::GpuDiscrete; } static double toGb(double bytes) { return bytes / (1024.0 * 1024.0 * 1024.0); } static const Reading* find(const std::vector<Reading>& rs, Quantity q, const std::string& channel) { for (const Reading& r : rs) { if (r.quantity == q && r.channel == channel) { return &r; } } return nullptr; } static void printStatus(const Snapshot& snap) { for (const DeviceInfo& d : snap.devices()) { std::vector<Reading> rs = snap.forDevice(d.id); if (d.id.kind == DeviceKind::Cpu) { if (const Reading* load = find(rs, Quantity::Load, "Total")) { std::printf("[CPU] load: %.1f %%\n", load->value); } } else if (isGpu(d.id.kind)) { std::printf("[GPU] %s:\n", d.name.c_str()); for (const Reading& r : rs) { if (r.quantity == Quantity::Temperature) { std::printf(" - temperature: %.1f %s\n", r.value, unitSymbol(r.unit)); } else if (r.quantity == Quantity::Load && r.channel == "Core") { std::printf(" - core load: %.1f %%\n", r.value); } } } else if (d.id.kind == DeviceKind::Memory) { std::printf("[RAM] %s:\n", d.name.c_str()); if (const Reading* usage = find(rs, Quantity::Load, "Usage")) { std::printf(" - usage: %.1f %%\n", usage->value); } const Reading* used = find(rs, Quantity::DataVolume, "Used"); const Reading* total = find(rs, Quantity::DataVolume, "Total"); if (used && total) { std::printf(" - used: %.2f / %.2f GB\n", toGb(used->value), toGb(total->value)); } if (const Reading* swap = find(rs, Quantity::DataVolume, "Swap Used")) { std::printf(" - swap used: %.2f GB\n", toGb(swap->value)); } } else if (d.id.kind == DeviceKind::Storage) { std::printf("[SSD] %s:\n", d.name.c_str()); for (const Reading& r : rs) { if (r.quantity == Quantity::Load && r.channel == "Activity") { std::printf(" - activity: %.1f %%\n", r.value); } else if (r.quantity == Quantity::Level && r.channel == "Used") { std::printf(" - space used: %.1f %%\n", r.value); } else if (r.quantity == Quantity::Temperature) { std::printf(" - temperature: %.1f %s\n", r.value, unitSymbol(r.unit)); } else if (r.quantity == Quantity::DataRate) { std::printf(" - %s: %.2f MB/s\n", r.channel.c_str(), r.value / (1024.0 * 1024.0)); } } } } } int main() { Monitor monitor; monitor.addPlatformSources(); monitor.open(); monitor.poll(); // prime the delta-based counters for (int i = 0; i < 3; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::printf("=== sample %d ===\n", i + 1); printStatus(monitor.poll()); } return 0; }
Дамп использования оперативной памяти и размера файла подкачки
#include <cstdio> #include <string> #include <vector> #include "hardware_monitor_cpp/hardware_monitor_cpp.hpp" using namespace hardware_monitor_cpp; static const Reading* find(const std::vector<Reading>& rs, const std::string& channel) { for (const Reading& r : rs) if (r.channel == channel) return &r; return nullptr; } int main() { Monitor monitor; monitor.addPlatformSources(); monitor.open(); Snapshot snap = monitor.poll(); for (const DeviceInfo& d : snap.devices()) { if (d.id.kind != DeviceKind::Memory) continue; std::printf("Memory: %s [%s]\n", d.name.c_str(), toString(d.id).c_str()); std::vector<Reading> rs = snap.forDevice(d.id); for (const Reading& r : rs) std::printf(" %-14s %12.2f %s\n", r.channel.c_str(), r.value, unitSymbol(r.unit)); if (const Reading* swap = find(rs, "Swap Used")) std::printf(" swap in use: %.2f GB\n", swap->value / (1024.0 * 1024.0 * 1024.0)); else std::printf(" swap: not reported on this platform\n"); } return 0; }
Спасибо за внимание!
Библиотеку можно скачать здесь https://github.com/simfeo/HardwareMonitorCpp или
git clone https://github.com/simfeo/HardwareMonitorCpp.git cd HardwareMonitorCpp git submodule update --init --recursive
Как и в прошлой статье про другую мою библиотеку, готов к конструктивной критике, и делать апдейты. После отзывов библиотека, кстати, немного подросла, но серьезно улушилась в синтаксисе использования. Если кому интересно, есть статья на медиум, но пока что на английском, так как большая половина критики прилетела с Reddit.