Salome HOME
bos #26455 Introduce service to obtain calculation node's load
authorViktor UZLOV <vuzlov@debian10-01.nnov.opencascade.com>
Fri, 8 Oct 2021 11:21:51 +0000 (14:21 +0300)
committervsr <vsr@opencascade.com>
Mon, 11 Oct 2021 15:05:36 +0000 (18:05 +0300)
CTestTestfileInstall.cmake.in
idl/SALOME_Component.idl
src/Container/CMakeLists.txt
src/Container/Container_i.cxx
src/Container/SALOME_ContainerPy.py
src/Container/SALOME_Container_i.hxx
src/Container/Test/CMakeLists.txt [new file with mode: 0644]
src/Container/Test/CTestTestfileInstall.cmake [new file with mode: 0644]
src/Container/Test/testcontainer.py [new file with mode: 0644]
src/KERNEL_PY/CMakeLists.txt
src/KERNEL_PY/salome_psutil.py [new file with mode: 0644]

index b6ee219e3b6e3b34921ba26aff41d33422195ae8..245b6874cd2f0de3eca993826314cbe97d97984f 100644 (file)
@@ -32,6 +32,7 @@ SUBDIRS( Launcher
          NamingService
          SALOMELocalTrace
          LifeCycleCORBA
+         Container
          Logger
          SALOMETraceCollector
          KernelHelpers
index 47d5f7c167008fcab78d0c82a2423b59135429c5..d58943ad2f00653845451c8e523dcf4ad30b3a34 100644 (file)
@@ -62,6 +62,7 @@ module Engines
   };
 
   typedef sequence<KeyValuePair> FieldsDict;
+  typedef sequence<double> vectorOfDouble;
 
   interface EngineComponent ;
   interface fileRef ;
@@ -247,6 +248,30 @@ module Engines
      * Previous scripts created on container may have been stored in a map. This method removes them. It then clean all the contexts dict attached to them.
      */
     void cleanAllPyScripts();
