Selaa lähdekoodia

Merge pull request #7989 from ghalliday/issue14582

HPCC-14582 Add spill and sort timings to stats

Reviewed-By: Jake Smith <jake.smith@lexisnexis.com>
Richard Chapman 9 vuotta sitten
vanhempi
commit
6ee3050285

+ 0 - 8
roxie/ccd/ccdfile.cpp

@@ -368,14 +368,6 @@ public:
 
     virtual unsigned __int64 getStatistic(StatisticKind kind)
     {
-        switch (kind)
-        {
-        case StTimeDiskReadIO:
-            return cycle_to_nanosec(getStatistic(StCycleDiskReadIOCycles));
-        case StTimeDiskWriteIO:
-            return cycle_to_nanosec(getStatistic(StCycleDiskWriteIOCycles));
-        }
-
         CriticalBlock b(crit);
         unsigned __int64 openValue = current->getStatistic(kind);
         return openValue + fileStats.getStatisticValue(kind);

+ 40 - 0
roxie/ccd/ccdserver.cpp

@@ -354,6 +354,8 @@ static const StatisticsMapping indexStatistics(&actStatistics, StNumServerCacheH
 static const StatisticsMapping diskStatistics(&actStatistics, StNumServerCacheHits, StNumDiskRowsRead, StNumDiskSeeks, StNumDiskAccepted,
                                                StNumDiskRejected, StKindNone);
 static const StatisticsMapping soapStatistics(&actStatistics, StTimeSoapcall, StKindNone);
+static const StatisticsMapping groupStatistics(&actStatistics, StNumGroups, StNumGroupMax, StKindNone);
+static const StatisticsMapping sortStatistics(&actStatistics, StTimeSortElapsed, StKindNone);
 
 //=================================================================================
 
@@ -7433,6 +7435,7 @@ public:
                 sorter.setown(createSortAlgorithm(sortAlgorithm, compare, ctx->queryRowManager(), meta, ctx->queryCodeContext(), tempDirectory, activityId));
             }
             sorter->prepare(input);
+            noteStatistic(StTimeSortElapsed, cycle_to_nanosec(sorter->getElapsedCycles(true)));
             readInput = true;
         }
         const void *ret = sorter->next();
@@ -7481,6 +7484,11 @@ public:
     {
         return new CRoxieServerSortActivity(this, _probeManager, sortAlgorithm, sortFlags);
     }
+
+    virtual const StatisticsMapping &queryStatsMapping() const
+    {
+        return sortStatistics;
+    }
 };
 
 IRoxieServerActivityFactory *createRoxieServerSortActivityFactory(unsigned _id, unsigned _subgraphId, IQueryFactory &_queryFactory, HelperFactory *_helperFactory, ThorActivityKind _kind, IPropertyTree &_graphNode)
@@ -16198,6 +16206,9 @@ class CRoxieServerGroupActivity : public CRoxieServerActivity
     bool endPending;
     bool eof;
     bool first;
+    unsigned numGroups;
+    unsigned numGroupMax;
+    unsigned numProcessedLastGroup;
     const void *next;
 
 public:
@@ -16208,6 +16219,9 @@ public:
         endPending = false;
         eof = false;
         first = true;
+        numGroups = 0;
+        numGroupMax = 0;
+        numProcessedLastGroup = 0;
     }
 
     virtual void start(unsigned parentExtractSize, const byte *parentExtract, bool paused)
@@ -16215,12 +16229,17 @@ public:
         endPending = false;
         eof = false;
         first = true;
+        numGroups = 0;
+        numGroupMax = 0;
+        numProcessedLastGroup = processed;
         assertex(next == NULL);
         CRoxieServerActivity::start(parentExtractSize, parentExtract, paused);
     }
 
     virtual void reset()    
     { 
+        noteStatistic(StNumGroups, numGroups);
+        noteStatistic(StNumGroupMax, numGroupMax);
         ReleaseClearRoxieRow(next);
         CRoxieServerActivity::reset();
     }
@@ -16246,14 +16265,30 @@ public:
         {
             assertex(prev);
             if (!helper.isSameGroup(prev, next))
+            {
+                noteEndOfGroup();
                 endPending = true;
+            }
         }
         else
+        {
+            noteEndOfGroup();
             eof = true;
+        }
         if (prev)
             processed++;
         return prev;
     }
+    inline void noteEndOfGroup()
+    {
+        unsigned numThisGroup = processed - numProcessedLastGroup;
+        if (numThisGroup == 0)
+            return;
+        numProcessedLastGroup = processed;
+        if (numThisGroup > numGroupMax)
+            numGroupMax = numThisGroup;
+        numGroups++;
+    }
 };
 
 class CRoxieServerGroupActivityFactory : public CRoxieServerActivityFactory
@@ -16268,6 +16303,11 @@ public:
     {
         return new CRoxieServerGroupActivity(this, _probeManager);
     }
+
+    virtual const StatisticsMapping &queryStatsMapping() const
+    {
+        return groupStatistics;
+    }
 };
 
 IRoxieServerActivityFactory *createRoxieServerGroupActivityFactory(unsigned _id, unsigned _subgraphId, IQueryFactory &_queryFactory, HelperFactory *_helperFactory, ThorActivityKind _kind)

+ 6 - 2
system/jlib/jdebug.hpp

@@ -121,11 +121,15 @@ public:
     {
         start_time = get_cycles_now();
     }
-    inline cycle_t elapsedCycles()
+    inline cycle_t elapsedCycles() const
     {
         return get_cycles_now() - start_time;
     }
-    inline unsigned elapsedMs()
+    inline unsigned __int64 elapsedNs() const
+    {
+        return cycle_to_nanosec(elapsedCycles());
+    }
+    inline unsigned elapsedMs() const
     {
         return static_cast<unsigned>(cycle_to_millisec(elapsedCycles()));
     }

+ 0 - 8
system/jlib/jfile.cpp

