//
//  File:   hzMemory.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  Desc:   1)  Optional overide of general case new and delete operators (when hzOveride.h is included in application's main .cpp file)
//          2)  Provision of memory use diagnostics
//
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <malloc.h>
#include "hzBasedefs.h"
#include "hzDatabase.h"
#include "hzDate.h"
#include "hzDocument.h"
#include "hzTextproc.h"
#include "hzUnixacc.h"
#include "hzSMAR.h"
using namespace std ;
/*
**  Global Variables
*/
global  hzMeminfo   _hzGlobal_Memstats ;        //  Memory statistics
#ifdef OVERRIDE
global  bool        _hzGlobal_XM = true ;       //  New/Delete Override activated
#else
global  bool        _hzGlobal_XM = false ;      //  New/Delete Override not activated
#endif
//  Standard sequential memory allocation regimes
global  hzSMAR* g_pSMAR_Str ;       //  Used exclusively by hzString
global  hzSMAR* g_pSMAR_Inet ;      //  Used by hzEmaddr, hzDomain and hzUrl
global  hzSMAR* g_pSMAR_Edo ;       //  Used exclusively by hdbObjRepos to store EDOs
/*
**  SECTION 1:  Overide of global new and delete operator
*/
class _hz_heap
{
    struct  _rblk
    {
        //  RAM block.
        //
        //  The global new/delete overload allocates small objects (between 8 and 128 bytes) from one of 16 separate free list, each of which deals only with objects of a specific
        //  size. There is a free list for 8 byte objects, another for 16 byte objects and so on. When any of these free lists become empty and new RAM block is allocated from main
        //  memory to top them up.
        //
        //  The RAM blocks are 64K minus 8 bytes. The (not very compelling) reason for this weird size is that system memory allocation overhead is 8 bytes, so each block consumes
        //  exactly 64K of real memory. As the RAM blocks are always the same size, RAM blocks assigned to the free list of 8 byte objects will contain twice the number of 'object
        //  slots' that RAM blocks assigned to the free list of 16 byte objects will have. The larger the fixed sized objects, the fewer slots each RAM block will hold. For some
        //  sizes, a small proportion of the RAM block is wasted. Upon allocation of a RAM block, as many slots that will fit for the intended size, are set up within the block so
        //  that one slot points to the next with the last one pointing to NULL. Then the whole lot is added to the free list for the object size.
        int32_t     m_size ;            //  If block is dedicated show the size, otherwise this will be 0
        int32_t     m_usage ;           //  If the block is not dedicated, this will be the next object address to allocate
        uint64_t    m_data[1200] ;      //  Data area
    } ;
    //  Mutexs
    hzLockRWD   m008 ;              //  Mutex for 8 byte allocations
    hzLockRWD   m016 ;              //  Mutex for 16 byte allocations
    hzLockRWD   m024 ;              //  Mutex for 24 byte allocations
    hzLockRWD   m032 ;              //  Mutex for 32 byte allocations
    hzLockRWD   m040 ;              //  Mutex for 40 byte allocations
    hzLockRWD   mAll ;              //  Mutex for all the above
    hzLockRWD   mSze ;              //  Mutex for oversized allocations
    //  Freelist pointers (last 3 digits denotes size)
    uint64_t*   fL008 ;             //  Freelist pointer for 8 byte objects
    uint64_t*   fL016 ;             //  Freelist pointer for 16 byte objects
    uint64_t*   fL024 ;             //  Freelist pointer for 24 byte objects
    uint64_t*   fL032 ;             //  Freelist pointer for 32 byte objects
    uint64_t*   fL040 ;             //  Freelist pointer for 40 byte objects
    _rblk*  _mkblk  (int32_t size) ;
public:
    void*   m_allBlocks[8192] ;     //  Pointers to all managed blocks (max of 8192 blocks)
    //  Number of used objects
    uint32_t    nU008 ;             //  In use counter for 8 byte objects
    uint32_t    nU016 ;             //  In use counter for 16 byte objects
    uint32_t    nU024 ;             //  In use counter for 24 byte objects
    uint32_t    nU032 ;             //  In use counter for 32 byte objects
    uint32_t    nU040 ;             //  In use counter for 40 byte objects
    //  Number of free objects
    uint32_t    nF008 ;             //  Free counter for 8 byte objects
    uint32_t    nF016 ;             //  Free counter for 16 byte objects
    uint32_t    nF024 ;             //  Free counter for 24 byte objects
    uint32_t    nF032 ;             //  Free counter for 32 byte objects
    uint32_t    nF040 ;             //  Free counter for 40 byte objects
    uint32_t    nCallsMalloc ;      //  Number of objects over 128 bytes
    uint32_t    nCallsFree ;        //  Number of objects over 128 bytes
    uint32_t    nOver ;             //  Number of objects over 128 bytes
    uint32_t    nOverSize ;         //  Number of bytes in objects over 128 bytes
    uint32_t    numAllBlks ;        //  Number of blocks
    uint32_t    m_allSizes[1001] ;  //  Numbers alocated for each object size.
    uint32_t    m_allSizeA[1001] ;  //  Number of alocations for each object size.
    uint32_t    m_allSizeF[1001] ;  //  Number of deletions for each object size.
    _hz_heap    (void)
    {
        m008.Setname("Mem_fl_008") ;
        m016.Setname("Mem_fl_016") ;
        m024.Setname("Mem_fl_024") ;
        m032.Setname("Mem_fl_032") ;
        m040.Setname("Mem_fl_040") ;
        fL008=0; fL016=0; fL024=0; fL032=0; fL040=0;
        nU008 = nU016 = nU024 = nU032 = nU040 = 0 ;
        nF008 = nF016 = nF024 = nF032 = nF040 = 0 ;
        nCallsMalloc = nCallsFree = nOver = nOverSize = numAllBlks = 0 ;
    }
    ~_hz_heap   (void)
    {
        shutdown() ;
    }
    void*   allocate    (uint32_t size) ;
    void    release     (void* ptr) ;
    void    shutdown    (void) ;
};
/*
**  Variables for memory regime
*/
static  _hz_heap*   s_Heap = 0 ;        //  The global heap
static  hzChain     s_memReport ;       //  Chain for memory report compilation
static  uint32_t    s_nBytesInit ;      //  Total bytes allocated under new override
static  uint32_t    s_nBytesExit ;      //  Total bytes reserved under new override
//  The actual working functions are
static  void*   _regime_alloc_Init  (uint32_t size) ;       //  Only called ONCE with the first thing allocated in the program anywhere
static  void*   _regime_alloc_Syst  (uint32_t size) ;       //  Allocate using malloc
static  void*   _regime_alloc_Ovrd  (uint32_t size) ;       //  Allocate using hzHeap
static  void    _regime_free_Syst   (void* mem) ;           //  This frees objects
static  void    _regime_free_Ovrd   (void* mem) ;           //  This frees objects
static  void*   _regime_alloc_Exit  (uint32_t size) ;       //  Allocates memory during shutdown of the memory manager
//  Note that hz_mem_allocate() and hz_mem_release() just called according to function pointers
void*   (*fnptr_Alloc)  (uint32_t) = _regime_alloc_Init ;   //  Allocator for new/delete override
void    (*fnptr_Free)   (void*) = _regime_free_Syst ;       //  De-allocator for new/delete override
/*
**  Heap functions
*/
_hz_heap::_rblk*    _hz_heap::_mkblk    (int32_t size)
{
    _rblk*      pBlk ;      //  Block pointer
    uint64_t*   ptr ;       //  Treat internal data as 8 byte ints
    int32_t     n ;         //  Data segment iterator
    int32_t     max ;       //  Max number of data segments
    //  Allocate the block
    pBlk= (_rblk*) malloc(sizeof(_rblk)) ;
    nCallsMalloc++;
    nOverSize += malloc_usable_size(pBlk) ;
    m_allBlocks[numAllBlks] = pBlk ;
    numAllBlks++ ;
    memset(pBlk, 0, sizeof(_rblk)) ;
    //  Partition the block
    switch  (size)
    {
    case 8:     max = 1200 ; nF008 += max ; break ;
    case 16:    max =  600 ; nF016 += max ; break ;
    case 24:    max =  400 ; nF024 += max ; break ;
    case 32:    max =  300 ; nF032 += max ; break ;
    case 40:    max =  240 ; nF040 += max ; break ;
    }
    size /= 8 ;
    ptr = pBlk->m_data ;
    ptr += size ;
    for (n = 0, max-- ; max ; max--, n += size)
    {
        pBlk->m_data[n] = (uint64_t) ptr ;
        ptr += size ;
    }
    pBlk->m_size = size * 8 ;
    return pBlk ;
}
void*   _hz_heap::allocate  (uint32_t size)
{
    //  Allocate memory from the memory regime if required size is 64 bytes or less and from the heap otherwise. Under the regime, allocation is
    //  always from a freelist of segments of 8,16,24, ... 128 bytes. If the appropriate freelist is empty, a new block is allocated and divided
    //  into multiple segments. Allocations of greater than 128 bytes are placed in a map for oversized objects.
    //
    //  Arguments:  1)  size    Number of bytes to allocate
    //
    //  Returns:    Pointer to the allocated memory
    _rblk*      pBlk = 0;   //  Fixed block allocated if any
    void*       pObj = 0;   //  Object allocated
    uint64_t*   ptr;        //  Intermeadiate pointer for free list navigation
    int32_t     nDiv;       //  Position devider
    nCallsMalloc++;
    nDiv = size%8;
        
    if (nDiv)
        size += (8-nDiv);
    switch  (size)
    {
    case 8:     m008.LockWrite(); if (!fL008) {pBlk=_mkblk(8);  fL008=pBlk->m_data;} nF008-- ; nU008++; ptr=fL008; fL008=(uint64_t*)*ptr; m008.Unlock(); break; 
    case 16:    m016.LockWrite(); if (!fL016) {pBlk=_mkblk(16); fL016=pBlk->m_data;} nF016-- ; nU016++; ptr=fL016; fL016=(uint64_t*)*ptr; m016.Unlock(); break; 
    case 24:    m024.LockWrite(); if (!fL024) {pBlk=_mkblk(24); fL024=pBlk->m_data;} nF024-- ; nU024++; ptr=fL024; fL024=(uint64_t*)*ptr; m024.Unlock(); break; 
    case 32:    m032.LockWrite(); if (!fL032) {pBlk=_mkblk(32); fL032=pBlk->m_data;} nF032-- ; nU032++; ptr=fL032; fL032=(uint64_t*)*ptr; m032.Unlock(); break; 
    case 40:    m040.LockWrite(); if (!fL040) {pBlk=_mkblk(40); fL040=pBlk->m_data;} nF040-- ; nU040++; ptr=fL040; fL040=(uint64_t*)*ptr; m040.Unlock(); break; 
    default:    //  Non-standard size.
        mSze.LockWrite();
            pObj = malloc(size);
            nDiv = malloc_usable_size(pObj) ;
            if (nDiv < 8192)
            {
                m_allSizes[nDiv/8]++ ;
                m_allSizeA[nDiv/8]++ ;
            }
            nOver++ ;
            nOverSize += nDiv ;
        mSze.Unlock();
        return pObj;
    }
    return (void*) ptr;
}
void    _hz_heap::release   (void* pObj)
{
    //  Places object in free list if it is of one of the precribed sizes, otherwise it frees it from the OS managed heap
    //
    //  Arguments:  1)  pObj    Pointer to previously decclard memory
    //  Returns:    None
    _rblk*      pBlk ;      //  Block in which object is found
    uint64_t*   ptr ;       //  Object as uint64_t*
    char*       pVoid ;     //  Start of block (for pointer comparison)
    char*       pLim ;      //  End of block (for pointer comparison)
    uint32_t    size = 0 ;  //  Size derived from block in which object is found
    uint32_t    nPos ;      //  Position in hzOrder
    uint32_t    nDiv ;      //  Position devider
    if (!pObj)
        return ;
    if (pObj == this)
        return ;
    nCallsFree++;
    ptr = (uint64_t*) pObj ;
    //  Size is not supplied so must be determined from the only thing that is supplied - the pointer to the object to be freed. From the
    //  address we can determine what block it is in and so the size.
    mAll.LockWrite() ;
        size = 1000000 ;
        for (nPos = 2 ; nPos < numAllBlks ; nPos *= 2) ;
        nDiv = nPos / 2 ;
        nPos-- ;
        for (;;)
        {
            //if (nPos >= m_allBlocks.Count())
            if (nPos >= numAllBlks)
                nPos -= nDiv ;
            else
            {
                //pLim = pVoid = (char*) m_allBlocks.GetObj(nPos) ;
                pLim = pVoid = (char*) m_allBlocks[nPos] ;
                pLim += sizeof(_rblk) ;
                if (pObj < pVoid)
                    nPos -= nDiv ;
                else if (pObj > pLim)
                    nPos += nDiv ;
                else
                {
                    pBlk = (_rblk*) pVoid ;
                    size = pBlk->m_size ;
                    break ;
                }
            }
            if (!nDiv)
                break ;
            nDiv /= 2 ;
        }
    mAll.Unlock() ;
    //  The following sets a pointer to the start of the object (first 8 bytes). Then it sets the contents of the start of the object so that it points
    //  to the start of the freelist (for the object size). The new start of the freelist is then set to the pointer (ie it is set to the start of the
    //  freed object)
    switch  (size)
    {
    case 8:     m008.LockWrite(); nU008--; nF008++ ; ptr=(uint64_t*)pObj; *ptr=(uint64_t)fL008; fL008=ptr; m008.Unlock(); break;
    case 16:    m016.LockWrite(); nU016--; nF016++ ; ptr=(uint64_t*)pObj; *ptr=(uint64_t)fL016; fL016=ptr; m016.Unlock(); break;
    case 24:    m024.LockWrite(); nU024--; nF024++ ; ptr=(uint64_t*)pObj; *ptr=(uint64_t)fL024; fL024=ptr; m024.Unlock(); break;
    case 32:    m032.LockWrite(); nU032--; nF032++ ; ptr=(uint64_t*)pObj; *ptr=(uint64_t)fL032; fL032=ptr; m032.Unlock(); break;
    case 40:    m040.LockWrite(); nU040--; nF040++ ; ptr=(uint64_t*)pObj; *ptr=(uint64_t)fL040; fL040=ptr; m040.Unlock(); break;
    default:
        nOver-- ;
        nDiv = malloc_usable_size(pObj) ;
        if (nDiv < 8192)
        {
            m_allSizes[nDiv/8]-- ;
            m_allSizeF[nDiv/8]++ ;
        }
        nOverSize -= nDiv ;
        break ;
    }
}
void    _hz_heap::shutdown  (void)
{
    //  Shutdown the heap
    //
    //  Arguments:  None
    //  Returns:    None
    fnptr_Alloc = _regime_alloc_Exit ;
}
/*
**  Global memory management functions
*/
void*   hz_mem_allocate (uint32_t size)
{
    //  Category:   Memory
    //
    //  This function is ALWAYS called whenever operator new or new[] is called on ANY object. It will invoke whater function fnptr_Alloc points to
    //
    //  Argument:   size    Requested number of bytes for allocation
    //
    //  Returns:    Pointer to the allocated memory
    return fnptr_Alloc(size) ;
}
void    hz_mem_release  (void* ptr)
{
    //  Category:   Memory
    //
    //  This function is ALWAYS called whenever operator delete or delete[] is called on ANY object. It will invoke whater function fnptr_Free points to
    //
    //  Argument:   ptr     Pointer to object to be freed
    //
    //  Returns:    None
    fnptr_Free(ptr) ;
}
static  void*   _regime_alloc_Init  (uint32_t size)
{
    //  Category:   Memory
    //
    //  Under the override regime operator new is defined as _hz_mem_allocate(), which calls whatever function fnptr_Alloc points to (initially this function). Delete is defined as
    //  _hz_mem_release(), which calls whatever function fnptr_Free points to. Currently, fnptr_Free always points to _regime_free_norm(), which tests _hzGlobal_XM to decide how to
    //  delete memory.
    //
    //  The first memory allocated for the program is allocated via this function, purely so _hzGlobal_XM can be tested before ANY further allocations. This function decides to set
    //  fnptr_Alloc to either _regime_alloc_Syst() for malloc/free and _regime_alloc_Ovrd for the override - then calls the selected function to allocate the requested memory. All
    //  subsequent allocations will be from whichever funtion fnptr_Alloc is set to.
    //
    //  Arguments:  1)  size    Number of bytes to allocate
    //  Returns:    Pointer to allocated memory block
    static  bool    bBeenHere = false ;
    void*   ptr ;       //  New object space
    if (_hzGlobal_XM)
    {
        if (bBeenHere)
            { printf("_regime_alloc_Init: Duplicate Call\n") ; exit(1) ; }
        bBeenHere = true ;
        //  printf("Allocating the heap %p %p\n", *fnptr_Alloc, *fnptr_Free) ; fflush(stdout) ;
        s_Heap = (_hz_heap*) malloc(sizeof(_hz_heap)) ;
        memset(s_Heap, 0, sizeof(_hz_heap)) ;
        s_Heap->nCallsMalloc++ ;
        s_Heap->nOverSize += sizeof(_hz_heap) ;
        fnptr_Alloc = _regime_alloc_Ovrd ;
        fnptr_Free = _regime_free_Ovrd ;
    }
    else
    {
        fnptr_Alloc = _regime_alloc_Syst ;
        fnptr_Free = _regime_free_Syst ;
    }
    //  printf("Now Allocating the memory using approved fn\n") ; fflush(stdout) ;
    ptr = fnptr_Alloc(size) ;
    return ptr ;
}
  
static  void*   _regime_alloc_Syst  (uint32_t size)
{
    //  Category:   Memory
    //
    //  Only called during new-overide initialization
    //
    //  Arguments:  1)  size    Bytes to allocate
    //  Returns:    Pointer to the allocated block
    void*   ptr ;       //  New object space
    s_nBytesInit += size ;
    ptr = malloc(size) ;
    return ptr ;
}
static  void*   _regime_alloc_Ovrd  (uint32_t size)
{
    //  Category:   Memory
    //
    //  This will allocate an object of the required size from a managed block if the size is small and by a direct call to malloc otherwise. In the
    //  latter case, the object will be placed in the map of outsized objects.
    //
    //  Arguments:  1)  size    Number of bytes requested
    //  Returns:    Pointer to the allocated block
    if (!s_Heap)
    {
        //  Fatal("No heap initialized\n") ;
        printf("No heap initialized\n") ; fflush(stdout) ;
        exit(1) ;
    }
    //  printf("_regime_alloc_Ovrd %d bytes\n", size) ; fflush(stdout) ;
    return s_Heap->allocate(size) ;
}
static  void    _regime_free_Ovrd   (void* ptr) { s_Heap->release(ptr) ; }
static  void    _regime_free_Syst   (void* ptr) { free(ptr) ; }
static  void*   _regime_alloc_Exit  (uint32_t size)
{
    //  Category:   Memory
    //
    //  Only called to allocate memory during shutdown (generally for diagnostic pursose only)
    //
    //  Arguments:  1)  size    Bytes to allocate
    //  Returns:    Pointer to the allocated block
    void*   ptr ;       //  New object space
    s_nBytesExit += size ;
    ptr = malloc(size) ;
    memset(ptr, 0, size) ;
    return ptr ;
}
/*
**  SECTION 2:  Memory use dianostics
*/
hzMeminfo::hzMeminfo    (void)
{
    m_numMCH = 0 ;              //  Total number of hzMCH instances (with or without data)
    m_numMCH_D = 0 ;            //  Number of hzMCH instances with data
    m_numChain = 0 ;            //  Total number of hzChain instances (with or without data container)
    m_numChainDC = 0 ;          //  Number of hzChain instances with data container
    m_numChainBlks = 0 ;        //  Total number of hzChain blocks
    m_numChainBF = 0 ;          //  Number of hzChain blocks in free list
    memset(m_strSm_u, 0, sizeof(uint32_t) * 32) ;   //  Small string spaces (8 to 256 bytes), in use
    memset(m_strSm_f, 0, sizeof(uint32_t) * 32) ;   //  Small string spaces (8 to 256 bytes), free
    m_numSblks = 0 ;            //  Number of string superblocks
    m_numStrOver = 0 ;          //  Number of hzString instances (oversize)
    m_ramStrOver = 0 ;          //  Total memory allocated to oversized hzStrings
    m_numStrings = 0 ;          //  Number of hzString instances
    m_numMemblkA = 0 ;          //  Number of type A memblk instances in RAM (for size A 16 byte objects)
    m_numMemblkB = 0 ;          //  Number of type B memblk instances in RAM (for size B 24 byte objects)
    m_numMemblkC = 0 ;          //  Number of type C memblk instances in RAM (for size C 32 byte objects)
    m_numMemblkD = 0 ;          //  Number of type D memblk instances in RAM (for size D 48 byte objects)
    m_numMemblkE = 0 ;          //  Number of type E memblk instances in RAM (for size E 64 byte objects)
    m_numIsams = 0 ;            //  Number of ISAM collections
    m_numIsamIndx = 0 ;         //  Number of ISAM index blocks
    m_numIsamData = 0 ;         //  Number of ISAM data blocks
    m_numArrays = 0 ;           //  Number of hzArray instances
    m_numArrayDA = 0 ;          //  Number of hzArray instances with data area
    m_numLists = 0 ;            //  Number of hzList instances
    m_numListDC = 0 ;           //  Number of hzList instances with data area
    m_numQues = 0 ;             //  Number of hzQue instances
    m_numStacks = 0 ;           //  Number of hzStack instances
    m_numSmaps = 0 ;            //  Number of hzMapS instances
    m_numMmaps = 0 ;            //  Number of hzMapM instances
    m_numSpmaps = 0 ;           //  Number of hzLookup instances
    m_numSets = 0 ;             //  Number of hzSet instances
    m_numVectors = 0 ;          //  Number of hzVect instances
    m_numBitmaps = 0 ;          //  Number of hzBitmap instances
    m_numBitmapSB = 0 ;         //  Number of hzBitmap 'segment block' instances
    m_numMCHB = 0 ;             //  Number of 'micro chain' blocks.
    m_numDochtm = 0 ;           //  Number of hzDocHtm instances
    m_numDocxml = 0 ;           //  Number of hzDocXml instances
    m_numBincron = 0 ;          //  Number of hdbBinCron instances
    m_numBinstore = 0 ;         //  Number of hdbBinStore instances
}
static const char*  _snt    (uint32_t nValue)   { return FormalNumber(nValue, 13) ; }
static const char*  _snh    (uint32_t nValue)   { return FormalNumber(nValue, 0) ; }
static  void    _report_mem_item3   (hzChain& Z, const char* title, uint32_t vA, uint32_t vB, uint32_t vC, bool bHtml)
{
    //  Reports 3 values (e.g allocated and in use, free reserves and total)
    if (bHtml)
        Z.Printf("\t<tr align=\"right\"><td align=\"left\">%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", title, _snh(vA), _snh(vB), _snh(vC)) ;
    else
        Z.Printf("%s %s %s %s\n", title, _snt(vA), _snt(vB), _snt(vC)) ;
}
static  void    _report_mem_itemA   (hzChain& Z, const char* title, uint32_t curr, uint32_t prev, uint32_t sum, bool bHtml)
{
    //  Reports 3 values (e.g allocated and in use, free reserves and total)
    if (bHtml)
    {
        Z << "<tr align=\"right\"><td align=\"left\">" ;
        if (!title)
            Z << "untitled" ;
        else
            Z << title ;
        Z << "</td><td></td><td></td><td></td><td></td>" ;
        Z.Printf("<td>%s</td><td>%s</td><td>%s</td></tr>\n", _snh(curr), _snh(prev), _snh(sum)) ;
    }
    else
    {
        Z.Printf("%s %s %s %s\n", title, _snt(curr), _snt(prev), _snt(sum)) ;
    }
}
static  void    _report_mem_itemC   (hzChain& Z, const char* title, uint32_t A, uint32_t B, uint32_t C, uint32_t D, uint32_t curr, uint32_t prev, uint32_t sum, bool bHtml)
{
    //  Reports 7 values
    if (bHtml)
    {
        Z << "<tr align=\"right\"><td align=\"left\">" ;
        if (!title)
            Z << "untitled" ;
        else
            Z << title ;
        Z.Printf("</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
            _snh(A), _snh(B), _snh(C), _snh(D), _snh(curr), _snh(prev), _snh(sum)) ;
    }
    else
    {
        Z.Printf("%s %s %s %s %s %s %s %s\n",
            title, _snt(A), _snt(B), _snt(C), _snt(D), _snt(curr), _snt(prev), _snt(sum)) ;
    }
}
static  void    _report_objects (hzChain& Z, const hzMeminfo& cms, const hzMeminfo& pms, uint32_t& total, bool bHtml)
{
    //  Category:   Diagnostics
    //
    //  Support function to ReportMemoryUsage(). Provides Report on strings and chain memory usage by the application.
    //
    //  Arguments:  1)  Z       The hzChain instance to receive the report
    //              2)  cms     Current memory stats
    //              3)  pms     Previous memory stats
    //              4)  total   Running total RAM
    //
    //  Returns:    None
    static int32_t  prev_StrTbl = 0 ;   //  Previous count of strings held in string table
    uint32_t    U ;     //  Memory used by given object class
    if (bHtml)
    {
        Z <<
        "<table width=\"750\" align=\"center\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\" "
            "style=\"text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000;\">\n"
        "<tr>\n"
        "\t<th width=\"90\">Entity</th>\n"
        "\t<th width=\"90\">Assigned</th>\n"
        "\t<th width=\"90\">Prev</th>\n"
        "\t<th width=\"90\">Free</th>\n"
        "\t<th width=\"90\">Prev</th>\n"
        "\t<th width=\"90\">Total</th>\n"
        "\t<th width=\"90\">Prev</th>\n"
        "\t<th width=\"110\">Total RAM</th>\n"
        "</tr>\n" ;
    }
    else
    {
        Z << "Entity__________ Assigned_____ Prev_________ Free_________ Prev_________ Total________ Prev_________ Total RAM____\n" ;
    }
    //  CHAINS
    U = cms.m_numChain * sizeof(hzChain) ;
    U += (cms.m_numChainDC * 32) ;
    total += U ;
    _report_mem_itemC(Z, "Chains..........",
        cms.m_numChainDC, pms.m_numChainDC, cms.m_numChain - cms.m_numChainDC, pms.m_numChain - pms.m_numChainDC, cms.m_numChain, pms.m_numChain, U, bHtml) ;
    //  CHAIN BLOCKS. Note that count of allocated blocks is the total number of blocks created minus the count of those in the freelist
    U = cms.m_numChainBlks * 1480 ;
    total += U ;
    _report_mem_itemC(Z, "Chain Blocks....",
        cms.m_numChainBlks - cms.m_numChainBF, pms.m_numChainBlks - pms.m_numChainBF, cms.m_numChainBF, pms.m_numChainBF, cms.m_numChainBlks, pms.m_numChainBlks, U, bHtml) ;
    if (bHtml)
    {
        Z <<
        "</table>\n"
        "<table width=\"350\" align=\"center\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\" "
            "style=\"text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000;\">\n"
        "<tr>\n"
        "\t<th width=\"90\">Entity</th>\n"
        "\t<th width=\"90\">Assigned</th>\n"
        "\t<th width=\"90\">Prev</th>\n"
        "\t<th width=\"90\">TOTAL RAM</th>\n"
        "\t<th width=\"90\"></th>\n"
        "</tr>\n" ;
    }
    else
    {
        Z << "__________________________________________________________________________________________________________________\n\n" ;
        Z << "Entity__________ Assigned_____ Prev_________ Total RAM____\n" ;
    }
    //  Fixed Strings
    /*
    if (_hzGlobal_setStrings)
    {
        U = _hzGlobal_setStrings->Count() * sizeof(hzStrRepos) ;
        total += U ;
        _report_mem_itemA(Z, "Fix Str Repos  :", _hzGlobal_setStrings->Count(), prev_StrTbl, U, bHtml) ;
        U = _hzGlobal_setStrings->Blocks() * 65536 ;
        total += U ;
        _report_mem_itemA(Z, "Fix Str Blocks :", _hzGlobal_setStrings->Blocks(), prev_StrTbl, U, bHtml) ;
    }
    _report_mem_itemA(Z, "Fixed Strings  :", cms.m_numStrings, pms.m_numStrings, U, bHtml) ;
    */
    //  COLLECTIONS
    U = cms.m_numLists * 8 ;        total += U ;    _report_mem_itemA(Z, "Lists          :",    cms.m_numLists,     pms.m_numLists,     U, bHtml) ;
    U = cms.m_numListDC * 32 ;      total += U ;    _report_mem_itemA(Z, "Lists (active) :",    cms.m_numListDC,    pms.m_numListDC,    U, bHtml) ;
    U = cms.m_numSmaps * 64 ;       total += U ;    _report_mem_itemA(Z, "Maps(S)        :",    cms.m_numSmaps,     pms.m_numSmaps,     U, bHtml) ;
    U = cms.m_numMmaps * 64 ;       total += U ;    _report_mem_itemA(Z, "Maps(M)        :",    cms.m_numMmaps,     pms.m_numMmaps,     U, bHtml) ;
    U = cms.m_numSets * 64 ;        total += U ;    _report_mem_itemA(Z, "Sets           :",    cms.m_numSets,      pms.m_numSets,      U, bHtml) ;
    U = cms.m_numArrays * 64 ;      total += U ;    _report_mem_itemA(Z, "Arrays         :",    cms.m_numArrays,    pms.m_numArrays,    U, bHtml) ;
    U = cms.m_numVectors * 64 ;     total += U ;    _report_mem_itemA(Z, "Vectors        :",    cms.m_numVectors,   pms.m_numVectors,   U, bHtml) ;
    U = cms.m_numIsams * 64 ;       total += U ;    _report_mem_itemA(Z, "ISAMS          :",    cms.m_numIsams,     pms.m_numIsams,     U, bHtml) ;
    U = cms.m_numIsamIndx * 64 ;    total += U ;    _report_mem_itemA(Z, "ISAM Blk Index :",    cms.m_numIsamIndx,  pms.m_numIsamIndx,  U, bHtml) ;
    U = cms.m_numIsamData * 64 ;    total += U ;    _report_mem_itemA(Z, "ISAM Blk Data  :",    cms.m_numIsamData,  pms.m_numIsamData,  U, bHtml) ;
    U = cms.m_numBitmaps * 64 ;     total += U ;    _report_mem_itemA(Z, "Bitmaps        :",    cms.m_numBitmaps,   pms.m_numBitmaps,   U, bHtml) ;
    U = cms.m_numBitmapSB * 64 ;    total += U ;    _report_mem_itemA(Z, "Bitmap Segments:",    cms.m_numBitmapSB,  pms.m_numBitmapSB,  U, bHtml) ;
    U = cms.m_numDochtm * 64 ;      total += U ;    _report_mem_itemA(Z, "Doc HTML       :",    cms.m_numDochtm,    pms.m_numDochtm,    U, bHtml) ;
    U = cms.m_numDocxml * 64 ;      total += U ;    _report_mem_itemA(Z, "Doc XML        :",    cms.m_numDocxml,    pms.m_numDocxml,    U, bHtml) ;
    U = cms.m_numBincron * 64 ;     total += U ;    _report_mem_itemA(Z, "BinCrons       :",    cms.m_numBincron,   pms.m_numBincron,   U, bHtml) ;
    U = cms.m_numBinstore * 64 ;    total += U ;    _report_mem_itemA(Z, "BinStores      :",    cms.m_numBinstore,  pms.m_numBinstore,  U, bHtml) ;
    //  TOTAL
    if (bHtml)
    {
        Z << "<tr><td>TOTAL EST:</td><td></td><td></td><td></td><td></td><td></td><td></td>" ;
        Z.Printf("<td align=\"right\">%s</td></tr>\n", _snh(total)) ;
        Z << "</table>\n\n" ;
    }
    else
    {
        Z << "__________________________________________________________\n" ;
        Z.Printf("TOTAL EST:                                   %s\n", _snt(total)) ;
    }
    ReportStringAllocations(Z, bHtml) ;
}
void    ReportStringAllocations (hzChain& Z, bool bHtml)
{
    //  Category:   Diagnostics
    hzMeminfo   cms ;       //  Current snapshot
    uint32_t    N ;         //  Number of string spaces used (for a given size)
    uint32_t    U ;         //  Memory used by N
    uint32_t    F ;         //  Number of string spaces free (for a given size)
    uint32_t    V ;         //  Memory used by F
    uint32_t    nss = 0 ;   //  Number of string spaces in use
    uint32_t    fss = 0 ;   //  Number of string spaces free
    uint32_t    mcu ;       //  Total memory consumed by used string spaces
    uint32_t    mcf ;       //  Total memory consumed by free string spaces
    //uint32_t  fre ;       //  Total memory held in free lists
    cms = _hzGlobal_Memstats ;
    //  Report number of string superblocks
    _report_mem_item3(Z, "512K Superblocks:", cms.m_numSblks, 0, cms.m_numSblks * 589832, bHtml) ;
    N = cms.m_strSm_u[0] ;  nss+=N ; U=N*8 ;    mcu+=U ;    F = cms.m_strSm_f[0] ;  fss+=F ; V=F*9 ;   mcf+=V ; _report_mem_item3(Z, "Strings (008)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[1] ;  nss+=N ; U=N*16 ;   mcu+=U ;    F = cms.m_strSm_f[1] ;  fss+=F ; V=F*16 ;  mcf+=V ; _report_mem_item3(Z, "Strings (016)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[2] ;  nss+=N ; U=N*24 ;   mcu+=U ;    F = cms.m_strSm_f[2] ;  fss+=F ; V=F*24 ;  mcf+=V ; _report_mem_item3(Z, "Strings (024)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[3] ;  nss+=N ; U=N*32 ;   mcu+=U ;    F = cms.m_strSm_f[3] ;  fss+=F ; V=F*32 ;  mcf+=V ; _report_mem_item3(Z, "Strings (032)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[4] ;  nss+=N ; U=N*40 ;   mcu+=U ;    F = cms.m_strSm_f[4] ;  fss+=F ; V=F*40 ;  mcf+=V ; _report_mem_item3(Z, "Strings (040)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[5] ;  nss+=N ; U=N*48 ;   mcu+=U ;    F = cms.m_strSm_f[5] ;  fss+=F ; V=F*48 ;  mcf+=V ; _report_mem_item3(Z, "Strings (048)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[6] ;  nss+=N ; U=N*56 ;   mcu+=U ;    F = cms.m_strSm_f[6] ;  fss+=F ; V=F*56 ;  mcf+=V ; _report_mem_item3(Z, "Strings (056)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[7] ;  nss+=N ; U=N*64 ;   mcu+=U ;    F = cms.m_strSm_f[7] ;  fss+=F ; V=F*64 ;  mcf+=V ; _report_mem_item3(Z, "Strings (064)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[8] ;  nss+=N ; U=N*72 ;   mcu+=U ;    F = cms.m_strSm_f[8] ;  fss+=F ; V=F*72 ;  mcf+=V ; _report_mem_item3(Z, "Strings (072)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[9] ;  nss+=N ; U=N*80 ;   mcu+=U ;    F = cms.m_strSm_f[9] ;  fss+=F ; V=F*80 ;  mcf+=V ; _report_mem_item3(Z, "Strings (080)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[10] ; nss+=N ; U=N*88 ;   mcu+=U ;    F = cms.m_strSm_f[10] ; fss+=F ; V=F*88 ;  mcf+=V ; _report_mem_item3(Z, "Strings (088)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[11] ; nss+=N ; U=N*96 ;   mcu+=U ;    F = cms.m_strSm_f[11] ; fss+=F ; V=F*96 ;  mcf+=V ; _report_mem_item3(Z, "Strings (096)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[12] ; nss+=N ; U=N*104 ;  mcu+=U ;    F = cms.m_strSm_f[12] ; fss+=F ; V=F*104 ; mcf+=V ; _report_mem_item3(Z, "Strings (104)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[13] ; nss+=N ; U=N*112 ;  mcu+=U ;    F = cms.m_strSm_f[13] ; fss+=F ; V=F*112 ; mcf+=V ; _report_mem_item3(Z, "Strings (112)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[14] ; nss+=N ; U=N*120 ;  mcu+=U ;    F = cms.m_strSm_f[14] ; fss+=F ; V=F*120 ; mcf+=V ; _report_mem_item3(Z, "Strings (120)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[15] ; nss+=N ; U=N*128 ;  mcu+=U ;    F = cms.m_strSm_f[15] ; fss+=F ; V=F*128 ; mcf+=V ; _report_mem_item3(Z, "Strings (128)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[16] ; nss+=N ; U=N*136 ;  mcu+=U ;    F = cms.m_strSm_f[16] ; fss+=F ; V=F*136 ; mcf+=V ; _report_mem_item3(Z, "Strings (136)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[17] ; nss+=N ; U=N*144 ;  mcu+=U ;    F = cms.m_strSm_f[17] ; fss+=F ; V=F*144 ; mcf+=V ; _report_mem_item3(Z, "Strings (144)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[18] ; nss+=N ; U=N*152 ;  mcu+=U ;    F = cms.m_strSm_f[18] ; fss+=F ; V=F*152 ; mcf+=V ; _report_mem_item3(Z, "Strings (152)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[19] ; nss+=N ; U=N*160 ;  mcu+=U ;    F = cms.m_strSm_f[19] ; fss+=F ; V=F*160 ; mcf+=V ; _report_mem_item3(Z, "Strings (160)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[20] ; nss+=N ; U=N*168 ;  mcu+=U ;    F = cms.m_strSm_f[20] ; fss+=F ; V=F*168 ; mcf+=V ; _report_mem_item3(Z, "Strings (168)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[21] ; nss+=N ; U=N*176 ;  mcu+=U ;    F = cms.m_strSm_f[21] ; fss+=F ; V=F*176 ; mcf+=V ; _report_mem_item3(Z, "Strings (176)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[22] ; nss+=N ; U=N*184 ;  mcu+=U ;    F = cms.m_strSm_f[22] ; fss+=F ; V=F*184 ; mcf+=V ; _report_mem_item3(Z, "Strings (184)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[23] ; nss+=N ; U=N*192 ;  mcu+=U ;    F = cms.m_strSm_f[23] ; fss+=F ; V=F*192 ; mcf+=V ; _report_mem_item3(Z, "Strings (192)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[24] ; nss+=N ; U=N*200 ;  mcu+=U ;    F = cms.m_strSm_f[24] ; fss+=F ; V=F*200 ; mcf+=V ; _report_mem_item3(Z, "Strings (200)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[25] ; nss+=N ; U=N*208 ;  mcu+=U ;    F = cms.m_strSm_f[25] ; fss+=F ; V=F*208 ; mcf+=V ; _report_mem_item3(Z, "Strings (208)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[26] ; nss+=N ; U=N*216 ;  mcu+=U ;    F = cms.m_strSm_f[26] ; fss+=F ; V=F*216 ; mcf+=V ; _report_mem_item3(Z, "Strings (216)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[27] ; nss+=N ; U=N*224 ;  mcu+=U ;    F = cms.m_strSm_f[27] ; fss+=F ; V=F*224 ; mcf+=V ; _report_mem_item3(Z, "Strings (224)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[28] ; nss+=N ; U=N*232 ;  mcu+=U ;    F = cms.m_strSm_f[28] ; fss+=F ; V=F*232 ; mcf+=V ; _report_mem_item3(Z, "Strings (232)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[29] ; nss+=N ; U=N*240 ;  mcu+=U ;    F = cms.m_strSm_f[29] ; fss+=F ; V=F*240 ; mcf+=V ; _report_mem_item3(Z, "Strings (240)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[30] ; nss+=N ; U=N*248 ;  mcu+=U ;    F = cms.m_strSm_f[30] ; fss+=F ; V=F*248 ; mcf+=V ; _report_mem_item3(Z, "Strings (248)", N, F, U, bHtml) ;
    N = cms.m_strSm_u[31] ; nss+=N ; U=N*256 ;  mcu+=U ;    F = cms.m_strSm_f[31] ; fss+=F ; V=F*256 ; mcf+=V ; _report_mem_item3(Z, "Strings (256)", N, F, U, bHtml) ;
    //  Oversized strings
    U = cms.m_ramStrOver ; mcu += U ; _report_mem_item3(Z, "Strings (ovr)", cms.m_numStrOver, 0, U, bHtml) ;
    //  Report number of string (use)
    nss += cms.m_numStrings ;
    U = cms.m_numStrings * sizeof(hzString) ;
    mcu += U ;
    _report_mem_item3(Z, "Population (incl copies)", cms.m_numStrings, 0, U, bHtml) ;
    _report_mem_item3(Z, "Total string spaces", nss, fss, mcu, bHtml) ;
}
static  void    _report_heap    (hzChain& Z, const hzMeminfo& cms, const hzMeminfo& pms, uint32_t& total, bool bHtml)
{
    //  Category:   Diagnostics
    //
    //  Support function to ReportMemoryUsage(). Provides Report on global new/delete override by the application, if applicable
    //
    //  Argument:   Z   The hzChain instance to receive the report
    //  Returns:    None
    uint32_t    usage ;     //  Memory used by given object class
    uint32_t    nA ;        //  Iterator for all sizes
    Z <<
    "<div class=\"stdpg\">\n"
    "<table width=\"506\" align=\"center\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\" "
        "style=\"text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000;\">\n"
    "<tr>\n"
    "\t<th width=\"120\">HadronZoo Entity</th>\n"
    "\t<th width=\"90\">Assigned</th>\n"
    "\t<th width=\"90\">Free</th>\n"
    "\t<th width=\"90\">Total</th>\n"
    "\t<th width=\"110\">Total RAM</th>\n"
    "</tr>\n" ;
    //  Managed
    Z << "<tr align=\"right\"><td align=\"left\">Heap   8-byte objects</td>" ;
    usage = (s_Heap->nU008 + s_Heap->nF008) * 8 ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nU008), _snh(s_Heap->nF008), _snh(s_Heap->nU008 + s_Heap->nF008),  _snh(usage)) ; 
    Z << "<tr align=\"right\"><td align=\"left\">Heap  16-byte objects</td>" ;
    usage = (s_Heap->nU016 + s_Heap->nF016) * 16 ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nU016), _snh(s_Heap->nF016), _snh(s_Heap->nU016 + s_Heap->nF016),  _snh(usage)) ; 
    Z << "<tr align=\"right\"><td align=\"left\">Heap  24-byte objects</td>" ;
    usage = (s_Heap->nU024 + s_Heap->nF024) * 24 ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nU024), _snh(s_Heap->nF024), _snh(s_Heap->nU024 + s_Heap->nF024),  _snh(usage)) ; 
    Z << "<tr align=\"right\"><td align=\"left\">Heap  32-byte objects</td>" ;
    usage = (s_Heap->nU032 + s_Heap->nF032) * 32 ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nU032), _snh(s_Heap->nF032), _snh(s_Heap->nU032 + s_Heap->nF032),  _snh(usage)) ; 
    Z << "<tr align=\"right\"><td align=\"left\">Heap  40-byte objects</td>" ;
    usage = (s_Heap->nU040 + s_Heap->nF040) * 40 ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nU040), _snh(s_Heap->nF040), _snh(s_Heap->nU040 + s_Heap->nF040),  _snh(usage)) ; 
    //  Print report for oversized objects for each multiple of 8 bytes for which there are outstanding allocatons
    Z << "<tr><td>Oversize (48+ bytes)</td><td></td><td></td><td></td></tr>\n" ;
    for (nA = 0 ; nA < 1001 ; nA++)
    {
        if (!s_Heap->m_allSizes[nA] && !s_Heap->m_allSizeF[nA])
            continue ;
        Z.Printf("<tr align=\"right\"><td align=\"left\">Obj size %d</td>", nA * 8) ;
        usage = s_Heap->m_allSizes[nA] * nA * 8 ;
        Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
            _snh(s_Heap->m_allSizeA[nA]), _snh(s_Heap->m_allSizeF[nA]), _snh(s_Heap->m_allSizes[nA]),   _snh(usage)) ; 
    }
    Z << "<tr align=\"right\"><td align=\"left\">All objects</td>" ;
    Z.Printf("<td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",
        _snh(s_Heap->nCallsMalloc),
        _snh(s_Heap->nCallsFree),
        _snh(s_Heap->nCallsMalloc - s_Heap->nCallsFree),
        _snh(s_Heap->nOverSize)) ;
    Z <<
    "</table>\n"
    "</div>\n" ;
}
void    ReportMemoryUsage   (hzChain& Z, bool bHtml)
{
    //  Category:   Diagnostics
    //
    //  Provides Report on memory usage by the application.
    //
    //  Please note, the HTML generated by this function is partial and comprises only the table of interest.
    //
    //  Argument:   Z   The hzChain instance to receive the report
    //  Returns:    None
    static  hzMeminfo   pms = _hzGlobal_Memstats ;      //  Previous/initial memory statistics
    hzMeminfo   cms ;               //  Current memory stats
    hzXDate     now ;               //  Current date and time
    uint32_t    total = 0 ;         //  Total memory usage
    now.SysDateTime() ;
    //  Take current snapshot
    cms = _hzGlobal_Memstats ;
    if (bHtml)
        Z.Printf("<p><center>Memory Report at %s</center></p>\n", *now) ;
    else
        Z.Printf("Memory Report at %s\n", *now) ;
    g_pSMAR_Str->Report(Z) ;    //  Used exclusively by hzString
    g_pSMAR_Inet->Report(Z) ;   //  Used by hzEmaddr, hzDomain and hzUrl
    g_pSMAR_Edo->Report(Z) ;    //  Used exclusively by hdbObjRepos to store EDOs
    //  If no heap, this is presented as collections on the LHS and string/chains on the RHS in one 'row'. If there is a heap we have string/chains on the top
    //  LHS, collections on the bottom LHS and the heap activity on the RHS
    if (s_Heap)
    {
        //  Two reports on LHS
        if (bHtml)
        {
            Z <<
            "<table width=\"1400\" slign=\"center\">\n"
            "<tr>\n"
            "   <td valign=\"top\">\n"
            "   <table width=\"750\" slign=\"center\">\n"
            "   <tr>\n"
            "       <td>\n" ;
            _report_objects(Z, cms, pms, total, bHtml) ;
            Z <<
            "       </td>\n"
            "   </tr>\n"
            "   </table>\n"
            "   </td>\n"
            "   <td width=\"15\"> </td>\n"
            "   <td>\n"
            "   <table width=\"550\" slign=\"center\">\n"
            "   <tr>\n"
            "       <td>\n" ;
            //  Heap on RHS
            _report_heap(Z, cms, pms, total, bHtml) ;
            Z <<
            "       </td>\n"
            "   </tr>\n"
            "   </table>\n"
            "   </td>\n"
            "</tr>\n"
            "</table>\n" ;
        }
        else
        {
            _report_objects(Z, cms, pms, total, bHtml) ;
            _report_heap(Z, cms, pms, total, bHtml) ;
        }
    }
    else
    {
        //  collection on LHS, string chans on RHS
        if (!bHtml)
            _report_objects(Z, cms, pms, total, bHtml) ;
        else
        {
            Z <<
            "<table width=\"1400\" slign=\"center\">\n"
            "<tr>\n"
            "   <td valign=\"top\">\n" ;
            _report_objects(Z, cms, pms, total, bHtml) ;
            Z <<
            "   </td>\n"
            "</tr>\n"
            "</table>\n" ;
        }
    }
    pms = cms ;
}
void    RecordMemoryUsage   (bool bHtml)
{
    //  Category:   Diagnostics
    //
    //  Provides Report on memory usage by the application and writes it to the logfile for the current thread
    //
    //  Arguments:  None
    //  Returns:    None
    hzChain     Z ;     //  Report output chain
    hzLogger*   pLog ;  //  Current thread logger
    pLog = GetThreadLogger() ;
    if (!pLog)
        return ;
    pLog->Out("Start of Memory Report\n") ;
    ReportMemoryUsage(Z, bHtml) ;
    pLog->Out(Z) ;
}
void    ReportIsamUsage (hzChain& Z)
{
    //  Category:   Diagnostics
    //
    //  Provides Report on ISAM usage by the application.
    //
    //  Argument:   Z   The hzChain instance to receive the report
    //  Returns:    None
    hzXDate now ;       //  Current date and time
    now.SysDateTime() ;
    Z.Clear() ;
    Z.Printf("<p><center>ISAM Report at %s</center></p>\n", *now) ;
    Z <<
    "<html>\n"
    "<head>\n"
    "<style>\n"
    ".main  { text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000; }\n"
    ".stdpg { height:600px; border:0px; margin-left:5px; overflow-x:auto; overflow-y:auto; }\n"
    "</style>\n"
    "</head>\n"
    "<body>\n"
    "<div class=\"stdpg\">\n"
    "<table width=\"70%\" align=\"center\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\" "
        "style=\"text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000;\">\n"
    "<tr>\n"
    "\t<th>Type</th>\n"
    "\t<th>Name</th>\n"
    "\t<th>Blocks</th>\n"
    "\t<th>Object Size</th>\n"
    "\t<th>Objects</th>\n"
    "\t<th>Total RAM</th>\n"
    "</tr>\n" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_BinDataCrons</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_BinDataStores</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_Datatypes</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_Enums</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_Classes</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_setitories</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">s_SSIncludes</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">s_PageStore</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_blockedIPs</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">s_mimesFile</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">s_mimesDesc</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">s_mimesEnum</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_Userlist</td>" ;
    Z << "<tr align=\"right\"><td align=\"left\">hzMapS</td><td align=\"left\">_hzGlobal_Grouplist</td>" ;
    Z <<
    "</table>\n"
    "</div>\n"
    "</body>\n"
    "</html>\n" ;
}