Add network

This commit is contained in:
2026-01-09 21:58:48 +00:00
parent f8e1948059
commit 5b5be7c3c8
24 changed files with 482 additions and 114 deletions
+1
View File
@@ -8,5 +8,6 @@ namespace config::client
struct CollectorConfig struct CollectorConfig
{ {
std::vector<std::vector<std::string>> disks; std::vector<std::vector<std::string>> disks;
std::vector<std::vector<std::string>> networks;
}; };
} // namespace config::client } // namespace config::client
+8
View File
@@ -19,6 +19,14 @@ namespace config::client
for (const std::string &disk : disks_vec) for (const std::string &disk : disks_vec)
cfg.collector.disks.push_back(config::splitList(disk, ':')); cfg.collector.disks.push_back(config::splitList(disk, ':'));
const std::string networks_line = ini.get("collector", "networks", "");
if (!networks_line.empty())
{
const std::vector<std::string> networks_vec = config::splitList(networks_line, ',');
for (const std::string &network : networks_vec)
cfg.collector.networks.push_back(config::splitList(network, ':'));
}
return cfg; return cfg;
} }
} // namespace config::client } // namespace config::client
+45
View File
@@ -35,6 +35,51 @@ namespace config::server
cfg.barStyle.fill = parseColor( cfg.barStyle.fill = parseColor(
ini.get("bar", "fill", "0,180,0")); ini.get("bar", "fill", "0,180,0"));
cfg.style.gap = std::stoi(ini.get("style", "gap", "2"));
cfg.style.header.width = std::stoi(ini.get("style", "header.width", "240"));
cfg.style.header.height = std::stoi(ini.get("style", "header.height", "16"));
cfg.style.header.padding = std::stoi(ini.get("style", "header.padding", "4"));
cfg.style.header.font.name = ini.get("style", "header.font.name", "Pixel10");
cfg.style.header.font.size = std::stoi(ini.get("style", "header.font.size", "14"));
cfg.style.hostblock.width = std::stoi(ini.get("style", "hostblock.width", "118"));
cfg.style.hostblock.height = std::stoi(ini.get("style", "hostblock.height", "150"));
cfg.style.hostblock.padding = std::stoi(ini.get("style", "hostblock.padding", "5"));
cfg.style.hostblock.gap = std::stoi(ini.get("style", "hostblock.gap", "4"));
cfg.style.hostblock.header.height = std::stoi(ini.get("style", "hostblock.header.height", "7"));
cfg.style.hostblock.header.font.name = ini.get("style", "hostblock.header.font.name", "PixelFive-Regular");
cfg.style.hostblock.header.font.size = std::stoi(ini.get("style", "hostblock.header.font.size", "5"));
cfg.style.hostblock.cpu.width = std::stoi(ini.get("style", "hostblock.cpu.width", "8"));
cfg.style.hostblock.cpu.height = std::stoi(ini.get("style", "hostblock.cpu.height", "16"));
cfg.style.hostblock.cpu.gap_h = std::stoi(ini.get("style", "hostblock.cpu.gap_h", "4"));
cfg.style.hostblock.cpu.gap_v = std::stoi(ini.get("style", "hostblock.cpu.gap_v", "4"));
cfg.style.hostblock.cpu.max_per_row = std::stoi(ini.get("style", "hostblock.cpu.max_per_row", "8"));
cfg.style.hostblock.cpu.rows = std::stoi(ini.get("style", "hostblock.cpu.rows", "2"));
cfg.style.hostblock.cpu.temperature.width = std::stoi(ini.get("style", "hostblock.cpu.temperature.width", "3"));
cfg.style.hostblock.cpu.temperature.min = std::stoi(ini.get("style", "hostblock.cpu.temperature.min", "20"));
cfg.style.hostblock.cpu.temperature.max = std::stoi(ini.get("style", "hostblock.cpu.temperature.max", "100"));
cfg.style.hostblock.memory.height = std::stoi(ini.get("style", "hostblock.memory.height", "11"));
cfg.style.hostblock.memory.gap = std::stoi(ini.get("style", "hostblock.memory.gap", "2"));
cfg.style.hostblock.memory.font.name = ini.get("style", "hostblock.memory.font.name", "PixelFive-Regular");
cfg.style.hostblock.memory.font.size = std::stoi(ini.get("style", "hostblock.memory.font.size", "5"));
cfg.style.hostblock.memory.font.padding = std::stoi(ini.get("style", "hostblock.memory.font.padding", "8"));
cfg.style.hostblock.disks.height = std::stoi(ini.get("style", "hostblock.disks.height", "11"));
cfg.style.hostblock.disks.gap = std::stoi(ini.get("style", "hostblock.disks.gap", "2"));
cfg.style.hostblock.disks.font.name = ini.get("style", "hostblock.disks.font.name", "PixelFive-Regular");
cfg.style.hostblock.disks.font.size = std::stoi(ini.get("style", "hostblock.disks.font.size", "5"));
cfg.style.hostblock.disks.font.padding = std::stoi(ini.get("style", "hostblock.disks.font.padding", "8"));
cfg.style.hostblock.networks.height = std::stoi(ini.get("style", "hostblock.networks.height", "11"));
cfg.style.hostblock.networks.gap = std::stoi(ini.get("style", "hostblock.networks.gap", "2"));
cfg.style.hostblock.networks.font.name = ini.get("style", "hostblock.networks.font.name", "PixelFive-Regular");
cfg.style.hostblock.networks.font.size = std::stoi(ini.get("style", "hostblock.networks.font.size", "5"));
cfg.style.hostblock.networks.font.padding = std::stoi(ini.get("style", "hostblock.networks.font.padding", "8"));
return cfg; return cfg;
} }
} // namespace config::server } // namespace config::server
+2
View File
@@ -5,6 +5,7 @@
#include "config/server/DisplayConfig.h" #include "config/server/DisplayConfig.h"
#include "config/server/NetworkConfig.h" #include "config/server/NetworkConfig.h"
#include "config/server/TextStyleConfig.h" #include "config/server/TextStyleConfig.h"
#include "config/server/StyleConfig.h"
namespace config::server namespace config::server
{ {
@@ -17,5 +18,6 @@ namespace config::server
NetworkConfig network; NetworkConfig network;
TextStyleConfig textStyle; TextStyleConfig textStyle;
BarStyleConfig barStyle; BarStyleConfig barStyle;
StyleConfig style;
}; };
} // namespace config::server } // namespace config::server
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include <string>
namespace config::server
{
struct StyleConfig
{
int gap = 2;
struct
{
int width = 240;
int height = 16;
int padding = 4;
struct
{
std::string name = "Pixel10";
int size = 14;
} font;
} header;
struct
{
int width = 118;
int height = 150;
int padding = 5;
int gap = 4;
struct
{
int height = 7;
struct
{
std::string name = "PixelFive-Regular";
int size = 5;
} font;
} header;
struct
{
int width = 8;
int height = 16;
int gap_h = 4;
int gap_v = 4;
int max_per_row = 8;
int rows = 2;
struct
{
int width = 3;
int min = 20;
int max = 100;
} temperature;
} cpu;
struct
{
int height = 11;
int gap = 2;
struct
{
std::string name = "PixelFive-Regular";
int size = 5;
int padding = 8;
} font;
} memory, disks, networks;
} hostblock;
};
} // namespace config::server
+12 -8
View File
@@ -2,9 +2,13 @@
namespace display::graphics namespace display::graphics
{ {
Renderer::Renderer(Framebuffer &framebuffer, model::HostRegistry &registry) Renderer::Renderer(Framebuffer &framebuffer, model::HostRegistry &registry, config::server::StyleConfig &config)
: framebuffer(framebuffer), : framebuffer(framebuffer),
registry(registry) registry(registry),
config(config),
textRenderer(std::make_unique<display::ui::text::Renderer>()),
header(std::make_unique<display::ui::header::Header>(config)),
hostblock(std::make_unique<display::ui::hostblock::HostBlock>(config))
{ {
} }
@@ -12,10 +16,10 @@ namespace display::graphics
{ {
framebuffer.clear(Color{0, 0, 0}); framebuffer.clear(Color{0, 0, 0});
header.draw(framebuffer, textRenderer, START_X, START_Y); header->draw(framebuffer, *textRenderer, START_X, START_Y);
int blocksPerRow = int blocksPerRow =
(SCREEN_WIDTH + BLOCK_GAP) / (display::ui::hostblock::BLOCK_WIDTH + BLOCK_GAP); (SCREEN_WIDTH + config.gap) / (config.hostblock.width + config.gap);
if (blocksPerRow < 1) if (blocksPerRow < 1)
blocksPerRow = 1; blocksPerRow = 1;
@@ -26,12 +30,12 @@ namespace display::graphics
int col = index % blocksPerRow; int col = index % blocksPerRow;
int row = index / blocksPerRow; int row = index / blocksPerRow;
int x = START_X + col * (display::ui::hostblock::BLOCK_WIDTH + BLOCK_GAP); int x = START_X + col * (config.hostblock.width + config.gap);
int y = START_Y + header.height() + BLOCK_GAP + row * (display::ui::hostblock::BLOCK_HEIGHT + BLOCK_GAP); int y = START_Y + config.header.height + config.gap + row * (config.hostblock.height + config.gap);
hostblock.draw( hostblock->draw(
framebuffer, framebuffer,
textRenderer, *textRenderer,
x, x,
y, y,
host, host,
+9 -7
View File
@@ -5,20 +5,21 @@
#include "display/ui/header/Header.h" #include "display/ui/header/Header.h"
#include "display/ui/hostblock/HostBlock.h" #include "display/ui/hostblock/HostBlock.h"
#include "display/graphics/Color.h" #include "display/graphics/Color.h"
#include "config/server/StyleConfig.h"
#include <string> #include <string>
#include <memory>
namespace display::graphics namespace display::graphics
{ {
static constexpr int START_X = 0; // 0 static constexpr int START_X = 0; // 0
static constexpr int START_Y = 0; // 20 static constexpr int START_Y = 0; // 20
static constexpr int BLOCK_GAP = 4; // 4
static constexpr int SCREEN_WIDTH = 240; static constexpr int SCREEN_WIDTH = 240;
class Renderer class Renderer
{ {
public: public:
Renderer(Framebuffer &framebuffer, model::HostRegistry &registry); Renderer(Framebuffer &framebuffer, model::HostRegistry &registry, config::server::StyleConfig &config);
Renderer(const Renderer &) = delete; Renderer(const Renderer &) = delete;
Renderer &operator=(const Renderer &) = delete; Renderer &operator=(const Renderer &) = delete;
@@ -28,8 +29,9 @@ namespace display::graphics
private: private:
Framebuffer &framebuffer; Framebuffer &framebuffer;
model::HostRegistry &registry; model::HostRegistry &registry;
display::ui::text::Renderer textRenderer; config::server::StyleConfig &config;
display::ui::header::Header header; std::unique_ptr<display::ui::text::Renderer> textRenderer;
display::ui::hostblock::HostBlock hostblock; std::unique_ptr<display::ui::header::Header> header;
std::unique_ptr<display::ui::hostblock::HostBlock> hostblock;
}; };
} // namespace display::graphics } // namespace display::graphics
+2 -2
View File
@@ -8,7 +8,7 @@ namespace display::ui::bar
{ {
} }
void Bar::draw(display::graphics::Framebuffer &framebuffer, int x, int y, float value, float overlay_value) void Bar::draw(display::graphics::Framebuffer &framebuffer, int x, int y, float value, float overlay_value, bool active)
{ {
value = std::clamp(value, 0.0f, 1.0f); value = std::clamp(value, 0.0f, 1.0f);
@@ -16,7 +16,7 @@ namespace display::ui::bar
framebuffer.fillRect(x, y, width, height, style.background); framebuffer.fillRect(x, y, width, height, style.background);
// 2. Рисуем заполнение // 2. Рисуем заполнение
display::graphics::Color fillColor = valueToColor(value); display::graphics::Color fillColor = active ? valueToColor(value) : display::graphics::Color{0x66, 0x66, 0x66};
if (orientation == Orientation::Horizontal) if (orientation == Orientation::Horizontal)
{ {
+1 -1
View File
@@ -13,7 +13,7 @@ namespace display::ui::bar
void draw(display::graphics::Framebuffer &framebuffer, void draw(display::graphics::Framebuffer &framebuffer,
int x, int y, int x, int y,
float value, float overlay_value = 0.0); // 0.0 .. 1.0 float value, float overlay_value = 0.0, bool active = true); // 0.0 .. 1.0
private: private:
int width; int width;
+7 -6
View File
@@ -13,7 +13,8 @@
namespace display::ui::header namespace display::ui::header
{ {
Header::Header() Header::Header(config::server::StyleConfig &config)
: config(config)
{ {
} }
@@ -23,19 +24,19 @@ namespace display::ui::header
int x, int y) int x, int y)
{ {
// ===== Block background ===== // ===== Block background =====
fb.fillRect(x, y, HEADER_WIDTH, HEADER_HEIGHT, display::ui::theme::header::BACKGROUND); fb.fillRect(x, y, config.header.width, config.header.height, display::ui::theme::header::BACKGROUND);
fb.drawRect(x, y, HEADER_WIDTH, HEADER_HEIGHT, display::ui::theme::header::BORDER); fb.drawRect(x, y, config.header.width, config.header.height, display::ui::theme::header::BORDER);
int cursorY = y + PADDING; int cursorY = y + config.header.padding;
text.drawTextOutlined( text.drawTextOutlined(
fb, fb,
x + PADDING + 120 - 2, x + config.header.padding + 120 - 2,
cursorY + 8, cursorY + 8,
getCurrentDateTime(), getCurrentDateTime(),
display::ui::theme::text::TEXT, display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE, display::ui::theme::text::OUTLINE,
display::ui::text::Font{std::string(HEADER_FONT_NAME.begin(), HEADER_FONT_NAME.end()), HEADER_FONT_SIZE}); display::ui::text::Font{config.header.font.name, config.header.font.size});
} }
std::string Header::getCurrentDateTime() std::string Header::getCurrentDateTime()
+3 -11
View File
@@ -6,23 +6,14 @@
#include "display/graphics/Framebuffer.h" #include "display/graphics/Framebuffer.h"
#include "display/graphics/Color.h" #include "display/graphics/Color.h"
#include "display/ui/text/Renderer.h" #include "display/ui/text/Renderer.h"
#include "config/server/StyleConfig.h"
namespace display::ui::header namespace display::ui::header
{ {
// ===== Layout =====
constexpr int HEADER_WIDTH = 240; // 116
constexpr int HEADER_HEIGHT = 16; // 116
constexpr int PADDING = 4; // 4
constexpr std::string_view HEADER_FONT_NAME = "Pixel10";
constexpr int HEADER_FONT_SIZE = 14; // 12
class Header class Header
{ {
public: public:
Header(); Header(config::server::StyleConfig &config);
static constexpr int width() { return HEADER_WIDTH; }
static constexpr int height() { return HEADER_HEIGHT; }
void draw( void draw(
display::graphics::Framebuffer &fb, display::graphics::Framebuffer &fb,
@@ -31,6 +22,7 @@ namespace display::ui::header
private: private:
std::string getCurrentDateTime(); std::string getCurrentDateTime();
config::server::StyleConfig &config;
}; };
} }
+119 -48
View File
@@ -11,20 +11,21 @@
namespace display::ui::hostblock namespace display::ui::hostblock
{ {
HostBlock::HostBlock() HostBlock::HostBlock(config::server::StyleConfig &config)
: cpuBar( : config(config),
CPU_BAR_WIDTH, cpuBar(
CPU_BAR_HEIGHT, config.hostblock.cpu.width,
config.hostblock.cpu.height,
display::ui::bar::Orientation::Vertical, display::ui::bar::Orientation::Vertical,
display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 180, 0}, display::graphics::Color{80, 80, 80}, true}), display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 180, 0}, display::graphics::Color{80, 80, 80}, true}),
cpuTempBar( cpuTempBar(
CPU_BAR_TEMP_WIDTH, config.hostblock.cpu.temperature.width,
CPU_BAR_HEIGHT, config.hostblock.cpu.height,
display::ui::bar::Orientation::Vertical, display::ui::bar::Orientation::Vertical,
display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 180, 0}, display::graphics::Color{80, 80, 80}, true}), display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 180, 0}, display::graphics::Color{80, 80, 80}, true}),
memBar( memBar(
BLOCK_WIDTH - PADDING * 2, config.hostblock.width - config.hostblock.padding * 2,
MEM_BAR_HEIGHT, config.hostblock.memory.height,
display::ui::bar::Orientation::Horizontal, display::ui::bar::Orientation::Horizontal,
display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 120, 200}, display::graphics::Color{80, 80, 80}, true}) display::ui::bar::Style{display::graphics::Color{40, 40, 40}, display::graphics::Color{0, 120, 200}, display::graphics::Color{80, 80, 80}, true})
@@ -38,60 +39,60 @@ namespace display::ui::hostblock
const std::string &hostname, const std::string &hostname,
const metrics::Host &metrics) const metrics::Host &metrics)
{ {
// ===== Block background ===== bool online = std::chrono::high_resolution_clock::now() - metrics.packetTimepoint < std::chrono::seconds(5);
fb.fillRect(x, y, BLOCK_WIDTH, BLOCK_HEIGHT, display::ui::theme::hostblock::BACKGROUND);
fb.drawRect(x, y, BLOCK_WIDTH, BLOCK_HEIGHT, display::ui::theme::hostblock::BORDER);
int cursorY = y + PADDING; // ===== Block background =====
fb.fillRect(x, y, config.hostblock.width, config.hostblock.height, display::ui::theme::hostblock::BACKGROUND);
fb.drawRect(x, y, config.hostblock.width, config.hostblock.height, display::ui::theme::hostblock::BORDER);
int cursorY = y + config.hostblock.padding;
// ===== Header ===== // ===== Header =====
fb.fillRect( fb.fillRect(
x + PADDING, x + config.hostblock.padding,
cursorY, cursorY,
BLOCK_WIDTH - PADDING * 2, config.hostblock.width - config.hostblock.padding * 2,
HEADER_HEIGHT, config.hostblock.header.height,
std::chrono::high_resolution_clock::now() - metrics.packetTimepoint < std::chrono::seconds(5) ? display::ui::theme::hostblock::HEADER : display::graphics::Red()); online ? display::ui::theme::hostblock::HEADER : display::graphics::Red());
text.drawTextOutlined( text.drawTextOutlined(
fb, fb,
x + PADDING + 2, x + config.hostblock.padding + 2,
cursorY + HEADER_HEIGHT - 1, cursorY + config.hostblock.header.height - 1,
hostname, hostname,
display::ui::theme::text::TEXT, display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE, display::ui::theme::text::OUTLINE,
// display::ui::text::Font{"LiberationSans-Regular", HEADER_FONT_SIZE}); display::ui::text::Font{config.hostblock.header.font.name, config.hostblock.header.font.size});
// display::ui::text::Font{"PixelFive-Regular", 5});
display::ui::text::Font{std::string(HEADER_FONT_NAME.begin(), HEADER_FONT_NAME.end()), HEADER_FONT_SIZE});
cursorY += HEADER_HEIGHT + SECTION_GAP; cursorY += config.hostblock.header.height + config.hostblock.gap;
// ===== CPU bars ===== // ===== CPU bars =====
int cpuCount = std::min<int>(metrics.cpu.coreLoads.size(), int cpuCount = std::min<int>(metrics.cpu.coreLoads.size(),
CPU_MAX_PER_ROW * CPU_ROWS); config.hostblock.cpu.max_per_row * config.hostblock.cpu.rows);
for (int i = 0; i < cpuCount; ++i) for (int i = 0; i < cpuCount; ++i)
{ {
int row = i / CPU_MAX_PER_ROW; int row = i / config.hostblock.cpu.max_per_row;
int col = i % CPU_MAX_PER_ROW; int col = i % config.hostblock.cpu.max_per_row;
int bx = x + PADDING + int bx = x + config.hostblock.padding +
col * (CPU_BAR_WIDTH + CPU_BAR_TEMP_WIDTH - 1 + CPU_BAR_GAP); col * (config.hostblock.cpu.width + config.hostblock.cpu.temperature.width - 1 + config.hostblock.cpu.gap_h);
int by = cursorY + int by = cursorY +
row * (CPU_BAR_HEIGHT + CPU_BAR_GAP); row * (config.hostblock.cpu.height + config.hostblock.cpu.gap_v);
float value = std::clamp(metrics.cpu.coreLoads[i] / 100.0f, 0.0f, 1.0f); float value = std::clamp(metrics.cpu.coreLoads[i] / 100.0f, 0.0f, 1.0f);
cpuBar.draw(fb, bx, by, value); cpuBar.draw(fb, bx, by, value, 0, online);
value = std::clamp((metrics.cpu.coreTemps[i].current - CPU_BAR_TEMP_MIN) / ((metrics.cpu.coreTemps[i].max == 0 ? 100.0f : metrics.cpu.coreTemps[i].max) - CPU_BAR_TEMP_MIN), 0.0f, 1.0f); value = std::clamp((metrics.cpu.coreTemps[i].current - config.hostblock.cpu.temperature.min) / ((metrics.cpu.coreTemps[i].max == 0 ? static_cast<float>(config.hostblock.cpu.temperature.max) : metrics.cpu.coreTemps[i].max) - config.hostblock.cpu.temperature.min), 0.0f, 1.0f);
cpuTempBar.draw(fb, bx + CPU_BAR_WIDTH - 1, by, value); cpuTempBar.draw(fb, bx + config.hostblock.cpu.width - 1, by, value, 0, online);
} }
cursorY += CPU_ROWS * CPU_BAR_HEIGHT + cursorY += config.hostblock.cpu.rows * config.hostblock.cpu.height +
(CPU_ROWS - 1) * CPU_BAR_GAP + (config.hostblock.cpu.rows - 1) * config.hostblock.cpu.gap_v +
SECTION_GAP; config.hostblock.gap;
// ===== Memory bar ===== // ===== Memory bar =====
float memValue, swapValue = 0.0f, hugepagesValue = 0.0f; float memValue, swapValue = 0.0f, hugepagesValue = 0.0f;
@@ -102,18 +103,18 @@ namespace display::ui::hostblock
memBar.draw( memBar.draw(
fb, fb,
x + PADDING, x + config.hostblock.padding,
cursorY, cursorY,
std::clamp(memValue, 0.0f, 1.0f), std::clamp(hugepagesValue, 0.0f, 1.0f)); std::clamp(memValue, 0.0f, 1.0f), std::clamp(hugepagesValue, 0.0f, 1.0f), online);
text.drawTextOutlined(fb, text.drawTextOutlined(fb,
x + MEM_BAR_TEXT_PADDING_X, x + config.hostblock.memory.font.padding,
cursorY + 8, cursorY + 8,
"MEM: " + display::ui::text::formatFloat(static_cast<float>(metrics.memory.memory.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.memory.memory.total) / 1073741824), "MEM: " + display::ui::text::formatFloat(static_cast<float>(metrics.memory.memory.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.memory.memory.total) / 1073741824),
display::ui::theme::text::TEXT, display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE, display::ui::theme::text::OUTLINE,
display::ui::text::Font{"PixelFive-Regular", 5}); display::ui::text::Font{config.hostblock.memory.font.name, config.hostblock.memory.font.size});
cursorY += MEM_BAR_HEIGHT + SECTION_GAP; cursorY += config.hostblock.memory.height + config.hostblock.memory.gap;
if (metrics.memory.swap.total > 0) if (metrics.memory.swap.total > 0)
{ {
@@ -121,19 +122,19 @@ namespace display::ui::hostblock
memBar.draw( memBar.draw(
fb, fb,
x + PADDING, x + config.hostblock.padding,
cursorY, cursorY,
std::clamp(swapValue, 0.0f, 1.0f)); std::clamp(swapValue, 0.0f, 1.0f), 0, online);
text.drawTextOutlined(fb, text.drawTextOutlined(fb,
x + MEM_BAR_TEXT_PADDING_X, x + config.hostblock.memory.font.padding,
cursorY + 8, cursorY + 8,
"SWP: " + display::ui::text::formatFloat(static_cast<float>(metrics.memory.swap.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.memory.swap.total) / 1073741824), "SWP: " + display::ui::text::formatFloat(static_cast<float>(metrics.memory.swap.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.memory.swap.total) / 1073741824),
display::ui::theme::text::TEXT, display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE, display::ui::theme::text::OUTLINE,
display::ui::text::Font{"PixelFive-Regular", 5}); display::ui::text::Font{config.hostblock.memory.font.name, config.hostblock.memory.font.size});
cursorY += MEM_BAR_HEIGHT + SECTION_GAP; cursorY += config.hostblock.memory.height + config.hostblock.gap;
} }
// ===== Disk bar ===== // ===== Disk bar =====
@@ -142,18 +143,88 @@ namespace display::ui::hostblock
for (int i = 0; i < diskCount; ++i) for (int i = 0; i < diskCount; ++i)
{ {
int by = cursorY + int by = cursorY +
i * (MEM_BAR_HEIGHT + SECTION_GAP); i * (config.hostblock.disks.height + config.hostblock.disks.gap);
float value = std::clamp(static_cast<float>(metrics.disks[i].metrics.used) / metrics.disks[i].metrics.total, 0.0f, 1.0f); float value = std::clamp(static_cast<float>(metrics.disks[i].metrics.used) / metrics.disks[i].metrics.total, 0.0f, 1.0f);
memBar.draw(fb, x + PADDING, by, value); memBar.draw(fb, x + config.hostblock.padding, by, value, 0, online);
text.drawTextOutlined(fb, text.drawTextOutlined(fb,
x + MEM_BAR_TEXT_PADDING_X, x + config.hostblock.disks.font.padding,
by + 8, by + 8,
"D/" + metrics.disks[i].name + ": " + display::ui::text::formatFloat(static_cast<float>(metrics.disks[i].metrics.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.disks[i].metrics.total) / 1073741824), "D/" + metrics.disks[i].name + ": " + display::ui::text::formatFloat(static_cast<float>(metrics.disks[i].metrics.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast<float>(metrics.disks[i].metrics.total) / 1073741824),
display::ui::theme::text::TEXT, display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE, display::ui::theme::text::OUTLINE,
display::ui::text::Font{"PixelFive-Regular", 5}); display::ui::text::Font{config.hostblock.disks.font.name, config.hostblock.disks.font.size});
}
cursorY += diskCount * (config.hostblock.disks.height + config.hostblock.disks.gap);
// ===== Network bar =====
for (const auto &iface : metrics.networks)
{
int by = cursorY;
float rxNorm = 0.0f;
float txNorm = 0.0f;
if (iface.online)
{
if (iface.rxMaxBps > 0)
rxNorm = std::clamp(static_cast<float>(iface.rxBps / iface.rxMaxBps), 0.0f, 1.0f);
if (iface.txMaxBps > 0)
txNorm = std::clamp(static_cast<float>(iface.txBps / iface.txMaxBps), 0.0f, 1.0f);
}
// background bar
memBar.draw(
fb,
x + config.hostblock.padding,
by,
0.0f,
0.0f,
iface.online && online);
int barX = x + config.hostblock.padding;
int barW = config.hostblock.width - config.hostblock.padding * 2;
int barH = config.hostblock.networks.height;
// RX overlay
fb.fillRect(
barX,
by,
static_cast<int>(barW * rxNorm),
barH,
{0, 180, 0});
// TX overlay
fb.fillRect(
barX,
by,
static_cast<int>(barW * txNorm),
barH,
{200, 200, 0});
// border
fb.drawRect(barX, by, barW, barH, display::ui::theme::hostblock::BORDER);
// interface name + speeds
std::string label =
iface.name + " " +
display::ui::text::formatSpeed(iface.rxBps) + "" +
display::ui::text::formatSpeed(iface.txBps) + "";
text.drawTextOutlined(
fb,
barX + config.hostblock.networks.font.padding,
by + barH - 1,
label,
display::ui::theme::text::TEXT,
display::ui::theme::text::OUTLINE,
display::ui::text::Font{
config.hostblock.networks.font.name,
config.hostblock.networks.font.size});
cursorY += config.hostblock.networks.height + config.hostblock.networks.gap;
} }
} }
} }
+3 -27
View File
@@ -8,39 +8,14 @@
#include "display/ui/text/Renderer.h" #include "display/ui/text/Renderer.h"
#include "display/ui/bar/Bar.h" #include "display/ui/bar/Bar.h"
#include "metrics/Host.h" #include "metrics/Host.h"
#include "config/server/StyleConfig.h"
namespace display::ui::hostblock namespace display::ui::hostblock
{ {
// ===== Layout =====
constexpr int BLOCK_WIDTH = 118; // 118
constexpr int BLOCK_HEIGHT = 146; // 146
constexpr int PADDING = 5; // 5
constexpr int HEADER_HEIGHT = 7; // 12
constexpr std::string_view HEADER_FONT_NAME = "PixelFive-Regular";
constexpr int HEADER_FONT_SIZE = 5; // 10
constexpr int SECTION_GAP = 4; // 4
// ===== CPU bars =====
constexpr int CPU_BAR_WIDTH = 8; // 10
constexpr int CPU_BAR_TEMP_WIDTH = 3;
constexpr int CPU_BAR_GAP = 4; // 4
constexpr int CPU_BAR_HEIGHT = 16; // 16
constexpr int CPU_MAX_PER_ROW = 8; // 8
constexpr int CPU_ROWS = 2; // 2
constexpr int CPU_BAR_TEMP_MIN = 20;
// ===== Memory bar =====
constexpr int MEM_BAR_HEIGHT = 11; // 8
constexpr int MEM_BAR_TEXT_PADDING_X = PADDING + 3;
class HostBlock class HostBlock
{ {
public: public:
HostBlock(); HostBlock(config::server::StyleConfig &config);
static constexpr int width() { return BLOCK_WIDTH; }
static constexpr int height() { return BLOCK_HEIGHT; }
void draw( void draw(
display::graphics::Framebuffer &fb, display::graphics::Framebuffer &fb,
@@ -50,6 +25,7 @@ namespace display::ui::hostblock
const metrics::Host &metrics); const metrics::Host &metrics);
private: private:
config::server::StyleConfig &config;
display::ui::bar::Bar cpuBar; display::ui::bar::Bar cpuBar;
display::ui::bar::Bar cpuTempBar; display::ui::bar::Bar cpuTempBar;
display::ui::bar::Bar memBar; display::ui::bar::Bar memBar;
+14
View File
@@ -17,4 +17,18 @@ namespace display::ui::text
std::snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); std::snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
return std::string(buffer); return std::string(buffer);
} }
std::string formatSpeed(float bps)
{
char buf[32];
if (bps < 1024)
snprintf(buf, sizeof(buf), "%.0f B/s", bps);
else if (bps < 1024 * 1024)
snprintf(buf, sizeof(buf), "%.1f kB/s", bps / 1024.0);
else
snprintf(buf, sizeof(buf), "%.1f MB/s", bps / (1024.0 * 1024.0));
return buf;
}
} // namespace display::ui::text } // namespace display::ui::text
+1
View File
@@ -6,4 +6,5 @@ namespace display::ui::text
{ {
std::string formatFloat(float value, int decimals = 1); std::string formatFloat(float value, int decimals = 1);
std::string formatDouble(double value, int decimals = 1); std::string formatDouble(double value, int decimals = 1);
std::string formatSpeed(float bps);
} // namespace display::ui::text } // namespace display::ui::text
+1 -1
View File
@@ -12,7 +12,7 @@ int main(int argc, char **argv)
{ {
config::client::Config config = config::client::Config::load("/etc/esdashboard/client.ini"); config::client::Config config = config::client::Config::load("/etc/esdashboard/client.ini");
network::Client client(config.network.serverHost, config.network.serverPort); network::Client client(config.network.serverHost, config.network.serverPort);
metrics::Collector collector(config.collector.disks); metrics::Collector collector(config.collector.disks, config.collector.networks);
network::Agent agent(std::move(client), std::move(collector), config.network.intervalMs); network::Agent agent(std::move(client), std::move(collector), config.network.intervalMs);
agent.start(); agent.start();
+1 -1
View File
@@ -15,7 +15,7 @@ int main(int argc, char **argv)
config::server::Config config = config::server::Config::load("/etc/esdashboard/server.ini"); config::server::Config config = config::server::Config::load("/etc/esdashboard/server.ini");
model::HostRegistry registry; model::HostRegistry registry;
display::graphics::Framebuffer fb(config.display.framebuffer.c_str(), display::graphics::FramebufferRotation(config.display.rotation)); display::graphics::Framebuffer fb(config.display.framebuffer.c_str(), display::graphics::FramebufferRotation(config.display.rotation));
display::graphics::Renderer renderer(fb, registry); display::graphics::Renderer renderer(fb, registry, config.style);
network::Server server(config.network.listenPort, registry); network::Server server(config.network.listenPort, registry);
server.start(); server.start();
+101 -1
View File
@@ -15,7 +15,7 @@
namespace metrics namespace metrics
{ {
Collector::Collector(const std::vector<std::vector<std::string>> &disks) : disks(disks) Collector::Collector(const std::vector<std::vector<std::string>> &disks, const std::vector<std::vector<std::string>> interfaces) : disks(disks)
{ {
std::vector<int> cpuLogicalIds; std::vector<int> cpuLogicalIds;
for (const auto &cpuDir : std::filesystem::directory_iterator("/sys/devices/system/cpu")) for (const auto &cpuDir : std::filesystem::directory_iterator("/sys/devices/system/cpu"))
@@ -43,6 +43,22 @@ namespace metrics
f >> coreId; f >> coreId;
logicalToPhysical[i] = coreId; logicalToPhysical[i] = coreId;
} }
lastNetUpdate = std::chrono::high_resolution_clock::now();
for (const auto &iface : interfaces)
{
Network network;
network.name = iface.at(0);
network.interface = iface.at(1);
network.online = isInterfaceOnline(network.interface);
if (network.online)
readInterfaceCounters(network.interface, network.rxBytes, network.txBytes);
netIfaces[network.interface] = network;
}
} }
Host Collector::collect() Host Collector::collect()
@@ -70,6 +86,11 @@ namespace metrics
host.uptime = readUptime(); host.uptime = readUptime();
host.hostname = readHostname(); host.hostname = readHostname();
updateNetwork();
host.networks.reserve(netIfaces.size());
for (const auto &[key, value] : netIfaces)
host.networks.push_back(value);
return host; return host;
} }
@@ -306,4 +327,83 @@ namespace metrics
return "Unknown"; return "Unknown";
} }
bool Collector::isInterfaceOnline(const std::string &iface)
{
std::ifstream f("/sys/class/net/" + iface + "/operstate");
if (!f)
return false;
std::string state;
f >> state;
return state == "up";
}
bool Collector::readInterfaceCounters(const std::string &iface, uint64_t &rx, uint64_t &tx)
{
std::string base = "/sys/class/net/" + iface + "/statistics/";
std::ifstream frx(base + "rx_bytes");
std::ifstream ftx(base + "tx_bytes");
if (!frx || !ftx)
return false;
frx >> rx;
ftx >> tx;
return true;
}
void Collector::updateNetwork()
{
auto now = std::chrono::high_resolution_clock::now();
float dt =
std::chrono::duration<float>(now - lastNetUpdate).count();
if (dt <= 0.0)
return;
for (auto &[name, s] : netIfaces)
{
bool online = isInterfaceOnline(name);
if (!online)
{
s.online = false;
s.rxBps = 0;
s.txBps = 0;
continue;
}
uint64_t rx = 0, tx = 0;
if (!readInterfaceCounters(name, rx, tx))
{
s.online = false;
s.rxBps = 0;
s.txBps = 0;
continue;
}
if (s.online) // был online и раньше
{
s.rxBps = (rx - s.rxBytes) / dt;
s.txBps = (tx - s.txBytes) / dt;
}
else
{
// только что появился — не считаем скачок
s.rxBps = 0;
s.txBps = 0;
}
s.rxBytes = rx;
s.txBytes = tx;
s.online = true;
s.rxMaxBps = std::max(s.rxMaxBps * 0.98f, s.rxBps);
s.txMaxBps = std::max(s.txMaxBps * 0.98f, s.txBps);
}
lastNetUpdate = now;
}
} }
+10 -1
View File
@@ -1,15 +1,18 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <string> #include <string>
#include <map>
#include <chrono>
#include "metrics/Host.h" #include "metrics/Host.h"
#include "metrics/CpuTemperature.h" #include "metrics/CpuTemperature.h"
#include "metrics/Network.h"
namespace metrics namespace metrics
{ {
class Collector class Collector
{ {
public: public:
Collector(const std::vector<std::vector<std::string>> &disks = {{"R", "/"}}); Collector(const std::vector<std::vector<std::string>> &disks = {{"R", "/"}}, const std::vector<std::vector<std::string>> interfaces = {});
explicit Collector(); explicit Collector();
Host collect(); Host collect();
@@ -25,6 +28,9 @@ namespace metrics
std::pair<CpuTimes, std::vector<CpuTimes>> prevCpu; std::pair<CpuTimes, std::vector<CpuTimes>> prevCpu;
std::vector<int> logicalToPhysical; std::vector<int> logicalToPhysical;
std::map<std::string, Network> netIfaces;
std::chrono::high_resolution_clock::time_point lastNetUpdate;
std::pair<CpuTimes, std::vector<CpuTimes>> readCpuTimes(); std::pair<CpuTimes, std::vector<CpuTimes>> readCpuTimes();
const std::vector<std::vector<std::string>> disks; const std::vector<std::vector<std::string>> disks;
float cpuLoad(const CpuTimes &prev, const CpuTimes &cur); float cpuLoad(const CpuTimes &prev, const CpuTimes &cur);
@@ -34,5 +40,8 @@ namespace metrics
void readLoad(float &l1, float &l5, float &l15); void readLoad(float &l1, float &l5, float &l15);
uint64_t readUptime(); uint64_t readUptime();
std::string readHostname(); std::string readHostname();
bool isInterfaceOnline(const std::string &iface);
bool readInterfaceCounters(const std::string &iface, uint64_t &rx, uint64_t &tx);
void updateNetwork();
}; };
} // namespace metrics } // namespace metrics
+34
View File
@@ -67,6 +67,21 @@ namespace metrics
buf.writeUint64(d.metrics.total); buf.writeUint64(d.metrics.total);
} }
// Network
buf.writeUint8(static_cast<uint8_t>(networks.size()));
for (const auto &n : networks)
{
buf.writeString(n.name);
buf.writeString(n.interface);
buf.writeBool(n.online);
buf.writeUint64(n.rxBytes);
buf.writeUint64(n.txBytes);
buf.writeFloat(n.rxBps);
buf.writeFloat(n.txBps);
buf.writeFloat(n.rxMaxBps);
buf.writeFloat(n.txMaxBps);
}
return buf.data(); return buf.data();
} }
@@ -120,6 +135,25 @@ namespace metrics
h.disks.push_back(disk); h.disks.push_back(disk);
} }
// Network
uint8_t numNetworks = buf.readUint8();
h.networks.clear();
h.networks.reserve(numNetworks);
for (uint8_t i = 0; i < numNetworks; ++i)
{
Network network;
network.name = buf.readString();
network.interface = buf.readString();
network.online = buf.readBool();
network.rxBytes = buf.readUint64();
network.txBytes = buf.readUint64();
network.rxBps = buf.readFloat();
network.txBps = buf.readFloat();
network.rxMaxBps = buf.readFloat();
network.txMaxBps = buf.readFloat();
h.networks.push_back(network);
}
h.packetTimepoint = std::chrono::high_resolution_clock::now(); h.packetTimepoint = std::chrono::high_resolution_clock::now();
return h; return h;
+2
View File
@@ -7,6 +7,7 @@
#include "metrics/Cpu.h" #include "metrics/Cpu.h"
#include "metrics/Memory.h" #include "metrics/Memory.h"
#include "metrics/Disk.h" #include "metrics/Disk.h"
#include "metrics/Network.h"
namespace metrics namespace metrics
{ {
@@ -17,6 +18,7 @@ namespace metrics
Cpu cpu; Cpu cpu;
Memory memory; Memory memory;
std::vector<Disk> disks; std::vector<Disk> disks;
std::vector<Network> networks;
uint64_t uptime; uint64_t uptime;
float load1, load5, load15; float load1, load5, load15;
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <string>
namespace metrics
{
struct Network
{
std::string name;
std::string interface;
bool online = false;
uint64_t rxBytes = 0;
uint64_t txBytes = 0;
float rxBps = 0.0;
float txBps = 0.0;
float rxMaxBps = 1.0;
float txMaxBps = 1.0;
};
} // namespace metrics
+11
View File
@@ -2,6 +2,11 @@
namespace network namespace network
{ {
void Buffer::writeBool(bool v)
{
writeUint8(v ? 1 : 0);
}
void Buffer::writeUint8(uint8_t v) void Buffer::writeUint8(uint8_t v)
{ {
buffer.push_back(v); buffer.push_back(v);
@@ -74,6 +79,12 @@ namespace network
} }
// --- Reading --- // --- Reading ---
bool Buffer::readBool()
{
uint8_t v = readUint8();
return v != 0;
}
uint8_t Buffer::readUint8() uint8_t Buffer::readUint8()
{ {
checkRemaining(1); checkRemaining(1);
+2
View File
@@ -14,6 +14,7 @@ namespace network
explicit Buffer(size_t reserve = 0) : buffer(reserve), pos(0) {} explicit Buffer(size_t reserve = 0) : buffer(reserve), pos(0) {}
// --- Writing --- // --- Writing ---
void writeBool(bool v);
void writeUint8(uint8_t v); void writeUint8(uint8_t v);
void writeUint32(uint32_t v); void writeUint32(uint32_t v);
void writeUint16(uint16_t v); void writeUint16(uint16_t v);
@@ -26,6 +27,7 @@ namespace network
void writeBytes(const uint8_t *data, size_t len); void writeBytes(const uint8_t *data, size_t len);
void writeString(const std::string &s); void writeString(const std::string &s);
// --- Reading --- // --- Reading ---
bool readBool();
uint8_t readUint8(); uint8_t readUint8();
uint16_t readUint16(); uint16_t readUint16();
uint32_t readUint32(); uint32_t readUint32();