@@ -6467,14 +6467,6 @@ public:
     
     unsigned __int64 getStatistic(StatisticKind kind)
     {
-        switch (kind)
-        {
-        case StTimeDiskReadIO:
-            return cycle_to_nanosec(getStatistic(StCycleDiskReadIOCycles));
-        case StTimeDiskWriteIO:
-            return cycle_to_nanosec(getStatistic(StCycleDiskWriteIOCycles));
-        }
-
         CriticalBlock block(sect);
         unsigned __int64 openValue = cachedio ? cachedio->getStatistic(kind) : 0;
         return openValue + fileStats.getStatisticValue(kind);

+ 8 - 1
system/jlib/jstatcodes.h

@@ -157,7 +157,6 @@ enum StatisticKind
     StNumIndexRowsRead,
     StNumDiskAccepted,
     StNumDiskRejected,
-
     StTimeSoapcall,                     // Time spent waiting for soapcalls
     StTimeFirstExecute,                 // Time waiting for first record from this activity
     StTimeDiskReadIO,
@@ -168,6 +167,14 @@ enum StatisticKind
     StCycleDiskWriteIOCycles,
     StNumDiskReads,
     StNumDiskWrites,
+    StNumSpills,
+    StTimeSpillElapsed,
+    StTimeSortElapsed,
+    StNumGroups,
+    StNumGroupMax,
+    StSizeSpillFile,
+    StCycleSpillElapsedCycles,
+    StCycleSortElapsedCycles,
 
     StMax,
 

+ 34 - 12
system/jlib/jstats.cpp

@@ -487,6 +487,7 @@ public:
     const char * tags[StNextModifier/StVariantScale];
 };
 
+//The order of entries in this table must match the order in the enumeration
 static const StatisticMeta statsMetaData[StMax] = {
     { StKindNone, SMeasureNone, { "none" }, { "@none" } },
     { StKindAll, SMeasureAll, { "all" }, { "@all" } },
@@ -550,6 +551,14 @@ static const StatisticMeta statsMetaData[StMax] = {
     { CYCLESTAT(DiskWriteIOCycles) },
     { NUMSTAT(DiskReads) },
     { NUMSTAT(DiskWrites) },
+    { NUMSTAT(Spills) },
+    { TIMESTAT(SpillElapsed) },
+    { TIMESTAT(SortElapsed) },
+    { NUMSTAT(Groups) },
+    { NUMSTAT(GroupMax) },
+    { SIZESTAT(SpillFile) },
+    { CYCLESTAT(SpillElapsedCycles) },
+    { CYCLESTAT(SortElapsedCycles) },
 };
 
 
@@ -595,6 +604,24 @@ const char * queryStatisticName(StatisticKind kind)
     return statsMetaData[rawkind].names[variant];
 }
 
+
+unsigned __int64 convertMeasure(StatisticMeasure from, StatisticMeasure to, unsigned __int64 value)
+{
+    if (from == to)
+        return value;
+    if ((from == SMeasureCycle) && (to == SMeasureTimeNs))
+        return cycle_to_nanosec(value);
+    if ((from == SMeasureTimeNs) && (to == SMeasureCycle))
+        return nanosec_to_cycle(value);
+    throwUnexpected();
+}
+
+unsigned __int64 convertMeasure(StatisticKind from, StatisticKind to, unsigned __int64 value)
+{
+    return convertMeasure(queryMeasure(from), queryMeasure(to), value);
+}
+
+
 //--------------------------------------------------------------------------------------------------------------------
 
 void queryLongStatisticName(StringBuffer & out, StatisticKind kind)
@@ -1496,18 +1523,6 @@ bool CRuntimeStatisticCollection::serialize(MemoryBuffer& out) const
 }
 
 
-void mergeStats(CRuntimeStatisticCollection & stats, IFileIO * file)
-{
-    if (!file)
-        return;
-
-    ForEachItemIn(iStat, stats)
-    {
-        StatisticKind kind = stats.getKind(iStat);
-        stats.mergeStatistic(kind, file->getStatistic(kind), queryMergeMode(kind));
-    }
-}
-
 //---------------------------------------------------
 
 bool ScopedItemFilter::matchDepth(unsigned low, unsigned high) const
@@ -1811,6 +1826,13 @@ extern int registerStatsCategory(const char *longName, const char *shortName)
 
 static void checkKind(StatisticKind kind)
 {
+    if (kind < StMax)
+    {
+        const StatisticMeta & meta = statsMetaData[kind];
+        if (meta.kind != kind)
+            throw makeStringExceptionV(0, "Statistic %u in the wrong order", kind);
+    }
+
     StatisticMeasure measure = queryMeasure(kind);
     const char * shortName = queryStatisticName(kind);
     StringBuffer longName;

+ 32 - 2
system/jlib/jstats.h

@@ -390,14 +390,41 @@ public:
     void deserializeMerge(MemoryBuffer& in);
 protected:
     void reportIgnoredStats() const;
-
+    const CRuntimeStatistic & queryUnknownStatistic() const { return values[mapping.numStatistics()]; }
 private:
     const StatisticsMapping & mapping;
     CRuntimeStatistic * values;
 };
 
+//---------------------------------------------------------------------------------------------------------------------
+
+//Some template helper classes for merging statistics from external sources.
+
+template <class INTERFACE>
+void mergeStats(CRuntimeStatisticCollection & stats, INTERFACE * source)
+{
+    if (!source)
+        return;
+
+    ForEachItemIn(iStat, stats)
+    {
+        StatisticKind kind = stats.getKind(iStat);
+        stats.mergeStatistic(kind, source->getStatistic(kind));
+    }
+}
+
+template <class INTERFACE>
+void mergeStats(CRuntimeStatisticCollection & stats, Shared<INTERFACE> source) { mergeStats(stats, source.get()); }
 
-extern jlib_decl void mergeStats(CRuntimeStatisticCollection & stats, IFileIO * file);
+template <class INTERFACE>
+void mergeStat(CRuntimeStatisticCollection & stats, INTERFACE * source, StatisticKind kind)
+{
+    if (source)
+        stats.mergeStatistic(kind, source->getStatistic(kind));
+}
+
+template <class INTERFACE>
+void mergeStat(CRuntimeStatisticCollection & stats, Shared<INTERFACE> source, StatisticKind kind) { mergeStat(stats, source.get(), kind); }
 
 //---------------------------------------------------------------------------------------------------------------------
 
@@ -445,6 +472,9 @@ extern jlib_decl IStatisticCollection * createStatisticCollection(MemoryBuffer &
 inline unsigned __int64 milliToNano(unsigned __int64 value) { return value * 1000000; } // call avoids need to upcast values
 inline unsigned __int64 nanoToMilli(unsigned __int64 value) { return value / 1000000; }
 
+extern jlib_decl unsigned __int64 convertMeasure(StatisticMeasure from, StatisticMeasure to, unsigned __int64 value);
+extern jlib_decl unsigned __int64 convertMeasure(StatisticKind from, StatisticKind to, unsigned __int64 value);
+
 extern jlib_decl StatisticCreatorType queryStatisticsComponentType();
 extern jlib_decl const char * queryStatisticsComponentName();
 extern jlib_decl void setStatisticsComponentName(StatisticCreatorType processType, const char * processName, bool appendIP);

+ 11 - 1
testing/unittests/unittests.cpp

@@ -77,7 +77,17 @@ class InternalStatisticsTest : public CppUnit::TestFixture
 
     void testMappings()
     {
-        verifyStatisticFunctions();
+        try
+        {
+            verifyStatisticFunctions();
+        }
+        catch (IException * e)
+        {
+            StringBuffer msg;
+            fprintf(stderr, "Failure: %s", e->errorMessage(msg).str());
+            e->Release();
+            ASSERT(false);
+        }
     }
 };
 

+ 37 - 3
thorlcr/activities/group/thgroup.cpp

@@ -19,10 +19,44 @@
 #include "thactivitymaster.ipp"
 
 
-class CGroupActivityMaster : public CMasterActivity
+class CGroupBaseActivityMaster : public CMasterActivity
 {
+    Owned<CThorStats> statNumGroups;
+    Owned<CThorStats> statNumGroupMax;
 public:
-    CGroupActivityMaster(CMasterGraphElement *info) : CMasterActivity(info)
+    CGroupBaseActivityMaster(CMasterGraphElement *info) : CMasterActivity(info)
+    {
+    }
+    virtual void init()
+    {
+        CMasterActivity::init();
+        statNumGroups.setown(new CThorStats(StNumGroups));
+        statNumGroupMax.setown(new CThorStats(StNumGroupMax));
+    }
+    virtual void deserializeStats(unsigned node, MemoryBuffer &mb)
+    {
+        CMasterActivity::deserializeStats(node, mb);
+
+        rowcount_t numGroups;
+        mb.read(numGroups);
+        statNumGroups->set(node, numGroups);
+
+        rowcount_t numGroupMax;
+        mb.read(numGroupMax);
+        statNumGroupMax->set(node, numGroupMax);
+    }
+    virtual void getActivityStats(IStatisticGatherer & stats)
+    {
+        CMasterActivity::getActivityStats(stats);
+        statNumGroups->getStats(stats, false);
+        statNumGroupMax->getStats(stats, false);
+    }
+};
+
+class CGroupActivityMaster : public CGroupBaseActivityMaster
+{
+public:
+    CGroupActivityMaster(CMasterGraphElement *info) : CGroupBaseActivityMaster(info)
     {
         mpTag = container.queryJob().allocateMPTag();
     }
@@ -35,7 +69,7 @@ public:
 CActivityBase *createGroupActivityMaster(CMasterGraphElement *container)
 {
     if (container->queryLocalOrGrouped())
-        return new CMasterActivity(container);
+        return new CGroupBaseActivityMaster(container);
     else
         return new CGroupActivityMaster(container);
 }

+ 30 - 1
thorlcr/activities/group/thgroupslave.cpp

@@ -25,6 +25,9 @@ class GroupSlaveActivity : public CSlaveActivity, public CThorDataLink
     IHThorGroupArg * helper;
     bool eogNext, prevEog, eof;
     bool rolloverEnabled, useRollover;
+    rowcount_t numGroups;
+    rowcount_t numGroupMax;
+    rowcount_t startLastGroup;
     IThorDataLink *input;
     Owned<IRowStream> stream, nextNodeStream;
     OwnedConstThorRow next;
@@ -56,6 +59,9 @@ public:
         helper = static_cast <IHThorGroupArg *> (queryHelper());
         rolloverEnabled = false;
         useRollover = false;
+        numGroups = 0;
+        numGroupMax = 0;
+        startLastGroup = 0;
     }
     virtual void init(MemoryBuffer &data, MemoryBuffer &slaveData)
     {
@@ -83,7 +89,7 @@ public:
         stream.set(input);
         startInput(input);
         dataLinkStart();
-
+        startLastGroup = getDataLinkGlobalCount();
         next.setown(getNext());
 
         if (rolloverEnabled && !firstNode())  // 1st node can have nothing to send
@@ -142,17 +148,34 @@ public:
         OwnedConstThorRow prev = next.getClear();
         next.setown(getNext());
         if (next && !helper->isSameGroup(prev, next))
+        {
+            noteEndOfGroup();
             eogNext = true;
+        }
         if (prev)
         {
             dataLinkIncrement();
             return prev.getClear();
         }
         if (prevEog)
+        {
+            noteEndOfGroup();
             eof = true;
+        }
         prevEog = true;
         return NULL;
     }
+    inline void noteEndOfGroup()
+    {
+        rowcount_t rowsProcessed = getDataLinkGlobalCount();
+        rowcount_t numThisGroup = rowsProcessed - startLastGroup;
+        if (0 == numThisGroup)
+            return;
+        startLastGroup = rowsProcessed;
+        if (numThisGroup > numGroupMax)
+            numGroupMax = numThisGroup;
+        numGroups++;
+    }
     virtual void getMetaInfo(ThorDataLinkMetaInfo &info)
     {
         initMetaInfo(info);
@@ -164,6 +187,12 @@ public:
         calcMetaInfoSize(info,inputs.item(0));
     }
     virtual bool isGrouped() { return true; }
+    void serializeStats(MemoryBuffer &mb)
+    {
+        CSlaveActivity::serializeStats(mb);
+        mb.append(numGroups);
+        mb.append(numGroupMax);
+    }
 };
 
 

+ 9 - 1
thorlcr/activities/join/thjoin.cpp

@@ -40,6 +40,7 @@ class JoinActivityMaster : public CMasterActivity
     mptag_t mpTagRPC, barrierMpTag;
     Owned<IBarrier> barrier;
     Owned<ProgressInfo> lhsProgress, rhsProgress;
+    CThorStatsCollection extraStats;
 
     bool nosortPrimary()
     {
@@ -77,7 +78,7 @@ class JoinActivityMaster : public CMasterActivity
         }
     } *climitedcmp;
 public:
-    JoinActivityMaster(CMasterGraphElement * info, bool local) : CMasterActivity(info)
+    JoinActivityMaster(CMasterGraphElement * info, bool local) : CMasterActivity(info), extraStats(spillStatistics)
     {
         ActPrintLog("JoinActivityMaster");
         lhsProgress.setown(new ProgressInfo);
@@ -98,6 +99,11 @@ public:
         container.queryJob().freeMPTag(barrierMpTag);
         delete climitedcmp;
     }
