diff --git a/CMakeLists.txt b/CMakeLists.txt index f842169..a52ad23 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_EXAMPLES "Build included example files (make example)" ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -33,6 +34,10 @@ endif() include(cmake/dependencies.cmake) +if (TELEMETRY_BUILD_EXAMPLES) + add_subdirectory(examples) +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/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..e49a6bc --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,44 @@ +add_executable(test1 + basicExample.cpp +) + +target_link_libraries(test1 PRIVATE + telemetry::telemetry + telemetry::appFs +) + +add_executable(test2 + contentExample.cpp +) + +target_link_libraries(test2 PRIVATE + telemetry::telemetry + telemetry::appFs +) + +add_executable(test3 + holderExample.cpp +) + +target_link_libraries(test3 PRIVATE + telemetry::telemetry + telemetry::appFs +) + +add_executable(test4 + utilsExample.cpp +) + +target_link_libraries(test4 PRIVATE + telemetry::telemetry + telemetry::appFs +) + +add_executable(test5 + aggregatedFileExample.cpp +) + +target_link_libraries(test5 PRIVATE + telemetry::telemetry + telemetry::appFs +) diff --git a/examples/aggregatedFileExample.cpp b/examples/aggregatedFileExample.cpp new file mode 100644 index 0000000..0f6074b --- /dev/null +++ b/examples/aggregatedFileExample.cpp @@ -0,0 +1,106 @@ +/** + * @file + * @author Daniel Pelanek + * @brief Shows how the aggregated files are used and it's methods + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +// example help +#include + +telemetry::Content returnValue(int64_t value) +{ + return telemetry::Scalar(value); +} + +telemetry::Content returnValueDict(int64_t value) +{ + telemetry::Dict dict; + dict["1"] = telemetry::Scalar(value * 2); + + return dict; +} + +int main() +{ + // Same as basic example + std::shared_ptr telemetryRootNode; + telemetryRootNode = telemetry::Directory::create(); + + std::string fusePath = "fusedir"; + + telemetry::appFs::AppFsFuse fuse + = telemetry::appFs::AppFsFuse(telemetryRootNode, fusePath, true, true); + fuse.start(); + + // We create two fileops that just return a constant value + const telemetry::FileOps fileOps1 = {[]() { return returnValue(2); }, nullptr}; + + const telemetry::FileOps fileOps2 = {[]() { return returnValue(5); }, nullptr}; + + // It is better to keep the files in another directory and not + // aggregate them straight from root dir. + auto aggFileDir = telemetryRootNode->addDir("aggDir"); + auto file1 = aggFileDir->addFile("file1", fileOps1); + auto file2 = aggFileDir->addFile("file2", fileOps2); + + // Here we init three different aggregation operations. + // One of them is join meaning it will add values from all files into an array. + // The two arguments after the method type are source dict field name and result dict field name + // If they are left empty ("") the values are taken directly from file->read() and + // directly returned as telemetry::Scalar, telemetry::Array, ... + // The result here is 1 : [2, 5]. + const telemetry::AggOperation aggOp0 = {telemetry::AggMethodType::JOIN, "", "1"}; + + // Average of read from files in aggfile if there are different types of Scalar(WithUnit)s the + // return type is same as in cpp meaning avg of uint64_t, int64_t and double the result is + // double. + const telemetry::AggOperation aggOp1 = {telemetry::AggMethodType::AVG}; + // Sum of read from files in aggfile + const telemetry::AggOperation aggOp2 = {telemetry::AggMethodType::SUM}; + + // We have to add these aggregation operations into a vector. + std::vector aggOps0 = {aggOp0}; + std::vector aggOps1 = {aggOp1}; + std::vector aggOps2 = {aggOp2}; + + // The method for creating an aggregated file is addAggFile + // You can specify which of the files from a directory are picked + // by regex. + auto aggFile0 = aggFileDir->addAggFile("aggFile", "file[0-9]+", aggOps0); + auto aggFile1 = aggFileDir->addAggFile("aggFile1", "file[0-9]+", aggOps1); + auto aggFile2 = aggFileDir->addAggFile("aggFile2", "file[0-9]+", aggOps2); + + // Just for showing the results to terminal. The same results will be in the + // actual filesystem. + std::cout << "Single aggregation operations: \n"; + std::cout << telemetry::contentToString(aggFile0->read()) << "\n"; + std::cout << telemetry::contentToString(aggFile1->read()) << "\n"; + std::cout << telemetry::contentToString(aggFile2->read()) << "\n"; + + // Using two aggregation operations in a single file is only possible when + // the sources are dicts. + const telemetry::FileOps fileOpsDict1 = {[]() { return returnValueDict(2); }, nullptr}; + + const telemetry::FileOps fileOpsDict2 = {[]() { return returnValueDict(5); }, nullptr}; + + auto aggFileDirDict = telemetryRootNode->addDir("aggDirDict"); + auto fileDict1 = aggFileDirDict->addFile("fileDict1", fileOpsDict1); + auto fileDict2 = aggFileDirDict->addFile("fileDict2", fileOpsDict2); + + // The result dict keys have to be different. If they are the same only + // the first result is valid and is read. + const telemetry::AggOperation aggOpDict0 = {telemetry::AggMethodType::JOIN, "1", "1"}; + const telemetry::AggOperation aggOpDict1 = {telemetry::AggMethodType::AVG, "1", "2"}; + + std::vector aggOpsDict = {aggOpDict0, aggOpDict1}; + + auto aggFileDict = aggFileDirDict->addAggFile("aggFileDict", "fileDict[0-9]+", aggOpsDict); + + std::cout << "\nMultiple aggregation operations: \n"; + std::cout << telemetry::contentToString(aggFileDict->read()) << "\n"; +} diff --git a/examples/basicExample.cpp b/examples/basicExample.cpp new file mode 100644 index 0000000..c0f1522 --- /dev/null +++ b/examples/basicExample.cpp @@ -0,0 +1,103 @@ +/** + * @file + * @author Daniel Pelanek + * @brief Shows the basic usage of telemetry with fuse + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +// example help +#include +#include +#include +std::atomic g_gotSIGINT(false); +void signalHandler(int signum) +{ + (void) signum; + g_gotSIGINT.store(true); +} + +// The return value has to be telemetry::Content or one of its variants. +telemetry::Content +getTimeElapsed(const std::chrono::time_point& startTime) +{ + auto now = std::chrono::system_clock::now(); + auto timeElapsed = std::chrono::duration_cast(now - startTime).count(); + + // telemetry::Content is std::variant. + // How they are used is shown in another example. + return telemetry::Scalar(timeElapsed); +} + +// Resets start time to the current time. +void clearTime(std::chrono::time_point& startTime) +{ + startTime = std::chrono::system_clock::now(); +} + +int main() +{ + // Creating root dir for filesystem. + std::shared_ptr telemetryRootNode; + telemetryRootNode = telemetry::Directory::create(); + + // The path to root dir is local to where the program is called from. + std::string fusePath = "fusedir"; + + // Linking root dir to the chosen directory on disk. + // If the fourth argument is true it means the directory on disk doesn't need to + // exist before starting program. + telemetry::appFs::AppFsFuse fuse + = telemetry::appFs::AppFsFuse(telemetryRootNode, fusePath, true, true); + fuse.start(); + // The filesystem is still just empty. + // / + + // So let's a directory named input into the root dir. + std::shared_ptr inputDir = telemetryRootNode->addDir("input"); + // Now the filesystem looks like this. + // / + // └─ input/ + + // Every file can have two lambdas attached to it. + // + // One for reading -> What gets called when something tries to read the file on disk. + // Here we write the return value of getTime to the file. + // + // One for clearing -> What gets called when you want to reset telemetry data. + // In this case we reset the startTime. + auto startTime = std::chrono::system_clock::now(); + const telemetry::FileOps fileOps + = {[&startTime]() { return getTimeElapsed(startTime); }, + [&startTime]() { return clearTime(startTime); }}; + + // The read and clear functions are optional. In the case you don't use them pass + // a null pointer instead. + const telemetry::FileOps anotherFileOps = {nullptr, nullptr}; + + // Files be put into the root directory. + const std::shared_ptr timeFile = telemetryRootNode->addFile("time", fileOps); + // Now it looks like this. + // / + // ├─ input/ + // └─ time + + // Or into another directory. + const std::shared_ptr anotherTimeFile + = inputDir->addFile("time", anotherFileOps); + // Now it looks like this. + // / + // ├─ input/ + // │ └─ time + // └─ time + + // Waiting for ctrl+c. In the meantime you can open another terminal and + // navigate to the newly linked directory. Just reading the new time file + // should print the time elapsed in seconds since the start of the program. + std::signal(SIGINT, signalHandler); + while (!g_gotSIGINT.load()) {}; +} diff --git a/examples/contentExample.cpp b/examples/contentExample.cpp new file mode 100644 index 0000000..ac2ffcc --- /dev/null +++ b/examples/contentExample.cpp @@ -0,0 +1,62 @@ +/** + * @file + * @author Daniel Pelanek + * @brief Shows how telemetry content data types are used + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +// example help +#include + +// Simplest type. Only contains one value . +// The values can be of type std::monostate, bool, uint64_t, int64_t, double, std::string. +telemetry::Scalar scalarReturn() +{ + return telemetry::Scalar(static_cast(100)); +} + +// Contains not only scalar but also has a unit. +// The unit is of type string. +telemetry::ScalarWithUnit scalartWithUnitReturn() +{ + return telemetry::ScalarWithUnit(static_cast(42), "%"); +} + +// Array is a vector of Scalars. +telemetry::Array arrayReturn() +{ + telemetry::Array arr; + + const uint64_t nScalars = 10; + + for (uint64_t index = 0; index < nScalars; index++) { + arr.push_back(telemetry::Scalar(index)); + } + + return arr; +} + +// Dict is a map with string as a key. +// Value can be std::monostate or any one of the three previous types +telemetry::Dict dictReturn() +{ + telemetry::Dict dict; + + dict["1"] = telemetry::Scalar(static_cast(10)); + dict["2"] = telemetry::ScalarWithUnit(static_cast(10), "bool"); + dict["3"] = telemetry::Array(std::vector()); + + return dict; +} + +int main() +{ + // telemetry::Content can be easily printed or stored as string with contentToString() + std::cout << telemetry::contentToString(scalarReturn()) << "\n"; + std::cout << telemetry::contentToString(scalartWithUnitReturn()) << "\n"; + std::cout << telemetry::contentToString(arrayReturn()) << "\n"; + std::cout << telemetry::contentToString(dictReturn()) << "\n"; +} diff --git a/examples/holderExample.cpp b/examples/holderExample.cpp new file mode 100644 index 0000000..5dc5297 --- /dev/null +++ b/examples/holderExample.cpp @@ -0,0 +1,57 @@ +/** + * @file + * @author Daniel Pelanek + * @brief Shows how to use the holder class + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +// Object that collects it's own telemetry or owns +// telemetry files or directories. +class IOwnTelemetryFiles { +public: + void configTelemetry(const std::shared_ptr& rootDir) + { + // Create a telemetry file + const telemetry::FileOps fileOps = {nullptr, nullptr}; + + auto someFile = rootDir->addFile("someFile", fileOps); + + // We also need to add the file to the holder. + m_telemetryHolder.add(someFile); + } + + // If you want to disable callback of files before the + // is destroyed. + void disableFiles() { m_telemetryHolder.disableFiles(); } + +private: + telemetry::Holder m_telemetryHolder; +}; + +int main() +{ + // Same as basic example + std::shared_ptr telemetryRootNode; + telemetryRootNode = telemetry::Directory::create(); + + std::string fusePath = "fusedir"; + + telemetry::appFs::AppFsFuse fuse + = telemetry::appFs::AppFsFuse(telemetryRootNode, fusePath, true, true); + fuse.start(); + + // The files callbacks get disabled when the telemetry holder + // is destroyed and if the files and dirs don't have another reference + // elsewhere they get destroyed too. + IOwnTelemetryFiles object; + + // Configuration of telemetry inside of the object. + object.configTelemetry(telemetryRootNode); + + // Disable callbacks before the objects destructor + object.disableFiles(); +} diff --git a/examples/utilsExample.cpp b/examples/utilsExample.cpp new file mode 100644 index 0000000..cfed9e0 --- /dev/null +++ b/examples/utilsExample.cpp @@ -0,0 +1,97 @@ +/** + * @file + * @author Daniel Pelanek + * @brief Shows how to use utility functions + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +// example help +#include +#include + +void traverseDir(std::shared_ptr& dir, int depth) +{ + // Using utils to check if a directory is a root directory + if (telemetry::utils::isRootDirectory(dir->getFullPath())) { + std::cout << "root\n"; + } + + // looping through files and dirs in directory + // listEntries returns their names as string + for (auto node_name : dir->listEntries()) { + auto node = dir->getEntry(node_name); + + std::cout << std::string(depth * 4, ' ') << node_name << "\n"; + + // Using utils to check if a node is a directory + if (telemetry::utils::isDirectory(node)) { + // dynamic_pointer_cast is needed to get a file or dir from node + auto newDir = std::dynamic_pointer_cast(node); + traverseDir(newDir, depth + 1); + + // Using utils to check if a node is a file + } else if (telemetry::utils::isFile(node)) { + auto file = std::dynamic_pointer_cast(node); + + // Manually reading file content + try { + std::cout << telemetry::contentToString(file->read()); + } catch (std::exception& ex) { + // File::read operation not supported + // std::cout << ex.what() << "\n"; + } + } + } +} + +int main() +{ + // Same as basic example + std::shared_ptr telemetryRootNode; + telemetryRootNode = telemetry::Directory::create(); + + std::string fusePath = "fusedir"; + + telemetry::appFs::AppFsFuse fuse + = telemetry::appFs::AppFsFuse(telemetryRootNode, fusePath, true, true); + fuse.start(); + + // Let's create a more complex dir structure + const telemetry::FileOps emptyFileOps = {nullptr, nullptr}; + + auto file1 = telemetryRootNode->addFile("file1", emptyFileOps); + auto file2 = telemetryRootNode->addFile("file2", emptyFileOps); + auto dir1 = telemetryRootNode->addDir("dir1"); + + auto file3 = dir1->addFile("file3", emptyFileOps); + auto dir2 = dir1->addDir("dir2"); + auto dir3 = dir1->addDir("dir3"); + + auto file4 = dir2->addFile("file4", emptyFileOps); + auto file5 = dir2->addFile("file5", emptyFileOps); + auto file6 = dir2->addFile("file6", emptyFileOps); + auto file7 = dir2->addFile("file7", emptyFileOps); + + auto file8 = dir3->addFile("file8", emptyFileOps); + + // And now traverse the directory with the use of telemetry utils + traverseDir(telemetryRootNode, 1); + + // Utils can also give you a node from path + auto node = telemetry::utils::getNodeFromPath(telemetryRootNode, "/dir1/dir2/file5"); + + // To silence compiler warnings. + (void) node; + (void) file1; + (void) file2; + (void) file3; + (void) file4; + (void) file5; + (void) file6; + (void) file7; + (void) file8; +} diff --git a/src/telemetry/aggregator/tests/testAggSum.cpp b/src/telemetry/aggregator/tests/testAggSum.cpp index f795a3a..a2d3676 100644 --- a/src/telemetry/aggregator/tests/testAggSum.cpp +++ b/src/telemetry/aggregator/tests/testAggSum.cpp @@ -166,7 +166,6 @@ TEST(AggSumTest, TestAggregate) AggMethodSum aggMethodSum; std::vector contents = {Scalar {true}, Scalar {5.0}}; EXPECT_THROW(aggMethodSum.aggregate(contents), TelemetryException); - } // Test aggregation of incompatible scalar types (expect failure)