+
+    //! Return number of CPU cores in the calculation node.
+    long getNumberOfCPUCores();
+
+    //! Return a load of each CPU core.
+    vectorOfDouble loadOfCPUCores() raises(SALOME::SALOME_Exception);
+
+    //! Set custom script to calculate a load of each CPU core.
+    /*!
+      \param script Python script to execute
+     */
+    void setPyScriptForCPULoad(in string script);
+
+    //! Nullify custom script to calculate each CPU core's load.
+    void resetScriptForCPULoad();
+
+    //! Get total physical memory of calculation node, in megabytes.
+    long getTotalPhysicalMemory();
+
+    //! Get used physical memory of calculation node, in megabytes.
+    long getTotalPhysicalMemoryInUse();
+
+    //! Obtain physical memory, used by the current process, in megabytes.
+    long getTotalPhysicalMemoryInUseByMe();
   };
 
   /*! \brief Interface of the %component.
index 7ff7cf0da217aa1b659709de38e3bc5c703d92a6..cb1dc345301c2fc1564b34351dfa185e7ef08ecf 100644 (file)
@@ -22,6 +22,7 @@ INCLUDE_DIRECTORIES(
   ${PTHREAD_INCLUDE_DIR}
   ${HDF5_INCLUDE_DIRS}
   ${PYTHON_INCLUDE_DIRS}
+  ${Boost_INCLUDE_DIRS}
   ${PROJECT_BINARY_DIR}/salome_adm
   ${CMAKE_CURRENT_SOURCE_DIR}/../Basics
   ${CMAKE_CURRENT_SOURCE_DIR}/../SALOMELocalTrace
@@ -44,7 +45,7 @@ SET(SCRIPTS
   SALOME_ContainerPy.py
 )
 
-ADD_DEFINITIONS(${HDF5_DEFINITIONS} ${OMNIORB_DEFINITIONS})
+ADD_DEFINITIONS(${HDF5_DEFINITIONS} ${OMNIORB_DEFINITIONS} ${BOOST_DEFINITIONS})
 SET(COMMON_LIBS
   Registry
   SalomeNotification
@@ -59,6 +60,7 @@ SET(COMMON_LIBS
   SalomeIDLKernel
   ${OMNIORB_LIBRARIES}
   ${PYTHON_LIBRARIES}
+  ${Boost_PYTHON_LIBRARY}
 )
 
 IF(WITH_MPI_SEQ_CONTAINER)
@@ -117,3 +119,7 @@ INSTALL(TARGETS SALOME_Container SALOME_Container_No_NS_Serv DESTINATION ${SALOM
 SALOME_INSTALL_SCRIPTS("${SCRIPTS}" ${SALOME_INSTALL_SCRIPT_PYTHON})
 FILE(GLOB COMMON_HEADERS_HXX "${CMAKE_CURRENT_SOURCE_DIR}/*.hxx")
 INSTALL(FILES ${COMMON_HEADERS_HXX} DESTINATION ${SALOME_INSTALL_HEADERS})
+
+IF(SALOME_BUILD_TESTS)
+  ADD_SUBDIRECTORY(Test)
+ENDIF(SALOME_BUILD_TESTS)
index 4d28965067b254e0c1ffd6b1de4f05eaf8340bf7..b196bfac0ed28e38ae5e653f52ee6551e70afcc0 100644 (file)
@@ -64,6 +64,7 @@ int SIGUSR1 = 1000;
 
 #include <Python.h>
 #include "Container_init_python.hxx"
+#include <boost/python.hpp>
 
 bool _Sleeping = false ;
 
@@ -342,6 +343,197 @@ void Abstract_Engines_Container_i::ping()
   MESSAGE("Engines_Container_i::ping() pid "<< getpid());
 }
 
+//=============================================================================
+//! Get number of CPU cores in the calculation node
+/*!
+*  CORBA method: get number of CPU cores
+*/
+//=============================================================================
+
+CORBA::Long Abstract_Engines_Container_i::getNumberOfCPUCores()
+{
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  PyObject *module = PyImport_ImportModuleNoBlock((char*)"salome_psutil");
+  PyObject *result = PyObject_CallMethod(module,
+                                         (char*)"getNumberOfCPUCores", NULL);
+  int n = PyLong_AsLong(result);
+  Py_DECREF(result);
+  PyGILState_Release(gstate);
+
+  return (CORBA::Long)n;
+}
+
+//=============================================================================
+//! Get a load of each CPU core in the calculation node
+/*!
+*  CORBA method: get a load of each CPU core
+*/
+//=============================================================================
+namespace {
+  std::string parseException()
+  {
+    std::string error;
+    if (PyErr_Occurred())
+    {
+      PyObject *ptype = nullptr;
+      PyObject *pvalue = nullptr;
+      PyObject *ptraceback = nullptr;
+      PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+      if (ptype == nullptr)
+        return std::string("Null exception type");
+      PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
+      if (ptraceback != nullptr)
+        PyException_SetTraceback(pvalue, ptraceback);
+      boost::python::handle<> htype(ptype);
+      boost::python::handle<> hvalue(boost::python::allow_null(pvalue));
+      boost::python::handle<> htraceback(boost::python::allow_null(ptraceback));
+      boost::python::object traceback = boost::python::import("traceback");
+      boost::python::object format_exc = traceback.attr("format_exception");
+      boost::python::object formatted = format_exc(htype, hvalue, htraceback);
+      error = boost::python::extract<std::string>(boost::python::str("\n").join(formatted));
+    }
+    return error;
+  }
+}
+  
+Engines::vectorOfDouble* Abstract_Engines_Container_i::loadOfCPUCores()
+{
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  PyObject *module = PyImport_ImportModuleNoBlock((char*)"salome_psutil");
+  PyObject *result = PyObject_CallMethod(module,
+                                         (char*)"loadOfCPUCores", "s",
+                                         _load_script.c_str());
+  if (PyErr_Occurred())
+  {
+    std::string error = parseException();
+    PyErr_Print();
+    PyGILState_Release(gstate);
+    SALOME::ExceptionStruct es;
+    es.type = SALOME::INTERNAL_ERROR;
+    es.text = CORBA::string_dup(error.c_str());
+    throw SALOME::SALOME_Exception(es);
+  }
+
+  int n = this->getNumberOfCPUCores();
+  if (!PyList_Check(result) || PyList_Size(result) != n) {
+    // bad number of cores
+    PyGILState_Release(gstate);
+    Py_DECREF(result);
+    SALOME::ExceptionStruct es;
+    es.type = SALOME::INTERNAL_ERROR;
+    es.text = "wrong number of cores";
+    throw SALOME::SALOME_Exception(es);
+  }
+
+  Engines::vectorOfDouble_var loads = new Engines::vectorOfDouble;
+  loads->length(n);
+  for (Py_ssize_t i = 0; i < PyList_Size(result); ++i) {
+    PyObject* item = PyList_GetItem(result, i);
+    double foo = PyFloat_AsDouble(item);
+    if (foo < 0.0 || foo > 1.0)
+    {
+      // value not in [0, 1] range
+      PyGILState_Release(gstate);
+      Py_DECREF(result);
+      SALOME::ExceptionStruct es;
+      es.type = SALOME::INTERNAL_ERROR;
+      es.text = "load not in [0, 1] range";
+      throw SALOME::SALOME_Exception(es);
+    }
+    loads[i] = foo;
+  }
+
+  Py_DECREF(result);
+  PyGILState_Release(gstate);
+
+  return loads._retn();
+}
+
+//=============================================================================
+//! Set custom script to calculate a load of each CPU core
+/*!
+*  CORBA method: Set custom script to calculate CPU load
+*  \param script Python script to execute
+*/
+//=============================================================================
+
+void Abstract_Engines_Container_i::setPyScriptForCPULoad(const char *script)
+{
+  _load_script = script;
+}
+
+//=============================================================================
+//! Nullify custom script to calculate each CPU core's load
+/*!
+*  CORBA method: reset script for load calculation to default implementation
+*/
+//=============================================================================
+
+void Abstract_Engines_Container_i::resetScriptForCPULoad()
+{
+  _load_script = "";
+}
+
+//=============================================================================
+//! Get total physical memory of calculation node, in megabytes
+/*!
+*  CORBA method: get total physical memory of calculation node
+*/
+//=============================================================================
+
+CORBA::Long Abstract_Engines_Container_i::getTotalPhysicalMemory()
+{
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  PyObject *module = PyImport_ImportModuleNoBlock((char*)"salome_psutil");
+  PyObject *result = PyObject_CallMethod(module,
+                                         (char*)"getTotalPhysicalMemory", NULL);
+  int n = PyLong_AsLong(result);
+  Py_DECREF(result);
+  PyGILState_Release(gstate);
+
+  return (CORBA::Long)n;
+}
+
+//=============================================================================
+//! Get used physical memory of calculation node, in megabytes
+/*!
+*  CORBA method: get used physical memory of calculation node
+*/
+//=============================================================================
+
+CORBA::Long Abstract_Engines_Container_i::getTotalPhysicalMemoryInUse()
+{
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  PyObject *module = PyImport_ImportModuleNoBlock((char*)"salome_psutil");
+  PyObject *result = PyObject_CallMethod(module,
+                                         (char*)"getTotalPhysicalMemoryInUse", NULL);
+  int n = PyLong_AsLong(result);
+  Py_DECREF(result);
+  PyGILState_Release(gstate);
+
+  return (CORBA::Long)n;
+}
+
+//=============================================================================
+//! Obtain physical memory, used by the current process, in megabytes.
+/*!
+*  CORBA method: get physical memory, used by the current process
+*/
+//=============================================================================
+
+CORBA::Long Abstract_Engines_Container_i::getTotalPhysicalMemoryInUseByMe()
+{
+  PyGILState_STATE gstate = PyGILState_Ensure();
+  PyObject *module = PyImport_ImportModuleNoBlock((char*)"salome_psutil");
+  PyObject *result = PyObject_CallMethod(module,
+                                         (char*)"getTotalPhysicalMemoryInUseByMe", NULL);
+  int n = PyLong_AsLong(result);
+  Py_DECREF(result);
+  PyGILState_Release(gstate);
+
+  return (CORBA::Long)n;
+}
+
 //=============================================================================
 //! Shutdown the container
 /*! 
index da96635bd4b5bc2f41e642059b1ed7630fb9c6ba..d5ee4ec145b78febeae90f7302c990f7103c1eec 100755 (executable)
@@ -35,6 +35,7 @@ import string
 from omniORB import CORBA, PortableServer
 import SALOMEDS 
 import Engines, Engines__POA
+import salome_psutil
 from SALOME_NamingServicePy import *
 from SALOME_Embedded_NamingService import SALOME_Embedded_NamingService
 from SALOME_ComponentPy import *
@@ -55,6 +56,7 @@ class SALOME_ContainerPy_Gen_i(Engines__POA.Container):
     _poa = None
     _numInstance = 0
     _listInstances_map = {}
+    _script = ""
 
     #-------------------------------------------------------------------------
 
@@ -62,6 +64,7 @@ class SALOME_ContainerPy_Gen_i(Engines__POA.Container):
         MESSAGE( "SALOME_ContainerPy_i::__init__" )
         self._orb = orb
         self._poa = poa
+        self._load_script = None
         myMachine=getShortHostName()
         Container_path = "/Containers/" + myMachine + "/" + containerName
         self._containerName = Container_path
@@ -205,6 +208,41 @@ class SALOME_ContainerPy_Gen_i(Engines__POA.Container):
 
     #-------------------------------------------------------------------------
 
+    def getNumberOfCPUCores(self):
+        return salome_psutil.getNumberOfCPUCores()
+
+    #-------------------------------------------------------------------------
+
+    def loadOfCPUCores(self):
+        return salome_psutil.loadOfCPUCores(self._load_script)
+
+    #-------------------------------------------------------------------------
+
+    def setPyScriptForCPULoad(self, script):
+        self._load_script = script
+
+    #-------------------------------------------------------------------------
+
+    def resetScriptForCPULoad(self):
+        self._load_script = None
+
+    #-------------------------------------------------------------------------
+
+    def getTotalPhysicalMemory(self):
+        return salome_psutil.getTotalPhysicalMemory()
+
+    #-------------------------------------------------------------------------
+
+    def getTotalPhysicalMemoryInUse(self):
+        return salome_psutil.getTotalPhysicalMemoryInUse()
+
+    #-------------------------------------------------------------------------
+
+    def getTotalPhysicalMemoryInUseByMe(self):
+        return salome_psutil.getTotalPhysicalMemoryInUseByMe()
+
+    #-------------------------------------------------------------------------
+
     def _get_name(self):
         MESSAGE( "SALOME_ContainerPy_i::_get_name" )
         return self._containerName
index 70a9a1b7e717ea95171aedfeda9644749f006c2e..ee2c5db54b051e5aa05ad12b5cbd2688e17bfaee 100644 (file)
@@ -96,6 +96,13 @@ public:
   void finalize_removal();
 
   virtual void ping();
+  CORBA::Long getNumberOfCPUCores();
+  Engines::vectorOfDouble* loadOfCPUCores();
+  void setPyScriptForCPULoad(const char *script);
+  void resetScriptForCPULoad();
+  CORBA::Long getTotalPhysicalMemory();
+  CORBA::Long getTotalPhysicalMemoryInUse();
+  CORBA::Long getTotalPhysicalMemoryInUseByMe();
   char *name();
   char *workingdir();
   char *logfilename();
@@ -158,6 +165,7 @@ protected:
   std::string _library_path;
   std::string _containerName;
   std::string _logfilename;
+  std::string _load_script;
   CORBA::ORB_var _orb;
   PortableServer::POA_var _poa;
   PortableServer::ObjectId *_id;
diff --git a/src/Container/Test/CMakeLists.txt b/src/Container/Test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..17d84c1
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright (C) 2012-2021  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This 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 2.1 of the License, or (at your option) any later version.
+#
+# This 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 this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(LOCAL_TEST_DIR ${KERNEL_TEST_DIR}/Container)
+INSTALL(FILES testcontainer.py DESTINATION ${LOCAL_TEST_DIR})
+
+INSTALL(FILES CTestTestfileInstall.cmake
+        DESTINATION ${LOCAL_TEST_DIR}
+        RENAME CTestTestfile.cmake)
diff --git a/src/Container/Test/CTestTestfileInstall.cmake b/src/Container/Test/CTestTestfileInstall.cmake
new file mode 100644 (file)
index 0000000..33360f1
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright (C) 2015-2021  CEA/DEN, EDF R&D
+#
+# This 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 2.1 of the License, or (at your option) any later version.
+#
+# This 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 this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+IF(NOT WIN32)
+  SET(TEST_NAME ${COMPONENT_NAME}_testcontainer)
+  ADD_TEST(${TEST_NAME} ${PYTHON_TEST_DRIVER} ${TIMEOUT} testcontainer.py)
+  SET_TESTS_PROPERTIES(${TEST_NAME} PROPERTIES
+                                    LABELS "${COMPONENT_NAME}"
+                                    ENVIRONMENT "LD_LIBRARY_PATH=${KERNEL_TEST_LIB}:$ENV{LD_LIBRARY_PATH}"
+                      )
+ENDIF()
diff --git a/src/Container/Test/testcontainer.py b/src/Container/Test/testcontainer.py
new file mode 100644 (file)
index 0000000..5eb7b25
--- /dev/null
@@ -0,0 +1,117 @@
+#  -*- coding: iso-8859-1 -*-
+# Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This 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 2.1 of the License, or (at your option) any later version.
+#
+# This 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 this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+import unittest
+from os import getcwd
+from Engines import ContainerParameters, ResourceParameters
+import SALOME
+import salome
+
+from time import sleep
+
+class TestResourceManager(unittest.TestCase):
+    def getContainer(self, name):
+        rp = ResourceParameters(name="localhost",
+                                hostname="localhost",
+                                can_launch_batch_jobs=False,
+                                can_run_containers=True,
+                                OS="Linux",
+                                componentList=[],
+                                nb_proc=1,
+                                mem_mb=1000,
+                                cpu_clock=1000,
+                                nb_node=1,
+                                nb_proc_per_node=1,
+                                policy="first",
+                                resList=[])
+        cp = ContainerParameters(container_name=name,
+                                 mode="start",
+                                 workingdir=getcwd(),
+                                 nb_proc=1,
+                                 isMPI=False,
+                                 parallelLib="",
+                                 resource_params=rp)
+        cm = salome.naming_service.Resolve("/ContainerManager")
+        return cm.GiveContainer(cp)
+
+    def checkLoads(self, cont, loads):
+        self.assertEqual(len(loads), cont.getNumberOfCPUCores())
+        for load in loads:
+            self.assertTrue(0.0 <= load <= 1.0)
+
+    def test1(self):
+        # Check loadOfCPUCores
+        cont = self.getContainer("test_container_1")
+        loads1 = cont.loadOfCPUCores()
+        self.checkLoads(cont, loads1)
+        sleep(1)
+        loads2 = cont.loadOfCPUCores()
+        self.checkLoads(cont, loads2)
+        self.assertNotEqual(loads1, loads2)
+        cont.Shutdown()
+
+    def test2(self):
+        # Check custom script
+        cont = self.getContainer("test_container_2")
+        import multiprocessing as mp
+        ref_load = [max(0.1*(i+1),1.0) for i in range(mp.cpu_count())]
+        cont.setPyScriptForCPULoad('cpu_loads = {}'.format(ref_load))
+        loads1 = cont.loadOfCPUCores()
+        self.assertEqual(loads1, ref_load)
+        cont.resetScriptForCPULoad()
+        loads2 = cont.loadOfCPUCores()
+        self.checkLoads(cont, loads2)
+        cont.Shutdown()
+
+    def test3(self):
+        # Check bad script
+        cont = self.getContainer("test_container_3")
+        cont.setPyScriptForCPULoad("bla-bla-bla")
+        self.assertRaises(Exception, cont.loadOfCPUCores)
+        cont.Shutdown()
+
+    def test4(self):
+        # check memory sizes
+        cont = self.getContainer("test_container_4")
+        memory_total = cont.getTotalPhysicalMemory()
+        memory_in_use = cont.getTotalPhysicalMemoryInUse()
+        memory_by_me = cont.getTotalPhysicalMemoryInUseByMe()
+        self.assertGreater(memory_total, memory_in_use)
+        self.assertGreater(memory_in_use, memory_by_me)
+        cont.Shutdown()
+    
+    def test5(self):
+        """
+        Test checking memory consumption of container
+        """
+        cont = self.getContainer("test_container_5")
+        memory_by_me_start = cont.getTotalPhysicalMemoryInUseByMe()
+        import pickle
+        psn = cont.createPyScriptNode("n","""b = bytearray(10485760)""")# create 10MB byte array abroad
+        psn.execute([],pickle.dumps(((),{})))
+        memory_by_me_end = cont.getTotalPhysicalMemoryInUseByMe()
+        self.assertGreater(memory_by_me_end,memory_by_me_start)
+        self.assertIn(memory_by_me_end-memory_by_me_start,[10,11,12])# test elevation of memory
+        cont.Shutdown()
+
+if __name__ == '__main__':
+    salome.standalone()
+    salome.salome_init()
+    unittest.main()
index ecc4038ace0334ed081dccb9e94397ed7fdd3db3..e5aaf76eb69e81c142dcebb9cf9f22c76369a2f5 100644 (file)
@@ -39,6 +39,7 @@ SET(salomepython_PYTHON
     salome_notebook.py
     salome_pynode.py
     salome_genericobj.py
+    salome_psutil.py
   )
 
 SALOME_INSTALL_SCRIPTS("${salomepython_PYTHON}" ${SALOME_INSTALL_PYTHON})
diff --git a/src/KERNEL_PY/salome_psutil.py b/src/KERNEL_PY/salome_psutil.py
new file mode 100644 (file)
index 0000000..da9908e
--- /dev/null
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2021  CEA/DEN, EDF R&D, OPEN CASCADE
+#
+# This 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 2.1 of the License, or (at your option) any later version.
+#
+# This 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 this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+'''
+SALOME utilities for process management.
+'''
+
+import os
+import psutil
+
+
+def getNumberOfCPUCores(): # pragma pylint: disable=invalid-name
+    '''
+    Get number of CPU cores in the calculation node.
+    :return Number of cores
+    '''
+    return psutil.cpu_count(logical=True)
+
+
+def loadOfCPUCores(script=None): # pragma pylint: disable=invalid-name
+    '''
+    Get a load of each CPU core in the calculation node.
+
+    A script to compute loads can be customized via `script` parameter.
+    In that case, the script must either set `cpu_loads` variable (which
+    should be of list type), or specify `getCPULoads()` function returning
+    list as result. In both cases, the list's size must be equal to the value
+    returning by method `getNumberOfCPUCores()`, and each value in this list
+    must be in range [0, 1], otherwise exception is raised.
+
+    If `script` is not specified, default implementation is used.
+
+    :param script Custom script to calculate loads
+    :return List that contains loads of each CPU core.
+    '''
+    if not script:
+        return [x/100 for x in psutil.cpu_percent(interval=None, percpu=True)]
+    cpu_loads, loc = None, {}
+    exec(script, globals(), loc) # pragma pylint: disable=exec-used
+    cpu_loads = loc['getCPULoads']() if 'getCPULoads' in loc else loc.get('cpu_loads')
+    if cpu_loads is None:
+        raise ValueError('Bad script. Specify `getCPULoads` function or `cpu_loads` variable')
+    if not isinstance(cpu_loads, (list, tuple)):
+        raise TypeError('Bad script. Result must be list or tuple.')
+    size = getNumberOfCPUCores()
+    if len(cpu_loads) != size:
+        raise ValueError('Bad script. Result is of incorrect length (must be {})'.format(size))
+    values = [i for i in cpu_loads if 0 <= i <= 1]
+    if len(values) != size:
+        raise ValueError('Bad script. Values are not in [0, 1] range')
+    return [i for i in cpu_loads]
+
+
+def getTotalPhysicalMemory(): # pragma pylint: disable=invalid-name
+    '''
+    Get total physical memory of the calculation node.
+    :return Size of physical memory, in megabytes.
+    '''
+    return int(psutil.virtual_memory().total/1024/1024)
+
+
+def getTotalPhysicalMemoryInUse(): # pragma pylint: disable=invalid-name
+    '''
+    Get used physical memory of the calculation node.
+    :return Size of used physical memory, in megabytes.
+    '''
+    return int(psutil.virtual_memory().used/1024/1024)
+
+
+def getTotalPhysicalMemoryInUseByMe(): # pragma pylint: disable=invalid-name
+    '''
+    Get physical memory occupied by the current process.
+    :return Size of physical memory, used by current process, in megabytes.
+    '''
+    process = psutil.Process(os.getpid())
+    memory_in_mb = int(process.memory_info().rss/1024/1024)
+    return memory_in_mb