From 5b5be7c3c8823452560a87efaac44a7145f5d592 Mon Sep 17 00:00:00 2001 From: Eugene Lykov Date: Fri, 9 Jan 2026 21:58:48 +0000 Subject: [PATCH] Add network --- src/config/client/CollectorConfig.h | 1 + src/config/client/Config.cpp | 8 ++ src/config/server/Config.cpp | 45 +++++++ src/config/server/Config.h | 2 + src/config/server/StyleConfig.h | 69 ++++++++++ src/display/graphics/Renderer.cpp | 20 +-- src/display/graphics/Renderer.h | 16 +-- src/display/ui/bar/Bar.cpp | 4 +- src/display/ui/bar/Bar.h | 2 +- src/display/ui/header/Header.cpp | 13 +- src/display/ui/header/Header.h | 14 +-- src/display/ui/hostblock/HostBlock.cpp | 167 ++++++++++++++++++------- src/display/ui/hostblock/HostBlock.h | 30 +---- src/display/ui/text/Helpers.cpp | 14 +++ src/display/ui/text/Helpers.h | 1 + src/main/client/main.cpp | 2 +- src/main/server/main.cpp | 2 +- src/metrics/Collector.cpp | 102 ++++++++++++++- src/metrics/Collector.h | 11 +- src/metrics/Host.cpp | 34 +++++ src/metrics/Host.h | 2 + src/metrics/Network.h | 24 ++++ src/network/common/Buffer.cpp | 11 ++ src/network/common/Buffer.h | 2 + 24 files changed, 482 insertions(+), 114 deletions(-) create mode 100644 src/config/server/StyleConfig.h create mode 100644 src/metrics/Network.h diff --git a/src/config/client/CollectorConfig.h b/src/config/client/CollectorConfig.h index c3ce788..589da78 100644 --- a/src/config/client/CollectorConfig.h +++ b/src/config/client/CollectorConfig.h @@ -8,5 +8,6 @@ namespace config::client struct CollectorConfig { std::vector> disks; + std::vector> networks; }; } // namespace config::client diff --git a/src/config/client/Config.cpp b/src/config/client/Config.cpp index cd2ea06..b3dbb14 100644 --- a/src/config/client/Config.cpp +++ b/src/config/client/Config.cpp @@ -19,6 +19,14 @@ namespace config::client for (const std::string &disk : disks_vec) cfg.collector.disks.push_back(config::splitList(disk, ':')); + const std::string networks_line = ini.get("collector", "networks", ""); + if (!networks_line.empty()) + { + const std::vector networks_vec = config::splitList(networks_line, ','); + for (const std::string &network : networks_vec) + cfg.collector.networks.push_back(config::splitList(network, ':')); + } + return cfg; } } // namespace config::client diff --git a/src/config/server/Config.cpp b/src/config/server/Config.cpp index 2dd0d99..fd39955 100644 --- a/src/config/server/Config.cpp +++ b/src/config/server/Config.cpp @@ -35,6 +35,51 @@ namespace config::server cfg.barStyle.fill = parseColor( 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; } } // namespace config::server diff --git a/src/config/server/Config.h b/src/config/server/Config.h index cfc0569..cc6b6db 100644 --- a/src/config/server/Config.h +++ b/src/config/server/Config.h @@ -5,6 +5,7 @@ #include "config/server/DisplayConfig.h" #include "config/server/NetworkConfig.h" #include "config/server/TextStyleConfig.h" +#include "config/server/StyleConfig.h" namespace config::server { @@ -17,5 +18,6 @@ namespace config::server NetworkConfig network; TextStyleConfig textStyle; BarStyleConfig barStyle; + StyleConfig style; }; } // namespace config::server diff --git a/src/config/server/StyleConfig.h b/src/config/server/StyleConfig.h new file mode 100644 index 0000000..7d26dd2 --- /dev/null +++ b/src/config/server/StyleConfig.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +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 diff --git a/src/display/graphics/Renderer.cpp b/src/display/graphics/Renderer.cpp index 203deed..c42aa36 100644 --- a/src/display/graphics/Renderer.cpp +++ b/src/display/graphics/Renderer.cpp @@ -2,9 +2,13 @@ namespace display::graphics { - Renderer::Renderer(Framebuffer &framebuffer, model::HostRegistry ®istry) + Renderer::Renderer(Framebuffer &framebuffer, model::HostRegistry ®istry, config::server::StyleConfig &config) : framebuffer(framebuffer), - registry(registry) + registry(registry), + config(config), + textRenderer(std::make_unique()), + header(std::make_unique(config)), + hostblock(std::make_unique(config)) { } @@ -12,10 +16,10 @@ namespace display::graphics { framebuffer.clear(Color{0, 0, 0}); - header.draw(framebuffer, textRenderer, START_X, START_Y); + header->draw(framebuffer, *textRenderer, START_X, START_Y); 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) blocksPerRow = 1; @@ -26,12 +30,12 @@ namespace display::graphics int col = index % blocksPerRow; int row = index / blocksPerRow; - int x = START_X + col * (display::ui::hostblock::BLOCK_WIDTH + BLOCK_GAP); - int y = START_Y + header.height() + BLOCK_GAP + row * (display::ui::hostblock::BLOCK_HEIGHT + BLOCK_GAP); + int x = START_X + col * (config.hostblock.width + config.gap); + int y = START_Y + config.header.height + config.gap + row * (config.hostblock.height + config.gap); - hostblock.draw( + hostblock->draw( framebuffer, - textRenderer, + *textRenderer, x, y, host, diff --git a/src/display/graphics/Renderer.h b/src/display/graphics/Renderer.h index b9c3d32..2cdf393 100644 --- a/src/display/graphics/Renderer.h +++ b/src/display/graphics/Renderer.h @@ -5,20 +5,21 @@ #include "display/ui/header/Header.h" #include "display/ui/hostblock/HostBlock.h" #include "display/graphics/Color.h" +#include "config/server/StyleConfig.h" #include +#include namespace display::graphics { - static constexpr int START_X = 0; // 0 - static constexpr int START_Y = 0; // 20 - static constexpr int BLOCK_GAP = 4; // 4 + static constexpr int START_X = 0; // 0 + static constexpr int START_Y = 0; // 20 static constexpr int SCREEN_WIDTH = 240; class Renderer { public: - Renderer(Framebuffer &framebuffer, model::HostRegistry ®istry); + Renderer(Framebuffer &framebuffer, model::HostRegistry ®istry, config::server::StyleConfig &config); Renderer(const Renderer &) = delete; Renderer &operator=(const Renderer &) = delete; @@ -28,8 +29,9 @@ namespace display::graphics private: Framebuffer &framebuffer; model::HostRegistry ®istry; - display::ui::text::Renderer textRenderer; - display::ui::header::Header header; - display::ui::hostblock::HostBlock hostblock; + config::server::StyleConfig &config; + std::unique_ptr textRenderer; + std::unique_ptr header; + std::unique_ptr hostblock; }; } // namespace display::graphics diff --git a/src/display/ui/bar/Bar.cpp b/src/display/ui/bar/Bar.cpp index 0ba35a1..5abd85d 100644 --- a/src/display/ui/bar/Bar.cpp +++ b/src/display/ui/bar/Bar.cpp @@ -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); @@ -16,7 +16,7 @@ namespace display::ui::bar framebuffer.fillRect(x, y, width, height, style.background); // 2. Рисуем заполнение - display::graphics::Color fillColor = valueToColor(value); + display::graphics::Color fillColor = active ? valueToColor(value) : display::graphics::Color{0x66, 0x66, 0x66}; if (orientation == Orientation::Horizontal) { diff --git a/src/display/ui/bar/Bar.h b/src/display/ui/bar/Bar.h index c64c205..454fb44 100644 --- a/src/display/ui/bar/Bar.h +++ b/src/display/ui/bar/Bar.h @@ -13,7 +13,7 @@ namespace display::ui::bar void draw(display::graphics::Framebuffer &framebuffer, 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: int width; diff --git a/src/display/ui/header/Header.cpp b/src/display/ui/header/Header.cpp index 3415b55..fc86d24 100644 --- a/src/display/ui/header/Header.cpp +++ b/src/display/ui/header/Header.cpp @@ -13,7 +13,8 @@ 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) { // ===== Block background ===== - fb.fillRect(x, y, HEADER_WIDTH, HEADER_HEIGHT, display::ui::theme::header::BACKGROUND); - fb.drawRect(x, y, HEADER_WIDTH, HEADER_HEIGHT, display::ui::theme::header::BORDER); + fb.fillRect(x, y, config.header.width, config.header.height, display::ui::theme::header::BACKGROUND); + 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( fb, - x + PADDING + 120 - 2, + x + config.header.padding + 120 - 2, cursorY + 8, getCurrentDateTime(), display::ui::theme::text::TEXT, 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() diff --git a/src/display/ui/header/Header.h b/src/display/ui/header/Header.h index e414494..062571c 100644 --- a/src/display/ui/header/Header.h +++ b/src/display/ui/header/Header.h @@ -6,23 +6,14 @@ #include "display/graphics/Framebuffer.h" #include "display/graphics/Color.h" #include "display/ui/text/Renderer.h" +#include "config/server/StyleConfig.h" 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 { public: - Header(); - - static constexpr int width() { return HEADER_WIDTH; } - static constexpr int height() { return HEADER_HEIGHT; } + Header(config::server::StyleConfig &config); void draw( display::graphics::Framebuffer &fb, @@ -31,6 +22,7 @@ namespace display::ui::header private: std::string getCurrentDateTime(); + config::server::StyleConfig &config; }; } diff --git a/src/display/ui/hostblock/HostBlock.cpp b/src/display/ui/hostblock/HostBlock.cpp index 4eda022..028167c 100644 --- a/src/display/ui/hostblock/HostBlock.cpp +++ b/src/display/ui/hostblock/HostBlock.cpp @@ -11,20 +11,21 @@ namespace display::ui::hostblock { - HostBlock::HostBlock() - : cpuBar( - CPU_BAR_WIDTH, - CPU_BAR_HEIGHT, + HostBlock::HostBlock(config::server::StyleConfig &config) + : config(config), + cpuBar( + config.hostblock.cpu.width, + config.hostblock.cpu.height, 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}), cpuTempBar( - CPU_BAR_TEMP_WIDTH, - CPU_BAR_HEIGHT, + config.hostblock.cpu.temperature.width, + config.hostblock.cpu.height, 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}), memBar( - BLOCK_WIDTH - PADDING * 2, - MEM_BAR_HEIGHT, + config.hostblock.width - config.hostblock.padding * 2, + config.hostblock.memory.height, 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}) @@ -38,60 +39,60 @@ namespace display::ui::hostblock const std::string &hostname, const metrics::Host &metrics) { - // ===== Block background ===== - 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); + bool online = std::chrono::high_resolution_clock::now() - metrics.packetTimepoint < std::chrono::seconds(5); - 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 ===== fb.fillRect( - x + PADDING, + x + config.hostblock.padding, cursorY, - BLOCK_WIDTH - PADDING * 2, - HEADER_HEIGHT, - std::chrono::high_resolution_clock::now() - metrics.packetTimepoint < std::chrono::seconds(5) ? display::ui::theme::hostblock::HEADER : display::graphics::Red()); + config.hostblock.width - config.hostblock.padding * 2, + config.hostblock.header.height, + online ? display::ui::theme::hostblock::HEADER : display::graphics::Red()); text.drawTextOutlined( fb, - x + PADDING + 2, - cursorY + HEADER_HEIGHT - 1, + x + config.hostblock.padding + 2, + cursorY + config.hostblock.header.height - 1, hostname, display::ui::theme::text::TEXT, display::ui::theme::text::OUTLINE, - // display::ui::text::Font{"LiberationSans-Regular", 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}); + display::ui::text::Font{config.hostblock.header.font.name, config.hostblock.header.font.size}); - cursorY += HEADER_HEIGHT + SECTION_GAP; + cursorY += config.hostblock.header.height + config.hostblock.gap; // ===== CPU bars ===== int cpuCount = std::min(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) { - int row = i / CPU_MAX_PER_ROW; - int col = i % CPU_MAX_PER_ROW; + int row = i / config.hostblock.cpu.max_per_row; + int col = i % config.hostblock.cpu.max_per_row; - int bx = x + PADDING + - col * (CPU_BAR_WIDTH + CPU_BAR_TEMP_WIDTH - 1 + CPU_BAR_GAP); + int bx = x + config.hostblock.padding + + col * (config.hostblock.cpu.width + config.hostblock.cpu.temperature.width - 1 + config.hostblock.cpu.gap_h); 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); - 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(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 + - (CPU_ROWS - 1) * CPU_BAR_GAP + - SECTION_GAP; + cursorY += config.hostblock.cpu.rows * config.hostblock.cpu.height + + (config.hostblock.cpu.rows - 1) * config.hostblock.cpu.gap_v + + config.hostblock.gap; // ===== Memory bar ===== float memValue, swapValue = 0.0f, hugepagesValue = 0.0f; @@ -102,18 +103,18 @@ namespace display::ui::hostblock memBar.draw( fb, - x + PADDING, + x + config.hostblock.padding, 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, - x + MEM_BAR_TEXT_PADDING_X, + x + config.hostblock.memory.font.padding, cursorY + 8, "MEM: " + display::ui::text::formatFloat(static_cast(metrics.memory.memory.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast(metrics.memory.memory.total) / 1073741824), display::ui::theme::text::TEXT, 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) { @@ -121,19 +122,19 @@ namespace display::ui::hostblock memBar.draw( fb, - x + PADDING, + x + config.hostblock.padding, cursorY, - std::clamp(swapValue, 0.0f, 1.0f)); + std::clamp(swapValue, 0.0f, 1.0f), 0, online); text.drawTextOutlined(fb, - x + MEM_BAR_TEXT_PADDING_X, + x + config.hostblock.memory.font.padding, cursorY + 8, "SWP: " + display::ui::text::formatFloat(static_cast(metrics.memory.swap.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast(metrics.memory.swap.total) / 1073741824), display::ui::theme::text::TEXT, 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 ===== @@ -142,18 +143,88 @@ namespace display::ui::hostblock for (int i = 0; i < diskCount; ++i) { int by = cursorY + - i * (MEM_BAR_HEIGHT + SECTION_GAP); + i * (config.hostblock.disks.height + config.hostblock.disks.gap); float value = std::clamp(static_cast(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, - x + MEM_BAR_TEXT_PADDING_X, + x + config.hostblock.disks.font.padding, by + 8, "D/" + metrics.disks[i].name + ": " + display::ui::text::formatFloat(static_cast(metrics.disks[i].metrics.used) / 1073741824) + "/" + display::ui::text::formatFloat(static_cast(metrics.disks[i].metrics.total) / 1073741824), display::ui::theme::text::TEXT, 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(iface.rxBps / iface.rxMaxBps), 0.0f, 1.0f); + if (iface.txMaxBps > 0) + txNorm = std::clamp(static_cast(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(barW * rxNorm), + barH, + {0, 180, 0}); + + // TX overlay + fb.fillRect( + barX, + by, + static_cast(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; } } } diff --git a/src/display/ui/hostblock/HostBlock.h b/src/display/ui/hostblock/HostBlock.h index b92db8b..3ba9f05 100644 --- a/src/display/ui/hostblock/HostBlock.h +++ b/src/display/ui/hostblock/HostBlock.h @@ -8,39 +8,14 @@ #include "display/ui/text/Renderer.h" #include "display/ui/bar/Bar.h" #include "metrics/Host.h" +#include "config/server/StyleConfig.h" 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 { public: - HostBlock(); - - static constexpr int width() { return BLOCK_WIDTH; } - static constexpr int height() { return BLOCK_HEIGHT; } + HostBlock(config::server::StyleConfig &config); void draw( display::graphics::Framebuffer &fb, @@ -50,6 +25,7 @@ namespace display::ui::hostblock const metrics::Host &metrics); private: + config::server::StyleConfig &config; display::ui::bar::Bar cpuBar; display::ui::bar::Bar cpuTempBar; display::ui::bar::Bar memBar; diff --git a/src/display/ui/text/Helpers.cpp b/src/display/ui/text/Helpers.cpp index 7b9f6eb..d1e9881 100644 --- a/src/display/ui/text/Helpers.cpp +++ b/src/display/ui/text/Helpers.cpp @@ -17,4 +17,18 @@ namespace display::ui::text std::snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); 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 diff --git a/src/display/ui/text/Helpers.h b/src/display/ui/text/Helpers.h index fc14bea..a3abaf2 100644 --- a/src/display/ui/text/Helpers.h +++ b/src/display/ui/text/Helpers.h @@ -6,4 +6,5 @@ namespace display::ui::text { std::string formatFloat(float value, int decimals = 1); std::string formatDouble(double value, int decimals = 1); + std::string formatSpeed(float bps); } // namespace display::ui::text diff --git a/src/main/client/main.cpp b/src/main/client/main.cpp index c95230f..2b2bd8f 100644 --- a/src/main/client/main.cpp +++ b/src/main/client/main.cpp @@ -12,7 +12,7 @@ int main(int argc, char **argv) { config::client::Config config = config::client::Config::load("/etc/esdashboard/client.ini"); 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); agent.start(); diff --git a/src/main/server/main.cpp b/src/main/server/main.cpp index 9a0558c..74e48c2 100644 --- a/src/main/server/main.cpp +++ b/src/main/server/main.cpp @@ -15,7 +15,7 @@ int main(int argc, char **argv) config::server::Config config = config::server::Config::load("/etc/esdashboard/server.ini"); model::HostRegistry registry; 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); server.start(); diff --git a/src/metrics/Collector.cpp b/src/metrics/Collector.cpp index cea28b6..813ee8b 100644 --- a/src/metrics/Collector.cpp +++ b/src/metrics/Collector.cpp @@ -15,7 +15,7 @@ namespace metrics { - Collector::Collector(const std::vector> &disks) : disks(disks) + Collector::Collector(const std::vector> &disks, const std::vector> interfaces) : disks(disks) { std::vector cpuLogicalIds; for (const auto &cpuDir : std::filesystem::directory_iterator("/sys/devices/system/cpu")) @@ -43,6 +43,22 @@ namespace metrics f >> 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() @@ -70,6 +86,11 @@ namespace metrics host.uptime = readUptime(); host.hostname = readHostname(); + updateNetwork(); + host.networks.reserve(netIfaces.size()); + for (const auto &[key, value] : netIfaces) + host.networks.push_back(value); + return host; } @@ -306,4 +327,83 @@ namespace metrics 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(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; + } + } \ No newline at end of file diff --git a/src/metrics/Collector.h b/src/metrics/Collector.h index 63caa1a..0dd9477 100644 --- a/src/metrics/Collector.h +++ b/src/metrics/Collector.h @@ -1,15 +1,18 @@ #pragma once #include #include +#include +#include #include "metrics/Host.h" #include "metrics/CpuTemperature.h" +#include "metrics/Network.h" namespace metrics { class Collector { public: - Collector(const std::vector> &disks = {{"R", "/"}}); + Collector(const std::vector> &disks = {{"R", "/"}}, const std::vector> interfaces = {}); explicit Collector(); Host collect(); @@ -25,6 +28,9 @@ namespace metrics std::pair> prevCpu; std::vector logicalToPhysical; + std::map netIfaces; + std::chrono::high_resolution_clock::time_point lastNetUpdate; + std::pair> readCpuTimes(); const std::vector> disks; float cpuLoad(const CpuTimes &prev, const CpuTimes &cur); @@ -34,5 +40,8 @@ namespace metrics void readLoad(float &l1, float &l5, float &l15); uint64_t readUptime(); 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 diff --git a/src/metrics/Host.cpp b/src/metrics/Host.cpp index 14e144f..3f97ece 100644 --- a/src/metrics/Host.cpp +++ b/src/metrics/Host.cpp @@ -67,6 +67,21 @@ namespace metrics buf.writeUint64(d.metrics.total); } + // Network + buf.writeUint8(static_cast(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(); } @@ -120,6 +135,25 @@ namespace metrics 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(); return h; diff --git a/src/metrics/Host.h b/src/metrics/Host.h index c82ccb2..b4935cb 100644 --- a/src/metrics/Host.h +++ b/src/metrics/Host.h @@ -7,6 +7,7 @@ #include "metrics/Cpu.h" #include "metrics/Memory.h" #include "metrics/Disk.h" +#include "metrics/Network.h" namespace metrics { @@ -17,6 +18,7 @@ namespace metrics Cpu cpu; Memory memory; std::vector disks; + std::vector networks; uint64_t uptime; float load1, load5, load15; diff --git a/src/metrics/Network.h b/src/metrics/Network.h new file mode 100644 index 0000000..0eb5155 --- /dev/null +++ b/src/metrics/Network.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +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 diff --git a/src/network/common/Buffer.cpp b/src/network/common/Buffer.cpp index 533be95..4e0f98c 100644 --- a/src/network/common/Buffer.cpp +++ b/src/network/common/Buffer.cpp @@ -2,6 +2,11 @@ namespace network { + void Buffer::writeBool(bool v) + { + writeUint8(v ? 1 : 0); + } + void Buffer::writeUint8(uint8_t v) { buffer.push_back(v); @@ -74,6 +79,12 @@ namespace network } // --- Reading --- + bool Buffer::readBool() + { + uint8_t v = readUint8(); + return v != 0; + } + uint8_t Buffer::readUint8() { checkRemaining(1); diff --git a/src/network/common/Buffer.h b/src/network/common/Buffer.h index 01a318c..52ce2ef 100644 --- a/src/network/common/Buffer.h +++ b/src/network/common/Buffer.h @@ -14,6 +14,7 @@ namespace network explicit Buffer(size_t reserve = 0) : buffer(reserve), pos(0) {} // --- Writing --- + void writeBool(bool v); void writeUint8(uint8_t v); void writeUint32(uint32_t v); void writeUint16(uint16_t v); @@ -26,6 +27,7 @@ namespace network void writeBytes(const uint8_t *data, size_t len); void writeString(const std::string &s); // --- Reading --- + bool readBool(); uint8_t readUint8(); uint16_t readUint16(); uint32_t readUint32();