From 5d51df41cb80a9140b4e56cf26e9d6b0ca10f5cb Mon Sep 17 00:00:00 2001 From: Scott Hemmert Date: Thu, 20 Jun 2024 14:53:14 -0600 Subject: [PATCH] Modified TraceFunction object to require that an environment variable be set to enable output. This makes it possible to leave the TraceFunction calls in while developing, but still be able to run and pass testing. Update TraceFunction for better formatting. --- src/sst/core/output.cc | 172 +++++++++++++++--- src/sst/core/output.h | 64 +++++-- src/sst/core/testElements/Makefile.inc | 2 + src/sst/core/testElements/coreTest_Output.cc | 62 +++++++ src/sst/core/testElements/coreTest_Output.h | 58 ++++++ tests/Makefile.inc | 4 + tests/refFiles/test_Output_TraceFunction.out | 21 +++ ...test_Output_TraceFunction_IndentMarker.out | 21 +++ tests/test_Output.py | 26 +++ tests/testsuite_default_Output.py | 143 +++++++++++++++ 10 files changed, 534 insertions(+), 39 deletions(-) create mode 100644 src/sst/core/testElements/coreTest_Output.cc create mode 100644 src/sst/core/testElements/coreTest_Output.h create mode 100644 tests/refFiles/test_Output_TraceFunction.out create mode 100644 tests/refFiles/test_Output_TraceFunction_IndentMarker.out create mode 100644 tests/test_Output.py create mode 100644 tests/testsuite_default_Output.py 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