Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basics of sst core #5

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

researcherben
Copy link
Contributor

uploading three tutorials on the basics of SST core written by Joe C

Each tutorial is compatible with SST-core release 11.0, as verified by me.

I added the Dockerfile and top-level Makefile.

@@ -0,0 +1,97 @@
// Created for SST-Core Version (9.1.0)
//
#include "ExampleComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing "#include <sst_config.h>" before any other include.

See http://sst-simulator.org/SSTPages/SSTDeveloperCodingStandards/.

@@ -0,0 +1,97 @@
// Created for SST-Core Version (9.1.0)
//
#include "ExampleComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing "#include <sst_config.h>"

@@ -0,0 +1,97 @@
// Created for SST-Core Version (9.1.0)
//
#include "ExampleComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing "#include <sst_config.h>"

void serialize_order(SST::Core::Serialization::serializer &ser) override
{
Event::serialize_order(ser);
ser & payload_; // Not sure what & means in this context but based
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is a note to self? The serialize_order function indicates how to serialize the class. Each class member needs to be added to those in the serializer. Complex types may require special handling (e.g., serializing struct members, handling pointers correctly, etc.), but most can just be added this way.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, the "and" operator (operator&) function has been overloaded. It is an overloaded function because there are three types of serializers: sizer, packer and unpacker. Each one does something different. It is specifically the & operator because this is more concise than calling a function on the serializer (i.e. ser.serialize(payload_)) and it has been used in other similar well known code (in this case boost serialization).

// Created for SST-Core Version (9.1.0)
// works with 10 and 11
//
#include "ExampleComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing "#include <sst_config.h>"


// Standard SST::Component functions. These all need to
// be implemented in the component, even if they are empty.
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These do not need to be implemented by components if they are empty. Change the comment?

Example project for creating a component for SST

Created for SST-Core Version (9.1.0)
Also works with SST-Core v10.0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExampleComponent.cc says it works with v9-v11, this says v9-v10. Perhaps it would be good to pick one place to keep this info for each tutorial example.

~ExampleComponent() {}

// Standard SST::Component functions. These all need to
// be implemented in the component, even if they are empty.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned in ExampleComponent.cc: These functions do not need to be implemented if empty.


As noted in the comments, all SST components are derived from the base component SST::Component or one of its children. The constructor and destructor are a standard format and required to interface with the SST core functions.

The `setup()` and `finish()` methods are also required to interface with the SST core functions. `setup()` is called after the component constructor but before the simulation starts. As such, it's a good place to put any component initialization that can't be done in the constructor. `finish()` is called after the simulation ends and it a good place to perform any necessary cleanup associated with the component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the init() and complete() functions which are called before setup() and finish() respectively. They can also be used for the same purposes. This comment is just to double check that you intended to leave those out and focus on setup() and finish().

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want an explanation of the init() and complete() phases for completeness. The simulation lifetime at a high level runs in these steps:

construct graph
partition/distribute graph
construct simulation objects (Components, Links, etc)
init phase
setup
run loop
complete phase
finish

Both the init and complete phases are interactive. The init()/complete() functions will continue to be called until there are no UntimedEvents sent. For many Components, these phases are critical to getting things setup correctly.

To implement the component functionality, use the editor of your choice and create a new file. Enter the code shown below and save it as `ExampleComponent.cc`.

```
#include "ExampleComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All .cc files need to include sst_config.h before anything else. No .h file should include that header. See http://sst-simulator.org/SSTPages/SSTDeveloperCodingStandards/.

// has completed, but before simulation time has begin.
//
// This is where you should do any other initialization that needs done
// but could be accomplished in the constructure.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"but could not be accomplished in the constructor."

//
std::string clock =
params.find<std::string>("clock", "1GHz");
clockTicks_ = static_cast<uint64_t>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static_cast isn't needed here, you can just do params.find<uint64_t>("clockTicks", 10);



// Called after the simulation is complete but before the objects are
// destroyed. This is a good place to print out statistics.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before SST contained built-in statistics support, this was the place to do statistics. Now however, it's recommended to use the Statistics API for statistics and use finish() for non-statistic output or to update statistics (through the Statistics API) if needed before they are printed by SST.


## Adding the Link

An SST link is an object within an SST component over which messages are sent. Its name is a bit of a misnomer since a link is normally thought of as the actual connection between two objects, like the telephone line between two houses or the network cable between two computers. In fact, the SST code interface and documentation seems to use the names "link" and "port" interchangeably. For this example, the term "link" will be used for consistency unless the SST interface specifically uses the term "port".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"link" and "port" are not interchangeable. In the telephone analogy, "link" would be the connection between two phone numbers, while "port" would be the telephone numbers. The link is the actual connection while the port is where a link attaches to a component.

// No handler for this example.
//
logger_.verbose(CALL_INFO, DEBUG, 0x00, "Configuring link.\n");
link_ = configureLink("link_");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good practice to check that link_ != nullptr after calling configureLink() as this would indicate that the user failed to connect the port (or misspelled something, etc.).

{
```

Just as components are required to inherit from the class `SST::Component`, events are required to inhert from `SST::Event` or one of its children.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"inherit"

//
ExampleComponent::ExampleComponent(SST::ComponentId_t id, SST::Params &params) :
SST::Component(id),
componentId_(id),
Copy link

@feldergast feldergast Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, you should avoid using or storing the component's id. If it were possible to make the id unavailable to the end component, we would have done that. The id should just be passed to the parent constructor. The id is a value that is generally meaningless to the end user because it is assigned opaquely by the core with no guarantees other than it is unique on each rank. In anything other than a trivial simulation, the number will not allow the user to know which component it refers to. And, while this is a trivial example, it is better not to teach this as something that is acceptable.

Better in this example would likely be to use the getName() function that exists in the BaseComponent API. This will return the name given to the component in the python file and will be meaningful to the end user.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above comment applies through all of the files that store the component id.

// Create the logger.
//
logger_ = SST::Output("Time=@t; File=@f; Func=@p; Line=@l; Thread=@I -- ", LogLevel::Trace, 0x00, SST::Output::STDOUT);
logger_.verbose(CALL_INFO, LogLevel::Trace, 0x00, "Entering constructor for component id %lu\n", componentId_);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output::verbose() calls have a non-zero overhead and adding so many calls can greatly slow simulation. For anything that is used primarily for debug (which in this example is probably the DEBUG and TRACE levels should call Output::debug(), which will be compiled out unless debug is enabled at configure time. Users should not be encouraged to use verbose for debug messages.


For this example we'll need to make the following changes.

1. Add a link to the component over which the message will be sent.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to "Add a port to the component so that it has a place to connect a link."

1. Add a link to the component over which the message will be sent.
2. Define the message to be sent.
3. Update the clock event handler to process the incoming messages.
4. Configure the simulation to connect two components via the link.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to "Configure the simulation with two components and connect the two components' ports via a link."


The first value in the tuple is the component connecting to the link. In this case, a link is being established between components obj0 and obj1.

The second value in the tuple is the identifier used to refer to the link within the component. This is the same value as that defined in the `SST_ELI_DOCUMENT_PORTS` macro in the component include file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The second value in the tuple is the port within the component to which to connect this link. This is the name given to the port in the ..."


The second value in the tuple is the identifier used to refer to the link within the component. This is the same value as that defined in the `SST_ELI_DOCUMENT_PORTS` macro in the component include file.

The final value in the tuple is the link delay. This specifies how long it will take the message to traverse the link from the corresponding component and must be greater than 0. The implications of this value will be discussed below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle point: it's the minimum latency for the message traversal as a component can add extra latency to the link, or to individual events when it sends one.

make clean; make
```

If there are errors, examine the source files to find the error in the source code an try again.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an --> and

void serialize_order(SST::Core::Serialization::serializer &ser) override
{
Event::serialize_order(ser);
ser & payload_; // Not sure what & means in this context but based
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment needs to be fixed.


Previous examples have demonstrated message processing using polling in the clock event handler. This works for simple simulations with a limited number of links but as the simulations become more complex a better solution is to use message handlers.

A message handler is a method associated with a link that is called whenever a message is received over that link. It is advantageous include eliminating unnecessary polling when no messages are available on a link and being able to separate processing of different message types.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"It is advantageous" --> "Its advantages".

"ExampleSubComponent", // "INSERT_CLASS_NAME"
SST_ELI_ELEMENT_VERSION(1, 0, 0), //
"Example subcomponent", // Subcomponent description
"SST::SubComponent" // "INSERT_FULL_PARENT_CLASS_NAME" or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Example04::ExampleSubComponent, as it is not the parent class but the API that this SubComponent implements (which in this case is itself).

Trace = 500
};

template<typename... Args>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting approach, but the templating can lead to severe code bloat since you get a new version of the code for every combination of variables passed into the function. This can lead to slow code due to more misses in the instruction cache. If you really want this type of logger, we can add a version of all of Output's functions that takes a va_list argument. That way you can get rid of the templating. I would not encourage templating in this case.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also possible to add an attribute so the compiler will warn the users when the types in the format string don't match the types of the corresponding parameters that are passed in. This is done with:

attribute((format(printf, 6, 7)));

Not sure it this will work with the templated functions, but would for sure work with non-templated functions that called a va_list version of the calls.


The constructor and destructor are a standard format and required to interface with the SST core functions.

Subcomponents still have clock handlers but they are not automatically called by the simulation. Instead they are called from the subcomponent parent as part of the parent's clock event handling.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite true. SubComponents can register a clock handler if they wish or they can implement a function that the Component calls during its own clock handler (or any other time). You may want to have a Component call a SubComponent clock handler if the order in which those handlers are called matters, but if it doesn't matter, then you can register an independent handler. It does not even have to be at the same clock frequency as the parent component. And no clock is required if the subcomponent doesn't need it.

// subcomponent inherits from. Should be
// the full parent class name.
// "INSERT_FULL_PARENT_CLASS_NAME" or
// "INSERT_COMPLETE_NAMESPACE::INSERT_PARENT_CLASS_NAME"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily the parent class name, rather the API that this subcomponent implements.

void setup(void);
void finish(void);

// Clock handler. This is the method called from the clock event.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want to comment on what the return bool represents. It represents the answer to the question: Do you want to be removed from the clock list?

// "INSERT_COMPLETE_NAMESPACE::INSERT_PARENT_CLASS_NAME"
)
```
`SST_ELI_REGISTER_SUBCOMPONENT` registers the subcomponent's interface with SST. This allows the subcomponent to serve as a parent for other subcomponents, including itself.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be SST_ELI_REGISTER_SUBCOMPONENT_API.


`SST_ELI_REGISTER_SUBCOMPONENT_DERIVED` registers the component with the ELI database, making it available for use by the simulation configuration file. Its parameters are similar to those used when registering a component.

There are other ELI macros that may be necessary depending upon circumstances. For example, if statistics are defined for the subcomponent they must be documented using the `SST_ELI_DOCUMENT_STATISTICS` macro. However, for this simple case, the two reference above will be sufficient.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reference --> referenced

@@ -0,0 +1,27 @@
# Execute from the command line with the command:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't seem to put a comment on Example02.out, but should it be empty?

{
// No defined subcomponents. Print an error message and exit.
//
Output::getDefaultObject().fatal(CALL_INFO, -1, "Must specify at least one SubComponent for slot.\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better practice to use the Component's fatal() function if you aren't going to use the component's own output object. This also will append info about the component to it. So: fatal(CALL_INFO, exit_code, "message"); Otherwise you could call "getSimulationOutput()" to get an output object and call fatal() on that.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to emphasize this. BaseComponent::fatal() is relatively new, but adds a lot more information to the output than calling Output::fatal(). We should be encouraging developers to use that function for fatal errors instead of an Output object of any sort inside of a Component or SubComponent.

#include <cstdarg>
#include "Logger.h"

using namespace LoggerExample;
Copy link

@feldergast feldergast Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I would avoid having a "using namespace" statement in a header file. This essentially "inflicts" that namespace on any source file that includes it. For this example, that's probably not an issue, but for a tutorial I would avoid it so that those new to C++ don't replicate it.

private:
// Member variables for this example.
//
Logger logger; // For displaying log messages.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoggerExample::Logger instead of using namespace LoggerExample above. This is the only place in the file the namespace would need to be added.

```
Output logger("@t; File=@f; Func=@p; Line=@l", 300, 0x00, SST::Output::STDOUT);
```
This logger will include the simulation time, file name, function name, and line number in the prefix. Any log message with a verbose level of less than 300 and a verbose mask of 0x00 will print to STDOUT.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less than or equal to 300


The SST Output class provides four different methods to write log messages.

**Output.** This method works like a standard printf. By default its messages do not contain the prefix data but can if desired.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calls to output will print unconditionally, regardless of verbose level and flag set.

Also, these functions are all actually lowercase.

subcomponent->clock(cycle);
}
```
As mentioned previously, subcomponent clock event handlers are not called automatically so they must be handled by the subcomponent's parent within the parent's clock event handler.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update per previous comment on subcomponent clock handlers.



// Method to initialize the subcomponent. Serves the same purpose as the
// start() method in a component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no built-in start() method in a component, is this something else?


// Document the component ports.
//
// Port name, description, vector fo supported events.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fo --> for


The constructors and destructor remain unchanged. However, there is a new `start()` method used to initialize the subcomponent. This is where parameters for the subcomponent are processed and the link is initialized.

You may wonder, why not do this initialization in the subcomponent constructor? For some reason the subcomponent constructors were not being called when created in the parent component. Use of the `start()` method is a reasonable alternative that models the method used for top level components. The difference is, this `start()` method will not be called automatically. It must be called explicitly when the subcomponent is created.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor is getting called, I put a print() statement inside it in Example05 and see it getting printed. This statement is incorrect.

Once the log levels are defined instantiation and calling the logger will look like:

```
Output logger("@t; File=@f; Func=@p; Line=@l", INFO, 0x00, SST::Output::STDOUT);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, you'll want to pass the level into the Component as a parameter, not set it statically in the constructor. This also means you'll have to have some way to map the string versions of the supported levels to the proper integer, or have the end user pass the verbose level in as an integer (which then somewhat defeats the use of #define or enums). I think this is an important point that should be brought out in all or some of the more advanced examples.


As stated by Colin Eberhardt, "Logging is the process of recording application actions and state to a secondary interface." (Eberhardt, 2014) Recording application actions usually takes the form of a printed message and the secondary interface can be a local file, network storage, or even the terminal.

According to the Syslog Protocol (Gerhards, 2009), logging messages can be categorized as:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure syslog is the best model to use for logging in SST. It was really intended to monitor running systems, not individual applications. Since each element gets to specify their own levels based off of the complexity of the code, it would be difficult to map things into these discrete buckets.

In general, this is advocating for something that is very different than what is currently implemented in the elements, but presented like it is standardized. That's fine if users want to use a model like this, but I worry about presenting it in a beginning tutorial in a way that may actually confuse the user when they use the main SST elements repository.

@@ -0,0 +1,110 @@
#include "StatsComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing sst_config.h include

@@ -0,0 +1,110 @@
#include "StatsComponent.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing sst_config.h include file


There are four standard types of statistics included as part of SST.

Accumulator - An Accumulator is a simple statistic that stores a simulation value's cumulative sum (x1 + x2 + ...), the sum squared (x1^2 + x2^2 + ...), and data count. It has the following parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also max & min


As mentioned previously, a statistic is defined as part of a component or subcomponent. However, it is configured and enabled as part of the simulation Python configuration file.

Configuring statistics consists of three steps, setting the simulation's statistic load level, setting the statistic output, and enabling the statistic. Each of these is discussed below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also setting the type of statistic to generate (accumulator, histogram, etc.)

"numbins":"2"})
```

The expected results are listed below. The values for each statistic are interspersed so further analysis of an individual statistic will require the file values to be parsed in some manner. In addition, the statistic name has not been included in the results. Therefore, some knowledge of the statistic type will be required when parsing data for individual statistics.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stat name not being included is a bug in SST (thanks for finding it!). It appears to be linked to using the enableStatisticByComponentName() function with CSV output, we are tracking it down.

```
## Final Notes

As of SST V10 there are a few idiosyncrasies to keep in mind when working with statistics in SST. The noted items may explain unusual behavior seen when using statistics but will not prevent using them.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These may be bugs in SST. We are tracking these down. In general, it'd be helpful to open a ticket (issue) on sst-core if you encounter unexpected behavior like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants