Bladeren bron

HPCC-18018 Remove all calls to getStatistics()

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 7 jaren geleden
bovenliggende
commit
c7965cdb62

+ 136 - 58
common/workunit/workunit.cpp

@@ -359,8 +359,10 @@ static void createDefaultDescription(StringBuffer & description, StatisticKind k
             unsigned subId = atoi(subgraph + strlen(SubGraphScopePrefix));
 
             formatGraphTimerLabel(description, graphname, 0, subId);
+            return;
         }
     }
+    describeScope(description, scope);
 }
 
 /* Represents a single statistic */
@@ -1968,6 +1970,8 @@ protected:
 class StatisticAggregator : public CInterfaceOf<IWuScopeVisitor>
 {
 public:
+    StatisticAggregator(StatisticKind _search) : filter(_search) {}
+
     virtual void noteAttribute(WuAttr attr, const char * value) override { throwUnexpected(); }
     virtual void noteHint(const char * kind, const char * value) override { throwUnexpected(); }
 protected:
@@ -1977,6 +1981,8 @@ protected:
 class SimpleAggregator : public StatisticAggregator
 {
 public:
+    SimpleAggregator(StatisticKind _search) : StatisticAggregator(_search) {}
+
     virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override
     {
         if (filter.matches(kind, value, extra))
@@ -1990,6 +1996,24 @@ protected:
 };
 
 
+class SimpleReferenceAggregator : public StatisticAggregator
+{
+public:
+    SimpleReferenceAggregator(StatisticKind _search, StatsAggregation & _summary) : StatisticAggregator(_search), summary(_summary) {}
+
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override
+    {
+        if (filter.matches(kind, value, extra))
+            summary.noteValue(value);
+    }
+
+    //How should these be reported?  Should there be a playAggregates(IWuAggregatedScopeVisitor)
+    //with a noteAggregate(value, variant, value, grouping)?
+protected:
+    StatsAggregation & summary;
+};
+
+
 class GroupedAggregator : public StatisticAggregator
 {
 public:
@@ -2378,10 +2402,10 @@ WuScopeFilter::WuScopeFilter(const char * filter)
     finishedFilter();
 }
 
-void WuScopeFilter::addFilter(const char * filter)
+WuScopeFilter & WuScopeFilter::addFilter(const char * filter)
 {
     if (!filter)
-        return;
+        return *this;
 
     StringAttr option;
     StringAttr arg;
@@ -2414,7 +2438,7 @@ void WuScopeFilter::addFilter(const char * filter)
             break;
         }
         case FOsource:
-            setSource(arg);
+            addSource(arg);
             break;
         case FOwhere: // where[stat<op>value]
             addRequiredStat(arg);
@@ -2429,7 +2453,7 @@ void WuScopeFilter::addFilter(const char * filter)
             setIncludeScopeType(arg);
             break;
         case FOproperties:
-            addOutputProperties(arg);
+            addOutputProperties((WuPropertyTypes)getEnum(arg, propertyMappings));
             break;
         case FOstatistic:
             addOutputStatistic(arg);
@@ -2441,8 +2465,7 @@ void WuScopeFilter::addFilter(const char * filter)
             addOutputHint(arg);
             break;
         case FOmeasure:
-            desiredMeasure = queryMeasure(arg);
-            properties |= PTstatistics;
+            setMeasure(arg);
             break;
         case FOversion:
             minVersion = atoi64(arg);
@@ -2451,24 +2474,28 @@ void WuScopeFilter::addFilter(const char * filter)
             throw makeStringExceptionV(0, "Unrecognised filter option: %s", option.str());
         }
     }
+    return *this;
 }
 
-void WuScopeFilter::addScope(const char * scope)
+WuScopeFilter & WuScopeFilter::addScope(const char * scope)
 {
     scopeFilter.addScope(scope);
+    return *this;
 }
 
-void WuScopeFilter::addScopeType(const char * scopeType)
+WuScopeFilter & WuScopeFilter::addScopeType(const char * scopeType)
 {
     scopeFilter.addScopeType(queryScopeType(scopeType));
+    return *this;
 }
 
-void WuScopeFilter::addId(const char * id)
+WuScopeFilter & WuScopeFilter::addId(const char * id)
 {
     scopeFilter.addId(id);
+    return *this;
 }
 
-void WuScopeFilter::addOutput(const char * prop)
+WuScopeFilter & WuScopeFilter::addOutput(const char * prop)
 {
     if (queryStatisticKind(prop) != StKindNone)
         addOutputStatistic(prop);
@@ -2476,11 +2503,16 @@ void WuScopeFilter::addOutput(const char * prop)
         addOutputAttribute(prop);
     else
         addOutputHint(prop);
+    return *this;
+}
+
+WuScopeFilter & WuScopeFilter::addOutputStatistic(const char * prop)
+{
+    return addOutputStatistic(queryStatisticKind(prop));
 }
 
-void WuScopeFilter::addOutputStatistic(const char * prop)
+WuScopeFilter & WuScopeFilter::addOutputStatistic(StatisticKind stat)
 {
-    StatisticKind stat = queryStatisticKind(prop);
     if (stat != StKindNone)
     {
         if (stat != StKindAll)
@@ -2491,11 +2523,16 @@ void WuScopeFilter::addOutputStatistic(const char * prop)
     }
     else
         properties &= ~PTstatistics;
+    return *this;
+}
+
+WuScopeFilter & WuScopeFilter::addOutputAttribute(const char * prop)
+{
+    return addOutputAttribute(queryWuAttribute(prop));
 }
 
-void WuScopeFilter::addOutputAttribute(const char * prop)
+WuScopeFilter & WuScopeFilter::addOutputAttribute(WuAttr attr)
 {
-    WuAttr attr = queryWuAttribute(prop);
     if (attr != WANone)
     {
         if (attr != WAAll)
@@ -2506,10 +2543,11 @@ void WuScopeFilter::addOutputAttribute(const char * prop)
     }
     else
         properties &= ~PTattributes;
+    return *this;
 }
 
 
-void WuScopeFilter::addOutputHint(const char * prop)
+WuScopeFilter & WuScopeFilter::addOutputHint(const char * prop)
 {
     if (strieq(prop, "none"))
     {
@@ -2524,30 +2562,53 @@ void WuScopeFilter::addOutputHint(const char * prop)
             desiredHints.kill();
         properties |= PThints;
     }
+    return *this;
 }
 
-void WuScopeFilter::setIncludeMatch(bool value)
+WuScopeFilter & WuScopeFilter::setIncludeMatch(bool value)
 {
     include.matchedScope = value;
+    return *this;
 }
 
-void WuScopeFilter::setIncludeNesting(unsigned depth)
+WuScopeFilter & WuScopeFilter::setIncludeNesting(unsigned depth)
 {
     include.nestedDepth = depth;
+    return *this;
 }
 
-void WuScopeFilter::setIncludeScopeType(const char * scopeType)
+WuScopeFilter & WuScopeFilter::setIncludeScopeType(const char * scopeType)
 {
     include.scopeTypes.append(queryScopeType(scopeType));
+    return *this;
+}
+
+WuScopeFilter & WuScopeFilter::setMeasure(const char * measure)
+{
+    desiredMeasure = queryMeasure(measure);
+    properties |= PTstatistics;
+    return *this;
 }
 
-void WuScopeFilter::addOutputProperties(const char * prop)
+WuScopeFilter & WuScopeFilter::addOutputProperties(WuPropertyTypes mask)
 {
-    WuPropertyTypes mask = (WuPropertyTypes)getEnum(prop, propertyMappings);
     if (properties == PTnone)
         properties = mask;
     else
         properties |= mask;
+    return *this;
+}
+
+WuScopeFilter & WuScopeFilter::addRequiredStat(StatisticKind statKind, stat_type lowValue, stat_type highValue)
+{
+    requiredStats.emplace_back(statKind, lowValue, highValue);
+    return *this;
+}
+
+WuScopeFilter & WuScopeFilter::addRequiredStat(StatisticKind statKind)
+{
+    requiredStats.emplace_back(statKind, 0, MaxStatisticValue);
+    return *this;
 }
 
 //This does not strictly validate - invalid filters may be accepted
@@ -2609,18 +2670,20 @@ void WuScopeFilter::addRequiredStat(const char * filter)
     requiredStats.emplace_back(statKind, lowValue, highValue);
 }
 
-void WuScopeFilter::setSource(const char * source)
+WuScopeFilter & WuScopeFilter::addSource(const char * source)
 {
     WuScopeSourceFlags mask = (WuScopeSourceFlags)getEnum(source, sourceMappings);
     if (!mask)
         sourceFlags = mask;
     else
         sourceFlags |= mask;
+    return *this;
 }
 
-void WuScopeFilter::setDepth(unsigned low, unsigned high)
+WuScopeFilter & WuScopeFilter::setDepth(unsigned low, unsigned high)
 {
     scopeFilter.setDepth(low, high);
+    return *this;
 }
 
 bool WuScopeFilter::matchOnly(StatisticScopeType scopeType) const
@@ -2646,6 +2709,8 @@ void WuScopeFilter::finishedFilter()
     optimized = true;
 
     preFilterScope = include.matchedScope && (include.nestedDepth == 0);
+    if (scopeFilter.canAlwaysPreFilter())
+        preFilterScope = true;
 
     //If the source flags have not been explicitly set then calculate which sources will provide the results
     if (!sourceFlags)
@@ -3399,10 +3464,12 @@ public:
             { return c->getRunningGraph(graphName, subId); }
     virtual IConstWUStatisticIterator & getStatistics(const IStatisticsFilter * filter) const
             { return c->getStatistics(filter); }
-    virtual IConstWUStatistic * getStatistic(const char * creator, const char * scope, StatisticKind kind) const
-            { return c->getStatistic(creator, scope, kind); }
+    virtual IConstWUStatistic * getStatistic(const char * scope, StatisticKind kind) const
+            { return c->getStatistic(scope, kind); }
     virtual IConstWUScopeIterator & getScopeIterator(const WuScopeFilter & filter) const override
             { return c->getScopeIterator(filter); }
+    virtual bool getStatistic(stat_type & value, const char * scope, StatisticKind kind) const override
+            { return c->getStatistic(value, scope, kind); }
     virtual IStringVal & getSnapshot(IStringVal & str) const
             { return c->getSnapshot(str); } 
     virtual const char *queryUser() const
