First commit
This commit is contained in:
Vendored
+32
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+5
-149
@@ -1,14 +1,11 @@
|
|||||||
# CMakeList.txt : Top-level CMake project file, do global configuration
|
# CMakeList.txt : Top-level CMake project file, do global configuration
|
||||||
# and include sub-projects here.
|
# and include sub-projects here.
|
||||||
#
|
#
|
||||||
cmake_minimum_required (VERSION 3.10)
|
cmake_minimum_required (VERSION 3.16)
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/tools/cmake)
|
|
||||||
include(SubDirList)
|
|
||||||
|
|
||||||
get_filename_component(PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
get_filename_component(PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||||
string(REPLACE " " "_" PROJECT_DIR ${PROJECT_DIR})
|
string(REPLACE " " "_" PROJECT_DIR ${PROJECT_DIR})
|
||||||
project(${PROJECT_DIR})
|
project(${PROJECT_DIR} LANGUAGES C CXX)
|
||||||
|
|
||||||
set(PROJECT_VERSION_MAJOR 0)
|
set(PROJECT_VERSION_MAJOR 0)
|
||||||
set(PROJECT_VERSION_MINOR 1)
|
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)
|
set(CMAKE_CACHEFILE_DIR ${CMAKE_SOURCE_DIR}/build)
|
||||||
|
|
||||||
include(CTest)
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CPACK_PROJECT_NAME ${${PROJECT_NAME}_NAME})
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CPACK_PROJECT_VERSION ${${PROJECT_NAME}_VERSION})
|
|
||||||
include(CPack)
|
|
||||||
|
|
||||||
option(${PROJECT_NAME}_BUILD_ENGINE "Build engine library" ON)
|
set(INCLUDE_BASE_DIR ${PROJECT_SOURCE_DIR}/src)
|
||||||
option(${PROJECT_NAME}_BUILD_APPS "Build supplimentary applications" ON)
|
|
||||||
option(${PROJECT_NAME}_BUILD_DEPS "Build dependencies" ON)
|
|
||||||
|
|
||||||
option(ONLY_COVERAGE "Build only tests necessary for coverage" OFF)
|
add_subdirectory("src")
|
||||||
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")
|
|
||||||
|
|||||||
@@ -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 "")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#include "Framebuffer.h"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <linux/fb.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<uint8_t *>(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<uint16_t *>(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<uint16_t *>(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<uint16_t *>(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<uint16_t *>(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
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Display/Graphics/Color.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -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 <string>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include "Display/UI/Bar/Bar.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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<int>(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
|
||||||
@@ -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
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#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
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include "Display/UI/Text/FontFace.h"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<FreeType>())
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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> freetype;
|
||||||
|
};
|
||||||
|
} // namespace Display::UI::Text
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Display::UI::Text
|
||||||
|
{
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int bearingX;
|
||||||
|
int bearingY;
|
||||||
|
int advance;
|
||||||
|
std::vector<uint8_t> buffer; // grayscale bitmap
|
||||||
|
};
|
||||||
|
} // namespace Display::UI::Text
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#include "Display/UI/Text/GlyphCache.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <iostream>
|
||||||
|
#include "Display/UI/Text/FontFace.h"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#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<FT_Face>(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
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Display/UI/Text/Glyph.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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<char, Glyph> cache;
|
||||||
|
};
|
||||||
|
} // namespace Display::UI::Text
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "Display/UI/Text/Renderer.h"
|
||||||
|
#include "Display/UI/Text/UTFDecoder.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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 <string>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string_view>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
message(STATUS " Configuring Metrics")
|
||||||
|
|
||||||
|
add_library(Metrics
|
||||||
|
Collector.cpp
|
||||||
|
Host.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(Metrics
|
||||||
|
PUBLIC
|
||||||
|
${INCLUDE_BASE_DIR}
|
||||||
|
)
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#include "Metrics/Collector.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sys/statvfs.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
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::CpuTimes> Collector::readCpuTimes()
|
||||||
|
{
|
||||||
|
std::ifstream f("/proc/stat");
|
||||||
|
std::vector<Collector::CpuTimes> 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<uint64_t>(up);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Collector::readHostname()
|
||||||
|
{
|
||||||
|
char buf[HOST_NAME_MAX + 1] = {};
|
||||||
|
if (gethostname(buf, sizeof(buf)) == 0)
|
||||||
|
return std::string(buf);
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Metrics/Host.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<CpuTimes> prevCpu_;
|
||||||
|
|
||||||
|
std::vector<Collector::CpuTimes> 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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Metrics
|
||||||
|
{
|
||||||
|
struct Cpu
|
||||||
|
{
|
||||||
|
std::vector<float> loads; // длина = num_cores
|
||||||
|
};
|
||||||
|
} // namespace Metrics
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Metrics
|
||||||
|
{
|
||||||
|
struct Disk
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
float used;
|
||||||
|
float free;
|
||||||
|
float total;
|
||||||
|
};
|
||||||
|
} // namespace Metrics
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
#include "Metrics/Host.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstring>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
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<uint8_t> Host::serialize() const
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
buffer.push_back(1); // версия протокола
|
||||||
|
|
||||||
|
// Host
|
||||||
|
buffer.push_back(static_cast<uint8_t>(hostname.size()));
|
||||||
|
buffer.insert(buffer.end(), hostname.begin(), hostname.end());
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
buffer.push_back(static_cast<uint8_t>(cpu.loads.size()));
|
||||||
|
for (float f : cpu.loads)
|
||||||
|
{
|
||||||
|
uint32_t net = floatToNetwork(f);
|
||||||
|
buffer.insert(buffer.end(),
|
||||||
|
reinterpret_cast<uint8_t *>(&net),
|
||||||
|
reinterpret_cast<uint8_t *>(&net) + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
uint32_t usedNet = floatToNetwork(memory.used);
|
||||||
|
uint32_t totalNet = floatToNetwork(memory.total);
|
||||||
|
buffer.insert(buffer.end(),
|
||||||
|
reinterpret_cast<uint8_t *>(&usedNet),
|
||||||
|
reinterpret_cast<uint8_t *>(&usedNet) + sizeof(usedNet));
|
||||||
|
buffer.insert(buffer.end(),
|
||||||
|
reinterpret_cast<uint8_t *>(&totalNet),
|
||||||
|
reinterpret_cast<uint8_t *>(&totalNet) + sizeof(totalNet));
|
||||||
|
|
||||||
|
// Disks
|
||||||
|
buffer.push_back(static_cast<uint8_t>(disks.size()));
|
||||||
|
for (const auto &d : disks)
|
||||||
|
{
|
||||||
|
buffer.push_back(static_cast<uint8_t>(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<uint8_t *>(&used),
|
||||||
|
reinterpret_cast<uint8_t *>(&used) + 4);
|
||||||
|
buffer.insert(buffer.end(),
|
||||||
|
reinterpret_cast<uint8_t *>(&total),
|
||||||
|
reinterpret_cast<uint8_t *>(&total) + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Десериализация ----
|
||||||
|
Host Host::deserialize(const std::vector<uint8_t> &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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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<Disk> disks;
|
||||||
|
uint64_t uptime;
|
||||||
|
float load1, load5, load15;
|
||||||
|
|
||||||
|
// Сериализация в вектор байт
|
||||||
|
std::vector<uint8_t> serialize() const;
|
||||||
|
|
||||||
|
// Десериализация из вектора байт
|
||||||
|
static Host deserialize(const std::vector<uint8_t> &buffer);
|
||||||
|
};
|
||||||
|
} // namespace Metrics
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Metrics
|
||||||
|
{
|
||||||
|
struct Memory
|
||||||
|
{
|
||||||
|
float used;
|
||||||
|
float available;
|
||||||
|
float total;
|
||||||
|
};
|
||||||
|
} // namespace Metrics
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "Model/HostRegistry.h"
|
||||||
|
|
||||||
|
namespace Model
|
||||||
|
{
|
||||||
|
void HostRegistry::update(const std::string &host, const Metrics::Host &m)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
hosts_[host] = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, Metrics::Host> HostRegistry::snapshot()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return hosts_;
|
||||||
|
}
|
||||||
|
} // namespace Model
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
|
#include "Metrics/Host.h"
|
||||||
|
|
||||||
|
namespace Model
|
||||||
|
{
|
||||||
|
class HostRegistry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void update(const std::string &host, const Metrics::Host &m);
|
||||||
|
std::map<std::string, Metrics::Host> snapshot();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, Metrics::Host> hosts_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
};
|
||||||
|
} // namespace Model
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "Network/Agent.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Network/Client.h"
|
||||||
|
#include "Metrics/Collector.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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<bool> running_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include "Network/Client.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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<ssize_t>(buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#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
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#include "Network/Server.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#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<sockaddr *>(&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<sockaddr *>(&clientAddr),
|
||||||
|
&addrLen);
|
||||||
|
|
||||||
|
if (len <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> 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
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Model/HostRegistry.h"
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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<bool> running_;
|
||||||
|
Model::HostRegistry ®istry_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Network
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -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 "")
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,25 @@
|
|||||||
macro(SubDirList curdir result)
|
macro(SubDirList curdir result)
|
||||||
file(GLOB children RELATIVE ${curdir} ${curdir}/*)
|
file(GLOB children RELATIVE ${curdir} ${curdir}/*)
|
||||||
|
|
||||||
foreach(child ${children})
|
foreach(child ${children})
|
||||||
if(IS_DIRECTORY ${curdir}/${child})
|
if(IS_DIRECTORY ${curdir}/${child})
|
||||||
list(APPEND ${result} ${child})
|
list(APPEND ${result} ${child})
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
endmacro()
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user