Browse Source

HPCC-19316 Add support for workflow scopes to wudetails

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 7 năm trước cách đây
mục cha
commit
f145dadcd2

+ 19 - 0
common/workunit/workflow.cpp

@@ -77,6 +77,21 @@ static int getEnum(IPropertyTree *p, const char *propname, EnumMapping *map)
     return 0;
 }
 
+const char * queryWorkflowTypeText(WFType type)
+{
+    return getEnumText(type, wftypes);
+}
+
+const char * queryWorkflowModeText(WFMode mode)
+{
+    return getEnumText(mode, wfmodes);
+}
+
+const char * queryWorkflowStateText(WFState state)
+{
+    return getEnumText(state, wfstates);
+}
+
 
 class CWorkflowDependencyIterator : implements IWorkflowDependencyIterator, public CInterface
 {
@@ -137,6 +152,7 @@ public:
     virtual unsigned     queryScheduleCount() const { assertex(tree->hasProp("Schedule/@count")); return tree->getPropInt("Schedule/@count"); }
     virtual IWorkflowDependencyIterator * getDependencies() const { return new CWorkflowDependencyIterator(tree); }
     virtual WFType       queryType() const { return static_cast<WFType>(getEnum(tree, "@type", wftypes)); }
+    virtual IStringVal & getLabel(IStringVal & val) const { val.set(tree->queryProp("@label")); return val; }
     virtual WFMode       queryMode() const { return static_cast<WFMode>(getEnum(tree, "@mode", wfmodes)); }
     virtual unsigned     querySuccess() const { return tree->getPropInt("@success", 0); }
     virtual unsigned     queryFailure() const { return tree->getPropInt("@failure", 0); }
@@ -337,6 +353,7 @@ private:
     StringAttr failmsg;
     SCMStringBuffer persistName;
     SCMStringBuffer clusterName;
+    SCMStringBuffer label;
     unsigned persistWfid;
     int persistCopies;
     bool persistRefresh;
@@ -378,6 +395,7 @@ public:
         persistRefresh = other->queryPersistRefresh();
         other->getCriticalName(criticalName);
         other->queryCluster(clusterName);
+        other->getLabel(label);
     }
     //info set at compile time
     virtual unsigned     queryWfid() const { return wfid; }
@@ -390,6 +408,7 @@ public:
     virtual IWorkflowDependencyIterator * getDependencies() const { return new CCloneIterator(dependencies); }
     virtual WFType       queryType() const { return type; }
     virtual WFMode       queryMode() const { return mode; }
+    virtual IStringVal & getLabel(IStringVal & val) const { val.set(label.str()); return val; }
     virtual unsigned     querySuccess() const { return success; }
     virtual unsigned     queryFailure() const { return failure; }
     virtual unsigned     queryRecovery() const { return recovery; }

+ 4 - 0
common/workunit/workflow.hpp

@@ -136,6 +136,10 @@ extern WORKUNIT_API IWorkflowItemArray *createWorkflowItemArray(unsigned size);
 extern WORKUNIT_API IWorkflowItem *createWorkflowItem(IPropertyTree * ptree, unsigned wfid, WFType type, WFMode mode, unsigned success, unsigned failure, unsigned recovery, unsigned retriesAllowed, unsigned contingencyFor);
 extern WORKUNIT_API IWorkflowItemIterator *createWorkflowItemIterator(IPropertyTree * ptree);
 
+extern WORKUNIT_API const char * queryWorkflowTypeText(WFType type);
+extern WORKUNIT_API const char * queryWorkflowModeText(WFMode mode);
+extern WORKUNIT_API const char * queryWorkflowStateText(WFState state);
+
 #ifdef TRACE_WORKFLOW
 extern const WORKUNIT_API LogMsgCategory MCworkflow;       // Category used to inform enqueue/start/finish of workflow item
 #endif

+ 222 - 9
common/workunit/workunit.cpp

@@ -1712,6 +1712,202 @@ protected:
     StatisticScopeType scopeType = SSTnone;
 };
 
+static int compareWorkflow(IInterface * const * pLeft, IInterface * const * pRight)
+{
+    IConstWorkflowItem * left = static_cast<IConstWorkflowItem *>(*pLeft);
+    IConstWorkflowItem * right = static_cast<IConstWorkflowItem *>(*pRight);
+    return left->queryWfid() - right->queryWfid();
+}
+
+static const char * trueToStr(bool value) { return value ? "true" : nullptr; }
+
+/*
+ * An implementation of IConstWUScopeIterator for the workflow information.
+ */
+class WorkflowStatisticsScopeIterator : public CInterfaceOf<IConstWUScopeIterator>
+{
+public:
+    WorkflowStatisticsScopeIterator(IConstWorkflowItemIterator * wfIter)
+    {
+        ForEach(*wfIter)
+            workflow.append(*LINK(wfIter->query()));
+        workflow.sort(compareWorkflow);
+    }
+
+    virtual bool first() override
+    {
+        if (workflow.empty())
+            return false;
+        curWorkflow = 0;
+        return initWorkflowItem();
+    }
+
+    virtual bool next() override
+    {
+        if (!workflow.isItem(++curWorkflow))
+            return false;
+        return initWorkflowItem();
+    }
+
+    virtual bool nextSibling() override
+    {
+        return next();
+    }
+
+    virtual bool nextParent() override
+    {
+        curWorkflow = NotFound;
+        return false;
+    }
+
+    virtual bool isValid() override
+    {
+        return workflow.isItem(curWorkflow);
+    }
+
+    virtual const char * queryScope() const override
+    {
+        return curScope.str();
+    }
+
+    virtual StatisticScopeType getScopeType() const override
+    {
+        return SSTworkflow;
+    }
+
+    virtual void playProperties(IWuScopeVisitor & visitor, WuPropertyTypes whichProperties) override
+    {
+        if (whichProperties & PTattributes)
+        {
+#if 0
+            //Enable this code when the mapping from multiple instance attributes to lists is implemented in the service layer above
+            //At that point remove WaDependencyList from the list below.
+            {
+                StringBuffer scratchpad;
+                Owned<IWorkflowDependencyIterator> depends = workflow.item(curWorkflow).getDependencies();
+                ForEach(*depends)
+                        visitor.noteAttribute(WaIdDependency, getValueText(depends->query(), scratchpad, WorkflowScopePrefix));
+            }
+#endif
+            play(visitor, { WaIdDependencyList, WaIsScheduled, WaIdSuccess, WaIdFailure, WaIdRecovery, WaIdPersist, WaIdScheduled,
+                            WaPersistName, WaLabel, WaMode, WaType, WaState, WaCluster, WaCriticalSection });
+        }
+    }
+
+    virtual bool getStat(StatisticKind kind, unsigned __int64 & value) const override
+    {
+        return false;
+    }
+
+    virtual const char * queryAttribute(WuAttr attr, StringBuffer & scratchpad) const override
+    {
+        auto wf = &workflow.item(curWorkflow);
+        StringBufferAdaptor adaptor(scratchpad);
+        switch (attr)
+        {
+        case WaIdDependencyList:
+        {
+            bool first = true;
+            Owned<IWorkflowDependencyIterator> depends = workflow.item(curWorkflow).getDependencies();
+            ForEach(*depends)
+            {
+                if (first)
+                    scratchpad.append("[");
+                else
+                    scratchpad.append(",");
+                scratchpad.append('"').append(WorkflowScopePrefix).append(depends->query()).append('"');
+                first = false;
+            }
+            if (first)
+                return nullptr;
+            scratchpad.append("]");
+            return scratchpad.str();
+        }
+        case WaIsScheduled: return trueToStr(wf->isScheduled());
+        case WaIdSuccess: return getWfidText(wf->querySuccess(), scratchpad);
+        case WaIdFailure: return getWfidText(wf->queryFailure(), scratchpad);
+        case WaIdRecovery: return getWfidText(wf->queryRecovery(), scratchpad);
+        case WaIdPersist: return getWfidText(wf->queryPersistWfid(), scratchpad);
+        case WaIdScheduled: return getWfidText(wf->queryScheduledWfid(), scratchpad);
+        case WaPersistName: return queryOptString(wf->getPersistName(adaptor));
+        case WaLabel: return queryOptString(wf->getLabel(adaptor));
+        case WaMode: return wf->queryMode() != WFModeNormal ? queryWorkflowModeText(wf->queryMode()) : nullptr;
+        case WaType: return wf->queryType() != WFTypeNormal ? queryWorkflowTypeText(wf->queryType()) : nullptr;
+        case WaState: return queryWorkflowStateText(wf->queryState());
+        case WaCluster: return queryOptString(wf->queryCluster(adaptor));
+        case WaCriticalSection: return queryOptString(wf->getCriticalName(adaptor));
+        /*
+        The followng attributes are not generated - I'm not convinced they are very useful, but they could be added later.
+        virtual bool isScheduledNow() const = 0;
+        virtual IWorkflowEvent * getScheduleEvent() const = 0;
+        virtual unsigned querySchedulePriority() const = 0;
+        virtual bool hasScheduleCount() const = 0;
+        virtual unsigned queryScheduleCount() const = 0;
+        virtual unsigned queryRetriesAllowed() const = 0;
+        virtual int queryPersistCopies() const = 0;  // 0 - unmangled name,  < 0 - use default, > 0 - max number
+        virtual bool queryPersistRefresh() const = 0;
+        virtual unsigned queryScheduleCountRemaining() const = 0;
+        virtual unsigned queryRetriesRemaining() const = 0;
+        virtual int queryFailCode() const = 0;
+        virtual const char * queryFailMessage() const = 0;
+        virtual const char * queryEventName() const = 0;
+        virtual const char * queryEventExtra() const = 0;
+        */
+        }
+        return nullptr;
+    }
+
+    virtual const char * queryHint(const char * kind) const
+    {
+        return nullptr;
+    }
+
+protected:
+    bool initWorkflowItem()
+    {
+        curScope.clear().append(WorkflowScopePrefix).append(workflow.item(curWorkflow).queryWfid());
+        return true;
+    }
+
+    const char * getValueText(unsigned value, StringBuffer & scratchpad, const char * prefix = nullptr) const
+    {
+        return scratchpad.clear().append(prefix).append(value).str();
+    }
+
+    const char * queryOptString(IStringVal & value) const
+    {
+        const char * text = value.str();
+        if (!text || !*text)
+            return nullptr;
+        return text;
+    }
+
+    const char * getWfidText(unsigned value, StringBuffer & scratchpad) const
+    {
+        if (!value)
+            return nullptr;
+        return scratchpad.clear().append(WorkflowScopePrefix).append(value).str();
+    }
+
+    void play(IWuScopeVisitor & visitor, const std::initializer_list<WuAttr> & attrs) const
+    {
+        StringBuffer scratchpad;
+        for (auto attr : attrs)
+        {
+            const char * value = queryAttribute(attr, scratchpad.clear());
+            if (value)
+                visitor.noteAttribute(attr, value);
+        }
+    }
+
+protected:
+    IArrayOf<IConstWorkflowItem> workflow;
+    unsigned curWorkflow = 0;
+    StringBuffer curScope;
+};
+
+
+
 
 /*
  * An implementation of IConstWUScopeIterator that combines results from multiple sources.
@@ -2264,7 +2460,7 @@ static constexpr EnumMapping filterOptions[] = {
         { FOproperty, "prop" }, { FOproperty, "property" },
         { FOmeasure, "measure" }, { FOversion, "version" }, { 0, nullptr} };
 static constexpr EnumMapping sourceMappings[] = {
-        { SSFsearchGlobalStats, "global" }, { SSFsearchGraphStats, "stats" }, { SSFsearchGraphStats, "statistics" }, { SSFsearchGraph, "graph" }, { SSFsearchExceptions, "exception" },
+        { SSFsearchGlobalStats, "global" }, { SSFsearchGraphStats, "stats" }, { SSFsearchGraphStats, "statistics" }, { SSFsearchGraph, "graph" }, { SSFsearchExceptions, "exception" }, { SSFsearchWorkflow, "workflow" },
         { (int)SSFsearchAll, "all" }, { 0, nullptr } };
 static constexpr EnumMapping propertyMappings[] = {
         { PTstatistics, "stat" }, { PTstatistics, "statistic" }, { PTattributes, "attr" }, { PTattributes, "attribute" }, { PThints, "hint" },
@@ -2703,10 +2899,12 @@ void WuScopeFilter::finishedFilter()
             if (!(properties & PTstatistics))
                 sourceFlags &= ~(SSFsearchGlobalStats|SSFsearchGraphStats);
 
-            //graph only contains attributes and hints => remove if not interested
+            //graph, workflow only contains attributes and hints => remove if not interested
             if (!(properties & (PTattributes|PThints)))
-                sourceFlags &= ~(SSFsearchGraph);
+                sourceFlags &= ~(SSFsearchGraph|SSFsearchWorkflow);
         }
+
+
     }
 
     //Optimize sources if they haven't been explicitly specified
@@ -2720,7 +2918,7 @@ void WuScopeFilter::finishedFilter()
     else if (matchOnly(SSTgraph))
     {
         //Graph starts are stored globally, not in the graph stats.
-        sourceFlags &= ~(SSFsearchGraphStats);
+        sourceFlags &= ~(SSFsearchGraphStats|SSFsearchWorkflow);
 
         if (!(properties & (PTattributes|PThints)))
             sourceFlags &= ~(SSFsearchGraph);
@@ -2729,6 +2927,8 @@ void WuScopeFilter::finishedFilter()
     }
     else if (matchOnly(SSTsubgraph))
     {
+        sourceFlags &= ~(SSFsearchWorkflow);
+
         //subgraph timings are stored globally - otherwise need to look in the graph stats.
         if (!(properties & (PTattributes|PThints)))
         {
@@ -2743,12 +2943,12 @@ void WuScopeFilter::finishedFilter()
     else if (matchOnly(SSTcompilestage))
     {
         //compile stages are not stored in the graph
-        sourceFlags &= ~(SSFsearchGraphStats|SSFsearchGraph);
+        sourceFlags &= ~(SSFsearchGraphStats|SSFsearchGraph|SSFsearchWorkflow);
     }
     else if (matchOnly(SSTactivity))
     {
         //information about activities is not stored globally
-        sourceFlags &= ~(SSFsearchGlobalStats);
+        sourceFlags &= ~(SSFsearchGlobalStats|SSFsearchWorkflow);
     }
 
     // Everything stored in the graphs stats has a depth of 2 or more - so ignore if there are not matches in that depth
@@ -2757,10 +2957,16 @@ void WuScopeFilter::finishedFilter()
         sourceFlags &= ~(SSFsearchGraphStats);
     }
 
+    if (scopeFilter.compareDepth(1) < 0)
+    {
+        //If minimum match depth is > 1 then it will never match workflow
+        sourceFlags &= ~(SSFsearchWorkflow);
+    }
+
     //The xml graph is never updated, so do not check it if minVersion != 0
     if (minVersion != 0)
     {
-        sourceFlags &= ~SSFsearchGraph;
+        sourceFlags &= ~(SSFsearchGraph|SSFsearchWorkflow);
     }
 }
 
@@ -7881,7 +8087,7 @@ bool parseGraphScope(const char *scope, StringAttr &graphName, unsigned & graphN
     if (!MATCHES_CONST_PREFIX(scope, GraphScopePrefix))
         return false;
 
-    graphNum = atoi(scope + CONST_STRLEN(GraphScopePrefix));
+    graphNum = atoi(scope + strlen(GraphScopePrefix));
     subGraphId = 0;
 
     const char * colon = strchr(scope, ':');
@@ -7894,7 +8100,7 @@ bool parseGraphScope(const char *scope, StringAttr &graphName, unsigned & graphN
     const char * subgraph = colon+1;
     graphName.set(scope, (size32_t)(colon - scope));
     if (MATCHES_CONST_PREFIX(subgraph, SubGraphScopePrefix))
-        subGraphId = atoi(subgraph+CONST_STRLEN(SubGraphScopePrefix));
+        subGraphId = atoi(subgraph+strlen(SubGraphScopePrefix));
     return true;
 }
 
@@ -8030,6 +8236,13 @@ IConstWUScopeIterator & CLocalWorkUnit::getScopeIterator(const WuScopeFilter & f
         compoundIter->addIter(graphIter);
     }
 
+    if (sources & SSFsearchWorkflow)
+    {
+        Owned<IConstWUScopeIterator> workflowIter(new WorkflowStatisticsScopeIterator(getWorkflowItems()));
+        compoundIter->addIter(workflowIter);
+    }
+
+
     return *compoundIter.getClear();
 }
 

+ 2 - 0
common/workunit/workunit.hpp

@@ -649,6 +649,7 @@ interface IConstWorkflowItem : extends IInterface
     virtual const char * queryEventExtra() const = 0;
     virtual unsigned queryScheduledWfid() const = 0;
     virtual IStringVal & queryCluster(IStringVal & val) const = 0;
+    virtual IStringVal & getLabel(IStringVal & val) const = 0;
 };
 inline bool isPersist(const IConstWorkflowItem & item) { return item.queryMode() == WFModePersist; }
 inline bool isCritical(const IConstWorkflowItem & item) { return item.queryMode() == WFModeCritical; }
@@ -1027,6 +1028,7 @@ enum WuScopeSourceFlags : unsigned
     SSFsearchGraphStats     = 0x0002,
     SSFsearchGraph          = 0x0004,
     SSFsearchExceptions     = 0x0008,
+    SSFsearchWorkflow       = 0x0010,
     SSFsearchAll            = 0x7fffffff,
     SSFunknown              = 0x80000000,
 };

+ 44 - 9
common/workunit/wuattr.cpp

@@ -29,20 +29,25 @@ public:
     const char * overridePath;  // Alternative xpath to check 1st for some overloaded attributes
     const char * childPath;     // The name of the <atr> for setting, or matching when iterating
     const char * dft;           // default value if not present
+    WuAttr singleKind;
+    WuAttr multiKind;
 };
 
 #define CHILDPATH(x) "att[@name='" x "']/@value"
 #define CHILDMPATH(x) "att[@name='" x "'][1]/@value"
-#define ATTR(kind, measure, path)           { Wa ## kind, measure, #kind, path, nullptr, nullptr, nullptr }
-#define ALTATTR(kind, measure, path, alt)   { Wa ## kind, measure, #kind, path, alt, nullptr }
-#define CHILD(kind, measure, path)          { Wa ## kind, measure, #kind, CHILDPATH(path), nullptr, path, nullptr }
-#define CHILD_MULTI(kind, measure, path)    { Wa ## kind, measure, #kind, CHILDMPATH(path), nullptr, path, nullptr }
-#define CHILD_D(kind, measure, path, dft)   { Wa ## kind, measure, #kind, CHILDPATH(path), nullptr, path, dft }
-
+#define ATTR(kind, measure, path)           { Wa ## kind, measure, #kind, path, nullptr, nullptr, nullptr, WaNone, WaNone }
+#define ALTATTR(kind, measure, path, alt)   { Wa ## kind, measure, #kind, path, alt, nullptr, nullptr, WaNone, WaNone }
+#define CHILD(kind, measure, path)          { Wa ## kind, measure, #kind, CHILDPATH(path), nullptr, path, nullptr, WaNone, WaNone }
+#define CHILD_MULTI(kind, measure, path)    { Wa ## kind, measure, #kind, CHILDMPATH(path), nullptr, path, nullptr, Wa ## kind, Wa ## kind ## List }
+#define CHILD_LIST(kind, measure)           { Wa ## kind ## List, measure, #kind "List", nullptr, nullptr, nullptr, nullptr, Wa ## kind, Wa ## kind ## List }
+#define CHILD_D(kind, measure, path, dft)   { Wa ## kind, measure, #kind, CHILDPATH(path), nullptr, path, dft, WaNone, WaNone  }
+#define ATTRX(kind, measure)                { Wa ## kind, measure, #kind, nullptr, nullptr, nullptr, nullptr, WaNone, WaNone  }
+#define ATTRX_MULTI(kind, measure)          { Wa ## kind , measure, #kind, nullptr, nullptr, nullptr, nullptr, Wa ## kind, Wa ## kind ## List  }
+#define ATTRX_LIST(kind, measure)           { Wa ## kind ## List, measure, #kind "List", nullptr, nullptr, nullptr, nullptr, Wa ## kind, Wa ## kind ## List }
 
 const static WuAttrInfo attrInfo[] = {
-    { WaNone, SMeasureNone, "none", nullptr, nullptr, nullptr },
-    { WaAll, SMeasureNone, "all", nullptr, nullptr, nullptr },
+    { WaNone, SMeasureNone, "none", nullptr, nullptr, nullptr, nullptr, WaNone, WaNone  },
+    { WaAll, SMeasureNone, "all", nullptr, nullptr, nullptr, nullptr, WaNone, WaNone  },
     CHILD(Kind, SMeasureEnum, "_kind"),
     ALTATTR(IdSource, SMeasureId, "@source", "att[@name='_sourceActivity']/@value"),
     ALTATTR(IdTarget, SMeasureId, "@target", "att[@name='_targetActivity']/@value"),
@@ -52,7 +57,9 @@ const static WuAttrInfo attrInfo[] = {
     CHILD(IsDependency, SMeasureBool, "_dependsOn"),
     CHILD(IsChildGraph, SMeasureBool, "_childGraph"),
     CHILD_MULTI(Definition, SMeasureText, "definition"),
+    CHILD_LIST(Definition, SMeasureText),
     CHILD_MULTI(EclName, SMeasureText, "name"),
+    CHILD_LIST(EclName, SMeasureText),
     CHILD(EclText, SMeasureText, "ecl"),
     CHILD(RecordSize, SMeasureText, "recordSize"),
     CHILD(PredictedCount, SMeasureText, "predictedCount"),
@@ -96,6 +103,7 @@ const static WuAttrInfo attrInfo[] = {
     CHILD(MetaLocalSortOrder, SMeasureText, "metaLocalSortOrder"),
     CHILD(MetaGroupSortOrder, SMeasureText, "metaGroupSortOrder"),
     CHILD_MULTI(Section, SMeasureText, "section"),
+    CHILD_LIST(Section, SMeasureText),
     CHILD(LibraryName, SMeasureText, "libname"),
     CHILD(MatchLikelihood, SMeasureText, "matchLikelihood"),
     CHILD(SpillReason, SMeasureText, "spillReason"),
@@ -125,7 +133,21 @@ const static WuAttrInfo attrInfo[] = {
     ATTR(IsLoopBody, SMeasureBool, "@loopBody"),
     CHILD(NumResults, SMeasureCount, "_numResults"),
     CHILD(WhenIndex, SMeasureText, "_when"),
-    { WaMax, SMeasureNone, nullptr, nullptr, nullptr, nullptr }
+    ATTRX_MULTI(IdDependency, SMeasureId),
+    ATTRX_LIST(IdDependency, SMeasureId),
+    ATTRX(IsScheduled, SMeasureBool),
+    ATTRX(IdSuccess, SMeasureId),
+    ATTRX(IdFailure, SMeasureId),
+    ATTRX(IdRecovery, SMeasureId),
+    ATTRX(IdPersist, SMeasureId),
+    ATTRX(IdScheduled, SMeasureId),
+    ATTRX(PersistName, SMeasureText),
+    ATTRX(Type, SMeasureText),
+    ATTRX(Mode, SMeasureText),
+    ATTRX(State, SMeasureText),
+    ATTRX(Cluster, SMeasureText),
+    ATTRX(CriticalSection, SMeasureText),
+    { WaMax, SMeasureNone, nullptr, nullptr, nullptr, nullptr, nullptr, WaNone, WaNone }
 };
 
 static MapConstStringTo<WuAttr, WuAttr> nameToWa(true);        // Names are case insensitive
@@ -283,3 +305,16 @@ void setAttributeValueInt(IPropertyTree & tgt, WuAttr kind, __int64 value)
     else
         addGraphAttribute(&tgt, info.childPath)->setPropInt64("@value", value);
 }
+
+bool isListAttribute(WuAttr kind)
+{
+    const WuAttrInfo & info = attrInfo[kind-WaNone];
+    return (info.multiKind != WaNone) && (info.multiKind == kind);
+}
+
+bool getListAttribute(WuAttr kind)
+{
+    const WuAttrInfo & info = attrInfo[kind-WaNone];
+    WuAttr multiKind = info.multiKind;
+    return (multiKind != WaNone) && (multiKind != kind) ? multiKind : WaNone;
+}

+ 21 - 0
common/workunit/wuattr.hpp

@@ -28,6 +28,7 @@
 #endif
 
 //The wuattribute values start from a high value - so that they do not overlap with StXXX
+//They are not currently persisted, so it is fine to modify the list for major releases
 enum WuAttr : unsigned
 {
     WaNone = 0x80000000,
@@ -41,7 +42,9 @@ enum WuAttr : unsigned
     WaIsDependency,
     WaIsChildGraph,
     WaDefinition,
+    WaDefinitionList,
     WaEclName,
+    WaEclNameList,
     WaEclText,
     WaRecordSize,
     WaPredictedCount,
@@ -85,6 +88,7 @@ enum WuAttr : unsigned
     WaMetaLocalSortOrder,
     WaMetaGroupSortOrder,
     WaSection,
+    WaSectionList,
     WaLibraryName,
     WaMatchLikelihood,
     WaSpillReason,
@@ -114,6 +118,20 @@ enum WuAttr : unsigned
     WaIsLoopBody,
     WaNumResults,
     WaWhenIndex,
+    WaIdDependency,
+    WaIdDependencyList,
+    WaIsScheduled,
+    WaIdSuccess,
+    WaIdFailure,
+    WaIdRecovery,
+    WaIdPersist,
+    WaIdScheduled,
+    WaPersistName,
+    WaType,
+    WaMode,
+    WaState,
+    WaCluster,
+    WaCriticalSection,
     WaMax
 };
 
@@ -127,4 +145,7 @@ extern WORKUNIT_API void setAttributeValue(IPropertyTree & tgt, WuAttr kind, con
 extern WORKUNIT_API void setAttributeValueBool(IPropertyTree & tgt, WuAttr kind, bool value, bool alwaysAdd = false);
 extern WORKUNIT_API void setAttributeValueInt(IPropertyTree & tgt, WuAttr kind, __int64 value);
 
+extern WORKUNIT_API bool isListAttribute(WuAttr kind);
+extern WORKUNIT_API bool getListAttribute(WuAttr kind);
+
 #endif

+ 1 - 2
system/jlib/jstatcodes.h

@@ -27,8 +27,7 @@
 #define WorkflowScopePrefix "w"
 #define ChildGraphScopePrefix "c"
 
-#define CONST_STRLEN(x) (sizeof(x)-1)       // sizeof(const-string) = strlen(const-string) + 1 byte for the \0 terminator
-#define MATCHES_CONST_PREFIX(search, prefix) (strncmp(search, prefix, CONST_STRLEN(prefix)) == 0)
+#define MATCHES_CONST_PREFIX(search, prefix) (strncmp(search, prefix, strlen(prefix)) == 0)
 
 enum CombineStatsAction
 {

+ 11 - 11
system/jlib/jstats.cpp

@@ -1375,9 +1375,9 @@ bool StatsScopeId::setScopeText(const char * text, const char * * _next)
     case ActivityScopePrefix[0]:
         if (MATCHES_CONST_PREFIX(text, ActivityScopePrefix))
         {
-            if (isdigit(text[CONST_STRLEN(ActivityScopePrefix)]))
+            if (isdigit(text[strlen(ActivityScopePrefix)]))
             {
-                unsigned id = strtoul(text + CONST_STRLEN(ActivityScopePrefix), next, 10);
+                unsigned id = strtoul(text + strlen(ActivityScopePrefix), next, 10);
                 setActivityId(id);
                 return true;
             }
@@ -1386,9 +1386,9 @@ bool StatsScopeId::setScopeText(const char * text, const char * * _next)
     case GraphScopePrefix[0]:
         if (MATCHES_CONST_PREFIX(text, GraphScopePrefix))
         {
-            if (isdigit(text[CONST_STRLEN(GraphScopePrefix)]))
+            if (isdigit(text[strlen(GraphScopePrefix)]))
             {
-                unsigned id = strtoul(text + CONST_STRLEN(GraphScopePrefix), next, 10);
+                unsigned id = strtoul(text + strlen(GraphScopePrefix), next, 10);
                 setId(SSTgraph, id);
                 return true;
             }
@@ -1397,9 +1397,9 @@ bool StatsScopeId::setScopeText(const char * text, const char * * _next)
     case SubGraphScopePrefix[0]:
         if (MATCHES_CONST_PREFIX(text, SubGraphScopePrefix))
         {
-            if (isdigit(text[CONST_STRLEN(SubGraphScopePrefix)]))
+            if (isdigit(text[strlen(SubGraphScopePrefix)]))
             {
-                unsigned id = strtoul(text + CONST_STRLEN(SubGraphScopePrefix), next, 10);
+                unsigned id = strtoul(text + strlen(SubGraphScopePrefix), next, 10);
                 setSubgraphId(id);
                 return true;
             }
@@ -1411,7 +1411,7 @@ bool StatsScopeId::setScopeText(const char * text, const char * * _next)
             const char * underscore = strchr(text, '_');
             if (!underscore || !isdigit(underscore[1]))
                 return false;
-            unsigned id1 = atoi(text + CONST_STRLEN(EdgeScopePrefix));
+            unsigned id1 = atoi(text + strlen(EdgeScopePrefix));
             unsigned id2 = strtoul(underscore+1, next, 10);
             setEdgeId(id1, id2);
             return true;
@@ -1420,21 +1420,21 @@ bool StatsScopeId::setScopeText(const char * text, const char * * _next)
     case FunctionScopePrefix[0]:
         if (MATCHES_CONST_PREFIX(text, FunctionScopePrefix))
         {
-            setFunctionId(text+CONST_STRLEN(FunctionScopePrefix));
+            setFunctionId(text+ strlen(FunctionScopePrefix));
             return true;
         }
         break;
     case WorkflowScopePrefix[0]:
-        if (MATCHES_CONST_PREFIX(text, WorkflowScopePrefix))
+        if (MATCHES_CONST_PREFIX(text, WorkflowScopePrefix) && isdigit(text[strlen(WorkflowScopePrefix)]))
         {
-            setWorkflowId(atoi(text+CONST_STRLEN(WorkflowScopePrefix)));
+            setWorkflowId(atoi(text+ strlen(WorkflowScopePrefix)));
             return true;
         }
         break;
     case ChildGraphScopePrefix[0]:
         if (MATCHES_CONST_PREFIX(text, ChildGraphScopePrefix))
         {
-            setChildGraphId(atoi(text+CONST_STRLEN(ChildGraphScopePrefix)));
+            setChildGraphId(atoi(text+ strlen(ChildGraphScopePrefix)));
             return true;
         }
         break;