Salome HOME
[EDF29150] : log performance of python scripts run inside SALOME container + verbosit...
[modules/kernel.git] / src / Container / SALOME_PyNode.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 #  File   : SALOME_PyNode.py
22 #  Author : Christian CAREMOLI, EDF
23 #  Module : SALOME
24 #  $Header$
25 #
26 import sys,traceback
27 import linecache
28 import pickle
29 import Engines__POA
30 import SALOME__POA
31 import SALOME
32 import logging
33 import os
34 import sys
35 from SALOME_ContainerHelper import ScriptExecInfo
36
37 MY_CONTAINER_ENTRY_IN_GLBS = "my_container"
38
39 class Generic(SALOME__POA.GenericObj):
40   """A Python implementation of the GenericObj CORBA IDL"""
41   def __init__(self,poa):
42     self.poa=poa
43     self.cnt=1
44
45   def Register(self):
46     #print("Register called : %d"%self.cnt)
47     self.cnt+=1
48
49   def UnRegister(self):
50     #print("UnRegister called : %d"%self.cnt)
51     self.cnt-=1
52     if self.cnt <= 0:
53       oid=self.poa.servant_to_id(self)
54       self.poa.deactivate_object(oid)
55
56   def Destroy(self):
57     print("WARNING SALOME::GenericObj::Destroy() function is obsolete! Use UnRegister() instead.")
58     self.UnRegister()
59
60   def __del__(self):
61     #print("Destuctor called")
62     pass
63
64 class PyNode_i (Engines__POA.PyNode,Generic):
65   """The implementation of the PyNode CORBA IDL"""
66   def __init__(self, nodeName,code,poa,my_container):
67     """Initialize the node : compilation in the local context"""
68     Generic.__init__(self,poa)
69     self.nodeName=nodeName
70     self.code=code
71     self.my_container=my_container._container
72     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
73     ccode=compile(code,nodeName,'exec')
74     self.context={}
75     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
76     exec(ccode, self.context)
77
78   def getContainer(self):
79     return self.my_container
80
81   def getCode(self):
82     return self.code
83
84   def getName(self):
85     return self.nodeName
86
87   def defineNewCustomVar(self,varName,valueOfVar):
88     self.context[varName] = pickle.loads(valueOfVar)
89     pass
90
91   def executeAnotherPieceOfCode(self,code):
92     """Called for initialization of container lodging self."""
93     try:
94       ccode=compile(code,self.nodeName,'exec')
95       exec(ccode, self.context)
96     except Exception:
97       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
98
99   def execute(self,funcName,argsin):
100     """Execute the function funcName found in local context with pickled args (argsin)"""
101     try:
102       argsin,kws=pickle.loads(argsin)
103       func=self.context[funcName]
104       argsout=func(*argsin,**kws)
105       argsout=pickle.dumps(argsout,-1)
106       return argsout
107     except Exception:
108       exc_typ,exc_val,exc_fr=sys.exc_info()
109       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
110       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyNode: %s, function: %s" % (self.nodeName,funcName),0))
111
112 class SenderByte_i(SALOME__POA.SenderByte,Generic):
113   def __init__(self,poa,bytesToSend):
114     Generic.__init__(self,poa)
115     self.bytesToSend = bytesToSend
116
117   def getSize(self):
118     return len(self.bytesToSend)
119
120   def sendPart(self,n1,n2):
121     return self.bytesToSend[n1:n2]
122
123 SALOME_FILE_BIG_OBJ_DIR = "SALOME_FILE_BIG_OBJ_DIR"
124     
125 SALOME_BIG_OBJ_ON_DISK_THRES_VAR = "SALOME_BIG_OBJ_ON_DISK_THRES"
126
127 # default is 50 MB
128 SALOME_BIG_OBJ_ON_DISK_THRES_DFT = 50000000
129
130 from ctypes import c_int
131 TypeCounter = c_int
132
133 def GetSizeOfTCnt():
134   return len( bytes(TypeCounter(0) ) )
135
136 def GetSizeOfBufferedReader(f):
137   """
138   This method returns in bytes size of a file openned.
139
140   Args:
141   ----
142       f (io.IOBase): buffered reader returned by open
143       
144   Returns
145   -------
146       int: number of bytes
147   """
148   import io
149   pos = f.tell()
150   f.seek(0,io.SEEK_END)
151   pos2 = f.tell()
152   f.seek(pos,io.SEEK_SET)
153   return pos2-pos
154
155 def GetObjectFromFile(fname, visitor = None):
156   with open(fname,"rb") as f:
157     cntb = f.read( GetSizeOfTCnt() )
158     cnt = TypeCounter.from_buffer_copy( cntb ).value
159     if visitor:
160       visitor.setHDDMem( GetSizeOfBufferedReader(f) )
161       visitor.setFileName( fname )
162     obj = pickle.load(f)
163   return obj,cnt
164
165 def DumpInFile(obj,fname):
166   with open(fname,"wb") as f:
167     f.write( bytes( TypeCounter(1) ) )
168     f.write( obj )
169
170 def IncrRefInFile(fname):
171   with open(fname,"rb") as f:
172     cntb = f.read( GetSizeOfTCnt() )
173   cnt = TypeCounter.from_buffer_copy( cntb ).value
174   with open(fname,"rb+") as f:
175     #import KernelServices ; KernelServices.EntryForDebuggerBreakPoint()
176     f.write( bytes( TypeCounter(cnt+1) ) )
177
178 def DecrRefInFile(fname):
179   import os
180   with open(fname,"rb") as f:
181     cntb = f.read( GetSizeOfTCnt() )
182   cnt = TypeCounter.from_buffer_copy( cntb ).value
183   #
184   #import KernelServices ; KernelServices.EntryForDebuggerBreakPoint()
185   if cnt == 1:
186     os.unlink( fname )
187   else:
188     with open(fname,"rb+") as f:
189         f.write( bytes( TypeCounter(cnt-1) ) )
190
191 def GetBigObjectOnDiskThreshold():
192   import os
193   if SALOME_BIG_OBJ_ON_DISK_THRES_VAR in os.environ:
194     return int( os.environ[SALOME_BIG_OBJ_ON_DISK_THRES_VAR] )
195   else:
196     return SALOME_BIG_OBJ_ON_DISK_THRES_DFT
197
198 def ActivateProxyMecanismOrNot( sizeInByte ):
199   thres = GetBigObjectOnDiskThreshold()
200   if thres == -1:
201     return False
202   else:
203     return sizeInByte > thres
204
205 def GetBigObjectDirectory():
206   import os
207   if SALOME_FILE_BIG_OBJ_DIR not in os.environ:
208     raise RuntimeError("An object of size higher than limit detected and no directory specified to dump it in file !")
209   return os.path.expanduser( os.path.expandvars( os.environ[SALOME_FILE_BIG_OBJ_DIR] ) )
210
211 def GetBigObjectFileName():
212   """
213   Return a filename in the most secure manner (see tempfile documentation)
214   """
215   import tempfile
216   with tempfile.NamedTemporaryFile(dir=GetBigObjectDirectory(),prefix="mem_",suffix=".pckl") as f:
217     ret = f.name
218   return ret
219
220 class BigObjectOnDiskBase:
221   def __init__(self, fileName, objSerialized):
222     """
223     :param fileName: the file used to dump into.
224     :param objSerialized: the object in pickeled form
225     :type objSerialized: bytes
226     """
227     self._filename = fileName
228     # attribute _destroy is here to tell client side or server side
229     # only client side can be with _destroy set to True. server side due to risk of concurrency
230     # so pickled form of self must be done with this attribute set to False.
231     self._destroy = False
232     self.__dumpIntoFile(objSerialized)
233
234   def getDestroyStatus(self):
235     return self._destroy
236
237   def incrRef(self):
238     if self._destroy:
239       IncrRefInFile( self._filename )
240     else:
241       # should never happen !
242       RuntimeError("Invalid call to incrRef !")
243
244   def decrRef(self):
245     if self._destroy:
246       DecrRefInFile( self._filename )
247     else:
248       # should never happen !
249       RuntimeError("Invalid call to decrRef !")
250
251   def unlinkOnDestructor(self):
252     self._destroy = True
253
254   def doNotTouchFile(self):
255     """
256     Method called slave side. The life cycle management of file is client side not slave side.
257     """
258     self._destroy = False
259
260   def __del__(self):
261     if self._destroy:
262       DecrRefInFile( self._filename )
263
264   def getFileName(self):
265     return self._filename
266   
267   def __dumpIntoFile(self, objSerialized):
268     DumpInFile( objSerialized, self._filename )
269
270   def get(self, visitor = None):
271     obj, _ = GetObjectFromFile( self._filename, visitor )
272     return obj
273
274   def __float__(self):
275     return float( self.get() )
276     
277   def __int__(self):
278     return int( self.get() )
279     
280   def __str__(self):
281     obj = self.get()
282     if isinstance(obj,str):
283         return obj
284     else:
285         raise RuntimeError("Not a string")
286       
287 class BigObjectOnDisk(BigObjectOnDiskBase):
288   def __init__(self, fileName, objSerialized):
289     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
290     
291 class BigObjectOnDiskListElement(BigObjectOnDiskBase):
292   def __init__(self, pos, length, fileName):
293     self._filename = fileName
294     self._destroy = False
295     self._pos = pos
296     self._length = length
297
298   def get(self, visitor = None):
299     fullObj = BigObjectOnDiskBase.get(self, visitor)
300     return fullObj[ self._pos ]
301     
302   def __getitem__(self, i):
303     return self.get()[i]
304
305   def __len__(self):
306     return len(self.get())
307     
308 class BigObjectOnDiskSequence(BigObjectOnDiskBase):
309   def __init__(self, length, fileName, objSerialized):
310     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
311     self._length = length
312
313   def __getitem__(self, i):
314     return BigObjectOnDiskListElement(i, self._length, self.getFileName())
315
316   def __len__(self):
317     return self._length
318
319 class BigObjectOnDiskList(BigObjectOnDiskSequence):
320   def __init__(self, length, fileName, objSerialized):
321     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
322     
323 class BigObjectOnDiskTuple(BigObjectOnDiskSequence):
324   def __init__(self, length, fileName, objSerialized):
325     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
326
327 def ProxyfyPickeled( obj, pickleObjInit = None, visitor = None ):
328   """
329   This method return a proxy instance of pickled form of object given in input.
330
331   Args:
332   ----
333       obj (pickelable type) : object to be proxified
334       pickleObjInit (bytes) : Optionnal. Original pickeled form of object to be proxyfied if already computed. If not this method generate it
335
336   Returns
337   -------
338       BigObjectOnDiskBase: proxy instance
339   """
340   pickleObj = pickleObjInit
341   if pickleObj is None:
342     pickleObj = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
343   fileName = GetBigObjectFileName()
344   if visitor:
345     visitor.setHDDMem( len(pickleObj) )
346     visitor.setFileName(fileName)
347   if isinstance( obj, list):
348     proxyObj = BigObjectOnDiskList( len(obj), fileName, pickleObj )
349   elif isinstance( obj, tuple):
350     proxyObj = BigObjectOnDiskTuple( len(obj), fileName , pickleObj )
351   else:
352     proxyObj = BigObjectOnDisk( fileName , pickleObj )
353   return proxyObj
354
355 def SpoolPickleObject( obj, visitor = None ):
356   import pickle
357   with InOutputObjVisitorCM(visitor) as v:
358     pickleObjInit = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
359     if not ActivateProxyMecanismOrNot( len(pickleObjInit) ):
360       return pickleObjInit
361     else:
362       proxyObj = ProxyfyPickeled( obj, pickleObjInit, v.visitor() )
363       pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
364       return pickleProxy
365
366 from SALOME_ContainerHelper import InOutputObjVisitorCM, InOutputObjVisitor
367
368 def UnProxyObjectSimple( obj, visitor = None ):
369   """
370   Method to be called in Remote mode. Alterate the obj _status attribute. 
371   Because the slave process does not participate in the reference counting
372   
373   Args:
374   ----
375       visitor (InOutputObjVisitor): A visitor to keep track of amount of memory on chip and those on HDD
376
377   """
378   with InOutputObjVisitorCM(visitor) as v:
379     logging.debug( "UnProxyObjectSimple {}".format(type(obj)) )
380     if isinstance(obj,BigObjectOnDiskBase):
381       obj.doNotTouchFile()
382       return obj.get( v )
383     elif isinstance( obj, list):
384       retObj = []
385       for elt in obj:
386         retObj.append( UnProxyObjectSimple(elt,v.visitor()) )
387       return retObj
388     else:
389       return obj
390
391 def UnProxyObjectSimpleLocal( obj ):
392   """
393   Method to be called in Local mode. Do not alterate the PyObj counter
394   """
395   if isinstance(obj,BigObjectOnDiskBase):
396     return obj.get()
397   elif isinstance( obj, list):
398     retObj = []
399     for elt in obj:
400       retObj.append( UnProxyObjectSimpleLocal(elt) )
401     return retObj
402   else:
403     return obj
404   
405 class FileDeleter:
406   def __init__(self, fileName):
407     self._filename = fileName
408   @property
409   def filename(self):
410     return self._filename
411   def __del__(self):
412     import os
413     if os.path.exists( self._filename ):
414       os.unlink( self._filename )
415
416 def LaunchMonitoring( intervalInMs ):
417   """
418   Launch a subprocess monitoring self process.
419   This monitoring subprocess is a python process lauching every intervalInMs ms evaluation of
420   CPU usage and RSS memory.
421   Communication between subprocess and self is done by file.
422   """
423   import KernelBasis
424   def BuildPythonFileForCPUPercent( intervalInMs ):
425     import os
426     import tempfile
427     with tempfile.NamedTemporaryFile(prefix="htop_",suffix=".py") as f:
428       tempPyFile = f.name
429     tempOutFile = "{}.txt".format( os.path.splitext( tempPyFile )[0] )
430     pid = os.getpid()
431     with open(tempPyFile,"w") as f:
432       f.write("""import psutil
433 pid = {}
434 process = psutil.Process( pid )
435 import time
436 with open("{}","a") as f:
437   while True:
438     f.write( "{{}}\\n".format( str( process.cpu_percent() ) ) )
439     f.write( "{{}}\\n".format( str( process.memory_info().rss  ) ) )
440     f.flush()
441     time.sleep( {} / 1000.0 )
442 """.format(pid, tempOutFile, intervalInMs))
443     return FileDeleter(tempPyFile), FileDeleter(tempOutFile)
444   pyFileName, outFileName = BuildPythonFileForCPUPercent( intervalInMs )
445   KernelBasis.LaunchMonitoring(pyFileName.filename,outFileName.filename)
446   return pyFileName, outFileName
447
448 def StopMonitoring( ):
449   """
450   Retrieve data of monitoring and kill monitoring subprocess.
451   
452   Returns
453   -------
454     list<float,str> : list of pairs. First param of pair is CPU usage. Second param of pair is rss memory usage
455   """
456   import KernelBasis
457   ret = KernelBasis.StopMonitoring()
458   cpu = ret[::2]
459   mem_rss = [ int(elt) for elt in ret[1::2]]
460   return [(a,b) for a,b in zip(cpu,mem_rss)]
461
462 class SeqByteReceiver:
463   # 2GB limit to trigger split into chunks
464   CHUNK_SIZE = 2000000000
465   def __init__(self,sender):
466     self._obj = sender
467   def __del__(self):
468     self._obj.UnRegister()
469     pass
470   def data(self):
471     size = self._obj.getSize()
472     if size <= SeqByteReceiver.CHUNK_SIZE:
473       return self.fetchOneShot( size )
474     else:
475       return self.fetchByChunks( size )
476   def fetchOneShot(self,size):
477     return self._obj.sendPart(0,size)
478   def fetchByChunks(self,size):
479       """
480       To avoid memory peak parts over 2GB are sent using EFF_CHUNK_SIZE size.
481       """
482       data_for_split_case = bytes(0)
483       EFF_CHUNK_SIZE = SeqByteReceiver.CHUNK_SIZE // 8
484       iStart = 0 ; iEnd = EFF_CHUNK_SIZE
485       while iStart!=iEnd and iEnd <= size:
486         part = self._obj.sendPart(iStart,iEnd)
487         data_for_split_case = bytes(0).join( [data_for_split_case,part] )
488         iStart = iEnd; iEnd = min(iStart + EFF_CHUNK_SIZE,size)
489       return data_for_split_case
490
491 class LogOfCurrentExecutionSession:
492   def __init__(self, handleToCentralizedInst):
493     self._remote_handle = handleToCentralizedInst
494     self._current_instance = ScriptExecInfo()
495
496   def addInfoOnLevel2(self, key, value):
497     setattr(self._current_instance,key,value)
498
499   def finalizeAndPushToMaster(self):
500     self._remote_handle.assign( pickle.dumps( self._current_instance ) )
501
502 class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
503   """The implementation of the PyScriptNode CORBA IDL that executes a script"""
504   def __init__(self, nodeName,code,poa,my_container,logscript):
505     """Initialize the node : compilation in the local context"""
506     Generic.__init__(self,poa)
507     self.nodeName=nodeName
508     self.code=code
509     self.my_container_py = my_container
510     self.my_container=my_container._container
511     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
512     self.ccode=compile(code,nodeName,'exec')
513     self.context={}
514     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
515     self._log_script = logscript
516     self._current_execution_session = None
517     sys.stdout.flush() ; sys.stderr.flush() # flush to correctly capture log per execution session
518       
519   def __del__(self):
520     # force removal of self.context. Don t know why it s not done by default
521     self.removeAllVarsInContext()
522     pass
523
524   def getContainer(self):
525     return self.my_container
526
527   def getCode(self):
528     return self.code
529
530   def getName(self):
531     return self.nodeName
532
533   def defineNewCustomVar(self,varName,valueOfVar):
534     self.context[varName] = pickle.loads(valueOfVar)
535     pass
536
537   def executeAnotherPieceOfCode(self,code):
538     """Called for initialization of container lodging self."""
539     try:
540       ccode=compile(code,self.nodeName,'exec')
541       exec(ccode, self.context)
542     except Exception:
543       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
544
545   def assignNewCompiledCode(self,codeStr):
546     try:
547       self.code=codeStr
548       self.ccode=compile(codeStr,self.nodeName,'exec')
549     except Exception:
550       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode.assignNewCompiledCode (%s) : code to be executed \"%s\"" %(self.nodeName,codeStr),0))
551
552   def executeSimple(self, key, val):
553     """
554     Same as execute method except that no pickelization mecanism is implied here. No output is expected
555     """
556     try:
557       self.context.update({ "env" : [(k,v) for k,v in zip(key,val)]})
558       exec(self.ccode,self.context)
559     except Exception:
560       exc_typ,exc_val,exc_fr=sys.exc_info()
561       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
562       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
563       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" % (self.nodeName),0))
564     
565   def execute(self,outargsname,argsin):
566     """Execute the script stored in attribute ccode with pickled args (argsin)"""
567     try:
568       argsname,kws=pickle.loads(argsin)
569       self.context.update(kws)
570       exec(self.ccode, self.context)
571       argsout=[]
572       for arg in outargsname:
573         if arg not in self.context:
574           raise KeyError("There is no variable %s in context" % arg)
575         argsout.append(self.context[arg])
576       argsout=pickle.dumps(tuple(argsout),-1)
577       return argsout
578     except Exception:
579       exc_typ,exc_val,exc_fr=sys.exc_info()
580       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
581       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
582       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s, outargsname: %s" % (self.nodeName,outargsname),0))
583
584   def executeFirst(self,argsin):
585     """ Same than first part of self.execute to reduce memory peak."""
586     try:
587       self.beginOfCurrentExecutionSession()
588       data = None
589       self.addTimeInfoOnLevel2("startInputTime")
590       if True: # to force call of SeqByteReceiver's destructor
591         argsInPy = SeqByteReceiver( argsin )
592         data = argsInPy.data()
593         self.addInfoOnLevel2("inputMem",len(data))
594       _,kws=pickle.loads(data)
595       vis = InOutputObjVisitor()
596       for elt in kws:
597         # fetch real data if necessary
598         kws[elt] = UnProxyObjectSimple( kws[elt],vis)
599       self.addInfoOnLevel2("inputHDDMem",vis)
600       self.context.update(kws)
601       self.addTimeInfoOnLevel2("endInputTime")
602     except Exception:
603       exc_typ,exc_val,exc_fr=sys.exc_info()
604       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
605       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
606       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:First %s" % (self.nodeName),0))
607
608   def executeSecond(self,outargsname):
609     """ Same than second part of self.execute to reduce memory peak."""
610     import sys
611     try:
612       self.addTimeInfoOnLevel2("startExecTime")
613       ##
614       self.addInfoOnLevel2("measureTimeResolution",self.my_container_py.monitoringtimeresms())
615       monitoringParams = LaunchMonitoring( self.my_container_py.monitoringtimeresms() )
616       exec(self.ccode, self.context)
617       cpumeminfo = StopMonitoring( )
618       ##
619       self.addInfoOnLevel2("CPUMemDuringExec",cpumeminfo)
620       del monitoringParams
621       self.addTimeInfoOnLevel2("endExecTime")
622       self.addTimeInfoOnLevel2("startOutputTime")
623       argsout=[]
624       for arg in outargsname:
625         if arg not in self.context:
626           raise KeyError("There is no variable %s in context" % arg)
627         argsout.append(self.context[arg])
628       ret = [ ]
629       outputMem = 0
630       vis = InOutputObjVisitor()
631       for arg in argsout:
632         # the proxy mecanism is catched here
633         argPickle = SpoolPickleObject( arg, vis )
634         retArg = SenderByte_i( self.poa,argPickle )
635         id_o = self.poa.activate_object(retArg)
636         retObj = self.poa.id_to_reference(id_o)
637         ret.append( retObj._narrow( SALOME.SenderByte ) )
638         outputMem += len(argPickle)
639       self.addInfoOnLevel2("outputMem",outputMem)
640       self.addInfoOnLevel2("outputHDDMem",vis)
641       self.addTimeInfoOnLevel2("endOutputTime")
642       self.endOfCurrentExecutionSession()
643       return ret
644     except Exception:
645       exc_typ,exc_val,exc_fr=sys.exc_info()
646       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
647       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
648       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:Second %s, outargsname: %s" % (self.nodeName,outargsname),0))
649
650   def listAllVarsInContext(self):
651       import re
652       pat = re.compile("^__([a-z]+)__$")
653       return [elt for elt in self.context if not pat.match(elt) and elt != MY_CONTAINER_ENTRY_IN_GLBS]
654       
655   def removeAllVarsInContext(self):
656       for elt in self.listAllVarsInContext():
657         del self.context[elt]
658
659   def getValueOfVarInContext(self,varName):
660     try:
661       return pickle.dumps(self.context[varName],-1)
662     except Exception:
663       exc_typ,exc_val,exc_fr=sys.exc_info()
664       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
665       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
666     pass
667   
668   def assignVarInContext(self, varName, value):
669     try:
670       self.context[varName][0] = pickle.loads(value)
671     except Exception:
672       exc_typ,exc_val,exc_fr=sys.exc_info()
673       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
674       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
675     pass
676
677   def callMethodOnVarInContext(self, varName, methodName, args):
678     try:
679       return pickle.dumps( getattr(self.context[varName][0],methodName)(*pickle.loads(args)),-1 )
680     except Exception:
681       exc_typ,exc_val,exc_fr=sys.exc_info()
682       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
683       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
684     pass
685
686   def beginOfCurrentExecutionSession(self):
687     self._current_execution_session = LogOfCurrentExecutionSession( self._log_script.addExecutionSession() )
688   
689   def endOfCurrentExecutionSession(self):
690     self._current_execution_session.finalizeAndPushToMaster()
691     self._current_execution_session = None
692
693   def addInfoOnLevel2(self, key, value):
694     self._current_execution_session.addInfoOnLevel2(key, value)
695       
696   def addTimeInfoOnLevel2(self, key):
697     from datetime import datetime
698     self._current_execution_session.addInfoOnLevel2(key,datetime.now())