@@ -4831,6 +4898,7 @@ public:
         conn = sdsManager->connect(wuRoot.str(), session, RTM_LOCK_WRITE|RTM_CREATE_UNIQUE, SDS_LOCK_TIMEOUT);
         conn->queryRoot()->setProp("@xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance");
         conn->queryRoot()->setPropInt("@wuidVersion", WUID_VERSION);
+        conn->queryRoot()->setProp("@totalThorTime", "");
         return new CDaliWorkUnit(conn, secmgr, secuser);
     }
 
@@ -7516,6 +7584,7 @@ void CLocalWorkUnit::copyWorkUnit(IConstWorkUnit *cached, bool copyStats, bool a
     p->setProp("@codeVersion", fromP->queryProp("@codeVersion"));
     p->setProp("@buildVersion", fromP->queryProp("@buildVersion"));
     p->setProp("@eclVersion", fromP->queryProp("@eclVersion"));
+    p->setProp("@totalThorTime", fromP->queryProp("@totalThorTime"));
     p->setProp("@hash", fromP->queryProp("@hash"));
     p->setPropBool("@cloneable", true);
     p->setPropBool("@isClone", true);
@@ -7986,10 +8055,20 @@ void CLocalWorkUnit::setStatistic(StatisticCreatorType creatorType, const char *
         else
             statTree->removeProp("@max");
     }
-    if (creatorType==SCTsummary && kind==StTimeElapsed && isGlobalScope(scope))
+
+    //Whenever a graph time is updated recalculate the total time spent in thor, and save it
+    if ((scopeType == SSTgraph) && (kind == StTimeElapsed))
     {
+        _loadStatistics();
+        stat_type totalTime = 0;
+        ForEachItemIn(i, statistics)
+        {
+            IConstWUStatistic & cur = statistics.item(i);
+            if ((cur.getScopeType() == SSTgraph) && (cur.getKind() == StTimeElapsed))
+                totalTime += cur.getValue();
+        }
         StringBuffer t;
-        formatTimeCollatable(t, value, false);
+        formatTimeCollatable(t, totalTime, false);
         p->setProp("@totalThorTime", t);
     }
 }
@@ -8012,17 +8091,37 @@ IConstWUStatisticIterator& CLocalWorkUnit::getStatistics(const IStatisticsFilter
     return * new CCompoundIteratorOf<IConstWUStatisticIterator, IConstWUStatistic>(localStats, graphStats);
 }
 
-IConstWUStatistic * CLocalWorkUnit::getStatistic(const char * creator, const char * scope, StatisticKind kind) const
+IConstWUStatistic * CLocalWorkUnit::getStatistic(const char * scope, StatisticKind kind) const
 {
+#if 0
+    //MORE: Optimize this....
+    WuScopeFilter filter;
+    filter.addScope(scope).setIncludeNesting(0).finishedFilter();
+    Owned<IConstWUScopeIterator> stats = &getScopeIterator(&filter);
+    if (stats->first())
+        return stats->getStat(kind, value);
+    return NULL;
+#else
     //MORE: Optimize this....
     StatisticsFilter filter;
-    filter.setCreator(creator);
     filter.setScope(scope);
     filter.setKind(kind);
     Owned<IConstWUStatisticIterator> stats = &getStatistics(&filter);
     if (stats->first())
         return LINK(&stats->query());
     return NULL;
+#endif
+}
+
+bool CLocalWorkUnit::getStatistic(stat_type & value, const char * scope, StatisticKind kind) const
+{
+    //MORE: Optimize this....
+    WuScopeFilter filter;
+    filter.addScope(scope).setIncludeNesting(0).addRequiredStat(kind).addOutputStatistic(kind).finishedFilter();
+    Owned<IConstWUScopeIterator> stats = &getScopeIterator(filter);
+    if (stats->first())
+        return stats->getStat(kind, value);
+    return false;
 }
 
 IConstWUScopeIterator & CLocalWorkUnit::getScopeIterator(const WuScopeFilter & filter) const
@@ -11016,7 +11115,10 @@ StatisticKind CLocalWUStatistic::getKind() const
 
 const char * CLocalWUStatistic::queryScope() const
 {
-    return p->queryProp("@scope");
+    const char * scope = p->queryProp("@scope");
+    if (scope && streq(scope, LEGACY_GLOBAL_SCOPE))
+        scope = GLOBAL_SCOPE;
+    return scope;
 }
 
 StatisticMeasure CLocalWUStatistic::getMeasure() const
@@ -12790,42 +12892,18 @@ extern WORKUNIT_API void updateWorkunitTimings(IWorkUnit * wu, StatisticScopeTyp
 }
 
 
-extern WORKUNIT_API void getWorkunitTotalTime(IConstWorkUnit* workunit, const char* creator, unsigned __int64 & totalTimeNs, unsigned __int64 & totalThisTimeNs)
-{
-    StatisticsFilter summaryTimeFilter(SCTsummary, creator, SSTglobal, GLOBAL_SCOPE, SMeasureTimeNs, StTimeElapsed);
-    Owned<IConstWUStatistic> totalThorTime = getStatistic(workunit, summaryTimeFilter);
-    if (!totalThorTime)
-    {
-        StatisticsFilter legacySummaryTimeFilter(SCTsummary, creator, SSTglobal, LEGACY_GLOBAL_SCOPE, SMeasureTimeNs, StTimeElapsed);
-        totalThorTime.setown(getStatistic(workunit, legacySummaryTimeFilter));
-    }
-
-    Owned<IConstWUStatistic> totalThisThorTime = workunit->getStatistic(queryStatisticsComponentName(), GLOBAL_SCOPE, StTimeElapsed);
-    if (!totalThisThorTime)
-        totalThisThorTime.setown(workunit->getStatistic(queryStatisticsComponentName(), LEGACY_GLOBAL_SCOPE, StTimeElapsed));
-
-    if (totalThorTime)
-        totalTimeNs = totalThorTime->getValue();
-    else
-        totalTimeNs = 0;
-    if (totalThisThorTime)
-        totalThisTimeNs = totalThisThorTime->getValue();
-    else
-        totalThisTimeNs = 0;
-}
-
 extern WORKUNIT_API void addTimeStamp(IWorkUnit * wu, StatisticScopeType scopeType, const char * scope, StatisticKind kind)
 {
     wu->setStatistic(queryStatisticsComponentType(), queryStatisticsComponentName(), scopeType, scope, kind, NULL, getTimeStampNowValue(), 1, 0, StatsMergeAppend);
 }
 
 
-IConstWUStatistic * getStatistic(IConstWorkUnit * wu, const IStatisticsFilter & filter)
+void aggregateStatistic(StatsAggregation & result, IConstWorkUnit * wu, const WuScopeFilter & filter, StatisticKind search)
 {
-    Owned<IConstWUStatisticIterator> iter = &wu->getStatistics(&filter);
-    if (iter->first())
-        return &OLINK(iter->query());
-    return NULL;
+    SimpleReferenceAggregator aggregator(search, result);
+    Owned<IConstWUScopeIterator> it = &wu->getScopeIterator(filter);
+    ForEach(*it)
+        it->playProperties(PTstatistics, aggregator);
 }
 
 

+ 33 - 20
common/workunit/workunit.hpp

@@ -993,7 +993,7 @@ interface IConstWUStatisticIterator : extends IScmIterator
 //---------------------------------------------------------------------------------------------------------------------
 
 /*
- * An interface that is provided as a callback to a scope iterator to report the when iterating scopes
+ * An interface that is provided as a callback to a scope iterator to report properties when iterating scopes
  */
 interface IWuScopeVisitor
 {
@@ -1002,6 +1002,13 @@ interface IWuScopeVisitor
     virtual void noteHint(const char * kind, const char * value) = 0;
 };
 
+class WORKUNIT_API WuScopeVisitorBase : implements IWuScopeVisitor
+{
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override {}
+    virtual void noteAttribute(WuAttr attr, const char * value) override {}
+    virtual void noteHint(const char * kind, const char * value) override {}
+};
+
 /*
  * Interface for an iterator that walks through the different logical elements (scopes) within a workunit
  */
@@ -1043,22 +1050,28 @@ public:
     WuScopeFilter() = default;
     WuScopeFilter(const char * filter);
 
-    void addFilter(const char * filter);
-    void addScope(const char * scope);
-    void addScopeType(const char * scopeType);
-    void addId(const char * id);
-    void setDepth(unsigned low, unsigned high);
-    void setSource(const char * source);
-
-    void setIncludeMatch(bool value);
-    void setIncludeNesting(unsigned depth);
-    void setIncludeScopeType(const char * scopeType);
-
-    void addOutput(const char * prop);              // Which statistics/properties/hints are required.
-    void addOutputProperties(const char * prop);    // stat/attr/hint/scope
-    void addOutputStatistic(const char * prop);
-    void addOutputAttribute(const char * prop);
-    void addOutputHint(const char * prop);
+    WuScopeFilter & addFilter(const char * filter);
+    WuScopeFilter & addScope(const char * scope);
+    WuScopeFilter & addScopeType(const char * scopeType);
+    WuScopeFilter & addId(const char * id);
+    WuScopeFilter & setDepth(unsigned low, unsigned high);
+    WuScopeFilter & addSource(const char * source);
+
+    WuScopeFilter & setIncludeMatch(bool value);
+    WuScopeFilter & setIncludeNesting(unsigned depth);
+    WuScopeFilter & setIncludeScopeType(const char * scopeType);
+    WuScopeFilter & setMeasure(const char * measure);
+
+    WuScopeFilter & addOutput(const char * prop);              // Which statistics/properties/hints are required.
+    WuScopeFilter & addOutputProperties(WuPropertyTypes prop); // stat/attr/hint/scope etc.
+    WuScopeFilter & addOutputStatistic(StatisticKind stat);
+    WuScopeFilter & addOutputStatistic(const char * prop);
+    WuScopeFilter & addOutputAttribute(WuAttr attr);
+    WuScopeFilter & addOutputAttribute(const char * prop);
+    WuScopeFilter & addOutputHint(const char * prop);
+
+    WuScopeFilter & addRequiredStat(StatisticKind statKind);
+    WuScopeFilter & addRequiredStat(StatisticKind statKind, stat_type lowValue, stat_type highValue);
 
     void finishedFilter(); // Call once filter has been completely set up
 
