diff --git a/src/Communicate/BufferHandler.h b/src/Communicate/BufferHandler.h new file mode 100644 index 000000000..c0400d1ac --- /dev/null +++ b/src/Communicate/BufferHandler.h @@ -0,0 +1,170 @@ +#ifndef IPPL_BUFFER_HANDLER_H +#define IPPL_BUFFER_HANDLER_H + +#include +#include + +#include "Communicate/Archive.h" + +namespace ippl { + + /** + * @brief Interface for memory buffer handling. + * + * Defines methods for acquiring, freeing, and managing memory buffers. + * Implementations are responsible for managing buffers efficiently, + * ensuring that allocated buffers are reused where possible. + * + * @tparam MemorySpace The memory space type used for buffer allocation. + */ + template + class BufferHandler { + public: + using archive_type = ippl::detail::Archive; + using buffer_type = std::shared_ptr; + using size_type = ippl::detail::size_type; + + virtual ~BufferHandler() {} + + /** + * @brief Requests a memory buffer of a specified size. + * + * Provides a buffer of at least the specified size, with the option + * to allocate additional space based on an overallocation multiplier. + * This function attempts to reuse available buffers if possible. + * + * @param size The required size of the buffer, in bytes. + * @param overallocation A multiplier to allocate extra space, which may help + * avoid frequent reallocation in some use cases. + * @return A shared pointer to the allocated buffer. + */ + virtual buffer_type getBuffer(size_type size, double overallocation) = 0; + + /** + * @brief Frees a specified buffer. + * + * Moves the specified buffer to a free state, making it available + * for reuse in future buffer requests. + * + * @param buffer The buffer to be freed. + */ + virtual void freeBuffer(buffer_type buffer) = 0; + + /** + * @brief Frees all currently used buffers. + * + * Transfers all used buffers to the free state, making them available + * for reuse. This does not deallocate memory but resets buffer usage. + */ + virtual void freeAllBuffers() = 0; + + /** + * @brief Deletes all buffers. + * + * Releases all allocated memory buffers, both used and free. + * After this call, no buffers are available until new allocations. + */ + virtual void deleteAllBuffers() = 0; + + /** + * @brief Gets the size of all buffers in use. + * + * @return Total size of buffers that are in use in bytes. + */ + virtual size_type getUsedSize() const = 0; + + /** + * @brief Gets the size of all free buffers. + * + * @return Total size of free buffers in bytes. + */ + virtual size_type getFreeSize() const = 0; + }; + + /** + * @class DefaultBufferHandler + * @brief Concrete implementation of BufferHandler for managing memory buffers. + * + * This class implements the BufferHandler interface, providing concrete behavior for + * buffer allocation, freeing, and memory management. It maintains two sorted sets of free and + * in-use buffers to allow for efficient queries. + * + * @tparam MemorySpace The memory space type for the buffer (e.g., `Kokkos::HostSpace`). + */ + template + class DefaultBufferHandler : public BufferHandler { + public: + using typename BufferHandler::archive_type; + using typename BufferHandler::buffer_type; + using typename BufferHandler::size_type; + + ~DefaultBufferHandler() override; + + /** + * @brief Acquires a buffer of at least the specified size. + * + * Requests a memory buffer of the specified size, with the option + * to request a buffer larger than the base size by an overallocation + * multiplier. If a sufficiently large buffer is available, it is returned. If not, the + * largest free buffer is reallocated. If there are no free buffers available, only then a + * new buffer is allocated. + * + * @param size The required buffer size. + * @param overallocation A multiplier to allocate additional buffer space. + * @return A shared pointer to the allocated buffer. + */ + buffer_type getBuffer(size_type size, double overallocation) override; + + /** + * @copydoc BufferHandler::freeBuffer + */ + void freeBuffer(buffer_type buffer) override; + + /** + * @copydoc BufferHandler::freeBuffer + */ + void freeAllBuffers() override; + + /** + * @copydoc BufferHandler::freeBuffer + */ + void deleteAllBuffers() override; + + /** + * @copydoc BufferHandler::freeBuffer + */ + size_type getUsedSize() const override; + + /** + * @copydoc BufferHandler::freeBuffer + */ + size_type getFreeSize() const override; + + private: + using buffer_comparator_type = bool (*)(const buffer_type&, const buffer_type&); + using buffer_set_type = std::set; + + static bool bufferSizeComparator(const buffer_type& lhs, const buffer_type& rhs); + + bool isBufferUsed(buffer_type buffer) const; + void releaseUsedBuffer(buffer_type buffer); + buffer_type findFreeBuffer(size_type requiredSize); + buffer_set_type::iterator findSmallestSufficientBuffer(size_type requiredSize); + buffer_type getFreeBuffer(buffer_type buffer); + buffer_type reallocateLargestFreeBuffer(size_type requiredSize); + buffer_type allocateNewBuffer(size_type requiredSize); + + size_type usedSize_m; ///< Total size of all allocated buffers + size_type freeSize_m; ///< Total size of all free buffers + + protected: + buffer_set_type used_buffers{ + &DefaultBufferHandler::bufferSizeComparator}; ///< Set of used buffers + buffer_set_type free_buffers{ + &DefaultBufferHandler::bufferSizeComparator}; ///< Set of free buffers + }; +} // namespace ippl + +#include "Communicate/BufferHandler.hpp" + +#endif diff --git a/src/Communicate/BufferHandler.hpp b/src/Communicate/BufferHandler.hpp new file mode 100644 index 000000000..012f7521b --- /dev/null +++ b/src/Communicate/BufferHandler.hpp @@ -0,0 +1,147 @@ +#ifndef IPPL_BUFFER_HANDLER_HPP +#define IPPL_BUFFER_HANDLER_HPP + +namespace ippl { + + template + DefaultBufferHandler::~DefaultBufferHandler() {} + + template + typename DefaultBufferHandler::buffer_type DefaultBufferHandler::getBuffer( + size_type size, double overallocation) { + size_type requiredSize = static_cast(size * overallocation); + + auto freeBuffer = findFreeBuffer(requiredSize); + if (freeBuffer != nullptr) { + return getFreeBuffer(freeBuffer); + } + + if (!free_buffers.empty()) { + return reallocateLargestFreeBuffer(requiredSize); + } + + return allocateNewBuffer(requiredSize); + } + + template + void DefaultBufferHandler::freeBuffer(buffer_type buffer) { + if (isBufferUsed(buffer)) { + releaseUsedBuffer(buffer); + } + } + + template + void DefaultBufferHandler::freeAllBuffers() { + free_buffers.insert(used_buffers.begin(), used_buffers.end()); + used_buffers.clear(); + + freeSize_m += usedSize_m; + usedSize_m = 0; + } + + template + void DefaultBufferHandler::deleteAllBuffers() { + freeSize_m = 0; + usedSize_m = 0; + + used_buffers.clear(); + free_buffers.clear(); + } + + template + typename DefaultBufferHandler::size_type DefaultBufferHandler::getUsedSize() + const { + return usedSize_m; + } + + template + typename DefaultBufferHandler::size_type DefaultBufferHandler::getFreeSize() const { + return freeSize_m; + } + + template + bool DefaultBufferHandler::bufferSizeComparator(const buffer_type& lhs, + const buffer_type& rhs) { + if (lhs->getBufferSize() != rhs->getBufferSize()) { + return lhs->getBufferSize() < rhs->getBufferSize(); + } + + // Use memory address as a tie-breaker to enforce total ordering of buffers. + return lhs < rhs; + } + + template + bool DefaultBufferHandler::isBufferUsed(buffer_type buffer) const { + return used_buffers.find(buffer) != used_buffers.end(); + } + + template + void DefaultBufferHandler::releaseUsedBuffer(buffer_type buffer) { + auto it = used_buffers.find(buffer); + + usedSize_m -= buffer->getBufferSize(); + freeSize_m += buffer->getBufferSize(); + + used_buffers.erase(it); + free_buffers.insert(buffer); + } + + template + typename DefaultBufferHandler::buffer_type DefaultBufferHandler::findFreeBuffer( + size_type requiredSize) { + auto it = findSmallestSufficientBuffer(requiredSize); + if (it != free_buffers.end()) { + return *it; + } + return nullptr; + } + + template + typename DefaultBufferHandler::buffer_set_type::iterator + DefaultBufferHandler::findSmallestSufficientBuffer(size_type requiredSize) { + return std::find_if(free_buffers.begin(), free_buffers.end(), + [requiredSize](const buffer_type& buffer) { + return buffer->getBufferSize() >= requiredSize; + }); + } + + template + typename DefaultBufferHandler::buffer_type + DefaultBufferHandler::getFreeBuffer(buffer_type buffer) { + freeSize_m -= buffer->getBufferSize(); + usedSize_m += buffer->getBufferSize(); + + free_buffers.erase(buffer); + used_buffers.insert(buffer); + return buffer; + } + + template + typename DefaultBufferHandler::buffer_type + DefaultBufferHandler::reallocateLargestFreeBuffer(size_type requiredSize) { + auto largest_it = std::prev(free_buffers.end()); + buffer_type buffer = *largest_it; + + freeSize_m -= buffer->getBufferSize(); + usedSize_m += requiredSize; + + free_buffers.erase(buffer); + buffer->reallocBuffer(requiredSize); + + used_buffers.insert(buffer); + return buffer; + } + + template + typename DefaultBufferHandler::buffer_type DefaultBufferHandler::allocateNewBuffer( + size_type requiredSize) { + buffer_type newBuffer = std::make_shared(requiredSize); + + usedSize_m += newBuffer->getBufferSize(); + used_buffers.insert(newBuffer); + return newBuffer; + } + +} // namespace ippl + +#endif diff --git a/src/Communicate/Buffers.cpp b/src/Communicate/Buffers.cpp index cc0f0338e..04cbb1808 100644 --- a/src/Communicate/Buffers.cpp +++ b/src/Communicate/Buffers.cpp @@ -32,9 +32,16 @@ namespace ippl { } void Communicator::deleteAllBuffers() { - buffers_m.forAll([](Map&& m) { - m.clear(); + buffer_handlers_m.forAll([](BufferHandler&& bh) { + bh.deleteAllBuffers(); }); } + + void Communicator::freeAllBuffers() { + buffer_handlers_m.forAll([](BufferHandler&& bh) { + bh.freeAllBuffers(); + }); + } + } // namespace mpi } // namespace ippl diff --git a/src/Communicate/Buffers.hpp b/src/Communicate/Buffers.hpp index 94bbe4bd8..bcc2aba77 100644 --- a/src/Communicate/Buffers.hpp +++ b/src/Communicate/Buffers.hpp @@ -24,20 +24,21 @@ namespace ippl { namespace mpi { template - Communicator::buffer_type Communicator::getBuffer(int id, size_type size, - double overallocation) { - auto& buffers = buffers_m.get(); - size *= sizeof(T); - if (buffers.contains(id)) { - if (buffers[id]->getBufferSize() < size) { - buffers[id]->reallocBuffer(size); - } - return buffers[id]; - } - buffers[id] = std::make_shared>( - (size_type)(size * std::max(overallocation, defaultOveralloc_m))); - return buffers[id]; + Communicator::buffer_type Communicator::getBuffer(size_type size, + double overallocation) { + auto& buffer_handler = buffer_handlers_m.get(); + + return buffer_handler.getBuffer(size * sizeof(T), std::max(overallocation, defaultOveralloc_m)); + } + + + template + void Communicator::freeBuffer(Communicator::buffer_type buffer) { + auto& buffer_handler = buffer_handlers_m.get(); + + buffer_handler.freeBuffer(buffer); } + } // namespace mpi } // namespace ippl diff --git a/src/Communicate/CMakeLists.txt b/src/Communicate/CMakeLists.txt index 086f17710..8a94ca26f 100644 --- a/src/Communicate/CMakeLists.txt +++ b/src/Communicate/CMakeLists.txt @@ -1,11 +1,16 @@ set (_SRCS Communicator.cpp + CommunicatorLogging.cpp Environment.cpp Buffers.cpp Request.cpp + LogEntry.cpp ) set (_HDRS + LogEntry.h + BufferHandler.h + LoggingBufferHandler.h Archive.h Archive.hpp Buffers.hpp @@ -23,6 +28,7 @@ set (_HDRS Window.h Window.hpp PointToPoint.hpp + CommunicatorLogging.hpp ) include_directories ( diff --git a/src/Communicate/Communicator.h b/src/Communicate/Communicator.h index 00280e9ce..24cf0c0e3 100644 --- a/src/Communicate/Communicator.h +++ b/src/Communicate/Communicator.h @@ -8,6 +8,8 @@ #include #include +#include "Communicate/BufferHandler.h" +#include "Communicate/LoggingBufferHandler.h" #include "Communicate/Request.h" #include "Communicate/Status.h" @@ -132,10 +134,11 @@ namespace ippl { using buffer_type = std::shared_ptr>; private: + template - using map_type = std::map>; + using buffer_container_type = LoggingBufferHandler; - using buffer_map_type = typename detail::ContainerForAllSpaces::type; + using buffer_handler_type = typename detail::ContainerForAllSpaces::type; public: using size_type = detail::size_type; @@ -144,14 +147,14 @@ namespace ippl { template - buffer_type getBuffer(int id, size_type size, double overallocation = 1.0); - - template - void deleteBuffer(int id) { - buffers_m.get().erase(id); - } + buffer_type getBuffer(size_type size, double overallocation = 1.0); + void deleteAllBuffers(); + void freeAllBuffers(); + + template + void freeBuffer(buffer_type buffer); const MPI_Comm& getCommunicator() const noexcept { return *comm_m; } @@ -190,9 +193,18 @@ namespace ippl { } MPI_Irecv(ar.getBuffer(), msize, MPI_BYTE, src, tag, *comm_m, &request); } + + void printLogs(const std::string& filename); private: - buffer_map_type buffers_m; + std::vector gatherLocalLogs(); + void sendLogsToRank0(const std::vector& localLogs); + std::vector gatherLogsFromAllRanks(const std::vector& localLogs); + void writeLogsToFile(const std::vector& allLogs, const std::string& filename); + + + buffer_handler_type buffer_handlers_m; + double defaultOveralloc_m = 1.0; ///////////////////////////////////////////////////////////////////////////////////// diff --git a/src/Communicate/CommunicatorLogging.cpp b/src/Communicate/CommunicatorLogging.cpp new file mode 100644 index 000000000..ae2a2376c --- /dev/null +++ b/src/Communicate/CommunicatorLogging.cpp @@ -0,0 +1,123 @@ +#include "Communicate/Communicator.h" +#include "Communicate/LogEntry.h" +#include "Communicate/CommunicatorLogging.hpp" +#include "Utility/Inform.h" + +#include +#include + +namespace ippl { + namespace mpi { + void Communicator::printLogs(const std::string& filename) { + std::vector localLogs = gatherLocalLogs(); + + std::vector allLogs; + if (rank() == 0) { + allLogs = gatherLogsFromAllRanks(localLogs); + } else { + sendLogsToRank0(localLogs); + } + + if (rank() == 0) { + writeLogsToFile(allLogs, filename); + } + + } + + std::vector Communicator::gatherLocalLogs() { + std::vector localLogs; + + buffer_handlers_m.forAll([&](auto& loggingHandler) { + const auto& logs = loggingHandler.getLogs(); + localLogs.insert(localLogs.end(), logs.begin(), logs.end()); + }); + + return localLogs; + } + + void Communicator::sendLogsToRank0(const std::vector& localLogs) { + std::vector buffer = serializeLogs(localLogs); + + int logSize = buffer.size(); + + this->send(logSize, 1, 0, 0); + this->send(buffer.data(), logSize, 0, 0); + } + + std::vector Communicator::gatherLogsFromAllRanks(const std::vector& localLogs) { + std::vector allLogs = localLogs; + + for (int rank = 1; rank < size_m; ++rank) { + int logSize; + Status status; + + this->recv(logSize, 1, rank, 0, status); + + std::vector buffer(logSize); + this->recv(buffer.data(), logSize, rank, 0, status); + + std::vector deserializedLogs = deserializeLogs(buffer); + allLogs.insert(allLogs.end(), deserializedLogs.begin(), deserializedLogs.end()); + } + + return allLogs; + } + + + std::vector serializeLogs(const std::vector& logs) { + std::vector buffer; + + for (const auto& logEntry : logs) { + std::vector serializedEntry = logEntry.serialize(); + buffer.insert(buffer.end(), serializedEntry.begin(), serializedEntry.end()); + } + + return buffer; + } + + + std::vector deserializeLogs(const std::vector& buffer) { + std::vector logs; + size_t offset = 0; + + while (offset < buffer.size()) { + LogEntry logEntry = LogEntry::deserialize(buffer, offset); + + logs.push_back(logEntry); + + offset += logEntry.serialize().size(); + } + return logs; + } + + void Communicator::writeLogsToFile(const std::vector& allLogs, const std::string& filename) { + Inform logFile(0, filename.c_str(), Inform::OVERWRITE, 0); + logFile.setOutputLevel(1); + + logFile << "Timestamp,Method,Rank,MemorySpace,usedSize,FreeSize,Parameters" << endl; + + for (const auto& log : allLogs) { + auto timestamp = std::chrono::duration_cast( + log.timestamp.time_since_epoch()) + .count(); + + logFile << timestamp << "," << log.methodName << "," << log.rank << "," << log.memorySpace << "," + << log.usedSize << "," << log.freeSize; + + logFile << ",\""; + bool first = true; + for (const auto& [key, value] : log.parameters) { + if (!first) { + logFile << "; "; + } + logFile << key << ": " << value; + first = false; + } + logFile << "\"" << endl; + } + + logFile.flush(); + } + + } // namespace mpi +} // namespace ippl diff --git a/src/Communicate/CommunicatorLogging.hpp b/src/Communicate/CommunicatorLogging.hpp new file mode 100644 index 000000000..5746c3df9 --- /dev/null +++ b/src/Communicate/CommunicatorLogging.hpp @@ -0,0 +1,15 @@ +#ifndef IPPL_COMMUNICATOR_LOGGING_HPP +#define IPPL_COMMUNICATOR_LOGGING_HPP + +#include + +#include "Communicate/LogEntry.h" + +namespace ippl { + namespace mpi { + std::vector serializeLogs(const std::vector& logs); + std::vector deserializeLogs(const std::vector& buffer); + } // namespace mpi +} // namespace ippl + +#endif diff --git a/src/Communicate/LogEntry.cpp b/src/Communicate/LogEntry.cpp new file mode 100644 index 000000000..444221d6d --- /dev/null +++ b/src/Communicate/LogEntry.cpp @@ -0,0 +1,67 @@ +#include "Communicate/LogEntry.h" + +namespace ippl { + + void serializeString(std::vector& buffer, const std::string& str) { + size_t length = str.size(); + serializeBasicType(buffer, length); // First, serialize the length of the string + buffer.insert(buffer.end(), str.begin(), str.end()); // Then, serialize the string itself + } + + std::string deserializeString(const std::vector& buffer, size_t& offset) { + size_t length = + deserializeBasicType(buffer, offset); // Get the length of the string + std::string str(buffer.begin() + offset, + buffer.begin() + offset + length); // Extract the string + offset += length; + return str; + } + + std::vector LogEntry::serialize() const { + std::vector buffer; + + serializeString(buffer, methodName); + serializeBasicType(buffer, usedSize); + serializeBasicType(buffer, freeSize); + serializeString(buffer, memorySpace); + serializeBasicType(buffer, rank); + + // Serialize the timestamp (as duration since epoch) + auto duration = timestamp.time_since_epoch().count(); + serializeBasicType(buffer, duration); + + size_t mapSize = parameters.size(); + serializeBasicType(buffer, mapSize); + for (const auto& pair : parameters) { + serializeString(buffer, pair.first); + serializeString(buffer, pair.second); + } + + return buffer; + } + + LogEntry LogEntry::deserialize(const std::vector& buffer, size_t offset) { + LogEntry entry; + size_t current_pos = offset; + + entry.methodName = deserializeString(buffer, current_pos); + entry.usedSize = deserializeBasicType(buffer, current_pos); + entry.freeSize = deserializeBasicType(buffer, current_pos); + entry.memorySpace = deserializeString(buffer, current_pos); + entry.rank = deserializeBasicType(buffer, current_pos); + + auto duration = deserializeBasicType(buffer, current_pos); + entry.timestamp = std::chrono::time_point( + std::chrono::high_resolution_clock::duration(duration)); + + size_t mapSize = deserializeBasicType(buffer, current_pos); + for (size_t i = 0; i < mapSize; ++i) { + std::string key = deserializeString(buffer, current_pos); + std::string value = deserializeString(buffer, current_pos); + entry.parameters[key] = value; + } + + return entry; + } + +} // namespace ippl diff --git a/src/Communicate/LogEntry.h b/src/Communicate/LogEntry.h new file mode 100644 index 000000000..a853c3431 --- /dev/null +++ b/src/Communicate/LogEntry.h @@ -0,0 +1,42 @@ +#ifndef IPPL_LOGENTRY_H +#define IPPL_LOGENTRY_H + +#include +#include +#include +#include +#include + +namespace ippl { + + struct LogEntry { + std::string methodName; + std::map parameters; + size_t usedSize; + size_t freeSize; + std::string memorySpace; + int rank; + std::chrono::time_point timestamp; + + std::vector serialize() const; + static LogEntry deserialize(const std::vector& buffer, size_t offset = 0); + }; + + template + void serializeBasicType(std::vector& buffer, const T& value) { + size_t size = sizeof(T); + buffer.resize(buffer.size() + size); + std::memcpy(buffer.data() + buffer.size() - size, &value, size); + } + + template + T deserializeBasicType(const std::vector& buffer, size_t& offset) { + T value; + std::memcpy(&value, buffer.data() + offset, sizeof(T)); + offset += sizeof(T); + return value; + } + +} // namespace ippl + +#endif diff --git a/src/Communicate/LoggingBufferHandler.h b/src/Communicate/LoggingBufferHandler.h new file mode 100644 index 000000000..f6a0bd5d6 --- /dev/null +++ b/src/Communicate/LoggingBufferHandler.h @@ -0,0 +1,129 @@ +#ifndef IPPL_LOGGING_BUFFER_HANDLER_H +#define IPPL_LOGGING_BUFFER_HANDLER_H + +#include +#include +#include +#include +#include + +#include "Communicate/BufferHandler.h" +#include "Communicate/LogEntry.h" + +namespace ippl { + + /** + * @class LoggingBufferHandler + * @brief Decorator class for buffer management that adds logging capabilities to buffer + * operations. + * + * `LoggingBufferHandler` extends the basic functionality of `BufferHandler` by recording + * detailed logs of buffer operations, such as allocation, deallocation, and resizing. + * This allows tracking of memory usage patterns and provides a record of buffer activity. + * + * @tparam MemorySpace The memory space in which buffers are managed (e.g., HostSpace, + * CudaSpace). + * + * This class is a decorator for `BufferHandler` and does not modify buffer management logic. + * Instead, it adds logging for monitoring purposes. + */ + template + class LoggingBufferHandler : public BufferHandler { + public: + using buffer_type = typename BufferHandler::buffer_type; + using size_type = typename BufferHandler::size_type; + + /** + * @brief Constructs a LoggingBufferHandler with an existing buffer handler. + * @param handler A shared pointer to an `BufferHandler` instance used for buffer + * operations. + * @param rank The MPI rank for logging purposes, used to identify the source of logs. + */ + LoggingBufferHandler(std::shared_ptr> handler, int rank); + + /** + * @brief Default constructor, creates an internal `BufferHandler` for managing buffers. + * This constructor also initializes the rank by calling `MPI_Comm_rank`. + */ + LoggingBufferHandler(); + + /** + * @brief Allocates or retrieves a buffer and logs the action. + * + * Overrides `BufferHandler::getBuffer`, providing the same buffer allocation behavior + * while recording an entry in the log with the operation details. + * + * @param size Requested size of the buffer. + * @param overallocation Optional multiplier to allocate extra buffer space. + * @return A buffer object. + */ + buffer_type getBuffer(size_type size, double overallocation) override; + + /** + * @brief Frees a buffer and logs the action. + * + * Overrides `BufferHandler::freeBuffer`. Calls `BufferHandler::freeBuffer` and records the + * operation in the log. + * + * @param buffer The buffer to be freed. + */ + void freeBuffer(buffer_type buffer) override; + + /** + * @brief Frees all buffers and logs the action. + * + * Overrides `BufferHandler::freeAllBuffers`. Calls `BufferHandler::freeAllBuffers` and + * logs the operation. + */ + void freeAllBuffers() override; + + /** + * @brief Deletes all buffers and logs the action. + * + * Overrides `BufferHandler::deleteAllBuffers`. Calls `BufferHandler::deleteAllBuffers` and + * logs the operation. + */ + void deleteAllBuffers() override; + + /** + * @brief Retrieves the total size of allocated buffers. + * @return The size of allocated buffers. + */ + size_type getUsedSize() const override; + + /** + * @brief Retrieves the total size of free buffers. + * @return The size of free buffers. + */ + size_type getFreeSize() const override; + + /** + * @brief Retrieves the list of log entries. + * @return A constant reference to a vector containing log entries. + */ + const std::vector& getLogs() const; + + private: + std::shared_ptr> + handler_m; ///< Internal handler for buffer management. + std::vector logEntries_m; ///< Log entries for buffer operations. + int rank_m; ///< MPI rank for identifying log sources. + + /** + * @brief Records a method call in the log with its parameters. + * + * This method creates a log entry with details of the buffer operation, + * including the method name, parameters, allocated size, free size, + * memory space, rank, and timestamp. + * + * @param methodName Name of the method being logged (e.g., "getBuffer"). + * @param parameters A map of parameter names and values for the operation. + */ + void logMethod(const std::string& methodName, + const std::map& parameters); + }; +} // namespace ippl + +#include "Communicate/LoggingBufferHandler.hpp" + +#endif diff --git a/src/Communicate/LoggingBufferHandler.hpp b/src/Communicate/LoggingBufferHandler.hpp new file mode 100644 index 000000000..33f4269cd --- /dev/null +++ b/src/Communicate/LoggingBufferHandler.hpp @@ -0,0 +1,75 @@ +#ifndef IPPL_LOGGING_BUFFER_HANDLER_HPP +#define IPPL_LOGGING_BUFFER_HANDLER_HPP + +#include +#include + +namespace ippl { + + template + LoggingBufferHandler::LoggingBufferHandler( + std::shared_ptr> handler, int rank) + : handler_m(std::move(handler)) + , rank_m(rank) {} + + template + LoggingBufferHandler::LoggingBufferHandler() { + handler_m = std::make_shared>(); + MPI_Comm_rank(MPI_COMM_WORLD, &rank_m); + } + + template + typename LoggingBufferHandler::buffer_type + LoggingBufferHandler::getBuffer(size_type size, double overallocation) { + auto buffer = handler_m->getBuffer(size, overallocation); + logMethod("getBuffer", {{"size", std::to_string(size)}, + {"overallocation", std::to_string(overallocation)}}); + return buffer; + } + + template + void LoggingBufferHandler::freeBuffer(buffer_type buffer) { + handler_m->freeBuffer(buffer); + logMethod("freeBuffer", {}); + } + + template + void LoggingBufferHandler::freeAllBuffers() { + handler_m->freeAllBuffers(); + logMethod("freeAllBuffers", {}); + } + + template + void LoggingBufferHandler::deleteAllBuffers() { + handler_m->deleteAllBuffers(); + logMethod("deleteAllBuffers", {}); + } + + template + typename LoggingBufferHandler::size_type + LoggingBufferHandler::getUsedSize() const { + return handler_m->getUsedSize(); + } + + template + typename LoggingBufferHandler::size_type + LoggingBufferHandler::getFreeSize() const { + return handler_m->getFreeSize(); + } + + template + const std::vector& LoggingBufferHandler::getLogs() const { + return logEntries_m; + } + + template + void LoggingBufferHandler::logMethod( + const std::string& methodName, const std::map& parameters) { + logEntries_m.push_back({methodName, parameters, handler_m->getUsedSize(), + handler_m->getFreeSize(), MemorySpace::name(), rank_m, + std::chrono::high_resolution_clock::now()}); + } + +} // namespace ippl + +#endif diff --git a/src/Communicate/PointToPoint.hpp b/src/Communicate/PointToPoint.hpp index 830c16385..1c30f5f70 100644 --- a/src/Communicate/PointToPoint.hpp +++ b/src/Communicate/PointToPoint.hpp @@ -12,9 +12,9 @@ namespace ippl { template void Communicator::send(const T* buf, int count, int dest, int tag) { - MPI_Datatype type = get_mpi_datatype(buf); + MPI_Datatype type = get_mpi_datatype(*buf); - MPI_Send(&buf, count, type, dest, tag, comm_m); + MPI_Send(buf, count, type, dest, tag, *comm_m); } template @@ -26,7 +26,7 @@ namespace ippl { void Communicator::recv(T* output, int count, int source, int tag, Status& status) { MPI_Datatype type = get_mpi_datatype(*output); - MPI_Recv(output, count, type, source, tag, comm_m, &status); + MPI_Recv(output, count, type, source, tag, *comm_m, status); } /* @@ -42,7 +42,7 @@ namespace ippl { void Communicator::isend(const T* buffer, int count, int dest, int tag, Request& request) { MPI_Datatype type = get_mpi_datatype(*buffer); - MPI_Isend(buffer, count, type, dest, tag, *comm_m, request); + MPI_Isend(buffer, count, type, dest, tag, comm_m, request); } template diff --git a/src/Field/BcTypes.hpp b/src/Field/BcTypes.hpp index fba6f65b5..4cc10e720 100644 --- a/src/Field/BcTypes.hpp +++ b/src/Field/BcTypes.hpp @@ -262,8 +262,7 @@ namespace ippl { detail::size_type nSends; halo.pack(range, view, haloData_m, nSends); - buffer_type buf = comm.template getBuffer( - mpi::tag::PERIODIC_BC_SEND + i, nSends); + buffer_type buf = comm.template getBuffer(nSends); comm.isend(rank, tag, haloData_m, *buf, requests[i], nSends); buf->resetWritePos(); @@ -279,8 +278,7 @@ namespace ippl { detail::size_type nRecvs = range.size(); - buffer_type buf = comm.template getBuffer( - mpi::tag::PERIODIC_BC_RECV + i, nRecvs); + buffer_type buf = comm.template getBuffer(nRecvs); comm.recv(rank, matchtag, haloData_m, *buf, nRecvs * sizeof(T), nRecvs); buf->resetReadPos(); @@ -290,6 +288,7 @@ namespace ippl { if (!requests.empty()) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + comm.freeAllBuffers(); } // For all other processors do nothing } else { diff --git a/src/Field/HaloCells.hpp b/src/Field/HaloCells.hpp index 579e90f13..34841f1ae 100644 --- a/src/Field/HaloCells.hpp +++ b/src/Field/HaloCells.hpp @@ -71,8 +71,7 @@ namespace ippl { size_type nsends; pack(range, view, haloData_m, nsends); - buffer_type buf = comm.template getBuffer( - mpi::tag::HALO_SEND + i * cubeCount + index, nsends); + buffer_type buf = comm.template getBuffer(nsends); comm.isend(targetRank, tag, haloData_m, *buf, requests[requestIndex++], nsends); buf->resetWritePos(); @@ -95,8 +94,7 @@ namespace ippl { size_type nrecvs = range.size(); - buffer_type buf = comm.template getBuffer( - mpi::tag::HALO_RECV + i * cubeCount + index, nrecvs); + buffer_type buf = comm.template getBuffer(nrecvs); comm.recv(sourceRank, tag, haloData_m, *buf, nrecvs * sizeof(T), nrecvs); buf->resetReadPos(); @@ -108,6 +106,7 @@ namespace ippl { if (totalRequests > 0) { MPI_Waitall(totalRequests, requests.data(), MPI_STATUSES_IGNORE); } + comm.freeAllBuffers(); } template diff --git a/src/Particle/ParticleBase.h b/src/Particle/ParticleBase.h index d261f11c3..97017c708 100644 --- a/src/Particle/ParticleBase.h +++ b/src/Particle/ParticleBase.h @@ -289,7 +289,7 @@ namespace ippl { * @param hash a hash view indicating which particles need to be sent to which rank */ template - void sendToRank(int rank, int tag, int sendNum, std::vector& requests, + void sendToRank(int rank, int tag, std::vector& requests, const HashType& hash); /*! @@ -299,7 +299,7 @@ namespace ippl { * @param recvNum the number of messages already received (to distinguish the buffers) * @param nRecvs the number of particles to receive */ - void recvFromRank(int rank, int tag, int recvNum, size_type nRecvs); + void recvFromRank(int rank, int tag, size_type nRecvs); /*! * Serialize to do MPI calls. diff --git a/src/Particle/ParticleBase.hpp b/src/Particle/ParticleBase.hpp index 0ce60a61e..adf4b9204 100644 --- a/src/Particle/ParticleBase.hpp +++ b/src/Particle/ParticleBase.hpp @@ -264,7 +264,7 @@ namespace ippl { template template - void ParticleBase::sendToRank(int rank, int tag, int sendNum, + void ParticleBase::sendToRank(int rank, int tag, std::vector& requests, const HashType& hash) { size_type nSends = hash.size(); @@ -280,7 +280,7 @@ namespace ippl { return; } - auto buf = Comm->getBuffer(mpi::tag::PARTICLE_SEND + sendNum, bufSize); + auto buf = Comm->getBuffer(bufSize); Comm->isend(rank, tag++, *this, *buf, requests.back(), nSends); buf->resetWritePos(); @@ -288,15 +288,14 @@ namespace ippl { } template - void ParticleBase::recvFromRank(int rank, int tag, int recvNum, - size_type nRecvs) { + void ParticleBase::recvFromRank(int rank, int tag, size_type nRecvs) { detail::runForAllSpaces([&]() { size_type bufSize = packedSize(nRecvs); if (bufSize == 0) { return; } - auto buf = Comm->getBuffer(mpi::tag::PARTICLE_RECV + recvNum, bufSize); + auto buf = Comm->getBuffer(bufSize); Comm->recv(rank, tag++, *this, *buf, bufSize, nRecvs); buf->resetReadPos(); diff --git a/src/Particle/ParticleSpatialLayout.hpp b/src/Particle/ParticleSpatialLayout.hpp index 2d15dfdbe..b926f48f9 100644 --- a/src/Particle/ParticleSpatialLayout.hpp +++ b/src/Particle/ParticleSpatialLayout.hpp @@ -114,13 +114,12 @@ namespace ippl { int tag = Comm->next_tag(mpi::tag::P_SPATIAL_LAYOUT, mpi::tag::P_LAYOUT_CYCLE); - int sends = 0; for (int rank = 0; rank < nRanks; ++rank) { if (nSends[rank] > 0) { hash_type hash("hash", nSends[rank]); fillHash(rank, ranks, hash); - pc.sendToRank(rank, tag, sends++, requests, hash); + pc.sendToRank(rank, tag, requests, hash); } } IpplTimings::stopTimer(sendTimer); @@ -137,10 +136,10 @@ namespace ippl { static IpplTimings::TimerRef recvTimer = IpplTimings::getTimer("particleRecv"); IpplTimings::startTimer(recvTimer); // 4th step - int recvs = 0; + for (int rank = 0; rank < nRanks; ++rank) { if (nRecvs[rank] > 0) { - pc.recvFromRank(rank, tag, recvs++, nRecvs[rank]); + pc.recvFromRank(rank, tag, nRecvs[rank]); } } IpplTimings::stopTimer(recvTimer); @@ -150,6 +149,8 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + Comm->freeAllBuffers(); + IpplTimings::stopTimer(sendTimer); IpplTimings::stopTimer(ParticleUpdateTimer); diff --git a/src/PoissonSolvers/FFTOpenPoissonSolver.hpp b/src/PoissonSolvers/FFTOpenPoissonSolver.hpp index 74b25d002..c4c4dcc9c 100644 --- a/src/PoissonSolvers/FFTOpenPoissonSolver.hpp +++ b/src/PoissonSolvers/FFTOpenPoissonSolver.hpp @@ -107,7 +107,7 @@ void unpack(const ippl::NDIndex<3> intersect, } template -void solver_send(int BUF_MSG, int TAG, int id, int i, const ippl::NDIndex intersection, +void solver_send(int TAG, int id, int i, const ippl::NDIndex intersection, const ippl::NDIndex ldom, int nghost, Kokkos::View& view, ippl::detail::FieldBufferData& fd, std::vector& requests) { using memory_space = typename Kokkos::View::memory_space; @@ -119,7 +119,7 @@ void solver_send(int BUF_MSG, int TAG, int id, int i, const ippl::NDIndex i // Buffer message indicates the domain intersection (x, y, z, xy, yz, xz, xyz). ippl::mpi::Communicator::buffer_type buf = - ippl::Comm->getBuffer(BUF_MSG + id * 8 + i, nsends); + ippl::Comm->getBuffer(nsends); int tag = TAG + id; @@ -128,19 +128,18 @@ void solver_send(int BUF_MSG, int TAG, int id, int i, const ippl::NDIndex i } template -void solver_recv(int BUF_MSG, int TAG, int id, int i, const ippl::NDIndex intersection, +void solver_recv(int TAG, int id, int i, const ippl::NDIndex intersection, const ippl::NDIndex ldom, int nghost, Kokkos::View& view, ippl::detail::FieldBufferData& fd, bool x = false, bool y = false, bool z = false) { using memory_space = typename Kokkos::View::memory_space; ippl::mpi::Communicator::size_type nrecvs; - const int myRank = ippl::Comm->rank(); - nrecvs = intersection.size(); + nrecvs = intersection.size(); // Buffer message indicates the domain intersection (x, y, z, xy, yz, xz, xyz). ippl::mpi::Communicator::buffer_type buf = - ippl::Comm->getBuffer(BUF_MSG + 8 * id + myRank, nrecvs); + ippl::Comm->getBuffer(nrecvs); int tag = TAG + id; @@ -554,14 +553,13 @@ namespace ippl { if (lDomains2[i].touches(ldom1)) { auto intersection = lDomains2[i].intersect(ldom1); - solver_send(mpi::tag::SOLVER_SEND, mpi::tag::OPEN_SOLVER, 0, i, intersection, + solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom1, nghost1, view1, fd_m, requests); } } // receive const auto& lDomains1 = layout_mp->getHostLocalDomains(); - int myRank = Comm->rank(); for (int i = 0; i < ranks; ++i) { if (lDomains1[i].touches(ldom2)) { @@ -570,8 +568,7 @@ namespace ippl { mpi::Communicator::size_type nrecvs; nrecvs = intersection.size(); - buffer_type buf = - Comm->getBuffer(mpi::tag::SOLVER_RECV + myRank, nrecvs); + buffer_type buf = Comm->getBuffer(nrecvs); Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs), nrecvs); buf->resetReadPos(); @@ -584,6 +581,7 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); } else { Kokkos::parallel_for( @@ -680,14 +678,13 @@ namespace ippl { if (lDomains1[i].touches(ldom2)) { auto intersection = lDomains1[i].intersect(ldom2); - solver_send(mpi::tag::SOLVER_SEND, mpi::tag::OPEN_SOLVER, 0, i, + solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2, nghost2, view2, fd_m, requests); } } // receive const auto& lDomains2 = layout2_m->getHostLocalDomains(); - int myRank = Comm->rank(); for (int i = 0; i < ranks; ++i) { if (ldom1.touches(lDomains2[i])) { @@ -696,8 +693,7 @@ namespace ippl { mpi::Communicator::size_type nrecvs; nrecvs = intersection.size(); - buffer_type buf = Comm->getBuffer( - mpi::tag::SOLVER_RECV + myRank, nrecvs); + buffer_type buf = Comm->getBuffer(nrecvs); Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs), nrecvs); @@ -711,6 +707,7 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); } else { Kokkos::parallel_for( @@ -837,14 +834,13 @@ namespace ippl { if (lDomains1[i].touches(ldom2)) { auto intersection = lDomains1[i].intersect(ldom2); - solver_send(mpi::tag::SOLVER_SEND, mpi::tag::OPEN_SOLVER, 0, i, + solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2, nghost2, view2, fd_m, requests); } } // receive const auto& lDomains2 = layout2_m->getHostLocalDomains(); - int myRank = Comm->rank(); for (int i = 0; i < ranks; ++i) { if (ldom1.touches(lDomains2[i])) { @@ -853,8 +849,7 @@ namespace ippl { mpi::Communicator::size_type nrecvs; nrecvs = intersection.size(); - buffer_type buf = Comm->getBuffer( - mpi::tag::SOLVER_RECV + myRank, nrecvs); + buffer_type buf = Comm->getBuffer(nrecvs); Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs), nrecvs); @@ -868,6 +863,7 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); } else { Kokkos::parallel_for( @@ -995,14 +991,13 @@ namespace ippl { if (lDomains1[i].touches(ldom2)) { auto intersection = lDomains1[i].intersect(ldom2); - solver_send(mpi::tag::SOLVER_SEND, mpi::tag::OPEN_SOLVER, 0, i, + solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2, nghost2, view2, fd_m, requests); } } // receive const auto& lDomains2 = layout2_m->getHostLocalDomains(); - int myRank = Comm->rank(); for (int i = 0; i < ranks; ++i) { if (ldom1.touches(lDomains2[i])) { @@ -1011,8 +1006,7 @@ namespace ippl { mpi::Communicator::size_type nrecvs; nrecvs = intersection.size(); - buffer_type buf = Comm->getBuffer( - mpi::tag::SOLVER_RECV + myRank, nrecvs); + buffer_type buf = Comm->getBuffer(nrecvs); Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs), nrecvs); @@ -1026,6 +1020,7 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); } else { Kokkos::parallel_for( @@ -1443,7 +1438,7 @@ namespace ippl { if (ldom_g.touches(intersection)) { intersection = intersection.intersect(ldom_g); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 0, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1461,7 +1456,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 1, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1479,7 +1474,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 2, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1497,7 +1492,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 3, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1517,7 +1512,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 4, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1536,7 +1531,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 5, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1555,7 +1550,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 6, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1576,7 +1571,7 @@ namespace ippl { if (ldom_g.touches(domain4)) { intersection = ldom_g.intersect(domain4); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 7, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1590,7 +1585,7 @@ namespace ippl { if (lDomains4[i].touches(intersection)) { intersection = intersection.intersect(lDomains4[i]); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 0, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom, nghost, view, fd_m); } } @@ -1613,7 +1608,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 1, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom, nghost, view, fd_m, true, false, false); } } @@ -1636,7 +1631,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 2, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom, nghost, view, fd_m, false, true, false); } } @@ -1659,7 +1654,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 3, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom, nghost, view, fd_m, false, false, true); } } @@ -1686,7 +1681,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 4, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom, nghost, view, fd_m, true, true, false); } } @@ -1713,7 +1708,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 5, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom, nghost, view, fd_m, false, true, true); } } @@ -1740,7 +1735,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 6, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom, nghost, view, fd_m, true, false, true); } } @@ -1771,7 +1766,7 @@ namespace ippl { intersection = intersection.intersect(domain4); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 7, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom, nghost, view, fd_m, true, true, true); } } @@ -1780,6 +1775,7 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); }; // CommunicateVico for DCT_VICO (2N+1 to 2N) @@ -1845,7 +1841,7 @@ namespace ippl { if (ldom_g.touches(intersection)) { intersection = intersection.intersect(ldom_g); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 0, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1863,7 +1859,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 1, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1881,7 +1877,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 2, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1899,7 +1895,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 3, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1919,7 +1915,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 4, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1939,7 +1935,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 5, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1959,7 +1955,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 6, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1981,7 +1977,7 @@ namespace ippl { if (ldom_g.touches(domain2n1)) { intersection = ldom_g.intersect(domain2n1); - solver_send(mpi::tag::VICO_SEND, mpi::tag::VICO_SOLVER, 7, i, intersection, + solver_send(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom_g, nghost_g, view_g, fd_m, requests); } } @@ -1995,7 +1991,7 @@ namespace ippl { if (lDomains2n1[i].touches(intersection)) { intersection = intersection.intersect(lDomains2n1[i]); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 0, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom, nghost, view, fd_m); } } @@ -2018,7 +2014,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 1, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom, nghost, view, fd_m, true, false, false); } } @@ -2041,7 +2037,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 2, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom, nghost, view, fd_m, false, true, false); } } @@ -2064,7 +2060,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 3, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom, nghost, view, fd_m, false, false, true); } } @@ -2091,7 +2087,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 4, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom, nghost, view, fd_m, true, true, false); } } @@ -2118,7 +2114,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 5, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom, nghost, view, fd_m, false, true, true); } } @@ -2145,7 +2141,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 6, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom, nghost, view, fd_m, true, false, true); } } @@ -2176,7 +2172,7 @@ namespace ippl { intersection = intersection.intersect(domain2n1); - solver_recv(mpi::tag::VICO_RECV, mpi::tag::VICO_SOLVER, 7, i, intersection, + solver_recv(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom, nghost, view, fd_m, true, true, true); } } @@ -2185,5 +2181,6 @@ namespace ippl { if (requests.size() > 0) { MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); } + ippl::Comm->freeAllBuffers(); }; } // namespace ippl diff --git a/src/Utility/TypeUtils.h b/src/Utility/TypeUtils.h index 0d86deef8..a49474bca 100644 --- a/src/Utility/TypeUtils.h +++ b/src/Utility/TypeUtils.h @@ -410,6 +410,12 @@ namespace ippl { using container_type = MultispaceContainer; using type = typename TypeForAllSpaces::memory_spaces_type; + + // Static factory function that takes a lambda to initialize each memory space + template + static type createContainer(Functor&& initFunc) { + return type{std::forward(initFunc)}; + } }; /*! diff --git a/test/field/TestHalo.cpp b/test/field/TestHalo.cpp index 8b78bc010..71df004b0 100644 --- a/test/field/TestHalo.cpp +++ b/test/field/TestHalo.cpp @@ -144,6 +144,9 @@ int main(int argc, char* argv[]) { IpplTimings::stopTimer(mainTimer); IpplTimings::print(); IpplTimings::print(std::string("timing.dat")); + + layout.comm.printLogs("buffer_memory_logging.csv"); + } ippl::finalize(); diff --git a/unit_tests/Communicate/BufferHandler.cpp b/unit_tests/Communicate/BufferHandler.cpp new file mode 100644 index 000000000..2c97d9648 --- /dev/null +++ b/unit_tests/Communicate/BufferHandler.cpp @@ -0,0 +1,205 @@ +#include "Ippl.h" + +#include "Communicate/BufferHandler.h" + +#include "Utility/TypeUtils.h" + +#include "TestUtils.h" +#include "gtest/gtest.h" + +using MemorySpaces = ippl::detail::TypeForAllSpaces<::testing::Types>::memory_spaces_type; + +template +class TypedBufferHandlerTest : public ::testing::Test { +protected: + using memory_space = MemorySpace; + + class TestableBufferHandler : public ippl::DefaultBufferHandler { + public: + using ippl::DefaultBufferHandler::deleteAllBuffers; + using ippl::DefaultBufferHandler::freeAllBuffers; + + size_t usedBuffersSize() const { return this->used_buffers.size(); } + + size_t freeBuffersSize() const { return this->free_buffers.size(); } + }; + + void SetUp() override { handler = std::make_unique(); } + + void TearDown() override { handler.reset(); } + + std::unique_ptr handler; +}; + +TYPED_TEST_CASE(TypedBufferHandlerTest, MemorySpaces); + +// Test: Allocating a buffer when no free buffers are available +TYPED_TEST(TypedBufferHandlerTest, GetBuffer_EmptyFreeBuffers) { + auto buffer = this->handler->getBuffer(100, 1.0); + ASSERT_NE(buffer, nullptr); + EXPECT_EQ(buffer->getBufferSize(), 100); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Allocating a buffer when a suitable free buffer is available +TYPED_TEST(TypedBufferHandlerTest, GetBuffer_SuitableBufferAvailable) { + auto buffer1 = this->handler->getBuffer(50, 1.0); + this->handler->freeBuffer(buffer1); + + auto buffer2 = this->handler->getBuffer(40, 1.0); + EXPECT_EQ(buffer2->getBufferSize(), 50); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Freeing a used buffer moves it to the free buffer pool +TYPED_TEST(TypedBufferHandlerTest, FreeBuffer) { + auto buffer = this->handler->getBuffer(100, 1.0); + this->handler->freeBuffer(buffer); + + EXPECT_EQ(this->handler->usedBuffersSize(), 0); + EXPECT_EQ(this->handler->freeBuffersSize(), 1); +} + +// Test: Freeing all used buffers moves them to the free buffer pool +TYPED_TEST(TypedBufferHandlerTest, FreeAllBuffers) { + auto buffer1 = this->handler->getBuffer(50, 1.0); + auto buffer2 = this->handler->getBuffer(100, 1.0); + + this->handler->freeAllBuffers(); + + EXPECT_EQ(this->handler->usedBuffersSize(), 0); + EXPECT_EQ(this->handler->freeBuffersSize(), 2); +} + +// Test: Deleting all buffers removes both used and free buffers +TYPED_TEST(TypedBufferHandlerTest, DeleteAllBuffers) { + this->handler->getBuffer(50, 1.0); + auto buffer = this->handler->getBuffer(100, 1.0); + this->handler->freeBuffer(buffer); + + this->handler->deleteAllBuffers(); + + EXPECT_EQ(this->handler->usedBuffersSize(), 0); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Allocating a buffer larger than any available free buffer resizes the largest free buffer +TYPED_TEST(TypedBufferHandlerTest, GetBuffer_ResizeLargerThanAvailable) { + auto smallBuffer = this->handler->getBuffer(50, 1.0); + this->handler->freeBuffer(smallBuffer); + + auto largeBuffer = this->handler->getBuffer(200, 1.0); + EXPECT_EQ(largeBuffer->getBufferSize(), 200); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Allocating a buffer that matches the size of a free buffer exactly +TYPED_TEST(TypedBufferHandlerTest, GetBuffer_ExactSizeMatch) { + auto buffer1 = this->handler->getBuffer(100, 1.0); + this->handler->freeBuffer(buffer1); + + auto buffer2 = this->handler->getBuffer(100, 1.0); + EXPECT_EQ(buffer2->getBufferSize(), 100); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Freeing a buffer that does not exist in the used pool has no effect +TYPED_TEST(TypedBufferHandlerTest, FreeNonExistentBuffer) { + auto buffer = this->handler->getBuffer(100, 1.0); + auto newBuffer = + std::make_shared>(200); + + this->handler->freeBuffer(newBuffer); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: Repeatedly allocating and freeing buffers should consolidate free buffers +TYPED_TEST(TypedBufferHandlerTest, RepeatedAllocateAndFreeCycle) { + for (int i = 0; i < 10; ++i) { + auto buffer = this->handler->getBuffer(100, 1.0); + this->handler->freeBuffer(buffer); + } + + EXPECT_EQ(this->handler->usedBuffersSize(), 0); + EXPECT_EQ(this->handler->freeBuffersSize(), 1); +} + +// Test: Allocating a zero-size buffer should succeed and result in a non-null buffer +TYPED_TEST(TypedBufferHandlerTest, GetBuffer_ZeroSize) { + auto buffer = this->handler->getBuffer(0, 1.0); + ASSERT_NE(buffer, nullptr); + EXPECT_GE(buffer->getBufferSize(), 0); + EXPECT_EQ(this->handler->usedBuffersSize(), 1); + EXPECT_EQ(this->handler->freeBuffersSize(), 0); +} + +// Test: The buffer sizes of an empty BufferHandler are zero +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_EmptyHandler) { + EXPECT_EQ(this->handler->getUsedSize(), 0); + EXPECT_EQ(this->handler->getFreeSize(), 0); +} + +// Test: Allocating increases used buffer size correctly +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_AfterBufferAllocation) { + auto buffer = this->handler->getBuffer(100, 1.0); + EXPECT_EQ(this->handler->getUsedSize(), 100); + EXPECT_EQ(this->handler->getFreeSize(), 0); +} + +// Test: Allocating increases used buffer size correctly +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_AfterFreeBuffer) { + auto buffer = this->handler->getBuffer(100, 1.0); + this->handler->freeBuffer(buffer); + + EXPECT_EQ(this->handler->getUsedSize(), 0); + EXPECT_EQ(this->handler->getFreeSize(), 100); +} + +// Test: Correct size is computed after freeing all buffers +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_AfterFreeAllBuffers) { + auto buffer1 = this->handler->getBuffer(50, 1.0); + auto buffer2 = this->handler->getBuffer(100, 1.0); + + this->handler->freeAllBuffers(); + + EXPECT_EQ(this->handler->getUsedSize(), 0); + EXPECT_EQ(this->handler->getFreeSize(), 150); +} + +// Test: Deleting all buffers results in zero free or used buffer sizes +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_AfterDeleteAllBuffers) { + this->handler->getBuffer(50, 1.0); + this->handler->getBuffer(100, 1.0); + + this->handler->deleteAllBuffers(); + + EXPECT_EQ(this->handler->getUsedSize(), 0); + EXPECT_EQ(this->handler->getFreeSize(), 0); +} + +// Test: Buffer size is correctly accounted for if a free buffer is available but we request a larger one, thus reallocating this one +TYPED_TEST(TypedBufferHandlerTest, GetAllocatedAndFreeSize_ResizeBufferLargerThanAvailable) { + auto smallBuffer = this->handler->getBuffer(50, 1.0); + this->handler->freeBuffer(smallBuffer); + + auto largeBuffer = this->handler->getBuffer(200, 1.0); + + EXPECT_EQ(this->handler->getUsedSize(), 200); + EXPECT_EQ(this->handler->getFreeSize(), 0); +} + +int main(int argc, char* argv[]) { + int success = 1; + ippl::initialize(argc, argv); + { + ::testing::InitGoogleTest(&argc, argv); + success = RUN_ALL_TESTS(); + } + ippl::finalize(); + return success; +} diff --git a/unit_tests/Communicate/CMakeLists.txt b/unit_tests/Communicate/CMakeLists.txt new file mode 100644 index 000000000..df21a5e5d --- /dev/null +++ b/unit_tests/Communicate/CMakeLists.txt @@ -0,0 +1,45 @@ +file (RELATIVE_PATH _relPath "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") +message (STATUS "Adding unit tests found in ${_relPath}") + +include_directories ( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +link_directories ( + ${CMAKE_CURRENT_SOURCE_DIR} + ${Kokkos_DIR}/.. +) + +add_executable (BufferHandler BufferHandler.cpp) +gtest_discover_tests(BufferHandler PROPERTIES TEST_DISCOVERY_TIMEOUT 600) + +add_executable (LoggingBufferHandler LoggingBufferHandler.cpp) +gtest_discover_tests(LoggingBufferHandler PROPERTIES TEST_DISCOVERY_TIMEOUT 600) + +add_executable (LogEntry LogEntry.cpp) +gtest_discover_tests(LogEntry PROPERTIES TEST_DISCOVERY_TIMEOUT 600) + +target_link_libraries ( + BufferHandler + ippl + pthread + GTest::gtest_main + ${MPI_CXX_LIBRARIES} +) + +target_link_libraries ( + LoggingBufferHandler + ippl + pthread + GTest::gtest_main + ${MPI_CXX_LIBRARIES} +) + +target_link_libraries ( + LogEntry + ippl + pthread + GTest::gtest_main + ${MPI_CXX_LIBRARIES} +) diff --git a/unit_tests/Communicate/LogEntry.cpp b/unit_tests/Communicate/LogEntry.cpp new file mode 100644 index 000000000..f351cf04d --- /dev/null +++ b/unit_tests/Communicate/LogEntry.cpp @@ -0,0 +1,70 @@ +#include "Ippl.h" + +#include "Communicate/LogEntry.h" + +#include "TestUtils.h" +#include "gtest/gtest.h" + +ippl::LogEntry createSampleLogEntry() { + ippl::LogEntry logEntry; + logEntry.methodName = "TestMethod"; + logEntry.usedSize = 1024; + logEntry.freeSize = 512; + logEntry.memorySpace = "HostSpace"; + logEntry.rank = 1; + logEntry.timestamp = std::chrono::high_resolution_clock::now(); + + logEntry.parameters["overallocation"] = "1.5"; + logEntry.parameters["buffer_size"] = "2048"; + + return logEntry; +} + +// Test to ensure LogEntry serialization produces a non-empty buffer +TEST(LogEntryTest, Serialize) { + ippl::LogEntry logEntry = createSampleLogEntry(); + + std::vector buffer; + buffer = logEntry.serialize(); + + EXPECT_GT(buffer.size(), 0); +} + +// Test to ensure LogEntry can be deserialized correctly +TEST(LogEntryTest, Deserialize) { + ippl::LogEntry logEntry = createSampleLogEntry(); + + std::vector buffer; + buffer = logEntry.serialize(); + + ippl::LogEntry deserializedLogEntry = ippl::LogEntry::deserialize(buffer); + + // Verify that all fields match the original LogEntry + EXPECT_EQ(deserializedLogEntry.methodName, logEntry.methodName); + EXPECT_EQ(deserializedLogEntry.usedSize, logEntry.usedSize); + EXPECT_EQ(deserializedLogEntry.freeSize, logEntry.freeSize); + EXPECT_EQ(deserializedLogEntry.memorySpace, logEntry.memorySpace); + EXPECT_EQ(deserializedLogEntry.rank, logEntry.rank); + + // Verify that all parameters are preserved + EXPECT_EQ(deserializedLogEntry.parameters.size(), logEntry.parameters.size()); + EXPECT_EQ(deserializedLogEntry.parameters.at("overallocation"), + logEntry.parameters.at("overallocation")); + EXPECT_EQ(deserializedLogEntry.parameters.at("buffer_size"), + logEntry.parameters.at("buffer_size")); + + auto originalTime = logEntry.timestamp.time_since_epoch().count(); + auto deserializedTime = deserializedLogEntry.timestamp.time_since_epoch().count(); + EXPECT_EQ(originalTime, deserializedTime); +} + +int main(int argc, char* argv[]) { + int success = 1; + ippl::initialize(argc, argv); + { + ::testing::InitGoogleTest(&argc, argv); + success = RUN_ALL_TESTS(); + } + ippl::finalize(); + return success; +} diff --git a/unit_tests/Communicate/LoggingBufferHandler.cpp b/unit_tests/Communicate/LoggingBufferHandler.cpp new file mode 100644 index 000000000..a0db38366 --- /dev/null +++ b/unit_tests/Communicate/LoggingBufferHandler.cpp @@ -0,0 +1,121 @@ +#include "Ippl.h" + +#include "Communicate/LoggingBufferHandler.h" + +#include "Communicate/BufferHandler.h" +#include "gtest/gtest.h" + +using MemorySpaces = ippl::detail::TypeForAllSpaces<::testing::Types>::memory_spaces_type; + +template +class TypedLoggingBufferHandlerTest : public ::testing::Test { +protected: + void SetUp() override { + rank = 0; + this->bufferHandler = std::make_shared>(); + this->loggingHandler = + std::make_shared>(bufferHandler, rank); + } + + int rank; + std::shared_ptr> bufferHandler; + std::shared_ptr> loggingHandler; +}; + +TYPED_TEST_SUITE(TypedLoggingBufferHandlerTest, MemorySpaces); + +template +void compareNumericParameter(const std::string& paramString, T expectedValue, + double tolerance = 1e-6) { + if constexpr (std::is_floating_point::value) { + double actualValue = std::stod(paramString); + EXPECT_NEAR(actualValue, expectedValue, tolerance); + } else if constexpr (std::is_integral::value) { + int actualValue = std::stoi(paramString); + EXPECT_EQ(actualValue, expectedValue); + } else { + FAIL() << "Unsupported type for numeric comparison"; + } +} + +// Test: The information stored when calling getBuffer is correct +TYPED_TEST(TypedLoggingBufferHandlerTest, GetBufferLogsCorrectly) { + auto buffer = this->loggingHandler->getBuffer(100, 1.5); + const auto& logs = this->loggingHandler->getLogs(); + ASSERT_EQ(logs.size(), 1); + + const auto& entry = logs[0]; + EXPECT_EQ(entry.methodName, "getBuffer"); + + std::string sizeStr = entry.parameters.at("size"); + compareNumericParameter(sizeStr, 100); + + std::string overallocationStr = entry.parameters.at("overallocation"); + compareNumericParameter(overallocationStr, 1.5); + + EXPECT_EQ(entry.usedSize, this->bufferHandler->getUsedSize()); + EXPECT_EQ(entry.freeSize, this->bufferHandler->getFreeSize()); + EXPECT_EQ(entry.memorySpace, TypeParam::name()); + EXPECT_EQ(entry.rank, this->rank); +} + +// Test: The information stored when calling freeBuffer is correct +TYPED_TEST(TypedLoggingBufferHandlerTest, FreeBufferLogsCorrectly) { + auto buffer = this->loggingHandler->getBuffer(100, 1.0); + this->loggingHandler->freeBuffer(buffer); + + const auto& logs = this->loggingHandler->getLogs(); + ASSERT_EQ(logs.size(), 2); + + const auto& entry = logs[1]; + EXPECT_EQ(entry.methodName, "freeBuffer"); + EXPECT_EQ(entry.usedSize, this->bufferHandler->getUsedSize()); + EXPECT_EQ(entry.freeSize, this->bufferHandler->getFreeSize()); + EXPECT_EQ(entry.memorySpace, TypeParam::name()); + EXPECT_EQ(entry.rank, this->rank); +} + +// Test: The information stored when calling freeAllBuffers is correct +TYPED_TEST(TypedLoggingBufferHandlerTest, FreeAllBuffersLogsCorrectly) { + this->loggingHandler->getBuffer(100, 1.0); + this->loggingHandler->getBuffer(200, 1.0); + this->loggingHandler->freeAllBuffers(); + + const auto& logs = this->loggingHandler->getLogs(); + ASSERT_EQ(logs.size(), 3); + + const auto& entry = logs[2]; + EXPECT_EQ(entry.methodName, "freeAllBuffers"); + EXPECT_EQ(entry.usedSize, this->bufferHandler->getUsedSize()); + EXPECT_EQ(entry.freeSize, this->bufferHandler->getFreeSize()); + EXPECT_EQ(entry.memorySpace, TypeParam::name()); + EXPECT_EQ(entry.rank, this->rank); +} + +// Test: The information stored when calling deleteAllBuffers is correct +TYPED_TEST(TypedLoggingBufferHandlerTest, DeleteAllBuffersLogsCorrectly) { + this->loggingHandler->getBuffer(100, 1.0); + this->loggingHandler->getBuffer(200, 1.0); + this->loggingHandler->deleteAllBuffers(); + + const auto& logs = this->loggingHandler->getLogs(); + ASSERT_EQ(logs.size(), 3); + + const auto& entry = logs[2]; + EXPECT_EQ(entry.methodName, "deleteAllBuffers"); + EXPECT_EQ(entry.usedSize, this->bufferHandler->getUsedSize()); + EXPECT_EQ(entry.freeSize, this->bufferHandler->getFreeSize()); + EXPECT_EQ(entry.memorySpace, TypeParam::name()); + EXPECT_EQ(entry.rank, this->rank); +} + +int main(int argc, char* argv[]) { + int success = 1; + ippl::initialize(argc, argv); + { + ::testing::InitGoogleTest(&argc, argv); + success = RUN_ALL_TESTS(); + } + ippl::finalize(); + return success; +}