Salome HOME
24d9df82e9ba0489e9f886cd4bf3242116a09afb
[modules/kernel.git] / src / Container / SALOME_PyNode.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2024  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 MY_PERFORMANCE_LOG_ENTRY_IN_GLBS = "my_log_4_this_session"
40
41 class Generic(SALOME__POA.GenericObj):
42   """A Python implementation of the GenericObj CORBA IDL"""
43   def __init__(self,poa):
44     self.poa=poa
45     self.cnt=1
46
47   def Register(self):
48     #print("Register called : %d"%self.cnt)
49     self.cnt+=1
50
51   def UnRegister(self):
52     #print("UnRegister called : %d"%self.cnt)
53     self.cnt-=1
54     if self.cnt <= 0:
55       oid=self.poa.servant_to_id(self)
56       self.poa.deactivate_object(oid)
57
58   def Destroy(self):
59     print("WARNING SALOME::GenericObj::Destroy() function is obsolete! Use UnRegister() instead.")
60     self.UnRegister()
61
62   def __del__(self):
63     #print("Destuctor called")
64     pass
65
66 class PyNode_i (Engines__POA.PyNode,Generic):
67   """The implementation of the PyNode CORBA IDL"""
68   def __init__(self, nodeName,code,poa,my_container):
69     """Initialize the node : compilation in the local context"""
70     Generic.__init__(self,poa)
71     self.nodeName=nodeName
72     self.code=code
73     self.my_container=my_container._container
74     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
75     ccode=compile(code,nodeName,'exec')
76     self.context={}
77     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
78     exec(ccode, self.context)
79
80   def getContainer(self):
81     return self.my_container
82
83   def getCode(self):
84     return self.code
85
86   def getName(self):
87     return self.nodeName
88
89   def defineNewCustomVar(self,varName,valueOfVar):
90     self.context[varName] = pickle.loads(valueOfVar)
91     pass
92
93   def executeAnotherPieceOfCode(self,code):
94     """Called for initialization of container lodging self."""
95     try:
96       ccode=compile(code,self.nodeName,'exec')
97       exec(ccode, self.context)
98     except Exception:
99       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
100
101   def execute(self,funcName,argsin):
102     """Execute the function funcName found in local context with pickled args (argsin)"""
103     try:
104       argsin,kws=pickle.loads(argsin)
105       func=self.context[funcName]
106       argsout=func(*argsin,**kws)
107       argsout=pickle.dumps(argsout,-1)
108       return argsout
109     except Exception:
110       exc_typ,exc_val,exc_fr=sys.exc_info()
111       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
112       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyNode: %s, function: %s" % (self.nodeName,funcName),0))
113
114 class SenderByte_i(SALOME__POA.SenderByte,Generic):
115   def __init__(self,poa,bytesToSend):
116     Generic.__init__(self,poa)
117     self.bytesToSend = bytesToSend
118
119   def getSize(self):
120     return len(self.bytesToSend)
121
122   def sendPart(self,n1,n2):
123     return self.bytesToSend[n1:n2]
124
125 SALOME_FILE_BIG_OBJ_DIR = "SALOME_FILE_BIG_OBJ_DIR"
126     
127 SALOME_BIG_OBJ_ON_DISK_THRES_VAR = "SALOME_BIG_OBJ_ON_DISK_THRES"
128
129 # default is 50 MB
130 SALOME_BIG_OBJ_ON_DISK_THRES_DFT = 50000000
131
132 DicoForProxyFile = { }
133
134 def GetSizeOfBufferedReader(f):
135   """
136   This method returns in bytes size of a file openned.
137
138   Args:
139   ----
140       f (io.IOBase): buffered reader returned by open
141       
142   Returns
143   -------
144       int: number of bytes
145   """
146   import io
147   pos = f.tell()
148   f.seek(0,io.SEEK_END)
149   pos2 = f.tell()
150   f.seek(pos,io.SEEK_SET)
151   return pos2-pos
152
153 def GetObjectFromFile(fname, visitor = None):
154   with open(fname,"rb") as f:
155     if visitor:
156       visitor.setHDDMem( GetSizeOfBufferedReader(f) )
157       visitor.setFileName( fname )
158     obj = pickle.load(f)
159   return obj
160
161 def DumpInFile(obj,fname):
162   with open(fname,"wb") as f:
163     f.write( obj )
164
165 def IncrRefInFile(fname):
166   if fname in DicoForProxyFile:
167     DicoForProxyFile[fname] += 1
168   else:
169     DicoForProxyFile[fname] = 2
170   pass
171
172 def DecrRefInFile(fname):
173   if fname not in DicoForProxyFile:
174     cnt = 1
175   else:
176     cnt = DicoForProxyFile[fname]
177     DicoForProxyFile[fname] -= 1
178     if cnt == 1:
179       del DicoForProxyFile[fname]
180   if cnt == 1:
181     if os.path.exists(fname):
182       os.unlink( fname )
183   pass
184
185 def GetBigObjectOnDiskThreshold():
186   import os
187   if SALOME_BIG_OBJ_ON_DISK_THRES_VAR in os.environ:
188     return int( os.environ[SALOME_BIG_OBJ_ON_DISK_THRES_VAR] )
189   else:
190     return SALOME_BIG_OBJ_ON_DISK_THRES_DFT
191
192 def ActivateProxyMecanismOrNot( sizeInByte ):
193   thres = GetBigObjectOnDiskThreshold()
194   if thres == -1:
195     return False
196   else:
197     return sizeInByte > thres
198
199 def GetBigObjectDirectory():
200   import os
201   if SALOME_FILE_BIG_OBJ_DIR not in os.environ:
202     raise RuntimeError("An object of size higher than limit detected and no directory specified to dump it in file !")
203   return os.path.expanduser( os.path.expandvars( os.environ[SALOME_FILE_BIG_OBJ_DIR] ) )
204
205 def GetBigObjectFileName():
206   """
207   Return a filename in the most secure manner (see tempfile documentation)
208   """
209   import tempfile
210   with tempfile.NamedTemporaryFile(dir=GetBigObjectDirectory(),prefix="mem_",suffix=".pckl") as f:
211     ret = f.name
212   return ret
213
214 class BigObjectOnDiskBase:
215   def __init__(self, fileName, objSerialized):
216     """
217     :param fileName: the file used to dump into.
218     :param objSerialized: the object in pickeled form
219     :type objSerialized: bytes
220     """
221     self._filename = fileName
222     # attribute _destroy is here to tell client side or server side
223     # only client side can be with _destroy set to True. server side due to risk of concurrency
224     # so pickled form of self must be done with this attribute set to False.
225     self._destroy = False
226     self.__dumpIntoFile(objSerialized)
227
228   def getDestroyStatus(self):
229     return self._destroy
230
231   def incrRef(self):
232     if self._destroy:
233       IncrRefInFile( self._filename )
234     else:
235       # should never happen !
236       RuntimeError("Invalid call to incrRef !")
237
238   def decrRef(self):
239     if self._destroy:
240       DecrRefInFile( self._filename )
241     else:
242       # should never happen !
243       RuntimeError("Invalid call to decrRef !")
244
245   def unlinkOnDestructor(self):
246     self._destroy = True
247
248   def doNotTouchFile(self):
249     """
250     Method called slave side. The life cycle management of file is client side not slave side.
251     """
252     self._destroy = False
253
254   def __del__(self):
255     if self._destroy:
256       DecrRefInFile( self._filename )
257
258   def getFileName(self):
259     return self._filename
260   
261   def __dumpIntoFile(self, objSerialized):
262     DumpInFile( objSerialized, self._filename )
263
264   def get(self, visitor = None):
265     obj = GetObjectFromFile( self._filename, visitor )
266     return obj
267
268   def __float__(self):
269     return float( self.get() )
270     
271   def __int__(self):
272     return int( self.get() )
273     
274   def __str__(self):
275     obj = self.get()
276     if isinstance(obj,str):
277         return obj
278     else:
279         raise RuntimeError("Not a string")
280       
281 class BigObjectOnDisk(BigObjectOnDiskBase):
282   def __init__(self, fileName, objSerialized):
283     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
284     
285 class BigObjectOnDiskListElement(BigObjectOnDiskBase):
286   def __init__(self, pos, length, fileName):
287     self._filename = fileName
288     self._destroy = False
289     self._pos = pos
290     self._length = length
291
292   def get(self, visitor = None):
293     fullObj = BigObjectOnDiskBase.get(self, visitor)
294     return fullObj[ self._pos ]
295     
296   def __getitem__(self, i):
297     return self.get()[i]
298
299   def __len__(self):
300     return len(self.get())
301     
302 class BigObjectOnDiskSequence(BigObjectOnDiskBase):
303   def __init__(self, length, fileName, objSerialized):
304     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
305     self._length = length
306
307   def __getitem__(self, i):
308     return BigObjectOnDiskListElement(i, self._length, self.getFileName())
309
310   def __len__(self):
311     return self._length
312
313 class BigObjectOnDiskList(BigObjectOnDiskSequence):
314   def __init__(self, length, fileName, objSerialized):
315     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
316     
317 class BigObjectOnDiskTuple(BigObjectOnDiskSequence):
318   def __init__(self, length, fileName, objSerialized):
319     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
320
321 def ProxyfyPickeled( obj, pickleObjInit = None, visitor = None ):
322   """
323   This method return a proxy instance of pickled form of object given in input.
324
325   Args:
326   ----
327       obj (pickelable type) : object to be proxified
328       pickleObjInit (bytes) : Optionnal. Original pickeled form of object to be proxyfied if already computed. If not this method generate it
329
330   Returns
331   -------
332       BigObjectOnDiskBase: proxy instance
333   """
334   pickleObj = pickleObjInit
335   if pickleObj is None:
336     pickleObj = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
337   fileName = GetBigObjectFileName()
338   if visitor:
339     visitor.setHDDMem( len(pickleObj) )
340     visitor.setFileName(fileName)
341   if isinstance( obj, list):
342     proxyObj = BigObjectOnDiskList( len(obj), fileName, pickleObj )
343   elif isinstance( obj, tuple):
344     proxyObj = BigObjectOnDiskTuple( len(obj), fileName , pickleObj )
345   else:
346     proxyObj = BigObjectOnDisk( fileName , pickleObj )
347   return proxyObj
348
349 def SpoolPickleObject( obj, visitor = None ):
350   import pickle
351   with InOutputObjVisitorCM(visitor) as v:
352     pickleObjInit = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
353     if not ActivateProxyMecanismOrNot( len(pickleObjInit) ):
354       return pickleObjInit
355     else:
356       proxyObj = ProxyfyPickeled( obj, pickleObjInit, v.visitor() )
357       pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
358       return pickleProxy
359
360 from SALOME_ContainerHelper import InOutputObjVisitorCM, InOutputObjVisitor
361
362 def UnProxyObjectSimple( obj, visitor = None ):
363   """
364   Method to be called in Remote mode. Alterate the obj _status attribute. 
365   Because the slave process does not participate in the reference counting
366   
367   Args:
368   ----
369       visitor (InOutputObjVisitor): A visitor to keep track of amount of memory on chip and those on HDD
370
371   """
372   with InOutputObjVisitorCM(visitor) as v:
373     logging.debug( "UnProxyObjectSimple {}".format(type(obj)) )
374     if isinstance(obj,BigObjectOnDiskBase):
375       obj.doNotTouchFile()
376       return obj.get( v )
377     elif isinstance( obj, list):
378       retObj = []
379       for elt in obj:
380         retObj.append( UnProxyObjectSimple(elt,v.visitor()) )
381       return retObj
382     else:
383       return obj
384
385 def UnProxyObjectSimpleLocal( obj ):
386   """
387   Method to be called in Local mode. Do not alterate the PyObj counter
388   """
389   if isinstance(obj,BigObjectOnDiskBase):
390     return obj.get()
391   elif isinstance( obj, list):
392     retObj = []
393     for elt in obj:
394       retObj.append( UnProxyObjectSimpleLocal(elt) )
395     return retObj
396   else:
397     return obj
398   
399 class FileHolder:
400   def __init__(self, fileName):
401     self._filename = fileName
402   @property
403   def filename(self):
404     return self._filename
405   
406 class FileDeleter(FileHolder):
407   def __init__(self, fileName):
408     super().__init__( fileName )
409   def __del__(self):
410     import os
411     if os.path.exists( self._filename ):
412       os.unlink( self._filename )
413
414 class MonitoringInfo:
415   def __init__(self, pyFileName, intervalInMs, outFileName, pid):
416     self._py_file_name = pyFileName
417     self._interval_in_ms = intervalInMs
418     self._out_file_name = outFileName
419     self._pid = pid
420
421   @property
422   def pyFileName(self):
423     return self._py_file_name
424
425   @property
426   def pid(self):
427     return self._pid
428   
429   @pid.setter
430   def pid(self, value):
431     self._pid = value
432
433   @property
434   def outFileName(self):
435     return self._out_file_name
436   
437   @property
438   def intervalInMs(self):
439     return self._interval_in_ms
440   
441 def FileSystemMonitoring(intervalInMs, dirNameToInspect, outFileName = None):
442     """
443     This method loops indefinitely every intervalInMs milliseconds to scan 
444     number of inodes and size of content recursively included into the in input directory.
445
446     Args:
447     ----
448
449     outFileName (str) : name of file inside the results will be written. If None a new file is generated
450
451     See also CPUMemoryMonitoring
452     """
453     global orb
454     import os
455     dirNameToInspect2 = os.path.abspath( os.path.expanduser(dirNameToInspect) )
456     import tempfile
457     import logging
458     import KernelBasis
459     # outFileNameSave stores the content of outFileName during phase of dumping
460     with tempfile.NamedTemporaryFile(prefix="fs_monitor_",suffix=".txt") as f:
461       outFileNameSave = f.name
462     with tempfile.NamedTemporaryFile(prefix="fs_monitor_",suffix=".py") as f:
463       tempPyFile = f.name
464     tempOutFile = outFileName
465     if tempOutFile is None:
466       tempOutFile = "{}.txt".format( os.path.splitext( tempPyFile )[0] )
467     with open(tempPyFile,"w") as f:
468         f.write("""
469 import subprocess as sp
470 import re
471 import os
472 import time
473 import datetime
474 with open("{tempOutFile}","a") as f:
475   f.write( "{{}}\\n".format( "{dirNameToInspect2}" ) )
476   f.write( "{{}}\\n".format( "{intervalInMs}" ) )
477   while(True):
478     nbinodes = sp.check_output("{{}} | wc -l".format( " ".join(["find","{dirNameToInspect2}"]),  ), shell = True).decode().strip()
479     szOfDirStr = re.split("[\s]+",sp.check_output(["du","-sh","{dirNameToInspect2}"]).decode())[0]
480     f.write( "{{}}\\n".format( str( datetime.datetime.now().timestamp() ) ) )
481     f.write( "{{}}\\n".format( str( nbinodes  ) ) )
482     f.write( "{{}}\\n".format( str( szOfDirStr ) ) )
483     f.flush()
484     time.sleep( {intervalInMs} / 1000.0 )
485 """.format( **locals()))
486     logging.debug( "File for FS monitoring dump file : {}".format(tempPyFile) )
487     pyFileName = FileDeleter( tempPyFile )
488     if outFileName is None:
489       outFileName = FileDeleter( tempOutFile )
490     else:
491       outFileName = FileHolder(outFileName)
492     return MonitoringInfo(pyFileName, intervalInMs, outFileName, None)
493
494 def CPUMemoryMonitoring( intervalInMs, outFileName = None ):
495   """
496   Launch a subprocess monitoring self process.
497   This monitoring subprocess is a python process lauching every intervalInMs ms evaluation of
498   CPU usage and RSS memory of the calling process.
499   Communication between subprocess and self is done by file.
500
501   Args:
502   ----
503     outFileName (str) : name of file inside the results will be written. If None a new file is generated
504
505   See also FileSystemMonitoring
506   """
507   import KernelBasis
508   def BuildPythonFileForCPUPercent( intervalInMs, outFileName):
509     import os
510     import tempfile
511     with tempfile.NamedTemporaryFile(prefix="cpu_mem_monitor_",suffix=".py") as f:
512       tempPyFile = f.name
513     tempOutFile = outFileName
514     if tempOutFile is None:
515       tempOutFile = "{}.txt".format( os.path.splitext( tempPyFile )[0] )
516     pid = os.getpid()
517     with open(tempPyFile,"w") as f:
518       f.write("""import psutil
519 pid = {}
520 process = psutil.Process( pid )
521 import time
522 with open("{}","a") as f:
523   f.write( "{{}}\\n".format( "{}" ) )
524   while True:
525     f.write( "{{}}\\n".format( str( process.cpu_percent() ) ) )
526     f.write( "{{}}\\n".format( str( process.memory_info().rss  ) ) )
527     f.flush()
528     time.sleep( {} / 1000.0 )
529 """.format(pid, tempOutFile, intervalInMs, intervalInMs))
530     if outFileName is None:
531       autoOutFile = FileDeleter(tempOutFile)
532     else:
533       autoOutFile = FileHolder(tempOutFile)
534     return FileDeleter(tempPyFile),autoOutFile
535   pyFileName, outFileName = BuildPythonFileForCPUPercent( intervalInMs, outFileName )
536   return MonitoringInfo(pyFileName, intervalInMs, outFileName, None)
537
538 class GenericPythonMonitoringLauncherCtxMgr:
539     def __init__(self, monitoringParams):
540         """
541         Args:
542         ----
543             monitoringParams (MonitoringInfo)
544         """
545         self._monitoring_params = monitoringParams
546     def __enter__(self):
547         import KernelBasis
548         pid = KernelBasis.LaunchMonitoring(self._monitoring_params.pyFileName.filename)
549         self._monitoring_params.pid = pid
550         return self._monitoring_params
551     
552     def __exit__(self,exctype, exc, tb):
553         StopMonitoring( self._monitoring_params )
554
555 def StopMonitoring( monitoringInfo ):
556   """
557   Kill monitoring subprocess.
558
559   Args:
560   ----
561       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
562   """
563   import KernelBasis
564   KernelBasis.StopMonitoring(monitoringInfo.pid)
565
566 class CPUMemInfo:
567   def __init__(self, intervalInMs, cpu, mem_rss):
568     """
569     Args:
570     ----
571     intervalInMs (int)
572     cpu (list<float>)  CPU usage
573     mem_rss (list<int>) rss memory usage
574     """
575     self._interval_in_ms = intervalInMs
576     self._data = [(a,b) for a,b in zip(cpu,mem_rss)]
577   def __str__(self):
578     st = """Interval in ms : {self.intervalInMs}
579 Data : ${self.data}
580 """.format( **locals() )
581     return st
582   @property
583   def intervalInMs(self):
584     return self._interval_in_ms
585   @property
586   def data(self):
587     """
588     list of triplets. First param of pair is cpu usage 
589                       Second param of pair is memory usage
590     """
591     return self._data
592
593 def ReadCPUMemInfoInternal( fileName ):
594   intervalInMs = 0
595   cpu = [] ; mem_rss = []
596   if os.path.exists( fileName ):
597     with open(fileName, "r") as f:
598       coarseData = [ elt.strip() for elt in f.readlines() ]
599     intervalInMs = int( coarseData[0] )
600     coarseData = coarseData[1:]
601     cpu = [float(elt) for elt in coarseData[::2]]
602     mem_rss = [ int(elt) for elt in coarseData[1::2]]
603   return CPUMemInfo(intervalInMs,cpu,mem_rss)
604
605 def ReadCPUMemInfo( monitoringInfo ):
606   """
607   Retrieve CPU/Mem data of monitoring.
608
609   Args:
610   ----
611       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
612   
613   Returns
614   -------
615     CPUMemInfo instance
616   """
617   return ReadCPUMemInfoInternal( monitoringInfo.outFileName.filename )
618
619 class InodeSizeInfo:
620   def __init__(self, dirNameMonitored, intervalInMs, timeStamps, nbInodes, volumeOfDir):
621     """
622     Args:
623     ----
624     timeStamps (list<datetimestruct>)
625     nbInodes (list<int>)
626     volumeOfDir (list<str>)
627     """
628     self._dir_name_monitored = dirNameMonitored
629     self._interval_in_ms = intervalInMs
630     self._data = [(t,a,b) for t,a,b in zip(timeStamps,nbInodes,volumeOfDir)]
631   def __str__(self):
632     st = """Filename monitored : {self.dirNameMonitored}
633 Interval in ms : ${self.intervalInMs}
634 Data : ${self.data}
635 """.format( **locals() )
636     return st
637   @property
638   def dirNameMonitored(self):
639     return self._dir_name_monitored
640   @property
641   def intervalInMs(self):
642     return self._interval_in_ms
643   @property
644   def data(self):
645     """
646     list of triplets. First param of triplet is datetimestruct
647                                       Second param of triplet is #inodes.
648                                       Thirst param of triplet is size.
649     """
650     return self._data
651
652 def ReadInodeSizeInfoInternal( fileName ):
653   import datetime
654   import os
655   with open(fileName, "r") as f:
656     coarseData = [ elt.strip() for elt in f.readlines() ]
657   dirNameMonitored = coarseData[0] ; intervalInMs = int( coarseData[1] ) ; coarseData = coarseData[2:]
658   tss = [ datetime.datetime.fromtimestamp( float(elt) ) for elt in coarseData[::3] ]
659   nbInodes = [int(elt) for elt in coarseData[1::3]]
660   volumeOfDir = coarseData[2::3]
661   return InodeSizeInfo(dirNameMonitored,intervalInMs,tss,nbInodes,volumeOfDir)
662
663 def ReadInodeSizeInfo( monitoringInfo ):
664   """
665   Retrieve nb of inodes and size of monitoring
666
667   Args:
668   ----
669       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
670
671   Returns
672   -------
673     InodeSizeInfo
674   """
675   return ReadInodeSizeInfoInternal( monitoringInfo.outFileName.filename )
676
677 class SeqByteReceiver:
678   # 2GB limit to trigger split into chunks
679   CHUNK_SIZE = 2000000000
680   def __init__(self,sender):
681     self._obj = sender
682   def __del__(self):
683     self._obj.UnRegister()
684     pass
685   def data(self):
686     size = self._obj.getSize()
687     if size <= SeqByteReceiver.CHUNK_SIZE:
688       return self.fetchOneShot( size )
689     else:
690       return self.fetchByChunks( size )
691   def fetchOneShot(self,size):
692     return self._obj.sendPart(0,size)
693   def fetchByChunks(self,size):
694       """
695       To avoid memory peak parts over 2GB are sent using EFF_CHUNK_SIZE size.
696       """
697       data_for_split_case = bytes(0)
698       EFF_CHUNK_SIZE = SeqByteReceiver.CHUNK_SIZE // 8
699       iStart = 0 ; iEnd = EFF_CHUNK_SIZE
700       while iStart!=iEnd and iEnd <= size:
701         part = self._obj.sendPart(iStart,iEnd)
702         data_for_split_case = bytes(0).join( [data_for_split_case,part] )
703         iStart = iEnd; iEnd = min(iStart + EFF_CHUNK_SIZE,size)
704       return data_for_split_case
705
706 class LogOfCurrentExecutionSession:
707   def __init__(self, handleToCentralizedInst):
708     self._remote_handle = handleToCentralizedInst
709     self._current_instance = ScriptExecInfo()
710
711   def addFreestyleAndFlush(self, value):
712     self._current_instance.freestyle = value
713     self.finalizeAndPushToMaster()
714
715   def addInfoOnLevel2(self, key, value):
716     setattr(self._current_instance,key,value)
717
718   def finalizeAndPushToMaster(self):
719     self._remote_handle.assign( pickle.dumps( self._current_instance ) )
720
721 class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
722   """The implementation of the PyScriptNode CORBA IDL that executes a script"""
723   def __init__(self, nodeName,code,poa,my_container,logscript):
724     """Initialize the node : compilation in the local context"""
725     Generic.__init__(self,poa)
726     self.nodeName=nodeName
727     self.code=code
728     self.my_container_py = my_container
729     self.my_container=my_container._container
730     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
731     self.ccode=compile(code,nodeName,'exec')
732     self.context={}
733     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
734     self._log_script = logscript
735     self._current_execution_session = None
736     sys.stdout.flush() ; sys.stderr.flush() # flush to correctly capture log per execution session
737       
738   def __del__(self):
739     # force removal of self.context. Don t know why it s not done by default
740     self.removeAllVarsInContext()
741     pass
742
743   def getContainer(self):
744     return self.my_container
745
746   def getCode(self):
747     return self.code
748
749   def getName(self):
750     return self.nodeName
751
752   def defineNewCustomVar(self,varName,valueOfVar):
753     self.context[varName] = pickle.loads(valueOfVar)
754     pass
755
756   def executeAnotherPieceOfCode(self,code):
757     """Called for initialization of container lodging self."""
758     try:
759       ccode=compile(code,self.nodeName,'exec')
760       exec(ccode, self.context)
761     except Exception:
762       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
763
764   def assignNewCompiledCode(self,codeStr):
765     try:
766       self.code=codeStr
767       self.ccode=compile(codeStr,self.nodeName,'exec')
768     except Exception:
769       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode.assignNewCompiledCode (%s) : code to be executed \"%s\"" %(self.nodeName,codeStr),0))
770
771   def executeSimple(self, key, val):
772     """
773     Same as execute method except that no pickelization mecanism is implied here. No output is expected
774     """
775     try:
776       self.context.update({ "env" : [(k,v) for k,v in zip(key,val)]})
777       exec(self.ccode,self.context)
778     except Exception:
779       exc_typ,exc_val,exc_fr=sys.exc_info()
780       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
781       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
782       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" % (self.nodeName),0))
783     
784   def execute(self,outargsname,argsin):
785     """Execute the script stored in attribute ccode with pickled args (argsin)"""
786     try:
787       argsname,kws=pickle.loads(argsin)
788       self.context.update(kws)
789       exec(self.ccode, self.context)
790       argsout=[]
791       for arg in outargsname:
792         if arg not in self.context:
793           raise KeyError("There is no variable %s in context" % arg)
794         argsout.append(self.context[arg])
795       argsout=pickle.dumps(tuple(argsout),-1)
796       return argsout
797     except Exception:
798       exc_typ,exc_val,exc_fr=sys.exc_info()
799       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
800       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
801       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s, outargsname: %s" % (self.nodeName,outargsname),0))
802
803   def executeFirst(self,argsin):
804     """ Same than first part of self.execute to reduce memory peak."""
805     def ArgInMananger(self,argsin):
806       argsInPy = SeqByteReceiver( argsin )
807       data = argsInPy.data()
808       self.addInfoOnLevel2("inputMem",len(data))
809       _,kws=pickle.loads(data)
810       return kws
811     try:
812       self.beginOfCurrentExecutionSession()
813       self.addTimeInfoOnLevel2("startInputTime")
814       # to force call of SeqByteReceiver's destructor
815       kws = ArgInMananger(self,argsin)
816       vis = InOutputObjVisitor()
817       for elt in kws:
818         # fetch real data if necessary
819         kws[elt] = UnProxyObjectSimple( kws[elt],vis)
820       self.addInfoOnLevel2("inputHDDMem",vis)
821       self.context.update(kws)
822       self.addTimeInfoOnLevel2("endInputTime")
823     except Exception:
824       exc_typ,exc_val,exc_fr=sys.exc_info()
825       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
826       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
827       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:First %s" % (self.nodeName),0))
828
829   def executeSecond(self,outargsname):
830     """ Same than second part of self.execute to reduce memory peak."""
831     import sys
832     try:
833       self.addTimeInfoOnLevel2("startExecTime")
834       ##
835       self.addInfoOnLevel2("measureTimeResolution",self.my_container_py.monitoringtimeresms())
836       with GenericPythonMonitoringLauncherCtxMgr( CPUMemoryMonitoring( self.my_container_py.monitoringtimeresms() ) ) as monitoringParams:
837         exec(self.ccode, self.context)
838         cpumeminfo = ReadCPUMemInfo( monitoringParams )
839       ##
840       self.addInfoOnLevel2("CPUMemDuringExec",cpumeminfo)
841       del monitoringParams
842       self.addTimeInfoOnLevel2("endExecTime")
843       self.addTimeInfoOnLevel2("startOutputTime")
844       argsout=[]
845       for arg in outargsname:
846         if arg not in self.context:
847           raise KeyError("There is no variable %s in context" % arg)
848         argsout.append(self.context[arg])
849       ret = [ ]
850       outputMem = 0
851       vis = InOutputObjVisitor()
852       for arg in argsout:
853         # the proxy mecanism is catched here
854         argPickle = SpoolPickleObject( arg, vis )
855         retArg = SenderByte_i( self.poa,argPickle )
856         id_o = self.poa.activate_object(retArg)
857         retObj = self.poa.id_to_reference(id_o)
858         ret.append( retObj._narrow( SALOME.SenderByte ) )
859         outputMem += len(argPickle)
860       self.addInfoOnLevel2("outputMem",outputMem)
861       self.addInfoOnLevel2("outputHDDMem",vis)
862       self.addTimeInfoOnLevel2("endOutputTime")
863       self.endOfCurrentExecutionSession()
864       return ret
865     except Exception:
866       exc_typ,exc_val,exc_fr=sys.exc_info()
867       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
868       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
869       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:Second %s, outargsname: %s" % (self.nodeName,outargsname),0))
870
871   def listAllVarsInContext(self):
872       import re
873       pat = re.compile("^__([a-z]+)__$")
874       return [elt for elt in self.context if not pat.match(elt) and elt != MY_CONTAINER_ENTRY_IN_GLBS]
875       
876   def removeAllVarsInContext(self):
877       for elt in self.listAllVarsInContext():
878         del self.context[elt]
879
880   def getValueOfVarInContext(self,varName):
881     try:
882       return pickle.dumps(self.context[varName],-1)
883     except Exception:
884       exc_typ,exc_val,exc_fr=sys.exc_info()
885       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
886       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
887     pass
888   
889   def assignVarInContext(self, varName, value):
890     try:
891       self.context[varName][0] = pickle.loads(value)
892     except Exception:
893       exc_typ,exc_val,exc_fr=sys.exc_info()
894       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
895       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
896     pass
897
898   def callMethodOnVarInContext(self, varName, methodName, args):
899     try:
900       return pickle.dumps( getattr(self.context[varName][0],methodName)(*pickle.loads(args)),-1 )
901     except Exception:
902       exc_typ,exc_val,exc_fr=sys.exc_info()
903       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
904       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
905     pass
906
907   def beginOfCurrentExecutionSession(self):
908     self._current_execution_session = LogOfCurrentExecutionSession( self._log_script.addExecutionSession() )
909     self.context[MY_PERFORMANCE_LOG_ENTRY_IN_GLBS] = self._current_execution_session
910   
911   def endOfCurrentExecutionSession(self):
912     self._current_execution_session.finalizeAndPushToMaster()
913     self._current_execution_session = None
914
915   def addInfoOnLevel2(self, key, value):
916     self._current_execution_session.addInfoOnLevel2(key, value)
917       
918   def addTimeInfoOnLevel2(self, key):
919     from datetime import datetime
920     self._current_execution_session.addInfoOnLevel2(key,datetime.now())