diff --git a/src/sst/core/output.cc b/src/sst/core/output.cc index e37e37098..7aaafe7ef 100644 --- a/src/sst/core/output.cc +++ b/src/sst/core/output.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -523,43 +524,54 @@ thread_local std::vector TraceFunction::indent_array(100, ' '); thread_local int TraceFunction::trace_level = 0; TraceFunction::TraceFunction(uint32_t line, const char* file, const char* func, bool print_sim_info, bool activate) : - line(line), - file(file), - function(func), - indent_length(2), - active(activate) + line_(line), + file_(file), + function_(func), + indent_length_(2), + active_(activate && global_active_) { - if ( !active ) return; + if ( !active_ ) return; if ( print_sim_info ) { - RankInfo ri = Simulation_impl::getSimulation()->getNumRanks(); - if ( ri.rank > 1 || ri.thread > 1 ) { output_obj.init("@R, @I (@t): " /*prefix*/, 0, 0, Output::STDOUT); } + Simulation_impl* sim = nullptr; + try { + sim = Simulation_impl::getSimulation(); + } + catch ( std::out_of_range& e ) { + // do nothing + } + + if ( sim ) { + RankInfo ri = sim->getNumRanks(); + if ( ri.rank > 1 || ri.thread > 1 ) { output_obj_.init("@x (@t): " /*prefix*/, 0, 0, Output::STDOUT); } + else { + output_obj_.init("(@t): ", 0, 0, Output::STDOUT); + } + } else { - output_obj.init("(@t): ", 0, 0, Output::STDOUT); + output_obj_.init("" /*prefix*/, 0, 0, Output::STDOUT); } - // rank = Simulation_impl::getSimulation()->getRank().rank; - // thread = Simulation_impl::getSimulation()->getRank().thread; } else { - output_obj.init("" /*prefix*/, 0, 0, Output::STDOUT); + output_obj_.init("" /*prefix*/, 0, 0, Output::STDOUT); } // Set up the indent - int indent = trace_level * indent_length; + int indent = trace_level * indent_length_; indent_array[indent] = '\0'; - output_obj.output(line, file, func, "%s%s enter function\n", indent_array.data(), function.c_str()); - indent_array[indent] = ' '; + output_obj_.output(line_, file, func, "%s%s enter function\n", indent_array.data(), function_.c_str()); + indent_array[indent] = indent_marker_; fflush(stdout); trace_level++; } TraceFunction::~TraceFunction() { - if ( !active ) return; + if ( !active_ ) return; trace_level--; - int indent = trace_level * indent_length; + int indent = trace_level * indent_length_; indent_array[indent] = '\0'; - output_obj.output( - line, file.c_str(), function.c_str(), "%s%s exit function\n", indent_array.data(), function.c_str()); + output_obj_.output( + line_, file_.c_str(), function_.c_str(), "%s%s exit function\n", indent_array.data(), function_.c_str()); indent_array[indent] = ' '; fflush(stdout); } @@ -567,20 +579,126 @@ TraceFunction::~TraceFunction() void TraceFunction::output(const char* format, ...) const { - if ( !active ) return; + if ( !active_ ) return; // Need to add the indent - char buf[200]; + char* buf = new char[200]; - int indent = trace_level * indent_length; + int indent = trace_level * indent_length_; indent_array[indent] = '\0'; - snprintf(buf, 200, "%s%s", indent_array.data(), format); + std::string message(indent_array.data()); + + va_list args; + va_start(args, format); + size_t n = vsnprintf(buf, 200, format, args); + va_end(args); + + if ( n >= 200 ) { + // Generated string longer than buffer + delete[] buf; + buf = new char[n + 1]; + va_start(args, format); + vsnprintf(buf, n + 1, format, args); + va_end(args); + } + + // Look for \n and print each line individually. We do this so we + // can put the correct indent in and so that the prefix prints + // correctly. + size_t start_index = 0; + for ( size_t i = 0; i < n - 1; ++i ) { + if ( buf[i] == '\n' ) { + // Terminate string here, then change it back after printing + buf[i] = '\0'; + output_obj_.outputprintf( + line_, file_.c_str(), function_.c_str(), "%s%s\n", indent_array.data(), &buf[start_index]); + buf[i] = '\n'; + start_index = i + 1; + } + } + + // Print the rest of the string + output_obj_.outputprintf(line_, file_.c_str(), function_.c_str(), "%s%s", indent_array.data(), &buf[start_index]); + + delete[] buf; + indent_array[indent] = ' '; - va_list arg; - va_start(arg, format); - output_obj.outputprintf(line, file.c_str(), function.c_str(), buf, arg); - va_end(arg); + // output_obj_.outputprintf(line_, file_.c_str(), function_.c_str(), "%s", message.c_str()); + // Since this class is for debug, force a flush after every output fflush(stdout); } +// void +// TraceFunction::output(const char* format, ...) const +// { +// if ( !active_ ) return; +// // Need to add the indent +// char* buf = new char[200]; + +// int indent = trace_level * indent_length_; +// indent_array[indent] = '\0'; +// std::string message(indent_array.data()); + +// va_list args; +// va_start(args, format); +// size_t n = vsnprintf(buf, 200, format, args); +// va_end(args); + +// if ( n >= 200 ) { +// // Generated string longer than buffer +// delete[] buf; +// buf = new char[n + 1]; +// va_start(args, format); +// vsnprintf(buf, n+1, format, args); +// va_end(args); +// } + +// // Replace all \n's with \n + indent_array to indent any new lines +// // in the string (unless the \n is the last character in the +// // string) +// size_t start_index = 0; +// for ( size_t i = 0; i < n - 1; ++i ) { +// if ( buf[i] == '\n' ) { +// message.append(&buf[start_index], i - start_index + 1); +// message.append(indent_array.data()); +// start_index = i + 1; +// } +// } +// message.append(&buf[start_index], n - start_index); +// delete[] buf; + +// indent_array[indent] = ' '; + +// output_obj_.outputprintf(line_, file_.c_str(), function_.c_str(), "%s", message.c_str()); +// fflush(stdout); +// } + + +// Functions to check for proper environment variable to turn on output +// for TraceFunction +bool +is_trace_function_active() +{ + const char* var = getenv("SST_TRACEFUNCTION_ACTIVATE"); + if ( var ) { return true; } + else { + return false; + } +} + +char +get_indent_marker() +{ + const char* var = getenv("SST_TRACEFUNCTION_INDENT_MARKER"); + if ( var ) { + if ( strlen(var) > 0 ) + return var[0]; + else + return '|'; + } + return ' '; +} + +bool TraceFunction::global_active_ = is_trace_function_active(); +char TraceFunction::indent_marker_ = get_indent_marker(); } // namespace SST diff --git a/src/sst/core/output.h b/src/sst/core/output.h index 551027ea6..1ba42aeb0 100644 --- a/src/sst/core/output.h +++ b/src/sst/core/output.h @@ -495,6 +495,24 @@ class Output uint32_t line, const std::string& file, const std::string& func, const char* format, va_list arg) const; void outputprintf(const char* format, va_list arg) const; + // Versions of outputprintf that takes variable arguments instead of va_list + inline void + outputprintf(uint32_t line, const std::string& file, const std::string& func, const char* format, ...) const + { + va_list args; + va_start(args, format); + outputprintf(line, file, func, format, args); + va_end(args); + } + + inline void outputprintf(const char* format, ...) const + { + va_list(args); + va_start(args, format); + outputprintf(format, args); + va_end(args); + } + friend int ::main(int argc, char** argv); static Output& setDefaultObject( const std::string& prefix, uint32_t verbose_level, uint32_t verbose_mask, output_location_t location, @@ -555,10 +573,32 @@ class Output static int m_mpiRank; }; -// Class to easily trace function enter and exit +/** + Class to easily trace function enter and exit. This class is for + temporary use during debugging only and is not part of the SST Core + stable API (i.e. the class can change at any time). + + NOTE: Output for TraceFunction will only be turned on if the + SST_TRACEFUNCTION_ACTIVIATE envirnoment variable is set. + + You can also control whether or not an "indent marker" will be used + by setting SST_TRACEFUNCTION_INDENT_MARKER. If the environment + variable is defined but empty, a | will be used. If the variable + isn't empty, it will use the first character as the indent marker. + This will look like: + +static void class1::func1() enter function +| static void class1::func2() enter function +| static void class1::func2() exit function +static void class1::func1() exit function +static void class1::func1() enter function +| static void class1::func2() enter function +| static void class1::func2() exit function +static void class1::func1() exit function + +*/ class TraceFunction { - thread_local static int trace_level; thread_local static std::vector indent_array; @@ -566,8 +606,6 @@ class TraceFunction TraceFunction(uint32_t line, const char* file, const char* func, bool print_sim_info = true, bool activate = true); ~TraceFunction(); - Output& getOutput() { return output_obj; } - /** Output the message with formatting as specified by the format parameter. @param format Format string. All valid formats for printf are available. @param ... Arguments for format. @@ -575,14 +613,16 @@ class TraceFunction void output(const char* format, ...) const __attribute__((format(printf, 2, 3))); private: - Output output_obj; - uint32_t line; - std::string file; - std::string function; - // uint32_t rank; - // uint32_t thread; - int indent_length; - bool active; + Output output_obj_; + uint32_t line_; + std::string file_; + std::string function_; + int indent_length_; + bool active_; + + // Static to determine if TraceFunction should be active + static bool global_active_; + static char indent_marker_; }; } // namespace SST diff --git a/src/sst/core/testElements/Makefile.inc b/src/sst/core/testElements/Makefile.inc index f38ae6def..0af861106 100644 --- a/src/sst/core/testElements/Makefile.inc +++ b/src/sst/core/testElements/Makefile.inc @@ -25,6 +25,8 @@ libcoreTestElement_la_SOURCES = \ testElements/coreTest_Message.h \ testElements/coreTest_MessageGeneratorComponent.h \ testElements/coreTest_MessageGeneratorComponent.cc \ + testElements/coreTest_Output.h \ + testElements/coreTest_Output.cc \ testElements/coreTest_Serialization.h \ testElements/coreTest_Serialization.cc \ testElements/coreTest_SharedObjectComponent.h \ diff --git a/src/sst/core/testElements/coreTest_Output.cc b/src/sst/core/testElements/coreTest_Output.cc new file mode 100644 index 000000000..264a0590f --- /dev/null +++ b/src/sst/core/testElements/coreTest_Output.cc @@ -0,0 +1,62 @@ +// Copyright 2009-2024 NTESS. Under the terms +// of Contract DE-NA0003525 with NTESS, the U.S. +// Government retains certain rights in this software. +// +// Copyright (c) 2009-2024, NTESS +// All rights reserved. +// +// This file is part of the SST software package. For license +// information, see the LICENSE file in the top level directory of the +// distribution. + +//#include + +#include "sst_config.h" + +#include "sst/core/testElements/coreTest_Output.h" + +namespace SST { +namespace CoreTestSerialization { + + +void +testTraceFunction(int level = 0) +{ + TraceFunction trace(CALL_INFO_LONG); + trace.output("level = %d\n", level); + + if ( level == 0 ) { + // for first level, output a string long enough that it goes + // into the overflow case for the string generation in + // TraceFunction::output(). The current overflow length is 200. + int chars_in_line = 0; + std::string str; + for ( int i = 0; i < 250; ++i ) { + str += std::to_string(i % 10); + chars_in_line++; + if ( chars_in_line == 40 ) { + chars_in_line = 0; + str += '\n'; + } + } + trace.output("%s\n", str.c_str()); + testTraceFunction(level + 1); + } + else if ( level == 1 || level == 2 ) { + testTraceFunction(level + 1); + } +} + +coreTestOutput::coreTestOutput(ComponentId_t id, Params& params) : Component(id) +{ + Output& out = getSimulationOutput(); + + std::string test = params.find("test"); + if ( test == "" ) out.fatal(CALL_INFO_LONG, 1, "ERROR: Must specify test type\n"); + + if ( test == "TraceFunction" ) { testTraceFunction(); } +} + + +} // namespace CoreTestSerialization +} // namespace SST diff --git a/src/sst/core/testElements/coreTest_Output.h b/src/sst/core/testElements/coreTest_Output.h new file mode 100644 index 000000000..435c58e4b --- /dev/null +++ b/src/sst/core/testElements/coreTest_Output.h @@ -0,0 +1,58 @@ +// Copyright 2009-2024 NTESS. Under the terms +// of Contract DE-NA0003525 with NTESS, the U.S. +// Government retains certain rights in this software. +// +// Copyright (c) 2009-2024, NTESS +// All rights reserved. +// +// This file is part of the SST software package. For license +// information, see the LICENSE file in the top level directory of the +// distribution. + +#ifndef SST_CORE_CORETEST_OUTPUT_H +#define SST_CORE_CORETEST_OUTPUT_H + +#include "sst/core/component.h" + +namespace SST { +namespace CoreTestSerialization { + +class coreTestOutput : public SST::Component +{ +public: + // REGISTER THIS COMPONENT INTO THE ELEMENT LIBRARY + SST_ELI_REGISTER_COMPONENT( + coreTestOutput, + "coreTestElement", + "coreTestOutput", + SST_ELI_ELEMENT_VERSION(1,0,0), + "Test element for output objects", + COMPONENT_CATEGORY_UNCATEGORIZED + ) + + SST_ELI_DOCUMENT_PARAMS( + { "test", "Type of output test to perform", NULL} + ) + + // Optional since there is nothing to document + SST_ELI_DOCUMENT_STATISTICS( + ) + + // Optional since there is nothing to document + SST_ELI_DOCUMENT_PORTS( + ) + + // Optional since there is nothing to document + SST_ELI_DOCUMENT_SUBCOMPONENT_SLOTS( + ) + + coreTestOutput(SST::ComponentId_t id, SST::Params& params); + ~coreTestOutput() {} + +private: +}; + +} // namespace CoreTestSerialization +} // namespace SST + +#endif // SST_CORE_CORETEST_OUTPUT_H diff --git a/tests/Makefile.inc b/tests/Makefile.inc index 6f5651a9f..02899053b 100644 --- a/tests/Makefile.inc +++ b/tests/Makefile.inc @@ -8,6 +8,7 @@ EXTRA_DIST += \ tests/testsuite_default_Links.py \ tests/testsuite_default_MemPoolTest.py \ tests/testsuite_default_Module.py \ + tests/testsuite_default_Output.py \ tests/testsuite_default_ParamComponent.py \ tests/testsuite_default_PerfComponent.py \ tests/testsuite_default_RNGComponent.py \ @@ -29,6 +30,7 @@ EXTRA_DIST += \ tests/test_LookupTable.py \ tests/test_LookupTable2.py \ tests/test_MessageMesh.py \ + tests/test_Output.py \ tests/test_ParamComponent.py \ tests/test_ParallelLoad.py \ tests/test_RNGComponent.py \ @@ -55,6 +57,8 @@ EXTRA_DIST += \ tests/refFiles/test_DistribComponent_expon.out \ tests/refFiles/test_DistribComponent_gaussian.out \ tests/refFiles/test_LookupTableComponent.out \ + tests/refFiles/test_Output_TraceFunction.out \ + tests/refFiles/test_Output_TraceFunction_IndentMarker.out \ tests/refFiles/test_ParamComponent.out \ tests/refFiles/test_MessageGeneratorComponent.out \ tests/refFiles/test_MemPool_overflow.out \ diff --git a/tests/refFiles/test_Output_TraceFunction.out b/tests/refFiles/test_Output_TraceFunction.out new file mode 100644 index 000000000..224f7ac17 --- /dev/null +++ b/tests/refFiles/test_Output_TraceFunction.out @@ -0,0 +1,21 @@ +WARNING: Building component "Component0" with no links assigned. +(0): void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): level = 0 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789012345678901234567890123456789 +(0): 0123456789 +(0): void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): level = 1 +(0): void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): level = 2 +(0): void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): level = 3 +(0): void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): void SST::CoreTestSerialization::testTraceFunction(int) exit function +Simulation is complete, simulated time: 1 us diff --git a/tests/refFiles/test_Output_TraceFunction_IndentMarker.out b/tests/refFiles/test_Output_TraceFunction_IndentMarker.out new file mode 100644 index 000000000..0957ec665 --- /dev/null +++ b/tests/refFiles/test_Output_TraceFunction_IndentMarker.out @@ -0,0 +1,21 @@ +WARNING: Building component "Component0" with no links assigned. +(0): void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): | level = 0 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789012345678901234567890123456789 +(0): | 0123456789 +(0): | void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): | | level = 1 +(0): | | void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): | | | level = 2 +(0): | | | void SST::CoreTestSerialization::testTraceFunction(int) enter function +(0): | | | | level = 3 +(0): | | | void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): | | void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): | void SST::CoreTestSerialization::testTraceFunction(int) exit function +(0): void SST::CoreTestSerialization::testTraceFunction(int) exit function +Simulation is complete, simulated time: 1 us diff --git a/tests/test_Output.py b/tests/test_Output.py new file mode 100644 index 000000000..2904a7e09 --- /dev/null +++ b/tests/test_Output.py @@ -0,0 +1,26 @@ +# Copyright 2009-2024 NTESS. Under the terms +# of Contract DE-NA0003525 with NTESS, the U.S. +# Government retains certain rights in this software. +# +# Copyright (c) 2009-2024, NTESS +# All rights reserved. +# +# This file is part of the SST software package. For license +# information, see the LICENSE file in the top level directory of the +# distribution. +import sst +import sys + +sst.setProgramOption("stop-at", "1us"); + +ranks = sst.getMPIRankCount(); +threads = sst.getThreadCount(); + +test = sys.argv[1] + +num_components = ranks * threads + +for x in range(num_components): + comp = sst.Component("Component{0}".format(x), "coreTestElement.coreTestOutput") + comp.addParam("test", test) + diff --git a/tests/testsuite_default_Output.py b/tests/testsuite_default_Output.py new file mode 100644 index 000000000..1837ecbc8 --- /dev/null +++ b/tests/testsuite_default_Output.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2009-2024 NTESS. Under the terms +# of Contract DE-NA0003525 with NTESS, the U.S. +# Government retains certain rights in this software. +# +# Copyright (c) 2009-2024, NTESS +# All rights reserved. +# +# This file is part of the SST software package. For license +# information, see the LICENSE file in the top level directory of the +# distribution. + +import os +import filecmp + +from sst_unittest import * +from sst_unittest_support import * + +################################################################################ +# Code to support a single instance module initialize, must be called setUp method + +module_init = 0 +module_sema = threading.Semaphore() + +def initializeTestModule_SingleInstance(class_inst): + global module_init + global module_sema + + module_sema.acquire() + if module_init != 1: + # Put your single instance Init Code Here + module_init = 1 + module_sema.release() + +################################################################################ + +class testcase_Output(SSTTestCase): + + def initializeClass(self, testName): + super(type(self), self).initializeClass(testName) + # Put test based setup code here. it is called before testing starts + # NOTE: This method is called once for every test + + def setUp(self): + super(type(self), self).setUp() + initializeTestModule_SingleInstance(self) + # Put test based setup code here. it is called once before every test + + def tearDown(self): + # Put test based teardown code here. it is called once after every test + super(type(self), self).tearDown() + +##### + + def test_Output_TraceFunction(self): + os.unsetenv("SST_TRACEFUNCTION_INDENT_MARKER"); + self.tracefunction_test_template("TraceFunction") + + def test_Output_TraceFunction_IndentMarker(self): + os.environ["SST_TRACEFUNCTION_INDENT_MARKER"] = ""; + self.tracefunction_test_template("TraceFunction_IndentMarker") + +##### + def tracefunction_test_template(self, testtype): + + os.environ["SST_TRACEFUNCTION_ACTIVATE"] = ""; + + testsuitedir = self.get_testsuite_dir() + outdir = test_output_get_run_dir() + + sdlfile = "{0}/test_Output.py".format(testsuitedir) + reffile = "{0}/refFiles/test_Output_{1}.out".format(testsuitedir,testtype) + + outfile = "{0}/test_Output_{1}.out".format(outdir,testtype) + + # Always run the TraceFunction test, the only difference is + # how the environment variables are set + options = "--model-options=\"TraceFunction\"" + # Run the simulation + self.run_sst(sdlfile, outfile, other_args=options) + + # Check the results. We will need to do a separate test for + # each partition in the simulation. We do this by filtering + # all lines prefixed with rank info and only keeping the ones + # that match the partition we're looking at. + + filter_warning = StartsWithFilter("WARNING:") + filter_time = StartsWithFilter("Simulation is complete") + filters = [filter_warning, filter_time] + + # Need to do a check for each partition in the run + ranks = testing_check_get_num_ranks() + threads = testing_check_get_num_threads() + + if ( ranks == 1 and threads == 1 ): + cmp_result = testing_compare_filtered_diff("tracefunction", outfile, reffile, False, filters) + self.assertTrue(cmp_result, "Output/Compare file {0} does not match Reference File {1}".format(outfile, reffile)) + else: + cmp_result = True + tf_filter = TraceFunctionFilter("") # will set for each loop + filters.append(tf_filter) + for r in range(ranks): + for t in range(threads): + tf_filter.setPrefix("[{0}:{1}] ".format(r,t)) + cmp_result &= testing_compare_filtered_diff("tracefunction", outfile, reffile, False, filters) + + self.assertTrue(cmp_result, "Output/Compare file {0} does not match Reference File {1}".format(outfile, reffile)) + + +class TraceFunctionFilter(LineFilter): + def __init__(self, prefix): + self._prefix = prefix; + + def filter(self, line): + """ + If line starts with a paren, then it will keep lines where + the line starts with prefix and discard lines where that + don't. Kept lines will have the prefix removed. If it doesn't + start with a paren, then it is passed through unchanged. + + Args: + line (str): Line to check + + Returns: + filtered line or None as described above + + """ + # Just return lines that don't start with [, they aren't from + # TraceFunction + if not line.startswith("["): + return line + + # Throw away lines that start with [, but don't match the + # prefix + if not line.startswith(self._prefix): + return None + + # Cut the prefix out of matching lines and return + return line[len(self._prefix):] + + def setPrefix(self, prefix): + self._prefix = prefix