Bound Statistics


Statistic Binding (new in 7.7) provides a standard way for objects to track statistics. These statistics are called Bound Statistics, or just statistics.

Adding Statistics to an Object

You can add statistics to any object that inherits SimpleDataType, which includes all FlexSimObjects, FixedResources, and TaskExecutors. In order to add statistics, follow these steps:

  1. Add a TreeNode* member to your object's definition:
    class MyObject : public FlexSimObject
    		TreeNode* someStat = nullptr;
    		void bindStatistics() override;
  2. Override the bindStatistics() method:
    void MyObject::bindStatistics()
    		FlexSimObject::bindStatistics(); // bind the parent class statistics
    		bindStatistic(someStat, flags);
    The flags argument is a bitmask that specifies the type and whether or not the statistic is time weighted. There are three types of statistics:
    • STAT_TYPE_LEVEL - This is the default type. It means that the statistic increases and decreases, and that the current value reflects the level of some value, like the content in a queue.
    • STAT_TYPE_INCREMENTAL - This kind of statistic only increases, like input or output.
    • STAT_TYPE_STREAM - This kind of statistic represents a set of values, like flowitem staytimes in a processor.
    Each statistic can only be one of these three types. The type flag can optionally be bitwise or'd with the STAT_TIME_WEIGHTED flag, although this is usually only done with STAT_TYPE_LEVEL statistics.
  3. Use one of three macros to update the statistic:
    • UPDATE_SDT_STAT(statNode, newValue) - sets the statistic to the new value
    • UPDATE_SDT_STAT_DELTA(statNode, delta) - sets the statistic to its old value, plus the delta
    • UPDATE_SDT_STAT_INCREMENTAL(statNode) - increases the statistic by 1
    void MyObject::doStuff()
    		// ... object logic
    		UPDATE_SDT_STAT(someStat, value);
  4. Activate and reset all of the object's statistics when the object is reset:
    enumerateStatistics(destNode, true);
    	StatisticBinding& binding = *(destNode->objectAs(StatisticBinding));
    	for (StatisticBindingEntry& entry : binding) {;
    		TrackedVariable* tv = sdt->assertStatistic(&binding)->objectAs(TrackedVariable);
    		if ((entry.flags & STAT_TYPE_MASK) != STAT_TYPE_STREAM)
    This approach works well, especially in a parent class method, as it will reset all the statistics of a calling child class. Notice that non-stream statistics are set to zero. This is because the initial value of level and incremental statistics is zero. If on reset logic adds items to the object, then these statistics should be updated as those items are added, not as part of the reset logic. Stream statistics (like staytimes) usually do not have an initial value, because no data has been gathered for that value yet.

Each statistic is represented by a TrackedVariable object. When you use the assertStatistic() function, it returns a treenode that has a TrackedVariable on it. Consequently, you can get the current, average, maximum, and minimum values for each statistic. However, the average, maximimum, and minimum values do not make sense for incremental statistics.

Enumerating Statistics

You can query an object for the statistics it has available. An example of this is shown in Step 4 in the previous section. In that case, an object is enumerating all of its own statistics. However, the enumerateStatistics() method can also return the statistic data as a treenode table. Set the second parameter to false to get this behavior.

Each statistic has a name and flags. The flags are a bitmask that include information about the type of statistic, as well as whether the statistic is relayed.

for (StatisticBindingEntry& statBindingEntry : listStatBinding) {
		if (statBindingEntry.flags & STAT_RELAYED) { /* check if a statistic is relayed */ }
		int numRequirements = sdt->getNumStatRequirementsFromFlags(statBindingEntry.flags);
		// numRequirements will be 0, 1, 2, or 3
		int statType = (statBindingEntry.flags & STAT_TYPE_MASK)
		// statType will be equal to 
		//     0 or STAT_TYPE_LEVEL (both are equivalent)

Relayed Statistics

Sometimes, your object manages sub objects that have individual statistics. You can make the sub object statistics available to the owning object by relaying statistics. For example, a List manages its Partitions, and Partitions have statistics. These statistics can be accessed through the List object, because the List relays the Partition statistics.

If an object relays statistics to a sub object, it must use the bindRelayedClassStatistics<>() function within its bindStatistics() method.

For example, here is the bindStatistics() method for the partition class:

void List::Partition::bindStatistics()
		bindStatistic(content, STAT_TIME_WEIGHTED);
		bindStatistic(input, STAT_TYPE_INCREMENTAL);
		bindStatistic(output, STAT_TYPE_INCREMENTAL);
		bindStatistic(staytime, STAT_TYPE_STREAM);

		bindStatistic(backOrderContent, STAT_TIME_WEIGHTED);
		bindStatistic(backOrderInput, STAT_TYPE_INCREMENTAL);
		bindStatistic(backOrderOutput, STAT_TYPE_INCREMENTAL);
		bindStatistic(backOrderStaytime, STAT_TYPE_STREAM);

Since the List manages Partitions, it also relays those statistics:

void List::bindStatistics()

		bindStatistic(input, STAT_TYPE_INCREMENTAL);
		bindStatistic(output, STAT_TYPE_INCREMENTAL);
		bindStatistic(content, STAT_TIME_WEIGHTED);
		bindStatistic(staytime, STAT_TYPE_STREAM);

		bindStatistic(backOrderInput, STAT_TYPE_INCREMENTAL);
		bindStatistic(backOrderOutput, STAT_TYPE_INCREMENTAL);
		bindStatistic(backOrderContent, STAT_TIME_WEIGHTED);
		bindStatistic(backOrderStaytime, STAT_TYPE_STREAM);

		bindRelayedClassStatistics<Partition>("Partition", STAT_1_REQUIREMENT, &List::partitionResolver, &List::getPartitionPossibilities);

The bindRelayedClassStatistics<>() function is a template function. The template parameter is the class for which statistics are being relayed. In the previous example, the Partition class is the template parameter.

The bindRelayedClassStatistics<>() function takes four arguments:

  1. A prefix - This is a string that is appended to the statistic name. When you enumerate statistics on the List, all Partition statistics will be included, but their names will have "Partition" prefixed to them, as in "PartitionContent".
  2. The number of requirements. Because the statistic is relayed, this must be STAT_1_REQUIREMENT, STAT_2_REQUIREMENTS or STAT_3_REQUIREMENTS, depending on if there are 1, 2, or 3 requirements to get the statistics.
  3. A requirement resolver. This is a function of the form
    TreeNode* resolver1(const Variant& p1)
    	TreeNode* resolver2(const Variant& p1, const Variant& p2)
    	TreeNode* resolver3(const Variant& p1, const Variant& p2, const Variant& p3)
    The function used will depend on whether there are 1, 2, or 3 requirements. In any case, the function should map the requirements given to return a treenode with the desired object on it. The List::partitionResolver, for example, takes a Partition ID and returns the treenode with that partition on it.
  4. A possibility enumerator. This is a function of the form
    int enumerator(TreeNode* destNode, const Variant& p1, const Variant& p2) {}
    This function should use the given requirements to enumerate all the possible nodes that can be used as the last requirement. If p1 is a null variant, then this function should add sub nodes for each possible value of p1 in the resolver. If p1 is not null, but p2 is, then this function should add sub nodes for each possible value of p2 in the resolver. For each possibility, add a sub node to the destNode that points to the possible value for the requirement.

    This function should also set the name of the destNode to the name of the requirement.

    Finally, this function should return one of three values:
    • STAT_ENUM_REQS_INVALID - This value indicates that no possibilities could be given.
    • STAT_ENUM_REQS_STATIC - This value indicates that the possibilities enumerated will not change while the model runs.
    • STAT_ENUM_REQS_DYNAMIC - This value indicates that no possibilities are enumerated, because they are dynamiv.