+    virtual void getActivityStats(IStatisticGatherer & stats)
+    {
+        CMasterActivity::getActivityStats(stats);
+        extraStats.getStats(stats);
+    }
     virtual void serializeSlaveData(MemoryBuffer &dst, unsigned slave)
     {
         if (!islocal)
@@ -341,6 +347,8 @@ public:
             mb.read(rhsProgressCount);
             rhsProgress->set(node, rhsProgressCount);
         }
+
+        extraStats.deserializeMerge(node, mb);
     }
     virtual void getEdgeStats(IStatisticGatherer & stats, unsigned idx)
     {

+ 7 - 1
thorlcr/activities/join/thjoinslave.cpp

@@ -73,6 +73,7 @@ class JoinSlaveActivity : public CSlaveActivity, public CThorDataLink, implement
     bool leftInputStopped;
     bool rightInputStopped;
     bool rightpartition;
+    CRuntimeStatisticCollection spillStats;
 
 
     bool noSortPartitionSide()
@@ -137,7 +138,7 @@ public:
 
 
     JoinSlaveActivity(CGraphElementBase *_container, bool local)
-        : CSlaveActivity(_container), CThorDataLink(this)
+        : CSlaveActivity(_container), CThorDataLink(this), spillStats(spillStatistics)
     {
         islocal = local;
         portbase = 0;
@@ -423,6 +424,7 @@ public:
             leftStream.setown(iLoaderL->load(leftInput, abortSoon));
             isemptylhs = 0 == iLoaderL->numRows();
             stopLeftInput();
+            mergeStats(spillStats, iLoaderL);
         }
         if (isemptylhs&&((helper->getJoinFlags()&JFrightouter)==0))
         {
@@ -442,6 +444,7 @@ public:
             Owned<IThorRowLoader> iLoaderR = createThorRowLoader(*this, ::queryRowInterfaces(rightInput), rightCompare, stableSort_earlyAlloc, rc_mixed, SPILL_PRIORITY_JOIN);
             rightStream.setown(iLoaderR->load(rightInput, abortSoon));
             stopRightInput();
+            mergeStats(spillStats, iLoaderR);
         }
     }
     bool doglobaljoin()
@@ -542,6 +545,8 @@ public:
         }
         // NB: on secondary sort, the primaryKeySerializer is used
         sorter->Gather(secondaryRowIf, secondaryInput, secondaryCompare, primarySecondaryCompare, primarySecondaryUpperCompare, primaryKeySerializer, partitionRow, noSortOtherSide(), isUnstable(), abortSoon, primaryRowIf); // primaryKeySerializer *is* correct
+        mergeStats(spillStats, sorter);
+        //MORE: Stats from spilling the primaryStream??
         partitionRow.clear();
         stopOtherInput();
         if (abortSoon)
@@ -580,6 +585,7 @@ public:
             mb.append(joinhelper->getLhsProgress());
             mb.append(joinhelper->getRhsProgress());
         }
+        spillStats.serialize(mb);
     }
 };
 

+ 1 - 1
thorlcr/activities/loop/thloop.cpp

@@ -147,7 +147,7 @@ public:
     virtual void getActivityStats(IStatisticGatherer & stats)
     {
         CMasterActivity::getActivityStats(stats);
-        loopCounterProgress->getStats(stats, false, false);
+        loopCounterProgress->getStats(stats, false);
     }
 
 };

+ 21 - 3
thorlcr/activities/msort/thgroupsortslave.cpp

@@ -38,12 +38,14 @@ class CLocalSortSlaveActivity : public CSlaveActivity, public CThorDataLink
     Owned<IThorRowLoader> iLoader;
     Owned<IRowStream> out;
     bool unstable, eoi;
+    CriticalSection statsCs;
+    CRuntimeStatisticCollection spillStats;
 
 public:
     IMPLEMENT_IINTERFACE_USING(CSimpleInterface);
 
     CLocalSortSlaveActivity(CGraphElementBase *_container)
-        : CSlaveActivity(_container), CThorDataLink(this)
+        : CSlaveActivity(_container), CThorDataLink(this), spillStats(spillStatistics)
     {
     }
     void init(MemoryBuffer &data, MemoryBuffer &slaveData)
@@ -51,7 +53,7 @@ public:
         helper = (IHThorSortArg *)queryHelper();
         iCompare = helper->queryCompare();
         IHThorAlgorithm * algo = helper?(static_cast<IHThorAlgorithm *>(helper->selectInterface(TAIalgorithm_1))):NULL;
-        unstable = (algo&&algo->getAlgorithmFlags()&TAFunstable);
+        unstable = (algo&&(algo->getAlgorithmFlags()&TAFunstable));
         appendOutputLinked(this);
     }
     void start()
@@ -70,12 +72,28 @@ public:
         if (0 == iLoader->numRows())
             eoi = true;
     }
+    void serializeStats(MemoryBuffer &mb)
+    {
+        CSlaveActivity::serializeStats(mb);
+
+        CriticalBlock block(statsCs);
+        CRuntimeStatisticCollection mergedStats(spillStats);
+        mergeStats(mergedStats, iLoader);
+        mergedStats.serialize(mb);
+    }
+
     void stop()
     {
         out.clear();
         stopInput(input);
         dataLinkStop();
-        iLoader.clear();
+
+        //Critical block
+        {
+            CriticalBlock block(statsCs);
+            mergeStats(spillStats, iLoader);
+            iLoader.clear();
+        }
     }
     CATCH_NEXTROW()
     {

+ 27 - 8
thorlcr/activities/msort/thmsort.cpp

@@ -32,14 +32,33 @@
 //
 
 
-class CGroupSortActivityMaster : public CMasterActivity
+class CSortBaseActivityMaster : public CMasterActivity
 {
+    CThorStatsCollection extraStats;
 public:
-    CGroupSortActivityMaster(CMasterGraphElement * info) : CMasterActivity(info) { }
+    CSortBaseActivityMaster(CMasterGraphElement * info) : CMasterActivity(info), extraStats(spillStatistics) { }
+
+    virtual void deserializeStats(unsigned node, MemoryBuffer &mb)
+    {
+        CMasterActivity::deserializeStats(node, mb);
+
+        extraStats.deserializeMerge(node, mb);
+    }
+    virtual void getActivityStats(IStatisticGatherer & stats)
+    {
+        CMasterActivity::getActivityStats(stats);
+        extraStats.getStats(stats);
+    }
+};
+
+class CGroupSortActivityMaster : public CSortBaseActivityMaster
+{
+public:
+    CGroupSortActivityMaster(CMasterGraphElement * info) : CSortBaseActivityMaster(info) { }
 
     virtual void init()
     {
-        CMasterActivity::init();
+        CSortBaseActivityMaster::init();
         IHThorSortArg *helper = (IHThorSortArg *)queryHelper();
         IHThorAlgorithm *algo = static_cast<IHThorAlgorithm *>(helper->selectInterface(TAIalgorithm_1));
         OwnedRoxieString algoname = algo->getAlgorithm();
@@ -52,7 +71,7 @@ public:
     }
 };
 
-class CMSortActivityMaster : public CMasterActivity
+class CMSortActivityMaster : public CSortBaseActivityMaster
 {
     IThorSorterMaster *imaster;
     mptag_t mpTagRPC, barrierMpTag;
@@ -61,7 +80,7 @@ class CMSortActivityMaster : public CMasterActivity
 
 public:
     CMSortActivityMaster(CMasterGraphElement *info)
-      : CMasterActivity(info)
+      : CSortBaseActivityMaster(info)
     {
         mpTagRPC = container.queryJob().allocateMPTag();
         barrierMpTag = container.queryJob().allocateMPTag();
@@ -76,7 +95,7 @@ public:
 protected:
     virtual void init()
     {
-        CMasterActivity::init();
+        CSortBaseActivityMaster::init();
         IHThorSortArg *helper = (IHThorSortArg *)queryHelper();
         IHThorAlgorithm *algo = static_cast<IHThorAlgorithm *>(helper->selectInterface(TAIalgorithm_1));
         OwnedRoxieString algoname(algo->getAlgorithm());
@@ -111,7 +130,7 @@ protected:
     }   
     virtual void preStart(size32_t parentExtractSz, const byte *parentExtract)
     {
-        CMasterActivity::preStart(parentExtractSz, parentExtract);
+        CSortBaseActivityMaster::preStart(parentExtractSz, parentExtract);
         ActPrintLog("preStart");
         imaster = CreateThorSorterMaster(this);
         unsigned s=0;
@@ -126,7 +145,7 @@ protected:
     {
         ActPrintLog("process");
 
-        CMasterActivity::process();
+        CSortBaseActivityMaster::process();
 
         IHThorSortArg *helper = (IHThorSortArg *)queryHelper();
         StringBuffer skewV;

+ 18 - 2
thorlcr/activities/msort/thmsortslave.cpp

@@ -47,6 +47,8 @@ class MSortSlaveActivity : public CSlaveActivity, public CThorDataLink
     mptag_t mpTagRPC;
     Owned<IBarrier> barrier;
     SocketEndpoint server;
+    CriticalSection statsCs;
+    CRuntimeStatisticCollection spillStats;
 
     bool isUnstable()
     {
@@ -57,7 +59,7 @@ class MSortSlaveActivity : public CSlaveActivity, public CThorDataLink
 public:
     IMPLEMENT_IINTERFACE_USING(CSimpleInterface);
 
-    MSortSlaveActivity(CGraphElementBase *_container) : CSlaveActivity(_container), CThorDataLink(this)
+    MSortSlaveActivity(CGraphElementBase *_container) : CSlaveActivity(_container), CThorDataLink(this), spillStats(spillStatistics)
     {
         input = NULL;
         portbase = 0;
@@ -171,10 +173,24 @@ public:
     void kill()
     {
         ActPrintLog("MSortSlaveActivity::kill");
-        sorter.clear();
+
+        {
+            CriticalBlock block(statsCs);
+            mergeStats(spillStats, sorter);
+            sorter.clear();
+        }
+
         CSlaveActivity::kill();
     }
+    void serializeStats(MemoryBuffer &mb)
+    {
+        CSlaveActivity::serializeStats(mb);
 
+        CriticalBlock block(statsCs);
+        CRuntimeStatisticCollection mergedStats(spillStats);
+        mergeStats(mergedStats, sorter);
+        mergedStats.serialize(mb);
+    }
     CATCH_NEXTROW()
     {
         ActivityTimer t(totalCycles, timeActivities);

+ 7 - 1
thorlcr/activities/selfjoin/thselfjoinslave.cpp

@@ -49,6 +49,7 @@ private:
     CriticalSection joinHelperCrit;
     Owned<IBarrier> barrier;
     SocketEndpoint server;
+    CRuntimeStatisticCollection spillStats;
 
     bool isUnstable()
     {
@@ -65,6 +66,7 @@ private:
 #endif
         Owned<IThorRowLoader> iLoader = createThorRowLoader(*this, ::queryRowInterfaces(input), compare, isUnstable() ? stableSort_none : stableSort_earlyAlloc, rc_mixed, SPILL_PRIORITY_SELFJOIN);
         Owned<IRowStream> rs = iLoader->load(input, abortSoon);
+        mergeStats(spillStats, iLoader);  // Not sure of the best policy if rs spills later on.
         stopInput(input);
         input = NULL;
         return rs.getClear();
@@ -102,7 +104,7 @@ public:
     IMPLEMENT_IINTERFACE_USING(CSimpleInterface);
 
     SelfJoinSlaveActivity(CGraphElementBase *_container, bool _isLocal, bool _isLightweight)
-        : CSlaveActivity(_container), CThorDataLink(this)
+        : CSlaveActivity(_container), CThorDataLink(this), spillStats(spillStatistics)
     {
         isLocal = _isLocal||_isLightweight;
         isLightweight = _isLightweight;
@@ -236,6 +238,10 @@ public:
         CriticalBlock b(joinHelperCrit);
         rowcount_t p = joinhelper?joinhelper->getLhsProgress():0;
         mb.append(p);
+
+        CRuntimeStatisticCollection mergedStats(spillStats);
+        mergeStats(mergedStats, sorter);    // No danger of a race with reset() because that never replaces a valid sorter
+        mergedStats.serialize(mb);
     }
 };
 

+ 6 - 24
thorlcr/activities/thdiskbase.cpp

@@ -29,13 +29,10 @@
 #include "eclhelper.hpp" // tmp for IHThorArg interface
 #include "thdiskbase.ipp"
 
-CDiskReadMasterBase::CDiskReadMasterBase(CMasterGraphElement *info) : CMasterActivity(info)
+CDiskReadMasterBase::CDiskReadMasterBase(CMasterGraphElement *info) : CMasterActivity(info), diskStats(diskWriteRemoteStatistics)
 {
     hash = NULL;
     inputProgress.setown(new ProgressInfo);
-    statTimeDiskRead.setown(new CThorStats(StTimeDiskReadIO));
-    statSizeDiskRead.setown(new CThorStats(StSizeDiskRead));
-    statNumDiskReads.setown(new CThorStats(StNumDiskReads));
 }
 
 void CDiskReadMasterBase::init()
@@ -124,19 +121,13 @@ void CDiskReadMasterBase::deserializeStats(unsigned node, MemoryBuffer &mb)
     mb.read(progress);
     inputProgress->set(node, progress);
 
-    CRuntimeStatisticCollection fileStats(diskReadRemoteStatistics);
-    fileStats.deserialize(mb);
-    statTimeDiskRead->set(node, fileStats.getStatisticValue(StTimeDiskReadIO));
-    statSizeDiskRead->set(node, fileStats.getStatisticValue(StSizeDiskRead));
-    statNumDiskReads->set(node, fileStats.getStatisticValue(StNumDiskReads));
+    diskStats.deserializeMerge(node, mb);
 }
 
 void CDiskReadMasterBase::getActivityStats(IStatisticGatherer & stats)
 {
     CMasterActivity::getActivityStats(stats);
-    statTimeDiskRead->getStats(stats, false, false);
-    statSizeDiskRead->getStats(stats, false, false);
-    statNumDiskReads->getStats(stats, false, false);
+    diskStats.getStats(stats);
 }
 
 void CDiskReadMasterBase::getEdgeStats(IStatisticGatherer & stats, unsigned idx)
@@ -247,13 +238,10 @@ void CWriteMasterBase::publish()
         queryThorFileManager().publish(container.queryJob(), fileName, *fileDesc, NULL, targetOffset);
 }
 
-CWriteMasterBase::CWriteMasterBase(CMasterGraphElement *info) : CMasterActivity(info)
+CWriteMasterBase::CWriteMasterBase(CMasterGraphElement *info) : CMasterActivity(info), diskStats(diskWriteRemoteStatistics)
 {
     publishReplicatedDone = !globals->getPropBool("@replicateAsync", true);
     replicateProgress.setown(new ProgressInfo);
-    statTimeDiskWrite.setown(new CThorStats(StTimeDiskWriteIO));
-    statSizeDiskWrite.setown(new CThorStats(StSizeDiskWrite));
-    statNumDiskWrites.setown(new CThorStats(StNumDiskWrites));
 
     diskHelperBase = (IHThorDiskWriteArg *)queryHelper();
     targetOffset = 0;
@@ -266,11 +254,7 @@ void CWriteMasterBase::deserializeStats(unsigned node, MemoryBuffer &mb)
     mb.read(repPerc);
     replicateProgress->set(node, repPerc);
 
-    CRuntimeStatisticCollection fileStats(diskWriteRemoteStatistics);
-    fileStats.deserialize(mb);
-    statTimeDiskWrite->set(node, fileStats.getStatisticValue(StTimeDiskWriteIO));
-    statSizeDiskWrite->set(node, fileStats.getStatisticValue(StSizeDiskWrite));
-    statNumDiskWrites->set(node, fileStats.getStatisticValue(StNumDiskWrites));
+    diskStats.deserializeMerge(node, mb);
 }
 
 void CWriteMasterBase::getActivityStats(IStatisticGatherer & stats)
@@ -281,9 +265,7 @@ void CWriteMasterBase::getActivityStats(IStatisticGatherer & stats)
         replicateProgress->processInfo();
         stats.addStatistic(StPerReplicated, replicateProgress->queryAverage() * 10000);
     }
-    statTimeDiskWrite->getStats(stats, false, false);
-    statSizeDiskWrite->getStats(stats, false, false);
-    statNumDiskWrites->getStats(stats, false, false);
+    diskStats.getStats(stats);
 }
 
 void CWriteMasterBase::preStart(size32_t parentExtractSz, const byte *parentExtract)

+ 2 - 6
thorlcr/activities/thdiskbase.ipp

@@ -32,9 +32,7 @@ protected:
     Owned<CSlavePartMapping> mapping;
     IHash *hash;
     Owned<ProgressInfo> inputProgress;
-    Owned<CThorStats> statTimeDiskRead;
-    Owned<CThorStats> statSizeDiskRead;
-    Owned<CThorStats> statNumDiskReads;
+    CThorStatsCollection diskStats;
     StringAttr fileName;
 
 public:
@@ -51,9 +49,7 @@ class CWriteMasterBase : public CMasterActivity
 {
     bool publishReplicatedDone;
     Owned<ProgressInfo> replicateProgress;
-    Owned<CThorStats> statTimeDiskWrite;
-    Owned<CThorStats> statSizeDiskWrite;
-    Owned<CThorStats> statNumDiskWrites;
+    CThorStatsCollection diskStats;
     __int64 recordsProcessed;
     bool published;
     StringAttr fileName;

+ 23 - 3
thorlcr/graph/thgraphmaster.cpp

@@ -2764,6 +2764,21 @@ IThorResult *CMasterGraph::createGraphLoopResult(CActivityBase &activity, IRowIn
 
 ///////////////////////////////////////////////////
 
+static bool suppressStatisticIfZero(StatisticKind kind)
+{
+    switch (kind)
+    {
+    case StNumSpills:
+    case StSizeSpillFile:
+    case StTimeSpillElapsed:
+        return true;
+    }
+    return false;
+}
+
+
+///////////////////////////////////////////////////
+
 CThorStats::CThorStats(StatisticKind _kind) : kind(_kind)
 {
     unsigned c = queryClusterWidth();
@@ -2771,6 +2786,11 @@ CThorStats::CThorStats(StatisticKind _kind) : kind(_kind)
     reset();
 }
 
+void CThorStats::extract(unsigned node, const CRuntimeStatisticCollection & stats)
+{
+    set(node, stats.getStatisticValue(kind));
+}
+
 void CThorStats::set(unsigned node, unsigned __int64 count)
 {
     counts.replace(count, node);
@@ -2825,10 +2845,10 @@ void CThorStats::processInfo()
     calculateSkew();
 }
 
-void CThorStats::getStats(IStatisticGatherer & stats, bool suppressMinMaxWhenEqual, bool suppressIfZero)
+void CThorStats::getStats(IStatisticGatherer & stats, bool suppressMinMaxWhenEqual)
 {
     processInfo();
-    if (suppressIfZero && (0 == tot))
+    if ((0 == tot) && suppressStatisticIfZero(kind))
         return;
 
     //MORE: For most measures (not time stamps etc.) it would be sensible to output the total here....
@@ -2879,7 +2899,7 @@ void ProgressInfo::processInfo() // reimplement as counts have special flags (i.
 
 void ProgressInfo::getStats(IStatisticGatherer & stats)
 {
-    CThorStats::getStats(stats, true, false);
+    CThorStats::getStats(stats, true);
     stats.addStatistic(kind, tot);
     stats.addStatistic(StNumSlaves, counts.ordinality());
     stats.addStatistic(StNumStarted, startcount);

+ 44 - 2
thorlcr/graph/thgraphmaster.ipp

@@ -210,19 +210,61 @@ public:
     unsigned queryMaxNode() { return maxNode; }
     unsigned queryMinNode() { return minNode; }
 
+    void extract(unsigned node, const CRuntimeStatisticCollection & stats);
     void set(unsigned node, unsigned __int64 count);
-    void getStats(IStatisticGatherer & stats, bool suppressMinMaxWhenEqual, bool suppressIfZero);
+    void getStats(IStatisticGatherer & stats, bool suppressMinMaxWhenEqual);
 
 protected:
     void calculateSkew();
     void tallyValue(unsigned __int64 value, unsigned node);
 };
 
+class graphmaster_decl CThorStatsCollection : public CInterface
+{
+public:
+    CThorStatsCollection(const StatisticsMapping & _mapping) : mapping(_mapping)
+    {
+        unsigned num = mapping.numStatistics();
+        stats = new Owned<CThorStats>[num];
+        for (unsigned i=0; i < num; i++)
+            stats[i].setown(new CThorStats(mapping.getKind(i)));
+    }
+    ~CThorStatsCollection()
+    {
+        delete [] stats;
+    }
+
+    void deserializeMerge(unsigned node, MemoryBuffer & mb)
+    {
+        CRuntimeStatisticCollection nodeStats(mapping);
+        nodeStats.deserialize(mb);
+        extract(node, nodeStats);
+    }
+
+    void extract(unsigned node, const CRuntimeStatisticCollection & source)
+    {
+        for (unsigned i=0; i < mapping.numStatistics(); i++)
+            stats[i]->extract(node, source);
+    }
+
+    void getStats(IStatisticGatherer & result)
+    {
+        for (unsigned i=0; i < mapping.numStatistics(); i++)
+        {
+            stats[i]->getStats(result, false);
+        }
+    }
+
+private:
+    Owned<CThorStats> * stats;
+    const StatisticsMapping & mapping;
+};
+
 class graphmaster_decl CTimingInfo : public CThorStats
 {
 public:
     CTimingInfo();
-    void getStats(IStatisticGatherer & stats) { CThorStats::getStats(stats, false, false); }
+    void getStats(IStatisticGatherer & stats) { CThorStats::getStats(stats, false); }
 };
 
 class graphmaster_decl ProgressInfo : public CThorStats

+ 12 - 2
thorlcr/msort/tsorts.cpp

@@ -151,7 +151,7 @@ class CWriteIntercept : public CSimpleInterface
     class CFileOwningStream : public CSimpleInterface, implements IRowStream
     {
         Linked<CWriteIntercept> parent;
-        Owned<IRowStream> stream;
+        Owned<IExtRowStream> stream;
         offset_t startOffset;
         rowcount_t max;
     public:
@@ -596,6 +596,7 @@ class CThorSorter : public CSimpleInterface, implements IThorSorter, implements
     Semaphore startgathersem, finishedmergesem, closedownsem;
     InterruptableSemaphore startmergesem;
     size32_t transferblocksize, midkeybufsize;
+    CRuntimeStatisticCollection spillStats;
 
     class CRowToKeySerializer : public CSimpleInterfaceOf<IOutputRowSerializer>
     {
@@ -773,7 +774,7 @@ public:
 
     CThorSorter(CActivityBase *_activity, SocketEndpoint &ep, IDiskUsage *_iDiskUsage, ICommunicator *_clusterComm, mptag_t _mpTagRPC)
         : activity(_activity), myendpoint(ep), iDiskUsage(_iDiskUsage), clusterComm(_clusterComm), mpTagRPC(_mpTagRPC),
-          rowArray(*_activity, _activity), threaded("CThorSorter", this)
+          rowArray(*_activity, _activity), threaded("CThorSorter", this), spillStats(spillStatistics)
     {
         numnodes = 0;
         partno = 0;
@@ -1248,6 +1249,9 @@ public:
             PrintExceptionLog(e,"**Exception(2)");
             throw;
         }
+
+        mergeStats(spillStats, sortedloader);
+
         if (!abort)
         {
             transferblocksize = TRANSFERBLOCKSIZE;
@@ -1261,7 +1265,9 @@ public:
                 assertex(!intercept);
                 overflowinterval=sortedloader->overflowScale();
                 intercept.setown(new CWriteIntercept(*activity, rowif, overflowinterval));
+                CCycleTimer startCycles;
                 grandtotalsize = intercept->write(overflowstream);
+                spillStats.mergeStatistic(StTimeSpillElapsed, startCycles.elapsedNs());
                 intercept->transferRows(rowArray); // get sample rows
             }
             else // all in memory
@@ -1290,6 +1296,10 @@ public:
         ActPrintLog(activity, "Local merge finished");
         rowif.clear();
     }
+    virtual unsigned __int64 getStatistic(StatisticKind kind)
+    {
+        return spillStats.getStatisticValue(kind);
+    }
 };
 
 

+ 1 - 0
thorlcr/msort/tsorts.hpp

@@ -50,6 +50,7 @@ public:
         )=0;
     virtual IRowStream * startMerge(rowcount_t &totalrows)=0;
     virtual void stopMerge()=0;
+    virtual unsigned __int64 getStatistic(StatisticKind kind) = 0;
 };
 
 interface IDiskUsage;

+ 40 - 1
thorlcr/thorutil/thmem.cpp

@@ -1474,6 +1474,7 @@ protected:
     unsigned overflowCount;
     unsigned maxCores;
     unsigned outStreams;
+    offset_t sizeSpill;
     ICompare *iCompare;
     StableSortFlag stableSort;
     bool preserveGrouping;
@@ -1483,6 +1484,8 @@ protected:
     Owned<CSharedSpillableRowSet> spillableRowSet;
     unsigned options;
     bool compressSpills;
+    __uint64 spillCycles;
+    __uint64 sortCycles;
 
     bool spillRows()
     {
@@ -1491,6 +1494,7 @@ protected:
         if (numRows == 0)
             return false;
 
+        CCycleTimer spillTimer;
         totalRows += numRows;
         StringBuffer tempPrefix, tempName;
         if (iCompare)
@@ -1498,6 +1502,7 @@ protected:
             ActPrintLog(&activity, "Sorting %" RIPF "d rows", spillableRows.numCommitted());
             CCycleTimer timer;
             spillableRows.sort(*iCompare, maxCores); // sorts committed rows
+            sortCycles += timer.elapsedCycles();
             ActPrintLog(&activity, "Sort took: %f", ((float)timer.elapsedMs())/1000);
             tempPrefix.append("srt");
         }
@@ -1508,7 +1513,8 @@ protected:
         spillableRows.save(*iFile, compressSpills, spillPrefixStr.str()); // saves committed rows
         spillFiles.append(new CFileOwner(iFile.getLink()));
         ++overflowCount;
-
+        sizeSpill += iFile->size();
+        spillCycles += spillTimer.elapsedCycles();
         return true;
     }
     void setPreserveGrouping(bool _preserveGrouping)
@@ -1606,7 +1612,11 @@ protected:
                 {
                     // Option(rcflag_noAllInMemSort) - avoid sorting allMemRows
                     if ((NULL == allMemRows) || (0 == (options & rcflag_noAllInMemSort)))
+                    {
+                        CCycleTimer timer;
                         spillableRows.sort(*iCompare, maxCores);
+                        sortCycles += timer.elapsedCycles();
+                    }
                 }
 
                 if ((rc_allDiskOrAllMem == diskMemMix) || // must supply allMemRows, only here if no spilling (see above)
@@ -1655,6 +1665,7 @@ protected:
         spillFiles.kill();
         totalRows = 0;
         overflowCount = outStreams = 0;
+        sizeSpill = 0;
     }
     inline bool spillingEnabled() const { return SPILL_PRIORITY_DISABLE != spillPriority; }
     void clearSpillingCallback()
@@ -1682,6 +1693,7 @@ public:
         preserveGrouping = false;
         totalRows = 0;
         overflowCount = outStreams = 0;
+        sizeSpill = 0;
         mmRegistered = false;
         if (rc_allMem == diskMemMix)
             spillPriority = SPILL_PRIORITY_DISABLE; // all mem, implies no spilling
@@ -1691,6 +1703,8 @@ public:
         options = 0;
         spillableRows.setup(rowIf, false, stableSort);
         compressSpills = activity.getOptBool(THOROPT_COMPRESS_SPILLS, true);
+        spillCycles = 0;
+        sortCycles = 0;
     }
     ~CThorRowCollectorBase()
     {
@@ -1703,7 +1717,11 @@ public:
         spillableRows.flush();
         totalRows += spillableRows.numCommitted();
         if (sort && iCompare)
+        {
+            CCycleTimer timer;
             spillableRows.sort(*iCompare, maxCores);
+            sortCycles += timer.elapsedCycles();
+        }
         out.transferFrom(spillableRows);
     }
 // IThorRowCollectorCommon
@@ -1773,6 +1791,25 @@ public:
         CThorArrayLockBlock block(spillableRows);
         return spillRows();
     }
+    virtual unsigned __int64 getStatistic(StatisticKind kind)
+    {
+        switch (kind)
+        {
+        case StCycleSpillElapsedCycles:
+            return spillCycles;
+        case StCycleSortElapsedCycles:
+            return sortCycles;
+        case StTimeSpillElapsed:
+            return cycle_to_nanosec(spillCycles);
+        case StTimeSortElapsed:
+            return cycle_to_nanosec(sortCycles);
+        case StNumSpills:
+            return overflowCount;
+        case StSizeSpillFile:
+            return sizeSpill;
+        }
+        return 0;
+    }
 };
 
 enum TRLGroupFlag { trl_ungroup, trl_preserveGrouping, trl_stopAtEog };
@@ -1825,6 +1862,7 @@ public:
     }
     virtual void resize(rowidx_t max) { CThorRowCollectorBase::resize(max); }
     virtual void setOptions(unsigned options)  { CThorRowCollectorBase::setOptions(options); }
