Bladeren bron

HPCC-19275 Add 3 WsMachine methods to get disk usages

1. GetComponentUsage
Input: a list of HPCC components with name and type; if no
component is specified, the disk usages for all related
HPCC components (thor, roxie, dali, eclagent, sasha, and
dropzone) are returned.
Output: Disk usages for each log/data/mirror drive on each
machine used by each component.

2. GetTargetClusterUsage
Input: a list of HPCC TargetCluster name; if no target
cluster is specified, the disk usages for all target
clusters are returned.
Output: Disk usages for each log/data/mirror drive on each
machine used by each TargetCluster.

3. GetNodeGroupUsage
Input: a list of HPCC NodeGroup name; if no node group is
specified, the disk usages for all related node groups
(thor and hthor) are returned.
Output: Disk usages for each log/data/mirror drive on each
machine used by each NodeGroup.

Signed-off-by: wangkx <kevin.wang@lexisnexis.com>
wangkx 6 jaren geleden
bovenliggende
commit
10f1d66756

+ 1 - 0
common/monitoring/CMakeLists.txt

@@ -16,3 +16,4 @@
 add_subdirectory (prosysinfo)
 
 Install ( PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/preflight DESTINATION sbin COMPONENT Runtime )
+Install ( PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/usage DESTINATION sbin COMPONENT Runtime )

+ 70 - 0
common/monitoring/usage

@@ -0,0 +1,70 @@
+#!/bin/bash
+################################################################################
+#
+#    HPCC SYSTEMS software Copyright (C) 2019 HPCC Systems®.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#################################################################################
+
+declare -a arr1
+declare -a arr2
+declare -a arrDiscPath
+
+if [ "${1:0:1}" == '-' ]; then
+    if [ $# -lt 1 ]; then
+        echo usage: $0 -d=DiskPath[,DiskPath,...]
+        exit 1
+    fi
+
+    for (( i=1;$i<=$#;i=$i+1 )) do
+        case ${!i:0:3} in '-d=')
+                 IFS=, read -a arrDiscPath <<< "${!i:3}";;
+            *)
+                 echo "wrong augument:${!i}";;
+        esac
+    done
+
+    echo "--Start--"
+    if [ "${#arrDiscPath[@]}" ]; then
+        index=0
+        for i in "${arrDiscPath[@]}"; do
+            ok=0
+            dirp=$i
+            until [ $ok = 1 ] ; do
+                if [ -d $dirp ] ; then
+                    ok=1
+                else
+                    dirp=$(dirname $dirp)
+                fi
+            done
+
+            if [ "$dirp" != "/" ] ; then
+                dfout=$(df $dirp | tail -n +2 | awk '{if(NF>=4) print $(NF-3) " " $(NF-2)}')
+                if [ -n "$dfout" ] ; then
+                    arr1[$index]=$(echo $dfout | awk '{print $1}')
+                    arr2[$index]=$(echo $dfout | awk '{print $2}')
+                    if [ "$dirp" != "$i" ] ; then
+                        echo "$i: ${arr1[$index]} ${arr2[$index]} $dirp"
+                    else
+                        echo "$i: ${arr1[$index]} ${arr2[$index]}"
+                    fi
+                fi
+            fi
+
+            index=$((index+1))
+        done
+    fi
+
+    echo "--Finish--"
+    exit 0
+fi

+ 7 - 0
common/workunit/workunit.cpp

@@ -7002,6 +7002,7 @@ class CEnvironmentClusterInfo: implements IConstWUClusterInfo, public CInterface
     StringAttr alias;
     StringAttr serverQueue;
     StringAttr agentQueue;
+    StringAttr agentName;
     StringAttr roxieProcess;
     SocketEndpointArray roxieServers;
     StringAttr thorQueue;
@@ -7096,6 +7097,7 @@ public:
         {
             assertex(!roxie);
             agentQueue.set(getClusterEclAgentQueueName(queue.clear(), name));
+            agentName.set(agent->queryProp("@name"));
         }
         else if (roxie)
             agentQueue.set(getClusterRoxieQueueName(queue.clear(), name));
@@ -7122,6 +7124,11 @@ public:
         str.set(agentQueue);
         return str;
     }
+    IStringVal & getAgentName(IStringVal & str) const
+    {
+        str.set(agentName);
+        return str;
+    }
     virtual IStringVal & getServerQueue(IStringVal & str) const
     {
         str.set(serverQueue);

+ 1 - 0
common/workunit/workunit.hpp

@@ -553,6 +553,7 @@ interface IConstWUClusterInfo : extends IInterface
     virtual unsigned getNumberOfSlaveLogs() const = 0;
     virtual ClusterType getPlatform() const = 0;
     virtual IStringVal & getAgentQueue(IStringVal & str) const = 0;
+    virtual IStringVal & getAgentName(IStringVal & str) const = 0;
     virtual IStringVal & getServerQueue(IStringVal & str) const = 0;
     virtual IStringVal & getRoxieProcess(IStringVal & str) const = 0;
     virtual const StringArray & getThorProcesses() const = 0;

+ 81 - 1
esp/scm/ws_machine.ecm

@@ -23,6 +23,52 @@ SCMenum ThresholdType
     THRESHOLD_MB
 };
 
+ESPStruct [nil_remove] DiskUsage
+{
+    string Name;
+    string Path;
+    string Description;
+    int64 InUse;
+    int64 Available;
+    int PercentAvailable;
+};
+
+ESPStruct [nil_remove] MachineUsage
+{
+    string Name;
+    string NetAddress;
+    string Description;
+    ESParray<ESPstruct DiskUsage> DiskUsages;
+};
+
+ESPStruct [nil_remove] Component
+{
+    string Type;
+    string Name;
+};
+
+ESPStruct [nil_remove] ComponentUsage
+{
+    string Type;
+    string Name;
+    string Description;
+    ESParray<ESPstruct MachineUsage> MachineUsages;
+};
+
+ESPStruct [nil_remove] TargetClusterUsage
+{
+    string Name;
+    string Description;
+    ESParray<ESPstruct ComponentUsage> ComponentUsages;
+};
+
+ESPStruct [nil_remove] NodeGroupUsage
+{
+    string Name;
+    string Description;
+    ESParray<ESPstruct ComponentUsage> ComponentUsages;
+};
+
 ESPrequest GetMachineInfoRequest
 {
     ESParray<string> Addresses;
@@ -367,8 +413,38 @@ ESPresponse [encode(0), nil_remove, exceptions_inline] UpdateComponentStatusResp
     string Status;
 };
 
+ESPrequest [nil_remove] GetComponentUsageRequest
+{
+    ESParray<ESPstruct Component> Components;
+};
+
+ESPresponse [encode(0), nil_remove, exceptions_inline] GetComponentUsageResponse 
+{
+    ESParray<ESPstruct ComponentUsage> ComponentUsages;
+};
+
+ESPrequest [nil_remove] GetTargetClusterUsageRequest
+{
+    ESParray<string> TargetClusters;
+};
+
+ESPresponse [encode(0), nil_remove, exceptions_inline] GetTargetClusterUsageResponse 
+{
+    ESParray<ESPstruct TargetClusterUsage> TargetClusterUsages;
+};
+
+ESPrequest [nil_remove] GetNodeGroupUsageRequest
+{
+    ESParray<string> NodeGroups;
+};
+
+ESPresponse [encode(0), nil_remove, exceptions_inline] GetNodeGroupUsageResponse 
+{
+    ESParray<ESPstruct NodeGroupUsage> NodeGroupUsages;
+};
+
 //-------- service ---------
-ESPservice [auth_feature("DEFERRED"), version("1.13")] ws_machine
+ESPservice [auth_feature("DEFERRED"), version("1.14")] ws_machine
 {
     ESPmethod [resp_xsl_default("./smc_xslt/clusterprocesses.xslt"), exceptions_inline("./smc_xslt/exceptions.xslt")]
        GetTargetClusterInfo(GetTargetClusterInfoRequest, GetTargetClusterInfoResponse);
@@ -382,6 +458,10 @@ ESPservice [auth_feature("DEFERRED"), version("1.13")] ws_machine
     ESPmethod GetComponentStatus(GetComponentStatusRequest, GetComponentStatusResponse);
     ESPmethod UpdateComponentStatus(UpdateComponentStatusRequest, UpdateComponentStatusResponse);
 
+    ESPmethod [min_ver("1.14")] GetComponentUsage(GetComponentUsageRequest, GetComponentUsageResponse);
+    ESPmethod [min_ver("1.14")] GetTargetClusterUsage(GetTargetClusterUsageRequest, GetTargetClusterUsageResponse);
+    ESPmethod [min_ver("1.14")] GetNodeGroupUsage(GetNodeGroupUsageRequest, GetNodeGroupUsageResponse);
+
     ESPmethod [resp_xsl_default("./smc_xslt/ws_machine/metrics.xslt"), exceptions_inline("./smc_xslt/exceptions.xslt")] 
        GetMetrics(MetricsRequest, MetricsResponse);
 };

+ 833 - 48
esp/services/ws_machine/ws_machineService.cpp

@@ -24,54 +24,7 @@
 #include "componentstatus.hpp"
 #include "rmtssh.hpp"
 #include "platform.h"
-
-#ifndef eqHoleCluster
-#define eqHoleCluster  "HoleCluster"
-#endif
-
-#ifndef eqThorCluster
-#define eqThorCluster  "ThorCluster"
-#endif
-
-#ifndef eqRoxieCluster
-#define eqRoxieCluster "RoxieCluster"
-#endif
-
-#ifndef eqEclCCServer
-#define eqEclCCServer       "EclCCServerProcess"
-#endif
-
-#ifndef eqEclServer
-#define eqEclServer       "EclServerProcess"
-#endif
-
-#ifndef eqEclAgent
-#define eqEclAgent          "EclAgentProcess"
-#endif
-
-#ifndef eqAgentExec
-#define eqAgentExec         "AgentExecProcess"
-#endif
-
-#ifndef eqEclScheduler
-#define eqEclScheduler      "EclSchedulerProcess"
-#endif
-
-#ifndef eqThorMasterProcess
-#define eqThorMasterProcess     "ThorMasterProcess"
-#endif
-
-#ifndef eqThorSlaveProcess
-#define eqThorSlaveProcess      "ThorSlaveProcess"
-#endif
-
-#ifndef eqThorSpareProcess
-#define eqThorSpareProcess      "ThorSpareProcess"
-#endif
-
-#ifndef eqRoxieServerProcess
-#define eqRoxieServerProcess    "RoxieServerProcess"
-#endif
+#include "TpWrapper.hpp"
 
 static const int THREAD_POOL_SIZE = 40;
 static const int THREAD_POOL_STACK_SIZE = 64000;
@@ -136,6 +89,20 @@ public:
     }
 };
 
+class CGetMachineUsageThreadParam : public CWsMachineThreadParam
+{
+public:
+    IEspContext&   espContext;
+    IPropertyTree* request;
+
+    CGetMachineUsageThreadParam(Cws_machineEx* pService, IEspContext& _espContext, IPropertyTree* _request)
+       : CWsMachineThreadParam(pService), espContext(_espContext), request(_request) {}
+
+    virtual void doWork()
+    {
+        m_pService->getMachineUsage(espContext, this);
+    }
+};
 
 void Cws_machineEx::init(IPropertyTree *cfg, const char *process, const char *service)
 {
@@ -2248,6 +2215,824 @@ void Cws_machineEx::getAccountAndPlatformInfo(const char* address, StringBuffer&
     bLinux = machine->getOS() == MachineOsLinux;
 }
 
+IPropertyTree* Cws_machineEx::createDiskUsageReq(IPropertyTree* envDirectories, const char* pathName,
+    const char* componentType, const char* componentName)
+{
+    StringBuffer path;
+    if (!getConfigurationDirectory(envDirectories, pathName, componentType, componentName, path))
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get %s Disk Path for component %s", pathName, componentName);
+
+    Owned<IPropertyTree> diskReq = createPTree("Folder");
+    diskReq->addProp("@name", pathName);
+    diskReq->addProp("@path", path);
+    return diskReq.getClear();
+}
+
+IPropertyTree* Cws_machineEx::createMachineUsageReq(IConstEnvironment* constEnv, const char* computer)
+{
+    Owned<IConstMachineInfo> machine = constEnv->getMachine(computer);
+    if (!machine)
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get machine %s", computer);
+
+    Owned<IPropertyTree> machineReq = createPTree("Machine");
+    machineReq->addProp("@name", computer);
+
+    SCMStringBuffer netAddress;
+    machine->getNetAddress(netAddress);
+    machineReq->addProp("@netAddress", netAddress.str());
+    machineReq->addPropInt("@OS", machine->getOS());
+    return machineReq.getClear();
+}
+
+void Cws_machineEx::readThorUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq)
+{
+    if (isEmptyString(name))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty Thor name");
+
+    Owned<IPropertyTree> componentReq = createPTree("Component");
+    componentReq->addProp("@name", name);
+    componentReq->addProp("@type", eqThorCluster);
+
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    IPropertyTree* envDirectories = envRoot->queryPropTree("Software/Directories");
+    Owned<IPropertyTree> logFolder = createDiskUsageReq(envDirectories, "log", "thor", name);
+    Owned<IPropertyTree> dataFolder = createDiskUsageReq(envDirectories, "data", "thor", name);
+    Owned<IPropertyTree> repFolder = createDiskUsageReq(envDirectories, "mirror", "thor", name);
+
+    VStringBuffer xpath("Software/ThorCluster[@name='%s']/ThorSlaveProcess", name);
+    Owned<IPropertyTreeIterator> slaveProcesses= envRoot->getElements(xpath);
+    ForEach(*slaveProcesses)
+    {
+        IPropertyTree& slaveProcess = slaveProcesses->query();
+        const char* computer = slaveProcess.queryProp("@computer");
+        if (isEmptyString(computer))
+            throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get @computer for %s", xpath.str());
+
+        Owned<IPropertyTree> newMachineReq = createMachineUsageReq(constEnv, computer);
+
+        //Not sure we need those folders here. Add them just in case.
+        if (logFolder)
+            newMachineReq->addPropTree(logFolder->queryName(), LINK(logFolder));
+        if (dataFolder)
+            newMachineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+        if (repFolder)
+            newMachineReq->addPropTree(repFolder->queryName(), LINK(repFolder));
+        componentReq->addPropTree(newMachineReq->queryName(), LINK(newMachineReq));
+    }
+
+    //Read ThorMasterProcess in case it is on a different machine
+    xpath.setf("Software/ThorCluster[@name='%s']/ThorMasterProcess/@computer", name);
+    const char* computer = envRoot->queryProp(xpath);
+    if (isEmptyString(computer))
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get %s", xpath.str());
+
+    Owned<IPropertyTree> machineReq = createMachineUsageReq(constEnv, computer);
+    xpath.setf("Machine[@netAddress='%s']", machineReq->queryProp("@netAddress"));
+    if (componentReq->queryPropTree(xpath))
+    {   //ThorMasterProcess is running on one of the ThorSlaveProcess machines.
+        //So, we do not add this machine again.
+        usageReq->addPropTree(componentReq->queryName(), LINK(componentReq));
+        return;
+    }
+
+    //Not sure we need those folders here. Add them just in case.
+    if (logFolder)
+        machineReq->addPropTree(logFolder->queryName(), LINK(logFolder));
+    if (dataFolder)
+        machineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+    if (repFolder)
+        machineReq->addPropTree(repFolder->queryName(), LINK(repFolder));
+    componentReq->addPropTree(machineReq->queryName(), LINK(machineReq));
+
+    usageReq->addPropTree(componentReq->queryName(), LINK(componentReq));
+}
+
+void Cws_machineEx::readRoxieUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq)
+{
+    if (isEmptyString(name))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty Roxie name");
+
+    Owned<IPropertyTree> componentReq = createPTree("Component");
+    componentReq->addProp("@name", name);
+    componentReq->addProp("@type", eqRoxieCluster);
+
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    IPropertyTree* envDirectories = envRoot->queryPropTree("Software/Directories");
+    Owned<IPropertyTree> logFolder = createDiskUsageReq(envDirectories, "log", "roxie", name);
+    Owned<IPropertyTree> dataFolder = createDiskUsageReq(envDirectories, "data", "roxie", name);
+
+    VStringBuffer xpath("Software/RoxieCluster[@name='%s']/RoxieServerProcess", name);
+    Owned<IPropertyTreeIterator> slaveProcesses= envRoot->getElements(xpath);
+    ForEach(*slaveProcesses)
+    {
+        IPropertyTree& slaveProcess = slaveProcesses->query();
+        const char* computer = slaveProcess.queryProp("@computer");
+        if (isEmptyString(computer))
+            throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get @computer for %s", xpath.str());
+
+        Owned<IPropertyTree> newMachineReq = createMachineUsageReq(constEnv, computer);
+        //Not sure we need those folders here. Add them just in case.
+        if (logFolder)
+            newMachineReq->addPropTree(logFolder->queryName(), LINK(logFolder));
+        if (dataFolder)
+            newMachineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+        componentReq->addPropTree(newMachineReq->queryName(), LINK(newMachineReq));
+    }
+
+    usageReq->addPropTree(componentReq->queryName(), LINK(componentReq));
+}
+
+void Cws_machineEx::readDropZoneUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq)
+{
+    if (isEmptyString(name))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty DropZone name");
+
+    Owned<IConstDropZoneInfo> envDropZone = constEnv->getDropZone(name);
+    if (!envDropZone || !envDropZone->isECLWatchVisible())
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Dropzone %s not found", name);
+
+    SCMStringBuffer directory;
+    envDropZone->getDirectory(directory);
+    if (directory.length() == 0)
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get directory for DropZone %s", name);
+
+    Owned<IPropertyTree> dataFolder = createPTree("Folder");
+    dataFolder->addProp("@name", "data");
+    dataFolder->addProp("@path", directory.str());
+
+    Owned<IPropertyTree> componentReq = createPTree("Component");
+    componentReq->addProp("@name", name);
+    componentReq->addProp("@type", eqDropZone);
+
+    SCMStringBuffer computerName;
+    envDropZone->getComputerName(computerName); 
+    if (computerName.length() == 0)
+    {
+        OS_TYPE os = (getPathSepChar(directory.str()) == '/') ? OS_LINUX : OS_WINDOWS;
+
+        Owned<IConstDropZoneServerInfoIterator> servers = envDropZone->getServers();
+        ForEach(*servers)
+        {
+            IConstDropZoneServerInfo &server = servers->query();
+
+            StringBuffer serverNetAddress;
+            server.getServer(serverNetAddress.clear());
+
+            Owned<IPropertyTree> machineReq = createPTree("Machine");
+            machineReq->addProp("@name", serverNetAddress.str());
+            machineReq->addProp("@netAddress", serverNetAddress.str());
+            machineReq->addPropInt("@OS", os);
+            machineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+            componentReq->addPropTree(machineReq->queryName(), LINK(machineReq));
+        }
+    }
+    else
+    { //legacy dropzone settings
+        Owned<IPropertyTree> machineReq = createMachineUsageReq(constEnv, computerName.str());
+        machineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+        componentReq->addPropTree(machineReq->queryName(), LINK(machineReq));
+    }
+    usageReq->addPropTree(componentReq->queryName(), LINK(componentReq));
+}
+
+void Cws_machineEx::readOtherComponentUsageReq(const char* name, const char* type, IConstEnvironment* constEnv, IPropertyTree* usageReq)
+{
+    if (isEmptyString(name))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty Component name");
+
+    if (!strieq(type, eqDali) && !strieq(type, eqEclAgent) && !strieq(type, eqSashaServer))
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Component usage function is not supported for %s", type);
+
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    VStringBuffer xpath("Software/%s[@name='%s']/Instance/@computer", type, name);
+    const char* computer = envRoot->queryProp(xpath);
+    if (isEmptyString(computer))
+        throw MakeStringException(ECLWATCH_CANNOT_GET_ENV_INFO, "Failed to get %s", xpath.str());
+
+    Owned<IPropertyTree> machineReq = createMachineUsageReq(constEnv, computer);
+
+    IPropertyTree* envDirectories = envRoot->queryPropTree("Software/Directories");
+    Owned<IPropertyTree> logFolder = createDiskUsageReq(envDirectories, "log", type, name);
+    if (logFolder)
+        machineReq->addPropTree(logFolder->queryName(), LINK(logFolder));
+
+    StringAttr componentType;
+    if (strieq(type, eqDali))
+        componentType.set("dali");
+    else if (strieq(type, eqEclAgent))
+        componentType.set("eclAgent");
+    else
+        componentType.set("sasha");
+
+    Owned<IPropertyTree> dataFolder = createDiskUsageReq(envDirectories, "data", componentType.get(), name);
+    if (dataFolder)
+        machineReq->addPropTree(dataFolder->queryName(), LINK(dataFolder));
+
+    if (strieq(type, eqDali))
+    {
+        Owned<IPropertyTree> repFolder = createDiskUsageReq(envDirectories, "mirror", "dali", name);
+        if (repFolder)
+            machineReq->addPropTree(repFolder->queryName(), LINK(repFolder));
+    }
+
+    Owned<IPropertyTree> componentReq = createPTree("Component");
+    componentReq->addProp("@name", name);
+    componentReq->addProp("@type", type);
+    componentReq->addPropTree(machineReq->queryName(), LINK(machineReq));
+    usageReq->addPropTree(componentReq->queryName(), LINK(componentReq));
+}
+
+void Cws_machineEx::setUniqueMachineUsageReq(IPropertyTree* usageReq, IPropertyTree* uniqueUsages)
+{
+    Owned<IPropertyTreeIterator> components= usageReq->getElements("Component");
+    ForEach(*components)
+    {
+        IPropertyTree& component = components->query();
+        Owned<IPropertyTreeIterator> machines= component.getElements("Machine");
+        ForEach(*machines)
+        {
+            IPropertyTree& machine = machines->query();
+            const char* netAddress = machine.queryProp("@netAddress");
+    
+            VStringBuffer xpath("Machine[@netAddress='%s']", netAddress);
+            IPropertyTree* uniqueMachineReqTree = uniqueUsages->queryPropTree(xpath);
+            if (!uniqueMachineReqTree)
+            {
+                uniqueUsages->addPropTree(machine.queryName(), LINK(&machine));
+                continue;
+            }
+
+            //Add unique disk folders from the usageReq.
+            Owned<IPropertyTreeIterator> folders = machine.getElements("Folder");
+            ForEach(*folders)
+            {
+                IPropertyTree& folder = folders->query();
+                const char* aDiskPath = folder.queryProp("@path");
+
+                xpath.setf("Folder[@path='%s']", aDiskPath);
+                IPropertyTree* uniqueFolderReqTree = uniqueMachineReqTree->queryPropTree(xpath);
+                if (!uniqueFolderReqTree)
+                {
+                    Owned<IPropertyTree> folderReq = createPTree("Folder");
+                    folderReq->addProp("@name", folder.queryProp("@name"));
+                    folderReq->addProp("@path", aDiskPath);
+                    uniqueMachineReqTree->addPropTree(folderReq->queryName(), LINK(folderReq));
+                }
+            }
+        }
+    }
+}
+
+IArrayOf<IConstComponent>& Cws_machineEx::listComponentsByType(IPropertyTree* envRoot,
+    const char* componentType, IArrayOf<IConstComponent>& componentList)
+{
+    VStringBuffer xpath("Software/%s", componentType);
+    Owned<IPropertyTreeIterator> components= envRoot->getElements(xpath);
+    ForEach(*components)
+    {
+        Owned<IEspComponent> component = createComponent();
+        component->setName(components->query().queryProp("@name"));
+        component->setType(componentType);
+        componentList.append(*component.getClear());
+    }
+
+    return componentList;
+}
+
+IArrayOf<IConstComponent>& Cws_machineEx::listComponentsForCheckingUsage(IConstEnvironment* constEnv,
+    IArrayOf<IConstComponent>& componentList)
+{
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    listComponentsByType(envRoot, eqThorCluster, componentList);
+    listComponentsByType(envRoot, eqRoxieCluster, componentList);
+    listComponentsByType(envRoot, eqDali, componentList);
+    listComponentsByType(envRoot, eqEclAgent, componentList);
+    listComponentsByType(envRoot, eqSashaServer, componentList);
+    listComponentsByType(envRoot, eqDropZone, componentList);
+
+    return componentList;
+}
+
+void Cws_machineEx::readComponentUsageReq(IEspGetComponentUsageRequest& req, IConstEnvironment* constEnv, IPropertyTree* usageReq, IPropertyTree* uniqueUsages)
+{
+    IArrayOf<IConstComponent>& componentList = req.getComponents();
+    if (!componentList.length())
+        listComponentsForCheckingUsage(constEnv, componentList);
+
+    ForEachItemIn(i, componentList)
+    {
+        IConstComponent& component = componentList.item(i);
+        const char* type = component.getType();
+        if (isEmptyString(type))
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty Component Type");
+
+        if (strieq(type, eqThorCluster))
+            readThorUsageReq(component.getName(), constEnv, usageReq);
+        else if (strieq(type, eqRoxieCluster))
+            readRoxieUsageReq(component.getName(), constEnv, usageReq);
+        else if (strieq(type, eqDropZone))
+            readDropZoneUsageReq(component.getName(), constEnv, usageReq);
+        else
+            readOtherComponentUsageReq(component.getName(), type, constEnv, usageReq);
+    }
+
+    //Add unique machines from the usageReq to uniqueUsages.
+    setUniqueMachineUsageReq(usageReq, uniqueUsages);
+}
+
+void Cws_machineEx::getMachineUsages(IEspContext& context, IPropertyTree* uniqueUsages)
+{
+    UnsignedArray threadHandles;
+    Owned<IPropertyTreeIterator> requests= uniqueUsages->getElements("Machine");
+    ForEach(*requests)
+    {
+        Owned<CGetMachineUsageThreadParam> threadParam = new CGetMachineUsageThreadParam(this, context, &requests->query());
+        PooledThreadHandle handle = m_threadPool->start(threadParam.getClear());
+        threadHandles.append(handle);
+    }
+
+    ForEachItemIn(i, threadHandles)
+        m_threadPool->join(threadHandles.item(i));
+}
+
+bool Cws_machineEx::readDiskSpaceResponse(const char* response, __int64& free, __int64& used, int& percentAvail, StringBuffer& pathUsed)
+{
+    if (isEmptyString(response))
+        return false;
+
+    StringArray data;
+    data.appendList(response, " ");
+    if (data.length() < 2)
+        return false;
+
+    used = atol(data.item(0));
+    free = atol(data.item(1));
+
+    __int64 total = free + used;
+    if (total > 0)
+        percentAvail = (int) ((free*100)/total);
+
+    //The given path (ex. /var/lib/HPCCSystems/hpcc-mirror/thor) in the usage request does not exist.
+    //The data.item(2) is the path (ex. /var/lib/HPCCSystems/hpcc-mirror/) the usage script is used
+    //to read the DiskSpace.
+    if (data.length() > 2)
+        pathUsed.set(data.item(2));
+
+    return true;
+}
+
+void Cws_machineEx::getMachineUsage(IEspContext& context, CGetMachineUsageThreadParam* param)
+{
+    VStringBuffer command("/%s/sbin/usage -d=", environmentConfData.m_executionPath.str());
+
+    unsigned pathCount = 0;
+    Owned<IPropertyTreeIterator> diskPathList = param->request->getElements("Folder");
+    ForEach(*diskPathList)
+    {
+        IPropertyTree& t = diskPathList->query();
+        if (pathCount > 0)
+            command.append(",");
+        command.appendf("%s", t.queryProp("@path"));
+        pathCount++;
+    }
+    ESPLOG(LogMax, "command(%s)", command.str());
+
+    StringBuffer response;
+    int error = runCommand(context, param->request->queryProp("@netAddress"), nullptr,
+        (EnvMachineOS) param->request->getPropInt("@OS", MachineOsLinux), command.str(), nullptr, nullptr, response);
+    if (error != 0 || isEmptyString(response))
+    {
+        if (isEmptyString(response))
+            param->request->addProp("@error", "Failed in getting component usage.");
+        else
+            param->request->addProp("@error", response);
+        return;
+    }
+    ESPLOG(LogMax, "response(%s)", response.str());
+
+    ForEach(*diskPathList)
+    {
+        IPropertyTree& diskPathTree = diskPathList->query();
+
+        StringBuffer aDiskPathResp, pathUsed;
+        VStringBuffer diskPath("%s:", diskPathTree.queryProp("@path"));
+        readALineFromResult(response, diskPath, aDiskPathResp, true);
+
+        int percentAvail = 0;
+        __int64 diskSpaceAvailable = 0, diskSpaceUsed = 0;
+        if (!readDiskSpaceResponse(aDiskPathResp.str(), diskSpaceAvailable, diskSpaceUsed, percentAvail, pathUsed))
+        {
+            DBGLOG("Failed to read disc space on %s: %s", param->request->queryProp("@netAddress"), aDiskPathResp.str());
+            diskPathTree.addProp("@error", "Failed to read disc space.");
+            continue;
+        }
+
+        diskPathTree.addPropInt64("@used", diskSpaceUsed);
+        diskPathTree.addPropInt64("@available", diskSpaceAvailable);
+        diskPathTree.addPropInt("@percentAvail", percentAvail);
+        if (!pathUsed.isEmpty())
+            diskPathTree.addProp("@pathUsed", pathUsed);
+    }
+}
+
+void Cws_machineEx::readComponentUsageResult(IEspContext& context, IPropertyTree* usageReq,
+    IPropertyTree* uniqueUsages, IArrayOf<IEspComponentUsage>& componentUsages)
+{
+    Owned<IPropertyTreeIterator> components= usageReq->getElements("Component");
+    ForEach(*components)
+    {
+        IPropertyTree& component = components->query();
+
+        Owned<IEspComponentUsage> componentUsage = createComponentUsage();
+        componentUsage->setName(component.queryProp("@name"));
+        componentUsage->setType(component.queryProp("@type"));
+
+        IArrayOf<IEspMachineUsage> machineUsages;
+        Owned<IPropertyTreeIterator> machines= component.getElements("Machine");
+        ForEach(*machines)
+        {
+            IPropertyTree& machine = machines->query();
+            const char* netAddress = machine.queryProp("@netAddress");
+
+            Owned<IEspMachineUsage> machineUsage = createMachineUsage();
+            machineUsage->setName(machine.queryProp("@name"));
+            machineUsage->setNetAddress(netAddress);
+
+            VStringBuffer xpath("Machine[@netAddress='%s']", netAddress);
+            IPropertyTree* uniqueMachineReqTree = uniqueUsages->queryPropTree(xpath);
+            if (!uniqueMachineReqTree)
+            {
+                machineUsage->setDescription("No data returns.");
+                machineUsages.append(*machineUsage.getClear());
+                continue;
+            }
+            const char* error = uniqueMachineReqTree->queryProp("@error");
+            if (!isEmptyString(error))
+            {
+                machineUsage->setDescription(error);
+                machineUsages.append(*machineUsage.getClear());
+                continue;
+            }
+
+            IArrayOf<IEspDiskUsage> diskUsages;
+            Owned<IPropertyTreeIterator> folders = machine.getElements("Folder");
+            ForEach(*folders)
+            {
+                IPropertyTree& folder = folders->query();
+                const char* aDiskPath = folder.queryProp("@path");
+
+                Owned<IEspDiskUsage> diskUsage = createDiskUsage();
+                diskUsage->setName(folder.queryProp("@name"));
+                diskUsage->setPath(aDiskPath);
+
+                xpath.setf("Folder[@path='%s']", aDiskPath);
+                IPropertyTree* folderTree = uniqueMachineReqTree->queryPropTree(xpath);
+                if (!folderTree)
+                    diskUsage->setDescription("No data returns.");
+                else
+                {
+                    const char* error = folderTree->queryProp("@error");
+                    if (!isEmptyString(error))
+                        diskUsage->setDescription(error);
+                    else
+                    {
+                        diskUsage->setAvailable(folderTree->getPropInt64("@available"));
+                        diskUsage->setInUse(folderTree->getPropInt64("@used"));
+                        diskUsage->setPercentAvailable(folderTree->getPropInt("@percentAvail"));
+                        const char* pathUsed = folderTree->queryProp("@pathUsed");
+                        if (!isEmptyString(pathUsed))
+                        {
+                            VStringBuffer desc("%s not found. Read disk usgae from %s", aDiskPath, pathUsed);
+                            diskUsage->setDescription(desc);
+                        }
+                    }
+                }
+                diskUsages.append(*diskUsage.getClear());
+            }
+            machineUsage->setDiskUsages(diskUsages);
+            machineUsages.append(*machineUsage.getClear());
+        }
+        componentUsage->setMachineUsages(machineUsages);
+        componentUsages.append(*componentUsage.getClear());
+    }
+}
+
+bool Cws_machineEx::onGetComponentUsage(IEspContext& context, IEspGetComponentUsageRequest& req,
+    IEspGetComponentUsageResponse& resp)
+{
+    try
+    {
+        context.ensureFeatureAccess(FEATURE_URL, SecAccess_Read, ECLWATCH_MACHINE_INFO_ACCESS_DENIED, "Failed to Get Machine Information. Permission denied.");
+
+        Owned<IEnvironmentFactory> envFactory = getEnvironmentFactory(true);
+        Owned<IConstEnvironment> constEnv = envFactory->openEnvironment();
+
+        Owned<IPropertyTree> usageReq = createPTree("Req");
+        Owned<IPropertyTree> uniqueUsages = createPTree("Usage");
+        readComponentUsageReq(req, constEnv, usageReq, uniqueUsages);
+
+        getMachineUsages(context, uniqueUsages);
+
+        IArrayOf<IEspComponentUsage> componentUsages;
+        readComponentUsageResult(context, usageReq, uniqueUsages, componentUsages);
+        resp.setComponentUsages(componentUsages);
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+
+    return true;
+}
+
+StringArray& Cws_machineEx::listTargetClusterNames(IConstEnvironment* constEnv, StringArray& targetClusters)
+{
+    Owned<IStringIterator> targets = getTargetClusters(nullptr, nullptr);
+    ForEach(*targets)
+    {
+        SCMStringBuffer target;
+        targetClusters.append(targets->str(target).str());
+    }
+    return targetClusters;
+}
+
+void Cws_machineEx::readTargetClusterUsageReq(IEspGetTargetClusterUsageRequest& req, IConstEnvironment* constEnv,
+    IPropertyTree* usageReq, IPropertyTree* uniqueUsages)
+{
+    StringArray& targetClusters = req.getTargetClusters();
+    if (targetClusters.empty())
+        listTargetClusterNames(constEnv, targetClusters);
+
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    ForEachItemIn(i, targetClusters)
+    {
+        const char* targetClusterName = targetClusters.item(i);
+        if (isEmptyString(targetClusterName))
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty Target Cluster specified.");
+
+        Owned<IConstWUClusterInfo> targetClusterInfo = getTargetClusterInfo(targetClusterName);
+        if (!targetClusterInfo)
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Could not find information about target cluster %s ", targetClusterName);
+
+        Owned<IPropertyTree> targetClusterTree = createPTree("TargetCluster");
+        targetClusterTree->addProp("@name", targetClusterName);
+
+        const StringArray& thors = targetClusterInfo->getThorProcesses();
+        ForEachItemIn(i, thors)
+        {
+            const char* thor = thors.item(i);
+            if (!isEmptyString(thor))
+                readThorUsageReq(thor, constEnv, targetClusterTree);
+        }
+
+        SCMStringBuffer roxie;
+        targetClusterInfo->getRoxieProcess(roxie);
+        if (roxie.length())
+            readRoxieUsageReq(roxie.str(), constEnv, targetClusterTree);
+
+        SCMStringBuffer eclAgent;
+        targetClusterInfo->getAgentName(eclAgent);
+        if (eclAgent.length())
+            readOtherComponentUsageReq(eclAgent.str(), eqEclAgent, constEnv, targetClusterTree);
+
+        usageReq->addPropTree(targetClusterTree->queryName(), LINK(targetClusterTree));
+    }
+
+    Owned<IPropertyTreeIterator> targetClusterItr= usageReq->getElements("TargetCluster");
+    ForEach(*targetClusterItr)
+    {
+        IPropertyTree& targetCluster = targetClusterItr->query();
+        setUniqueMachineUsageReq(&targetCluster, uniqueUsages);
+    }
+}
+
+void Cws_machineEx::readTargetClusterUsageResult(IEspContext& context, IPropertyTree* usageReq,
+    IPropertyTree* uniqueUsages, IArrayOf<IEspTargetClusterUsage>& targetClusterUsages)
+{
+    Owned<IPropertyTreeIterator> targetClusters= usageReq->getElements("TargetCluster");
+    ForEach(*targetClusters)
+    {
+        IPropertyTree& targetCluster = targetClusters->query();
+
+        Owned<IEspTargetClusterUsage> targetClusterUsage = createTargetClusterUsage();
+        targetClusterUsage->setName(targetCluster.queryProp("@name"));
+
+        IArrayOf<IEspComponentUsage> componentUsages;
+        readComponentUsageResult(context, &targetCluster, uniqueUsages, componentUsages);
+        targetClusterUsage->setComponentUsages(componentUsages);
+
+        targetClusterUsages.append(*targetClusterUsage.getClear());
+    }
+}
+
+bool Cws_machineEx::onGetTargetClusterUsage(IEspContext& context, IEspGetTargetClusterUsageRequest& req,
+    IEspGetTargetClusterUsageResponse& resp)
+{
+    try
+    {
+        context.ensureFeatureAccess(FEATURE_URL, SecAccess_Read, ECLWATCH_MACHINE_INFO_ACCESS_DENIED, "Failed to Get Machine Information. Permission denied.");
+
+        Owned<IEnvironmentFactory> envFactory = getEnvironmentFactory(true);
+        Owned<IConstEnvironment> constEnv = envFactory->openEnvironment();
+
+        Owned<IPropertyTree> usageReq = createPTree("Req");
+        Owned<IPropertyTree> uniqueUsages = createPTree("Usage");
+        readTargetClusterUsageReq(req, constEnv, usageReq, uniqueUsages);
+
+        getMachineUsages(context, uniqueUsages);
+
+        IArrayOf<IEspTargetClusterUsage> targetClusterUsages;
+        readTargetClusterUsageResult(context, usageReq, uniqueUsages, targetClusterUsages);
+        resp.setTargetClusterUsages(targetClusterUsages);
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+
+    return true;
+}
+
+bool Cws_machineEx::getEclAgentNameFromNodeGroupName(const char* nodeGroupName, StringBuffer& agentName)
+{
+    //Node group name for an eclagent should be: 'hthor__' + ECLAgentName[ + '_' + a number]
+    if ((strlen(nodeGroupName) <= 7) || strnicmp(nodeGroupName, "hthor__", 7))
+        return false;
+
+    agentName.set(nodeGroupName + 7);
+    const char* ptr = strrchr(agentName.str(), '_');
+    if (isEmptyString(ptr) || isEmptyString(++ptr))
+        return true;
+
+    const char* ptrSuffix = ptr - 1;
+    do
+    {
+        if (!isdigit(ptr[0]))
+            return true;
+        ptr++;
+    } while(!isEmptyString(ptr));
+
+    agentName.setLength(agentName.length() - (ptr - ptrSuffix));
+    return true;
+}
+
+void Cws_machineEx::getThorClusterNamesByGroupName(IPropertyTree* envRoot, const char* group, StringArray& thorClusterNames)
+{
+    Owned<IPropertyTreeIterator> thorClusters= envRoot->getElements("Software/ThorCluster");
+    ForEach(*thorClusters)
+    {
+        IPropertyTree& thorCluster = thorClusters->query();
+        const char *nodeGroupName = thorCluster.queryProp("@nodeGroup");
+        if (!isEmptyString(nodeGroupName) && !strieq(nodeGroupName, group))
+            continue;
+
+        if (!isEmptyString(nodeGroupName))
+            thorClusterNames.append(nodeGroupName);
+        else
+        {
+            const char *name = thorCluster.queryProp("@name");
+            if (strieq(name, group))
+                thorClusterNames.append(name);
+        }
+    }
+}
+
+StringArray& Cws_machineEx::listThorHThorNodeGroups(IConstEnvironment* constEnv, StringArray& nodeGroups)
+{
+    BoolHash uniqueThorClusterGroupNames;
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    Owned<IPropertyTreeIterator> it =envRoot->getElements("Software/ThorCluster");
+    ForEach(*it)
+    {
+        IPropertyTree& cluster = it->query();
+
+        StringBuffer thorClusterGroupName;
+        getClusterGroupName(cluster, thorClusterGroupName);
+        if (!thorClusterGroupName.length())
+            continue;
+        bool* found = uniqueThorClusterGroupNames.getValue(thorClusterGroupName);
+        if (found && *found)
+            continue;
+
+        nodeGroups.append(thorClusterGroupName);
+        uniqueThorClusterGroupNames.setValue(thorClusterGroupName, true);
+    }
+
+    it.setown(envRoot->getElements("Software/EclAgentProcess"));
+    ForEach(*it)
+    {
+        IPropertyTree &cluster = it->query();
+        const char* name = cluster.queryProp("@name");
+        if (!name||!*name)
+            continue;
+
+        unsigned ins = 0;
+        Owned<IPropertyTreeIterator> insts = cluster.getElements("Instance");
+        ForEach(*insts) 
+        {
+            ins++;
+            StringBuffer gname("hthor__");
+            gname.append(name);
+            if (ins>1)
+                gname.append('_').append(ins);
+
+            nodeGroups.append(gname);
+        }
+    }
+    return nodeGroups;
+}
+
+void Cws_machineEx::readNodeGroupUsageReq(IEspGetNodeGroupUsageRequest& req, IConstEnvironment* constEnv,
+    IPropertyTree* usageReq, IPropertyTree* uniqueUsages)
+{
+    StringArray& nodeGroups = req.getNodeGroups();
+    if (nodeGroups.empty())
+        listThorHThorNodeGroups(constEnv, nodeGroups);
+    if (nodeGroups.empty())
+        throw MakeStringException(ECLWATCH_INVALID_INPUT, "No node group found.");
+
+    Owned<IPropertyTree> envRoot = &constEnv->getPTree();
+    ForEachItemIn(i, nodeGroups)
+    {
+        const char* nodeGroupName = nodeGroups.item(i);
+        if (isEmptyString(nodeGroupName))
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Empty node group specified.");
+
+        Owned<IPropertyTree> nodeGroupTree = createPTree("NodeGroup");
+        nodeGroupTree->addProp("@name", nodeGroupName);
+
+        StringBuffer agentName;
+        if (getEclAgentNameFromNodeGroupName(nodeGroupName, agentName))
+            readOtherComponentUsageReq(agentName.str(), eqEclAgent, constEnv, nodeGroupTree);
+        else
+        {
+            StringArray thorNames;
+            getThorClusterNamesByGroupName(envRoot, nodeGroupName, thorNames);
+            if (thorNames.length() == 0)
+                throw MakeStringException(ECLWATCH_INVALID_INPUT, "No thor/hthor can be found for node group name %s.", nodeGroupName);
+
+            ForEachItemIn(ii, thorNames)
+                readThorUsageReq(thorNames.item(ii), constEnv, nodeGroupTree);
+        }
+
+        usageReq->addPropTree(nodeGroupTree->queryName(), LINK(nodeGroupTree));
+    }
+
+    Owned<IPropertyTreeIterator> nodeGroupItr= usageReq->getElements("NodeGroup");
+    ForEach(*nodeGroupItr)
+    {
+        IPropertyTree& nodeGroup = nodeGroupItr->query();
+        setUniqueMachineUsageReq(&nodeGroup, uniqueUsages);
+    }
+}
+
+void Cws_machineEx::readNodeGroupUsageResult(IEspContext& context, IPropertyTree* usageReq,
+    IPropertyTree* uniqueUsages, IArrayOf<IEspNodeGroupUsage>& nodeGroupUsages)
+{
+    Owned<IPropertyTreeIterator> nodeGroups= usageReq->getElements("NodeGroup");
+    ForEach(*nodeGroups)
+    {
+        IPropertyTree& nodeGroup = nodeGroups->query();
+
+        Owned<IEspNodeGroupUsage> nodeGroupUsage = createNodeGroupUsage();
+        nodeGroupUsage->setName(nodeGroup.queryProp("@name"));
+
+        IArrayOf<IEspComponentUsage> componentUsages;
+        readComponentUsageResult(context, &nodeGroup, uniqueUsages, componentUsages);
+        nodeGroupUsage->setComponentUsages(componentUsages);
+
+        nodeGroupUsages.append(*nodeGroupUsage.getClear());
+    }
+}
+
+bool Cws_machineEx::onGetNodeGroupUsage(IEspContext& context, IEspGetNodeGroupUsageRequest& req,
+    IEspGetNodeGroupUsageResponse& resp)
+{
+    try
+    {
+        context.ensureFeatureAccess(FEATURE_URL, SecAccess_Read, ECLWATCH_MACHINE_INFO_ACCESS_DENIED, "Failed to Get Machine Information. Permission denied.");
+
+        Owned<IEnvironmentFactory> envFactory = getEnvironmentFactory(true);
+        Owned<IConstEnvironment> constEnv = envFactory->openEnvironment();
+
+        Owned<IPropertyTree> usageReq = createPTree("Req");
+        Owned<IPropertyTree> uniqueUsages = createPTree("Usage");
+        readNodeGroupUsageReq(req, constEnv, usageReq, uniqueUsages);
+
+        getMachineUsages(context, uniqueUsages);
+
+        IArrayOf<IEspNodeGroupUsage> nodeGroupUsages;
+        readNodeGroupUsageResult(context, usageReq, uniqueUsages, nodeGroupUsages);
+        resp.setNodeGroupUsages(nodeGroupUsages);
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+
+    return true;
+}
+
 bool Cws_machineEx::onGetComponentStatus(IEspContext &context, IEspGetComponentStatusRequest &req, IEspGetComponentStatusResponse &resp)
 {
     try

+ 34 - 0
esp/services/ws_machine/ws_machineService.hpp

@@ -29,6 +29,7 @@ class CMachineInfoThreadParam;
 class CRoxieStateInfoThreadParam;
 class CMetricsThreadParam;
 class CRemoteExecThreadParam;
+class CGetMachineUsageThreadParam;
 
 static const char *legacyFilterStrings[] = {"AttrServerProcess:attrserver", "DaliProcess:daserver",
 "DfuServerProcess:dfuserver", "DKCSlaveProcess:dkcslave", "EclServerProcess:eclserver", "EclCCServerProcess:eclccserver",
@@ -734,6 +735,9 @@ public:
     bool onGetMachineInfoEx(IEspContext &context, IEspGetMachineInfoRequestEx &req, IEspGetMachineInfoResponseEx &resp);
     bool onGetComponentStatus(IEspContext &context, IEspGetComponentStatusRequest &req, IEspGetComponentStatusResponse &resp);
     bool onUpdateComponentStatus(IEspContext &context, IEspUpdateComponentStatusRequest &req, IEspUpdateComponentStatusResponse &resp);
+    bool onGetComponentUsage(IEspContext &context, IEspGetComponentUsageRequest &req, IEspGetComponentUsageResponse &resp);
+    bool onGetTargetClusterUsage(IEspContext &context, IEspGetTargetClusterUsageRequest &req, IEspGetTargetClusterUsageResponse &resp);
+    bool onGetNodeGroupUsage(IEspContext &context, IEspGetNodeGroupUsageRequest &req, IEspGetNodeGroupUsageResponse &resp);
 
     bool onGetMetrics(IEspContext &context, IEspMetricsRequest &req, IEspMetricsResponse &resp);
     bool onStartStop( IEspContext &context, IEspStartStopRequest &req,  IEspStartStopResponse &resp);
@@ -744,6 +748,7 @@ public:
     void doGetMachineInfo(IEspContext& context, CMachineInfoThreadParam* pReq);
     void doGetMetrics(CMetricsThreadParam* pParam);
     bool doStartStop(IEspContext &context, StringArray& addresses, char* userName, char* password, bool bStop, IEspStartStopResponse &resp);
+    void getMachineUsage(IEspContext& context, CGetMachineUsageThreadParam* param);
 
     IConstEnvironment* getConstEnvironment();
 
@@ -806,6 +811,35 @@ private:
     void updateMajorRoxieStateHash(StateHashes& stateHashes, CIArrayOf<CRoxieStateData>& roxieStates);
     StringBuffer& getAcceptLanguage(IEspContext& context, StringBuffer& acceptLanguage);
 
+    StringArray& listTargetClusterNames(IConstEnvironment* constEnv, StringArray& targetClusters);
+    StringArray& listThorHThorNodeGroups(IConstEnvironment* constEnv, StringArray& nodeGroups);
+    IArrayOf<IConstComponent>& listComponentsForCheckingUsage(IConstEnvironment* constEnv, IArrayOf<IConstComponent>& componentList);
+    IArrayOf<IConstComponent>& listComponentsByType(IPropertyTree* envRoot, const char* componentType, IArrayOf<IConstComponent>& componentList);
+    IPropertyTree* createDiskUsageReq(IPropertyTree* envDirectories, const char* pathName,
+        const char* componentType, const char* componentName);
+    IPropertyTree* createMachineUsageReq(IConstEnvironment* constEnv, const char* computer);
+    void readTargetClusterUsageReq(IEspGetTargetClusterUsageRequest& req, IConstEnvironment* constEnv,
+        IPropertyTree* usageReq, IPropertyTree* uniqueUsages);
+    void readNodeGroupUsageReq(IEspGetNodeGroupUsageRequest& req, IConstEnvironment* constEnv,
+        IPropertyTree* usageReq, IPropertyTree* uniqueUsages);
+    void readComponentUsageReq(IEspGetComponentUsageRequest& req, IConstEnvironment* constEnv, IPropertyTree* usageReq,
+        IPropertyTree* uniqueUsages);
+    void readThorUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq);
+    void readRoxieUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq);
+    void readDropZoneUsageReq(const char* name, IConstEnvironment* constEnv, IPropertyTree* usageReq);
+    void readOtherComponentUsageReq(const char* name, const char* type, IConstEnvironment* constEnv, IPropertyTree* usageReq);
+    void setUniqueMachineUsageReq(IPropertyTree* usageReq, IPropertyTree* uniqueUsages);
+    void getMachineUsages(IEspContext& context, IPropertyTree* uniqueUsageReq);
+    bool getEclAgentNameFromNodeGroupName(const char* nodeGroupName, StringBuffer& agentName);
+    void getThorClusterNamesByGroupName(IPropertyTree* envRoot, const char* group, StringArray& thorClusters);
+    void readTargetClusterUsageResult(IEspContext& context, IPropertyTree* usageReq, IPropertyTree* uniqueUsages,
+        IArrayOf<IEspTargetClusterUsage>& targetClusterUsages);
+    void readNodeGroupUsageResult(IEspContext& context, IPropertyTree* usageReq, IPropertyTree* uniqueUsages,
+        IArrayOf<IEspNodeGroupUsage>& nodeGroupUsages);
+    void readComponentUsageResult(IEspContext& context, IPropertyTree* usageReq, IPropertyTree* uniqueUsages,
+        IArrayOf<IEspComponentUsage>& componentUsages);
+    bool readDiskSpaceResponse(const char* buf, __int64& free, __int64& used, int& percentAvail, StringBuffer& pathUsed);
+
     //Still used in StartStop/Rexec, so keep them for now.
     enum OpSysType { OS_Windows, OS_Solaris, OS_Linux };
     StringBuffer m_sTestStr1;

+ 1 - 0
esp/smc/SMCLib/TpWrapper.hpp

@@ -121,6 +121,7 @@ using std::string;
 #define eqHoleCollatorProcess   "HoleCollatorProcess"
 #define eqHoleStandbyProcess    "HoleStandbyProcess"
 #define eqSparkThorProcess      "SparkThorProcess"
+#define eqRoxieServerProcess    "RoxieServerProcess"
 
 #define SDS_LOCK_TIMEOUT 30000