CollectingGlobalStatistics

Problem

How do I collect global statistics in a model? I read some warning agains global variables in the Manual and it recommends using a separate module instead, but I have no idea how to do that.

Solution

Based on the mailing list post "RE: Singleton Class or a Statistics Collector Module" on 25 Nov 2003 (online)

Jump to preferred solution, or read the full text below.

To start with, global variables aren't very good for use in the model, because they don't get reset when you restart the simulation in Tkenv. The same problem occurs with singletons, which are basically global variables put in the fancy way. Let's see an example:

 class ProtocolX : public cSimpleModule
 {
    protected:
      static long totalPkDropped; // counter
      virtual void handleMessage(cMessage *msg);
 };

 long ProtocolX::totalPkDropped = 0; // definition and initialization -- WRONG!

 void ProtocolX::handleMessage(cMessage *msg)
 {
     ...
     totalPkDropped++;
     ...
 }

The problem is, when you restart the simulation in Tkenv, the counter will start counting where it left off last time, not from zero. C++ initializes global vars at the start of the program, and it has no idea that you also want to reset it when OMNeT++ rebuilds a network.

To solve this, you can initialize the variable in the module's initialize() method. The drawback: if you have 100 instances of the module, the variable will be initialized 100 times. (That might be OK though.)

A similar problem occurs when you want to record the variables at the end of the simulation. If you just put the code into finish(), you'll end up recording the same value 100 times. The workaround is to introduce a statsAlreadyRecorded boolean global variable (which also has to be initialized in initialize()!).

This yields the following code:

 class ProtocolX : public cSimpleModule
 {
    protected:
      static bool statsAlreadyRecorded;
      static long totalPkDropped;

      virtual void initialize();
      virtual void handleMessage(cMessage *msg);
      virtual void finish()
 };

 bool ProtocolX::statsAlreadyRecorded;
 long ProtocolX::totalPkDropped;

 void ProtocolX::initialize()
 {
     statsAlreadyRecorded = false;
     totalPkDropped = 0;
 }
 void ProtocolX::handleMessage(cMessage *msg)
 {
     ...
     totalPkDropped++;
     ...
 }
 void ProtocolX::finish()
 {
     if (!statsAlreadyRecorded) {
        recordScalar("total pks dropped", totalPkDropped);
        statsAlreadyRecorded = true;
     }
 }

But even so, a drawback is that

  • (a) in the output file the recorded stats appear to belong to the particular module that recorded it, which might be misleading, and
  • (b) if you have to subnets in the model for which you want to collect statistics separately (i.e. "total pks dropped in subnet A" and "total pks dropped in subnet B"), then you're stuck.

A slightly more complex but superior solution is to use a a single global module, described below.

Using a single global module

A good way is to introduce a single global module, encapsulate the variables into it as private or protected data members, and expose them via public methods. Other modules can then call these public methods to get or set the values.

How does it look like in practice? You could define a module called e.g. StatisticsCollector, and add public member functions to it for updating the statistics:

 class StatisticsCollector : public cSimpleModule
 {
   ...
   public:
     void addEndToEndDelay(double d);
     void incNumPacketsDropped();
     ...
 };
 Define_Module(StatisticsCollector);

Add this definition to your ned file.

simple StatisticsCollector
endsimple

Add a module of this type to your submodules list

    submodules:
        statisticsCollector: StatisticsCollector;

Then from other modules you can get a pointer to it using cSimulation::moduleByPath(), then cast it to the specific subclass:

 const char *statsModulePath = par("statsModulePath");
 cModule *modp = simulation.moduleByPath(statsModulePath);
 StatisticsCollector *stats = check_and_cast<StatisticsCollector *>(modp);

Then you could use your StatisticsCollector methods to update the statistics.

 double eed = ...
 stats->addEndToEndDelay(eed);
 ...

If you use classes such as cDoubleHistogram inside the StatisticsCollector (see Modules/Statistical data collection in the API-doc), the inspectors of those classes can display mean, stddev, etc and also the histogram graphically. If you keep or calculate additional statistics, you can make them visible via WATCH().

Now if you want to collect separate statistics for "subnet A" and "subnet B", you just create two instances of StatisticsCollector, and point ProtocolX's statsModulePath parameters to the appropriate one:

 net.subnetA.**.protX.statsModulePath = "net.subnetA.statisticsCollector"
 net.subnetB.**.protX.statsModulePath = "net.subnetB.statisticsCollector"

And to make the whole thing look nice, it's possible to use a Tkenv plugin which displays a custom panel with whatever data needs to be displayed. (If someone needs this, please drop a note here, and I'll add more info. Andras)

---

Notes

I am interested in the plugin mentioned which displays the information of collecting statistic. Michael

Edit - History - Print - Recent Changes - Search
Page last modified on April 01, 2006, at 04:22 PM