diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 658d106095c..16526dd79b8 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -150,47 +150,47 @@ jobs: fail-fast : false matrix: include: - - name: Ubuntu (20.04, gcc 9.4.0, open-mpi 4.0.3) - CMAKE_BUILD_TYPE: Release - DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 - BUILD_SHARED_LIBS: ON - ENABLE_HYPRE: OFF - ENABLE_TRILINOS: ON - GEOS_ENABLE_BOUNDS_CHECK: ON - HOST_CONFIG: /spack-generated.cmake - - - name: Ubuntu debug (20.04, gcc 10.5.0, open-mpi 4.0.3) - github codespaces - BUILD_AND_TEST_CLI_ARGS: "--no-run-unit-tests" - CMAKE_BUILD_TYPE: Debug - DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 - BUILD_SHARED_LIBS: ON - ENABLE_HYPRE: OFF - ENABLE_TRILINOS: ON - GEOS_ENABLE_BOUNDS_CHECK: ON - RUNS_ON: streak2 - NPROC: 8 - DOCKER_RUN_ARGS: "--cpus=8 --memory=256g --runtime=nvidia -v /etc/pki/tls/certs/ca-bundle.crt:/etc/pki/tls/certs/ca-bundle.crt:ro -v /etc/pki/tls/certs/ca-bundle.crt:/certs/ca-bundle.crt:ro" - - HOST_CONFIG: /spack-generated.cmake - - - name: Ubuntu (20.04, gcc 10.5.0, open-mpi 4.0.3) - github codespaces - CMAKE_BUILD_TYPE: Release - DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 - BUILD_SHARED_LIBS: ON - ENABLE_HYPRE: OFF - ENABLE_TRILINOS: ON - GEOS_ENABLE_BOUNDS_CHECK: ON - HOST_CONFIG: /spack-generated.cmake - - - name: Ubuntu (22.04, gcc 11.4.0, open-mpi 4.1.2) - CMAKE_BUILD_TYPE: Release - DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 - ENABLE_HYPRE: ON - ENABLE_TRILINOS: OFF - BUILD_SHARED_LIBS: ON - GEOS_ENABLE_BOUNDS_CHECK: OFF - GCP_BUCKET: geosx/ubuntu22.04-gcc11 - HOST_CONFIG: /spack-generated.cmake + # - name: Ubuntu (20.04, gcc 9.4.0, open-mpi 4.0.3) + # CMAKE_BUILD_TYPE: Release + # DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 + # BUILD_SHARED_LIBS: ON + # ENABLE_HYPRE: OFF + # ENABLE_TRILINOS: ON + # GEOS_ENABLE_BOUNDS_CHECK: ON + # HOST_CONFIG: /spack-generated.cmake + + # - name: Ubuntu debug (20.04, gcc 10.5.0, open-mpi 4.0.3) - github codespaces + # BUILD_AND_TEST_CLI_ARGS: "--no-run-unit-tests" + # CMAKE_BUILD_TYPE: Debug + # DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + # BUILD_SHARED_LIBS: ON + # ENABLE_HYPRE: OFF + # ENABLE_TRILINOS: ON + # GEOS_ENABLE_BOUNDS_CHECK: ON + # RUNS_ON: streak2 + # NPROC: 8 + # DOCKER_RUN_ARGS: "--cpus=8 --memory=256g --runtime=nvidia -v /etc/pki/tls/certs/ca-bundle.crt:/etc/pki/tls/certs/ca-bundle.crt:ro -v /etc/pki/tls/certs/ca-bundle.crt:/certs/ca-bundle.crt:ro" + + # HOST_CONFIG: /spack-generated.cmake + + # - name: Ubuntu (20.04, gcc 10.5.0, open-mpi 4.0.3) - github codespaces + # CMAKE_BUILD_TYPE: Release + # DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + # BUILD_SHARED_LIBS: ON + # ENABLE_HYPRE: OFF + # ENABLE_TRILINOS: ON + # GEOS_ENABLE_BOUNDS_CHECK: OFF + # HOST_CONFIG: /spack-generated.cmake + + # - name: Ubuntu (22.04, gcc 11.4.0, open-mpi 4.1.2) + # CMAKE_BUILD_TYPE: Release + # DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 + # ENABLE_HYPRE: ON + # ENABLE_TRILINOS: OFF + # BUILD_SHARED_LIBS: ON + # GEOS_ENABLE_BOUNDS_CHECK: OFF + # GCP_BUCKET: geosx/ubuntu22.04-gcc11 + # HOST_CONFIG: /spack-generated.cmake - name: Ubuntu (22.04, gcc 12.3.0, open-mpi 4.1.2) CMAKE_BUILD_TYPE: Release diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index d0143b9564b..083085113a8 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -39,10 +39,13 @@ set( common_headers GEOS_RAJA_Interface.hpp GeosxMacros.hpp MemoryInfos.hpp + logger/DiagnosticMessage.hpp + logger/GeosExceptions.hpp logger/GeosExceptions.hpp logger/Logger.hpp logger/ErrorHandling.hpp logger/ExternalErrorHandler.hpp + logger/LogHistory.hpp MpiWrapper.hpp Path.hpp Span.hpp @@ -72,6 +75,7 @@ endif( ) # Specify all sources # set( common_sources + format/EnumStrings.cpp format/table/TableData.cpp format/table/TableFormatter.cpp format/table/TableLayout.cpp @@ -82,6 +86,7 @@ set( common_sources logger/Logger.cpp logger/ErrorHandling.cpp logger/ExternalErrorHandler.cpp + logger/LogHistory.cpp BufferAllocator.cpp MemoryInfos.cpp MpiWrapper.cpp diff --git a/src/coreComponents/common/MpiWrapper.cpp b/src/coreComponents/common/MpiWrapper.cpp index b80d0aa802a..5dd157a3d53 100644 --- a/src/coreComponents/common/MpiWrapper.cpp +++ b/src/coreComponents/common/MpiWrapper.cpp @@ -462,6 +462,26 @@ int MpiWrapper::nodeCommSize() return nodeCommSize; } +void MpiWrapper::gatherStringOnRank0( string_view rankStr, std::function< void(string_view) > && func ) +{ + std::vector< buffer_unit_type > localbuffer; + localbuffer.reserve( rankStr.size()); + localbuffer.insert( localbuffer.end(), rankStr.begin(), rankStr.end()); + auto [globalLogRecords, counts, offsets] = + MpiWrapper::gatherBufferRank0< std::vector< buffer_unit_type > >( localbuffer ); + if( MpiWrapper::commRank() == 0 ) + { + for( integer rankId = 0; rankId < MpiWrapper::commSize(); ++rankId ) + { + if( counts[rankId] > 0 ) + { + func( string( globalLogRecords.begin() + offsets[rankId], + globalLogRecords.begin() + offsets[rankId]+ counts[rankId] ) ); + } + } + } +} + namespace internal { diff --git a/src/coreComponents/common/MpiWrapper.hpp b/src/coreComponents/common/MpiWrapper.hpp index fd3264a5317..35c309258e1 100644 --- a/src/coreComponents/common/MpiWrapper.hpp +++ b/src/coreComponents/common/MpiWrapper.hpp @@ -331,6 +331,88 @@ struct MpiWrapper */ static int nodeCommSize(); + /** + * @brief Structure holding the result from all the gather operation + * @tparam CONTAINER The container type holding the data. + * The underlying storage in CONTAINER must be contiguous + */ + template< typename CONTAINER > + struct GatherResult + { + // Collected data who must be trivially copyable + CONTAINER data; + // Number of elements per row + stdVector< integer > counts; + // Starting index for each row in 'data' + stdVector< integer > offsets; + }; + +/** + * @brief Gather buffers of varying sizes from all ranks to rank 0. + * @tparam CONTAINER The container type holding the data. + * @tparam VALUE_T The trivially copyable underlying data type (deduced automatically). + * @param localBuffer The local buffer to be gathered on rank 0. + * @return A struct containing: + * - 'data': all the gathered data on rank 0 + * - 'counts': number of elements for each rank + * - 'offsets': starting index for each rank in 'data' + */ + template< + typename CONTAINER, + typename VALUE_T = typename CONTAINER::value_type, + typename = std::enable_if_t< + std::is_trivially_copyable_v< VALUE_T > && + std::is_same_v< decltype(std::declval< CONTAINER >().data()), VALUE_T * > && + std::is_same_v< decltype(std::declval< CONTAINER >().size()), std::size_t > + > + > + static GatherResult< CONTAINER > + gatherBufferRank0( CONTAINER const & localBuffer ) + { + integer const numRanks = MpiWrapper::commSize(); + integer const numLocalValues = static_cast< integer >(localBuffer.size()); + + GatherResult< CONTAINER > gatherResult; + + if( MpiWrapper::commRank() == 0 ) + { + gatherResult.counts.resize( numRanks ); + gatherResult.offsets.resize( numRanks ); + } + + + MpiWrapper::gather( &numLocalValues, 1, gatherResult.counts.data(), 1, 0 ); + + if( MpiWrapper::commRank() == 0 ) + { + integer totalSize = 0; + for( integer i = 0; i < numRanks; ++i ) + { + gatherResult.offsets[i] = totalSize; + totalSize += gatherResult.counts[i]; + } + gatherResult.data.resize( totalSize ); + } + + MpiWrapper::gatherv( localBuffer.data(), + numLocalValues, + gatherResult.data.data(), + gatherResult.counts.data(), + gatherResult.offsets.data(), + 0 ); + + return gatherResult; + } + + /** + * @brief Gather srting from all ranks to rank 0 + * @tparam FUNC Callable type invoked as void(string_view) for each non-empty rank string. + * @param str The local string to send from the calling rank. + * @param func Callback invoked on rank 0 for each non-empty received string. + */ + static void gatherStringOnRank0( string_view str, + std::function< void(string_view) > && func ); + /** * @brief Strongly typed wrapper around MPI_Allgather. * @tparam T_SEND The pointer type for \p sendbuf diff --git a/src/coreComponents/common/format/EnumStrings.cpp b/src/coreComponents/common/format/EnumStrings.cpp new file mode 100644 index 00000000000..97d8614eef0 --- /dev/null +++ b/src/coreComponents/common/format/EnumStrings.cpp @@ -0,0 +1,42 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file EnumStrings.cpp + */ + + #include "common/format/EnumStrings.hpp" + #include "common/logger/Logger.hpp" + +namespace geos +{ + +void internal::EnumErrorMessageToString( size_t index, + string_view typeName, + std::size_t size ) +{ + GEOS_THROW( "Invalid value " << index << " of type " << typeName<< ". Valid range is 0.." << size - 1, + InputError ); +} + +void internal::EnumErrorMessageFromString( string_view s, + string_view typeName, + string_view concat ) +{ + GEOS_THROW( "Invalid value '" << s << "' of type " << typeName << ". Valid options are: " << concat, + InputError ); +} + +} diff --git a/src/coreComponents/common/format/EnumStrings.hpp b/src/coreComponents/common/format/EnumStrings.hpp index 0bedb0f0081..7984156ac56 100644 --- a/src/coreComponents/common/format/EnumStrings.hpp +++ b/src/coreComponents/common/format/EnumStrings.hpp @@ -28,7 +28,6 @@ #include "common/format/StringUtilities.hpp" // #include "codingUtilities/RTTypes.hpp" #include "common/DataTypes.hpp" -#include "common/logger/Logger.hpp" #include "common/format/Format.hpp" #include @@ -40,6 +39,25 @@ namespace geos namespace internal { + +/** + * @brief Raise a throw error in case of bad use of enum::toString + * @param index The index of the enum causing the error + * @param typeName The enum name + * @param size The size of the enum + */ +void EnumErrorMessageToString( size_t index, string_view typeName, std::size_t size ); + +/** + * @brief Raise a throw error in case of bad use of enum::FromString + * @param s The string that not belonging to the enum + * @param typeName The enum name + * @param size The string concatenation of the enum + */ +void EnumErrorMessageFromString( string_view s, + string_view typeName, + string_view concat ); + /** * @brief Simple compile-time variadic function that counts its arguments. * @tparam ARGS variadic pack of argument types @@ -152,12 +170,10 @@ struct EnumStrings auto const & strings = get(); std::size_t size = std::distance( std::begin( strings ), std::end( strings ) ); base_type const index = static_cast< base_type >( e ); - GEOS_THROW_IF( index >= LvArray::integerConversion< base_type >( size ), - GEOS_FMT( "Invalid value {} of type {}. Valid range is 0..{}", - index, - getEnumTypeNameString( enum_type{} ), - size - 1 ), - InputError ); + if( index >= LvArray::integerConversion< base_type >( size )) + { + internal::EnumErrorMessageToString( index, getEnumTypeNameString( enum_type{} ), size - 1 ); + } return strings[ index ]; } @@ -170,12 +186,10 @@ struct EnumStrings { auto const & strings = get(); auto const it = std::find( std::begin( strings ), std::end( strings ), s ); - GEOS_THROW_IF( it == std::end( strings ), - GEOS_FMT( "Invalid value '{}' of type {}. Valid options are: {}", - s, - getEnumTypeNameString( enum_type{} ), - concat( ", " ) ), - InputError ); + if( it == std::end( strings )) + { + internal::EnumErrorMessageFromString( s, getEnumTypeNameString( enum_type{} ), EnumStrings::concat( ", " )); + } enum_type const e = static_cast< enum_type >( LvArray::integerConversion< base_type >( std::distance( std::begin( strings ), it ) ) ); return e; } diff --git a/src/coreComponents/common/format/LogPart.cpp b/src/coreComponents/common/format/LogPart.cpp index a9ce3c69da6..f229ffc8012 100644 --- a/src/coreComponents/common/format/LogPart.cpp +++ b/src/coreComponents/common/format/LogPart.cpp @@ -18,18 +18,22 @@ #include "LogPart.hpp" #include "common/format/StringUtilities.hpp" +#include "common/logger/ErrorHandling.hpp" #include using namespace geos::stringutilities; namespace geos { -LogPart::LogPart( string_view logPartTitle, bool enableOutput ) +LogPart::LogPart( string_view logpartName, bool enableOutput ) { - m_formattedStartDescription.m_title = logPartTitle; - m_formattedEndDescription.m_title = GEOS_FMT( "{}{}", m_prefixEndTitle, logPartTitle ); + m_formattedStartDescription.m_title = logpartName; + m_formattedEndDescription.m_title = GEOS_FMT( "{}{}", m_prefixEndTitle, logpartName ); m_enableOutput = enableOutput; + + ErrorLogger::global().setCurrentLogPart( std::string( logpartName ) ); + } void LogPart::addDescription( string_view description ) diff --git a/src/coreComponents/common/format/LogPart.hpp b/src/coreComponents/common/format/LogPart.hpp index d4223c008b0..d996c03b40b 100644 --- a/src/coreComponents/common/format/LogPart.hpp +++ b/src/coreComponents/common/format/LogPart.hpp @@ -19,9 +19,8 @@ #ifndef GEOS_COMMON_FORMAT_LOGPART_HPP #define GEOS_COMMON_FORMAT_LOGPART_HPP -#include "common/DataTypes.hpp" -#include "common/format/Format.hpp" #include "common/format/StringUtilities.hpp" +#include "common/format/Format.hpp" namespace geos { diff --git a/src/coreComponents/common/format/table/TableData.cpp b/src/coreComponents/common/format/table/TableData.cpp index b9bfc885584..d3dd33ddcf1 100644 --- a/src/coreComponents/common/format/table/TableData.cpp +++ b/src/coreComponents/common/format/table/TableData.cpp @@ -59,14 +59,81 @@ TableData & TableData::operator=( TableData const & other ) bool TableData::operator<( TableData const & other ) const { - return m_rows < other.m_rows; + if( other.getCellsData().size()!= getCellsData().size()) + return false; + + for( size_t i = 0; i < getCellsData().size(); i++ ) + { + if( getCellsData()[i].data()->value > other.getCellsData()[i].data()->value ) + return false; + } + return true; } +bool TableData::operator==( TableData const & comparingTable ) const +{ + if( comparingTable.getCellsData().size()!= getCellsData().size()) + return false; + for( size_t i = 0; i < getCellsData().size(); i++ ) + { + if( getCellsData()[i].data()->value != comparingTable.getCellsData()[i].data()->value ) + return false; + } + return true; +} + +void TableData::CellData::serialize( stdVector< buffer_unit_type > & out ) const +{ + serialBuffer::serializePrimitive( type, out ); + serialBuffer::serializeString( value, out ); +} -void TableData::addRow( stdVector< TableData::CellData > const & row ) +size_t TableData::CellData::getSerializedSize() const { - m_rows.push_back( row ); + return serialBuffer::sizeOfPrimitive(type) + serialBuffer::sizeOfString( value ); +} + +size_t TableData::getSerializedSize() const +{ + size_t totalSize =0; + + if( m_rows.empty()) + return totalSize; + + for( auto & row : m_rows ) + { + size_t rowSize = 0; + for( auto & cell : row ) + { + rowSize += cell.getSerializedSize(); + } + totalSize += sizeof(size_t) + rowSize; + } + return totalSize; +} + +void TableData::serialize( stdVector< buffer_unit_type > & serializedTableData ) const +{ + if( m_rows.empty()) + return; + + for( auto & row : m_rows ) + { + { // pack row size; + size_t rowSize = 0; + for( auto const & cell : row ) + rowSize += cell.getSerializedSize(); + serialBuffer::serializePrimitive( rowSize, serializedTableData ); + } + + { // pack cells + for( auto const & cell : row ) + { + cell.serialize( serializedTableData ); + } + } + } } void TableData::addSeparator() @@ -180,4 +247,63 @@ TableData2D::TableDataHolder TableData2D::buildTableData( string_view targetUnit return tableData1D; } + +void serialBuffer::serializeString ( string const & data, stdVector< buffer_unit_type > & out ) +{ + serialBuffer::serializePrimitive( data.size(), out ); + auto * begin = data.data(); + auto * end = begin + data.size(); + out.insert( out.end(), begin, end ); +} + +void serialBuffer::deserializeString( string & str, buffer_unit_type const * & ptr, buffer_unit_type const * end ) +{ + string::size_type strSize = 0; + serialBuffer::deserializePrimitive( strSize, ptr, end ); + if( std::distance( ptr, end ) < (long) strSize ) + { + throw std::runtime_error( "Buffer overflow reading string" ); + } + str.assign( ptr, ptr + strSize ); + ptr += str.size(); +} + +bool tableDataSorting::positiveNumberStringComp( string_view s1, string_view s2 ) +{ + auto split = []( string_view s, string & intPart, string & decPart ) + { + size_t dotPos = s.find( '.' ); + if( dotPos == string::npos ) + { + intPart = s; + decPart = ""; + } + else + { + intPart = s.substr( 0, dotPos ); + decPart = s.substr( dotPos + 1 ); + } + }; + + string s1Int, s1Dec, s2Int, s2Dec; + split( s1, s1Int, s1Dec ); + split( s2, s2Int, s2Dec ); + + if( s1Int.length() != s2Int.length()) + return s1Int.length() < s2Int.length(); + + if( s1Int != s2Int ) + return s1Int < s2Int; + + size_t minLen = std::min( s1Dec.length(), s2Dec.length()); + for( size_t i = 0; i < minLen; ++i ) + { + if( s1Dec[i] != s2Dec[i] ) + return s1Dec[i] < s2Dec[i]; + } + + + return false; +} + } diff --git a/src/coreComponents/common/format/table/TableData.hpp b/src/coreComponents/common/format/table/TableData.hpp index e36afb037c5..2e1715b031c 100644 --- a/src/coreComponents/common/format/table/TableData.hpp +++ b/src/coreComponents/common/format/table/TableData.hpp @@ -55,7 +55,14 @@ class TableData bool operator<( TableData const & other ) const; /** - * @brief Representing a data in TableData + * @brief Comparison operator for data rows + * @param comparingTable The tableData values to compare + * @return The comparison result + */ + bool operator==( TableData const & comparingTable ) const; + + /** + * @brief Representing a single cell's data within a TableData row. */ struct CellData { @@ -64,22 +71,33 @@ class TableData /// The cell value string value; - /// @cond DO_NOT_DOCUMENT - bool operator==( CellData const & other ) const - { - return value == other.value; - } + /** + * @return Total size in bytes required to serialize this cell. + */ + size_t getSerializedSize() const; - bool operator<( CellData const & other ) const - { - return value < other.value; - } - ///@endcond + /** + * @brief Serializes the cell type and value into the output buffer. + * @param out Buffer to append the serialized cell data to. + */ + void serialize( stdVector< buffer_unit_type > & out ) const; }; + /** + * @brief Returns the total serialized byte size of all rows in the table. + * @return Total size in bytes required to serialize the entire TableData. + */ + size_t getSerializedSize() const; + /// Alias for table data rows with cells values using DataRows = stdVector< stdVector< CellData > >; + /** + * @brief Serializes the tableData into the output buffer. + * @param serializedTableData Buffer to append the serialized tableData. + */ + void serialize( stdVector< buffer_unit_type > & serializedTableData ) const; + /** * @brief Add a row to the table. * The values passed to addRow (can be any type). @@ -92,7 +110,8 @@ class TableData * @brief Add a row to the table * @param row A vector of string representing a row */ - void addRow( stdVector< CellData > const & row ); + void addRow( stdVector< CellData > const & row ) + { m_rows.push_back( row ); } /** * @brief Add a line separator to the table @@ -129,14 +148,6 @@ class TableData DataRows & getCellsData() { return m_rows; } - /** - * @brief Comparison operator for data rows - * @param comparingTable The tableData values to compare - * @return The comparison result - */ - inline bool operator==( TableData const & comparingTable ) const - { return getCellsData() == comparingTable.getCellsData(); } - /** * @brief Get all error messages * @return The list of error messages @@ -151,6 +162,17 @@ class TableData TableErrorListing & getErrorsList() { return *m_errors; } + /** + * @brief Sorts the rows using a custom comparator functor. + * @tparam SortingFunc Type of the sorting comparator functor. + * @param sortingFunctor Comparator functor used to sort the rows. + */ + template< typename SortingFunc > + void sort( SortingFunc sortingFunctor ) + { + std::sort( m_rows.begin(), m_rows.end(), sortingFunctor ); + } + private: /// @brief vector containing all rows with cell values DataRows m_rows; @@ -262,6 +284,7 @@ class TableData2D template< typename T > constexpr bool isCellType = std::is_same_v< T, CellType >; + template< typename ... Args > void TableData::addRow( Args const &... args ) { @@ -302,5 +325,91 @@ void TableData2D::addCell( real64 const rowValue, real64 const columnValue, T co m_data.get_inserted( rowValue ).get_inserted( columnValue ) = GEOS_FMT( "{}", value ); } +// Serialisation/ Deserialisation utils for common +namespace serialBuffer +{ + +/** + * @tparam T The trivial type + * @return Returns the size occupied by a trivial type in memory. + */ +template< typename T > +inline unsigned long sizeOfPrimitive( T ) +{ return sizeof(T); } + +/** + * @brief Returns the size of a string (header size + content). + * @param str The target string + * @return Size in bytes. + */ +inline unsigned long sizeOfString( string const & str ) +{ return sizeof(string::size_type) + str.size(); } + +/** + * @brief Write the data to the buffer. + * @tparam T The type of the data who must be trivially copiable + * @param data Destination variable. + * @param out The buffer to write in. + */ +template< typename T > +void serializePrimitive ( T const data, stdVector< buffer_unit_type > & out ); + +/** + * @brief Write a string value to the buffer. + * @param data String variable. + * @param out The buffer to write in. + */ +void serializeString ( string const & data, stdVector< buffer_unit_type > & out ); + +/** + * @brief Reads the data from the buffer and advances the pointer. + * @tparam T The type of the data who must be trivially copiable + * @param data Destination variable. + * @param ptr Current read pointer (advanced by sizeof(string)). + * @param end Safety: maximum buffer limit. + */ +template< typename T > +void deserializePrimitive( T & data, buffer_unit_type const * & ptr, buffer_unit_type const * end ); + +/** + * @brief Reads a string value from the buffer and advances the pointer. + * @param data Destination variable. + * @param ptr Current read pointer (advanced by sizeof(string)). + * @param end Safety: maximum buffer limit. + */ +void deserializeString( string & str, buffer_unit_type const * & ptr, buffer_unit_type const * end ); +} + +// Custom Comp function; +namespace tableDataSorting +{ +/** + * @brief Compare two string number string by in ascending numerical order. + * @param a The string to compare + * @param b The string to compare + * @return True if a is greater than b + */ +bool positiveNumberStringComp( string_view a, string_view b ); +} + +template< typename T > +void serialBuffer::serializePrimitive ( T const data, stdVector< buffer_unit_type > & out ) +{ + static_assert( std::is_trivially_copyable_v< T > ); + buffer_unit_type const * begin = reinterpret_cast< buffer_unit_type const * >( &data ); + buffer_unit_type const * end = begin + sizeof(data); + out.insert( out.end(), begin, end ); +} + +template< typename T > +void serialBuffer::deserializePrimitive( T & data, buffer_unit_type const * & ptr, buffer_unit_type const * end ) +{ + static_assert( std::is_trivially_copyable_v< T > ); + if( ptr + sizeof(T)> end ) + throw std::runtime_error( "Buffer overflow" ); + data = *reinterpret_cast< T const * >(ptr); + ptr += sizeof(T); +} + } #endif /* GEOS_COMMON_FORMAT_TABLE_TABLEDATA_HPP */ diff --git a/src/coreComponents/common/format/table/TableFormatter.cpp b/src/coreComponents/common/format/table/TableFormatter.cpp index 3d454887da7..7b198202fd9 100644 --- a/src/coreComponents/common/format/table/TableFormatter.cpp +++ b/src/coreComponents/common/format/table/TableFormatter.cpp @@ -227,28 +227,28 @@ template<> string TableTextFormatter::toString< TableData >( TableData const & tableData ) const { std::ostringstream tableOutput; - CellLayoutRows headerCellsLayout; - CellLayoutRows dataCellsLayout; - CellLayoutRows errorCellsLayout; + CellLayoutRows headerRows; + CellLayoutRows dataRows; + CellLayoutRows errorRows; size_t tableTotalWidth = 0; initalizeTableGrids( m_tableLayout, tableData, - headerCellsLayout, dataCellsLayout, errorCellsLayout, + headerRows, dataRows, errorRows, tableTotalWidth, nullptr ); string const sepLine = string( tableTotalWidth, m_horizontalLine ); - outputTableHeader( tableOutput, m_tableLayout, headerCellsLayout, sepLine ); - outputTableData( tableOutput, m_tableLayout, dataCellsLayout ); - outputTableFooter( tableOutput, m_tableLayout, errorCellsLayout, sepLine, !dataCellsLayout.empty() ); + outputTableHeader( tableOutput, m_tableLayout, headerRows, sepLine ); + outputTableData( tableOutput, m_tableLayout, dataRows ); + outputTableFooter( tableOutput, m_tableLayout, errorRows, sepLine, !dataRows.empty() ); return tableOutput.str(); } void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableLayout, TableData const & tableInputData, - CellLayoutRows & headerCellsLayout, - CellLayoutRows & dataCellsLayout, - CellLayoutRows & errorCellsLayout, + CellLayoutRows & headerRows, + CellLayoutRows & dataRows, + CellLayoutRows & errorRows, size_t & tableTotalWidth, ColumnWidthModifier columnWidthModifier ) const { @@ -261,30 +261,30 @@ void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableL // this array will store the displayed width of all columns (it will be scaled by data & headers width) stdVector< size_t > columnsWidth = stdVector< size_t >( nbVisibleColumns, 0 ); - populateTitleCellsLayout( tableLayout, headerCellsLayout, nbVisibleColumns ); + populateTitleCellsLayout( tableLayout, headerRows, nbVisibleColumns ); if( hasColumnLayout ) { - populateHeaderCellsLayout( tableLayout, headerCellsLayout, nbVisibleColumns ); - populateDataCellsLayout( tableLayout, dataCellsLayout, inputDataValues, nbVisibleColumns ); + populateHeaderCellsLayout( tableLayout, headerRows, nbVisibleColumns ); + populateDataCellsLayout( tableLayout, dataRows, inputDataValues, nbVisibleColumns ); } else { - populateDataCellsLayout( tableLayout, dataCellsLayout, inputDataValues ); + populateDataCellsLayout( tableLayout, dataRows, inputDataValues ); } if( getErrorsList().hasErrors() || tableInputData.getErrorsList().hasErrors()) { - populateErrorCellsLayout( tableLayout, errorCellsLayout, tableInputData.getErrorsList() ); + populateErrorCellsLayout( tableLayout, errorRows, tableInputData.getErrorsList() ); } - stretchColumnsByCellsWidth( columnsWidth, headerCellsLayout ); - stretchColumnsByCellsWidth( columnsWidth, dataCellsLayout ); - stretchColumnsByCellsWidth( columnsWidth, errorCellsLayout ); + stretchColumnsByCellsWidth( columnsWidth, headerRows ); + stretchColumnsByCellsWidth( columnsWidth, dataRows ); + stretchColumnsByCellsWidth( columnsWidth, errorRows ); // only after all cells that are not merge, we can process the merged cells. - stretchColumnsByMergedCellsWidth( columnsWidth, headerCellsLayout, tableLayout, false ); - stretchColumnsByMergedCellsWidth( columnsWidth, dataCellsLayout, tableLayout, true ); - stretchColumnsByMergedCellsWidth( columnsWidth, errorCellsLayout, tableLayout, true ); + stretchColumnsByMergedCellsWidth( columnsWidth, headerRows, tableLayout, false ); + stretchColumnsByMergedCellsWidth( columnsWidth, dataRows, tableLayout, true ); + stretchColumnsByMergedCellsWidth( columnsWidth, errorRows, tableLayout, true ); if( columnWidthModifier ) columnWidthModifier( columnsWidth ); @@ -298,9 +298,9 @@ void TableTextFormatter::initalizeTableGrids( PreparedTableLayout const & tableL } // we can now propagate the columns width width to all cells - applyColumnsWidth( columnsWidth, headerCellsLayout, tableLayout ); - applyColumnsWidth( columnsWidth, dataCellsLayout, tableLayout ); - applyColumnsWidth( columnsWidth, errorCellsLayout, tableLayout ); + applyColumnsWidth( columnsWidth, headerRows, tableLayout ); + applyColumnsWidth( columnsWidth, dataRows, tableLayout ); + applyColumnsWidth( columnsWidth, errorRows, tableLayout ); } void TableTextFormatter::populateTitleCellsLayout( PreparedTableLayout const & tableLayout, diff --git a/src/coreComponents/common/format/table/TableMpiComponents.cpp b/src/coreComponents/common/format/table/TableMpiComponents.cpp index 4bf969282c9..3ebf9ea5833 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.cpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.cpp @@ -20,69 +20,24 @@ #include "TableMpiComponents.hpp" #include "common/MpiWrapper.hpp" +#include "common/format/table/TableTypes.hpp" namespace geos { -TableTextMpiOutput::TableTextMpiOutput( TableMpiLayout mpiLayout ): +TableTextMpiFormatter::TableTextMpiFormatter( TableMpiLayout mpiLayout ): TableTextFormatter(), m_mpiLayout( mpiLayout ) {} -TableTextMpiOutput::TableTextMpiOutput( TableLayout const & tableLayout, - TableMpiLayout mpiLayout ): +TableTextMpiFormatter::TableTextMpiFormatter( TableLayout const & tableLayout, + TableMpiLayout mpiLayout ): TableTextFormatter( tableLayout ), m_mpiLayout( mpiLayout ) {} -template<> -void TableTextMpiOutput::toStream< TableData >( std::ostream & tableOutput, - TableData const & tableData ) const -{ - TableTextMpiOutput::Status status { - // m_isMasterRank (only the master rank does the output of the header && bottom of the table) - MpiWrapper::commRank() == 0, - // m_isContributing (some ranks does not have any output to produce) - !tableData.getCellsData().empty(), - // m_hasContent - false, - // m_sepLine - "" - }; - - CellLayoutRows headerCellsLayout; - CellLayoutRows dataCellsLayout; - CellLayoutRows errorCellsLayout; - size_t tableTotalWidth = 0; - - { - ColumnWidthModifier const columnWidthModifier = [this, status]( stdVector< size_t > & columnsWidth ) { - stretchColumnsByRanks( columnsWidth, status ); - }; - initalizeTableGrids( m_tableLayout, tableData, - headerCellsLayout, dataCellsLayout, errorCellsLayout, - tableTotalWidth, columnWidthModifier ); - status.m_sepLine = string( tableTotalWidth, m_horizontalLine ); - } - - if( status.m_isMasterRank ) - { - outputTableHeader( tableOutput, m_tableLayout, headerCellsLayout, status.m_sepLine ); - tableOutput.flush(); - } - - outputTableDataToRank0( tableOutput, m_tableLayout, dataCellsLayout, status ); - - if( status.m_isMasterRank ) - { - outputTableFooter( tableOutput, m_tableLayout, errorCellsLayout, - status.m_sepLine, status.m_hasContent ); - tableOutput.flush(); - } -} - -void TableTextMpiOutput::stretchColumnsByRanks( stdVector< size_t > & columnsWidth, - TableTextMpiOutput::Status const & status ) const +void TableTextMpiFormatter::stretchColumnsByRanks( stdVector< size_t > & columnsWidth, + TableTextMpiFormatter::Status const & status ) const { { // we ensure we have the correct amount of columns on all ranks (for correct MPI reduction operation) size_t const rankColumnsCount = columnsWidth.size(); @@ -103,13 +58,33 @@ void TableTextMpiOutput::stretchColumnsByRanks( stdVector< size_t > & columnsWid MpiWrapper::allReduce( columnsWidth, columnsWidth, MpiWrapper::Reduction::Max ); } -void TableTextMpiOutput::outputTableDataToRank0( std::ostream & tableOutput, - PreparedTableLayout const & tableLayout, - CellLayoutRows const & dataCellsLayout, - TableTextMpiOutput::Status & status ) const +stdVector< TableData::CellData > TableTextMpiFormatter::parseStringRow( string_view rowString ) const { - integer const ranksCount = MpiWrapper::commSize(); + if( rowString.empty() ) + return stdVector< TableData::CellData >{}; + + if( rowString.front() == '|' ) + rowString.remove_prefix( 1 ); + string_view rowContent =rowString; + string cell; + stdVector< TableData::CellData > dataRow; + + std::string::size_type end = 0; + while( (end = rowContent.find( m_verticalLine )) != string_view::npos ) + { + cell =std::string( stringutilities::trimSpaces( rowContent.substr( 0, end ))); + dataRow.emplace_back( TableData::CellData( {CellType::Value, cell} )); + rowContent.remove_prefix( end + 1 ); + } + return dataRow; +} + +void TableTextMpiFormatter::gatherAndOutputTableDataInRankOrder( std::ostream & tableOutput, + TableFormatter::CellLayoutRows const & rows, + PreparedTableLayout const & tableLayout, + TableTextMpiFormatter::Status & status ) const +{ // master rank does the output directly to the output, other ranks will have to send it through a string. std::ostringstream localStringStream; std::ostream & rankOutput = status.m_isMasterRank ? tableOutput : localStringStream; @@ -118,45 +93,130 @@ void TableTextMpiOutput::outputTableDataToRank0( std::ostream & tableOutput, { if( m_mpiLayout.m_separatorBetweenRanks ) { - string const rankSepLine = GEOS_FMT( "{:-^{}}", m_mpiLayout.m_rankTitle, status.m_sepLine.size() - 2 ); + size_t const sepWidth = status.m_sepLine.size() > 2 ? status.m_sepLine.size() - 2 : 0; + string const rankSepLine = GEOS_FMT( "{:-^{}}", m_mpiLayout.m_rankTitle, sepWidth ); rankOutput << tableLayout.getIndentationStr() << m_verticalLine << rankSepLine << m_verticalLine << '\n'; } - outputTableData( rankOutput, tableLayout, dataCellsLayout ); + outputTableData( rankOutput, tableLayout, rows ); } - - // all other ranks than rank 0 render their output in a string and comunicate its size - stdVector< integer > ranksStrsSizes = stdVector< integer >( ranksCount, 0 ); string const rankStr = !status.m_isMasterRank && status.m_isContributing ? localStringStream.str() : ""; - integer const rankStrSize = rankStr.size(); - MpiWrapper::gather( &rankStrSize, 1, ranksStrsSizes.data(), 1, 0 ); + stdVector< string > strsAccrossRanks; - // we compute the memory layout of the ranks strings - stdVector< integer > ranksStrsOffsets = stdVector< integer >( ranksCount, 0 ); - integer ranksStrsTotalSize = 0; - for( integer rankId = 1; rankId < ranksCount; ++rankId ) + MpiWrapper::gatherStringOnRank0( rankStr, std::function< void(string_view) >( [&]( string_view str ){ + status.m_hasContent = true; + strsAccrossRanks.emplace_back( str ); + } ) ); + + if( status.m_isMasterRank && status.m_hasContent ) { - ranksStrsOffsets[rankId] = ranksStrsTotalSize; - ranksStrsTotalSize += ranksStrsSizes[rankId]; + for( string_view str : strsAccrossRanks ) + tableOutput << str; + } +} + +TableData TableTextMpiFormatter::gatherTableDataRank0( TableData const & localTableData ) const +{ + stdVector< buffer_unit_type > serializedTableData( 0 ); + size_t totalSize = 0; + + { // allocation + totalSize = localTableData.getSerializedSize(); + serializedTableData.reserve( totalSize ); } - // finally, we can send all text data to rank 0, then we output it in the output stream. - string ranksStrs = string( ranksStrsTotalSize, '\0' ); - MpiWrapper::gatherv( &rankStr[0], rankStrSize, - &ranksStrs[0], ranksStrsSizes.data(), ranksStrsOffsets.data(), - 0, MPI_COMM_GEOS ); - if( status.m_isMasterRank ) - { - // master rank status - status.m_hasContent = !dataCellsLayout.empty(); + { // Packing + if( totalSize > 0 ) + { + localTableData.serialize( serializedTableData ); + } + } + auto [globalLogRecords, counts, offsets] = + MpiWrapper::gatherBufferRank0< stdVector< buffer_unit_type > >( serializedTableData ); - for( integer rankId = 1; rankId < ranksCount; ++rankId ) + { // Unpacking + TableData tableDataGathered; + if( MpiWrapper::commRank() == 0 ) { - if( ranksStrsSizes[rankId] > 0 ) + buffer_unit_type const * startBuff = globalLogRecords.data(); + for( size_t idxRank = 0; idxRank < (size_t)MpiWrapper::commSize(); ++idxRank ) { - status.m_hasContent = true; - tableOutput << string_view( &ranksStrs[ranksStrsOffsets[rankId]], ranksStrsSizes[rankId] ); + integer byteFromThisRank = counts[idxRank]; + buffer_unit_type const * endRowsBuff = startBuff + byteFromThisRank; + while( startBuff < endRowsBuff ) + { + size_t byteFromThisRow = 0; + serialBuffer::deserializePrimitive( byteFromThisRow, startBuff, endRowsBuff ); + buffer_unit_type const * endRowBuff= startBuff + byteFromThisRow; + stdVector< TableData::CellData > row; + while( startBuff < endRowBuff ) + { + CellType cellType; + serialBuffer::deserializePrimitive( cellType, startBuff, endRowBuff ); + string cellValue; + serialBuffer::deserializeString( cellValue, startBuff, endRowBuff ); + row.push_back( {cellType, cellValue} ); + } + tableDataGathered.addRow( row ); + } } } + return tableDataGathered; + } +} + +template<> +void TableTextMpiFormatter::toStream< TableData >( std::ostream & tableOutput, + TableData const & tableData ) const +{ + TableTextMpiFormatter::Status status { + // m_isMasterRank (only the master rank does the output of the header && bottom of the table) + MpiWrapper::commRank() == 0, + // m_isContributing (some ranks does not have any output to produce) + !tableData.getCellsData().empty(), + // m_hasContent + false, + // m_sepLine + "" + }; + + if( m_sortingFunctor ) + { + TableData tableDataGathered = gatherTableDataRank0( tableData ); + + if( status.m_isMasterRank ) + { + tableDataGathered.sort( *m_sortingFunctor ); + TableTextFormatter::toStream( tableOutput, tableDataGathered ); + } + } + else + { // this version is faster (MPI cooperation) but can only be ordered by rank id + CellLayoutRows headerRows; + CellLayoutRows dataRows; + CellLayoutRows errorRows; + size_t tableTotalWidth = 0; + { // compute layout + ColumnWidthModifier const columnWidthModifier = [this, status]( stdVector< size_t > & columnsWidth ) { + stretchColumnsByRanks( columnsWidth, status ); + }; + initalizeTableGrids( m_tableLayout, tableData, + headerRows, dataRows, errorRows, + tableTotalWidth, columnWidthModifier ); + status.m_sepLine = string( tableTotalWidth, m_horizontalLine ); + } + + if( status.m_isMasterRank ) + { + outputTableHeader( tableOutput, m_tableLayout, headerRows, status.m_sepLine ); + tableOutput.flush(); + } + gatherAndOutputTableDataInRankOrder( tableOutput, dataRows, m_tableLayout, status ); + if( status.m_isMasterRank ) + { + outputTableFooter( tableOutput, m_tableLayout, errorRows, + status.m_sepLine, status.m_hasContent ); + tableOutput.flush(); + } } } diff --git a/src/coreComponents/common/format/table/TableMpiComponents.hpp b/src/coreComponents/common/format/table/TableMpiComponents.hpp index aeb37caa0ce..f4f3876489b 100644 --- a/src/coreComponents/common/format/table/TableMpiComponents.hpp +++ b/src/coreComponents/common/format/table/TableMpiComponents.hpp @@ -42,26 +42,27 @@ struct TableMpiLayout * @brief class to format data in a formatted text format, allowing contributions from multiple * MPI ranks. */ -class TableTextMpiOutput : public TableTextFormatter +class TableTextMpiFormatter : public TableTextFormatter { public: /// base class using Base = TableTextFormatter; - + /// Callable comparison function object used for std::sort for a TableData + using SortingFunc = std::function< bool (stdVector< TableData::CellData >, stdVector< TableData::CellData >) >; /** * @brief Construct a default Table Formatter without layout specification (to only insert data in it, * without any column / title). Feature is not tested. * @param mpiLayout MPI-specific layout information (default is having contiguous ranks data). */ - TableTextMpiOutput( TableMpiLayout mpiLayout = TableMpiLayout() ); + TableTextMpiFormatter( TableMpiLayout mpiLayout = TableMpiLayout() ); /** * @brief Construct a new TableTextMpiOutput from a tableLayout * @param tableLayout Contain all tableColumnData names and optionnaly the table title * @param mpiLayout MPI-specific layout information (default is having contiguous ranks data). */ - TableTextMpiOutput( TableLayout const & tableLayout, - TableMpiLayout mpiLayout = TableMpiLayout() ); + TableTextMpiFormatter( TableLayout const & tableLayout, + TableMpiLayout mpiLayout = TableMpiLayout() ); /** * @brief Convert a data source to a table string. @@ -74,6 +75,14 @@ class TableTextMpiOutput : public TableTextFormatter template< typename DATASOURCE > void toStream( std::ostream & outputStream, DATASOURCE const & tableData ) const; + /** + * @brief Set the Sorting Func object + * @param func The callable comparison function object + */ + void setSortingFunc( SortingFunc && func ) + { m_sortingFunctor = std::make_unique< SortingFunc >( std::move( func )); } + + private: // hiding toString() methods as they are not implemented with MPI support. @@ -89,6 +98,9 @@ class TableTextMpiOutput : public TableTextFormatter TableMpiLayout m_mpiLayout; + /// The custom comparison function object for std::sort + std::unique_ptr< SortingFunc > m_sortingFunctor; + /** * @brief Expend the columns width to accomodate with the content of all MPI ranks. * As it is based on MPI communications, every ranks must call this method. @@ -98,11 +110,31 @@ class TableTextMpiOutput : public TableTextFormatter void stretchColumnsByRanks( stdVector< size_t > & columnsWidth, Status const & status ) const; - void outputTableDataToRank0( std::ostream & tableOutput, - PreparedTableLayout const & tableLayout, - CellLayoutRows const & dataCellsLayout, - Status & status ) const; + /** + * @brief Parse a string row to a TablaData cells. + * @param rowString The string row string to parse. + * @return The parsed row as a vector of CellData. + */ + stdVector< TableData::CellData > parseStringRow( string_view rowString ) const; + + /** + * @brief Gather all the TableData to the rank 0. + * @param localTableData The local TableData to send to rank 0; + */ + TableData gatherTableDataRank0( TableData const & localTableData ) const; + + /** + * @brief Gather data cell rows across all MPI ranks and output them to rank 0 in rank order. + * @param tableOutput The output stream to display the resulting table + * @param dataCellsLayout The layout for the data cells + * @param tableLayout The layout of the table + * @param status The TableMpi status for the current rank + */ + void gatherAndOutputTableDataInRankOrder( std::ostream & tableOutput, + CellLayoutRows const & dataCellsLayout, + PreparedTableLayout const & tableLayout, + TableTextMpiFormatter::Status & status )const; }; } diff --git a/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp b/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp index be3a532565a..41547241b51 100644 --- a/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp +++ b/src/coreComponents/common/format/table/unitTests/testMpiTable.cpp @@ -99,13 +99,14 @@ TEST( testMpiTables, testDifferentRankData ) "-------------------------------------------\n" }, }; - - int const rankId = MpiWrapper::commRank(); - int const nbRanks = MpiWrapper::commSize(); - ASSERT_EQ( nbRanks, 4 ) << "This unit test cases are designed for exactly 4 ranks to check row ordering consistency."; - for( TestCase const & testCase: testCases ) { + int const rankId = MpiWrapper::commRank(); + int const nbRanks = MpiWrapper::commSize(); + ASSERT_EQ( nbRanks, 4 ) << "This unit test cases are designed for exactly 4 ranks to check row ordering consistency."; + + + TableLayout const layout = TableLayout(). setTitle( "Summary of negative pressure elements" ). addColumns( { "Global Id", "pressure [Pa]" } ). @@ -124,8 +125,7 @@ TEST( testMpiTables, testDifferentRankData ) data.addRow( id, value ); } } - - TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + TableTextMpiFormatter const formatter = TableTextMpiFormatter( layout, mpiLayout ); std::ostringstream oss; formatter.toStream( oss, data ); if( rankId == 0 ) @@ -134,6 +134,119 @@ TEST( testMpiTables, testDifferentRankData ) oss.str().data() ); } } + +} + +TEST( testMpiTables, testSortingMethod ) +{ + struct TestCase + { + stdVector< stdVector< std::pair< integer, real64 > > > m_ranksValues; + string m_expectedResult; + }; + + TestCase const testCase = + { + { // m_ranksValues: in this test, rank 2 has no value + { {2, 0.624}, {3, 0.791} }, + { {1, 0.502} }, + { {4, 0.243}, {5, 0.804}, {6, 0.302} }, + {}, + }, + "\n" // m_expectedResult + "-------------------------------------------\n" + "| Summary of negative pressure elements |\n" + "|-----------------------------------------|\n" + "| Global Id | pressure [Pa] |\n" + "|------------------|----------------------|\n" + "| 1 | 0.502 |\n" + "| 2 | 0.624 |\n" + "| 3 | 0.791 |\n" + "| 4 | 0.243 |\n" + "| 5 | 0.804 |\n" + "| 6 | 0.302 |\n" + "-------------------------------------------\n" + }; + + int const rankId = MpiWrapper::commRank(); + int const nbRanks = MpiWrapper::commSize(); + ASSERT_EQ( nbRanks, 4 ) << "This unit test cases are designed for exactly 4 ranks to check row ordering consistency."; + + + TableLayout const layout = TableLayout(). + setTitle( "Summary of negative pressure elements" ). + addColumns( { "Global Id", "pressure [Pa]" } ). + setDefaultHeaderAlignment( TableLayout::Alignment::left ); + TableData data; + auto const & rankTestData = testCase.m_ranksValues[rankId]; + + TableMpiLayout mpiLayout; + mpiLayout.m_separatorBetweenRanks = true; + + if( !rankTestData.empty() ) + { + mpiLayout.m_rankTitle = GEOS_FMT( "Rank {}, {} values", rankId, rankTestData.size() ); + for( auto const & [id, value] : rankTestData ) + { + data.addRow( id, value ); + } + } + + TableTextMpiFormatter formatter = TableTextMpiFormatter( layout, mpiLayout ); + formatter.setSortingFunc( []( std::vector< TableData::CellData > const & row1, + std::vector< TableData::CellData > const & row2 ) { + return tableDataSorting::positiveNumberStringComp( row1[0].value, row2[0].value ); + } ); + + std::ostringstream oss; + formatter.toStream( oss, data ); + if( rankId == 0 ) + { + EXPECT_STREQ( testCase.m_expectedResult.data(), + oss.str().data() ); + } + + +} + +TEST( testMpiTables, testCompPositiveValueTable ) +{ + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "123", "45" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "45", "123" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "42", "42" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "0", "0" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "9", "1" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10000000", "9999999" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9999999", "10000000" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10.5", "9.99" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9.99", "10.5" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "10", "9.999" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "9.999", "10" )); + + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "1.2", "1.9" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "1.9", "1.2" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.5", "1.50" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.50", "1.5" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.500", "1.5" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.5", "1.500" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "1.51", "1.510" )); + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "1.51", "1.509" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "3.14", "3.14" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "0.001", "0.001" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "100.0", "100.0" )); + + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "5", "5.0" )); + EXPECT_FALSE( tableDataSorting::positiveNumberStringComp( "5.0", "5" )); + + EXPECT_FALSE ( tableDataSorting::positiveNumberStringComp( "5595", "5155" )); + EXPECT_TRUE( tableDataSorting::positiveNumberStringComp( "5155", "5595" )); + + } int main( int argc, char * * argv ) diff --git a/src/coreComponents/common/initializeEnvironment.cpp b/src/coreComponents/common/initializeEnvironment.cpp index f08ad7582c2..a4d45dc5e0a 100644 --- a/src/coreComponents/common/initializeEnvironment.cpp +++ b/src/coreComponents/common/initializeEnvironment.cpp @@ -333,6 +333,14 @@ void setupEnvironment( int argc, char * argv[] ) void cleanupEnvironment() { MemoryLogging::getInstance().memoryStatsReport(); + + ErrorLogger::global().getLoggerReportData().gatherRecordsRank0(); + if( MpiWrapper::commRank() == 0 ) + { + TableTextFormatter tableReportFormatter; + GEOS_LOG( tableReportFormatter.toString< LogHistory >( ErrorLogger::global().getLoggerReportData() )); + } + LvArray::system::resetSignalHandling(); finalizeLogger(); finalizeCaliper(); diff --git a/src/coreComponents/common/logger/DiagnosticMessage.hpp b/src/coreComponents/common/logger/DiagnosticMessage.hpp new file mode 100644 index 00000000000..b91b4f4d6e0 --- /dev/null +++ b/src/coreComponents/common/logger/DiagnosticMessage.hpp @@ -0,0 +1,292 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DiagnosticMessage.hpp + */ + +#include "common/format/EnumStrings.hpp" + +namespace geos +{ + +/** + * @enum MsgType + * Enum listing the different types of possible errors + */ +enum class MsgType +{ + Error, + ExternalError, + Warning, + Exception, + Undefined +}; + +/// Declare strings associated with output MsgType values. +ENUM_STRINGS( MsgType, + "Error", + "ExternalError", + "Warning", + "Exception", + "Undefined" ); + + +/** + * @struct DiagnosticContext + * Store contextual information about the error that occurred and assign it a priority + * default is 0 + */ +struct DiagnosticContext +{ + + /** + * @enum Attribute + * Enumeration used to secure potential map keys + */ + enum class Attribute + { + InputFile, + InputLine, + DataPath, + DetectionLoc, + Signal, + }; + + /// String containing the target object name followed by the the file and line declaring it. + string m_formattedContext; + + /** + * @brief The map contains contextual information about the error + * It could be something like + * "file" = "/path/to/file.xml" + * "line" = "24" + * or something like + * "dataPath" = "/Functions/co2brine_philipsDensityTable + * The key is a field of the Attribute enumeration and is converted to a string for writing in the YAML + */ + map< Attribute, std::string > m_attributes; + + /** + * @brief Priority level assigned to an error context. + * @details Used to prioritize contexts (higher values = more relevant). Default is 0. + */ + integer m_priority = 0; + + /** + * @brief Construct to initialize DiagnosticContext + * @param formattedContext String containing the target object name followed by the the file and line declaring it. + * @param attributes Map containing contextual information about the error + */ + DiagnosticContext( string formattedContext, map< Attribute, std::string > attributes ): + m_formattedContext( formattedContext ), + m_attributes( attributes ) {}; + + /** + * @brief Construct to initialize DiagnosticContext given a string containing the context and his priority + * @param formattedContext String containing the target object name followed by the the file and line declaring it. + * @param attributes Map containing contextual information about the error + * @param priority Priority level assigned to an error context. + */ + DiagnosticContext( string formattedContext, map< Attribute, std::string > attributes, integer priority ): + m_formattedContext( formattedContext ), + m_attributes( attributes ), + m_priority( priority ) {}; + + /** + * @brief Set the priority value of the current error context information + * This way the different context information will appear in descending order during the error log output + * @param priority the new value to asign + * @return the reference to the corresponding error + */ + DiagnosticContext & setPriority( integer priority ) + { m_priority = priority; return *this; } + + /** + * @brief Convert a value from the Attribute enumeration to a string + * @param attribute the value of the enumeration to be converted + * @return a string representation of the enumeration value + */ + static std::string attributeToString( Attribute attribute ); +}; + + +/** + * @brief Struct to construct the diagnostic message object + */ +struct DiagnosticMsg +{ + /// Type of diagnostic (Warning, Error or Exception) + MsgType m_type = MsgType::Undefined; + /// the message that can be completed + std::string m_msg; + /// the cause of the error (erroneous condition, failed assertion...) if identified (optional) + std::string m_cause; + /// the rank(s) on which the diagnostic occured + std::set< int > m_ranksInfo; + /// the path of the source location file + std::string m_file; + /// the source location line (default is 0) + integer m_line = 0; + /// The log part where the diagnostic occured + string m_logPart; + /// Additional information about the diagnostic in the input file + std::vector< DiagnosticContext > m_contextsInfo; + /// the stack trace + std::vector< std::string > m_sourceCallStack; + /// Indicates whether the stored call stack trace is valid and usable. + bool m_isValidStackTrace = false; +}; + +/** + * @brief Builder class for constructing DiagnosticMsg objects + */ +class DiagnosticMsgBuilder +{ +public: + +/** + * @brief Initialize a new DiagnosticMsg + * @param msg The DiagnosticMsg being built + * @param msgType Type of the diagnostic + * @param msgContent The message of the diagnostic. It can be completed afterward + * @param rank The rank on which the diagnostic occured + * @return DiagnosticMsgBuilder + */ + static DiagnosticMsgBuilder init( DiagnosticMsg & msg, + MsgType msgType, + std::string_view msgContent, + integer rank ); + + /** + * @brief Modify an existing DiagnosticMsg + * @param errorMsg The existing DiagnosticMsg + * @return DiagnosticMsgBuilder + */ + static DiagnosticMsgBuilder modify( DiagnosticMsg & errorMsg ); + + /** + * @brief Append exception text to the message + * @param e The exception containing text to add + * @param toEnd If true, append at end; otherwise prepend + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & addToMsg( std::exception const & e, bool toEnd = false ); + + /** + * @brief Append text to the message + * @param msg The text to add + * @param toEnd If true, append at end; otherwise prepend + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & addToMsg( std::string_view msg, bool toEnd = false ); + /** + * @brief Adds one or more context elements to the error + * @tparam Args Variadic pack of compatible types (DiagnosticContext / DataContext) + * @param args List of context data structures. + * @return Reference to the current instance for method chaining. + */ + template< typename ... Args > + DiagnosticMsgBuilder & addContextInfo( Args && ... args ) + { + ( this->addContextInfoImpl( DiagnosticContext( args ) ), ... ); + return *this; + } + + /** + * @brief Add the detection location the DiagnosticMsg + * @param detectionLocation The context where the diagnostic happoned + * @return The instance, for builder pattern. + */ + DiagnosticMsgBuilder & addDetectionLocation( string_view detectionLocation ); + + /** + * @brief Add the signal to the DiagnosticMsg. + * - the signal can be one of the main error signals. + * - if the signal is SIGFPE, the nature of floating point error will be interpreted. + * @param sig The signal, from ISO C99 or POSIX standard. + * @param toEnd adds the message to the end if true, at the start otherwise. + * @return The instance, for builder pattern. + */ + DiagnosticMsgBuilder & addSignal( integer sig, bool toEnd = false ); + + /** + * @brief Set the source code location values (file and line where the error is detected) + * @param msgFile Name of the source file location to add + * @param msgLine Line of the source file location to add + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & setCodeLocation( std::string_view msgFile, integer msgLine ); + /** + * @brief Set the type of the error + * @param msgType The type can be error, warning or exception + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & setType( MsgType msgType ); + + /** + * @brief Set the cause of the error + * @param cause See documentation of m_cause. + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & setCause( std::string_view cause ); + + /** + * @brief Add a rank on which the error has been raised + * @param rank The value to add + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & addRank( integer rank ); + + /** + * @brief Add stack trace information about the error + * @param stacktrace stack trace information to add + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & addCallStackInfo( std::string_view stacktrace ); + + /** + * @brief Set log part where the disgnostic occured + * @param logPart The targetted log part + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & setLogPart( std::string_view logPart ); + + + + /** + * @return Get the DiagnosticMsg + */ + DiagnosticMsg & getDiagnosticMsg(); + +private: + + /** + * @brief Private constructor - use init() or modify() instead + * @param msg Reference to the DiagnosticMsg to build/modify + */ + DiagnosticMsgBuilder( DiagnosticMsg & msg ): + m_errorMsg( msg ){} + + /** + * @brief Add contextual information about the error/warning + * @param ctxInfo rvalue of the DiagnosticContext class + */ + DiagnosticMsgBuilder & addContextInfoImpl( DiagnosticContext && ctxInfo ); + + /// The diagnosticMsg being constructed + DiagnosticMsg & m_errorMsg; +}; +} diff --git a/src/coreComponents/common/logger/ErrorHandling.cpp b/src/coreComponents/common/logger/ErrorHandling.cpp index 6881e11259a..a0ed1540e76 100644 --- a/src/coreComponents/common/logger/ErrorHandling.cpp +++ b/src/coreComponents/common/logger/ErrorHandling.cpp @@ -20,11 +20,6 @@ #include "ErrorHandling.hpp" #include "common/DataTypes.hpp" #include "common/logger/Logger.hpp" -#include "common/format/StringUtilities.hpp" - -#include -#include -#include // signal management #include @@ -45,15 +40,15 @@ ErrorLogger g_errorLogger{}; ErrorLogger & ErrorLogger::global() { return g_errorLogger; } -std::string ErrorContext::attributeToString( ErrorContext::Attribute attribute ) +std::string DiagnosticContext::attributeToString( DiagnosticContext::Attribute attribute ) { switch( attribute ) { - case ErrorContext::Attribute::InputFile: return "inputFile"; - case ErrorContext::Attribute::InputLine: return "inputLine"; - case ErrorContext::Attribute::DataPath: return "dataPath"; - case ErrorContext::Attribute::DetectionLoc: return "detectionLocation"; - case ErrorContext::Attribute::Signal: return "signal"; + case DiagnosticContext::Attribute::InputFile: return "inputFile"; + case DiagnosticContext::Attribute::InputLine: return "inputLine"; + case DiagnosticContext::Attribute::DataPath: return "dataPath"; + case DiagnosticContext::Attribute::DetectionLoc: return "detectionLocation"; + case DiagnosticContext::Attribute::Signal: return "signal"; default: return "unknown"; } } @@ -87,11 +82,11 @@ DiagnosticMsgBuilder DiagnosticMsgBuilder::modify( DiagnosticMsg & errorMsg ) return DiagnosticMsgBuilder( errorMsg ); } -DiagnosticMsgBuilder & DiagnosticMsgBuilder::addContextInfoImpl( ErrorContext && ctxInfo ) +DiagnosticMsgBuilder & DiagnosticMsgBuilder::addContextInfoImpl( DiagnosticContext && ctxInfo ) { auto lowerBoundPos = std::lower_bound( m_errorMsg.m_contextsInfo.begin(), m_errorMsg.m_contextsInfo.end(), ctxInfo.m_priority, - []( ErrorContext const & ctx, integer priority ) + []( DiagnosticContext const & ctx, integer priority ) { return ctx.m_priority >= priority; } ); m_errorMsg.m_contextsInfo.insert( lowerBoundPos, std::move( ctxInfo ) ); return *this; @@ -99,9 +94,9 @@ DiagnosticMsgBuilder & DiagnosticMsgBuilder::addContextInfoImpl( ErrorContext && DiagnosticMsgBuilder & DiagnosticMsgBuilder::addDetectionLocation( string_view detectionLocation ) { - addContextInfo( ErrorContext{ string( detectionLocation ), - { { ErrorContext::Attribute::DetectionLoc, - string( detectionLocation ) } } } ); + addContextInfo( DiagnosticContext{ string( detectionLocation ), + { { DiagnosticContext::Attribute::DetectionLoc, + string( detectionLocation ) } } } ); return *this; } @@ -166,12 +161,12 @@ DiagnosticMsgBuilder & DiagnosticMsgBuilder::addSignal( integer const sig, bool // standard messages addToMsg( errorMsg, toEnd ); - this->addContextInfo( ErrorContext{ "Signal (detected from Signal Handler)", - { { ErrorContext::Attribute::Signal, - std::to_string( sig ) }, - { ErrorContext::Attribute::DetectionLoc, - string( "Signal handler" ) } - } } ); + this->addContextInfo( DiagnosticContext{ "Signal (detected from Signal Handler)", + { { DiagnosticContext::Attribute::Signal, + std::to_string( sig ) }, + { DiagnosticContext::Attribute::DetectionLoc, + string( "Signal handler" ) } + } } ); return *this; } @@ -201,6 +196,12 @@ DiagnosticMsgBuilder & DiagnosticMsgBuilder::addRank( integer const rank ) return *this; } +DiagnosticMsgBuilder & DiagnosticMsgBuilder::setLogPart( std::string_view logPart ) +{ + m_errorMsg.m_logPart = logPart; + return *this; +} + DiagnosticMsgBuilder & DiagnosticMsgBuilder::addCallStackInfo( std::string_view ossStackTrace ) { std::string str = std::string( ossStackTrace ); @@ -309,7 +310,7 @@ void ErrorLogger::formatMsgForLog( DiagnosticMsg const & errMsg, std::ostream & } os << PREFIX << "Rank " << stringutilities::join( errMsg.m_ranksInfo, ", " ) << "\n"; // --- ERROR CONTEXT & MESSAGE --- - std::vector< ErrorContext > const & contexts = errMsg.m_contextsInfo; + std::vector< DiagnosticContext > const & contexts = errMsg.m_contextsInfo; if( contexts.empty() || contexts.front().m_formattedContext.empty()) { os << PREFIX << "Message :\n"; @@ -374,15 +375,15 @@ void ErrorLogger::writeToYamlStream( DiagnosticMsg & errMsg ) // context information if( !errMsg.m_contextsInfo.empty() ) { - std::vector< ErrorContext > contextInfo = errMsg.m_contextsInfo; + std::vector< DiagnosticContext > contextInfo = errMsg.m_contextsInfo; // Sort contextual information by decreasing priority std::sort( contextInfo.begin(), contextInfo.end(), - []( const ErrorContext & a, const ErrorContext & b ) { + []( const DiagnosticContext & a, const DiagnosticContext & b ) { return a.m_priority > b.m_priority; } ); // Additional informations about the context of the error and priority information of each context yamlFile << g_level1Next << "contexts:\n"; - for( ErrorContext const & ctxInfo : contextInfo ) + for( DiagnosticContext const & ctxInfo : contextInfo ) { yamlFile << g_level3Start << "priority: " << ctxInfo.m_priority << "\n"; if( !ctxInfo.m_formattedContext.empty()) @@ -391,7 +392,7 @@ void ErrorLogger::writeToYamlStream( DiagnosticMsg & errMsg ) } for( auto const & [key, value] : ctxInfo.m_attributes ) { - yamlFile << g_level3Next << ErrorContext::attributeToString( key ) << ": " << value << "\n"; + yamlFile << g_level3Next << DiagnosticContext::attributeToString( key ) << ": " << value << "\n"; } } } @@ -435,8 +436,11 @@ void ErrorLogger::writeToYamlStream( DiagnosticMsg & errMsg ) } } + + void ErrorLogger::flushErrorMsg( DiagnosticMsg & errMsg ) { + loggerMsgReportData.recordDiagnostic( errMsg ); writeToLogStream( errMsg ); if( isOutputFileEnabled() ) { @@ -446,6 +450,8 @@ void ErrorLogger::flushErrorMsg( DiagnosticMsg & errMsg ) void ErrorLogger::flushCurrentExceptionMessage() { + loggerMsgReportData.recordDiagnostic( m_getCurrentExceptionMsg ); + writeToLogStream( m_getCurrentExceptionMsg ); if( isOutputFileEnabled() ) { diff --git a/src/coreComponents/common/logger/ErrorHandling.hpp b/src/coreComponents/common/logger/ErrorHandling.hpp index 38df2a2566c..e0821228a6a 100644 --- a/src/coreComponents/common/logger/ErrorHandling.hpp +++ b/src/coreComponents/common/logger/ErrorHandling.hpp @@ -21,263 +21,13 @@ #define INITIALIZATION_ERROR_LOGGER_HPP #include "common/DataTypes.hpp" -#include "common/format/Format.hpp" -#include "common/format/StringUtilities.hpp" +#include "common/format/LogPart.hpp" +#include "common/logger/LogHistory.hpp" #include namespace geos { -/** - * @struct ErrorContext - * Store contextual information about the error that occurred and assign it a priority - * default is 0 - */ -struct ErrorContext -{ - - /** - * @enum Attribute - * Enumeration used to secure potential map keys - */ - enum class Attribute - { - InputFile, - InputLine, - DataPath, - DetectionLoc, - Signal, - }; - - /// String containing the target object name followed by the the file and line declaring it. - string m_formattedContext; - - /** - * @brief The map contains contextual information about the error - * It could be something like - * "file" = "/path/to/file.xml" - * "line" = "24" - * or something like - * "dataPath" = "/Functions/co2brine_philipsDensityTable - * The key is a field of the Attribute enumeration and is converted to a string for writing in the YAML - */ - map< Attribute, std::string > m_attributes; - - /** - * @brief Priority level assigned to an error context. - * @details Used to prioritize contexts (higher values = more relevant). Default is 0. - */ - integer m_priority = 0; - - /** - * @brief Construct to initialize ErrorContext - * @param formattedContext String containing the target object name followed by the the file and line declaring it. - * @param attributes Map containing contextual information about the error - */ - ErrorContext( string formattedContext, map< Attribute, std::string > attributes ): - m_formattedContext( formattedContext ), - m_attributes( attributes ) {}; - - /** - * @brief Construct to initialize ErrorContext given a string containing the context and his priority - * @param formattedContext String containing the target object name followed by the the file and line declaring it. - * @param attributes Map containing contextual information about the error - * @param priority Priority level assigned to an error context. - */ - ErrorContext( string formattedContext, map< Attribute, std::string > attributes, integer priority ): - m_formattedContext( formattedContext ), - m_attributes( attributes ), - m_priority( priority ) {}; - - /** - * @brief Set the priority value of the current error context information. - * This way the different context information will appear in descending order during the error log output. - * @param priority the new value to asign - * @return the reference to the corresponding error - */ - ErrorContext & setPriority( integer priority ) - { m_priority = priority; return *this; } - - /** - * @brief Convert a value from the Attribute enumeration to a string - * @param attribute the value of the enumeration to be converted - * @return a string representation of the enumeration value - */ - static std::string attributeToString( Attribute attribute ); - -}; - -/** - * @enum MsgType - * Enum listing the different types of possible diagnostics - */ -enum class MsgType -{ - Error, - ExternalError, - Warning, - Exception, - Undefined -}; - -/** - * @brief Struct to construct the diagnostic message object - */ -struct DiagnosticMsg -{ - /// Type of diagnostic (Warning, Error or Exception) - MsgType m_type = MsgType::Undefined; - /// the message that can be completed - std::string m_msg; - /// the cause of the error (erroneous condition, failed assertion...) if identified (optional) - std::string m_cause; - /// the rank(s) on which the diagnostic occured - std::set< int > m_ranksInfo; - /// the source location file - std::string m_file; - /// the source location line (default is 0) - integer m_line = 0; - /// Additional information about the diagnostic in the input file - std::vector< ErrorContext > m_contextsInfo; - /// the stack trace - std::vector< std::string > m_sourceCallStack; - /// Indicates whether the stored call stack trace is valid and usable. - bool m_isValidStackTrace = false; -}; - -/** - * @brief Builder class for constructing DiagnosticMsg objects - */ -class DiagnosticMsgBuilder -{ -public: - -/** - * @brief Initialize a new DiagnosticMsg - * @param msg The DiagnosticMsg being built - * @param msgType Type of the diagnostic - * @param msgContent The message of the diagnostic. It can be completed afterward - * @param rank The rank on which the diagnostic occured - * @return DiagnosticMsgBuilder - */ - static DiagnosticMsgBuilder init( DiagnosticMsg & msg, - MsgType msgType, - std::string_view msgContent, - integer rank ); - - /** - * @brief Modify an existing DiagnosticMsg - * @param errorMsg The existing DiagnosticMsg - * @return DiagnosticMsgBuilder - */ - static DiagnosticMsgBuilder modify( DiagnosticMsg & errorMsg ); - - /** - * @brief Append exception text to the message - * @param e The exception containing text to add - * @param toEnd If true, append at end; otherwise prepend - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & addToMsg( std::exception const & e, bool toEnd = false ); - - /** - * @brief Append text to the message - * @param msg The text to add - * @param toEnd If true, append at end; otherwise prepend - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & addToMsg( std::string_view msg, bool toEnd = false ); - - /** - * @brief Adds one or more context elements to the error - * @tparam Args Variadic pack of compatible types (ErrorContext / DataContext) - * @param args List of context data structures. - * @return Reference to the current instance for method chaining. - */ - template< typename ... Args > - DiagnosticMsgBuilder & addContextInfo( Args && ... args ) - { - ( this->addContextInfoImpl( ErrorContext( args ) ), ... ); - return *this; - } - - /** - * @brief Add where the detection occured - * @param detectionLocation The context where the diagnostic happoned - * @return The instance, for builder pattern. - */ - DiagnosticMsgBuilder & addDetectionLocation( string_view detectionLocation ); - - /** - * @brief Add the signal to the DiagnosticMsg. - * - the signal can be one of the main error signals. - * - if the signal is SIGFPE, the nature of floating point error will be interpreted. - * @param sig The signal, from ISO C99 or POSIX standard. - * @param toEnd adds the message to the end if true, at the start otherwise. - * @return The instance, for builder pattern. - */ - DiagnosticMsgBuilder & addSignal( integer sig, bool toEnd = false ); - - /** - * @brief Set the source code location values (file and line where the error is detected) - * @param msgFile Name of the source file location to add - * @param msgLine Line of the source file location to add - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & setCodeLocation( std::string_view msgFile, integer msgLine ); - - /** - * @brief Set the type of the error, (amoung one of the MsgType) - * @param msgType The type can be error, warning or exception - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & setType( MsgType msgType ); - - /** - * @brief Set the cause of the error - * @param cause See documentation of m_cause. - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & setCause( std::string_view cause ); - - /** - * @brief Add a rank on which the error has been raised - * @param rank The rank value - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & addRank( integer rank ); - - /** - * @brief Add the stack trace information about the error - * @param stacktrace stack trace information to add - * @return Reference to the current instance for method chaining. - */ - DiagnosticMsgBuilder & addCallStackInfo( std::string_view stacktrace ); - - /** - * @return Get the DiagnosticMsg - */ - DiagnosticMsg & getDiagnosticMsg(); - -private: - - /** - * @brief Private constructor - use init() or modify() instead - * @param msg Reference to the DiagnosticMsg to build/modify - */ - DiagnosticMsgBuilder( DiagnosticMsg & msg ): - m_errorMsg( msg ){} - - /** - * @brief Add contextual information about the error/warning - * @param ctxInfo rvalue of the ErrorContext class - */ - DiagnosticMsgBuilder & addContextInfoImpl( ErrorContext && ctxInfo ); - - /// The diagnosticMsg being constructed - DiagnosticMsg & m_errorMsg; -}; - /** * @brief Logger for formatting and outputting diagnostics */ @@ -374,7 +124,7 @@ class ErrorLogger /** * @brief Write all the information retrieved about the diagnostic message into the instance - * outputs (stream specified, std::cout by default + optional yaml file) + * outputs (stream specified + optional yaml file) * @param errMsg a reference to the ErrorMsg to output, and will be re-initialized * @note Used for warnings and non-exception errors */ @@ -393,16 +143,49 @@ class ErrorLogger */ void writeToLogStream( DiagnosticMsg & errMsg ); + /** + * @brief Gets the current logger report data. + * @return The current log part as a string. + */ + LogHistory const & getLoggerReportData() const + {return loggerMsgReportData;} + + /** + * @brief Gets the current logger report data. + * @return The current log part as a string. + */ + LogHistory & getLoggerReportData() + {return loggerMsgReportData;} + + /** + * @brief Gets the current log part. + * @return The current log part as a string. + */ + string_view getCurrentLogPart() const + {return m_currentLogPart;} + +/** + * @brief Sets the current log part. + * @param currentLogPart The new log part to set. + */ + void setCurrentLogPart( string_view currentLogPart ) + { m_currentLogPart = currentLogPart; } + private: /// The error constructed via exceptions DiagnosticMsg m_getCurrentExceptionMsg; + /// The log history associated + LogHistory loggerMsgReportData = {}; + /// Indicate whether the write to YAML command line option is enabled bool m_writeYaml = false; /// YAML file name std::string_view m_filename = "errors.yaml"; /// The stream used for the log output. By default used std::cout std::ostream & m_stream = std::cout; + /// The current log part being executed + string m_currentLogPart; /// Avoid concurrent access between threads for log outputs std::mutex m_errorHandlerAsciiMutex; /// Avoid concurrent access between threads for yaml outputs diff --git a/src/coreComponents/common/logger/LogHistory.cpp b/src/coreComponents/common/logger/LogHistory.cpp new file mode 100644 index 00000000000..bb384daa76b --- /dev/null +++ b/src/coreComponents/common/logger/LogHistory.cpp @@ -0,0 +1,240 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file LogHistory.cpp + */ + +#include "LogHistory.hpp" +#include "common/DataTypes.hpp" +#include "common/StdContainerWrappers.hpp" +#include "common/format/EnumStrings.hpp" +#include "common/format/table/TableData.hpp" +#include "common/format/table/TableLayout.hpp" +#include "common/format/table/TableTypes.hpp" +#include "common/MpiWrapper.hpp" +#include +#include +#include + +namespace geos +{ + +string_view extractAfterLastOccurrence( string_view str, char delimiter ) +{ + size_t pos = str.find_last_of( delimiter ); + + if( pos == std::string::npos ) + { + return str; + } + + return str.substr( pos + 1 ); +} + +LogHistory::LogRecord::LogRecord(): + m_key( {} ), + m_value( {} ) +{} +LogHistory::LogRecord::LogRecord( Key const & key, Values const & values ): + m_key( key ), + m_value( values ) +{} + +bool LogHistory::LogRecord::Key::operator==( Key const & rhs ) const +{ + return this->m_filename == rhs.m_filename && + this->m_lineId == rhs.m_lineId; +} + +void LogHistory::LogRecord::deserialize( buffer_unit_type const * & logRecordBytes, buffer_unit_type const * end ) +{ + serialBuffer::deserializeString( m_key.m_filename, logRecordBytes, end ); + serialBuffer::deserializePrimitive( m_key.m_lineId, logRecordBytes, end ); + serialBuffer::deserializeString( m_value.m_logPart, logRecordBytes, end ); + serialBuffer::deserializePrimitive( m_value.m_msgType, logRecordBytes, end ); + + m_value.m_count = 0; +} + +void LogHistory::LogRecord::serialize( stdVector< buffer_unit_type > & out ) const +{ + auto const serializePrimitive = [&]( auto const data ) + { + buffer_unit_type const * begin = reinterpret_cast< buffer_unit_type const * >( &data ); + buffer_unit_type const * end = begin + sizeof(data); + out.insert( out.end(), begin, end ); + }; + + auto const serializeString = [&]( string const & data ) + { + serializePrimitive( data.size()); + auto * begin = data.data(); + auto * end = begin + data.size(); + out.insert( out.end(), begin, end ); + }; + + serializeString( m_key.m_filename ); + serializePrimitive( m_key.m_lineId ); + serializeString( m_value.m_logPart ); + serializePrimitive( m_value.m_msgType ); +} + +void LogHistory::recordDiagnostic( DiagnosticMsg const & msgType ) +{ + string_view fileName = extractAfterLastOccurrence( msgType.m_file, '/' ); + integer lineCount = msgType.m_line; + insertDiagnosticReport( { + /*.m_key = */ { + /* .m_filename = */ string( fileName ), + /* .m_lineId = */ lineCount + }, + /*.m_value =*/ { + /* .m_logPart = */ string( msgType.m_logPart ), + /* .m_msgType = */ msgType.m_type, + /* .m_count = */ 1 + } + } ); +} + +void LogHistory::insertDiagnosticReport( LogRecord const & logRecord ) +{ + auto it = m_diagnosticHistory.find( {logRecord.m_key} ); + if( it == m_diagnosticHistory.end()) + { + m_diagnosticHistory.emplace( logRecord.m_key, logRecord.m_value ); + } + else + { + it->second.m_count += logRecord.m_value.m_count; + } +} + + + +size_t LogHistory::LogRecord::getSerializedSize() const +{ + return + serialBuffer::sizeOfString( m_key.m_filename ) + + serialBuffer::sizeOfPrimitive( m_key.m_lineId ) + + serialBuffer::sizeOfString( m_value.m_logPart ) + + serialBuffer::sizeOfPrimitive( m_value.m_msgType ); +} + +void LogHistory::gatherRecordsRank0() +{ + LogHistory & history = ErrorLogger::global().getLoggerReportData(); + stdVector< buffer_unit_type > localLogRecords( 0 ); + integer totalSize = 0; + + { // allocation + for( auto const & [key, value] : getDiagnosticHistory() ) + { + LogRecord record( key, value ); + totalSize += record.getSerializedSize(); + } + localLogRecords.reserve( totalSize ); + } + + + { // Packing + if( getDiagnosticHistory().size() > 0 ) + { + for( auto const & [key, value] : getDiagnosticHistory() ) + { + LogRecord record( key, value ); + record.serialize( localLogRecords ); + } + } + } + + auto [globalLogRecords, counts, offsets] = + MpiWrapper::gatherBufferRank0< stdVector< buffer_unit_type > >( localLogRecords ); + + { // Unpacking + if( MpiWrapper::commRank() == 0 ) + { + buffer_unit_type const * startGlobalRecord = globalLogRecords.data(); + for( size_t idxRank = 0; idxRank < (size_t)MpiWrapper::commSize(); ++idxRank ) + { + integer byteFromThisRank = counts[idxRank]; + buffer_unit_type const * rankEnd= startGlobalRecord + byteFromThisRank; + while( startGlobalRecord < rankEnd ) + { + LogRecord unpackRecord; + unpackRecord.deserialize( startGlobalRecord, rankEnd ); + history.insertDiagnosticReport( unpackRecord ); + } + } + } + } +} + +template<> +string TableTextFormatter::toString< LogHistory >( LogHistory const & logHistory ) const +{ + using CellRow = stdArray< TableData::CellData, (size_t) MsgType::Undefined >; + + TableLayout tableLayout; + tableLayout.addColumn( "Types" ); + + // fill header + for( size_t msgTypeIdx = 0; msgTypeIdx != (size_t)MsgType::Undefined; msgTypeIdx++ ) + { + tableLayout.addColumn( EnumStrings< MsgType >::toString( (MsgType) msgTypeIdx ) ); + } + + // prepare logHistory row + stdMap< std::pair< string, MsgType >, integer > countPerPartAndType; + CellRow emptyCellRow; + emptyCellRow.fill( TableData::CellData{CellType::Value, "0"} ); + + stdMap< string, CellRow > rowByPart; + // retrive unique message + for( const auto & [key, values] : logHistory.getDiagnosticHistory()) + { + auto logPart = values.m_logPart; + MsgType msgType = values.m_msgType; + countPerPartAndType.get_inserted( std::make_pair( logPart, msgType ))++; + + if( rowByPart.find( logPart ) == rowByPart.end()) + rowByPart.get_inserted( logPart ) = emptyCellRow; + } + + // update rowByPart values + for( auto & [keyPair, count] : countPerPartAndType ) + { + auto logPart = std::get< 0 >( keyPair ); + auto msgType = std::get< 1 >( keyPair ); + rowByPart.get_inserted( logPart ).at((size_t)msgType ).value = std::to_string( count ); + } + + TableData data; + for( auto const & [logPart, cells] : rowByPart ) + { + stdVector< TableData::CellData >row ( { + TableData::CellData{ CellType::Value, logPart } + } ); + + row.insert( row.end(), cells.begin(), cells.end()); + data.addRow( row ); + } + + TableTextFormatter textFormatter( tableLayout ); + return textFormatter.toString( data ) + "\n"; +} + + +} diff --git a/src/coreComponents/common/logger/LogHistory.hpp b/src/coreComponents/common/logger/LogHistory.hpp new file mode 100644 index 00000000000..7973e51a93a --- /dev/null +++ b/src/coreComponents/common/logger/LogHistory.hpp @@ -0,0 +1,154 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file LogHistory.hpp + */ + +#ifndef GEOS_COMMON_LOGGER_MSG_REPORT_DATA_HPP +#define GEOS_COMMON_LOGGER_MSG_REPORT_DATA_HPP + +#include "common/DataTypes.hpp" +#include "common/StdContainerWrappers.hpp" +#include "common/format/LogPart.hpp" +#include "common/format/table/TableFormatter.hpp" +#include "DiagnosticMessage.hpp" +#include + + +namespace geos +{ + +/** + * @brief Keep track of all diagnostic message occured during the simulation + */ +class LogHistory +{ +public: + + /** + * @brief Records a diagnostic message occurrence in the history. + * @param diagMsg The diagnostic message associated + */ + void recordDiagnostic( DiagnosticMsg const & diagMsg ); + + /** + * @return The const historical diagnostic + */ + auto const & getDiagnosticHistory() const + { return m_diagnosticHistory; } + + /** + * @brief Gather all the records to the rank 0 + * @note Store all records to an unordered_map, non unique records will increment the LogRecord::m_count + */ + void gatherRecordsRank0(); + +private: + + /** + * @brief Contains the essential information of a DiagnosticMsg and can be serialized. + */ + struct LogRecord + { + /** @brief Identifier for a diagnostic message (source localization). */ + struct Key + { + string m_filename; ///< Source file name + integer m_lineId; ///< Line number in the file. + + /// @cond DO_NOT_DOCUMENT + bool operator==( Key const & lhs ) const; + /// @endcond DO_NOT_DOCUMENT + + } m_key; + + /** @brief Content and metadata of the diagnostic message. */ + struct Values + { + string m_logPart; ///< The string logPart. + MsgType m_msgType; ///< Message type. + integer m_count; ///< Number of occurrences detected. + } m_value; + + /** @brief Calculates the total size required for the serialization. + * @return Size in bytes. + */ + size_t getSerializedSize() const; + + /** + * @brief Construct an empty Log Record object + */ + LogRecord(); + + /** + * @brief Construct a new Log Record object + * @param key The log record key + * @param values The log record values + */ + LogRecord( Key const & key, Values const & values ); + + /** @brief Serializes the record fields into a binary buffer. + * @param out Destination vector for the serialized data. + */ + void serialize( stdVector< buffer_unit_type > & out ) const; + + /** + * @brief Deserializes a complete record and advances the read pointer. + * @param logRecordBytes Reference to the read pointer. + * @param end Upper limit of readable memory. + */ + void deserialize( buffer_unit_type const * & logRecordBytes, buffer_unit_type const * end ); + }; + + /** + * @brief Insert a LogRepord in the m_diagnosticHistory + * @param log The logRecord with all the information + */ + void insertDiagnosticReport( LogRecord const & log ); + + /// @cond DO_NOT_DOCUMENT + struct LocationKeyHash + { + + size_t operator()( LogRecord::Key const & key ) const noexcept + { + size_t h1 = std::hash< string >{} (key.m_filename); + size_t h2 = std::hash< integer >{} (key.m_lineId); + + return h1 ^ (h2 << 1); + } + + }; + /// @endcond + + /** + * @brief Diagnostic history happened during the simulation + */ + stdUnorderedMap< LogRecord::Key, LogRecord::Values, + LocationKeyHash > m_diagnosticHistory; +}; + +/** + * @brief Template specialisation to convert a LogHistory to a table string. + * @param logHistory The LogHistory object to convert. + * @return The CSV string representation of the logHistory. + */ +template<> +string TableTextFormatter::toString< LogHistory >( LogHistory const & logHistory ) const; + +} + +#endif diff --git a/src/coreComponents/common/unitTests/CMakeLists.txt b/src/coreComponents/common/unitTests/CMakeLists.txt index 2f6966bf487..8ef1ba9beaf 100644 --- a/src/coreComponents/common/unitTests/CMakeLists.txt +++ b/src/coreComponents/common/unitTests/CMakeLists.txt @@ -1,7 +1,7 @@ # Specify list of tests set( gtest_geosx_tests - testDataTypes.cpp testFixedSizeDeque.cpp + testDataTypes.cpp testMacros.cpp testMpiWrapper.cpp testTypeDispatch.cpp diff --git a/src/coreComponents/common/unitTests/testDataTypes.cpp b/src/coreComponents/common/unitTests/testDataTypes.cpp index 58ebc681cae..b2ad99a432e 100644 --- a/src/coreComponents/common/unitTests/testDataTypes.cpp +++ b/src/coreComponents/common/unitTests/testDataTypes.cpp @@ -16,7 +16,10 @@ // Source includes #include "common/DataTypes.hpp" -// TPL includes +#include "common/StdContainerWrappers.hpp" + + +// // TPL includes #include #include @@ -24,8 +27,7 @@ using namespace geos; TEST( testDataTypes, testBoundChecking ) { - internal::StdVectorWrapper< std::string, - std::allocator< std::string >, + internal::StdVectorWrapper< std::string, std::allocator< std::string >, true > vectorBoundsChecking = {"test"}; EXPECT_THROW( { try diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/parameters/PhaseType.cpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/parameters/PhaseType.cpp index a86de714834..f9dcb183408 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/parameters/PhaseType.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/parameters/PhaseType.cpp @@ -19,6 +19,7 @@ #include "PhaseType.hpp" #include "common/format/StringUtilities.hpp" +#include "common/logger/Logger.hpp" namespace geos { diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 8ce7f1c7b55..c7d1e1bd77b 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -108,12 +108,12 @@ string DataFileContext::toString() const } } -ErrorContext DataFileContext::getContextInfo() const +DiagnosticContext DataFileContext::getContextInfo() const { - ErrorContext ctxInfo{ + DiagnosticContext ctxInfo{ toString(), - { { ErrorContext::Attribute::InputFile, m_filePath }, - { ErrorContext::Attribute::InputLine, std::to_string( m_line )} } + { { DiagnosticContext::Attribute::InputFile, m_filePath }, + { DiagnosticContext::Attribute::InputLine, std::to_string( m_line )} } }; return ctxInfo; } diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 8a43381b438..7ab4685ed95 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -63,15 +63,15 @@ class DataContext /** * @brief Returns contextual information, including the file name and the line number - * @return ErrorContext + * @return DiagnosticContext */ - virtual ErrorContext getContextInfo() const = 0; + virtual DiagnosticContext getContextInfo() const = 0; /** - * @brief Conversion operator to ErrorContext - * @return ErrorContext + * @brief Conversion operator to DiagnosticContext + * @return DiagnosticContext */ - explicit operator ErrorContext() const { + explicit operator DiagnosticContext() const { return getContextInfo(); } @@ -172,9 +172,9 @@ class DataFileContext final : public DataContext /** * @brief Return contextual information (file and line of the input file where the error occured) - * @return ErrorContext ErrorLogger instance updated with context information + * @return DiagnosticContext ErrorLogger instance updated with context information */ - ErrorContext getContextInfo() const override; + DiagnosticContext getContextInfo() const override; /** * @return the type name in the source file (XML node tag name / attribute name). diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 04e1fa1f2a3..9c3e3dbb6fb 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -54,11 +54,11 @@ string GroupContext::toString() const return path.str(); } -ErrorContext GroupContext::getContextInfo() const +DiagnosticContext GroupContext::getContextInfo() const { - ErrorContext ctxInfo{ + DiagnosticContext ctxInfo{ toString(), - { { ErrorContext::Attribute::DataPath, GEOS_FMT( "{}/{}", m_group.getPath(), m_targetName )} } + { { DiagnosticContext::Attribute::DataPath, GEOS_FMT( "{}/{}", m_group.getPath(), m_targetName )} } }; return ctxInfo; } diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index abdc1199e7d..698fa02a301 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -71,9 +71,9 @@ class GroupContext : public DataContext /** * @brief Return contextual information here it is a data path - * @return ErrorContext ErrorLogger instance updated with context information + * @return DiagnosticContext ErrorLogger instance updated with context information */ - ErrorContext getContextInfo() const override; + DiagnosticContext getContextInfo() const override; /** * @copydoc DataContext::getToStringInfo() diff --git a/src/coreComponents/dataRepository/WrapperContext.cpp b/src/coreComponents/dataRepository/WrapperContext.cpp index 3d689ade254..323acdc3e83 100644 --- a/src/coreComponents/dataRepository/WrapperContext.cpp +++ b/src/coreComponents/dataRepository/WrapperContext.cpp @@ -38,11 +38,11 @@ string WrapperContext::toString() const GEOS_FMT( "{}/{}", m_group.getDataContext().toString(), m_typeName ); } -ErrorContext WrapperContext::getContextInfo() const +DiagnosticContext WrapperContext::getContextInfo() const { - ErrorContext ctxInfo{ + DiagnosticContext ctxInfo{ toString(), - {{ ErrorContext::Attribute::DataPath, GEOS_FMT( "{}/{}", m_group.getPath(), m_typeName ) } }, + {{ DiagnosticContext::Attribute::DataPath, GEOS_FMT( "{}/{}", m_group.getPath(), m_typeName ) } }, }; return ctxInfo; } diff --git a/src/coreComponents/dataRepository/WrapperContext.hpp b/src/coreComponents/dataRepository/WrapperContext.hpp index b136a66862c..1cab552e581 100644 --- a/src/coreComponents/dataRepository/WrapperContext.hpp +++ b/src/coreComponents/dataRepository/WrapperContext.hpp @@ -56,9 +56,9 @@ class WrapperContext final : public GroupContext /** * @brief Return contextual information here it is a data path - * @return ErrorContext ErrorLogger instance updated with context information + * @return DiagnosticContext ErrorLogger instance updated with context information */ - ErrorContext getContextInfo() const override; + DiagnosticContext getContextInfo() const override; }; diff --git a/src/coreComponents/events/EventManager.cpp b/src/coreComponents/events/EventManager.cpp index 5e24698b008..261542cf52f 100644 --- a/src/coreComponents/events/EventManager.cpp +++ b/src/coreComponents/events/EventManager.cpp @@ -176,7 +176,7 @@ bool EventManager::run( DomainPartition & domain ) m_dt = dt_global; #endif } - LogPart logPart( "TIMESTEP", MpiWrapper::commRank() == 0 ); + LogPart logPart( "Timestep", MpiWrapper::commRank() == 0 ); outputTime( logPart ); logPart.begin(); diff --git a/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/MetisInterface.cpp b/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/MetisInterface.cpp index f4d48c82b9a..817bef0c015 100644 --- a/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/MetisInterface.cpp +++ b/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/MetisInterface.cpp @@ -19,6 +19,7 @@ #include "MetisInterface.hpp" #include "common/TimingMacros.hpp" +#include "common/logger/Logger.hpp" #include diff --git a/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/ScotchInterface.cpp b/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/ScotchInterface.cpp index f85ca5aee1b..41a1bfe6f27 100644 --- a/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/ScotchInterface.cpp +++ b/src/coreComponents/linearAlgebra/multiscale/mesh/coarsening/ScotchInterface.cpp @@ -19,7 +19,7 @@ #include "ScotchInterface.hpp" #include "common/TimingMacros.hpp" - +#include "common/logger/Logger.hpp" #include static_assert( std::is_same< SCOTCH_Num, int64_t >::value, diff --git a/src/coreComponents/mainInterface/GeosxState.cpp b/src/coreComponents/mainInterface/GeosxState.cpp index 1fcb001bc18..e4ffd96b132 100644 --- a/src/coreComponents/mainInterface/GeosxState.cpp +++ b/src/coreComponents/mainInterface/GeosxState.cpp @@ -127,7 +127,8 @@ bool GeosxState::initializeDataRepository() Timer timer( m_initTime ); GEOS_THROW_IF_NE( m_state, State::UNINITIALIZED, geos::LogicError ); - + LogPart postProcessiveLog( "Input Files Processing", MpiWrapper::commRank() == 0 ); + postProcessiveLog.begin(); getProblemManager().parseCommandLineInput(); if( !getProblemManager().getSchemaFileName().empty() ) @@ -138,6 +139,7 @@ bool GeosxState::initializeDataRepository() } getProblemManager().parseInputFile(); + postProcessiveLog.end(); getProblemManager().problemSetup(); m_state = State::INITIALIZED; diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 3c2718b1955..ff31196f4af 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -172,7 +172,7 @@ void ProblemManager::problemSetup() postInputInitializationRecursive(); - LogPart meshGenerationLog( "Mesh generation", MpiWrapper::commRank() == 0 ); + LogPart meshGenerationLog( "Mesh Generation", MpiWrapper::commRank() == 0 ); meshGenerationLog.begin(); generateMesh(); meshGenerationLog.end(); @@ -187,7 +187,7 @@ void ProblemManager::problemSetup() initialize(); - LogPart importFieldsLog( "Import fields", MpiWrapper::commRank() == 0 ); + LogPart importFieldsLog( "Import Fields", MpiWrapper::commRank() == 0 ); importFieldsLog.begin(); importFields(); importFieldsLog.end(); diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 963db5c6c3c..ef633336011 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -20,6 +20,8 @@ #include "common/DataLayouts.hpp" #include "common/TimingMacros.hpp" +#include "common/format/table/TableMpiComponents.hpp" +#include "mesh/WellElementSubRegion.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "SurfaceElementRegion.hpp" #include "constitutive/ConstitutiveManager.hpp" @@ -183,7 +185,7 @@ void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockM MeshLevel & meshLevel ) { NodeManager & nodeManager = meshLevel.getNodeManager(); - + int const rankId = MpiWrapper::commRank(); // get the offsets to construct local-to-global maps for well nodes and elements nodeManager.setMaxGlobalIndex(); globalIndex const nodeOffsetGlobal = nodeManager.maxGlobalIndex() + 1; @@ -229,6 +231,70 @@ void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockM } ); + forElementRegions< WellElementRegion >( [&]( WellElementRegion const & wellRegion ){ + WellElementSubRegion const & + wellSubRegion = wellRegion.getSubRegion< WellElementSubRegion >( wellRegion.getSubRegionName() ); + + PerforationData const * wellSubRegionPerforationData= wellSubRegion.getPerforationData(); + arrayView2d< const real64 > wsrPerfLocation = wellSubRegionPerforationData->getLocation(); + TableData localPerfoData; + for( globalIndex iperfLocal = 0; iperfLocal < wellSubRegionPerforationData->getNumPerforationsGlobal(); ++iperfLocal ) + { + integer const cellId = wellSubRegionPerforationData->getReservoirElementGlobalIndex()[iperfLocal]; + arrayView1d< globalIndex const > const globalIperf = wellSubRegionPerforationData->localToGlobalMap(); + + array1d< integer > localCoords; + if( cellId != -1 ) + { + auto const & meshElems = wellSubRegionPerforationData->getMeshElements(); + localIndex const targetRegionIndex = meshElems.m_toElementRegion[iperfLocal]; + localIndex const targetSubRegionIndex = meshElems.m_toElementSubRegion[iperfLocal]; + + ElementRegionBase const & region = + meshLevel.getElemManager().getRegion< ElementRegionBase >( targetRegionIndex ); + + ElementSubRegionBase const & subRegion = region.getSubRegion< ElementSubRegionBase >( targetSubRegionIndex ); + integer const globalWellElemIndices = wellSubRegion.getGlobalWellElementIndex()[iperfLocal]; + localCoords.emplace_back( wsrPerfLocation[iperfLocal][0] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][1] ); + localCoords.emplace_back( wsrPerfLocation[iperfLocal][2] ); + localPerfoData.addRow( globalIperf[iperfLocal], globalWellElemIndices, localCoords, + region.getName(), subRegion.getName(), cellId, rankId ); + } + } + + integer perfoDetected = MpiWrapper::max( localPerfoData.getCellsData().size() ) > 0; + + if( perfoDetected ) + { + TableLayout const layoutPerforation ( GEOS_FMT( "Well '{}' Perforation Table", + wellRegion.getWellGeneratorName()), + { + "Perforation", "Well element", "Coordinates", + "Cell region", "Cell sub-region", "Cell ID", "Rank" + } ); + + TableMpiLayout mpiLayout; + TableTextMpiFormatter formatter = TableTextMpiFormatter( layoutPerforation, mpiLayout ); + + formatter.setSortingFunc( + []( std::vector< TableData::CellData > const & row1, + std::vector< TableData::CellData > const & row2 ) { + return tableDataSorting::positiveNumberStringComp( row1[1].value, row2[1].value ); + } ); + + std::ostringstream outputStream; + formatter.toStream( outputStream, localPerfoData ); + + if( rankId == 0 ) + { + TableTextFormatter const globalFormatter( layoutPerforation ); + GEOS_LOG( outputStream.str()); + } + } + + } ); + // communicate to rebuild global node info since we modified global ordering nodeManager.setMaxGlobalIndex(); } diff --git a/src/coreComponents/mesh/ElementType.hpp b/src/coreComponents/mesh/ElementType.hpp index 76df6db0754..056cae6f010 100644 --- a/src/coreComponents/mesh/ElementType.hpp +++ b/src/coreComponents/mesh/ElementType.hpp @@ -21,6 +21,7 @@ #define GEOS_MESH_ELEMENTTYPE_HPP #include "common/format/EnumStrings.hpp" +#include "common/logger/Logger.hpp" namespace geos { diff --git a/src/coreComponents/mesh/PerforationFields.hpp b/src/coreComponents/mesh/PerforationFields.hpp index d2490470bbc..b13297d6fa1 100644 --- a/src/coreComponents/mesh/PerforationFields.hpp +++ b/src/coreComponents/mesh/PerforationFields.hpp @@ -61,7 +61,7 @@ DECLARE_FIELD( reservoirElementIndex, DECLARE_FIELD( reservoirElementGlobalIndex, "reservoirElementGlobalIndex", array1d< globalIndex >, - 0, + -1, NOPLOT, WRITE_AND_READ, "For each perforation, global element index of the perforated element" ); diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp index ee3118133a8..3255384b01e 100644 --- a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp @@ -141,7 +141,6 @@ void WellGeneratorBase::generateWellGeometry( ) if( isLogLevelActive< logInfo::GenerateWell >( this->getLogLevel() ) && MpiWrapper::commRank() == 0 ) { logInternalWell(); - logPerforationTable(); } } @@ -569,18 +568,4 @@ void WellGeneratorBase::logInternalWell() const GEOS_LOG_RANK_0( wellFormatter.toString( wellData )); } -void WellGeneratorBase::logPerforationTable() const -{ - TableData dataPerforation; - for( globalIndex iperf = 0; iperf < m_numPerforations; ++iperf ) - { - dataPerforation.addRow( iperf, m_perfCoords[iperf], m_perfElemId[iperf] ); - } - - TableLayout const layoutPerforation ( GEOS_FMT( "Well '{}' Perforation Table", getName()), - { "Perforation no.", "Coordinates", "Well element no." } ); - TableTextFormatter const logPerforation( layoutPerforation ); - GEOS_LOG_RANK_0( logPerforation.toString( dataPerforation )); -} - } diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp index e9a2dd81046..0367b2f0109 100644 --- a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp @@ -306,7 +306,6 @@ class WellGeneratorBase : public MeshComponentBase /// @cond DO_NOT_DOCUMENT void logInternalWell() const; - void logPerforationTable() const; /// @endcond /// Global number of perforations diff --git a/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp b/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp index 942fa1ed041..8a2575e1cb3 100644 --- a/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp @@ -19,6 +19,7 @@ #include "common/MpiWrapper.hpp" #include "codingUtilities/RTTypes.hpp" #include "common/format/EnumStrings.hpp" +#include "common/logger/Logger.hpp" #include "dataRepository/Group.hpp" #include "physicsSolvers/LogLevelsInfo.hpp" #include "common/format/LogPart.hpp" @@ -374,10 +375,13 @@ void PhysicsSolverBase::logEndOfCycleInformation( integer const cycleNumber, integer const numOfSubSteps, stdVector< real64 > const & subStepDts ) const { - LogPart logpart( "TIMESTEP", MpiWrapper::commRank() == 0 ); + LogPart logpart( "Time step", MpiWrapper::commRank() == 0 ); logpart.addEndDescription( "- Cycle ", cycleNumber ); logpart.addEndDescription( "- N substeps ", numOfSubSteps ); - + if( MpiWrapper::commRank() == 1 ) + { + GEOS_WARNING( "labla" ); + } std::stringstream logMessage; for( integer i = 0; i < numOfSubSteps; ++i ) { @@ -387,10 +391,16 @@ void PhysicsSolverBase::logEndOfCycleInformation( integer const cycleNumber, } logMessage << subStepDts[i] << " " << units::getSymbol( units::Unit::Time ); } - + if( MpiWrapper::commRank() != 1 ) + { + GEOS_WARNING( "labla" ); + } if( logMessage.rdbuf()->in_avail() == 0 ) logMessage << "/"; - + if( MpiWrapper::commRank() ==3 ) + { + GEOS_WARNING( "labla" ); + } logpart.addEndDescription( "- substep dts ", logMessage.str() ); logpart.end(); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp index 3d9366ef3b7..d1cd87b1976 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SolutionCheckHelpers.cpp @@ -102,7 +102,7 @@ void ElementsReporterOutput::outputTooLowValues( string_view linesPrefix, } } - TableTextMpiOutput const formatter = TableTextMpiOutput( layout, mpiLayout ); + TableTextMpiFormatter const formatter = TableTextMpiFormatter( layout, mpiLayout ); formatter.toStream( std::cout, data ); GEOS_LOG_RANK_0( '\n' ); }