@@ -1211,7 +1224,8 @@ interface IConstWorkUnit : extends IConstWorkUnitInfo
     virtual bool getRunningGraph(IStringVal & graphName, WUGraphIDType & subId) const = 0;
     virtual IConstWUWebServicesInfo * getWebServicesInfo() const = 0;
     virtual IConstWUStatisticIterator & getStatistics(const IStatisticsFilter * filter) const = 0; // filter must currently stay alive while the iterator does.
-    virtual IConstWUStatistic * getStatistic(const char * creator, const char * scope, StatisticKind kind) const = 0;
+    virtual IConstWUStatistic * getStatistic(const char * scope, StatisticKind kind) const = 0;
+    virtual bool getStatistic(stat_type & value, const char * scope, StatisticKind kind) const = 0;
     virtual IConstWUScopeIterator & getScopeIterator(const WuScopeFilter & filter) const = 0; // filter must currently stay alive while the iterator does.
     virtual IConstWUResult * getVariableByName(const char * name) const = 0;
     virtual IConstWUResultIterator & getVariables() const = 0;
@@ -1662,8 +1676,7 @@ interface ITimeReporter;
 extern WORKUNIT_API void updateWorkunitTimeStat(IWorkUnit * wu, StatisticScopeType scopeType, const char * scope, StatisticKind kind, const char * description, unsigned __int64 value);
 extern WORKUNIT_API void updateWorkunitTimings(IWorkUnit * wu, ITimeReporter *timer);
 extern WORKUNIT_API void updateWorkunitTimings(IWorkUnit * wu, StatisticScopeType scopeType, StatisticKind kind, ITimeReporter *timer);
-extern WORKUNIT_API void getWorkunitTotalTime(IConstWorkUnit* workunit, const char* creator, unsigned __int64 & totalTimeNs, unsigned __int64 & totalThisTimeNs);
-extern WORKUNIT_API IConstWUStatistic * getStatistic(IConstWorkUnit * wu, const IStatisticsFilter & filter);
+extern WORKUNIT_API void aggregateStatistic(StatsAggregation & result, IConstWorkUnit * wu, const WuScopeFilter & filter, StatisticKind search);
 
 extern WORKUNIT_API const char *getTargetClusterComponentName(const char *clustname, const char *processType, StringBuffer &name);
 extern WORKUNIT_API void descheduleWorkunit(char const * wuid);

+ 3 - 1
common/workunit/workunit.ipp

@@ -112,6 +112,7 @@ template <typename T, typename IT> struct CachedTags
 
     operator IArrayOf<IT>&() { return tags; }
     unsigned ordinality() const { return tags.ordinality(); }
+    IT & item(unsigned i) const { return tags.item(i); }
 
     void kill()
     {
@@ -302,8 +303,9 @@ public:
     virtual IConstWUResult * getTemporaryByName(const char * name) const;
     virtual IConstWUResultIterator & getTemporaries() const;
     virtual IConstWUStatisticIterator & getStatistics(const IStatisticsFilter * filter) const;
-    virtual IConstWUStatistic * getStatistic(const char * creator, const char * scope, StatisticKind kind) const;
+    virtual IConstWUStatistic * getStatistic(const char * scope, StatisticKind kind) const;
     virtual IConstWUScopeIterator & getScopeIterator(const WuScopeFilter & filter) const override;
+    virtual bool getStatistic(stat_type & value, const char * scope, StatisticKind kind) const override;
     virtual IConstWUWebServicesInfo * getWebServicesInfo() const;
     virtual IStringVal & getXmlParams(IStringVal & params, bool hidePasswords) const;
     virtual const IPropertyTree *getXmlParams() const;

+ 0 - 159
dali/daliadmin/daliadmin.cpp

@@ -123,8 +123,6 @@ void usage(const char *exe)
   printf("  validatestore [fix=<true|false>]\n"
          "                [verbose=<true|false>]\n"
          "                [deletefiles=<true|false>]-- perform some checks on dali meta data an optionally fix or remove redundant info \n");
-  printf("  stats <workunit> [<creator-type> <creator> <scope-type> <scope> <kind>|category'['value']',...]\n"
-         "                                  -- dump the statistics for a workunit\n");
   printf("  workunit <workunit> [true]      -- dump workunit xml, if 2nd parameter equals true, will also include progress data\n");
   printf("  wuidcompress <wildcard> <type>  --  scan workunits that match <wildcard> and compress resources of <type>\n");
   printf("  wuiddecompress <wildcard> <type> --  scan workunits that match <wildcard> and decompress resources of <type>\n");
@@ -2538,148 +2536,6 @@ static void dumpProgress(const char *wuid, const char * graph)
     saveXML("stdout:", tree);
 }
 
-static const char * checkDash(const char * s)
-{
-    //Supplying * on the command line is a pain because it needs quoting. Allow - instead.
-    if (streq(s, ".") || streq(s, "-"))
-        return "*";
-    return s;
-}
-
-static void dumpStats(IConstWorkUnit * workunit, const StatisticsFilter & filter, bool csv)
-{
-    Owned<IConstWUStatisticIterator> stats = &workunit->getStatistics(&filter);
-    if (!csv)
-        printf("<Statistics wuid=\"%s\">\n", workunit->queryWuid());
-    ForEach(*stats)
-    {
-        IConstWUStatistic & cur = stats->query();
-        StringBuffer xml;
-        SCMStringBuffer curCreator;
-        SCMStringBuffer curDescription;
-        SCMStringBuffer curFormattedValue;
-
-        StatisticCreatorType curCreatorType = cur.getCreatorType();
-        StatisticScopeType curScopeType = cur.getScopeType();
-        StatisticMeasure curMeasure = cur.getMeasure();
-        StatisticKind curKind = cur.getKind();
-        unsigned __int64 value = cur.getValue();
-        unsigned __int64 count = cur.getCount();
-        unsigned __int64 max = cur.getMax();
-        unsigned __int64 ts = cur.getTimestamp();
-        const char * curScope = cur.queryScope();
-        cur.getCreator(curCreator);
-        cur.getDescription(curDescription, false);
-        cur.getFormattedValue(curFormattedValue);
-
-        if (csv)
-        {
-            xml.append(workunit->queryWuid());
-            xml.append(",");
-            if (curCreatorType != SCTnone)
-                xml.append(queryCreatorTypeName(curCreatorType));
-            xml.append(",");
-            if (curCreator.length())
-                xml.append(curCreator.str());
-            xml.append(",");
-            if (curScopeType != SSTnone)
-                xml.append(queryScopeTypeName(curScopeType));
-            xml.append(",");
-            if (!isEmptyString(curScope))
-                xml.append(curScope);
-            xml.append(",");
-            if (curMeasure != SMeasureNone)
-                xml.append(queryMeasureName(curMeasure));
-            xml.append(",");
-            if (curKind != StKindNone)
-                xml.append(queryStatisticName(curKind));
-            xml.append(",");
-            xml.append(value);
-            xml.append(",");
-            xml.append(curFormattedValue);
-            xml.append(",");
-            if (count != 1)
-                xml.append(count);
-            xml.append(",");
-            if (max)
-                xml.append(max);
-            xml.append(",");
-            if (ts)
-                formatStatistic(xml, ts, SMeasureTimestampUs);
-            xml.append(",");
-            if (curDescription.length())
-                xml.append('"').append(curDescription.str()).append('"');
-            printf("%s\n", xml.str());
-        }
-        else
-        {
-            if (curCreatorType != SCTnone)
-                xml.append("<ctype>").append(queryCreatorTypeName(curCreatorType)).append("</ctype>");
-            if (curCreator.length())
-                xml.append("<creator>").append(curCreator.str()).append("</creator>");
-            if (curScopeType != SSTnone)
-                xml.append("<stype>").append(queryScopeTypeName(curScopeType)).append("</stype>");
-            if (!isEmptyString(curScope))
-                xml.append("<scope>").append(curScope).append("</scope>");
-            if (curMeasure != SMeasureNone)
-                xml.append("<unit>").append(queryMeasureName(curMeasure)).append("</unit>");
-            if (curKind != StKindNone)
-                xml.append("<kind>").append(queryStatisticName(curKind)).append("</kind>");
-            xml.append("<rawvalue>").append(value).append("</rawvalue>");
-            xml.append("<value>").append(curFormattedValue).append("</value>");
-            if (count != 1)
-                xml.append("<count>").append(count).append("</count>");
-            if (max)
-                xml.append("<max>").append(value).append("</max>");
-            if (ts)
-            {
-                xml.append("<ts>");
-                formatStatistic(xml, ts, SMeasureTimestampUs);
-                xml.append("</ts>");
-            }
-            if (curDescription.length())
-                xml.append("<desc>").append(curDescription.str()).append("</desc>");
-            printf("<stat>%s</stat>\n", xml.str());
-        }
-    }
-    if (!csv)
-        printf("</Statistics>\n");
-}
-
-static void dumpStats(const char *wuid, const char * creatorTypeText, const char * creator, const char * scopeTypeText, const char * scope, const char * kindText, const char * userFilter, bool csv)
-{
-    StatisticsFilter filter(checkDash(creatorTypeText), checkDash(creator), checkDash(scopeTypeText), checkDash(scope), NULL, checkDash(kindText));
-    if (userFilter)
-        filter.setFilter(userFilter);
-
-    Owned<IWorkUnitFactory> factory = getWorkUnitFactory();
-    const char * star = strchr(wuid, '*');
-    if (star)
-    {
-        WUSortField filters[2];
-        MemoryBuffer filterbuf;
-        filters[0] = WUSFwildwuid;
-        filterbuf.append(wuid);
-        filters[1] = WUSFterm;
-        Owned<IConstWorkUnitIterator> iter = factory->getWorkUnitsSorted((WUSortField) (WUSFwuid), filters, filterbuf.bufferBase(), 0, INT_MAX, NULL, NULL);
-
-        ForEach(*iter)
-        {
-            Owned<IConstWorkUnit> workunit = factory->openWorkUnit(iter->query().queryWuid());
-            if (workunit)
-                dumpStats(workunit, filter, csv);
-        }
-    }
-    else
-    {
-        Owned<IConstWorkUnit> workunit = factory->openWorkUnit(wuid);
-        if (!workunit)
-            return;
-        dumpStats(workunit, filter, csv);
-    }
-}
-
-
 /* Callback used to output the different scope properties as xml */
 class ScopeDumper : public IWuScopeVisitor
 {
@@ -3629,21 +3485,6 @@ int main(int argc, char* argv[])
                         CHECKPARAMS(2,2);
                         dumpProgress(params.item(1), params.item(2));
                     }
