Statistic Binding (new in 7.7) provides a standard way for objects to track statistics. These statistics are called Bound Statistics, or just statistics.
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:
TreeNode*
member to your object's definition:
class MyObject : public FlexSimObject
{
public:
TreeNode* someStat = nullptr;
void bindStatistics() override;
...
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.STAT_TIME_WEIGHTED
flag, although this is usually only done with
STAT_TYPE_LEVEL
statistics.
UPDATE_SDT_STAT(statNode, newValue)
- sets the statistic to the new valueUPDATE_SDT_STAT_DELTA(statNode, delta)
- sets the statistic to its old value, plus
the deltaUPDATE_SDT_STAT_INCREMENTAL(statNode)
- increases the statistic by 1void MyObject::doStuff()
{
// ... object logic
UPDATE_SDT_STAT(someStat, value);
enumerateStatistics(destNode, true);
StatisticBinding& binding = *(destNode->objectAs(StatisticBinding));
for (StatisticBindingEntry& entry : binding) {
binding.select(entry.statName.getBuffer());
TrackedVariable* tv = sdt->assertStatistic(&binding)->objectAs(TrackedVariable);
tv->reset();
if ((entry.flags & STAT_TYPE_MASK) != STAT_TYPE_STREAM)
tv->set(0);
}
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.
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) // STAT_TYPE_INCREMENTAL // STAT_TYPE_STREAM
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() { SimpleDataType::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() { ObjectDataType::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:
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.
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.