From ba4097936d5baf0455a0217c6426860ffeb9f556 Mon Sep 17 00:00:00 2001 From: Viktor UZLOV Date: Fri, 8 Oct 2021 14:21:51 +0300 Subject: [PATCH] bos #26455 Introduce service to obtain calculation node's load --- CTestTestfileInstall.cmake.in | 1 + idl/SALOME_Component.idl | 25 +++ src/Container/CMakeLists.txt | 8 +- src/Container/Container_i.cxx | 192 ++++++++++++++++++ src/Container/SALOME_ContainerPy.py | 38 ++++ src/Container/SALOME_Container_i.hxx | 8 + src/Container/Test/CMakeLists.txt | 25 +++ src/Container/Test/CTestTestfileInstall.cmake | 27 +++ src/Container/Test/testcontainer.py | 117 +++++++++++ src/KERNEL_PY/CMakeLists.txt | 1 + src/KERNEL_PY/salome_psutil.py | 94 +++++++++ 11 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 src/Container/Test/CMakeLists.txt create mode 100644 src/Container/Test/CTestTestfileInstall.cmake create mode 100644 src/Container/Test/testcontainer.py create mode 100644 src/KERNEL_PY/salome_psutil.py diff --git a/CTestTestfileInstall.cmake.in b/CTestTestfileInstall.cmake.in index b6ee219e3..245b6874c 100644 --- a/CTestTestfileInstall.cmake.in +++ b/CTestTestfileInstall.cmake.in @@ -32,6 +32,7 @@ SUBDIRS( Launcher NamingService SALOMELocalTrace LifeCycleCORBA + Container Logger SALOMETraceCollector KernelHelpers diff --git a/idl/SALOME_Component.idl b/idl/SALOME_Component.idl index 47d5f7c16..d58943ad2 100644 --- a/idl/SALOME_Component.idl +++ b/idl/SALOME_Component.idl @@ -62,6 +62,7 @@ module Engines }; typedef sequence FieldsDict; + typedef sequence 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. diff --git a/src/Container/CMakeLists.txt b/src/Container/CMakeLists.txt index 7ff7cf0da..cb1dc3453 100644 --- a/src/Container/CMakeLists.txt +++ b/src/Container/CMakeLists.txt @@ -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) diff --git a/src/Container/Container_i.cxx b/src/Container/Container_i.cxx index 4d2896506..b196bfac0 100644 --- a/src/Container/Container_i.cxx +++ b/src/Container/Container_i.cxx @@ -64,6 +64,7 @@ int SIGUSR1 = 1000; #include #include "Container_init_python.hxx" +#include 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(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 /*! diff --git a/src/Container/SALOME_ContainerPy.py b/src/Container/SALOME_ContainerPy.py index da96635bd..d5ee4ec14 100755 --- a/src/Container/SALOME_ContainerPy.py +++ b/src/Container/SALOME_ContainerPy.py @@ -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 diff --git a/src/Container/SALOME_Container_i.hxx b/src/Container/SALOME_Container_i.hxx index 70a9a1b7e..ee2c5db54 100644 --- a/src/Container/SALOME_Container_i.hxx +++ b/src/Container/SALOME_Container_i.hxx @@ -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 index 000000000..17d84c143 --- /dev/null +++ b/src/Container/Test/CMakeLists.txt @@ -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 index 000000000..33360f185 --- /dev/null +++ b/src/Container/Test/CTestTestfileInstall.cmake @@ -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 index 000000000..5eb7b255f --- /dev/null +++ b/src/Container/Test/testcontainer.py @@ -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() diff --git a/src/KERNEL_PY/CMakeLists.txt b/src/KERNEL_PY/CMakeLists.txt index ecc4038ac..e5aaf76eb 100644 --- a/src/KERNEL_PY/CMakeLists.txt +++ b/src/KERNEL_PY/CMakeLists.txt @@ -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 index 000000000..da9908e93 --- /dev/null +++ b/src/KERNEL_PY/salome_psutil.py @@ -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 -- 2.39.2