+    virtual unsigned __int64 getStatistic(StatisticKind kind) { return CThorRowCollectorBase::getStatistic(kind); }
 // IThorRowLoader
     virtual IRowStream *load(IRowStream *in, const bool &abort, bool preserveGrouping, CThorExpandingRowArray *allMemRows, memsize_t *memUsage, bool doReset)
     {
@@ -1876,6 +1914,7 @@ public:
     }
     virtual void resize(rowidx_t max) { CThorRowCollectorBase::resize(max); }
     virtual void setOptions(unsigned options) { CThorRowCollectorBase::setOptions(options); }
+    virtual unsigned __int64 getStatistic(StatisticKind kind) { return CThorRowCollectorBase::getStatistic(kind); }
 // IThorRowCollector
     virtual IRowWriter *getWriter()
     {

+ 1 - 0
thorlcr/thorutil/thmem.hpp

@@ -517,6 +517,7 @@ interface IThorRowCollectorCommon : extends IInterface
     virtual void setup(ICompare *iCompare, StableSortFlag stableSort=stableSort_none, RowCollectorSpillFlags diskMemMix=rc_mixed, unsigned spillPriority=50) = 0;
     virtual void resize(rowidx_t max) = 0;
     virtual void setOptions(unsigned options) = 0;
+    virtual unsigned __int64 getStatistic(StatisticKind kind) = 0;
 };
 
 interface IThorRowLoader : extends IThorRowCollectorCommon

+ 3 - 0
thorlcr/thorutil/thormisc.cpp

@@ -1336,3 +1336,6 @@ IPerfMonHook *createThorMemStatsPerfMonHook(CJobBase &job, int maxLevel, IPerfMo
     };
     return new CPerfMonHook(job, maxLevel, chain);
 }
+
+
+const StatisticsMapping spillStatistics(StTimeSpillElapsed, StTimeSortElapsed, StNumSpills, StSizeSpillFile, StKindNone);

+ 3 - 0
thorlcr/thorutil/thormisc.hpp

@@ -484,5 +484,8 @@ extern graph_decl void logDiskSpace();
 class CJobBase;
 extern graph_decl IPerfMonHook *createThorMemStatsPerfMonHook(CJobBase &job, int minLevel, IPerfMonHook *chain=NULL); // for passing to jdebug startPerformanceMonitor
 
+//statistics gathered by the different activities
+extern const graph_decl StatisticsMapping spillStatistics;
+
 #endif