-                    else if (strieq(cmd, "stats")) {
-                        CHECKPARAMS(1, 7);
-                        if ((params.ordinality() >= 3) && (strchr(params.item(2), '[')))
-                        {
-                            bool csv = params.isItem(3) && strieq(params.item(3), "csv");
-                            dumpStats(params.item(1), "-", "-", "-", "-", "-", params.item(2), csv);
-                        }
-                        else
-                        {
-                            while (params.ordinality() < 7)
-                                params.append("*");
-                            bool csv = params.isItem(7) && strieq(params.item(7), "csv");
-                            dumpStats(params.item(1), params.item(2), params.item(3), params.item(4), params.item(5), params.item(6), nullptr, csv);
-                        }
-                    }
                     else if (strieq(cmd, "migratefiles"))
                     {
                         CHECKPARAMS(2, 7);

+ 0 - 7
ecl/eclagent/eclgraph.cpp

@@ -1194,15 +1194,8 @@ void EclGraph::execute(const byte * parentExtract)
             StringBuffer description;
             formatGraphTimerLabel(description, queryGraphName(), 0, 0);
 
-            unsigned __int64 totalTimeNs = 0;
-            unsigned __int64 totalThisTimeNs = 0;
             unsigned __int64 elapsedNs = milliToNano(elapsed);
-            const char *totalTimeStr = "Total cluster time";
-            getWorkunitTotalTime(wu, "hthor", totalTimeNs, totalThisTimeNs);
-
             updateWorkunitTimeStat(wu, SSTgraph, queryGraphName(), StTimeElapsed, description.str(), elapsedNs);
-            updateWorkunitTimeStat(wu, SSTglobal, GLOBAL_SCOPE, StTimeElapsed, NULL, totalThisTimeNs+elapsedNs);
-            wu->setStatistic(SCTsummary, "hthor", SSTglobal, GLOBAL_SCOPE, StTimeElapsed, totalTimeStr, totalTimeNs+elapsedNs, 1, 0, StatsMergeReplace);
         }
 
         if (agent->queryRemoteWorkunit())

+ 25 - 17
ecl/eclcc/eclcc.cpp

@@ -1979,6 +1979,26 @@ void EclCompileInstance::checkEclVersionCompatible()
     ::checkEclVersionCompatible(errorProcessor, eclVersion);
 }
 
+class StatsLogger : public WuScopeVisitorBase
+{
+public:
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & cur) override
+    {
+        const char * scope = cur.queryScope();
+        OwnedPTree tree = createPTree("stat", ipt_fast);
+        tree->setProp("@kind", queryStatisticName(cur.getKind()));
+        tree->setProp("@scope", scope);
+        tree->setPropInt("@scopeType", (unsigned)cur.getScopeType());
+        tree->setPropInt64("@value", cur.getValue());
+        tree->setPropInt64("@max", cur.getMax());
+        tree->setPropInt64("@count", cur.getCount());
+
+        StringBuffer msg;
+        toXML(tree, msg, 0, XML_Embed);
+        fprintf(stderr, "%s\n", msg.str());
+    }
+};
+
 void EclCompileInstance::logStats(bool logTimings)
 {
     if (wu && wu->getDebugValueBool("logCompileStats", false))
@@ -1996,23 +2016,11 @@ void EclCompileInstance::logStats(bool logTimings)
 
     if (logTimings)
     {
-        Owned<IConstWUStatisticIterator> stats = &wu->getStatistics(nullptr);
-        ForEach(*stats)
-        {
-            IConstWUStatistic & cur = stats->query();
-            const char * scope = cur.queryScope();
-            OwnedPTree tree = createPTree("stat", ipt_fast);
-            tree->setProp("@kind", queryStatisticName(cur.getKind()));
-            tree->setProp("@scope", scope);
-            tree->setPropInt("@scopeType", (unsigned)cur.getScopeType());
-            tree->setPropInt64("@value", cur.getValue());
-            tree->setPropInt64("@max", cur.getMax());
-            tree->setPropInt64("@count", cur.getCount());
-
-            StringBuffer msg;
-            toXML(tree, msg, 0, XML_Embed);
-            fprintf(stderr, "%s\n", msg.str());
-        }
+        const WuScopeFilter filter("prop[stat]");
+        StatsLogger logger;
+        Owned<IConstWUScopeIterator> scopes = &wu->getScopeIterator(filter);
+        ForEach(*scopes)
+            scopes->playProperties(PTall, logger);
     }
 }
 

+ 162 - 119
esp/services/ws_workunits/ws_workunitsHelpers.cpp

@@ -40,6 +40,8 @@
 
 namespace ws_workunits {
 
+const char * const timerFilterText = "measure[time],source[global],depth[1,]"; // Does not include hthor subgraph timings
+
 SecAccessFlags chooseWuAccessFlagsByOwnership(const char *user, const char *owner, SecAccessFlags accessOwn, SecAccessFlags accessOthers)
 {
     return (isEmpty(owner) || (user && streq(user, owner))) ? accessOwn : accessOthers;
@@ -378,58 +380,52 @@ void WsWuInfo::addTimerToList(SCMStringBuffer& name, const char * scope, IConstW
 
 void WsWuInfo::doGetTimers(IArrayOf<IEspECLTimer>& timers)
 {
-    unsigned __int64 totalThorTimeValue = 0;
-    unsigned __int64 totalThorTimerCount = 0; //Do we need this?
-
-    StatisticsFilter filter;
-    filter.setScopeDepth(1, 2);
-    filter.setMeasure(SMeasureTimeNs);
-    Owned<IConstWUStatisticIterator> it = &cw->getStatistics(&filter);
-    if (it->first())
+    class TimingVisitor : public WuScopeVisitorBase
     {
-        ForEach(*it)
+    public:
+        TimingVisitor(WsWuInfo & _wuInfo, IArrayOf<IEspECLTimer>& _timers) : wuInfo(_wuInfo), timers(_timers) {}
+
+        virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override
         {
-            IConstWUStatistic & cur = it->query();
             SCMStringBuffer name;
-            cur.getDescription(name, true);
-            const char * scope = cur.queryScope();
+            extra.getDescription(name, true);
+            const char * scope = extra.queryScope();
+            wuInfo.addTimerToList(name, scope, extra, timers);
 
-            bool isThorTiming = false;//Should it be renamed as isClusterTiming?
-            if ((cur.getCreatorType() == SCTsummary) && (cur.getKind() == StTimeElapsed) && isGlobalScope(scope))
-            {
-                SCMStringBuffer creator;
-                cur.getCreator(creator);
-                if (streq(creator.str(), "thor") || streq(creator.str(), "hthor") ||
-                    streq(creator.str(), "roxie"))
-                    isThorTiming = true;
-            }
-            else if (strieq(name.str(), TOTALTHORTIME)) // legacy
-                isThorTiming = true;
+            //Aggregate all the times spent executing graphs
+            if ((kind == StTimeElapsed) && (extra.getScopeType() == SSTgraph))
+                totalGraphTime.noteValue(value);
+        }
 
-            if (isThorTiming)
+        void addSummary()
+        {
+            if (totalGraphTime.getCount())
             {
-                totalThorTimeValue += cur.getValue();
-                totalThorTimerCount += cur.getCount();
+                StringBuffer totalThorTimeText;
+                formatStatistic(totalThorTimeText, totalGraphTime.getSum(), SMeasureTimeNs);
+
+                Owned<IEspECLTimer> t= createECLTimer("","");
+                if (wuInfo.version > 1.52)
+                    t->setName(TOTALCLUSTERTIME);
+                else
+                    t->setName(TOTALTHORTIME);
+                t->setValue(totalThorTimeText.str());
+                t->setCount((unsigned)totalGraphTime.getCount());
+                timers.append(*t.getClear());
             }
-            else
-                addTimerToList(name, scope, cur, timers);
         }
-    }
-
-    if (totalThorTimeValue > 0)
-    {
-        StringBuffer totalThorTimeText;
-        formatStatistic(totalThorTimeText, totalThorTimeValue, SMeasureTimeNs);
+    protected:
+        WsWuInfo & wuInfo;
+        IArrayOf<IEspECLTimer>& timers;
+        StatsAggregation totalGraphTime;
+    } visitor(*this, timers);
+
+    WuScopeFilter filter(timerFilterText);
+    Owned<IConstWUScopeIterator> it = &cw->getScopeIterator(filter);
+    ForEach(*it)
+        it->playProperties(PTstatistics, visitor);
 
-        Owned<IEspECLTimer> t= createECLTimer("","");
-        if (version > 1.52)
-            t->setName(TOTALCLUSTERTIME);
-        else
-            t->setName(TOTALTHORTIME);
-        t->setValue(totalThorTimeText.str());
-        t->setCount((unsigned)totalThorTimerCount);
-        timers.append(*t.getLink());
-    }
+    visitor.addSummary();
 }
 
 void WsWuInfo::getTimers(IEspECLWorkunit &info, unsigned long flags)
@@ -451,18 +447,34 @@ void WsWuInfo::getTimers(IEspECLWorkunit &info, unsigned long flags)
     }
 }
 
-unsigned WsWuInfo::getTimerCount()
+class TimingCounter : public WuScopeVisitorBase
 {
+public:
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override
+    {
+        numTimers++;
+        if ((kind == StTimeElapsed) && (extra.getScopeType() == SSTgraph))
+            hasGraphTiming = true;
+    }
+
+    unsigned getNumTimers() const
+    {
+        return numTimers + (hasGraphTiming ? 1 : 0);
+    }
+protected:
+    bool hasGraphTiming = false;
     unsigned numTimers = 0;
+};
+
+unsigned WsWuInfo::getTimerCount()
+{
+    TimingCounter visitor;
     try
     {
-        //This filter must match the filter in the function above, otherwise it will be inconsistent
-        StatisticsFilter filter;
-        filter.setScopeDepth(1, 2);
-        filter.setMeasure(SMeasureTimeNs);
-        Owned<IConstWUStatisticIterator> it = &cw->getStatistics(&filter);
+        WuScopeFilter filter(timerFilterText);
+        Owned<IConstWUScopeIterator> it = &cw->getScopeIterator(filter);
         ForEach(*it)
-            numTimers++;
+            it->playProperties(PTstatistics, visitor);
     }
     catch(IException* e)
     {
@@ -470,7 +482,8 @@ unsigned WsWuInfo::getTimerCount()
         ERRLOG("%s", e->errorMessage(eMsg).str());
         e->Release();
     }
-    return numTimers;
+
+    return visitor.getNumTimers();
 }
 
 EnumMapping queryFileTypes[] = {
@@ -694,11 +707,25 @@ const char *getGraphNum(const char *s,unsigned &num)
 
 bool WsWuInfo::hasSubGraphTimings()
 {
-    StatisticsFilter filter;
-    filter.setScopeType(SSTsubgraph);
-    filter.setKind(StTimeElapsed);
-    Owned<IConstWUStatisticIterator> times = &cw->getStatistics(&filter);
-    return times->first();
+    try
+    {
+        WuScopeFilter filter("stype[subgraph],nested[0],prop[stat]");
+        Owned<IConstWUScopeIterator> it = &cw->getScopeIterator(filter);
+        ForEach(*it)
+        {
+            stat_type value;
+            if (it->getStat(StTimeElapsed, value))
+                return true;
+        }
+    }
+    catch(IException* e)
+    {
+        StringBuffer eMsg;
+        ERRLOG("%s", e->errorMessage(eMsg).str());
+        e->Release();
+    }
+
+    return false;
 }
 
 void WsWuInfo::doGetGraphs(IArrayOf<IEspECLGraph>& graphs)
@@ -737,18 +764,23 @@ void WsWuInfo::doGetGraphs(IArrayOf<IEspECLGraph>& graphs)
 
         if (version >= 1.53)
         {
-            SCMStringBuffer s;
-            Owned<IConstWUStatistic> whenGraphStarted = cw->getStatistic(NULL, name.str(), StWhenStarted);
-            if (!whenGraphStarted) // 6.x backward compatibility
-                whenGraphStarted.setown(cw->getStatistic(NULL, name.str(), StWhenGraphStarted));
-            Owned<IConstWUStatistic> whenGraphFinished = cw->getStatistic(NULL, name.str(), StWhenFinished);
-            if (!whenGraphFinished) // 6.x backward compatibility
-                whenGraphFinished.setown(cw->getStatistic(NULL, name.str(), StWhenGraphFinished));
-
-            if (whenGraphStarted)
-                g->setWhenStarted(whenGraphStarted->getFormattedValue(s).str());
-            if (whenGraphFinished)
-                g->setWhenFinished(whenGraphFinished->getFormattedValue(s).str());
+            //MORE: Will need to be prefixed with the wfid
+            StringBuffer scope;
+            scope.append(name);
+
+            StringBuffer s;
+            stat_type timeStamp;
+            if (cw->getStatistic(timeStamp, scope.str(), StWhenStarted) ||
+                cw->getStatistic(timeStamp, name.str(), StWhenGraphStarted))
+            {
+                g->setWhenStarted(formatStatistic(s.clear(), timeStamp, SMeasureTimestampUs));
+            }
+
+            if (cw->getStatistic(timeStamp, scope.str(), StWhenFinished) ||
+               cw->getStatistic(timeStamp, name.str(), StWhenGraphFinished))
+            {
+                g->setWhenFinished(formatStatistic(s.clear(), timeStamp, SMeasureTimestampUs));
+            }
         }
         graphs.append(*g.getLink());
     }
@@ -798,37 +830,47 @@ void WsWuInfo::getWUGraphNameAndTypes(WUGraphType graphType, IArrayOf<IEspNameAn
 
 void WsWuInfo::getGraphTimingData(IArrayOf<IConstECLTimingData> &timingData)
 {
-    StatisticsFilter filter(SCTall, SSTsubgraph, SMeasureTimeNs, StTimeElapsed);
-    Owned<IConstWUStatisticIterator> times = &cw->getStatistics(&filter);
-    bool matched = false;
-    ForEach(*times)
+    class TimingVisitor : public WuScopeVisitorBase
     {
-        IConstWUStatistic & cur = times->query();
-        const char * scope = cur.queryScope();
+    public:
+        TimingVisitor(WsWuInfo & _wuInfo, IArrayOf<IConstECLTimingData> & _timingData) : wuInfo(_wuInfo), timingData(_timingData) {}
 
-        StringAttr graphName;
-        unsigned graphNum;
-        unsigned subGraphId;
-        if (parseGraphScope(scope, graphName, graphNum, subGraphId))
+        virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & cur) override
         {
-            unsigned time = (unsigned)nanoToMilli(cur.getValue());
+            const char * scope = cur.queryScope();
+            StringAttr graphName;
+            unsigned graphNum;
+            unsigned subGraphId;
+            if (parseGraphScope(scope, graphName, graphNum, subGraphId))
+            {
+                unsigned time = (unsigned)nanoToMilli(value);
 
-            SCMStringBuffer name;
-            cur.getDescription(name, true);
-
-            Owned<IEspECLTimingData> g = createECLTimingData();
-            g->setName(name.str());
-            g->setGraphNum(graphNum);
-            g->setSubGraphNum(subGraphId); // Use the Id - the number is not known
-            g->setGID(subGraphId);
-            g->setMS(time);
-            g->setMin(time/60000);
-            timingData.append(*g.getClear());
-            matched = true;
+                SCMStringBuffer name;
+                cur.getDescription(name, true);
+
+                Owned<IEspECLTimingData> g = createECLTimingData();
+                g->setName(name.str());
+                g->setGraphNum(graphNum);
+                g->setSubGraphNum(subGraphId); // Use the Id - the number is not known
+                g->setGID(subGraphId);
+                g->setMS(time);
+                g->setMin(time/60000);
+                timingData.append(*g.getClear());
+            }
         }
-    }
+
+    protected:
+        WsWuInfo & wuInfo;
+        IArrayOf<IConstECLTimingData> & timingData;
+    } visitor(*this, timingData);
+
+    WuScopeFilter filter("stype[subgraph],stat[TimeElapsed],nested[0]");
+    Owned<IConstWUScopeIterator> it = &cw->getScopeIterator(filter);
+    ForEach(*it)
+        it->playProperties(PTstatistics, visitor);
 }
 
+
 void WsWuInfo::getEventScheduleFlag(IEspECLWorkunit &info)
 {
     info.setEventSchedule(0);
@@ -864,27 +906,12 @@ void WsWuInfo::getEventScheduleFlag(IEspECLWorkunit &info)
     }
 }
 
-unsigned WsWuInfo::getTotalThorTime(const char * scope)
-{
-    StatisticsFilter filter;
-    filter.setCreatorType(SCTsummary);
-    filter.setScope(scope);
-    filter.setKind(StTimeElapsed);
-
-    //Should only be a single value
-    unsigned totalThorTimeMS = 0;
-    Owned<IConstWUStatisticIterator> times = &cw->getStatistics(&filter);
-    ForEach(*times)
-    {
-        totalThorTimeMS += (unsigned)nanoToMilli(times->query().getValue());
-    }
-
-    return totalThorTimeMS;
-}
-
 unsigned WsWuInfo::getTotalThorTime()
 {
-    return getTotalThorTime(GLOBAL_SCOPE) + getTotalThorTime(LEGACY_GLOBAL_SCOPE);
+    const WuScopeFilter filter("stype[graph],nested[0],stat[TimeElapsed]");
+    StatsAggregation summary;
+    aggregateStatistic(summary, cw, filter, StTimeElapsed);
+    return nanoToMilli(summary.getSum());
 }
 
 void WsWuInfo::getCommon(IEspECLWorkunit &info, unsigned long flags)
@@ -1543,20 +1570,20 @@ void WsWuInfo::getResults(IEspECLWorkunit &info, unsigned long flags)
     }
 }
 
