Salome HOME
[EDF29150] : Container is able to keep track of input/output memory size and IO in...
authorAnthony Geay <anthony.geay@edf.fr>
Fri, 29 Dec 2023 14:57:27 +0000 (15:57 +0100)
committerAnthony Geay <anthony.geay@edf.fr>
Fri, 29 Dec 2023 14:57:27 +0000 (15:57 +0100)
src/Container/CMakeLists.txt
src/Container/SALOME_Container.py
src/Container/SALOME_ContainerHelper.py [new file with mode: 0644]
src/Container/SALOME_ContainerManager.cxx
src/Container/SALOME_PyNode.py

index cc291a4a0094b228d8aaf6729b4ee0ddc0d0f55a..dfc050ba8a32657740f6cc7dab3847cdf2b1663c 100644 (file)
@@ -45,6 +45,7 @@ SET(SCRIPTS
   SALOME_ComponentPy.py
   SALOME_PyNode.py
   SALOME_Container.py
+  SALOME_ContainerHelper.py
   SALOME_ContainerPy.py
 )
 
index e58adf5650fb6e3491a83514b88cf8d461ee86f1..e9ad62f99132fb7ae9a3740e778b9c3c4461aa61 100644 (file)
@@ -41,121 +41,12 @@ import Engines, Engines__POA
 from SALOME_NamingServicePy import *
 from SALOME_ComponentPy import *
 import SALOME_PyNode
-from collections import defaultdict
 
 from SALOME_utilities import *
 from Utils_Identity import getShortHostName
 from launchConfigureParser import verbose
 from KernelBasis import VerbosityActivated
-
-class ScriptExecInfo:
-    @classmethod
-    def GetRepresentationOfTimeDelta(cls,endTime, startTime):
-       if endTime is None and startTime is None:
-          return "not measured"
-       td = endTime - startTime
-       import time
-       ts_of_td = time.gmtime(td.total_seconds())
-       return "{}.{:06d}".format(time.strftime("%H:%M:%S",ts_of_td),td.microseconds)
-
-    def __init__(self):
-      self._start_exec_time = None
-      self._end_exec_time = None
-      self._start_input_time = None
-      self._end_input_time = None
-      self._start_output_time = None
-      self._end_output_time = None
-
-    @property
-    def startInputTime(self):
-      return self._start_input_time
-    
-    @startInputTime.setter
-    def startInputTime(self,value):
-      self._start_input_time = value
-
-    @property
-    def endInputTime(self):
-      return self._end_input_time
-    
-    @endInputTime.setter
-    def endInputTime(self,value):
-      self._end_input_time = value
-
-    @property
-    def startExecTime(self):
-      return self._start_exec_time
-    
-    @startExecTime.setter
-    def startExecTime(self,value):
-      self._start_exec_time = value
-
-    @property
-    def endExecTime(self):
-      return self._end_exec_time
-    
-    @endExecTime.setter
-    def endExecTime(self,value):
-      self._end_exec_time = value
-
-    @property
-    def startOutputTime(self):
-      return self._start_output_time
-    
-    @startOutputTime.setter
-    def startOutputTime(self,value):
-      self._start_output_time = value
-
-    @property
-    def endOutputTime(self):
-      return self._end_output_time
-    
-    @endOutputTime.setter
-    def endOutputTime(self,value):
-      self._end_output_time = value
-
-    @property
-    def execTimeStr(self):
-       return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endExecTime,self.startExecTime)
-    
-    @property
-    def inputTimeStr(self):
-       return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endInputTime,self.startInputTime)
-
-    def __str__(self):
-      return """start exec time = {self.startExecTime}
-end exec time = {self.endExecTime}
-exec_time = {self.execTimeStr}
-input unpickling and ev load from disk time = {self.inputTimeStr}
-output serialization and ev write to disk time = {self.inputTimeStr}""".format(**locals())
-
-class ScriptInfo:
-  def __init__(self, nodeName):
-      self._node_name = nodeName
-      self._code = ""
-      self._exec = defaultdict(ScriptExecInfo)
-
-  @property
-  def execs(self):
-      return self._exec
-
-  @property
-  def nodeName(self):
-      return self._node_name
-
-  @property
-  def code(self):
-      return self._code
-  
-  @code.setter
-  def code(self,value):
-      self._code = value
-
-  def __str__(self):
-      return """code = {self.code}\nexecs = {self.execs}""".format(**locals())
-  
-  def __repr__(self):
-      return """ScriptInfo \"{self.nodeName}\"""".format(**locals())
+from SALOME_ContainerHelper import ScriptInfo
 
 #=============================================================================
 
@@ -256,7 +147,6 @@ class SALOME_Container_i:
           return 1,"".join(l)
 
     def create_pyscriptnode(self,nodeName,code):
-        import pickle
         try:
           self._dbg_info.append( ScriptInfo(nodeName) )
           node=SALOME_PyNode.PyScriptNode_i(nodeName,code,self._poa,self)
@@ -269,6 +159,7 @@ class SALOME_Container_i:
         except Exception:
           exc_typ,exc_val,exc_fr=sys.exc_info()
           l=traceback.format_exception(exc_typ,exc_val,exc_fr)
+          print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
           return 1,"".join(l)
         
     def positionVerbosityOfLogger(self):
diff --git a/src/Container/SALOME_ContainerHelper.py b/src/Container/SALOME_ContainerHelper.py
new file mode 100644 (file)
index 0000000..ca18cce
--- /dev/null
@@ -0,0 +1,305 @@
+#  -*- coding: iso-8859-1 -*-
+# Copyright (C) 2023  CEA, EDF
+#
+# 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
+#
+
+from collections import defaultdict
+
+class ScriptExecInfo:
+    @classmethod
+    def GetRepresentationOfTimeDelta(cls,endTime, startTime):
+       if endTime is None and startTime is None:
+          return "not measured"
+       td = endTime - startTime
+       import time
+       ts_of_td = time.gmtime(td.total_seconds())
+       return "{}.{:06d}".format(time.strftime("%H:%M:%S",ts_of_td),td.microseconds)
+    
+    @classmethod
+    def MemRepr(cls,memInByte):
+      m = memInByte
+      UNITS=["B","kB","MB","GB"]
+      remain = 0
+      oss = ""
+      for i in range( len(UNITS) ):
+          if m<1024:
+              oss = "{:03d}".format( int( (remain/1024)*1000 ) )
+              oss = "{}.{} {}".format(m,oss,UNITS[i])
+              return oss
+          else:
+              if i!=3:
+                  remain = m%1024
+                  m//=1024
+      return "{} {}".format(m,UNITS[3])
+
+    def __init__(self):
+      self._start_exec_time = None
+      self._end_exec_time = None
+      self._start_input_time = None
+      self._end_input_time = None
+      self._start_output_time = None
+      self._end_output_time = None
+      self._input_mem = 0
+      self._input_hdd_mem = None
+      self._output_mem = 0
+      self._output_hdd_mem = None
+
+    @property
+    def inputMem(self):
+      return self._input_mem
+    
+    @inputMem.setter
+    def inputMem(self,value):
+      self._input_mem = value
+       
+    @property
+    def inputMemStr(self):
+      return ScriptExecInfo.MemRepr( self.inputMem )
+    
+    @property
+    def outputMem(self):
+      return self._output_mem
+    
+    @outputMem.setter
+    def outputMem(self,value):
+      self._output_mem = value
+       
+    @property
+    def outputMemStr(self):
+      return ScriptExecInfo.MemRepr( self.outputMem )
+    
+    @property
+    def inputHDDMem(self):
+      return self._input_hdd_mem
+    
+    @inputHDDMem.setter
+    def inputHDDMem(self,value):
+      self._input_hdd_mem = value
+
+    @property
+    def inputHDDMemStr(self):
+      if self._input_hdd_mem is None:
+         return "not computed"
+      return " ".join( [ ScriptExecInfo.MemRepr( elt.getSizeOfFileRead() ) for elt in self._input_hdd_mem] )
+    
+    @property
+    def outputHDDMem(self):
+      return self._output_hdd_mem
+    
+    @outputHDDMem.setter
+    def outputHDDMem(self,value):
+      self._output_hdd_mem = value
+
+    @property
+    def outputHDDMemStr(self):
+      if self._output_hdd_mem is None:
+         return "not computed"
+      return " ".join( [ ScriptExecInfo.MemRepr( elt.getSizeOfFileRead() ) for elt in self._output_hdd_mem] )
+
+    @property
+    def startInputTime(self):
+      return self._start_input_time
+    
+    @startInputTime.setter
+    def startInputTime(self,value):
+      self._start_input_time = value
+
+    @property
+    def endInputTime(self):
+      return self._end_input_time
+    
+    @endInputTime.setter
+    def endInputTime(self,value):
+      self._end_input_time = value
+
+    @property
+    def startExecTime(self):
+      return self._start_exec_time
+    
+    @startExecTime.setter
+    def startExecTime(self,value):
+      self._start_exec_time = value
+
+    @property
+    def endExecTime(self):
+      return self._end_exec_time
+    
+    @endExecTime.setter
+    def endExecTime(self,value):
+      self._end_exec_time = value
+
+    @property
+    def startOutputTime(self):
+      return self._start_output_time
+    
+    @startOutputTime.setter
+    def startOutputTime(self,value):
+      self._start_output_time = value
+
+    @property
+    def endOutputTime(self):
+      return self._end_output_time
+    
+    @endOutputTime.setter
+    def endOutputTime(self,value):
+      self._end_output_time = value
+
+    @property
+    def execTimeStr(self):
+       return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endExecTime,self.startExecTime)
+    
+    @property
+    def inputTimeStr(self):
+       return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endInputTime,self.startInputTime)
+
+    def __str__(self):
+      return """start exec time = {self.startExecTime}
+end exec time = {self.endExecTime}
+exec_time = {self.execTimeStr}
+input unpickling and ev load from disk time = {self.inputTimeStr}
+output serialization and ev write to disk time = {self.inputTimeStr}
+input memory size before exec (MemoryPeak 2x) = {self.inputMemStr}
+input memory size from HDD = {self.inputHDDMemStr}
+output memory size after exec (MemoryPeak 2x) = {self.outputMemStr}
+output memory size from HDD = {self.outputHDDMemStr}""".format(**locals())
+
+class ScriptInfo:
+  def __init__(self, nodeName):
+      self._node_name = nodeName
+      self._code = ""
+      self._exec = defaultdict(ScriptExecInfo)
+
+  @property
+  def execs(self):
+      return self._exec
+
+  @property
+  def nodeName(self):
+      return self._node_name
+
+  @property
+  def code(self):
+      return self._code
+  
+  @code.setter
+  def code(self,value):
+      self._code = value
+
+  def __str__(self):
+      return """code = {self.code}\nexecs = {self.execs}""".format(**locals())
+  
+  def __repr__(self):
+      return """ScriptInfo \"{self.nodeName}\"""".format(**locals())
+
+from abc import ABC, abstractmethod
+
+class InOutputObjVisitorAbstract(ABC):
+  def __init__(self):
+      self._cur_obj = None
+      self._data = []
+
+  def enter(self):
+      self._cur_obj = ObjMemModel()
+      return self._cur_obj
+  
+  def leave(self):
+      self._data.append( self._cur_obj )
+      self._cur_obj = None
+
+  def getSizeOfFileRead(self):
+      return sum( [elt.getSizeOfFileRead() for elt in self._data] )
+      
+  def visitor(self):
+      return self
+
+  def setHDDMem(self, v):
+      pass
+  
+  def setFileName(self, fileName):
+      pass
+
+  @abstractmethod
+  def getRepr(self):
+      pass
+
+class InOutputObjVisitorIter:
+  def __init__(self, visitor):
+      self._visitor = visitor
+      self._current = 0
+
+  def __next__(self):
+      if self._current >= len(self._visitor._data):
+            raise StopIteration
+      else:
+        ret = self._visitor._data[ self._current ]
+        self._current += 1
+        return ret
+
+class InOutputObjVisitor(InOutputObjVisitorAbstract):
+  def __init__(self):
+      super().__init__()
+      
+  def getRepr(self):
+      return self.getSizeOfFileRead()
+  
+  def __iter__(self):
+     return InOutputObjVisitorIter(self)
+
+class ObjMemModel(InOutputObjVisitorAbstract):
+  def __init__(self):
+      super().__init__()
+      self._hdd_mem = 0
+      self._file_name = None
+      
+  def setHDDMem(self, v):
+      self._hdd_mem = v
+      del self._data
+
+  def setFileName(self, fileName):
+      self._file_name = fileName
+      pass
+      
+  def getSizeOfFileRead(self):
+      if hasattr(self,"_data"):
+        return super().getSizeOfFileRead()
+      else:
+        return self._hdd_mem
+  
+  def getRepr(self):
+      return self.getSizeOfFileRead()
+
+class FakeObjVisitor:
+  def setHDDMem(self, v):
+      pass
+    
+  def visitor(self):
+      return None
+
+class InOutputObjVisitorCM:
+  def __init__(self, visitor):
+     self._visitor = visitor
+  def __enter__(self):
+      if self._visitor:
+        r = self._visitor.enter()
+        return r
+      else:
+        return FakeObjVisitor()
+  def __exit__(self,exctype, exc, tb):
+      if self._visitor:
+        self._visitor.leave()
+      pass
index 40dbb201131f18ddc7b9546c691eb42095f3ead6..730f35cd62e915785c3156d0146b5d8052fb218e 100644 (file)
@@ -504,7 +504,11 @@ Engines::Container_ptr SALOME_ContainerManager::GiveContainer(const Engines::Con
         }
         cont->override_environment_python( envCorba );
         if( !_code_to_exe_on_startup.empty() )
+        {
+          INFOS("[GiveContainer] container " << containerNameInNS << " python code executed " << _code_to_exe_on_startup);
           cont->execute_python_code( _code_to_exe_on_startup.c_str() );
+        }
+        INFOS("[GiveContainer] container " << containerNameInNS << " verbosity positionning Activation = " << SALOME::VerbosityActivated() << " Verbosity Level = " << SALOME::VerbosityLevelStr());
         cont->setVerbosity( SALOME::VerbosityActivated(), SALOME::VerbosityLevelStr().c_str() );
         return cont._retn();
       }
index 6e04446e86b53f846bd92638c049739c0589b1e8..7c1985d0d9ef8505eabdaeafec5d10dd9b846af1 100644 (file)
@@ -29,6 +29,7 @@ import pickle
 import Engines__POA
 import SALOME__POA
 import SALOME
+import logging
 
 MY_CONTAINER_ENTRY_IN_GLBS = "my_container"
 
@@ -129,10 +130,32 @@ TypeCounter = c_int
 def GetSizeOfTCnt():
   return len( bytes(TypeCounter(0) ) )
 
-def GetObjectFromFile(fname):
+def GetSizeOfBufferedReader(f):
+  """
+  This method returns in bytes size of a file openned.
+
+  Args:
+  ----
+      f (io.IOBase): buffered reader returned by open
+      
+  Returns
+  -------
+      int: number of bytes
+  """
+  import io
+  pos = f.tell()
+  f.seek(0,io.SEEK_END)
+  pos2 = f.tell()
+  f.seek(pos,io.SEEK_SET)
+  return pos2-pos
+
+def GetObjectFromFile(fname, visitor = None):
   with open(fname,"rb") as f:
     cntb = f.read( GetSizeOfTCnt() )
     cnt = TypeCounter.from_buffer_copy( cntb ).value
+    if visitor:
+      visitor.setHDDMem( GetSizeOfBufferedReader(f) )
+      visitor.setFileName( fname )
     obj = pickle.load(f)
   return obj,cnt
 
@@ -241,8 +264,8 @@ class BigObjectOnDiskBase:
   def __dumpIntoFile(self, objSerialized):
     DumpInFile( objSerialized, self._filename )
 
-  def get(self):
-    obj, _ = GetObjectFromFile( self._filename )
+  def get(self, visitor = None):
+    obj, _ = GetObjectFromFile( self._filename, visitor )
     return obj
 
   def __float__(self):
@@ -269,8 +292,8 @@ class BigObjectOnDiskListElement(BigObjectOnDiskBase):
     self._pos = pos
     self._length = length
 
-  def get(self):
-    fullObj = BigObjectOnDiskBase.get(self)
+  def get(self, visitor = None):
+    fullObj = BigObjectOnDiskBase.get(self, visitor)
     return fullObj[ self._pos ]
     
   def __getitem__(self, i):
@@ -298,36 +321,69 @@ class BigObjectOnDiskTuple(BigObjectOnDiskSequence):
   def __init__(self, length, fileName, objSerialized):
     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
 
-def SpoolPickleObject( obj ):
-  import pickle
-  pickleObjInit = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
-  if not ActivateProxyMecanismOrNot( len(pickleObjInit) ):
-    return pickleObjInit
+def ProxyfyPickeled( obj, pickleObjInit = None, visitor = None ):
+  """
+  This method return a proxy instance of pickled form of object given in input.
+
+  Args:
+  ----
+      obj (pickelable type) : object to be proxified
+      pickleObjInit (bytes) : Optionnal. Original pickeled form of object to be proxyfied if already computed. If not this method generate it
+
+  Returns
+  -------
+      BigObjectOnDiskBase: proxy instance
+  """
+  pickleObj = pickleObjInit
+  if pickleObj is None:
+    pickleObj = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
+  fileName = GetBigObjectFileName()
+  if visitor:
+    visitor.setHDDMem( len(pickleObj) )
+    visitor.setFileName(fileName)
+  if isinstance( obj, list):
+    proxyObj = BigObjectOnDiskList( len(obj), fileName, pickleObj )
+  elif isinstance( obj, tuple):
+    proxyObj = BigObjectOnDiskTuple( len(obj), fileName , pickleObj )
   else:
