diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index 0e37ba56703..502f4727592 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -38,10 +38,14 @@ 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 + logger/MsgType.hpp MpiWrapper.hpp Path.hpp Span.hpp @@ -71,6 +75,7 @@ endif( ) # Specify all sources # set( common_sources + format/EnumStrings.cpp format/table/TableLayout.cpp format/table/TableFormatter.cpp format/table/TableData.cpp @@ -80,6 +85,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/format/EnumStrings.cpp b/src/coreComponents/common/format/EnumStrings.cpp new file mode 100644 index 00000000000..1828d012268 --- /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 const & typeName, + std::size_t size ) +{ + GEOS_THROW( "Invalid value " << index << " of type " << typeName<< ". Valid range is 0.." << size - 1, + InputError ); +} + +void internal::EnumErrorMessageFromString( string const & s, + string const & typeName, + string const & 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 f5b37de1df1..ac315476a1e 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,13 @@ namespace geos namespace internal { + +void EnumErrorMessageToString( size_t index, string const & typeName, std::size_t size ); + +void EnumErrorMessageFromString( string const & s, + string const & typeName, + string const & concat ); + /** * @brief Simple compile-time variadic function that counts its arguments. * @tparam ARGS variadic pack of argument types @@ -152,9 +158,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 ), - "Invalid value " << index << " of type " << getEnumTypeNameString( enum_type{} ) << ". Valid range is 0.." << size - 1, - InputError ); + if( index >= LvArray::integerConversion< base_type >( size )) + { + internal::EnumErrorMessageToString( index, getEnumTypeNameString( enum_type{} ), size - 1 ); + } return strings[ index ]; } @@ -167,9 +174,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 ), - "Invalid value '" << s << "' of type " << getEnumTypeNameString( enum_type{} ) << ". Valid options are: " << 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..047eae13d9a 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 const & 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( 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..2367cf5edb8 100644 --- a/src/coreComponents/common/format/LogPart.hpp +++ b/src/coreComponents/common/format/LogPart.hpp @@ -19,9 +19,7 @@ #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/EnumStrings.hpp" namespace geos { @@ -39,7 +37,7 @@ class LogPart * @param logPartTitle The title who will be used for top and bottom LogPart * @param enableOutput Boolean to activate or not csv output */ - LogPart( string_view logPartTitle, bool enableOutput ); + LogPart( string const & logPartTitle, bool enableOutput ); /** * @brief Add a description to the top LogPart diff --git a/src/coreComponents/common/format/table/TableData.hpp b/src/coreComponents/common/format/table/TableData.hpp index 22135105ecf..49ef1588516 100644 --- a/src/coreComponents/common/format/table/TableData.hpp +++ b/src/coreComponents/common/format/table/TableData.hpp @@ -92,7 +92,7 @@ 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< TableData::CellData > const & row ); /** * @brief Add a line separator to the table diff --git a/src/coreComponents/common/format/table/TableLayout.cpp b/src/coreComponents/common/format/table/TableLayout.cpp index 55446491cd2..659aa84dddf 100644 --- a/src/coreComponents/common/format/table/TableLayout.cpp +++ b/src/coreComponents/common/format/table/TableLayout.cpp @@ -40,15 +40,17 @@ void TableLayout::addColumns( stdVector< TableLayout::Column > const & columns ) } } -void TableLayout::addColumn( string_view columnName ) +TableLayout::Column & TableLayout::addColumn( string_view columnName ) { TableLayout::Column column = TableLayout::Column().setName( columnName ); m_tableColumns.emplace_back( column ); + return m_tableColumns.back(); } -void TableLayout::addColumn( TableLayout::Column const & column ) +TableLayout::Column & TableLayout::addColumn( TableLayout::Column const & column ) { m_tableColumns.emplace_back( column ); + return m_tableColumns.back(); } TableLayout & TableLayout::setTitle( string_view title ) diff --git a/src/coreComponents/common/format/table/TableLayout.hpp b/src/coreComponents/common/format/table/TableLayout.hpp index 4ef156ad760..0f1f85a1419 100644 --- a/src/coreComponents/common/format/table/TableLayout.hpp +++ b/src/coreComponents/common/format/table/TableLayout.hpp @@ -697,13 +697,13 @@ class TableLayout * @brief Create and add a column to the columns vector given a string * @param columnName The column name */ - void addColumn( string_view columnName ); + TableLayout::Column & addColumn( string_view columnName ); /** * @brief Create and add a column to the columns vector given a Column * @param column Vector containing addition information on the column */ - void addColumn( TableLayout::Column const & column ); + TableLayout::Column & addColumn( TableLayout::Column const & column ); protected: diff --git a/src/coreComponents/common/initializeEnvironment.cpp b/src/coreComponents/common/initializeEnvironment.cpp index 4985d886981..d6d2632f322 100644 --- a/src/coreComponents/common/initializeEnvironment.cpp +++ b/src/coreComponents/common/initializeEnvironment.cpp @@ -332,6 +332,8 @@ void setupEnvironment( int argc, char * argv[] ) void cleanupEnvironment() { MemoryLogging::getInstance().memoryStatsReport(); + ErrorLogger::global().getLoggerReportData().diagnosticStatsReport(); + 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..3f14e6aed0c --- /dev/null +++ b/src/coreComponents/common/logger/DiagnosticMessage.hpp @@ -0,0 +1,254 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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/logger/MsgType.hpp" + +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 + * @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 ); +}; + + +/** + * @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 the dectection 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 ossStackTrace stack trace information to add + * @return Reference to the current instance for method chaining. + */ + DiagnosticMsgBuilder & addCallStackInfo( std::string_view ossStackTrace ); + + /** + * @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; +}; +} diff --git a/src/coreComponents/common/logger/ErrorHandling.cpp b/src/coreComponents/common/logger/ErrorHandling.cpp index 6881e11259a..a703eb2cd8b 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 @@ -435,8 +430,12 @@ void ErrorLogger::writeToYamlStream( DiagnosticMsg & errMsg ) } } + + void ErrorLogger::flushErrorMsg( DiagnosticMsg & errMsg ) { + loggerMsgReportData.notifyMsg( getCurrentLogPart(), + errMsg ); writeToLogStream( errMsg ); if( isOutputFileEnabled() ) { @@ -446,6 +445,9 @@ void ErrorLogger::flushErrorMsg( DiagnosticMsg & errMsg ) void ErrorLogger::flushCurrentExceptionMessage() { + loggerMsgReportData.notifyMsg( getCurrentLogPart(), + 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..e945f372f44 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 logPart The new log part to set. + */ + void setCurrentLogPart( string const & currentLogPart ) + { m_currentLogPart = currentLogPart; } + private: /// The error constructed via exceptions DiagnosticMsg m_getCurrentExceptionMsg; + + 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; + + 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/GeosExceptions.hpp b/src/coreComponents/common/logger/GeosExceptions.hpp index f3934bebbc3..95a9d4c65f1 100644 --- a/src/coreComponents/common/logger/GeosExceptions.hpp +++ b/src/coreComponents/common/logger/GeosExceptions.hpp @@ -96,8 +96,7 @@ struct LogicError : public geos::Exception }; /** - * @brief Exception class used to report domain errors. - * Generally, the domain of a mathematical function is the subset of values that it is defined for + * @brief Exception class used to report errors in user input. */ struct DomainError : public geos::Exception { @@ -149,7 +148,7 @@ struct InputError : public geos::Exception }; /** - * @brief Exception class used to report errors related to the simulation + * @brief Exception class used to report errors in user input. */ struct SimulationError : public geos::Exception { diff --git a/src/coreComponents/common/logger/LogHistory.cpp b/src/coreComponents/common/logger/LogHistory.cpp new file mode 100644 index 00000000000..db4e7be9c9c --- /dev/null +++ b/src/coreComponents/common/logger/LogHistory.cpp @@ -0,0 +1,233 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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/LogPart.hpp" +#include "common/format/table/TableData.hpp" +#include "common/format/table/TableLayout.hpp" +#include "common/format/table/TableTypes.hpp" +#include "common/logger/MsgType.hpp" +#include "common/MpiWrapper.hpp" +#include "dataRepository/BufferOps.hpp" +#include "dataRepository/BufferOps_inline.hpp" +#include +#include +#include +#include + +namespace geos +{ + +std::string extractAfterLastOccurrence( string const & str, char delimiter ) +{ + size_t pos = str.find_last_of( delimiter ); + + if( pos == std::string::npos ) + { + return ""; + } + + return str.substr( pos + 1 ); +} + +void LogHistory::notifyMsg( string_view logPartName, DiagnosticMsg const & msgType ) +{ + string fileName = extractAfterLastOccurrence( msgType.m_file, '/' ); + integer lineCount = msgType.m_line; + + auto & stats = m_errorHistory.get_inserted( std::make_tuple( string( logPartName ), msgType.m_type, + fileName, lineCount )); + stats.count++; +} + +std::pair< stdVector< buffer_unit_type >, stdVector< integer > > +gatherTuplesRank0( buffer_unit_type * bufferToSend, localIndex packedSize ) +{ + integer const numRanks = MpiWrapper::commSize(); + integer const numValues = packedSize; + + // Allows to know how much data each rank will send + stdVector< integer > recvCounts; + // Displacments vector for global alloc + stdVector< integer > displs; + stdVector< buffer_unit_type > globalAllocations; + + if( MpiWrapper::commRank() == 0 ) + { + recvCounts.resize( numRanks ); + displs.resize( numRanks ); + } + + + MpiWrapper::gather( &numValues, 1, recvCounts.data(), 1, 0 ); + + if( MpiWrapper::commRank() == 0 ) + { + integer totalSize = 0; + for( integer i = 0; i < numRanks; ++i ) + { + displs[i] = totalSize; + totalSize += recvCounts[i]; + } + globalAllocations.resize( totalSize ); + } + + MpiWrapper::gatherv( bufferToSend, + numValues, + globalAllocations.data(), + recvCounts.data(), + displs.data(), + 0 ); + + return {globalAllocations, recvCounts}; + + +} + +void LogHistory::diagnosticStatsReport() +{ + LogHistory & history = ErrorLogger::global().getLoggerReportData(); + stdVector< buffer_unit_type > gTuple( 0 ); + integer totalSize = 0; + //1 - dry run for vector size + for( auto const & [key, value] : getDiagnosticHistory() ) + { + auto const & [logPartType, msgType, filename, lineCount] = key; + + buffer_unit_type * dummy = nullptr; + localIndex entrySize = bufferOps::Pack< false >( dummy, logPartType ) + + bufferOps::Pack< false >( dummy, msgType ) + + bufferOps::Pack< false >( dummy, filename ) + + bufferOps::Pack< false >( dummy, lineCount ); + + totalSize += entrySize; + } + gTuple.resize( totalSize ); + + //2 - Packing + buffer_unit_type * tupleBuffer = gTuple.data(); + for( auto const & [key, value] : getDiagnosticHistory() ) + { + auto const & [logPartType, msgType, filename, lineCount] = key; + + bufferOps::Pack< true >( tupleBuffer, logPartType ); + bufferOps::Pack< true >( tupleBuffer, msgType ); + bufferOps::Pack< true >( tupleBuffer, filename ); + bufferOps::Pack< true >( tupleBuffer, lineCount ); + } + + auto [tuplesPerIt, recvCounts] = gatherTuplesRank0( gTuple.data(), gTuple.size() ); + + //3 - Unpacking + if( MpiWrapper::commRank() == 0 ) + { + buffer_unit_type const * rankStart = tuplesPerIt.data(); + for( size_t idxRank = 0; idxRank < (size_t)MpiWrapper::commSize(); ++idxRank ) + { + integer byteFromThisRank = recvCounts[idxRank]; + if( byteFromThisRank != 0 ) + { + buffer_unit_type const * rankEnd= rankStart + byteFromThisRank; + while( rankStart < rankEnd ) + { + string logPartUnpacked; + MsgType MsgTypeUnpacked; + string fileNameUnpacked; + integer lineCountUnpacked; + bufferOps::Unpack( rankStart, logPartUnpacked ); + bufferOps::Unpack( rankStart, MsgTypeUnpacked ); + bufferOps::Unpack( rankStart, fileNameUnpacked ); + bufferOps::Unpack( rankStart, lineCountUnpacked ); + history.insertBlanckReport( logPartUnpacked, MsgTypeUnpacked, fileNameUnpacked, lineCountUnpacked ); + } + } + } + } + + + if( MpiWrapper::commRank() == 0 ) + { + TableTextFormatter tableReportFormatter; + GEOS_LOG( tableReportFormatter.toString< LogHistory >( GEOS_GLOBAL_LOGGER.getLoggerReportData())); + } +} + +void LogHistory::insertBlanckReport( string_view logPartName, MsgType msgType, string const & fileName, integer lineCount ) +{ + m_errorHistory.get_inserted( std::make_tuple( string( logPartName ), msgType, fileName, lineCount )); +} + +template<> +string TableTextFormatter::toString< LogHistory >( LogHistory const & messageCounts ) const +{ + TableLayout tableLayout; + tableLayout.addColumn( "Types" ); + + for( size_t msgTypeIdx = (size_t) MsgType::Error; msgTypeIdx != (size_t)MsgType::Undefined; msgTypeIdx++ ) + { + tableLayout.addColumn( EnumStrings< MsgType >::toString( (MsgType) msgTypeIdx ) ); + } + + stdMap< std::pair< string, MsgType >, int > countPerPartAndType; + using CellRow = stdArray< TableData::CellData, (size_t) MsgType::Undefined >; + CellRow emptyCellRow; + emptyCellRow.fill( TableData::CellData{CellType::Value, "0"} ); + stdMap< string, CellRow > rowByPart; + + + for( const auto & [tupleKey, msgTypes] : messageCounts.getDiagnosticHistory()) + { + auto part = std::get< 0 >( tupleKey ); + auto type = std::get< 1 >( tupleKey ); + + countPerPartAndType.get_inserted( std::make_pair( part, type ))++; + + if( rowByPart.find( part ) == rowByPart.end()) + rowByPart.get_inserted( part ) = emptyCellRow; + } + + for( auto & [keyPair, count] : countPerPartAndType ) + { + auto part = std::get< 0 >( keyPair ); + auto type = std::get< 1 >( keyPair ); + rowByPart.get_inserted( part ).at((size_t)type ).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..db8b0a59e6a --- /dev/null +++ b/src/coreComponents/common/logger/LogHistory.hpp @@ -0,0 +1,137 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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/StdContainerWrappers.hpp" +#include "common/format/LogPart.hpp" +#include "common/format/table/TableFormatter.hpp" +#include "DiagnosticMessage.hpp" +#include "common/logger/MsgType.hpp" +#include + + +namespace geos +{ + +/** + * @brief Statistics for a diagnostic message at a specific location + */ +struct MsgStatistics +{ + /// + integer count; +}; + + +/** + * @brief Keep track of all diagnostic message occured during the simulation + */ +class LogHistory +{ +public: + + /// Alias for the historical error unordered_map key + using HistoricalErrorUnorderedMapKey = std::tuple< string, MsgType, string, integer >; + + /** + * @brief Report a diagnostic message + * @param logPartName The logPart where the message occured + * @param diagMsg The diagnostic message to record + * @param threadCount + */ + void notifyMsg( string_view logPartName, DiagnosticMsg const & diagMsg ); + + /** + * @brief Display the error statistics to the log + */ + void diagnosticStatsReport(); + + /** + * @return The const historical error + */ + auto const & getDiagnosticHistory() const + { return m_errorHistory; } + + /** + * @brief Insert an element to the error history container if an equivalent key doesn't exist. + * @param logPartName The logPart where the error occured + * @param msgType The error message type + * @param locationKey The key identifying the error source location + */ + void insertBlanckReport( string_view logPartName, MsgType msgType, string const & fileName, integer lineCount ); + +private: + + /// @cond DO_NOT_DOCUMENT + struct LocationKeyHash + { + + std::size_t operator()( HistoricalErrorUnorderedMapKey const & key ) const noexcept + { + auto const & [logPartType, msgType, filename, lineCount] = key; + + std::size_t h1 = std::hash< std::string >{} (logPartType); + std::size_t h2 = std::hash< MsgType >{} (msgType); + std::string str; + str.assign( filename ); + std::size_t h3 = std::hash< std::string >{} (str); + std::size_t h4 = std::hash< int >{} (lineCount); + + return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3); + } + + }; + /// @endcond + + /// @cond DO_NOT_DOCUMENT + struct LocationKeyEqual + { + + bool operator()( HistoricalErrorUnorderedMapKey const & lhs, + HistoricalErrorUnorderedMapKey const & rhs ) const + { + return std::get< 0 >( lhs ) == std::get< 0 >( rhs ) && + std::get< 1 >( lhs ) == std::get< 1 >( rhs ) && + std::get< 2 >( lhs ) == std::get< 2 >( rhs ) && + std::get< 2 >( lhs ) == std::get< 2 >( rhs ); + } + }; + /// @endcond + + /** + * @brief Historical error happened during the simulation + */ + stdUnorderedMap< HistoricalErrorUnorderedMapKey, + MsgStatistics, + LocationKeyHash, LocationKeyEqual > m_errorHistory; +}; + +/** + * @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/logger/MsgType.hpp b/src/coreComponents/common/logger/MsgType.hpp new file mode 100644 index 00000000000..41c30d673f3 --- /dev/null +++ b/src/coreComponents/common/logger/MsgType.hpp @@ -0,0 +1,51 @@ + /* + * ------------------------------------------------------------------------------------------------------------ + * 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 MsgType.hpp + */ + +#ifndef INITIALIZATION_MSG_TYPE_HPP +#define INITIALIZATION_MSG_TYPE_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" ); + +}; + +#endif 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/events/EventManager.cpp b/src/coreComponents/events/EventManager.cpp index 5e24698b008..f27d3e6378e 100644 --- a/src/coreComponents/events/EventManager.cpp +++ b/src/coreComponents/events/EventManager.cpp @@ -176,7 +176,8 @@ 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 df30b457b45..3b9e7fc2a33 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/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index ca19992b0aa..27254af2d07 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -13,6 +13,8 @@ * ------------------------------------------------------------------------------------------------------------ */ +#include "common/MpiWrapper.hpp" +#include "common/format/EnumStrings.hpp" #define GEOS_DISPATCH_VEM /// enables VEM in FiniteElementDispatch // Source includes @@ -172,22 +174,24 @@ void ProblemManager::problemSetup() postInputInitializationRecursive(); - LogPart meshGenerationLog( "Mesh generation", MpiWrapper::commRank() == 0 ); + LogPart meshGenerationLog( "MeshGeneration", MpiWrapper::commRank() == 0 ); meshGenerationLog.begin(); + generateMesh(); meshGenerationLog.end(); // initialize_postMeshGeneration(); - LogPart numericalMethodLog( "Numerical Methods", MpiWrapper::commRank() == 0 ); + LogPart numericalMethodLog( "NumericalMethods", MpiWrapper::commRank() == 0 ); numericalMethodLog.begin(); applyNumericalMethods(); + numericalMethodLog.end(); registerDataOnMeshRecursive( getDomainPartition().getMeshBodies() ); initialize(); - LogPart importFieldsLog( "Import fields", MpiWrapper::commRank() == 0 ); + LogPart importFieldsLog( "ImportFields", MpiWrapper::commRank() == 0 ); importFieldsLog.begin(); importFields(); importFieldsLog.end(); diff --git a/src/coreComponents/mesh/ElementType.hpp b/src/coreComponents/mesh/ElementType.hpp index 0ebb7ba5e03..161e22b8942 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/physicsSolvers/PhysicsSolverBase.cpp b/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp index 116c4268c14..f4518502d4b 100644 --- a/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/PhysicsSolverBase.cpp @@ -374,7 +374,8 @@ void PhysicsSolverBase::logEndOfCycleInformation( integer const cycleNumber, integer const numOfSubSteps, stdVector< real64 > const & subStepDts ) const { - LogPart logpart( "TIMESTEP", MpiWrapper::commRank() == 0 ); + LogPart logpart( "Timeestep", + MpiWrapper::commRank() == 0 ); logpart.addEndDescription( "- Cycle ", cycleNumber ); logpart.addEndDescription( "- N substeps ", numOfSubSteps ); diff --git a/src/pygeosx/pygeosx.cpp b/src/pygeosx/pygeosx.cpp index fff9d7aea9c..3941c74668a 100644 --- a/src/pygeosx/pygeosx.cpp +++ b/src/pygeosx/pygeosx.cpp @@ -119,7 +119,7 @@ static constexpr char const * initializeDocString = "--\n\n" "Initialize GEOSX, this must be the first module call.\n\n" "Should only be called once. All calls after the first will\n" - "raise a `RuntimeError`. To reinitialize GEOSX for a new problem,\n" + "raise a `geos::RuntimeError`. To reinitialize GEOSX for a new problem,\n" "use the `reinit` function.\n" "\n" "Parameters\n"