-void WsWuInfo::getStats(StatisticsFilter& filter, bool createDescriptions, IArrayOf<IEspWUStatisticItem>& statistics)
+class FilteredStatisticsVisitor : public WuScopeVisitorBase
 {
-    Owned<IConstWUStatisticIterator> stats = &cw->getStatistics(&filter);
-    ForEach(*stats)
+public:
+    FilteredStatisticsVisitor(WsWuInfo & _wuInfo, bool _createDescriptions, IArrayOf<IEspWUStatisticItem>& _statistics, const StatisticsFilter& _statsFilter)
+        : wuInfo(_wuInfo), statistics(_statistics), statsFilter(_statsFilter), createDescriptions(_createDescriptions) {}
+
+    virtual void noteStatistic(StatisticKind curKind, unsigned __int64 value, IConstWUStatistic & cur) override
     {
-        IConstWUStatistic & cur = stats->query();
         StringBuffer xmlBuf, tsValue;
         SCMStringBuffer curCreator, curDescription, curFormattedValue;
 
         StatisticCreatorType curCreatorType = cur.getCreatorType();
         StatisticScopeType curScopeType = cur.getScopeType();
         StatisticMeasure curMeasure = cur.getMeasure();
-        StatisticKind curKind = cur.getKind();
-        unsigned __int64 value = cur.getValue();
         unsigned __int64 count = cur.getCount();
         unsigned __int64 max = cur.getMax();
         unsigned __int64 ts = cur.getTimestamp();
@@ -1566,9 +1593,11 @@ void WsWuInfo::getStats(StatisticsFilter& filter, bool createDescriptions, IArra
         cur.getFormattedValue(curFormattedValue);
 
         Owned<IEspWUStatisticItem> wuStatistic = createWUStatisticItem();
+        if (!statsFilter.matches(curCreatorType, curCreator.str(), curScopeType, curScope, curMeasure, curKind, value))
+            return;
 
-        if (version > 1.61)
-            wuStatistic->setWuid(wuid);
+        if (wuInfo.version > 1.61)
+            wuStatistic->setWuid(wuInfo.wuid);
         if (curCreatorType != SCTnone)
             wuStatistic->setCreatorType(queryCreatorTypeName(curCreatorType));
         if (curCreator.length())
@@ -1597,6 +1626,20 @@ void WsWuInfo::getStats(StatisticsFilter& filter, bool createDescriptions, IArra
 
         statistics.append(*wuStatistic.getClear());
     }
+
+protected:
+    WsWuInfo & wuInfo;
+    const StatisticsFilter& statsFilter;
+    IArrayOf<IEspWUStatisticItem>& statistics;
+    bool createDescriptions;
+};
+
+void WsWuInfo::getStats(const WuScopeFilter & filter, const StatisticsFilter& statsFilter, bool createDescriptions, IArrayOf<IEspWUStatisticItem>& statistics)
+{
+    FilteredStatisticsVisitor visitor(*this, createDescriptions, statistics, statsFilter);
+    Owned<IConstWUScopeIterator> it = &cw->getScopeIterator(filter);
+    ForEach(*it)
+        it->playProperties(PTstatistics, visitor);
 }
 
 bool WsWuInfo::getFileSize(const char* fileName, const char* IPAddress, offset_t& fileSize)

+ 2 - 6
esp/services/ws_workunits/ws_workunitsHelpers.hpp

@@ -185,7 +185,7 @@ public:
     void getEclSchemaFields(IArrayOf<IEspECLSchemaItem>& schemas, IHqlExpression * expr, bool isConditional);
     bool getResultEclSchemas(IConstWUResult &r, IArrayOf<IEspECLSchemaItem>& schemas);
     void getResult(IConstWUResult &r, IArrayOf<IEspECLResult>& results, unsigned long flags);
-    void getStats(StatisticsFilter& filter, bool createDescriptions, IArrayOf<IEspWUStatisticItem>& statistics);
+    void getStats(const WuScopeFilter & filter, const StatisticsFilter& statsFilter, bool createDescriptions, IArrayOf<IEspWUStatisticItem>& statistics);
 
     void getWorkunitEclAgentLog(const char* eclAgentInstance, const char* agentPid, MemoryBuffer& buf);
     void getWorkunitThorLog(const char *processName, MemoryBuffer& buf);
@@ -208,14 +208,10 @@ public:
     void setWUAbortTime(IEspECLWorkunit &info, unsigned __int64 abortTS);
     IConstWUQuery* getEmbeddedQuery();
 
-protected:
     void addTimerToList(SCMStringBuffer& name, const char * scope, IConstWUStatistic & stat, IArrayOf<IEspECLTimer>& timers);
+protected:
     unsigned getTotalThorTime();
-    unsigned getTotalThorTime(const char * scope);
-    unsigned getLegacyTotalThorTime();
     bool hasSubGraphTimings();
-    bool legacyHasSubGraphTimings();
-    void legacyGetGraphTimingData(IArrayOf<IConstECLTimingData> &timingData);
 
 public:
     IEspContext &context;

+ 4 - 3
esp/services/ws_workunits/ws_workunitsQuerySets.cpp

@@ -1689,10 +1689,11 @@ bool CWsWorkunitsEx::onWUQueryDetails(IEspContext &context, IEspWUQueryDetailsRe
         resp.setIsLibrary(query->getPropBool("@isLibrary"));
         SCMStringBuffer s;
         resp.setWUSnapShot(cw->getSnapshot(s).str()); //Label
-        Owned<IConstWUStatistic> whenCompiled = cw->getStatistic(NULL, NULL, StWhenCompiled);
-        if (whenCompiled)
+
+        stat_type whenCompiled;
+        if (cw->getStatistic(whenCompiled, "", StWhenCompiled))
         {
-            whenCompiled->getFormattedValue(s);
+            formatStatistic(s.s.clear(), whenCompiled, StWhenCompiled);
             resp.setCompileTime(s.str());
         }
 

+ 34 - 12
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -4883,6 +4883,13 @@ bool CWsWorkunitsEx::onWUCheckFeatures(IEspContext &context, IEspWUCheckFeatures
     return true;
 }
 
+static const char * checkGetStatsNullInput(const char * s)
+{
+    if (!s || !*s)
+        return nullptr;
+    return s;
+}
+
 static const char * checkGetStatsInput(const char * s)
 {
     if (!s || !*s)
@@ -4892,20 +4899,33 @@ static const char * checkGetStatsInput(const char * s)
 
 bool CWsWorkunitsEx::onWUGetStats(IEspContext &context, IEspWUGetStatsRequest &req, IEspWUGetStatsResponse &resp)
 {
+    //This function is deprecated for 7.x and will be removed shortly afterwards.
+    //Anything that cannot be implemented with the scope iterator is implemented as a post filter
     try
     {
-        const char* creatorType = checkGetStatsInput(req.getCreatorType());
-        const char* creator = checkGetStatsInput(req.getCreator());
-        const char* scopeType = checkGetStatsInput(req.getScopeType());
-        const char* scope = checkGetStatsInput(req.getScope());
-        const char* kind = checkGetStatsInput(req.getKind());
+        const char* creatorType = checkGetStatsNullInput(req.getCreatorType());
+        const char* creator = checkGetStatsNullInput(req.getCreator());
+        const char* scopeType = checkGetStatsNullInput(req.getScopeType());
+        const char* scope = checkGetStatsNullInput(req.getScope());
+        const char* kind = checkGetStatsNullInput(req.getKind());
         const char* measure = req.getMeasure();
 
-        StatisticsFilter filter(creatorType, creator, scopeType, scope, measure, kind);
+        WuScopeFilter filter;
+        StatisticsFilter statsFilter(creatorType, creator, "*", "*", "*", "*");
+        filter.addOutputProperties(PTstatistics);
+        if (scopeType)
+            filter.addScopeType(scopeType);
+        if (scope)
+            filter.addScope(scope);
+        if (kind)
+            filter.addOutputStatistic(kind);
+        if (measure)
+            filter.setMeasure(measure);
         if (!req.getMinScopeDepth_isNull() && !req.getMaxScopeDepth_isNull())
-            filter.setScopeDepth(req.getMinScopeDepth(), req.getMaxScopeDepth());
+            filter.setDepth(req.getMinScopeDepth(), req.getMaxScopeDepth());
         else if (!req.getMinScopeDepth_isNull())
-            filter.setScopeDepth(req.getMinScopeDepth());
+            filter.setDepth(req.getMinScopeDepth(), req.getMinScopeDepth());
+
         if (!req.getMinValue_isNull() || !req.getMaxValue_isNull())
         {
             unsigned __int64 lowValue = 0;
@@ -4914,12 +4934,14 @@ bool CWsWorkunitsEx::onWUGetStats(IEspContext &context, IEspWUGetStatsRequest &r
                 lowValue = (unsigned __int64)req.getMinValue();
             if (!req.getMaxValue_isNull())
                 highValue = (unsigned __int64)req.getMaxValue();
-            filter.setValueRange(lowValue, highValue);
+            statsFilter.setValueRange(lowValue, highValue);
         }
 
         const char * textFilter = req.getFilter();
         if (textFilter)
-            filter.setFilter(textFilter);
+            statsFilter.setFilter(textFilter);
+
+        filter.setIncludeNesting(0).finishedFilter();
 
         bool createDescriptions = false;
         if (!req.getCreateDescriptions_isNull())
@@ -4945,7 +4967,7 @@ bool CWsWorkunitsEx::onWUGetStats(IEspContext &context, IEspWUGetStatsRequest &r
                 {
                     //No need to check for access since the list is already filtered
                     WsWuInfo winfo(context, workunit->queryWuid());
-                    winfo.getStats(filter, createDescriptions, statistics);
+                    winfo.getStats(filter, statsFilter, createDescriptions, statistics);
                 }
             }
         }
@@ -4955,7 +4977,7 @@ bool CWsWorkunitsEx::onWUGetStats(IEspContext &context, IEspWUGetStatsRequest &r
             ensureWsWorkunitAccess(context, wuid, SecAccess_Read);
 
             WsWuInfo winfo(context, wuid);
-            winfo.getStats(filter, createDescriptions, statistics);
+            winfo.getStats(filter, statsFilter, createDescriptions, statistics);
         }
         resp.setStatistics(statistics);
         resp.setWUID(wuid.str());

+ 1 - 0
plugins/cassandra/cassandrawu.cpp

@@ -3259,6 +3259,7 @@ public:
                 Owned<IPTree> wuXML = createPTree(useWuid);
                 wuXML->setProp("@xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance");
                 wuXML->setPropInt("@wuidVersion", WUID_VERSION);  // we implement the latest version.
+                wuXML->setProp("@totalThorTime", ""); // must be non null, otherwise sorting by thor time excludes the values
                 Owned<IRemoteConnection> daliLock;
                 lockWuid(daliLock, useWuid);
                 Owned<CLocalWorkUnit> wu = new CCassandraWorkUnit(this, wuXML.getClear(), secmgr, secuser, daliLock.getClear(), false);

+ 78 - 90
plugins/workunitservices/workunitservices.cpp

@@ -526,37 +526,48 @@ WORKUNITSERVICES_API char * wsWUIDdaysAgo(unsigned daysago)
     return getWUIDdaysAgo(ret,(int)daysago).detach();
 }
 
+class WsTimeStampVisitor : public WuScopeVisitorBase
+{
+public:
+    WsTimeStampVisitor(MemoryBuffer & _mb) : mb(_mb) {}
+
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & cur) override
+    {
+        const char * curScope = cur.queryScope();
+        const char * kindName = queryStatisticName(kind);
+        assertex(kindName && memicmp(kindName, "when", 4) == 0);
+        kindName += 4;
+
+        StringBuffer formattedTime;
+        convertTimestampToStr(value, formattedTime, true);
+
+        SCMStringBuffer creator;
+        cur.getCreator(creator);
+        const char * at = strchr(creator.str(), '@');
+        const char * instance = at ? at + 1 : creator.str();
+
+        fixedAppend(mb, 32, curScope);
+        fixedAppend(mb, 16, kindName); // id
+        fixedAppend(mb, 20, formattedTime);            // time
+        fixedAppend(mb, 16, instance);                 // item correct here
+    }
+
+protected:
+    MemoryBuffer & mb;
+};
+
 WORKUNITSERVICES_API void wsWorkunitTimeStamps(ICodeContext *ctx, size32_t & __lenResult, void * & __result, const char *wuid)
 {
     Owned<IConstWorkUnit> wu = getWorkunit(ctx, wuid);
     MemoryBuffer mb;
     if (wu)
     {
-        Owned<StatisticsFilter> filter = new StatisticsFilter(SCTall, SSTall, SMeasureTimestampUs, StKindAll);
-        filter->setScopeDepth(1, 2);
-        Owned<IConstWUStatisticIterator> stats = &wu->getStatistics(filter);
-        ForEach(*stats)
+        WsTimeStampVisitor visitor(mb);
+        WuScopeFilter filter("measure[When],source[global]");
+        Owned<IConstWUScopeIterator> iter = &wu->getScopeIterator(filter);
+        ForEach(*iter)
         {
-            IConstWUStatistic & cur = stats->query();
-
-            const char * curScope = cur.queryScope();
-            StatisticKind kind = cur.getKind();
-            const char * kindName = queryStatisticName(kind);
-            assertex(kindName && memicmp(kindName, "when", 4) == 0);
-            kindName += 4;
-
-            StringBuffer formattedTime;
-            convertTimestampToStr(cur.getValue(), formattedTime, true);
-
-            SCMStringBuffer creator;
-            cur.getCreator(creator);
-            const char * at = strchr(creator.str(), '@');
-            const char * instance = at ? at + 1 : creator.str();
-
-            fixedAppend(mb, 32, curScope);
-            fixedAppend(mb, 16, kindName); // id
-            fixedAppend(mb, 20, formattedTime);            // time
-            fixedAppend(mb, 16, instance);                 // item correct here
+            iter->playProperties(PTstatistics, visitor);
         }
     }
     __lenResult = mb.length();
@@ -632,31 +643,42 @@ WORKUNITSERVICES_API void wsWorkunitFilesWritten( ICodeContext *ctx, size32_t &
     __result = mb.detach();
 }
 
+
+class WsTimingVisitor : public WuScopeVisitorBase
+{
+public:
+    WsTimingVisitor(MemoryBuffer & _mb) : mb(_mb) {}
+
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & cur) override
+    {
+        SCMStringBuffer desc;
+
+        unsigned __int64 count = cur.getCount();
+        unsigned __int64 max = cur.getMax();
+        cur.getDescription(desc, true);
+
+        mb.append((unsigned) count);
+        mb.append((unsigned) (value / 1000000));
+        mb.append((unsigned) max);
+        varAppend(mb, desc.str());
+    }
+
+protected:
+    MemoryBuffer & mb;
+};
+
 WORKUNITSERVICES_API void wsWorkunitTimings( ICodeContext *ctx, size32_t & __lenResult, void * & __result, const char *wuid )
 {
     Owned<IConstWorkUnit> wu = getWorkunit(ctx, wuid);
     MemoryBuffer mb;
     if (wu)
     {
-        StatisticsFilter filter;
-        filter.setScopeDepth(1, 2);
-        filter.setMeasure(SMeasureTimeNs);
-
-        SCMStringBuffer desc;
-        Owned<IConstWUStatisticIterator> iter = &wu->getStatistics(&filter);
+        WsTimingVisitor visitor(mb);
+        WuScopeFilter filter("measure[Time],source[global]");
+        Owned<IConstWUScopeIterator> iter = &wu->getScopeIterator(filter);
         ForEach(*iter)
         {
-            IConstWUStatistic & cur = iter->query();
-
-            unsigned __int64 value = cur.getValue();
-            unsigned __int64 count = cur.getCount();
-            unsigned __int64 max = cur.getMax();
-            cur.getDescription(desc, true);
-
-            mb.append((unsigned) count);
-            mb.append((unsigned) (value / 1000000));
-            mb.append((unsigned) max);
-            varAppend(mb, desc.str());
+            iter->playProperties(PTstatistics, visitor);
         }
     }
     __lenResult = mb.length();
@@ -664,52 +686,24 @@ WORKUNITSERVICES_API void wsWorkunitTimings( ICodeContext *ctx, size32_t & __len
 }
 
 
-class StreamedStatistics : public CInterfaceOf<IRowStream>
+//This function is deprecated and no longer supported - I'm not sure it ever worked
+WORKUNITSERVICES_API IRowStream * wsWorkunitStatistics( ICodeContext *ctx, IEngineRowAllocator * allocator, const char *wuid, bool includeActivities, const char * filterText)
+{
+    return createNullRowStream();
+}
+
+class WsStreamedStatistics : public CInterfaceOf<IRowStream>
 {
 public:
-    StreamedStatistics(IConstWorkUnit * _wu, IEngineRowAllocator * _resultAllocator, IConstWUStatisticIterator * _iter)
-    : wu(_wu), resultAllocator(_resultAllocator),iter(_iter)
+    WsStreamedStatistics(IConstWorkUnit * _wu, IEngineRowAllocator * _resultAllocator, const char * _filter)
+    : wu(_wu), resultAllocator(_resultAllocator), filter(_filter)
     {
+        iter.setown(&wu->getScopeIterator(filter));
     }
 
     virtual const void *nextRow()
     {
-        if (!iter || !iter->isValid())
-            return NULL;
-
-        IConstWUStatistic & cur = iter->query();
-
-        unsigned __int64 value = cur.getValue();
-        unsigned __int64 count = cur.getCount();
-        unsigned __int64 max = cur.getMax();
-        StatisticCreatorType creatorType = cur.getCreatorType();
-        cur.getCreator(creator);
-        StatisticScopeType scopeType = cur.getScopeType();
-        const char * scope = cur.queryScope();
-        cur.getDescription(description, true);
-        StatisticMeasure measure = cur.getMeasure();
-        StatisticKind kind = cur.getKind();
-
-        MemoryBuffer mb;
-        mb.append(sizeof(value),&value);
-        mb.append(sizeof(count),&count);
-        mb.append(sizeof(max),&max);
-        varAppend(mb, queryCreatorTypeName(creatorType));
-        varAppend(mb, creator.str());
-        varAppend(mb, queryScopeTypeName(scopeType));
-        varAppend(mb, scope);
-        varAppend(mb, queryStatisticName(kind));
-        varAppend(mb, description.str());
-        varAppend(mb, queryMeasureName(measure));
-
-        size32_t len = mb.length();
-        size32_t newSize;
-        void * row = resultAllocator->createRow(newSize);
-        row = resultAllocator->resizeRow(len, row, newSize);
-        memcpy(row, mb.bufferBase(), len);
-
-        iter->next();
-        return resultAllocator->finalizeRow(len, row, newSize);
+        return NULL;
     }
     virtual void stop()
     {
@@ -720,22 +714,16 @@ public:
 protected:
     Linked<IConstWorkUnit> wu;
     Linked<IEngineRowAllocator> resultAllocator;
-    Linked<IConstWUStatisticIterator> iter;
-    SCMStringBuffer creator;
-    SCMStringBuffer description;
+    WuScopeFilter filter;
+    Linked<IConstWUScopeIterator> iter;
 };
 
-WORKUNITSERVICES_API IRowStream * wsWorkunitStatistics( ICodeContext *ctx, IEngineRowAllocator * allocator, const char *wuid, bool includeActivities, const char * filterText)
+WORKUNITSERVICES_API IRowStream * wsNewWorkunitStatistics( ICodeContext *ctx, IEngineRowAllocator * allocator, const char *wuid, const char * filterText)
 {
     Owned<IConstWorkUnit> wu = getWorkunit(ctx, wuid);
     if (!wu)
         return createNullRowStream();
-    //Filter needs to be allocated because the iterator outlasts it.
-    Owned<StatisticsFilter> filter = new StatisticsFilter(filterText);
-    if (!includeActivities)
-        filter->setScopeDepth(1, 2);
-    Owned<IConstWUStatisticIterator> stats = &wu->getStatistics(filter);
-    return new StreamedStatistics(wu, allocator, stats);
+    return new WsStreamedStatistics(wu, allocator, filterText);
 }
 
 WORKUNITSERVICES_API bool wsSetWorkunitAppValue( ICodeContext *ctx, const char *appname, const char *key, const char *value, bool overwrite)

+ 1 - 7
roxie/ccd/ccdcontext.cpp

@@ -1452,19 +1452,13 @@ public:
                     graph->abort();
                 if (workUnit)
                 {
-                    unsigned __int64 totalTimeNs = 0;
-                    unsigned __int64 totalThisTimeNs = 0;
-                    const char *totalTimeStr = "Total cluster time";
-                    getWorkunitTotalTime(workUnit, "roxie", totalTimeNs, totalThisTimeNs);
-
                     const char * graphName = graph->queryName();
                     StringBuffer graphDesc;
                     formatGraphTimerLabel(graphDesc, graphName);
                     WorkunitUpdate progressWorkUnit(&workUnit->lock());
                     progressWorkUnit->setStatistic(queryStatisticsComponentType(), queryStatisticsComponentName(), SSTgraph, graphName, StWhenStarted, NULL, startTimeStamp, 1, 0, StatsMergeAppend);
                     updateWorkunitTimeStat(progressWorkUnit, SSTgraph, graphName, StTimeElapsed, graphDesc, elapsedTime);
-                    updateWorkunitTimeStat(progressWorkUnit, SSTglobal, GLOBAL_SCOPE, StTimeElapsed, NULL, totalThisTimeNs+elapsedTime);
-                    progressWorkUnit->setStatistic(SCTsummary, "roxie", SSTglobal, GLOBAL_SCOPE, StTimeElapsed, totalTimeStr, totalTimeNs+elapsedTime, 1, 0, StatsMergeReplace);
+                    addTimeStamp(progressWorkUnit, SSTgraph, graphName, StWhenFinished);
                 }
                 graph->reset();
             }

+ 77 - 37
system/jlib/jstats.cpp

@@ -344,7 +344,7 @@ static const unsigned oneKb = 1024;
 static const unsigned oneMb = 1024 * 1024;
 static const unsigned oneGb = 1024 * 1024 * 1024;
 static unsigned toPermille(unsigned x) { return (x * 1000) / 1024; }
-static void formatSize(StringBuffer & out, unsigned __int64 value)
+static StringBuffer & formatSize(StringBuffer & out, unsigned __int64 value)
 {
 
     unsigned Gb = (unsigned)(value / oneGb);
@@ -352,84 +352,74 @@ static void formatSize(StringBuffer & out, unsigned __int64 value)
     unsigned Kb = (unsigned)((value % oneMb) / oneKb);
     unsigned b = (unsigned)(value % oneKb);
     if (Gb)
-        out.appendf("%u.%03uGb", Gb, toPermille(Mb));
+        return out.appendf("%u.%03uGb", Gb, toPermille(Mb));
     else if (Mb)
-        out.appendf("%u.%03uMb", Mb, toPermille(Kb));
+        return out.appendf("%u.%03uMb", Mb, toPermille(Kb));
     else if (Kb)
-        out.appendf("%u.%03uKb", Kb, toPermille(b));
+        return out.appendf("%u.%03uKb", Kb, toPermille(b));
     else
-        out.appendf("%ub", b);
+        return out.appendf("%ub", b);
 }
 
-static void formatLoad(StringBuffer & out, unsigned __int64 value)
+static StringBuffer & formatLoad(StringBuffer & out, unsigned __int64 value)
 {
     //Stored as millionth of a core.  Display as a percentage => scale by 10,000
-    out.appendf("%u.%03u%%", (unsigned)(value / 10000), (unsigned)(value % 10000) / 10);
+    return out.appendf("%u.%03u%%", (unsigned)(value / 10000), (unsigned)(value % 10000) / 10);
 }
 
-static void formatSkew(StringBuffer & out, unsigned __int64 value)
+static StringBuffer & formatSkew(StringBuffer & out, unsigned __int64 value)
 {
     //Skew stored as 10000 = perfect, display as percentage
-    out.appendf("%.2f%%", ((double)(__int64)value) / 100.0);
+    return out.appendf("%.2f%%", ((double)(__int64)value) / 100.0);
 }
 
-static void formatIPV4(StringBuffer & out, unsigned __int64 value)
+static StringBuffer & formatIPV4(StringBuffer & out, unsigned __int64 value)
 {
     byte ip1 = (value & 255);
     byte ip2 = ((value >> 8) & 255);
     byte ip3 = ((value >> 16) & 255);
     byte ip4 = ((value >> 24) & 255);
-    out.appendf("%d.%d.%d.%d", ip1, ip2, ip3, ip4);
+    return out.appendf("%d.%d.%d.%d", ip1, ip2, ip3, ip4);
 }
 
-void formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticMeasure measure)
+StringBuffer & formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticMeasure measure)
 {
     switch (measure)
     {
     case SMeasureNone: // Unknown stat - e.g, on old esp accessing a new workunit
-        out.append(value);
-        break;
+        return out.append(value);
     case SMeasureTimeNs:
         formatTime(out, value);
-        break;
+        return out;
     case SMeasureTimestampUs:
         formatTimeStamp(out, value);
-        break;
+        return out;
     case SMeasureCount:
-        out.append(value);
-        break;
+        return out.append(value);
     case SMeasureSize:
-        formatSize(out, value);
-        break;
+        return formatSize(out, value);
     case SMeasureLoad:
-        formatLoad(out, value);
-        break;
+        return formatLoad(out, value);
     case SMeasureSkew:
-        formatSkew(out, value);
-        break;
+        return formatSkew(out, value);
     case SMeasureNode:
-        out.append(value);
-        break;
+        return out.append(value);
     case SMeasurePercent:
-        out.appendf("%.2f%%", (double)value / 10000.0);  // stored as ppm
-        break;
+        return out.appendf("%.2f%%", (double)value / 10000.0);  // stored as ppm
     case SMeasureIPV4:
-        formatIPV4(out, value);
-        break;
+        return formatIPV4(out, value);
     case SMeasureCycle:
-        out.append(value);
-        break;
+        return out.append(value);
     case SMeasureBool:
-        out.append(boolToStr(value != 0));
-        break;
+        return out.append(boolToStr(value != 0));
     default:
-        out.append(value).append('?');
+        return out.append(value).append('?');
     }
 }
 
-void formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticKind kind)
+StringBuffer & formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticKind kind)
 {
-    formatStatistic(out, value, queryMeasure(kind));
+    return formatStatistic(out, value, queryMeasure(kind));
 }
 
 //--------------------------------------------------------------------------------------------------------------------
@@ -541,6 +531,23 @@ bool getParentScope(StringBuffer & parent, const char * scope)
 }
 
 