-    if isinstance( obj, list):
-      proxyObj = BigObjectOnDiskList( len(obj), GetBigObjectFileName() , pickleObjInit )
-    elif isinstance( obj, tuple):
-      proxyObj = BigObjectOnDiskTuple( len(obj), GetBigObjectFileName() , pickleObjInit )
+    proxyObj = BigObjectOnDisk( fileName , pickleObj )
+  return proxyObj
+
+def SpoolPickleObject( obj, visitor = None ):
+  import pickle
+  with InOutputObjVisitorCM(visitor) as v:
+    pickleObjInit = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
+    if not ActivateProxyMecanismOrNot( len(pickleObjInit) ):
+      return pickleObjInit
     else:
-      proxyObj = BigObjectOnDisk( GetBigObjectFileName() , pickleObjInit )
-    pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
-    return pickleProxy
+      proxyObj = ProxyfyPickeled( obj, pickleObjInit, v.visitor() )
+      pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
+      return pickleProxy
+
+from SALOME_ContainerHelper import InOutputObjVisitorCM, InOutputObjVisitor
 
-def UnProxyObjectSimple( obj ):
+def UnProxyObjectSimple( obj, visitor = None ):
   """
   Method to be called in Remote mode. Alterate the obj _status attribute. 
   Because the slave process does not participate in the reference counting
+  
+  Args:
+  ----
+      visitor (InOutputObjVisitor): A visitor to keep track of amount of memory on chip and those on HDD
+
   """
-  if isinstance(obj,BigObjectOnDiskBase):
-    obj.doNotTouchFile()
-    return obj.get()
-  elif isinstance( obj, list):
-    retObj = []
-    for elt in obj:
-      retObj.append( UnProxyObjectSimple(elt) )
-    return retObj
-  else:
-    return obj
+  with InOutputObjVisitorCM(visitor) as v:
+    logging.debug( "UnProxyObjectSimple {}".format(type(obj)) )
+    if isinstance(obj,BigObjectOnDiskBase):
+      obj.doNotTouchFile()
+      return obj.get( v )
+    elif isinstance( obj, list):
+      retObj = []
+      for elt in obj:
+        retObj.append( UnProxyObjectSimple(elt,v.visitor()) )
+      return retObj
+    else:
+      return obj
 
 def UnProxyObjectSimpleLocal( obj ):
   """
@@ -468,17 +524,19 @@ class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
 
   def executeFirst(self,argsin):
     """ Same than first part of self.execute to reduce memory peak."""
-    import time
     try:
       data = None
       self.my_container_py.addTimeInfoOnLevel2(self.getIDInContainer(),self._current_exec,"startInputTime")
       if True: # to force call of SeqByteReceiver's destructor
         argsInPy = SeqByteReceiver( argsin )
         data = argsInPy.data()
+        self.my_container_py.addInfoOnLevel2(self.getIDInContainer(),self._current_exec,"inputMem",len(data))
       _,kws=pickle.loads(data)
+      vis = InOutputObjVisitor()
       for elt in kws:
         # fetch real data if necessary
-        kws[elt] = UnProxyObjectSimple( kws[elt] )
+        kws[elt] = UnProxyObjectSimple( kws[elt],vis)
+      self.my_container_py.addInfoOnLevel2(self.getIDInContainer(),self._current_exec,"inputHDDMem",vis)
       self.context.update(kws)
       self.my_container_py.addTimeInfoOnLevel2(self.getIDInContainer(),self._current_exec,"endInputTime")
     except Exception:
@@ -501,13 +559,18 @@ class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
           raise KeyError("There is no variable %s in context" % arg)
         argsout.append(self.context[arg])
       ret = [ ]
+      outputMem = 0
+      vis = InOutputObjVisitor()
       for arg in argsout:
         # the proxy mecanism is catched here
-        argPickle = SpoolPickleObject( arg )
+        argPickle = SpoolPickleObject( arg, vis )
         retArg = SenderByte_i( self.poa,argPickle )
         id_o = self.poa.activate_object(retArg)
         retObj = self.poa.id_to_reference(id_o)
         ret.append( retObj._narrow( SALOME.SenderByte ) )
+        outputMem += len(argPickle)
+      self.my_container_py.addInfoOnLevel2(self.getIDInContainer(),self._current_exec,"outputMem",outputMem)
+      self.my_container_py.addInfoOnLevel2(self.getIDInContainer(),self._current_exec,"outputHDDMem",vis)
       self.my_container_py.addTimeInfoOnLevel2(self.getIDInContainer(),self._current_exec,"endOutputTime")
       return ret
     except Exception: