diff --git a/CMakeLists.txt b/CMakeLists.txt index 8be551d1..a19be301 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,7 @@ ecbuild_add_option( FEATURE HEALPIX_EXAMPLES ### Definitions used by the output manager ecbuild_add_option( FEATURE OUTPUT_MANAGER - DEFAULT ON + DEFAULT OFF DESCRIPTION "Build the output manager" ) ecbuild_add_option( FEATURE OUTPUT_MANAGER_ENCODER_REPORT @@ -109,9 +109,13 @@ ecbuild_add_option( FEATURE OUTPUT_MANAGER_ENCODER_REPORT ecbuild_add_option( FEATURE OUTPUT_MANAGER_TOOL CONDITION HAVE_OUTPUT_MANAGER - DEFAULT ON + DEFAULT OFF DESCRIPTION "Enable generation of standalone tool" ) +ecbuild_add_option( FEATURE MULTIO_SERVER_MEMORY_PROFILE + DEFAULT OFF + DESCRIPTION "Enable multio server memory profiling" ) + ### export package info set( MULTIO_CONFIG_DIR share/multio/config ) diff --git a/src/multio/CMakeLists.txt b/src/multio/CMakeLists.txt index 7efc4a2e..cf0b69d2 100644 --- a/src/multio/CMakeLists.txt +++ b/src/multio/CMakeLists.txt @@ -35,11 +35,20 @@ list( APPEND multio_util_srcs util/Substitution.cc util/Substitution.h util/BinaryUtils.h - util/RingBuffer.h - util/Tracer.h - util/Tracer.cc ) +if ( ENABLE_MULTIO_SERVER_MEMORY_PROFILE ) + list( APPEND multio_util_srcs + util/RingBuffer.h + util/Tracer.h + util/Tracer.cc + util/TraceEventIds.h + util/MemoryInformation.h + util/MemoryInformation.cc + ) + set(multio_utils_definitions "MULTIO_SERVER_MEMORY_PROFILE_ENABLED") +endif() + list( APPEND multio_config_srcs config/ComponentConfiguration.cc config/ComponentConfiguration.h @@ -187,6 +196,8 @@ ecbuild_add_library( ${METKIT_INCLUDE_DIRS} ${ECKIT_INCLUDE_DIRS} + PRIVATE_DEFINITIONS ${multio_utils_definitions} + PUBLIC_LIBS metkit eckit eckit_mpi) diff --git a/src/multio/server/Listener.cc b/src/multio/server/Listener.cc index 02db7357..ac5f5d2e 100644 --- a/src/multio/server/Listener.cc +++ b/src/multio/server/Listener.cc @@ -28,6 +28,72 @@ #include "multio/transport/TransportRegistry.h" #include "multio/util/ScopedThread.h" +#ifdef MULTIO_SERVER_MEMORY_PROFILE_ENABLED + +#include "multio/util/MemoryInformation.h" +#include "multio/util/TraceEventIds.h" +#include "multio/util/Tracer.h" + +using namespace multio::util; + +namespace { +const auto tracerMemoryReportPeriod = std::chrono::seconds(10); +const auto tracerFlushPeriod = std::chrono::minutes(10); + +const auto tracerNumberOfChunks = 8; +const auto tracerEventsPerChunk = 32768; + +const auto tracerValueMask = 0xFFFFFFFFULL; +const auto unitShiftAmount = 32; + +const std::unordered_map infoTypeToTraceIdMapping = { + { multio::util::InformationTypes::PeakVirtualMemory, MULTIO_PEAK_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::VirtualMemory, MULTIO_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::LockedVirtualMemory, MULTIO_LOCKED_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::PinnedVirtualMemory, MULTIO_PINNED_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::MaximumResidentMemory, MULTIO_MAXIMUM_RESIDENT_MEMORY }, + { multio::util::InformationTypes::ResidentMemory, MULTIO_RESIDENT_MEMORY }, + { multio::util::InformationTypes::AnonimousResidentMemory, MULTIO_ANONIMOUS_RESIDENT_MEMORY }, + { multio::util::InformationTypes::FileMappingResidentMemory, MULTIO_FILE_MAPPING_RESIDENT_MEMORY }, + { multio::util::InformationTypes::SharedResidentMemory, MULTIO_SHARED_RESIDENT_MEMORY }, + { multio::util::InformationTypes::DataVirtualMemory, MULTIO_DATA_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::StackVirtualMemory, MULTIO_STACK_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::TextSegmentVirtualMemory, MULTIO_TEXT_SEGMENT_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::SharedLibraryTextVirtualMemory, MULTIO_SHARED_LIBRARY_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::PageTableEntryVirtualMemory, MULTIO_PAGE_TABLE_ENTRY_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::SecondLevelPageTableEntryVirtualMemory, MULTIO_SECOND_LEVEL_PAGE_TABLE_ENTRY_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::SwappedOutVirtualMemory, MULTIO_SWAPPED_OUT_VIRTUAL_MEMORY }, + { multio::util::InformationTypes::HugeTablesMemory, MULTIO_HUGE_TABLE_MEMORY }, +}; + +const std::unordered_map sizeUnitToTraceValueMapping = { + { multio::util::InformationSizeUnits::Bytes, 0}, + { multio::util::InformationSizeUnits::KiloBytes, 1}, + { multio::util::InformationSizeUnits::MegaBytes, 2}, + { multio::util::InformationSizeUnits::GigaBytes, 3}, +}; + +multio::util::Tracer tracer(tracerNumberOfChunks, tracerEventsPerChunk, "./multio_memory.bin"); + +void reportMemoryUsage() { + const multio::util::MemoryInformation usage; + const auto keys = usage.getAvailableKeys(); + + for (const auto key : keys) { + const auto item = usage.get(key); + + const auto id = infoTypeToTraceIdMapping.at(key); + const auto value = item.Value & tracerValueMask; + const uint64_t unit = sizeUnitToTraceValueMapping.at(item.Unit) << unitShiftAmount; + + tracer.recordEvent(id | unit | value); + } +} + +} + +#endif + namespace multio::server { using message::Message; @@ -109,8 +175,32 @@ void Listener::start() { void Listener::listen() { withFailureHandling([this]() { + +#ifdef MULTIO_SERVER_MEMORY_PROFILE_ENABLED + tracer.startWriterThread(); + + auto last_report_time = std::chrono::system_clock::now(); + auto last_flush_time = std::chrono::system_clock::now(); +#endif + do { transport_.listen(); + +#ifdef MULTIO_SERVER_MEMORY_PROFILE_ENABLED + const auto current_time = std::chrono::system_clock::now(); + const auto elapsed_from_report = current_time - last_report_time; + const auto elapsed_from_flush = current_time - last_flush_time; + + if (elapsed_from_report > tracerMemoryReportPeriod) { + reportMemoryUsage(); + last_report_time = current_time; + } + + if (elapsed_from_flush > tracerFlushPeriod) { + tracer.flushCurrentChunk(); + last_flush_time = current_time; + } +#endif } while (msgQueue_.checkInterrupt() && !msgQueue_.closed()); }); } diff --git a/src/multio/util/MemoryInformation.cc b/src/multio/util/MemoryInformation.cc new file mode 100644 index 00000000..a2eb3478 --- /dev/null +++ b/src/multio/util/MemoryInformation.cc @@ -0,0 +1,120 @@ +#include "MemoryInformation.h" + +#include "eckit/exception/Exceptions.h" + +#include +#include +#include + +#include +#include +#include + +namespace { + using namespace multio::util; + + const std::map extractors = { + { InformationTypes::PeakVirtualMemory, std::regex("VmPeak:\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::VirtualMemory, std::regex("VmSize:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::LockedVirtualMemory, std::regex("VmLck:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::PinnedVirtualMemory, std::regex("VmPin:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::MaximumResidentMemory, std::regex("VmHWM:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::ResidentMemory, std::regex("VmRSS:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::AnonimousResidentMemory, std::regex("RssAnon:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::FileMappingResidentMemory, std::regex("RssFile:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::SharedResidentMemory, std::regex("RssShmem:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::DataVirtualMemory, std::regex("VmData:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::StackVirtualMemory, std::regex("VmStk:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::TextSegmentVirtualMemory, std::regex("VmExe:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::SharedLibraryTextVirtualMemory, std::regex("VmLib:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::PageTableEntryVirtualMemory, std::regex("VmPTE:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::SecondLevelPageTableEntryVirtualMemory, std::regex("VmPMD:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::SwappedOutVirtualMemory, std::regex("VmSwap:..\\s*(\\d*)\\s(\\w*)") }, + { InformationTypes::HugeTablesMemory, std::regex("HugetlbPages:..\\s*(\\d*)\\s(\\w*)") }, + }; + + InformationSizeUnits toSizeUnit(const std::string& unit) { + switch (std::toupper(unit[0])) { + case 'K': + return InformationSizeUnits::KiloBytes; + case 'M': + return InformationSizeUnits::MegaBytes; + case 'G': + return InformationSizeUnits::GigaBytes; + default: + return InformationSizeUnits::Bytes; + } + } + + std::map createMemoryInformation() { + const auto maximumStatusStringLengthInChars = 16 * 1024; + + std::map information; + int fd = -1; + + try { + const auto path = "/proc/self/status"; + + fd = open(path, O_RDONLY); + if (fd < 0) { + throw eckit::FailedSystemCall("open", Here()); + } + + auto mem = reinterpret_cast(alloca(maximumStatusStringLengthInChars)); + + ssize_t read_bytes = read(fd, mem, maximumStatusStringLengthInChars); + ssize_t already_read = read_bytes; + while ((read_bytes > 0) && (already_read < maximumStatusStringLengthInChars)) { + read_bytes = read(fd, mem + already_read, maximumStatusStringLengthInChars - already_read); + if ((read_bytes < 0) && (errno != EAGAIN)) { + throw eckit::FailedSystemCall("read", Here()); + } + + already_read += read_bytes; + } + + close(fd); + fd = -1; + + auto statusContents = std::istringstream(mem); + + auto line = reinterpret_cast(alloca(maximumStatusStringLengthInChars)); + while (statusContents.getline(line, maximumStatusStringLengthInChars)) { + const auto lineText = std::string(line); + for (auto const& extractor : extractors) { + std::smatch match; + if (std::regex_search(lineText, match, extractor.second)) { + information.emplace(extractor.first, + InformationItem { std::atoi(match[1].str().c_str()), toSizeUnit(match[2].str()) }); + } + } + } + } catch (...) { + if (fd >= 0) { + close(fd); + fd = -1; + } + + throw; + } + + return information; + } +} + +namespace multio::util { + +MemoryInformation::MemoryInformation() : memInfo_(createMemoryInformation()) {} + +std::vector MemoryInformation::getAvailableKeys() const { + std::vector keys; + keys.reserve(memInfo_.size()); + + for (const auto& item : memInfo_) { + keys.emplace_back(item.first); + } + + return keys; +} + +} diff --git a/src/multio/util/MemoryInformation.h b/src/multio/util/MemoryInformation.h new file mode 100644 index 00000000..6897d5bc --- /dev/null +++ b/src/multio/util/MemoryInformation.h @@ -0,0 +1,74 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +/// @author Razvan Aguridan + +/// @date Jun 2024 + +#pragma once + +#include +#include +#include + +namespace multio::util { + +enum class InformationTypes { + PeakVirtualMemory, + VirtualMemory, + LockedVirtualMemory, + PinnedVirtualMemory, + MaximumResidentMemory, + ResidentMemory, + AnonimousResidentMemory, + FileMappingResidentMemory, + SharedResidentMemory, + DataVirtualMemory, + StackVirtualMemory, + TextSegmentVirtualMemory, + SharedLibraryTextVirtualMemory, + PageTableEntryVirtualMemory, + SecondLevelPageTableEntryVirtualMemory, + SwappedOutVirtualMemory, + HugeTablesMemory +}; + +enum class InformationSizeUnits { + Bytes, + KiloBytes, + MegaBytes, + GigaBytes +}; + +struct InformationItem { + uint64_t Value; + InformationSizeUnits Unit; +}; + +class MemoryInformation { +public: + MemoryInformation(); + ~MemoryInformation() = default; + + std::vector getAvailableKeys() const; + + const InformationItem& get(InformationTypes info) const { return memInfo_.at(info); } + +private: + MemoryInformation(MemoryInformation const&) = delete; + MemoryInformation& operator=(MemoryInformation const&) = delete; + + MemoryInformation(MemoryInformation const&&) = delete; + MemoryInformation& operator=(MemoryInformation const&&) = delete; + + const std::map memInfo_; +}; + +} // namespace multio::util \ No newline at end of file diff --git a/src/multio/util/TraceEventIds.h b/src/multio/util/TraceEventIds.h new file mode 100644 index 00000000..0bb7b1db --- /dev/null +++ b/src/multio/util/TraceEventIds.h @@ -0,0 +1,42 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +/// @author Razvan Aguridan + +/// @date May 2024 + +#pragma once + +#include + +namespace multio::util { + +enum TraceEventIds : uint64_t +{ + MULTIO_PEAK_VIRTUAL_MEMORY = 0x0100000000000000ULL, + MULTIO_VIRTUAL_MEMORY = 0x0200000000000000ULL, + MULTIO_LOCKED_VIRTUAL_MEMORY = 0x0300000000000000ULL, + MULTIO_PINNED_VIRTUAL_MEMORY = 0x0400000000000000ULL, + MULTIO_MAXIMUM_RESIDENT_MEMORY = 0x0500000000000000ULL, + MULTIO_RESIDENT_MEMORY = 0x0600000000000000ULL, + MULTIO_ANONIMOUS_RESIDENT_MEMORY = 0x0700000000000000ULL, + MULTIO_FILE_MAPPING_RESIDENT_MEMORY = 0x0800000000000000ULL, + MULTIO_SHARED_RESIDENT_MEMORY = 0x0900000000000000ULL, + MULTIO_DATA_VIRTUAL_MEMORY = 0x0A00000000000000ULL, + MULTIO_STACK_VIRTUAL_MEMORY = 0x0B00000000000000ULL, + MULTIO_TEXT_SEGMENT_VIRTUAL_MEMORY = 0x0C00000000000000ULL, + MULTIO_SHARED_LIBRARY_VIRTUAL_MEMORY = 0x0D00000000000000ULL, + MULTIO_PAGE_TABLE_ENTRY_VIRTUAL_MEMORY = 0x0E00000000000000ULL, + MULTIO_SECOND_LEVEL_PAGE_TABLE_ENTRY_VIRTUAL_MEMORY = 0x0F00000000000000ULL, + MULTIO_SWAPPED_OUT_VIRTUAL_MEMORY = 0x1000000000000000ULL, + MULTIO_HUGE_TABLE_MEMORY = 0x1100000000000000ULL, +}; + +} \ No newline at end of file diff --git a/src/multio/util/Tracer.cc b/src/multio/util/Tracer.cc index 5f1ccd13..110d76c6 100644 --- a/src/multio/util/Tracer.cc +++ b/src/multio/util/Tracer.cc @@ -107,6 +107,37 @@ void Tracer::recordEvent(uint64_t event) { traceChunks_[chunk][index + 1] = timestamp; } +void Tracer::flushCurrentChunk() { + bool updated = false; + uint32_t chunk = 0; + uint32_t chunkAndIndex = currentChunkAndIndex_.load(std::memory_order::memory_order_acquire); + + do { + chunk = (chunkAndIndex & CHUNK_MASK) >> CHUNK_SHIFT; + + // get the next available chunk from the available queue + auto availableChunkId = availableQueue_.pop(); + while (!availableChunkId) { + availableChunkId = availableQueue_.pop(); + } + + auto chunkNext = (availableChunkId.value() << CHUNK_SHIFT) & CHUNK_MASK; + updated = currentChunkAndIndex_.compare_exchange_strong( + chunkAndIndex, chunkNext, std::memory_order::memory_order_acq_rel, std::memory_order_acquire); + + // we wanted to change the chunk + if (updated) { + // we changed the chunk, push the completed chunk to the write queue + writeQueue_.push(chunk); + } + else { + // some other thread updated the chunk counters, most likely also changing the chunk, + // so we put the available chunk we retrieved back in the available queue + availableQueue_.push(chunkNext); + } + } while (!updated); +} + void Tracer::writerThread_() { const auto myRank = eckit::mpi::comm().rank(); diff --git a/src/multio/util/Tracer.h b/src/multio/util/Tracer.h index 340d1f3b..9e427794 100644 --- a/src/multio/util/Tracer.h +++ b/src/multio/util/Tracer.h @@ -32,6 +32,8 @@ class Tracer { void recordEvent(uint64_t event); + void flushCurrentChunk(); + private: Tracer(Tracer const&) = delete; Tracer& operator=(Tracer const&) = delete;