+void describeScope(StringBuffer & description, const char * scope)
+{
+    if (!*scope)
+        return;
+
+    StatsScopeId id;
+    for(;;)
+    {
+        id.extractScopeText(scope, &scope);
+        id.describe(description);
+        if (!*scope)
+            return;
+        description.append(": ");
+        scope++;
+    }
+}
+
 const char * queryMeasurePrefix(StatisticMeasure measure)
 {
     switch (measure)
@@ -1230,6 +1237,33 @@ int StatsScopeId::compare(const StatsScopeId & other) const
     return 0;
 }
 
+void StatsScopeId::describe(StringBuffer & description) const
+{
+    const char * name = queryScopeTypeName(scopeType);
+    description.append((char)toupper(*name)).append(name+1);
+    switch (scopeType)
+    {
+    case SSTgraph:
+        description.append(" graph").append(id);
+        break;
+    case SSTsubgraph:
+    case SSTactivity:
+    case SSTworkflow:
+    case SSTchildgraph:
+        description.append(' ').append(id);
+        break;
+    case SSTedge:
+        description.append(' ').append(id).append(',').append(extra);
+        break;
+    case SSTfunction:
+        description.append(' ').append(name);
+        break;
+    default:
+        throwUnexpected();
+        break;
+    }
+
+}
 
 
 bool StatsScopeId::matches(const StatsScopeId & other) const
