diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c95fca..8fae404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ option(TELEMETRY_BUILD_SHARED "Build shared library" ON) option(TELEMETRY_PACKAGE_BUILDER "Enable RPM package builder (make rpm)" ON) option(TELEMETRY_INSTALL_TARGETS "Generate the install target" ON) option(TELEMETRY_ENABLE_TESTS "Build Unit tests (make test)" OFF) +option(TELEMETRY_BUILD_EXAMPLE "Build included example files (make example)" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -33,6 +34,10 @@ endif() include(cmake/dependencies.cmake) +if (TELEMETRY_BUILD_EXAMPLE) + add_subdirectory(example) +endif() + if (TELEMETRY_ENABLE_TESTS) include(cmake/googletest.cmake) include(GoogleTest) diff --git a/Makefile b/Makefile index 8afd119..6a88fbb 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ ifeq ($(RUN_CLANG_TIDY),) RUN_CLANG_TIDY := run-clang-tidy endif -SOURCE_DIR = src/ include/ +SOURCE_DIR = src/ include/ examples/ SOURCE_REGEX = '.*\.\(cpp\|hpp\)' .PHONY: all @@ -52,3 +52,6 @@ test: build @$(MAKE) --no-print-directory -C build @$(MAKE) test --no-print-directory -C build +example: build + @cd build && $(CMAKE) $(CMAKE_ARGS) -DTELEMETRY_BUILD_EXAMPLES=ON .. + @$(MAKE) --no-print-directory -C build diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..c0a7c3b --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(example + main.cpp + dataCenter.cpp + server.cpp +) + +target_link_libraries(example PRIVATE + telemetry::telemetry + telemetry::appFs +) diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..9c4661a --- /dev/null +++ b/example/README.md @@ -0,0 +1,68 @@ +# Telemetry Example + +## Overview + +This example demonstrates a telemetry data structure that organizes and stores metrics from multiple servers located in different data centers. The structure allows for efficient retrieval and monitoring of key telemetry metrics such as CPU usage, memory usage, latency, and disk usage. + +## AppFs Directory Structure + +The resulting directory structure after running the application is as follows: + +```bash +$ tree /tmp/telemetry +/tmp/telemetry +└── data_centers + ├── new_york + │ ├── server_count + │ ├── servers + │ │ ├── server_0 + │ │ │ └── stats + │ │ ├── server_1 + │ │ │ └── stats + │ │ └── server_2 + │ │ └── stats + │ └── summary + │ └── summary_stats + ├── prague + │ ├── server_count + │ ├── servers + │ │ ├── server_0 + │ │ │ └── stats + │ │ ├── server_1 + │ │ │ └── stats + │ │ └── server_2 + │ │ └── stats + │ └── summary + │ └── summary_stats + └── tokyo + ├── server_count + ├── servers + │ ├── server_0 + │ │ └── stats + │ ├── server_1 + │ │ └── stats + │ └── server_2 + │ └── stats + └── summary + └── summary_stats +``` +## Components + +- **Data Centers**: The project currently supports three data centers: New York, Prague, and Tokyo. +- **Servers**: Each data center contains multiple servers (three in this example), each with its own set of telemetry metrics. +- **Telemetry Metrics**: For each server, metrics such as CPU usage, memory usage, latency, and disk usage are generated and stored. +- **Random Data Generation**: All telemetry metrics are generated randomly to simulate server performance in different conditions. +- **Summary Stats**: Each data center has a summary directory that aggregates the statistics from its servers. + +## Example of Telemetry Output + +Here is an example of the contents of the `stats` file for a specific server: + +```bash +$ cat /tmp/telemetry/data_centers/prague/servers/server_0/stats +cpu_usage: 74.28 (%) +disk_usage: 13.48 (%) +latency: 170.20 (ms) +memory_usage: 31.17 (%) +timestamp: 2024-10-03 15:18:41 +``` diff --git a/example/dataCenter.cpp b/example/dataCenter.cpp new file mode 100644 index 0000000..ba5076e --- /dev/null +++ b/example/dataCenter.cpp @@ -0,0 +1,96 @@ +/** + * @file + * @author Pavel Siska + * @brief Implementation of the DataCenter class for managing multiple servers and their telemetry + * data. + * + * This source file implements the methods of the `DataCenter` class, which manages a collection + * of `Server` objects and their associated telemetry data. It includes functionalities for adding + * servers, setting up telemetry reporting, and aggregating data across all servers for summary + * statistics. + * + * @copyright Copyright (c) 2024 CESNET, z.s.p.o. + */ + +#include "dataCenter.hpp" + +namespace telemetry::example { + +/** + * @brief Creates a summary file with aggregated telemetry data. + * + * @param filename The name of the summary file to be created. + * @param filePattern The pattern to match server telemetry files. + * @param patternRootDir Shared pointer to the working directory for patterns. + * @param dir Shared pointer to the directory where the summary file will be added. + * @return A shared pointer to the created aggregated file. + */ +static std::shared_ptr createSummaryFile( + const std::string& filename, + const std::string& filePattern, + std::shared_ptr& patternRootDir, + std::shared_ptr& dir) +{ + const std::vector aggOps { + {telemetry::AggMethodType::AVG, "cpu_usage", "avg_cpu_usage"}, + {telemetry::AggMethodType::AVG, "memory_usage", "avg_memory_usage"}, + {telemetry::AggMethodType::AVG, "latency", "avg_latency"}, + {telemetry::AggMethodType::AVG, "disk_usage", "avg_disk_usage"}, + }; + + return dir->addAggFile(filename, filePattern, aggOps, patternRootDir); +} + +DataCenter::DataCenter(std::string location, std::shared_ptr& dataCenterDir) + : m_rootDir(dataCenterDir) + , m_location(std::move(location)) +{ + setupTelemetry(dataCenterDir); +} + +void DataCenter::addServer(Server server) +{ + auto serverDir = m_rootDir->addDirs("servers/" + server.getId()); + server.setupTelemetry(serverDir); + + m_servers.emplace_back(std::move(server)); +} + +/** + * @brief Sets up telemetry reporting for the data center. + * + * This method initializes the telemetry reporting structure for the data center. It creates + * the necessary directories for storing server-specific telemetry data and summary statistics. + * + * It performs the following actions: + * - Creates a directory to hold all server directories (`servers`). + * - Creates a directory to store aggregated summary statistics (`summary`). + * - Adds a file that tracks the count of servers currently managed by the data center. + * - Creates an aggregated summary file that calculates average telemetry metrics (such as + * CPU usage, memory usage, latency, and disk usage) across all servers. + * + * The generated directories and files are added to the telemetry holder to manage their lifecycle + * and ensure they are updated as telemetry data is collected from individual servers. + * + * @param dataCenterDir Shared pointer to the telemetry directory where the telemetry structure will + * be established. + */ +void DataCenter::setupTelemetry(std::shared_ptr& dataCenterDir) +{ + auto serversDir = dataCenterDir->addDir("servers"); + auto summaryDir = dataCenterDir->addDir("summary"); + + const auto serverCountFile = dataCenterDir->addFile( + "server_count", + {[&]() -> telemetry::Scalar { return m_servers.size(); }, nullptr}); + + const auto summaryFile + = createSummaryFile("summary_stats", "server_\\d+/stats", serversDir, summaryDir); + + m_holder.add(serversDir); + m_holder.add(summaryDir); + m_holder.add(serverCountFile); + m_holder.add(summaryFile); +} + +} // namespace telemetry::example diff --git a/example/dataCenter.hpp b/example/dataCenter.hpp new file mode 100644 index 0000000..e6016fb --- /dev/null +++ b/example/dataCenter.hpp @@ -0,0 +1,65 @@ +/** + * @file + * @author Pavel Siska + * @brief Declaration of the DataCenter class for managing multiple servers and their telemetry + * data. + * + * This header file defines the `DataCenter` class, which is responsible for managing a collection + * of `Server` objects and their associated telemetry data. The `DataCenter` allows adding servers, + * setting up their telemetry reporting, and aggregating data across all servers for summary + * statistics. + * + * The telemetry data is organized in a hierarchical directory structure and can include information + * such as CPU usage, memory usage, latency, and disk usage for each server. + * + * @copyright Copyright (c) 2024 CESNET, z.s.p.o. + */ + +#pragma once + +#include "server.hpp" + +#include +#include +#include +#include + +namespace telemetry::example { + +/** + * @brief Class representing a data center that manages multiple servers. + * + * This class provides functionality to add servers, setup telemetry reporting, + * and aggregate telemetry data across all managed servers. + */ +class DataCenter { +public: + /** + * @brief Constructs a new DataCenter object with a specified location and telemetry directory. + * + * @param location The physical location of the data center. + * @param dataCenterDir Shared pointer to the telemetry directory for this data center. + */ + DataCenter(std::string location, std::shared_ptr& dataCenterDir); + + /** + * @brief Adds a server to the data center. + * @param server The server to be added. + */ + void addServer(Server server); + +private: + /** + * @brief Sets up telemetry reporting for the data center. + * @param dataCenterDir Shared pointer to the telemetry directory. + */ + void setupTelemetry(std::shared_ptr& dataCenterDir); + + std::shared_ptr + m_rootDir; ///< Pointer to the root data center telemetry directory. + std::string m_location; ///< The location of the data center. + telemetry::Holder m_holder; ///< Holder for managing telemetry files. + std::vector m_servers; ///< Vector to store added servers. +}; + +} // namespace telemetry::example diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..55b01ad --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,132 @@ +/** + * @file main.cpp + * @brief Main entry point for the telemetry data center application. + * + * This program initializes telemetry data centers, each containing a set of servers that report + * telemetry data. It uses a signal handler to allow for graceful termination of the application. + * The telemetry data is organized in a hierarchical structure and can be mounted as a filesystem. + * + * ## Usage + * + * The application expects one argument: + * - `mount_point`: The directory where the telemetry data can be accessed as a filesystem. + * + * @note The application will run indefinitely until interrupted by a signal (e.g., SIGINT). + * + * @example ./example "/tmp/telemetry" + */ + +#include "dataCenter.hpp" +#include "server.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace telemetry::example; + +std::atomic g_stopFlag(false); + +void signalHandler(int signum) +{ + (void) signum; + g_stopFlag.store(true); +} + +/** + * @brief Creates a DataCenter object for a given location. + * + * This function initializes a DataCenter for the specified location and associates it with a + * telemetry directory. + * + * @param location The location of the data center. + * @param dataCentersDir Shared pointer to the directory where the data center will be created. + * @return A DataCenter object initialized for the specified location. + */ +DataCenter +createDataCenter(const std::string& location, std::shared_ptr& dataCentersDir) +{ + auto dataCenterDir = dataCentersDir->addDir(location); + return {location, dataCenterDir}; +} + +/** + * @brief Creates multiple DataCenter objects for predefined locations. + * + * This function initializes a set of data centers, each containing a predefined number of servers. + * + * @param dataCentersDir Shared pointer to the directory where the data centers will be created. + * @return A vector of initialized DataCenter objects. + */ +std::vector createDataCenters(std::shared_ptr& dataCentersDir) +{ + std::vector dataCenters; + dataCenters.emplace_back(createDataCenter("prague", dataCentersDir)); + dataCenters.emplace_back(createDataCenter("new_york", dataCentersDir)); + dataCenters.emplace_back(createDataCenter("tokyo", dataCentersDir)); + + const std::size_t serversPerDatacenter = 3; + + // add servers to each data center + for (auto& dataCenter : dataCenters) { + for (std::size_t serverId = 0; serverId < serversPerDatacenter; serverId++) { + dataCenter.addServer(Server("server_" + std::to_string(serverId))); + } + } + + return dataCenters; +} + +int main(int argc, char** argv) +{ + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " \n"; + return EXIT_FAILURE; + } + + const std::string mountPoint = argv[1]; + + signal(SIGINT, signalHandler); + + std::shared_ptr telemetryRootDirectory; + std::unique_ptr appFs; + + try { + // create telemetry root directory + telemetryRootDirectory = telemetry::Directory::create(); + + auto dataCentersDir = telemetryRootDirectory->addDir("data_centers"); + auto dataCenters = createDataCenters(dataCentersDir); + + const bool tryToUnmountOnStart = true; + const bool createMountPoint = true; + appFs = std::make_unique( + telemetryRootDirectory, + mountPoint, + tryToUnmountOnStart, + createMountPoint); + /** + * Starts the application filesystem. + * + * Telemetry data is now accessible as a filesystem mounted at the specified mount point + */ + appFs->start(); + + while (!g_stopFlag.load()) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + } catch (const std::exception& ex) { + std::cerr << ex.what() << "\n"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/example/server.cpp b/example/server.cpp new file mode 100644 index 0000000..9f264ce --- /dev/null +++ b/example/server.cpp @@ -0,0 +1,124 @@ +/** + * @file + * @author Pavel Siska + * @brief Implementation of the Server class and associated telemetry functions. + * + * This source file implements the methods of the `Server` class, which is responsible for + * managing server telemetry data. It includes functionalities for generating random telemetry + * data, converting timestamps to string format, and reporting telemetry data to a specified + * telemetry directory. + * + * The `Server` class allows tracking of various performance metrics such as CPU usage, memory + * usage, latency, and disk usage. It utilizes the `ServerTelemetry` struct to hold the telemetry + * data and interacts with the telemetry system to store and update this information. + * + * @copyright Copyright (c) 2024 CESNET, z.s.p.o. + */ + +#include "server.hpp" + +#include +#include +#include +#include + +namespace telemetry::example { + +/** + * @brief Converts a `std::chrono::system_clock::time_point` to a formatted string. + * + * @param timePoint The time point to convert. + * @return A formatted string representation of the time point in the format "YYYY-MM-DD HH:MM:SS". + */ +static std::string timePointToString(const std::chrono::system_clock::time_point& timePoint) +{ + const std::time_t time = std::chrono::system_clock::to_time_t(timePoint); + + std::stringstream timeStream; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + timeStream << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); + + return timeStream.str(); +} + +/** + * @brief Generates random telemetry data for the server. + * + * This function generates random values for CPU usage, memory usage, latency, and disk usage, + * and assigns the current system time as the timestamp. + * + * @return A `ServerTelemetry` structure populated with randomly generated telemetry data. + */ +static ServerTelemetry generateServerTelemetry() +{ + // NOLINTBEGIN + std::random_device randDevice; + std::mt19937 gen(randDevice()); + std::uniform_real_distribution<> cpuDist(0.0, 100.0); + std::uniform_real_distribution<> memoryDist(0.0, 100.0); + std::uniform_real_distribution<> latencyDist(1.0, 200.0); + std::uniform_real_distribution<> diskDist(0.0, 100.0); + // NOLINTEND + + ServerTelemetry telemetry; + telemetry.cpuUsage = cpuDist(gen); + telemetry.memoryUsage = memoryDist(gen); + telemetry.latency = latencyDist(gen); + telemetry.diskUsage = diskDist(gen); + telemetry.timestamp = std::chrono::system_clock::now(); + + return telemetry; +} + +/** + * @brief Converts `ServerTelemetry` data to a `telemetry::Dict`. + * + * @param telemetry The `ServerTelemetry` structure containing telemetry data. + * @return A `telemetry::Dict` with telemetry values formatted and ready for reporting. + */ +static telemetry::Dict getServerTelemetry(const ServerTelemetry& telemetry) +{ + telemetry::Dict dict; + dict["cpu_usage"] = telemetry::ScalarWithUnit {telemetry.cpuUsage, "%"}; + dict["memory_usage"] = telemetry::ScalarWithUnit {telemetry.memoryUsage, "%"}; + dict["latency"] = telemetry::ScalarWithUnit {telemetry.latency, "ms"}; + dict["disk_usage"] = telemetry::ScalarWithUnit {telemetry.diskUsage, "%"}; + dict["timestamp"] = timePointToString(telemetry.timestamp); + return dict; +} + +Server::Server(std::string serverId) + : m_serverId(std::move(serverId)) +{ +} + +std::string Server::getId() const +{ + return m_serverId; +} + +/** + * @brief Sets up telemetry for the server and adds a telemetry file to the directory. + * + * This function generates random telemetry data and sets up a file in the provided directory + * to report the telemetry stats. + * + * @param serverDir A shared pointer to a telemetry directory where the telemetry data will be + * stored. + */ +void Server::setupTelemetry(std::shared_ptr& serverDir) +{ + const auto statsFile = serverDir->addFile( + "stats", + { + [this]() { + m_telemetry = generateServerTelemetry(); + return getServerTelemetry(m_telemetry); + }, + nullptr, + }); + + m_holder.add(statsFile); +} + +} // namespace telemetry::example diff --git a/example/server.hpp b/example/server.hpp new file mode 100644 index 0000000..de34311 --- /dev/null +++ b/example/server.hpp @@ -0,0 +1,78 @@ +/** + * @file + * @author Pavel Siska + * @brief Contains the declaration of the Server class and ServerTelemetry struct for tracking + * server telemetry data. + * + * This header file defines a class and associated functions that allow tracking and reporting + * server telemetry data such as CPU usage, memory usage, network latency, and disk usage. + * The telemetry data is stored in a telemetry directory. + * + * The primary class is `Server`, which allows telemetry to be set up for a specific server + * and stores data in a telemetry system using the `telemetry::Directory` and `telemetry::Holder` + * objects. + * + * @copyright Copyright (c) 2024 CESNET, z.s.p.o. + */ + +#pragma once + +#include +#include +#include +#include + +namespace telemetry::example { + +/** + * @brief Struct that holds telemetry data for a server. + * + * This structure stores key telemetry metrics including CPU usage, memory usage, + * latency, disk usage, and a timestamp representing when the data was collected. + */ +struct ServerTelemetry { + double cpuUsage; //< CPU usage percentage. + double memoryUsage; //< Memory usage percentage. + double latency; //< Network latency in milliseconds. + double diskUsage; //< Disk usage percentage. + std::chrono::system_clock::time_point timestamp; //< Timestamp when the telemetry was captured. +}; + +/** + * @brief Class that represents a server and its associated telemetry data. + * + * The `Server` class allows tracking and storing telemetry data for a specific + * server, including CPU usage, memory usage, network latency, and disk usage. + * It also provides methods to set up the telemetry reporting mechanism. + */ +class Server { +public: + /** + * @brief Constructor for the `Server` class. + * + * @param serverId A string that uniquely identifies the server. + */ + explicit Server(std::string serverId); + + /** + * @brief Sets up telemetry reporting for the server. + * + * @param serverDir A shared pointer to a telemetry directory where server + * telemetry data will be stored. + */ + void setupTelemetry(std::shared_ptr& serverDir); + + /** + * @brief Gets the server's unique identifier. + * + * @return A string representing the server's ID. + */ + [[nodiscard]] std::string getId() const; + +private: + std::string m_serverId; //< Unique identifier for the server. + ServerTelemetry m_telemetry = {}; //< Holds the server's telemetry data. + telemetry::Holder m_holder; //< Telemetry holder for managing telemetry data files. +}; + +} // namespace telemetry::example