浏览代码

HPCC-17625 JPTree memory usage and contention issues

Allow PTree priority (speed vs memory) to be selected at creation time.

Create some baseline stats and timings to allow further investigation of
contention and evaluation of potential improvements.

Signed-off-by: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 8 年之前
父节点
当前提交
4d5a398017
共有 4 个文件被更改,包括 430 次插入33 次删除
  1. 92 23
      system/jlib/jptree.cpp
  2. 14 4
      system/jlib/jptree.hpp
  3. 50 1
      system/jlib/jptree.ipp
  4. 274 5
      testing/unittests/unittests.cpp

+ 92 - 23
system/jlib/jptree.cpp

@@ -2878,14 +2878,19 @@ LocalPTree::LocalPTree(const char *_name, byte _flags, IPTArrayValue *_value, Ch
 LocalPTree::~LocalPTree()
 LocalPTree::~LocalPTree()
 {
 {
     if (name)
     if (name)
+    {
+#ifdef TRACE_NAME_SIZE
+        totsize -= sizeof(*name)+strlen(name->get())+1;
+#endif
         free(name);
         free(name);
+    }
     if (!attrs)
     if (!attrs)
         return;
         return;
     AttrValue *a = attrs+numAttrs;
     AttrValue *a = attrs+numAttrs;
     while (a--!=attrs)
     while (a--!=attrs)
     {
     {
-        free(a->key);
-        free(a->value);
+        AttrStrC::destroy((AttrStrC *)a->key);
+        AttrStrC::destroy((AttrStrC *)a->value);
     }
     }
     free(attrs);
     free(attrs);
 }
 }
@@ -2896,9 +2901,27 @@ void LocalPTree::setName(const char *_name)
     if (!_name)
     if (!_name)
         name = nullptr;
         name = nullptr;
     else
     else
+    {
+#ifdef TRACE_ALL_NAME
+        DBGLOG("LocalPTree::setName %s", _name);
+#endif
+#ifdef TRACE_NAME_SIZE
+        totsize += sizeof(HashKeyElement)+strlen(_name)+1;
+        if (totsize > maxsize)
+        {
+            maxsize.store(totsize);
+            DBGLOG("Name total size now %" I64F "d", maxsize.load());
+        }
+#endif
         name = AtomRefTable::createKeyElement(_name, isnocase());
         name = AtomRefTable::createKeyElement(_name, isnocase());
+    }
     if (oname)
     if (oname)
+    {
+#ifdef TRACE_NAME_SIZE
+        totsize -= sizeof(HashKeyElement)+strlen(oname->get())+1;
+#endif
         free(oname);
         free(oname);
+    }
 }
 }
 
 
 bool LocalPTree::removeAttribute(const char *key)
 bool LocalPTree::removeAttribute(const char *key)
@@ -2908,8 +2931,8 @@ bool LocalPTree::removeAttribute(const char *key)
         return false;
         return false;
     numAttrs--;
     numAttrs--;
     unsigned pos = del-attrs;
     unsigned pos = del-attrs;
-    free(del->key);
-    free(del->value);
+    AttrStrC::destroy((AttrStrC *)del->key);
+    AttrStrC::destroy((AttrStrC *)del->value);
     memmove(attrs+pos, attrs+pos+1, (numAttrs-pos)*sizeof(AttrValue));
     memmove(attrs+pos, attrs+pos+1, (numAttrs-pos)*sizeof(AttrValue));
     return true;
     return true;
 }
 }
@@ -2942,6 +2965,17 @@ void LocalPTree::setAttribute(const char *key, const char *val)
     }
     }
 }
 }
 
 
+#ifdef TRACE_ATTRSTR_SIZE
+std::atomic<__int64> AttrStr::totsize { 0 };
+std::atomic<__int64> AttrStr::maxsize { 0 };
+#endif
+
+#ifdef TRACE_NAME_SIZE
+std::atomic<__int64> CAtomPTree::totsize { 0 };
+std::atomic<__int64> CAtomPTree::maxsize { 0 };
+std::atomic<__int64> LocalPTree::totsize { 0 };
+std::atomic<__int64> LocalPTree::maxsize { 0 };
+#endif
 
 
 ///////////////////
 ///////////////////
 
 
@@ -2957,6 +2991,10 @@ CAtomPTree::~CAtomPTree()
     if (name)
     if (name)
     {
     {
         AtomRefTable *kT = nc?keyTableNC:keyTable;
         AtomRefTable *kT = nc?keyTableNC:keyTable;
+#ifdef TRACE_NAME_SIZE
+        if (name->queryReferences()==1)  // Not strictly accurate but good enough
+            totsize -= sizeof(HashKeyElement)+strlen(name->get())+1;
+#endif
         kT->releaseKey(name);
         kT->releaseKey(name);
     }
     }
     if (!attrs)
     if (!attrs)
@@ -2984,9 +3022,29 @@ void CAtomPTree::setName(const char *_name)
         if (!validateXMLTag(_name))
         if (!validateXMLTag(_name))
             throw MakeIPTException(PTreeExcpt_InvalidTagName, ": %s", _name);
             throw MakeIPTException(PTreeExcpt_InvalidTagName, ": %s", _name);
         name = kT->queryCreate(_name);
         name = kT->queryCreate(_name);
+#ifdef TRACE_ALL_NAME
+        DBGLOG("CAtomPTree::setName %s", _name);
+#endif
+#ifdef TRACE_NAME_SIZE
+        if (name->queryReferences()==1)  // Not strictly accurate but good enough
+        {
+            totsize += sizeof(HashKeyElement)+strlen(_name)+1;
+            if (totsize > maxsize)
+            {
+                maxsize.store(totsize);
+                DBGLOG("AtomName total size now %" I64F "d", maxsize.load());
+            }
+        }
+#endif
     }
     }
     if (oname)
     if (oname)
+    {
+#ifdef TRACE_NAME_SIZE
+        if (oname->queryReferences()==1)  // Not strictly accurate but good enough
+            totsize -= sizeof(HashKeyElement)+strlen(oname->get())+1;
+#endif
         kT->releaseKey(oname);
         kT->releaseKey(oname);
+    }
 }
 }
 
 
 AttrValue *CAtomPTree::newAttrArray(unsigned n)
 AttrValue *CAtomPTree::newAttrArray(unsigned n)
@@ -3438,9 +3496,9 @@ IPropertyTreeIterator *PTStackIterator::popFromStack(StringAttr &path)
 
 
 // factory methods
 // factory methods
 
 
-IPropertyTree *createPTree(MemoryBuffer &src)
+IPropertyTree *createPTree(MemoryBuffer &src, byte flags)
 {
 {
-    IPropertyTree *tree = new DEFAULT_PTREE_TYPE();
+    IPropertyTree *tree = createPTree(nullptr, flags);
     tree->deserialize(src);
     tree->deserialize(src);
     return tree;
     return tree;
 }
 }
@@ -6070,8 +6128,8 @@ jlib_decl void testJdocCompare()
 
 
 #endif
 #endif
 
 
-
-class COrderedPTree : public DEFAULT_PTREE_TYPE
+template <class BASE_PTREE>
+class COrderedPTree : public BASE_PTREE
 {
 {
     template <class BASECHILDMAP>
     template <class BASECHILDMAP>
     class jlib_decl COrderedChildMap : public BASECHILDMAP
     class jlib_decl COrderedChildMap : public BASECHILDMAP
@@ -6137,43 +6195,54 @@ class COrderedPTree : public DEFAULT_PTREE_TYPE
         }
         }
     };
     };
 public:
 public:
-    COrderedPTree(const char *name=NULL, byte flags=ipt_none, IPTArrayValue *value=NULL, ChildMap *children=NULL)
-        : DEFAULT_PTREE_TYPE(name, flags|ipt_ordered, value, children) { }
+    typedef COrderedPTree<BASE_PTREE> SELF;
+    COrderedPTree<BASE_PTREE>(const char *name=NULL, byte flags=ipt_none, IPTArrayValue *value=NULL, ChildMap *children=NULL)
+        : BASE_PTREE(name, flags|ipt_ordered, value, children) { }
 
 
-    virtual bool isEquivalent(IPropertyTree *tree) const override { return (NULL != QUERYINTERFACE(tree, COrderedPTree)); }
+    virtual bool isEquivalent(IPropertyTree *tree) const override { return (NULL != QUERYINTERFACE(tree, COrderedPTree<BASE_PTREE>)); }
     virtual IPropertyTree *create(const char *name=NULL, IPTArrayValue *value=NULL, ChildMap *children=NULL, bool existing=false) override
     virtual IPropertyTree *create(const char *name=NULL, IPTArrayValue *value=NULL, ChildMap *children=NULL, bool existing=false) override
     {
     {
-        return new COrderedPTree(name, flags, value, children);
+        return new COrderedPTree<BASE_PTREE>(name, SELF::flags, value, children);
     }
     }
     virtual IPropertyTree *create(MemoryBuffer &mb) override
     virtual IPropertyTree *create(MemoryBuffer &mb) override
     {
     {
-        IPropertyTree *tree = new COrderedPTree();
+        IPropertyTree *tree = new COrderedPTree<BASE_PTREE>();
         tree->deserialize(mb);
         tree->deserialize(mb);
         return tree;
         return tree;
     }
     }
     virtual void createChildMap() override
     virtual void createChildMap() override
     {
     {
-        if (isnocase())
-            children = new COrderedChildMap<ChildMapNC>();
+        if (SELF::isnocase())
+            SELF::children = new COrderedChildMap<ChildMapNC>();
         else
         else
-            children = new COrderedChildMap<ChildMap>();
+            SELF::children = new COrderedChildMap<ChildMap>();
     }
     }
 };
 };
 
 
 IPropertyTree *createPTree(byte flags)
 IPropertyTree *createPTree(byte flags)
 {
 {
-    if (flags & ipt_ordered)
-        return new COrderedPTree(NULL, flags);
-    else
-        return new DEFAULT_PTREE_TYPE(NULL, flags);
+    return createPTree(NULL, flags);
 }
 }
 
 
 IPropertyTree *createPTree(const char *name, byte flags)
 IPropertyTree *createPTree(const char *name, byte flags)
 {
 {
-    if (flags & ipt_ordered)
-        return new COrderedPTree(name, flags);
-    else
+    switch (flags & (ipt_ordered|ipt_fast|ipt_lowmem))
+    {
+    case ipt_ordered|ipt_fast:
+        return new COrderedPTree<LocalPTree>(name, flags);
+    case ipt_ordered|ipt_lowmem:
+        return new COrderedPTree<CAtomPTree>(name, flags);
+    case ipt_ordered:
+        return new COrderedPTree<DEFAULT_PTREE_TYPE>(name, flags);
+    case ipt_fast:
+        return new LocalPTree(name, flags);
+    case ipt_lowmem:
+        return new CAtomPTree(name, flags);
+    case 0:
         return new DEFAULT_PTREE_TYPE(name, flags);
         return new DEFAULT_PTREE_TYPE(name, flags);
+    default:
+        throwUnexpectedX("Invalid flags - ipt_fast and ipt_lowmem should not be specified together");
+    }
 }
 }
 
 
 typedef enum _ptElementType
 typedef enum _ptElementType

+ 14 - 4
system/jlib/jptree.hpp

@@ -170,9 +170,19 @@ interface IPTreeNodeCreator : extends IInterface
     virtual IPropertyTree *create(const char *tag) = 0;
     virtual IPropertyTree *create(const char *tag) = 0;
 };
 };
 
 
-// NB ipt_ext4 - used by SDS
-// NB ipt_ext5 - used by SDS
-enum ipt_flags { ipt_none=0x00, ipt_caseInsensitive=0x01, ipt_binary=0x02, ipt_ordered=0x04, ipt_ext1=0x08, ipt_ext2=16, ipt_ext3=32, ipt_ext4=64, ipt_ext5=128 };
+enum ipt_flags
+{
+    ipt_none=0x00,
+    ipt_caseInsensitive=0x01,
+    ipt_binary  = 0x02,
+    ipt_ordered = 0x04,   // Preserve element ordering
+    ipt_fast    = 0x08,   // Prioritize speed over low memory usage
+    ipt_lowmem  = 0x10,   // Prioritize low memory usage over speed
+    ipt_ext3    = 0x20,   // Unused
+    ipt_ext4    = 0x40,   // Used internally in Dali
+    ipt_ext5    = 0x80    // Used internally in Dali
+};
+
 jlib_decl IPTreeMaker *createPTreeMaker(byte flags=ipt_none, IPropertyTree *root=NULL, IPTreeNodeCreator *nodeCreator=NULL);
 jlib_decl IPTreeMaker *createPTreeMaker(byte flags=ipt_none, IPropertyTree *root=NULL, IPTreeNodeCreator *nodeCreator=NULL);
 jlib_decl IPTreeMaker *createRootLessPTreeMaker(byte flags=ipt_none, IPropertyTree *root=NULL, IPTreeNodeCreator *nodeCreator=NULL);
 jlib_decl IPTreeMaker *createRootLessPTreeMaker(byte flags=ipt_none, IPropertyTree *root=NULL, IPTreeNodeCreator *nodeCreator=NULL);
 jlib_decl IPTreeReader *createXMLStreamReader(ISimpleReadStream &stream, IPTreeNotifyEvent &iEvent, PTreeReaderOptions xmlReaderOptions=ptr_ignoreWhiteSpace, size32_t bufSize=0);
 jlib_decl IPTreeReader *createXMLStreamReader(ISimpleReadStream &stream, IPTreeNotifyEvent &iEvent, PTreeReaderOptions xmlReaderOptions=ptr_ignoreWhiteSpace, size32_t bufSize=0);
@@ -194,7 +204,7 @@ jlib_decl void synchronizePTree(IPropertyTree *target, IPropertyTree *source);
 jlib_decl IPropertyTree *ensurePTree(IPropertyTree *root, const char *xpath);
 jlib_decl IPropertyTree *ensurePTree(IPropertyTree *root, const char *xpath);
 jlib_decl bool areMatchingPTrees(IPropertyTree * left, IPropertyTree * right);
 jlib_decl bool areMatchingPTrees(IPropertyTree * left, IPropertyTree * right);
 
 
-jlib_decl IPropertyTree *createPTree(MemoryBuffer &src);
+jlib_decl IPropertyTree *createPTree(MemoryBuffer &src, byte flags=ipt_none);
 
 
 jlib_decl IPropertyTree *createPTree(byte flags=ipt_none);
 jlib_decl IPropertyTree *createPTree(byte flags=ipt_none);
 jlib_decl IPropertyTree *createPTree(const char *name, byte flags=ipt_none);
 jlib_decl IPropertyTree *createPTree(const char *name, byte flags=ipt_none);

+ 50 - 1
system/jlib/jptree.ipp

@@ -28,6 +28,7 @@
 
 
 #include "jptree.hpp"
 #include "jptree.hpp"
 #include "jbuff.hpp"
 #include "jbuff.hpp"
+#include "jlog.hpp"
 
 
 #define ANE_APPEND -1
 #define ANE_APPEND -1
 #define ANE_SET -2
 #define ANE_SET -2
@@ -205,7 +206,7 @@ public:
     virtual const void *queryValueRaw() const override { return get(); }
     virtual const void *queryValueRaw() const override { return get(); }
     virtual size32_t queryValueRawSize() const override { return (size32_t)length(); }
     virtual size32_t queryValueRawSize() const override { return (size32_t)length(); }
 
 
-// serilizable
+// serializable
     virtual void serialize(MemoryBuffer &tgt) override;
     virtual void serialize(MemoryBuffer &tgt) override;
     virtual void deserialize(MemoryBuffer &src) override;
     virtual void deserialize(MemoryBuffer &src) override;
 
 
@@ -217,12 +218,24 @@ private:
 #define IptFlagSet(fs, f) (fs |= (f))
 #define IptFlagSet(fs, f) (fs |= (f))
 #define IptFlagClr(fs, f) (fs &= (~f))
 #define IptFlagClr(fs, f) (fs &= (~f))
 
 
+#ifdef _DEBUG
+  //#define TRACE_ATTRSTR_SIZE
+  //#define TRACE_NAME_SIZE
+  //#define TRACE_ALL_ATTRSTR
+  //#define TRACE_ALL_NAME
+#endif
+
 struct AttrStr
 struct AttrStr
 {
 {
     unsigned hash;
     unsigned hash;
     unsigned short linkcount;
     unsigned short linkcount;
     char str[1];
     char str[1];
     const char *get() const { return str; }
     const char *get() const { return str; }
+
+#ifdef TRACE_ATTRSTR_SIZE
+    static std::atomic<__int64> totsize;
+    static std::atomic<__int64> maxsize;
+#endif
 };
 };
 
 
 struct AttrValue
 struct AttrValue
@@ -384,6 +397,17 @@ struct AttrStrC : public AttrStr
     static AttrStrC *create(const char *k)
     static AttrStrC *create(const char *k)
     {
     {
         size32_t kl = (k?strlen(k):0);
         size32_t kl = (k?strlen(k):0);
+#ifdef TRACE_ALL_ATTRSTR
+        DBGLOG("AttrStr::Create %s", k);
+#endif
+#ifdef TRACE_ATTRSTR_SIZE
+        totsize += sizeof(AttrStrC)+kl+1;
+        if (totsize > maxsize)
+        {
+            maxsize.store(totsize);
+            DBGLOG("AttrStr total size now %" I64F "d", maxsize.load());
+        }
+#endif
         AttrStrC *ret = (AttrStrC *)malloc(sizeof(AttrStrC)+kl);
         AttrStrC *ret = (AttrStrC *)malloc(sizeof(AttrStrC)+kl);
         memcpy(ret->str, k, kl);
         memcpy(ret->str, k, kl);
         ret->str[kl] = 0;
         ret->str[kl] = 0;
@@ -393,6 +417,9 @@ struct AttrStrC : public AttrStr
     }
     }
     static void destroy(AttrStrC *a)
     static void destroy(AttrStrC *a)
     {
     {
+#ifdef TRACE_ATTRSTR_SIZE
+        if (a) totsize -= sizeof(AttrStrC)+strlen(a->str)+1;
+#endif
         free(a);
         free(a);
     }
     }
 };
 };
@@ -410,6 +437,17 @@ struct AttrStrNC : public AttrStr
     static AttrStrNC *create(const char *k)
     static AttrStrNC *create(const char *k)
     {
     {
         size32_t kl = (k?strlen(k):0);
         size32_t kl = (k?strlen(k):0);
+#ifdef TRACE_ALL_ATTRSTR
+        DBGLOG("AttrStr::Create %s", k);
+#endif
+#ifdef TRACE_ATTRSTR_SIZE
+        totsize += sizeof(AttrStrNC)+kl+1;
+        if (totsize > maxsize)
+        {
+            maxsize.store(totsize);
+            DBGLOG("AttrStr total size now %" I64F "d", maxsize.load());
+        }
+#endif
         AttrStrNC *ret = (AttrStrNC *)malloc(sizeof(AttrStrNC)+kl);
         AttrStrNC *ret = (AttrStrNC *)malloc(sizeof(AttrStrNC)+kl);
         memcpy(ret->str,k,kl);
         memcpy(ret->str,k,kl);
         ret->str[kl] = 0;
         ret->str[kl] = 0;
@@ -419,6 +457,9 @@ struct AttrStrNC : public AttrStr
     }
     }
     static void destroy(AttrStrNC *a)
     static void destroy(AttrStrNC *a)
     {
     {
+#ifdef TRACE_ATTRSTR_SIZE
+        if (a) totsize -= sizeof(AttrStrC)+strlen(a->str)+1;
+#endif
         free(a);
         free(a);
     }
     }
 };
 };
@@ -471,6 +512,10 @@ public:
 
 
 class jlib_decl CAtomPTree : public PTree
 class jlib_decl CAtomPTree : public PTree
 {
 {
+#ifdef TRACE_NAME_SIZE
+    static std::atomic<__int64> totsize;
+    static std::atomic<__int64> maxsize;
+#endif
     AttrValue *newAttrArray(unsigned n);
     AttrValue *newAttrArray(unsigned n);
     void freeAttrArray(AttrValue *a, unsigned n);
     void freeAttrArray(AttrValue *a, unsigned n);
 
 
@@ -500,6 +545,10 @@ jlib_decl IPropertyTree *createPropBranch(IPropertyTree *tree, const char *xpath
 class jlib_decl LocalPTree : public PTree
 class jlib_decl LocalPTree : public PTree
 {
 {
 protected:
 protected:
+#ifdef TRACE_NAME_SIZE
+    static std::atomic<__int64> totsize;
+    static std::atomic<__int64> maxsize;
+#endif
     virtual void setAttribute(const char *attr, const char *val) override;
     virtual void setAttribute(const char *attr, const char *val) override;
     virtual bool removeAttribute(const char *k) override;
     virtual bool removeAttribute(const char *k) override;
 public:
 public:

+ 274 - 5
testing/unittests/unittests.cpp

@@ -102,11 +102,13 @@ void loadDlls(IArray &objects, const char * libDirectory)
     {
     {
         const char *thisDll = libFiles->query().queryFilename();
         const char *thisDll = libFiles->query().queryFilename();
         if (!strstr(thisDll, "javaembed"))  // Bit of a hack, but loading this if java not present terminates...
         if (!strstr(thisDll, "javaembed"))  // Bit of a hack, but loading this if java not present terminates...
-        {
-            LoadedObject *loaded = loadDll(thisDll);
-            if (loaded)
-                objects.append(*loaded);
-        }
+            if (!strstr(thisDll, "py2embed"))      // These two clash, so ...
+                if (!strstr(thisDll, "py3embed"))  // ... best to load neither...
+                {
+                    LoadedObject *loaded = loadDll(thisDll);
+                    if (loaded)
+                        objects.append(*loaded);
+                }
     }
     }
 }
 }
 
 
@@ -280,6 +282,273 @@ class InternalStatisticsTest : public CppUnit::TestFixture
 CPPUNIT_TEST_SUITE_REGISTRATION( InternalStatisticsTest );
 CPPUNIT_TEST_SUITE_REGISTRATION( InternalStatisticsTest );
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( InternalStatisticsTest, "StatisticsTest" );
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( InternalStatisticsTest, "StatisticsTest" );
 
 
+class PtreeThreadingTest : public CppUnit::TestFixture
+{
+    CPPUNIT_TEST_SUITE( PtreeThreadingTest  );
+        CPPUNIT_TEST(testContention);
+    CPPUNIT_TEST_SUITE_END();
+
+    void testContention()
+    {
+        _testContention(ipt_lowmem);
+        _testContention(ipt_fast);
+    }
+    void _testContention(byte flags)
+    {
+        class casyncfor: public CAsyncFor
+        {
+            volatile int v;
+            void donothing()
+            {
+                v++;
+            }
+            byte flags = ipt_none;
+            int mode = 0;
+            int iterations = 0;
+            const char *desc = nullptr;
+
+        public:
+            casyncfor(const char *_desc, byte _flags, int _mode, int _iter)
+            : flags(_flags), mode(_mode), iterations(_iter), desc(_desc)
+            {
+            };
+            double For(unsigned num, unsigned maxatonce, double overhead = 0.0)
+            {
+                unsigned start = msTick();
+                CAsyncFor::For(num, maxatonce);
+                unsigned elapsed = msTick()-start;
+                double looptime = (elapsed * 1.0) / (iterations*num);
+                if (mode < 3)
+                    DBGLOG("%s (%s) test completed in %d ms (%f ms/iter)", desc, flags & ipt_fast ? "fast" : "lowmem", elapsed, looptime-overhead);
+                return looptime;
+            }
+            void Do(unsigned i)
+            {
+                for (unsigned i = 0; i < iterations; i++)
+                {
+                    Owned<IPropertyTree> p = mode >= 3 ? nullptr : createPTreeFromXMLString(
+                            "<W_LOCAL buildVersion='community_6.0.0-trunk0Debug[heads/cass-wu-part3-0-g10b954-dirty]'"
+                            "         cloneable='1'"
+                            "         clusterName=''"
+                            "         codeVersion='158'"
+                            "         eclVersion='6.0.0'"
+                            "         hash='2796091347'"
+                            "         state='completed'"
+                            "         xmlns:xsi='http://www.w3.org/1999/XMLSchema-instance'>"
+                            " <Debug>"
+                            "  <debugquery>1</debugquery>"
+                            "  <expandpersistinputdependencies>1</expandpersistinputdependencies>"
+                            "  <savecpptempfiles>1</savecpptempfiles>"
+                            "  <saveecltempfiles>1</saveecltempfiles>"
+                            "  <spanmultiplecpp>0</spanmultiplecpp>"
+                            "  <standaloneexe>1</standaloneexe>"
+                            "  <targetclustertype>hthor</targetclustertype>"
+                            " </Debug>"
+                            " <FilesRead>"
+                            "  <File name='myfile' useCount='2' cluster = 'mycluster'/>"
+                            "  <File name='mysuperfile' useCount='2' cluster = 'mycluster'>"
+                            "   <Subfile name='myfile'/>"
+                            "  </File>"
+                            "</FilesRead>"
+                            " <Graphs>"
+                            "  <Graph name='graph1' type='activities'>"
+                            "   <xgmml>"
+                            "    <graph wfid='2'>"
+                            "     <node id='1'>"
+                            "      <att>"
+                            "       <graph>"
+                            "        <att name='rootGraph' value='1'/>"
+                            "        <edge id='2_0' source='2' target='3'/>"
+                            "        <edge id='3_0' source='3' target='4'/>"
+                            "        <edge id='4_0' source='4' target='5'/>"
+                            "        <node id='2' label='Inline Row&#10;{1}'>"
+                            "         <att name='definition' value='./sets.ecl(2,13)'/>"
+                            "         <att name='_kind' value='148'/>"
+                            "         <att name='ecl' value='ROW(TRANSFORM({ integer8 v },SELF.v := 1;));&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "         <att name='predictedCount' value='1'/>"
+                            "        </node>"
+                            "        <node id='3' label='Filter'>"
+                            "         <att name='definition' value='./sets.ecl(3,15)'/>"
+                            "         <att name='_kind' value='5'/>"
+                            "         <att name='ecl' value='FILTER(v = STORED(&apos;one&apos;));&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "         <att name='predictedCount' value='0..?[disk]'/>"
+                            "        </node>"
+                            "        <node id='4' label='Count'>"
+                            "         <att name='_kind' value='125'/>"
+                            "         <att name='ecl' value='TABLE({ integer8 value := COUNT(group) });&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "         <att name='predictedCount' value='1'/>"
+                            "        </node>"
+                            "        <node id='5' label='Store&#10;Internal(&apos;wf2&apos;)'>"
+                            "         <att name='_kind' value='22'/>"
+                            "         <att name='ecl' value='extractresult(value, named(&apos;wf2&apos;));&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "        </node>"
+                            "       </graph>"
+                            "      </att>"
+                            "     </node>"
+                            "    </graph>"
+                            "   </xgmml>"
+                            "  </Graph>"
+                            "  <Graph name='graph2' type='activities'>"
+                            "   <xgmml>"
+                            "    <graph wfid='3'>"
+                            "     <node id='6'>"
+                            "      <att>"
+                            "       <graph>"
+                            "        <att name='rootGraph' value='1'/>"
+                            "        <edge id='7_0' source='7' target='8'/>"
+                            "        <edge id='8_0' source='8' target='9'/>"
+                            "        <node id='7' label='Inline Row&#10;{1}'>"
+                            "         <att name='definition' value='./sets.ecl(2,13)'/>"
+                            "         <att name='_kind' value='148'/>"
+                            "         <att name='ecl' value='ROW(TRANSFORM({ integer8 v },SELF.v := 1;));&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "         <att name='predictedCount' value='1'/>"
+                            "        </node>"
+                            "        <node id='8' label='Filter'>"
+                            "         <att name='definition' value='./sets.ecl(5,1)'/>"
+                            "         <att name='_kind' value='5'/>"
+                            "         <att name='ecl' value='FILTER(v = INTERNAL(&apos;wf2&apos;));&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "         <att name='predictedCount' value='0..?[disk]'/>"
+                            "        </node>"
+                            "        <node id='9' label='Output&#10;Result #1'>"
+                            "         <att name='definition' value='./sets.ecl(1,1)'/>"
+                            "         <att name='name' value='sets'/>"
+                            "         <att name='definition' value='./sets.ecl(5,1)'/>"
+                            "         <att name='_kind' value='16'/>"
+                            "         <att name='ecl' value='OUTPUT(..., workunit);&#10;'/>"
+                            "         <att name='recordSize' value='8'/>"
+                            "        </node>"
+                            "       </graph>"
+                            "      </att>"
+                            "     </node>"
+                            "    </graph>"
+                            "   </xgmml>"
+                            "  </Graph>"
+                            " </Graphs>"
+                            " <Query fetchEntire='1'>"
+                            "  <Associated>"
+                            "   <File desc='a.out.cpp'"
+                            "         filename='/Users/rchapman/HPCC-Platform/ossd/a.out.cpp'"
+                            "         ip='192.168.2.203'"
+                            "         type='cpp'/>"
+                            "  </Associated>"
+                            " </Query>"
+                            " <Results>"
+                            "  <Result isScalar='0'"
+                            "          name='Result 1'"
+                            "          recordSizeEntry='mf1'"
+                            "          rowLimit='-1'"
+                            "          sequence='0'"
+                            "          status='calculated'>"
+                            "   <rowCount>1</rowCount>"
+                            "   <SchemaRaw xsi:type='SOAP-ENC:base64'>"
+                            "    dgABCAEAGBAAAAB7IGludGVnZXI4IHYgfTsK   </SchemaRaw>"
+                            "   <totalRowCount>1</totalRowCount>"
+                            "   <Value xsi:type='SOAP-ENC:base64'>"
+                            "    AQAAAAAAAAA=   </Value>"
+                            "  </Result>"
+                            " </Results>"
+                            " <State>completed</State>"
+                            " <Statistics>"
+                            "  <Statistic c='eclcc'"
+                            "             count='1'"
+                            "             creator='eclcc'"
+                            "             kind='TimeElapsed'"
+                            "             s='compile'"
+                            "             scope='compile:parseTime'"
+                            "             ts='1431603789722535'"
+                            "             unit='ns'"
+                            "             value='805622'/>"
+                            "  <Statistic c='unknown'"
+                            "             count='1'"
+                            "             creator='unknownRichards-iMac.local'"
+                            "             kind='WhenQueryStarted'"
+                            "             s='global'"
+                            "             scope='workunit'"
+                            "             ts='1431603790007020'"
+                            "             unit='ts'"
+                            "             value='1431603790007001'/>"
+                            "  <Statistic c='unknown'"
+                            "             count='1'"
+                            "             creator='unknownRichards-iMac.local'"
+                            "             desc='Graph graph1'"
+                            "             kind='TimeElapsed'"
+                            "             s='graph'"
+                            "             scope='graph1'"
+                            "             ts='1431603790007912'"
+                            "             unit='ns'"
+                            "             value='0'/>"
+                            " </Statistics>"
+                            " <Temporaries>"
+                            "  <Variable name='wf2' status='calculated'>"
+                            "   <rowCount>1</rowCount>"
+                            "   <totalRowCount>1</totalRowCount>"
+                            "   <Value xsi:type='SOAP-ENC:base64'>"
+                            "    AQAAAAAAAAA=   </Value>"
+                            "  </Variable>"
+                            " </Temporaries>"
+                            " <Tracing>"
+                            "  <EclAgentBuild>community_6.0.0-trunk0Debug[heads/cass-wu-part3-0-g10b954-dirty]</EclAgentBuild>"
+                            " </Tracing>"
+                            " <Variables>"
+                            "  <Variable name='one' sequence='-1' status='calculated'>"
+                            "   <rowCount>1</rowCount>"
+                            "   <SchemaRaw xsi:type='SOAP-ENC:base64'>"
+                            "    b25lAAEIAQAYAAAAAA==   </SchemaRaw>"
+                            "   <totalRowCount>1</totalRowCount>"
+                            "   <Value xsi:type='SOAP-ENC:base64'>"
+                            "    AQAAAAAAAAA=   </Value>"
+                            "  </Variable>"
+                            " </Variables>"
+                            " <Workflow>"
+                            "  <Item mode='normal'"
+                            "        state='done'"
+                            "        type='normal'"
+                            "        wfid='1'/>"
+                            "  <Item mode='normal'"
+                            "        state='done'"
+                            "        type='normal'"
+                            "        wfid='2'>"
+                            "   <Dependency wfid='1'/>"
+                            "  </Item>"
+                            "  <Item mode='normal'"
+                            "        state='done'"
+                            "        type='normal'"
+                            "        wfid='3'>"
+                            "   <Dependency wfid='2'/>"
+                            "   <Schedule/>"
+                            "  </Item>"
+                            " </Workflow>"
+                            "</W_LOCAL>"
+                    , flags);
+                    switch(mode)
+                    {
+                    case 1: case 3: for (int j = 0; j < 100000; j++) donothing(); break;
+                    case 2: case 4: for (int j = 0; j < 1000000; j++) donothing(); break;
+                    }
+                }
+            }
+        } max("maxContention",flags,0,1000),
+          some("someContention",flags,1,200),
+          min("minContention",flags,2,200),
+          csome("control some",flags,3,200),
+          cmin("control min",flags,4,200),
+          seq("single",flags,0,1000);
+        max.For(8,8);
+        some.For(8,8,csome.For(8,8));
+        min.For(8,8,cmin.For(8,8));
+        seq.For(8,1);
+    }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( PtreeThreadingTest );
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( PtreeThreadingTest, "PtreeThreadingTest" );
+
 //MORE: This can't be included in jlib because of the dll dependency
 //MORE: This can't be included in jlib because of the dll dependency
 class StringBufferTest : public CppUnit::TestFixture
 class StringBufferTest : public CppUnit::TestFixture
 {
 {