После того как мой пет-проект случайно «выстрелил» на Reddit, я решил, что пора поделиться так же и своей кроссплатформенной библиотекой на C++20 для мониторинга железа, с нативной поддержкой Python.Рассказываю историю создания hardware_monitor_cpp: как избавиться от лишних зависимостей, получить адекватную статистику swap-файла под Windows и macOS, упаковать все в удобное C++ API, а заодно получить готовую консольную утилиту с визуализацией графиков прямо в терминале. Читать далее
Уровень сложностиПростой
Время на прочтение7 мин
Охват и читатели11K
Обзор
Всем привет!
Предыстория (можете пропустить)Долго думал, писать или не писать статью про мою библиотеку, которой активно пользуюсь и тестирую уже который месяц.
Но недавно обнаружил, что «очередное решение несуществующей проблемы» в виде моего личного 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, который выдаёт эту информацию в сеть.

Скрины из консольного приложения на MacOS

Внешний вид UI питоновского сервера
Несмотря на то, что они могут не покрыть ваши нужды, приложение и сервер — хорошая точка старта. Потом с помощью молотка и напильника можно получить то, что бы вам хотелось. Ну и да, сборка питоновского сервера сразу оборачивает биндинги библиотеки в питоновское расширение, так что можно будет использовать бинарь и в других местах.
Билдится всё очень просто. Всё, что нужно — установленные 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.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Написание ядра ОС с нуля. Часть 3 — Пропуск в высшую лигу | 0 | 8.66 | 23-08-2026 |
| 2 | Я написал верификатор eBPF для микроконтроллера. А потом ядро Linux объяснило мне, где я неправ | 0 | 9.18 | 23-09-2026 |
| 3 | Как на ровном месте сэкономить 1000+ ядер, или Куда на самом деле уходили 80% CPU | 0 | 9.12 | 08-09-2026 |
| 4 | EvertyDisplay: что я сделал и зачем | 0 | 6.54 | 14-09-2026 |
| 5 | C++: пишем свою std::function | 0 | 7.06 | 01-10-2026 |
| 6 | Ускорение Python-сервиса с CinderX: JIT и статическая типизация | 0 | 6.86 | 21-09-2026 |
| 7 | hw-monitor 0.6 Released With Per-Process GPU Usage & Memory Reporting | 0 | 16.72 | 29-09-2026 |
| 8 | Как на ровном месте сэкономить 1000+ ядер, или Куда на самом деле уходили 80% CPU | 0 | 9.12 | 08-09-2026 |
| 9 | Хороший код, минусов нет: встреча «плюсовиков» YADRO и C++ Russia | 0 | 9.15 | 21-08-2026 |
| 10 | C++29 — начало. Встреча ISO C++ в Брно | 0 | 7.99 | 17-08-2026 |