From 4cbcb1e1f61a9540629d9978f95e095c82dc9deb Mon Sep 17 00:00:00 2001 From: Scott Hemmert Date: Mon, 24 Jul 2023 16:23:25 -0600 Subject: [PATCH] Added the ability to have extended help for command line options using --help=option. Also added extended help for --enable-profiling. --- src/sst/core/bootsst.cc | 2 +- src/sst/core/bootsstinfo.cc | 2 +- src/sst/core/config.cc | 66 +++++++++++++++++--- src/sst/core/config.h | 2 +- src/sst/core/configBase.cc | 118 ++++++++++++++++++++++++++++------- src/sst/core/configBase.h | 69 ++++++++++++++------ src/sst/core/configShared.cc | 9 +-- src/sst/core/configShared.h | 6 +- src/sst/core/sstinfo.cc | 2 +- 9 files changed, 216 insertions(+), 60 deletions(-) diff --git a/src/sst/core/bootsst.cc b/src/sst/core/bootsst.cc index 369ed81c4..704c221b1 100644 --- a/src/sst/core/bootsst.cc +++ b/src/sst/core/bootsst.cc @@ -28,7 +28,7 @@ main(int argc, char* argv[]) // Create a ConfigShred object. This object won't print any error // messages about unknown command line options, that will be // deferred to the actual sstsim.x executable. - SST::ConfigShared cfg(true, true, true, true, true); + SST::ConfigShared cfg(true, true, true, true); // Make a copy of the argv array (shallow) char* argv_copy[argc + 1]; diff --git a/src/sst/core/bootsstinfo.cc b/src/sst/core/bootsstinfo.cc index 564a642f7..4482bf1f8 100644 --- a/src/sst/core/bootsstinfo.cc +++ b/src/sst/core/bootsstinfo.cc @@ -23,7 +23,7 @@ main(int argc, char* argv[]) // for the wrapper. We will suppress output so that it won't // report unknown options which are only parsed by the actual // sst-info executable. - SST::ConfigShared cfg(true, true, true, true, true); + SST::ConfigShared cfg(true, true, true, true); // Make a copy of the argv array (shallow) char* argv_copy[argc + 1]; diff --git a/src/sst/core/config.cc b/src/sst/core/config.cc index 00a24735e..4e97b3465 100644 --- a/src/sst/core/config.cc +++ b/src/sst/core/config.cc @@ -34,7 +34,14 @@ class ConfigHelper { public: // Print usage - static int printHelp(Config* cfg, const std::string& UNUSED(arg)) { return cfg->printUsage(); } + static int printUsage(Config* cfg, const std::string& UNUSED(arg)) { return cfg->printUsage(); } + + // Print usage + static int printHelp(Config* cfg, const std::string& arg) + { + if ( arg != "" ) return cfg->printExtHelp(arg); + return cfg->printUsage(); + } // Prints the SST version static int printVersion(Config* UNUSED(cfg), const std::string& UNUSED(arg)) @@ -355,6 +362,39 @@ class ConfigHelper return 0; } + static std::string getProfilingExtHelp() + { + std::string msg = "Profiling Points [EXPERIMENTAL]:\n\n"; + msg.append( + "NOTE: Profiling points are still in development and syntax for enabling profiling tools, as well as " + "available profiling points is subject to change. However, it is intended that profiling points " + "will continue to be supported into the future.\n\n"); + msg.append(" Profiling points are points in the code where a profiling tool can be instantiated. The " + "profiling tool allows you to collect various data about code segments. There are currently three " + "profiling points in SST core:\n"); + msg.append(" - clock: profiles calls to user registered clock handlers\n"); + msg.append(" - event: profiles calls to user registered event handlers set on Links\n"); + msg.append(" - sync: profiles calls into the SyncManager (only valid for parallel simulations)\n"); + msg.append("\n"); + msg.append(" The format for enabling profile point is a semicolon separated list where each item specifies " + "details for a given profiling tool using the following format:\n"); + msg.append(" name:type(params)[point]\n"); + msg.append(" name: name of tool to be shown in output\n"); + msg.append(" type: type of profiling tool in ELI format (lib.type)\n"); + msg.append(" params: optional parameters to pass to profiling tool, format is key=value,key=value...\n"); + msg.append(" point: profiling point to load the tool into\n"); + msg.append("\n"); + msg.append("Profiling tools can all be enabled in a single instance of --enable-profiling, or you can use " + "multiple instances of --enable-profiling can be used to enable more than one profiling tool. It " + "is also possible to attach more than one profiling tool to a given profiling point.\n"); + msg.append("\n"); + msg.append("Examples:\n"); + msg.append( + " --enable-profiling=\"events:sst.profile.handler.event.time.high_resolution(level=component)[event]\"\n"); + msg.append(" --enable-profiling=\"clocks:sst.profile.handler.clock.count(level=subcomponent)[clock]\"\n"); + msg.append(" --enable-profiling=sync:sst.profile.sync.time.steady[sync]\n"); + return msg; + } // Advanced options - debug @@ -464,8 +504,13 @@ Config::print() std::cout << "no_env_config = " << no_env_config_ << std::endl; } +static std::vector annotations = { + { 'S', + "Options annotated with 'S' can be set in the SDL file (input configuration file)\n - Note: Options set on the " + "command line take precedence over options set in the SDL file\n" } +}; -Config::Config(uint32_t num_ranks, bool first_rank) : ConfigShared(!first_rank, false) +Config::Config(uint32_t num_ranks, bool first_rank) : ConfigShared(!first_rank, annotations) { // Basic Options first_rank_ = first_rank; @@ -567,7 +612,10 @@ Config::insertOptions() using namespace std::placeholders; /* Informational options */ DEF_SECTION_HEADING("Informational Options"); - DEF_FLAG("help", 'h', "Print help message", std::bind(&ConfigHelper::printHelp, this, _1)); + DEF_FLAG("usage", 'h', "Print usage information.", std::bind(&ConfigHelper::printUsage, this, _1)); + DEF_ARG( + "help", 0, "option", "Print extended help information for requested option.", + std::bind(&ConfigHelper::printHelp, this, _1), false); DEF_FLAG("version", 'V', "Print SST Release Version", std::bind(&ConfigHelper::printVersion, this, _1)); /* Basic Options */ @@ -679,11 +727,11 @@ Config::insertOptions() /* Advanced Features - Profiling */ DEF_SECTION_HEADING("Advanced Options - Profiling (EXPERIMENTAL)"); - DEF_ARG( + DEF_ARG_EH( "enable-profiling", 0, "POINTS", "Enables default profiling for the specified points. Argument is a semicolon separated list specifying the " "points to enable.", - std::bind(&ConfigHelper::enableProfiling, this, _1), true); + std::bind(&ConfigHelper::enableProfiling, this, _1), std::bind(&ConfigHelper::getProfilingExtHelp), true); DEF_ARG( "profiling-output", 0, "FILE", "Set output location for profiling data [stdout (default) or a filename]", std::bind(&ConfigHelper::setProfilingOutput, this, _1), true); @@ -721,8 +769,6 @@ Config::getUsagePrelude() { std::string prelude = "Usage: sst [options] config-file\n"; prelude.append(" Arguments to options contained in [] are optional\n"); - prelude.append(" Options available to be set in the sdl file (input configuration file) are denoted by (S)\n"); - prelude.append(" - Options set on the command line take precedence over options set in the SDL file\n"); prelude.append(" Notes on flag options (options that take an optional BOOL value):\n"); prelude.append(" - BOOL values can be expressed as true/false, yes/no, on/off or 1/0\n"); prelude.append(" - Program default for flags is false\n"); @@ -782,7 +828,11 @@ Config::checkArgsAfterParsing() bool Config::setOptionFromModel(const string& entryName, const string& value) { - return setOptionExternal(entryName, value); + // Check to make sure option is settable in the SDL file + if ( getAnnotation(entryName, 'S') ) { return setOptionExternal(entryName, value); } + fprintf(stderr, "ERROR: Option \"%s\" is not available to be set in the SDL file\n", entryName.c_str()); + exit(-1); + return false; } diff --git a/src/sst/core/config.h b/src/sst/core/config.h index 353e886d1..7b1352013 100644 --- a/src/sst/core/config.h +++ b/src/sst/core/config.h @@ -53,7 +53,7 @@ class Config : public ConfigShared, public SST::Core::Serialization::serializabl Default constructor used for serialization. At this point, first_rank_ is no longer needed, so just initialize to false. */ - Config() : ConfigShared(true, true), first_rank_(false) {} + Config() : ConfigShared(true, {}), first_rank_(false) {} //// Functions for use in main diff --git a/src/sst/core/configBase.cc b/src/sst/core/configBase.cc index b7ee2f371..012759977 100644 --- a/src/sst/core/configBase.cc +++ b/src/sst/core/configBase.cc @@ -60,11 +60,11 @@ ConfigBase::parseBoolean(const std::string& arg, bool& success, const std::strin void ConfigBase::addOption( - struct option opt, const char* argname, const char* desc, std::function callback, bool header, - bool sdl_avail) + struct option opt, const char* argname, const char* desc, std::function callback, + std::vector annotations, std::function ext_help) { // Put this into the options vector - options.emplace_back(opt, argname, desc, callback, header, sdl_avail, false); + options.emplace_back(opt, argname, desc, callback, false, annotations, ext_help, false); LongOption& new_option = options.back(); @@ -74,6 +74,9 @@ ConfigBase::addOption( // Increment the number of options num_options++; + // See if there is extended help + if ( ext_help ) has_extended_help_ = true; + // See if this is the longest option size_t size = 0; if ( new_option.opt.name != nullptr ) { size = strlen(new_option.opt.name); } @@ -95,6 +98,18 @@ ConfigBase::addOption( short_options_string.append("::"); } } + + // Handle any extra help functions + if ( ext_help ) { extra_help_map[opt.name] = ext_help; } +} + +void +ConfigBase::addHeading(const char* desc) +{ + struct option opt = { "", optional_argument, 0, 0 }; + std::vector vec; + options.emplace_back( + opt, "", desc, std::function(), true, vec, std::function(), false); } std::string @@ -121,6 +136,7 @@ ConfigBase::addPositionalCallback(std::function c positional_args = callback; } + int ConfigBase::printUsage() { @@ -138,14 +154,21 @@ ConfigBase::printUsage() if ( errno == E_OK ) MAX_WIDTH = x; } - const char* sdl_indicator = suppress_sdl_ ? "" : "(S)"; - const uint32_t sdl_start = longest_option + 6; - const uint32_t desc_start = sdl_start + strlen(sdl_indicator) + 1; - const uint32_t desc_width = MAX_WIDTH - desc_start; + const uint32_t ann_start = longest_option + 6; + const uint32_t desc_start = ann_start + annotations_.size() + 2; + const uint32_t desc_width = MAX_WIDTH - desc_start; - /* Print usage */ + /* Print usage prelude */ fprintf(stderr, "%s", getUsagePrelude().c_str()); + /* Print info about annotations */ + if ( has_extended_help_ ) { fprintf(stderr, "\nOptions annotated with 'H' have extended help available\n"); } + for ( size_t i = 0; i < annotations_.size(); ++i ) { + fprintf(stderr, "%s\n", annotations_[i].help.c_str()); + } + + // Print info about annotations + for ( auto& option : options ) { if ( option.header ) { // Just a section heading @@ -164,17 +187,26 @@ ConfigBase::printUsage() if ( option.opt.has_arg != no_argument ) { npos += fprintf(stderr, "=%s", option.argname.c_str()); } // If we have already gone beyond the description start, // description starts on new line - if ( npos >= sdl_start ) { + if ( npos >= ann_start ) { fprintf(stderr, "\n"); npos = 0; } - // If this can be set in the sdl file, start description with - // "(S)" - while ( npos < sdl_start ) { + // Get to the start of the annotations + while ( npos < ann_start ) { npos += fprintf(stderr, " "); } - if ( option.sdl_avail ) { npos += fprintf(stderr, "%s", sdl_indicator); } + + // Print the annotations + // First check for extended help + npos += fprintf(stderr, "%c", option.ext_help ? 'H' : ' '); + + // Now do the rest of the annotations + for ( size_t i = 0; i < annotations_.size(); ++i ) { + char c = ' '; + if ( option.annotations.size() >= (i + 1) && option.annotations[i] ) c = annotations_[i].annotation; + npos += fprintf(stderr, "%c", c); + } const char* text = option.desc.c_str(); while ( text != nullptr && *text != '\0' ) { @@ -209,6 +241,25 @@ ConfigBase::printUsage() return 1; /* Should not continue */ } + +int +ConfigBase::printExtHelp(const std::string& option) +{ + if ( suppress_print_ ) return 1; + + if ( extra_help_map.find(option) == extra_help_map.end() ) { + fprintf(stderr, "No additional help found for option \"%s\"\n", option.c_str()); + } + else { + std::function& func = extra_help_map[option]; + std::string help = func(); + fprintf(stderr, "%s\n", help.c_str()); + } + + return 1; /* Should not continue */ +} + + int ConfigBase::parseCmdLine(int argc, char* argv[], bool ignore_unknown) { @@ -348,16 +399,8 @@ ConfigBase::setOptionExternal(const string& entryName, const string& value) // NOTE: print outs in this function will not be suppressed for ( auto& option : options ) { if ( !entryName.compare(option.opt.name) ) { - if ( option.sdl_avail ) { - // If this was set on the command line, skip it - if ( option.set_cmdline ) return false; - return option.callback(value.c_str()); - } - else { - fprintf(stderr, "ERROR: Option \"%s\" is not available to be set in the SDL file\n", entryName.c_str()); - exit(-1); - return false; - } + if ( option.set_cmdline ) return false; + return option.callback(value.c_str()); } } fprintf(stderr, "ERROR: Unknown configuration entry \"%s\"\n", entryName.c_str()); @@ -365,4 +408,33 @@ ConfigBase::setOptionExternal(const string& entryName, const string& value) return false; } +bool +ConfigBase::getAnnotation(const std::string& entryName, char annotation) +{ + // Need to look for the index of the annotation + size_t index = std::numeric_limits::max(); + for ( size_t i = 0; i < annotations_.size(); ++i ) { + if ( annotations_[i].annotation == annotation ) { index = i; } + } + + if ( index == std::numeric_limits::max() ) { + fprintf(stderr, "ERROR: Searching for unknown annotation: '%c'\n", annotation); + exit(-1); + } + + // NOTE: print outs in this function will not be suppressed + for ( auto& option : options ) { + if ( !entryName.compare(option.opt.name) ) { + // Check for the annotation. If the index is not in the + // vector, we assume false + if ( option.annotations.size() <= index ) return false; + return option.annotations[index]; + } + } + + fprintf(stderr, "ERROR: Unknown configuration entry \"%s\"\n", entryName.c_str()); + exit(-1); + return false; +} + } // namespace SST diff --git a/src/sst/core/configBase.h b/src/sst/core/configBase.h index e8b53ec6d..bc78d90d1 100644 --- a/src/sst/core/configBase.h +++ b/src/sst/core/configBase.h @@ -37,22 +37,29 @@ struct LongOption std::string desc; std::function callback; bool header; // if true, desc is actually the header - bool sdl_avail; + std::vector annotations; + std::function ext_help; mutable bool set_cmdline; LongOption( struct option opt, const char* argname, const char* desc, const std::function& callback, - bool header, bool sdl_avail, bool set_cmdline) : + bool header, std::vector annotations, std::function ext_help, bool set_cmdline) : opt(opt), argname(argname), desc(desc), callback(callback), header(header), - sdl_avail(sdl_avail), + annotations(annotations), + ext_help(ext_help), set_cmdline(set_cmdline) {} }; +struct AnnotationInfo +{ + char annotation; + std::string help; +}; // Macros to make defining options easier. These must be called // inside of a member function of a class inheriting from ConfigBase @@ -66,21 +73,24 @@ struct LongOption // shortName - single character name referenced using - // text - help text // func - function called if option is found -#define DEF_FLAG_OPTVAL(longName, shortName, text, func, sdl_avail) \ - addOption({ longName, optional_argument, 0, shortName }, "[BOOL]", text, func, false, sdl_avail); +#define DEF_FLAG_OPTVAL(longName, shortName, text, func, ...) \ + addOption({ longName, optional_argument, 0, shortName }, "[BOOL]", text, func, { __VA_ARGS__ }); -#define DEF_FLAG(longName, shortName, text, func) \ - addOption({ longName, no_argument, 0, shortName }, "", text, func, false, false); +#define DEF_FLAG(longName, shortName, text, func, ...) \ + addOption({ longName, no_argument, 0, shortName }, "", text, func, { __VA_ARGS__ }); -#define DEF_ARG(longName, shortName, argName, text, func, sdl_avail) \ - addOption({ longName, required_argument, 0, shortName }, argName, text, func, false, sdl_avail); +#define DEF_ARG(longName, shortName, argName, text, func, ...) \ + addOption({ longName, required_argument, 0, shortName }, argName, text, func, { __VA_ARGS__ }); -#define DEF_ARG_OPTVAL(longName, shortName, argName, text, func, sdl_avail) \ - addOption({ longName, optional_argument, 0, shortName }, "[" argName "]", text, func, false, sdl_avail); +#define DEF_ARG_OPTVAL(longName, shortName, argName, text, func, ...) \ + addOption({ longName, optional_argument, 0, shortName }, "[" argName "]", text, func, { __VA_ARGS__ }); +// Macros that include extended help +#define DEF_ARG_EH(longName, shortName, argName, text, func, eh, ...) \ + addOption({ longName, required_argument, 0, shortName }, argName, text, func, { __VA_ARGS__ }, eh); -#define DEF_SECTION_HEADING(text) \ - addOption({ "", optional_argument, 0, 0 }, "", text, std::function(), true, false); + +#define DEF_SECTION_HEADING(text) addHeading(text); /** @@ -99,7 +109,7 @@ class ConfigBase ConfigBase constructor. Meant to only be created by main function */ - ConfigBase(bool suppress_print, bool suppress_sdl) : suppress_print_(suppress_print), suppress_sdl_(suppress_sdl) {} + ConfigBase(bool suppress_print) : suppress_print_(suppress_print) {} /** Default constructor used for serialization. After @@ -109,8 +119,13 @@ class ConfigBase to true. None of this class needs to be serialized because it it's state is only for parsing the arguments. */ - ConfigBase() : suppress_print_(true), suppress_sdl_(true) { options.reserve(100); } + ConfigBase() : suppress_print_(true) { options.reserve(100); } + + ConfigBase(bool suppress_print, std::vector annotations) : + annotations_(annotations), + suppress_print_(suppress_print) + {} /** Called to print the help/usage message @@ -118,6 +133,11 @@ class ConfigBase int printUsage(); + /** + Called to print the extended help for an option + */ + int printExtHelp(const std::string& option); + /** Add options to the Config object. The options will be added in the order they are in the input array, and across calls to the @@ -125,7 +145,12 @@ class ConfigBase */ void addOption( struct option opt, const char* argname, const char* desc, std::function callback, - bool header, bool sdl_avail); + std::vector annotations, std::function ext_help = std::function()); + + /** + Adds a heading to the usage output + */ + void addHeading(const char* desc); /** Called to get the prelude for the help/usage message @@ -155,6 +180,9 @@ class ConfigBase /** Set a configuration string to update configuration values */ bool setOptionExternal(const std::string& entryName, const std::string& value); + /** Get the value of an annotation for an option */ + bool getAnnotation(const std::string& entryName, char annotation); + public: // Function to uniformly parse boolean values for command line // arguments @@ -171,7 +199,6 @@ class ConfigBase */ int parseCmdLine(int argc, char* argv[], bool ignore_unknown = false); - private: std::vector options; std::map short_options; @@ -181,9 +208,15 @@ class ConfigBase std::function dashdash_callback; std::function positional_args; + // Map to hold extended help function calls + std::map> extra_help_map; + + // Annotations + std::vector annotations_; + std::string run_name_; bool suppress_print_; - bool suppress_sdl_; + bool has_extended_help_ = false; }; } // namespace SST diff --git a/src/sst/core/configShared.cc b/src/sst/core/configShared.cc index 8ac6a96b4..cbdfbb2fc 100644 --- a/src/sst/core/configShared.cc +++ b/src/sst/core/configShared.cc @@ -21,9 +21,8 @@ using namespace std; namespace SST { -ConfigShared::ConfigShared( - bool suppress_print, bool suppress_sdl, bool include_libpath, bool include_env, bool include_verbose) : - ConfigBase(suppress_print, suppress_sdl) +ConfigShared::ConfigShared(bool suppress_print, bool include_libpath, bool include_env, bool include_verbose) : + ConfigBase(suppress_print) { if ( include_libpath ) addLibraryPathOptions(); if ( include_env ) addEnvironmentOptions(); @@ -31,7 +30,9 @@ ConfigShared::ConfigShared( } -ConfigShared::ConfigShared(bool suppress_print, bool suppress_sdl) : ConfigBase(suppress_print, suppress_sdl) {} +ConfigShared::ConfigShared(bool suppress_print, std::vector annotations) : + ConfigBase(suppress_print, annotations) +{} void ConfigShared::addLibraryPathOptions() diff --git a/src/sst/core/configShared.h b/src/sst/core/configShared.h index 0fc9cca60..318755dea 100644 --- a/src/sst/core/configShared.h +++ b/src/sst/core/configShared.h @@ -37,10 +37,10 @@ class ConfigShared : public ConfigBase /** ConfigShared constructor that it meant to be used when needing - a stand only ConfigShared (i.e. for the bootstrap wrappers for + a stand alone ConfigShared (i.e. for the bootstrap wrappers for sst and sst-info */ - ConfigShared(bool suppress_print, bool suppress_sdl, bool include_libpath, bool include_env, bool include_verbose); + ConfigShared(bool suppress_print, bool include_libpath, bool include_env, bool include_verbose); protected: void addLibraryPathOptions(); @@ -50,7 +50,7 @@ class ConfigShared : public ConfigBase /** ConfigShared constructor for child classes */ - ConfigShared(bool suppress_print, bool suppress_sdl); + ConfigShared(bool suppress_print, std::vector annotations); /** Default constructor used for serialization. At this point, diff --git a/src/sst/core/sstinfo.cc b/src/sst/core/sstinfo.cc index df81fbdc0..9e6e96d27 100644 --- a/src/sst/core/sstinfo.cc +++ b/src/sst/core/sstinfo.cc @@ -246,7 +246,7 @@ OverallOutputter::outputXML() XMLDocument.SaveFile(g_configuration.getXMLFilePath().c_str()); } -SSTInfoConfig::SSTInfoConfig(bool suppress_print) : ConfigShared(suppress_print, true) +SSTInfoConfig::SSTInfoConfig(bool suppress_print) : ConfigShared(suppress_print, {}) { using namespace std::placeholders;