@@ -2747,6 +2781,12 @@ ScopeCompare ScopeFilter::compare(const char * scope) const
     return result;
 }
 
+bool ScopeFilter::canAlwaysPreFilter() const
+{
+    //If the only filter being applied is a restriction on the minimum depth, then you can always apply it as a pre-filter
+    return (!ids && !scopeTypes && !scopes && maxDepth == UINT_MAX);
+}
+
 int ScopeFilter::compareDepth(unsigned depth) const
 {
     if (depth < minDepth)

+ 5 - 2
system/jlib/jstats.h

@@ -58,6 +58,7 @@ public:
     unsigned getHash() const;
     bool matches(const StatsScopeId & other) const;
     unsigned queryActivity() const;
+    void describe(StringBuffer & description) const;
 
     void deserialize(MemoryBuffer & in, unsigned version);
     void serialize(MemoryBuffer & out) const;
@@ -305,6 +306,7 @@ public:
 
     int compareDepth(unsigned depth) const; // -1 too shallow, 0 a match, +1 too deep
     bool hasSingleMatch() const;
+    bool canAlwaysPreFilter() const;
     const StringArray & queryScopes() const { return scopes; }
     bool matchOnly(StatisticScopeType scopeType) const;
 
@@ -675,8 +677,8 @@ class IpAddress;
 
 extern jlib_decl unsigned __int64 getTimeStampNowValue();
 extern jlib_decl unsigned __int64 getIPV4StatsValue(const IpAddress & ip);
-extern jlib_decl void formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticMeasure measure);
-extern jlib_decl void formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticKind kind);
+extern jlib_decl StringBuffer & formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticMeasure measure);
+extern jlib_decl StringBuffer & formatStatistic(StringBuffer & out, unsigned __int64 value, StatisticKind kind);
 extern jlib_decl void formatTimeStampAsLocalTime(StringBuffer & out, unsigned __int64 value);
 extern jlib_decl stat_type readStatisticValue(const char * cur, const char * * end, StatisticMeasure measure);
 
@@ -723,6 +725,7 @@ extern jlib_decl int compareScopeName(const char * left, const char * right);
 extern jlib_decl unsigned queryScopeDepth(const char * text);
 extern jlib_decl const char * queryScopeTail(const char * scope);
 extern jlib_decl bool getParentScope(StringBuffer & parent, const char * scope);
+extern jlib_decl void describeScope(StringBuffer & description, const char * scope);
 
 //This interface is primarily here to reduce the dependency between the different components.
 interface IStatisticTarget

+ 1 - 0
system/jlib/jstring.hpp

@@ -134,6 +134,7 @@ public:
         return clear().append(value.str());
     }
     StringBuffer& operator=(StringBuffer&& value);
+    explicit operator bool() const { return (length() != 0); }
 
     StringBuffer &  appendlong(long value);
     StringBuffer &  appendulong(unsigned long value);

+ 0 - 5
thorlcr/master/thgraphmanager.cpp

@@ -834,9 +834,6 @@ bool CJobManager::executeGraph(IConstWorkUnit &workunit, const char *graphName,
     StringAttr wuid(workunit.queryWuid());
     const char *totalTimeStr = "Total thor time";
     cycle_t startCycles = get_cycles_now();
-    unsigned __int64 totalTimeNs = 0;
-    unsigned __int64 totalThisTimeNs = 0;
-    getWorkunitTotalTime(&workunit, "thor", totalTimeNs, totalThisTimeNs);
 
     Owned<IConstWUQuery> query = workunit.getQuery(); 
     SCMStringBuffer soName;
@@ -913,8 +910,6 @@ bool CJobManager::executeGraph(IConstWorkUnit &workunit, const char *graphName,
         formatGraphTimerLabel(graphTimeStr, graphName);
 
         updateWorkunitTimeStat(wu, SSTgraph, graphName, StTimeElapsed, graphTimeStr, graphTimeNs);
-        updateWorkunitTimeStat(wu, SSTglobal, GLOBAL_SCOPE, StTimeElapsed, NULL, totalThisTimeNs+graphTimeNs);
-        wu->setStatistic(SCTsummary, "thor", SSTglobal, GLOBAL_SCOPE, StTimeElapsed, totalTimeStr, totalTimeNs+graphTimeNs, 1, 0, StatsMergeReplace);
 
         addTimeStamp(wu, SSTgraph, graphName, StWhenFinished);
         

+ 2 - 1
tools/wutool/wutool.cpp

@@ -540,7 +540,7 @@ protected:
             wu->setClusterName(clusterName);
             if (i % 3)
                 wu->setJobName(jobName);
-            wu->setStatistic(SCTsummary, "thor", SSTglobal, GLOBAL_SCOPE, StTimeElapsed, "Total thor time", ((i+2)/2) * 1000000, 1, 0, StatsMergeReplace);
+            wu->setStatistic(SCTthor, "thor", SSTgraph, "graph1", StTimeElapsed, "Total thor time", ((i+2)/2) * 1000000, 1, 0, StatsMergeReplace);
             wu->setApplicationValue("appname", "userId", userId.str(), true);
             wu->setApplicationValue("appname2", "clusterName", clusterName.str(), true);
             wuids.append(wu->queryWuid());
@@ -598,6 +598,7 @@ protected:
                 "         eclVersion='6.0.0'"
                 "         hash='2796091347'"
                 "         state='completed'"
+                "         totalThorTime=''"
                 "         xmlns:xsi='http://www.w3.org/1999/XMLSchema-instance'>"
                 " <Debug>"
                 "  <debugquery>1</debugquery>"