From f5dc0ccbc95d9c3bf9a74f26c7466de8c886de4b Mon Sep 17 00:00:00 2001 From: Eugene Lykov Date: Tue, 23 Dec 2025 21:24:06 +0000 Subject: [PATCH] First commit --- .vscode/launch.json | 32 ++++ CMakeLists.txt | 154 +----------------- ...de-workspace => esDashboard.code-workspace | 0 {libs => lib}/CMakeLists.txt | 0 src/CMakeLists.txt | 58 +++++++ src/Client.cpp | 22 +++ src/Display/CMakeLists.txt | 17 ++ src/Display/Graphics/CMakeLists.txt | 15 ++ src/Display/Graphics/Color.h | 38 +++++ src/Display/Graphics/Framebuffer.cpp | 104 ++++++++++++ src/Display/Graphics/Framebuffer.h | 31 ++++ src/Display/Graphics/Renderer.cpp | 41 +++++ src/Display/Graphics/Renderer.h | 27 +++ src/Display/UI/Bar/Bar.cpp | 31 ++++ src/Display/UI/Bar/Bar.h | 34 ++++ src/Display/UI/Bar/CMakeLists.txt | 15 ++ src/Display/UI/CMakeLists.txt | 21 +++ src/Display/UI/Text/BitmapFont.cpp | 36 ++++ src/Display/UI/Text/BitmapFont.h | 13 ++ src/Display/UI/Text/CMakeLists.txt | 21 +++ src/Display/UI/Text/FontFace.cpp | 38 +++++ src/Display/UI/Text/FontFace.h | 25 +++ src/Display/UI/Text/Glyph.h | 15 ++ src/Display/UI/Text/GlyphCache.cpp | 35 ++++ src/Display/UI/Text/GlyphCache.h | 28 ++++ src/Display/UI/Text/Renderer.cpp | 47 ++++++ src/Display/UI/Text/Renderer.h | 28 ++++ src/Display/UI/Text/UTFDecoder.h | 39 +++++ src/Helpers/CMakeLists.txt | 17 ++ {srcs => src/Helpers}/Version.h.in | 0 src/Metrics/CMakeLists.txt | 11 ++ src/Metrics/Collector.cpp | 150 +++++++++++++++++ src/Metrics/Collector.h | 32 ++++ src/Metrics/Cpu.h | 10 ++ src/Metrics/Disk.h | 13 ++ src/Metrics/Host.cpp | 143 ++++++++++++++++ src/Metrics/Host.h | 27 +++ src/Metrics/Memory.h | 11 ++ src/Model/CMakeLists.txt | 14 ++ src/Model/HostRegistry.cpp | 16 ++ src/Model/HostRegistry.h | 19 +++ src/Network/Agent.cpp | 53 ++++++ src/Network/Agent.h | 37 +++++ src/Network/CMakeLists.txt | 16 ++ src/Network/Client.cpp | 69 ++++++++ src/Network/Client.h | 29 ++++ src/Network/Server.cpp | 109 +++++++++++++ src/Network/Server.h | 29 ++++ src/Server.cpp | 25 +++ srcs/CMakeLists.txt | 54 ------ srcs/main.cpp | 5 - tools/cmake/SubDirList.cmake | 17 ++ 52 files changed, 1663 insertions(+), 208 deletions(-) create mode 100644 .vscode/launch.json rename esProject.code-workspace => esDashboard.code-workspace (100%) rename {libs => lib}/CMakeLists.txt (100%) create mode 100644 src/CMakeLists.txt create mode 100644 src/Client.cpp create mode 100644 src/Display/CMakeLists.txt create mode 100644 src/Display/Graphics/CMakeLists.txt create mode 100644 src/Display/Graphics/Color.h create mode 100644 src/Display/Graphics/Framebuffer.cpp create mode 100644 src/Display/Graphics/Framebuffer.h create mode 100644 src/Display/Graphics/Renderer.cpp create mode 100644 src/Display/Graphics/Renderer.h create mode 100644 src/Display/UI/Bar/Bar.cpp create mode 100644 src/Display/UI/Bar/Bar.h create mode 100644 src/Display/UI/Bar/CMakeLists.txt create mode 100644 src/Display/UI/CMakeLists.txt create mode 100644 src/Display/UI/Text/BitmapFont.cpp create mode 100644 src/Display/UI/Text/BitmapFont.h create mode 100644 src/Display/UI/Text/CMakeLists.txt create mode 100644 src/Display/UI/Text/FontFace.cpp create mode 100644 src/Display/UI/Text/FontFace.h create mode 100644 src/Display/UI/Text/Glyph.h create mode 100644 src/Display/UI/Text/GlyphCache.cpp create mode 100644 src/Display/UI/Text/GlyphCache.h create mode 100644 src/Display/UI/Text/Renderer.cpp create mode 100644 src/Display/UI/Text/Renderer.h create mode 100644 src/Display/UI/Text/UTFDecoder.h create mode 100644 src/Helpers/CMakeLists.txt rename {srcs => src/Helpers}/Version.h.in (100%) create mode 100644 src/Metrics/CMakeLists.txt create mode 100644 src/Metrics/Collector.cpp create mode 100644 src/Metrics/Collector.h create mode 100644 src/Metrics/Cpu.h create mode 100644 src/Metrics/Disk.h create mode 100644 src/Metrics/Host.cpp create mode 100644 src/Metrics/Host.h create mode 100644 src/Metrics/Memory.h create mode 100644 src/Model/CMakeLists.txt create mode 100644 src/Model/HostRegistry.cpp create mode 100644 src/Model/HostRegistry.h create mode 100644 src/Network/Agent.cpp create mode 100644 src/Network/Agent.h create mode 100644 src/Network/CMakeLists.txt create mode 100644 src/Network/Client.cpp create mode 100644 src/Network/Client.h create mode 100644 src/Network/Server.cpp create mode 100644 src/Network/Server.h create mode 100644 src/Server.cpp delete mode 100644 srcs/CMakeLists.txt delete mode 100644 srcs/main.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..490b84c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "esDashboard", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/Debug/bin/esDashboard", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a089b8..288687b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,11 @@ # CMakeList.txt : Top-level CMake project file, do global configuration # and include sub-projects here. # -cmake_minimum_required (VERSION 3.10) - -list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/tools/cmake) -include(SubDirList) +cmake_minimum_required (VERSION 3.16) get_filename_component(PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR} NAME) string(REPLACE " " "_" PROJECT_DIR ${PROJECT_DIR}) -project(${PROJECT_DIR}) +project(${PROJECT_DIR} LANGUAGES C CXX) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 1) @@ -42,150 +39,9 @@ set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install) set(CMAKE_CACHEFILE_DIR ${CMAKE_SOURCE_DIR}/build) -include(CTest) -enable_testing() - set(CMAKE_CXX_STANDARD 20) -set(CPACK_PROJECT_NAME ${${PROJECT_NAME}_NAME}) -set(CPACK_PROJECT_VERSION ${${PROJECT_NAME}_VERSION}) -include(CPack) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -option(${PROJECT_NAME}_BUILD_ENGINE "Build engine library" ON) -option(${PROJECT_NAME}_BUILD_APPS "Build supplimentary applications" ON) -option(${PROJECT_NAME}_BUILD_DEPS "Build dependencies" ON) +set(INCLUDE_BASE_DIR ${PROJECT_SOURCE_DIR}/src) -option(ONLY_COVERAGE "Build only tests necessary for coverage" OFF) -option(LIBCPP "Build with libc++" OFF) -option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" OFF) -option(ENABLE_ASAN "Enable address sanitizer" OFF) -option(BUILD_SHARED_LIBS "Enable compilation of shared libraries" OFF) -option(ENABLE_TESTING "Enable the building of the test" OFF) -option(ENABLE_CLANG_TIDY "Enable testing with clang-tidy" OFF) -option(ENABLE_CPPCHECK "Enable testing with cppcheck" OFF) -option(SIMPLE_BUILD "Build the project as minimally as possible" OFF) -option(BUILD_DOC "Build the project's documentation" OFF) -option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." OFF) -option(DEBUG_LOGGING "Enabling debug logging" OFF) - -add_library(project_warnings INTERFACE) -add_library(project_options INTERFACE) - -if(ONLY_COVERAGE OR ENABLE_COVERAGE) - target_compile_options(project_options INTERFACE --coverage -O0 -g) - target_link_libraries(project_options INTERFACE --coverage) -endif() - -if(ENABLE_ASAN) - target_compile_options(project_options INTERFACE -fsanitize=address) - target_link_libraries(project_options INTERFACE -fsanitize=address) -endif() - -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - target_compile_options(project_warnings - INTERFACE - -Wall - -Wextra - -Wshadow - -Wnon-virtual-dtor - -Wold-style-cast - -Wcast-align - -Wunused - -Woverloaded-virtual - -Wpedantic - -Wconversion - -Wsign-conversion - -Wnull-dereference - -Wdouble-promotion - -Wformat=2) -endif() - -# some GCC specific warnings. These flags are added only if the used compiler is GCC. -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - target_compile_options(project_warnings - INTERFACE - -Wmisleading-indentation - -Wduplicated-cond - -Wlogical-op - -Wuseless-cast - ) - target_link_libraries(project_options INTERFACE stdc++fs) -endif() - -if (${FORCE_COLORED_OUTPUT}) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - target_compile_options(project_options INTERFACE -fdiagnostics-color=always) - elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - target_compile_options(project_options INTERFACE -fcolor-diagnostics) - endif () -endif () - -find_program(CCACHE ccache) -if(CCACHE) - set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) -endif() - -if(ENABLE_CPPCHECK) - find_program(CPPCHECK cppcheck) - if(CPPCHECK) - set(CMAKE_CXX_CPPCHECK - ${CPPCHECK} - --suppress=syntaxError - --enable=all - --inconclusive) - else() - message(SEND_ERROR "cppcheck requested but executable not found") - endif() -endif() - -if(ENABLE_CLANG_TIDY) - find_program(CLANGTIDY clang-tidy) - if(CLANGTIDY) - set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY}) - else() - message(SEND_ERROR "clang-tidy requested but executable not found") - endif() -endif() - -if(BUILD_DOC) - find_package(Doxygen) - if (DOXYGEN_FOUND) - SET(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in) - SET(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - - configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) - - add_custom_target(doc ALL - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Buidling Doxygen documentation" - VERBATIM ) - else (DOXYGEN_FOUND) - message("No doxygen binary found on the system.") - SET(${BUILD_DOC} OFF) - endif () -endif() - -message(STATUS "") -message(STATUS "Summary") -#message(STATUS "") -#message(STATUS "Build libs: \t ${${PROJECT_NAME}_BUILD_DEPS}") -#message(STATUS "Build src: \t ${${PROJECT_NAME}_BUILD_ENGINE}") -message(STATUS "") -message(STATUS "Build type: \t ${CMAKE_BUILD_TYPE}") -message(STATUS "Install prefix: \t ${CMAKE_INSTALL_PREFIX}") -message(STATUS "Testing enabled: \t ${ENABLE_TESTING}") -message(STATUS "Clang-tidy: \t ${ENABLE_CLANG_TIDY}") -message(STATUS "Cppcheck: \t ${ENABLE_CPPCHECK}") -message(STATUS "Compiler: \t ${CMAKE_CXX_COMPILER_ID}") -message(STATUS "Sanizizers: \t ${ENABLE_ASAN}") -message(STATUS "Shared libs: \t ${BUILD_SHARED_LIBS}") -message(STATUS "Build libcpp: \t ${LIBCPP}") -message(STATUS "CCache executable:\t ${CCACHE}") -message(STATUS "Building doc: \t ${BUILD_DOC}") -message(STATUS "Force color output:\t ${FORCE_COLORED_OUTPUT}") -message(STATUS "") -message(STATUS "Version: \t ${${PROJECT_NAME}_VERSION}") -message(STATUS "") - -add_subdirectory("libs") -add_subdirectory("srcs") +add_subdirectory("src") diff --git a/esProject.code-workspace b/esDashboard.code-workspace similarity index 100% rename from esProject.code-workspace rename to esDashboard.code-workspace diff --git a/libs/CMakeLists.txt b/lib/CMakeLists.txt similarity index 100% rename from libs/CMakeLists.txt rename to lib/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..b0ab278 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,58 @@ +# Добавляем все модули как поддиректории +add_subdirectory(Display) +add_subdirectory(Helpers) +add_subdirectory(Metrics) +add_subdirectory(Model) +add_subdirectory(Network) + +set(TARGET_NAME_SERVER ${PROJECT_NAME}-Server) +message(STATUS "Configuring ${TARGET_NAME_SERVER}") + +# Основной исполняемый файл +add_executable(${TARGET_NAME_SERVER} Server.cpp) + +# Линкуем зависимости +target_link_libraries(${TARGET_NAME_SERVER} + PRIVATE + Display + Helpers + Metrics + Model + Network +) + +# Указываем корень include для dashboard +target_include_directories(${TARGET_NAME_SERVER} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + + +message(STATUS "") +message(STATUS "${TARGET_NAME_SERVER} summary") +message(STATUS "") + +set(TARGET_NAME_CLIENT ${PROJECT_NAME}-Client) +message(STATUS "Configuring ${TARGET_NAME_CLIENT}") + +# Основной исполняемый файл +add_executable(${TARGET_NAME_CLIENT} Client.cpp) + +# Линкуем зависимости +target_link_libraries(${TARGET_NAME_CLIENT} + PRIVATE + Helpers + Metrics + Network +) + +# Указываем корень include для dashboard +target_include_directories(${TARGET_NAME_CLIENT} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + + +message(STATUS "") +message(STATUS "${TARGET_NAME_CLIENT} summary") +message(STATUS "") diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..edbfdda --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +#include "Network/Client.h" +#include "Network/Agent.h" +#include "Metrics/Collector.h" + +int main(int argc, char **argv) +{ + Network::Client client("172.24.5.54", 5005); + Metrics::Collector collector; + Network::Agent agent(std::move(client), std::move(collector), std::chrono::milliseconds(5000)); + + agent.start(); + + while (true) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + return 0; +} \ No newline at end of file diff --git a/src/Display/CMakeLists.txt b/src/Display/CMakeLists.txt new file mode 100644 index 0000000..e3a935e --- /dev/null +++ b/src/Display/CMakeLists.txt @@ -0,0 +1,17 @@ +message(STATUS " Configuring Display") + +add_library(Display + INTERFACE +) + +add_subdirectory(Graphics) +add_subdirectory(UI) + +target_link_libraries(Display + INTERFACE Graphics UI +) + +target_include_directories(Display + INTERFACE + ${INCLUDE_BASE_DIR} +) diff --git a/src/Display/Graphics/CMakeLists.txt b/src/Display/Graphics/CMakeLists.txt new file mode 100644 index 0000000..e6369ef --- /dev/null +++ b/src/Display/Graphics/CMakeLists.txt @@ -0,0 +1,15 @@ +message(STATUS " Configuring Graphics") + +add_library(Graphics + Framebuffer.cpp + Renderer.cpp +) + +target_link_libraries(Graphics + PRIVATE Model Helpers UI +) + +target_include_directories(Graphics + PUBLIC + ${INCLUDE_BASE_DIR} +) diff --git a/src/Display/Graphics/Color.h b/src/Display/Graphics/Color.h new file mode 100644 index 0000000..957e7b3 --- /dev/null +++ b/src/Display/Graphics/Color.h @@ -0,0 +1,38 @@ +#pragma once +#include + +namespace Display::Graphics +{ + struct Color + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a = 255; // по умолчанию непрозрачный + + Color() = default; + Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) + : r(red), g(green), b(blue), a(alpha) {} + + operator uint16_t() const + { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + }; + + inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) + { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + + inline uint16_t rgb565(const Color &c) + { + return rgb565(c.r, c.g, c.b); + } + + inline Color Black() { return {0, 0, 0}; } + inline Color White() { return {255, 255, 255}; } + inline Color Red() { return {255, 0, 0}; } + inline Color Green() { return {0, 255, 0}; } + inline Color Blue() { return {0, 0, 255}; } +} // namespace Display::Graphics \ No newline at end of file diff --git a/src/Display/Graphics/Framebuffer.cpp b/src/Display/Graphics/Framebuffer.cpp new file mode 100644 index 0000000..b1ef01d --- /dev/null +++ b/src/Display/Graphics/Framebuffer.cpp @@ -0,0 +1,104 @@ +#include "Framebuffer.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Display::Graphics +{ + Framebuffer::Framebuffer(const char *device) + { + fd_ = open(device, O_RDWR); + if (fd_ < 0) + throw std::runtime_error("Cannot open framebuffer"); + + fb_var_screeninfo vinfo; + fb_fix_screeninfo finfo; + + ioctl(fd_, FBIOGET_VSCREENINFO, &vinfo); + ioctl(fd_, FBIOGET_FSCREENINFO, &finfo); + + width_ = vinfo.xres; + height_ = vinfo.yres; + line_len_ = finfo.line_length; + size_ = finfo.smem_len; + + fb_ = static_cast(mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0)); + if (fb_ == MAP_FAILED) + throw std::runtime_error("mmap failed"); + } + + Framebuffer::~Framebuffer() + { + munmap(fb_, size_); + close(fd_); + } + + void Framebuffer::clear(uint16_t color) + { + for (int y = 0; y < height_; ++y) + { + uint16_t *row = reinterpret_cast(fb_ + y * line_len_); + for (int x = 0; x < width_; ++x) + row[x] = color; + } + } + + void Framebuffer::fillRect(int x, int y, int w, int h, uint16_t color) + { + for (int j = 0; j < h; ++j) + { + int py = y + j; + if (py < 0 || py >= height_) + continue; + + uint16_t *row = reinterpret_cast(fb_ + py * line_len_); + for (int i = 0; i < w; ++i) + { + int px = x + i; + if (px < 0 || px >= width_) + continue; + + row[px] = color; + } + } + } + + void Framebuffer::drawRect(int x, int y, int w, int h, const Color &c) + { + // верх + fillRect(x, y, w, 1, c); + // низ + fillRect(x, y + h - 1, w, 1, c); + // лево + fillRect(x, y, 1, h, c); + // право + fillRect(x + w - 1, y, 1, h, c); + } + + void Framebuffer::setPixel(int x, int y, const Color &c) + { + if (x < 0 || x >= width_ || y < 0 || y >= height_) + return; + + uint16_t *p = reinterpret_cast(fb_ + y * line_len_) + x; + *p = ((c.r & 0xF8) << 8) | ((c.g & 0xFC) << 3) | (c.b >> 3); + } + + Color Framebuffer::getPixel(int x, int y) const + { + if (x < 0 || x >= width_ || y < 0 || y >= height_) + return {0, 0, 0, 255}; + + uint16_t p = *(reinterpret_cast(fb_ + y * line_len_) + x); + Color c; + c.r = (p >> 8) & 0xF8; + c.g = (p >> 3) & 0xFC; + c.b = (p << 3) & 0xF8; + c.a = 255; + return c; + } +} // namespace Display::Graphics diff --git a/src/Display/Graphics/Framebuffer.h b/src/Display/Graphics/Framebuffer.h new file mode 100644 index 0000000..61b0c43 --- /dev/null +++ b/src/Display/Graphics/Framebuffer.h @@ -0,0 +1,31 @@ +#pragma once +#include "Display/Graphics/Color.h" +#include +#include + +namespace Display::Graphics +{ + class Framebuffer + { + public: + Framebuffer(const char *device); + ~Framebuffer(); + + void clear(uint16_t color); + void fillRect(int x, int y, int w, int h, uint16_t color); + void drawRect(int x, int y, int w, int h, const Color &c); + void setPixel(int x, int y, const Color &c); + Color getPixel(int x, int y) const; + + int width() const { return width_; } + int height() const { return height_; } + + private: + int fd_; + uint8_t *fb_; + size_t size_; + int width_; + int height_; + int line_len_; + }; +} // namespace Display::Graphics diff --git a/src/Display/Graphics/Renderer.cpp b/src/Display/Graphics/Renderer.cpp new file mode 100644 index 0000000..fd9ca38 --- /dev/null +++ b/src/Display/Graphics/Renderer.cpp @@ -0,0 +1,41 @@ +#include "Renderer.h" + +namespace Display::Graphics +{ + Renderer::Renderer(Framebuffer &framebuffer, Model::HostRegistry ®istry) + : framebuffer(framebuffer), + registry(registry), + barMem(200, 10) + { + } + + void Renderer::render() + { + // очистка экрана + framebuffer.clear(Color{0, 0, 0}); + + int y = 10; // начальная вертикальная позиция + int lineHeight = 24; // высота строки (подбираем под шрифт) + + for (auto &[host, m] : registry.snapshot()) + { + // рисуем фон строки + framebuffer.fillRect(10, y - 18, 300, lineHeight, Color{0, 64, 0}); + + // формируем строку + std::string line = host + " " + std::to_string(int(m.cpu.loads.at(0))) + + "% " + std::to_string(int(m.memory.used / 1024 / 1024)) + + "/" + std::to_string(int(m.memory.total / 1024 / 1024)) + + " " + std::to_string(int(m.disks.at(0).used / 1024 / 1024)) + + "/" + std::to_string(int(m.disks.at(0).total / 1024 / 1024)); + + // выводим текст на экран + textRenderer.drawText(framebuffer, 12, y, line, Color{255, 255, 255}); + + float mem = (float)m.memory.used / (float)m.memory.total; + barMem.draw(framebuffer, 10, y + 35, mem); + + y += lineHeight; // переход на следующую строку + } + } +} // namespace Display::Graphics diff --git a/src/Display/Graphics/Renderer.h b/src/Display/Graphics/Renderer.h new file mode 100644 index 0000000..db9a88b --- /dev/null +++ b/src/Display/Graphics/Renderer.h @@ -0,0 +1,27 @@ +#pragma once +#include "Display/Graphics/Framebuffer.h" +#include "Model/HostRegistry.h" +#include "Display/UI/Text/Renderer.h" +#include "Display/UI/Bar/Bar.h" +#include "Display/Graphics/Color.h" +#include + +namespace Display::Graphics +{ + class Renderer + { + public: + Renderer(Framebuffer &framebuffer, Model::HostRegistry ®istry); + + Renderer(const Renderer &) = delete; + Renderer &operator=(const Renderer &) = delete; + + void render(); + + private: + Framebuffer &framebuffer; + Model::HostRegistry ®istry; + Display::UI::Text::Renderer textRenderer; + Display::UI::Bar::Bar barMem; + }; +} // namespace Display::Graphics diff --git a/src/Display/UI/Bar/Bar.cpp b/src/Display/UI/Bar/Bar.cpp new file mode 100644 index 0000000..f0c40e3 --- /dev/null +++ b/src/Display/UI/Bar/Bar.cpp @@ -0,0 +1,31 @@ +#include "Display/UI/Bar/Bar.h" +#include + +namespace Display::UI::Bar +{ + Bar::Bar(int width, int height, Style style) + : width(width), height(height), style(style) + { + } + + void Bar::draw(Display::Graphics::Framebuffer &framebuffer, int x, int y, float value) + { + value = std::clamp(value, 0.0f, 1.0f); + + // фон + framebuffer.fillRect(x, y, width, height, style.background); + + // заполнение + int fillWidth = static_cast(width * value); + if (fillWidth > 0) + { + framebuffer.fillRect(x, y, fillWidth, height, style.fill); + } + + // рамка + if (style.drawBorder) + { + framebuffer.drawRect(x, y, width, height, style.border); + } + } +} // namespace Display::UI::Bar diff --git a/src/Display/UI/Bar/Bar.h b/src/Display/UI/Bar/Bar.h new file mode 100644 index 0000000..b1796f6 --- /dev/null +++ b/src/Display/UI/Bar/Bar.h @@ -0,0 +1,34 @@ +#pragma once +#include "Display/Graphics/Framebuffer.h" +#include "Display/Graphics/Color.h" + +namespace Display::UI::Bar +{ + class Bar + { + public: + struct Style + { + Display::Graphics::Color background; + Display::Graphics::Color fill; + Display::Graphics::Color border; + bool drawBorder; + + Style() + : background{30, 30, 30}, fill{0, 180, 0}, border{80, 80, 80}, drawBorder(true) + { + } + }; + + Bar(int width, int height, Style style = {}); + + void draw(Display::Graphics::Framebuffer &framebuffer, + int x, int y, + float value); // 0.0 .. 1.0 + + private: + int width; + int height; + Style style; + }; +} // namespace Display::UI::Bar diff --git a/src/Display/UI/Bar/CMakeLists.txt b/src/Display/UI/Bar/CMakeLists.txt new file mode 100644 index 0000000..a8cb030 --- /dev/null +++ b/src/Display/UI/Bar/CMakeLists.txt @@ -0,0 +1,15 @@ +message(STATUS " Configuring Bar") + +add_library(Bar + Bar.cpp +) + +target_link_libraries(Bar + PRIVATE Graphics +) + +# include-root общий +target_include_directories(Bar + PUBLIC + ${INCLUDE_BASE_DIR} +) \ No newline at end of file diff --git a/src/Display/UI/CMakeLists.txt b/src/Display/UI/CMakeLists.txt new file mode 100644 index 0000000..e694d96 --- /dev/null +++ b/src/Display/UI/CMakeLists.txt @@ -0,0 +1,21 @@ +message(STATUS " Configuring UI") + +add_library(UI + INTERFACE +) + +add_subdirectory(Bar) +add_subdirectory(Text) + +target_link_libraries(UI + INTERFACE Bar Text +) + +target_include_directories(UI + INTERFACE + ${INCLUDE_BASE_DIR} +) + + + + diff --git a/src/Display/UI/Text/BitmapFont.cpp b/src/Display/UI/Text/BitmapFont.cpp new file mode 100644 index 0000000..069908e --- /dev/null +++ b/src/Display/UI/Text/BitmapFont.cpp @@ -0,0 +1,36 @@ +#include "Display/UI/Text/BitmapFont.h" + +namespace Display::UI::Text +{ + // 5x7 bitmap digits (0–9) + static const uint8_t digits[10][7] = { + {0x1E, 0x29, 0x25, 0x23, 0x21, 0x29, 0x1E}, // 0 + {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1 + {0x1E, 0x21, 0x01, 0x0E, 0x10, 0x20, 0x3F}, // 2 + {0x1E, 0x21, 0x02, 0x06, 0x01, 0x21, 0x1E}, // 3 + {0x02, 0x06, 0x0A, 0x12, 0x3F, 0x02, 0x02}, // 4 + {0x3F, 0x20, 0x3E, 0x01, 0x01, 0x21, 0x1E}, // 5 + {0x0E, 0x10, 0x20, 0x3E, 0x21, 0x21, 0x1E}, // 6 + {0x3F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, // 7 + {0x1E, 0x21, 0x21, 0x1E, 0x21, 0x21, 0x1E}, // 8 + {0x1E, 0x21, 0x21, 0x1F, 0x01, 0x02, 0x1C} // 9 + }; + + void BitmapFont::drawDigit(Display::Graphics::Framebuffer &fb, int x, int y, int d, uint16_t color) + { + if (d < 0 || d > 9) + return; + + for (int row = 0; row < 7; ++row) + { + for (int col = 0; col < 5; ++col) + { + if (digits[d][row] & (1 << (4 - col))) + { + // Рисуем пиксель как 2x2 квадрат для читаемости + fb.fillRect(x + col * 2, y + row * 2, 2, 2, color); + } + } + } + } +} // namespace Display::UI::Text diff --git a/src/Display/UI/Text/BitmapFont.h b/src/Display/UI/Text/BitmapFont.h new file mode 100644 index 0000000..99432f0 --- /dev/null +++ b/src/Display/UI/Text/BitmapFont.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include "Display/Graphics/Framebuffer.h" +#include "Display/Graphics/Color.h" + +namespace Display::UI::Text +{ + class BitmapFont + { + public: + void drawDigit(Display::Graphics::Framebuffer &fb, int x, int y, int d, uint16_t color); + }; +} // namespace Display::UI::Text diff --git a/src/Display/UI/Text/CMakeLists.txt b/src/Display/UI/Text/CMakeLists.txt new file mode 100644 index 0000000..cb741cb --- /dev/null +++ b/src/Display/UI/Text/CMakeLists.txt @@ -0,0 +1,21 @@ +message(STATUS " Configuring Text") + +add_library(Text + BitmapFont.cpp + FontFace.cpp + GlyphCache.cpp + Renderer.cpp +) + +find_package(Freetype REQUIRED) + +target_link_libraries(Text + PRIVATE Graphics ${FREETYPE_LIBRARIES} +) + +# include-root общий +target_include_directories(Text + PUBLIC + ${INCLUDE_BASE_DIR} + ${FREETYPE_INCLUDE_DIRS} +) \ No newline at end of file diff --git a/src/Display/UI/Text/FontFace.cpp b/src/Display/UI/Text/FontFace.cpp new file mode 100644 index 0000000..5173c73 --- /dev/null +++ b/src/Display/UI/Text/FontFace.cpp @@ -0,0 +1,38 @@ +#include "Display/UI/Text/FontFace.h" +#include +#include FT_FREETYPE_H +#include + +namespace Display::UI::Text +{ + struct FontFace::FreeType + { + FT_Library library = nullptr; + FT_Face face = nullptr; + }; + + FontFace::FontFace(const std::string &path, int pixelSize) + : freetype(std::make_unique()) + { + if (FT_Init_FreeType(&freetype->library)) + throw std::runtime_error("FT_Init_FreeType failed"); + + if (FT_New_Face(freetype->library, path.c_str(), 0, &freetype->face)) + throw std::runtime_error("FT_New_Face failed"); + + FT_Set_Pixel_Sizes(freetype->face, 0, pixelSize); + } + + FontFace::~FontFace() + { + if (freetype->face) + FT_Done_Face(freetype->face); + if (freetype->library) + FT_Done_FreeType(freetype->library); + } + + void *FontFace::getFace() const + { + return freetype->face; + } +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/FontFace.h b/src/Display/UI/Text/FontFace.h new file mode 100644 index 0000000..3e38f8d --- /dev/null +++ b/src/Display/UI/Text/FontFace.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace Display::UI::Text +{ + class FontFace + { + public: + FontFace(const std::string &path, int pixelSize); + ~FontFace(); + + FontFace(const FontFace &) = delete; + FontFace &operator=(const FontFace &) = delete; + + FontFace(FontFace &&) = delete; + FontFace &operator=(FontFace &&) = delete; + + void *getFace() const; + + private: + struct FreeType; + std::unique_ptr freetype; + }; +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/Glyph.h b/src/Display/UI/Text/Glyph.h new file mode 100644 index 0000000..9383458 --- /dev/null +++ b/src/Display/UI/Text/Glyph.h @@ -0,0 +1,15 @@ +#include +#include + +namespace Display::UI::Text +{ + struct Glyph + { + int width; + int height; + int bearingX; + int bearingY; + int advance; + std::vector buffer; // grayscale bitmap + }; +} // namespace Display::UI::Text diff --git a/src/Display/UI/Text/GlyphCache.cpp b/src/Display/UI/Text/GlyphCache.cpp new file mode 100644 index 0000000..6b18ccf --- /dev/null +++ b/src/Display/UI/Text/GlyphCache.cpp @@ -0,0 +1,35 @@ +#include "Display/UI/Text/GlyphCache.h" +#include +#include +#include "Display/UI/Text/FontFace.h" +#include +#include FT_FREETYPE_H + +namespace Display::UI::Text +{ + GlyphCache::GlyphCache(const FontFace &fontFace) : fontFace(fontFace) {} + + const Glyph &GlyphCache::getGlyph(char c) + { + FT_Face face = static_cast(fontFace.getFace()); + auto it = cache.find(c); + if (it != cache.end()) + return it->second; + + if (!face) + throw std::runtime_error("FontFace not initialized!"); + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + throw std::runtime_error("FT_Load_Char failed"); + FT_GlyphSlot g = face->glyph; + + Glyph glyph; + glyph.width = g->bitmap.width; + glyph.height = g->bitmap.rows; + glyph.bearingX = g->bitmap_left; + glyph.bearingY = g->bitmap_top; + glyph.advance = g->advance.x >> 6; + glyph.buffer.assign(g->bitmap.buffer, g->bitmap.buffer + g->bitmap.width * g->bitmap.rows); + + return cache.emplace(c, std::move(glyph)).first->second; + } +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/GlyphCache.h b/src/Display/UI/Text/GlyphCache.h new file mode 100644 index 0000000..799f497 --- /dev/null +++ b/src/Display/UI/Text/GlyphCache.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Display/UI/Text/Glyph.h" + +#include +#include + +namespace Display::UI::Text +{ + class FontFace; + class GlyphCache + { + public: + GlyphCache(const FontFace &fontFace); + + GlyphCache(const GlyphCache &) = delete; + GlyphCache &operator=(const GlyphCache &) = delete; + + GlyphCache(GlyphCache &&) = delete; + GlyphCache &operator=(GlyphCache &&) = delete; + + const Glyph &getGlyph(char c); + + private: + const FontFace &fontFace; + std::unordered_map cache; + }; +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/Renderer.cpp b/src/Display/UI/Text/Renderer.cpp new file mode 100644 index 0000000..1e3ed49 --- /dev/null +++ b/src/Display/UI/Text/Renderer.cpp @@ -0,0 +1,47 @@ +#include "Display/UI/Text/Renderer.h" +#include "Display/UI/Text/UTFDecoder.h" + +#include + +namespace Display::UI::Text +{ + + Renderer::Renderer() : fontFace("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf", 12), glyphCache(fontFace) {} + + void Renderer::drawText(Display::Graphics::Framebuffer &fb, int x, int y, + const std::string &text, const Display::Graphics::Color &color) + { + int penX = x; + int baseline = y; + + for (char ch : text) + { + const Glyph &g = glyphCache.getGlyph(ch); + + for (int row = 0; row < g.height; ++row) + { + int py = baseline - g.bearingY + row; + + for (int col = 0; col < g.width; ++col) + { + int px = penX + col + g.bearingX; + + uint8_t alpha = g.buffer[row * g.width + col]; + if (!alpha) + continue; + + Display::Graphics::Color bg = fb.getPixel(px, py); + Display::Graphics::Color out; + out.r = (alpha * color.r + (255 - alpha) * bg.r) / 255; + out.g = (alpha * color.g + (255 - alpha) * bg.g) / 255; + out.b = (alpha * color.b + (255 - alpha) * bg.b) / 255; + + fb.setPixel(px, py, out); + } + } + + penX += g.advance; + } + } + +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/Renderer.h b/src/Display/UI/Text/Renderer.h new file mode 100644 index 0000000..dcb59cb --- /dev/null +++ b/src/Display/UI/Text/Renderer.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Display/Graphics/Framebuffer.h" +#include "Display/UI/Text/FontFace.h" +#include "Display/UI/Text/GlyphCache.h" +#include "Display/Graphics/Color.h" + +#include + +namespace Display::UI::Text +{ + + class Renderer + { + public: + Renderer(); + + Renderer(const Renderer &) = delete; + Renderer &operator=(const Renderer &) = delete; + + void drawText(Display::Graphics::Framebuffer &fb, int x, int y, const std::string &text, const Display::Graphics::Color &color); + + private: + FontFace fontFace; + GlyphCache glyphCache; + }; + +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Display/UI/Text/UTFDecoder.h b/src/Display/UI/Text/UTFDecoder.h new file mode 100644 index 0000000..25eefd2 --- /dev/null +++ b/src/Display/UI/Text/UTFDecoder.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +namespace Display::UI::Text +{ + + inline bool utf8Next(std::string_view &s, uint32_t &codepoint) + { + if (s.empty()) + return false; + + uint8_t c = s[0]; + + if ((c & 0x80) == 0) + { + codepoint = c; + s.remove_prefix(1); + } + else if ((c & 0xE0) == 0xC0) + { + codepoint = ((c & 0x1F) << 6) | (s[1] & 0x3F); + s.remove_prefix(2); + } + else if ((c & 0xF0) == 0xE0) + { + codepoint = ((c & 0x0F) << 12) | + ((s[1] & 0x3F) << 6) | + (s[2] & 0x3F); + s.remove_prefix(3); + } + else + { + return false; + } + return true; + } + +} // namespace Display::UI::Text \ No newline at end of file diff --git a/src/Helpers/CMakeLists.txt b/src/Helpers/CMakeLists.txt new file mode 100644 index 0000000..baf9e65 --- /dev/null +++ b/src/Helpers/CMakeLists.txt @@ -0,0 +1,17 @@ +message(STATUS " Configuring Model") + +# Генерация Version.h из Version.h.in +configure_file( + Version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/Version.h +) + +# Создаём "интерфейсную библиотеку", чтобы передавать include dirs +add_library(Helpers INTERFACE) + +# Добавляем include директории: и исходники, и сгенерированные файлы +target_include_directories(Helpers + INTERFACE + ${INCLUDE_BASE_DIR} # Color.h + ${CMAKE_CURRENT_BINARY_DIR} # Version.h +) diff --git a/srcs/Version.h.in b/src/Helpers/Version.h.in similarity index 100% rename from srcs/Version.h.in rename to src/Helpers/Version.h.in diff --git a/src/Metrics/CMakeLists.txt b/src/Metrics/CMakeLists.txt new file mode 100644 index 0000000..6cc89f5 --- /dev/null +++ b/src/Metrics/CMakeLists.txt @@ -0,0 +1,11 @@ +message(STATUS " Configuring Metrics") + +add_library(Metrics + Collector.cpp + Host.cpp +) + +target_include_directories(Metrics + PUBLIC + ${INCLUDE_BASE_DIR} +) diff --git a/src/Metrics/Collector.cpp b/src/Metrics/Collector.cpp new file mode 100644 index 0000000..44bfef3 --- /dev/null +++ b/src/Metrics/Collector.cpp @@ -0,0 +1,150 @@ +#include "Metrics/Collector.h" + +#include +#include +#include +#include +#include + +namespace Metrics +{ + + Collector::Collector() + { + } + + Host Collector::collect() + { + Host host; + + auto curCpu = readCpuTimes(); + + if (prevCpu_.empty()) + prevCpu_ = curCpu; + + host.cpu.loads.resize(curCpu.size()); + + for (size_t i = 0; i < curCpu.size(); ++i) + host.cpu.loads[i] = cpuLoad(prevCpu_[i], curCpu[i]); + + prevCpu_ = curCpu; + + host.memory = readMemory(); + host.disks.push_back(readDisk()); + readLoad(host.load1, host.load5, host.load15); + host.uptime = readUptime(); + host.hostname = readHostname(); + + return host; + } + + std::vector Collector::readCpuTimes() + { + std::ifstream f("/proc/stat"); + std::vector out; + + std::string line; + while (std::getline(f, line)) + { + if (line.rfind("cpu", 0) != 0 || line == "cpu") + continue; + + std::istringstream ss(line); + std::string cpu; + Collector::CpuTimes t; + + ss >> cpu >> t.user >> t.nice >> t.system >> t.idle >> t.iowait >> t.irq >> t.softirq >> t.steal; + + out.push_back(t); + } + + return out; + } + + float Collector::cpuLoad(const Collector::CpuTimes &prev, + const Collector::CpuTimes &cur) + { + uint64_t prevIdle = prev.idle + prev.iowait; + uint64_t curIdle = cur.idle + cur.iowait; + + uint64_t prevTotal = + prev.user + prev.nice + prev.system + + prevIdle + prev.irq + prev.softirq + prev.steal; + + uint64_t curTotal = + cur.user + cur.nice + cur.system + + curIdle + cur.irq + cur.softirq + cur.steal; + + uint64_t totalDiff = curTotal - prevTotal; + uint64_t idleDiff = curIdle - prevIdle; + + if (totalDiff == 0) + return 0.f; + + return 100.f * (totalDiff - idleDiff) / totalDiff; + } + + Memory Collector::readMemory() + { + std::ifstream f("/proc/meminfo"); + std::string key; + uint64_t value; + std::string unit; + + uint64_t total = 0, available = 0; + + while (f >> key >> value >> unit) + { + if (key == "MemTotal:") + total = value * 1024; + else if (key == "MemAvailable:") + available = value * 1024; + } + + Memory memory; + memory.total = total; + memory.available = available; + memory.used = total - available; + return memory; + } + + Disk Collector::readDisk(const char *path) + { + struct statvfs vfs{}; + statvfs(path, &vfs); + + uint64_t total = vfs.f_blocks * vfs.f_frsize; + uint64_t free = vfs.f_bavail * vfs.f_frsize; + + Disk d; + d.name = path; + d.total = total; + d.free = free; + d.used = total - free; + return d; + } + + void Collector::readLoad(float &l1, float &l5, float &l15) + { + std::ifstream f("/proc/loadavg"); + f >> l1 >> l5 >> l15; + } + + uint64_t Collector::readUptime() + { + std::ifstream f("/proc/uptime"); + double up; + f >> up; + return static_cast(up); + } + + std::string Collector::readHostname() + { + char buf[HOST_NAME_MAX + 1] = {}; + if (gethostname(buf, sizeof(buf)) == 0) + return std::string(buf); + + return "Unknown"; + } + +} \ No newline at end of file diff --git a/src/Metrics/Collector.h b/src/Metrics/Collector.h new file mode 100644 index 0000000..c719602 --- /dev/null +++ b/src/Metrics/Collector.h @@ -0,0 +1,32 @@ +#pragma once +#include "Metrics/Host.h" +#include + +namespace Metrics +{ + class Collector + { + public: + explicit Collector(); + + Host collect(); + + private: + struct CpuTimes + { + uint64_t user = 0, nice = 0, system = 0, + idle = 0, iowait = 0, irq = 0, + softirq = 0, steal = 0; + }; + + std::vector prevCpu_; + + std::vector readCpuTimes(); + float cpuLoad(const Collector::CpuTimes &prev, const Collector::CpuTimes &cur); + Memory readMemory(); + Disk readDisk(const char *path = "/"); + void readLoad(float &l1, float &l5, float &l15); + uint64_t readUptime(); + std::string readHostname(); + }; +} // namespace Metrics diff --git a/src/Metrics/Cpu.h b/src/Metrics/Cpu.h new file mode 100644 index 0000000..ec36bb2 --- /dev/null +++ b/src/Metrics/Cpu.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Metrics +{ + struct Cpu + { + std::vector loads; // длина = num_cores + }; +} // namespace Metrics \ No newline at end of file diff --git a/src/Metrics/Disk.h b/src/Metrics/Disk.h new file mode 100644 index 0000000..ba0ebca --- /dev/null +++ b/src/Metrics/Disk.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace Metrics +{ + struct Disk + { + std::string name; + float used; + float free; + float total; + }; +} // namespace Metrics diff --git a/src/Metrics/Host.cpp b/src/Metrics/Host.cpp new file mode 100644 index 0000000..8a66519 --- /dev/null +++ b/src/Metrics/Host.cpp @@ -0,0 +1,143 @@ +#include "Metrics/Host.h" + +#include +#include +#include + +namespace Metrics +{ + + // ---- Вспомогательные функции для float <-> uint32_t ---- + static uint32_t floatToNetwork(float f) + { + uint32_t tmp; + static_assert(sizeof(tmp) == sizeof(f), "Size mismatch"); + std::memcpy(&tmp, &f, sizeof(f)); + return htonl(tmp); + } + + static float networkToFloat(uint32_t n) + { + uint32_t tmp = ntohl(n); + float f; + std::memcpy(&f, &tmp, sizeof(f)); + return f; + } + + // ---- Сериализация ---- + std::vector Host::serialize() const + { + std::vector buffer; + buffer.push_back(1); // версия протокола + + // Host + buffer.push_back(static_cast(hostname.size())); + buffer.insert(buffer.end(), hostname.begin(), hostname.end()); + + // CPU + buffer.push_back(static_cast(cpu.loads.size())); + for (float f : cpu.loads) + { + uint32_t net = floatToNetwork(f); + buffer.insert(buffer.end(), + reinterpret_cast(&net), + reinterpret_cast(&net) + 4); + } + + // Memory + uint32_t usedNet = floatToNetwork(memory.used); + uint32_t totalNet = floatToNetwork(memory.total); + buffer.insert(buffer.end(), + reinterpret_cast(&usedNet), + reinterpret_cast(&usedNet) + sizeof(usedNet)); + buffer.insert(buffer.end(), + reinterpret_cast(&totalNet), + reinterpret_cast(&totalNet) + sizeof(totalNet)); + + // Disks + buffer.push_back(static_cast(disks.size())); + for (const auto &d : disks) + { + buffer.push_back(static_cast(d.name.size())); + buffer.insert(buffer.end(), d.name.begin(), d.name.end()); + + uint32_t used = floatToNetwork(d.used); + uint32_t total = floatToNetwork(d.total); + buffer.insert(buffer.end(), + reinterpret_cast(&used), + reinterpret_cast(&used) + 4); + buffer.insert(buffer.end(), + reinterpret_cast(&total), + reinterpret_cast(&total) + 4); + } + + return buffer; + } + + // ---- Десериализация ---- + Host Host::deserialize(const std::vector &buffer) + { + Host m; + size_t pos = 0; + if (buffer.size() < 1) + throw std::runtime_error("Buffer too small"); + uint8_t version = buffer[pos++]; + if (version != 1) + throw std::runtime_error("Unsupported protocol version"); + + // Host + uint8_t hostLen = buffer[pos++]; + if (pos + hostLen > buffer.size()) + throw std::runtime_error("Buffer too small for host"); + m.hostname = std::string(buffer.begin() + pos, buffer.begin() + pos + hostLen); + pos += hostLen; + + // CPU + uint8_t numCpu = buffer[pos++]; + if (pos + numCpu * 4 > buffer.size()) + throw std::runtime_error("Buffer too small for CPU"); + for (int i = 0; i < numCpu; ++i) + { + uint32_t tmp; + std::memcpy(&tmp, &buffer[pos], 4); + m.cpu.loads.push_back(networkToFloat(tmp)); + pos += 4; + } + + // Memory + if (pos + 8 > buffer.size()) + throw std::runtime_error("Buffer too small for memory"); + uint32_t memUsed, memTotal; + std::memcpy(&memUsed, &buffer[pos], 4); + pos += 4; + std::memcpy(&memTotal, &buffer[pos], 4); + pos += 4; + m.memory.used = networkToFloat(memUsed); + m.memory.total = networkToFloat(memTotal); + + // Disks + if (pos + 1 > buffer.size()) + throw std::runtime_error("Buffer too small for disks count"); + uint8_t numDisks = buffer[pos++]; + for (int i = 0; i < numDisks; ++i) + { + if (pos + 1 > buffer.size()) + throw std::runtime_error("Buffer too small for disk name length"); + uint8_t nameLen = buffer[pos++]; + if (pos + nameLen + 8 > buffer.size()) + throw std::runtime_error("Buffer too small for disk data"); + std::string diskName(buffer.begin() + pos, buffer.begin() + pos + nameLen); + pos += nameLen; + + uint32_t used, total; + std::memcpy(&used, &buffer[pos], 4); + pos += 4; + std::memcpy(&total, &buffer[pos], 4); + pos += 4; + + m.disks.push_back({diskName, networkToFloat(used), networkToFloat(total)}); + } + + return m; + } +} // namespace Metrics \ No newline at end of file diff --git a/src/Metrics/Host.h b/src/Metrics/Host.h new file mode 100644 index 0000000..8386671 --- /dev/null +++ b/src/Metrics/Host.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include + +#include "Metrics/Cpu.h" +#include "Metrics/Memory.h" +#include "Metrics/Disk.h" + +namespace Metrics +{ + struct Host + { + std::string hostname; + Cpu cpu; + Memory memory; + std::vector disks; + uint64_t uptime; + float load1, load5, load15; + + // Сериализация в вектор байт + std::vector serialize() const; + + // Десериализация из вектора байт + static Host deserialize(const std::vector &buffer); + }; +} // namespace Metrics diff --git a/src/Metrics/Memory.h b/src/Metrics/Memory.h new file mode 100644 index 0000000..b4932e9 --- /dev/null +++ b/src/Metrics/Memory.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Metrics +{ + struct Memory + { + float used; + float available; + float total; + }; +} // namespace Metrics diff --git a/src/Model/CMakeLists.txt b/src/Model/CMakeLists.txt new file mode 100644 index 0000000..fa503bb --- /dev/null +++ b/src/Model/CMakeLists.txt @@ -0,0 +1,14 @@ +message(STATUS " Configuring Model") + +add_library(Model + HostRegistry.cpp +) + +target_link_libraries(Model + PRIVATE Metrics +) + +target_include_directories(Model + PUBLIC + ${INCLUDE_BASE_DIR} +) \ No newline at end of file diff --git a/src/Model/HostRegistry.cpp b/src/Model/HostRegistry.cpp new file mode 100644 index 0000000..e6e89b1 --- /dev/null +++ b/src/Model/HostRegistry.cpp @@ -0,0 +1,16 @@ +#include "Model/HostRegistry.h" + +namespace Model +{ + void HostRegistry::update(const std::string &host, const Metrics::Host &m) + { + std::lock_guard lock(mutex_); + hosts_[host] = m; + } + + std::map HostRegistry::snapshot() + { + std::lock_guard lock(mutex_); + return hosts_; + } +} // namespace Model diff --git a/src/Model/HostRegistry.h b/src/Model/HostRegistry.h new file mode 100644 index 0000000..b1b651d --- /dev/null +++ b/src/Model/HostRegistry.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include "Metrics/Host.h" + +namespace Model +{ + class HostRegistry + { + public: + void update(const std::string &host, const Metrics::Host &m); + std::map snapshot(); + + private: + std::map hosts_; + std::mutex mutex_; + }; +} // namespace Model diff --git a/src/Network/Agent.cpp b/src/Network/Agent.cpp new file mode 100644 index 0000000..eca558a --- /dev/null +++ b/src/Network/Agent.cpp @@ -0,0 +1,53 @@ +#include "Network/Agent.h" +#include + +namespace Network +{ + + Agent::Agent( + Client client, + Metrics::Collector collector, + std::chrono::milliseconds interval) + : client_(std::move(client)), collector_(std::move(collector)), interval_(interval) + { + } + + Agent::~Agent() + { + stop(); + } + + void Agent::start() + { + if (running_) + return; + running_ = true; + worker_ = std::thread(&Agent::run, this); + } + + void Agent::stop() + { + running_ = false; + if (worker_.joinable()) + worker_.join(); + } + + void Agent::run() + { + while (running_) + { + try + { + auto metrics = collector_.collect(); + client_.sendMetrics(metrics); + } + catch (const std::exception &e) + { + std::cerr << "Agent error: " << e.what() << std::endl; + } + + std::this_thread::sleep_for(interval_); + } + } + +} \ No newline at end of file diff --git a/src/Network/Agent.h b/src/Network/Agent.h new file mode 100644 index 0000000..c6e462b --- /dev/null +++ b/src/Network/Agent.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Network/Client.h" +#include "Metrics/Collector.h" + +#include +#include +#include + +namespace Network +{ + + class Agent + { + public: + Agent( + Client client, + Metrics::Collector collector, + std::chrono::milliseconds interval); + + ~Agent(); + + void start(); + void stop(); + + private: + void run(); + + Client client_; + Metrics::Collector collector_; + std::chrono::milliseconds interval_; + + std::thread worker_; + std::atomic running_{false}; + }; + +} \ No newline at end of file diff --git a/src/Network/CMakeLists.txt b/src/Network/CMakeLists.txt new file mode 100644 index 0000000..69b3a64 --- /dev/null +++ b/src/Network/CMakeLists.txt @@ -0,0 +1,16 @@ +message(STATUS " Configuring Network") + +add_library(Network + Agent.cpp + Client.cpp + Server.cpp +) + +target_link_libraries(Network + PRIVATE Model Metrics +) + +target_include_directories(Network + PUBLIC + ${INCLUDE_BASE_DIR} +) \ No newline at end of file diff --git a/src/Network/Client.cpp b/src/Network/Client.cpp new file mode 100644 index 0000000..9dc947f --- /dev/null +++ b/src/Network/Client.cpp @@ -0,0 +1,69 @@ +#include "Network/Client.h" +#include +#include +#include +#include + +namespace Network +{ + + Client::Client(const std::string &host, uint16_t port) + { + sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd_ < 0) + { + perror("socket"); + throw std::runtime_error("Cannot create UDP socket"); + } + + memset(&serverAddr_, 0, sizeof(serverAddr_)); + serverAddr_.sin_family = AF_INET; + serverAddr_.sin_port = htons(port); + + if (inet_pton(AF_INET, host.c_str(), &serverAddr_.sin_addr) <= 0) + { + throw std::runtime_error("Invalid server IP"); + } + } + + Client::~Client() + { + if (sockfd_ >= 0) + close(sockfd_); + } + + Client::Client(Client &&other) noexcept + { + sockfd_ = other.sockfd_; + serverAddr_ = other.serverAddr_; + other.sockfd_ = -1; + } + + Client &Client::operator=(Client &&other) noexcept + { + if (this != &other) + { + if (sockfd_ >= 0) + close(sockfd_); + + sockfd_ = other.sockfd_; + serverAddr_ = other.serverAddr_; + other.sockfd_ = -1; + } + return *this; + } + + bool Client::sendMetrics(const Metrics::Host &metrics) + { + auto buffer = metrics.serialize(); + ssize_t sent = sendto(sockfd_, buffer.data(), buffer.size(), 0, + (struct sockaddr *)&serverAddr_, sizeof(serverAddr_)); + if (sent < 0) + { + perror("sendto"); + return false; + } + return sent == static_cast(buffer.size()); + } + +} // namespace Network \ No newline at end of file diff --git a/src/Network/Client.h b/src/Network/Client.h new file mode 100644 index 0000000..10ac047 --- /dev/null +++ b/src/Network/Client.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include "Metrics/Host.h" + +namespace Network +{ + + class Client + { + public: + Client(const std::string &host, uint16_t port); + ~Client(); + + Client(const Client &) = delete; + Client &operator=(const Client &) = delete; + + Client(Client &&other) noexcept; + Client &operator=(Client &&other) noexcept; + + // Отправить данные + bool sendMetrics(const Metrics::Host &metrics); + + private: + int sockfd_; + struct sockaddr_in serverAddr_; + }; + +} // namespace Network \ No newline at end of file diff --git a/src/Network/Server.cpp b/src/Network/Server.cpp new file mode 100644 index 0000000..9350ac5 --- /dev/null +++ b/src/Network/Server.cpp @@ -0,0 +1,109 @@ +#include "Network/Server.h" +#include +#include +#include +#include +#include "Metrics/Host.h" + +namespace Network +{ + + Server::Server(uint16_t port, Model::HostRegistry ®istry) + : port_(port), registry_(registry), running_(false) + { + + sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd_ < 0) + throw std::runtime_error("Failed to create UDP socket"); + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port_); + + if (bind(sockfd_, reinterpret_cast(&addr), sizeof(addr)) < 0) + { + throw std::runtime_error("Failed to bind UDP socket"); + } + } + + Server::~Server() + { + stop(); + close(sockfd_); + } + + void Server::start() + { + running_ = true; + serverThread_ = std::thread(&Server::run, this); + } + + void Server::stop() + { + running_ = false; + if (serverThread_.joinable()) + serverThread_.join(); + } + + void Server::run() + { + constexpr size_t BUF_SIZE = 4096; + uint8_t buffer[BUF_SIZE]; + + while (running_) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd_, &readfds); + + timeval tv{}; + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; // 500 ms + + int ret = select(sockfd_ + 1, &readfds, nullptr, nullptr, &tv); + + if (ret < 0) + { + if (errno == EINTR) + continue; + perror("select"); + break; + } + + if (ret == 0) + { + // timeout — просто проверяем running_ + continue; + } + + if (FD_ISSET(sockfd_, &readfds)) + { + sockaddr_in clientAddr{}; + socklen_t addrLen = sizeof(clientAddr); + ssize_t len = recvfrom( + sockfd_, + buffer, + BUF_SIZE, + 0, + reinterpret_cast(&clientAddr), + &addrLen); + + if (len <= 0) + continue; + + try + { + std::vector data(buffer, buffer + len); + auto metrics = Metrics::Host::deserialize(data); + registry_.update(metrics.hostname, metrics); + } + catch (const std::exception &e) + { + std::cerr << "UDP parse error: " << e.what() << std::endl; + } + } + } + } + +} // namespace Network \ No newline at end of file diff --git a/src/Network/Server.h b/src/Network/Server.h new file mode 100644 index 0000000..938c1f2 --- /dev/null +++ b/src/Network/Server.h @@ -0,0 +1,29 @@ +#pragma once +#include "Model/HostRegistry.h" +#include +#include +#include + +namespace Network +{ + + class Server + { + public: + Server(uint16_t port, Model::HostRegistry ®istry); + ~Server(); + + void start(); + void stop(); + + private: + void run(); + + int sockfd_; + uint16_t port_; + std::thread serverThread_; + std::atomic running_; + Model::HostRegistry ®istry_; + }; + +} // namespace Network \ No newline at end of file diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..9118e89 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "Display/Graphics/Framebuffer.h" +#include "Model/HostRegistry.h" +#include "Network/Server.h" +#include "Display/Graphics/Renderer.h" + +int main(int argc, char **argv) +{ + Model::HostRegistry registry; + Display::Graphics::Framebuffer fb("/dev/fb1"); + Display::Graphics::Renderer renderer(fb, registry); + Network::Server server(5005, registry); + + server.start(); + + while (true) + { + renderer.render(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + return 0; +} \ No newline at end of file diff --git a/srcs/CMakeLists.txt b/srcs/CMakeLists.txt deleted file mode 100644 index 6c4dc75..0000000 --- a/srcs/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -set(TARGET_NAME ${PROJECT_NAME}) -message(STATUS "Configuring ${TARGET_NAME}") - -# Build configuration - -configure_file( - ${CMAKE_CURRENT_LIST_DIR}/Version.h.in - ${CMAKE_CURRENT_LIST_DIR}/Version.h) - -set(${PROJECT_NAME}_SOURCES "main.cpp") -set(${PROJECT_NAME}_INCLUDES "Version.h") -set(${PROJECT_NAME}_INCLUDE_DIRS "") -set(${PROJECT_NAME}_DEPENDENCIES "") - -# Find all subdirectories -set(SUBDIRS_LIST "") -SubDirList(${CMAKE_CURRENT_LIST_DIR} SUBDIRS_LIST) - -foreach(SUBDIR IN LISTS SUBDIRS_LIST) - if (NOT ${PROJECT_NAME}_BUILD_SUBDIR_${SUBDIR}) - option(${PROJECT_NAME}_BUILD_SUBDIR_${SUBDIR} "Build ${SUBDIR} subdirectory" ON) - endif() - if(${PROJECT_NAME}_BUILD_SUBDIR_${SUBDIR}) - file(GLOB SUBDIR_SOURCES ${CMAKE_CURRENT_LIST_DIR}/${SUBDIR}/*.cc ${CMAKE_CURRENT_LIST_DIR}/${SUBDIR}/*.cpp) - list(APPEND ${PROJECT_NAME}_SOURCES ${SUBDIR_SOURCES}) - file(GLOB SUBDIR_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/${SUBDIR}/*.h) - list(APPEND ${PROJECT_NAME}_INCLUDES ${SUBDIR_INCLUDES}) - list(APPEND ${PROJECT_NAME}_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/${SUBDIR}) - endif() -endforeach() - -add_executable(${TARGET_NAME} ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_INCLUDES}) -if(NOT ${${PROJECT_NAME}_DEPENDENCIES}) - add_dependencies(${TARGET_NAME} ${${PROJECT_NAME}_DEPENDENCIES}) - target_link_libraries(${TARGET_NAME} ${${PROJECT_NAME}_DEPENDENCIES}) -endif() - -#include_directories(${CMAKE_CURRENT_LIST_DIR}) -#target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) - -install(TARGETS ${TARGET_NAME} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${TARGET_NAME}/ - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${TARGET_NAME}/ - ARCHIVE DESTINATION ${CMAKE_INSTALL_SHAREDSTATEDIR}) - -message(STATUS "") -message(STATUS "${TARGET_NAME} summary") -message(STATUS "") -foreach(SUBDIR IN LISTS SUBDIRS_LIST) - message(STATUS "Build subdirectory ${SUBDIR}: \t \t ${${PROJECT_NAME}_BUILD_SUBDIR_${SUBDIR}}") -endforeach() -message(STATUS "") \ No newline at end of file diff --git a/srcs/main.cpp b/srcs/main.cpp deleted file mode 100644 index 73e32ca..0000000 --- a/srcs/main.cpp +++ /dev/null @@ -1,5 +0,0 @@ - -int main(int argc, char **argv) -{ - return 0; -} \ No newline at end of file diff --git a/tools/cmake/SubDirList.cmake b/tools/cmake/SubDirList.cmake index 30124cc..7fde817 100644 --- a/tools/cmake/SubDirList.cmake +++ b/tools/cmake/SubDirList.cmake @@ -1,8 +1,25 @@ macro(SubDirList curdir result) file(GLOB children RELATIVE ${curdir} ${curdir}/*) + foreach(child ${children}) if(IS_DIRECTORY ${curdir}/${child}) list(APPEND ${result} ${child}) endif() endforeach() endmacro() + +macro(SubDirListRecurse curdir basedir result) + file(GLOB children RELATIVE ${curdir} ${curdir}/*) + + foreach(child ${children}) + if(IS_DIRECTORY ${curdir}/${child}) + if(${child} STREQUAL ${basedir}) + list(APPEND ${result} ${child}) + else() + file(RELATIVE_PATH relpath ${basedir} ${curdir}/${child}) + list(APPEND ${result} ${relpath}) + SubDirListRecurse(${curdir}/${child} ${basedir} ${result}) + endif() + endif() + endforeach() +endmacro()