Creating new Data Groups

Overview

This document cover how to add new data groups to Statoscope.

The log format is self describing so that the tool doesn't need updating when new stats are added.

All that needs to be done is to create an implemetation of IStatoscopeDataGroup and register it with CStatoscope::RegisterDataGroup().

Here's an example of the simplest data group:

struct SFrameLengthDG : public IStatoscopeDataGroup
{
  virtual SDescription GetDescription() const
  {
    return SDescription('f', "frame lengths", "['/' (float frameLengthInMS)]");
  }
  virtual void Write(IStatoscopeFrameRecord& fr)
  {
    fr.AddValue(gEnv->pTimer->GetRealFrameTime() * 1000.0f);
  }
};
...
RegisterDataGroup(new SFrameLengthDG());
...

It can be enabled by adding 'f' to e_StatoscopeDataGroups, "frame lengths" will appear in e_StatoscopeDataGroups's help string and every frame it will output a single float value that appears as "/frameLengthInMS" in the Overview tree view in the tool.

When adding a new data group, be sure to avoid choosing a letter that's already in use. Check in Statoscope.cpp and Statoscope - Data Groups and please update that page with what you're adding. Short of looking for a free letter, there's currently no easy way to know which ones are in use. Best to go for an upper case letter as all the lower case ones are taken at the time of writing.

Here's the frame profilers data group, which shows how to record bar data:

struct SFrameProfilersDG : public IStatoscopeDataGroup
{
  virtual SDescription GetDescription() const
  {
    return SDescription('r', "frame profilers", "['/Threads/$' (int count) (float selfTimeInMS)]");
  }
  virtual void Enable()
  {
    IStatoscopeDataGroup::Enable();
    ICVar *pCV_profile = gEnv->pConsole->GetCVar("profile");
    if (pCV_profile)
      pCV_profile->Set(-1);
  }
  virtual void Disable()
  {
    IStatoscopeDataGroup::Disable();
    ICVar *pCV_profile = gEnv->pConsole->GetCVar("profile");
    if (pCV_profile)
      pCV_profile->Set(0);
  }
  virtual void Write(IStatoscopeFrameRecord &fr)
  {
    for (uint32 i=0; i<m_frameProfilerRecords.size(); i++)
    {
      SPerfStatFrameProfilerRecord &fpr = m_frameProfilerRecords[i];
      string fpPath = GetFrameProfilerPath(fpr.m_pProfiler);
      fr.AddValue(fpPath.c_str());
      fr.AddValue(fpr.m_count);
      fr.AddValue(fpr.m_selfTime);
    }
    m_frameProfilerRecords.clear();
  }
  virtual uint32 PrepareToWrite()
  {
    return m_frameProfilerRecords.size();
  }
  std::vector<SPerfStatFrameProfilerRecord> m_frameProfilerRecords;  // the most recent frame's profiler data - filled out externally
};

With bar stats like this, the same format is output many times per frame, in this case count and selfTimeInMS for each named profiler. The number of items needs to be returned by PrepareToWrite(). To specify the name of each item, put a '$' in the appropriate place in the format string of GetDescription() and the first value output will be used to replace it. In this example, if fpPath is "Main/Action/CFlowSystem::Update()", the values output will be attributed to "/Threads/Main/Action/CFlowSystem::Update()" and hierarchied in the tool accordingly.

Values can either be given type float or int and are ultimately stored as floats in the tool. The distinction with ints is only for neater display (i.e. 423 rather than 423.0000000).