Salome HOME
[EDF29150] : log performance of python scripts run inside SALOME container + verbosit...
[modules/kernel.git] / src / Container / SALOME_ContainerHelper.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2023  CEA, EDF
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 from collections import defaultdict
22
23 import pickle
24
25 class ScriptExecInfo:
26     @classmethod
27     def GetRepresentationOfTimeDelta(cls,endTime, startTime):
28        if endTime is None and startTime is None:
29           return "not measured"
30        td = endTime - startTime
31        import time
32        ts_of_td = time.gmtime(td.total_seconds())
33        return "{}.{:06d}".format(time.strftime("%H:%M:%S",ts_of_td),td.microseconds)
34     
35     @classmethod
36     def MemRepr(cls,memInByte):
37       m = memInByte
38       UNITS=["B","kB","MB","GB"]
39       remain = 0
40       oss = ""
41       for i in range( len(UNITS) ):
42           if m<1024:
43               oss = "{:03d}".format( int( (remain/1024)*1000 ) )
44               oss = "{}.{} {}".format(m,oss,UNITS[i])
45               return oss
46           else:
47               if i!=3:
48                   remain = m%1024
49                   m//=1024
50       return "{} {}".format(m,UNITS[3])
51
52     def __init__(self):
53       self._measure_time_resolution_ms = None
54       self._cpu_mem_during_exec = None
55       self._start_exec_time = None
56       self._end_exec_time = None
57       self._start_input_time = None
58       self._end_input_time = None
59       self._start_output_time = None
60       self._end_output_time = None
61       self._input_mem = 0
62       self._input_hdd_mem = None
63       self._output_mem = 0
64       self._output_hdd_mem = None
65       self._start_pos_log = None
66       self._stop_pos_log = None
67
68     @property
69     def measureTimeResolution(self):
70       return self._measure_time_resolution_ms
71     
72     @measureTimeResolution.setter
73     def measureTimeResolution(self, value):
74       self._measure_time_resolution_ms = value
75
76     @property
77     def tracePosStart(self):
78       return self._start_pos_log
79     
80     @tracePosStart.setter
81     def tracePosStart(self,value):
82       self._start_pos_log = value
83
84     @property
85     def tracePosStop(self):
86       return self._stop_pos_log
87     
88     @tracePosStop.setter
89     def tracePosStop(self,value):
90       self._stop_pos_log = value
91       
92     @property
93     def CPUMemDuringExec(self):
94       return self._cpu_mem_during_exec
95     
96     @CPUMemDuringExec.setter
97     def CPUMemDuringExec(self,value):
98       self._cpu_mem_during_exec = value
99
100     @property
101     def CPUMemDuringExecStr(self):
102       cpu = self._cpu_mem_during_exec[::2]
103       mem_rss = self._cpu_mem_during_exec[1::2]
104       return [(a,ScriptExecInfo.MemRepr(b)) for a,b in self._cpu_mem_during_exec]
105
106     @property
107     def inputMem(self):
108       return self._input_mem
109     
110     @inputMem.setter
111     def inputMem(self,value):
112       self._input_mem = value
113        
114     @property
115     def inputMemStr(self):
116       return ScriptExecInfo.MemRepr( self.inputMem )
117     
118     @property
119     def outputMem(self):
120       return self._output_mem
121     
122     @outputMem.setter
123     def outputMem(self,value):
124       self._output_mem = value
125        
126     @property
127     def outputMemStr(self):
128       return ScriptExecInfo.MemRepr( self.outputMem )
129     
130     @property
131     def inputHDDMem(self):
132       return self._input_hdd_mem
133     
134     @inputHDDMem.setter
135     def inputHDDMem(self,value):
136       self._input_hdd_mem = value
137
138     @property
139     def inputHDDMemStr(self):
140       if self._input_hdd_mem is None:
141          return "not computed"
142       return " ".join( [ ScriptExecInfo.MemRepr( elt.getSizeOfFileRead() ) for elt in self._input_hdd_mem] )
143     
144     @property
145     def outputHDDMem(self):
146       return self._output_hdd_mem
147     
148     @outputHDDMem.setter
149     def outputHDDMem(self,value):
150       self._output_hdd_mem = value
151
152     @property
153     def outputHDDMemStr(self):
154       if self._output_hdd_mem is None:
155          return "not computed"
156       return " ".join( [ ScriptExecInfo.MemRepr( elt.getSizeOfFileRead() ) for elt in self._output_hdd_mem] )
157
158     @property
159     def startInputTime(self):
160       return self._start_input_time
161     
162     @startInputTime.setter
163     def startInputTime(self,value):
164       self._start_input_time = value
165
166     @property
167     def endInputTime(self):
168       return self._end_input_time
169     
170     @endInputTime.setter
171     def endInputTime(self,value):
172       self._end_input_time = value
173
174     @property
175     def startExecTime(self):
176       return self._start_exec_time
177     
178     @startExecTime.setter
179     def startExecTime(self,value):
180       self._start_exec_time = value
181
182     @property
183     def endExecTime(self):
184       return self._end_exec_time
185     
186     @endExecTime.setter
187     def endExecTime(self,value):
188       self._end_exec_time = value
189
190     @property
191     def startOutputTime(self):
192       return self._start_output_time
193     
194     @startOutputTime.setter
195     def startOutputTime(self,value):
196       self._start_output_time = value
197
198     @property
199     def endOutputTime(self):
200       return self._end_output_time
201     
202     @endOutputTime.setter
203     def endOutputTime(self,value):
204       self._end_output_time = value
205
206     @property
207     def execTimeStr(self):
208        return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endExecTime,self.startExecTime)
209     
210     @property
211     def inputTimeStr(self):
212        return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endInputTime,self.startInputTime)
213     
214     @property
215     def outputTimeStr(self):
216        return ScriptExecInfo.GetRepresentationOfTimeDelta(self.endOutputTime,self.startOutputTime)
217
218     def __str__(self):
219       return """start exec time = {self.startExecTime}
220 end exec time = {self.endExecTime}
221 exec_time = {self.execTimeStr}
222 Measure time resolution = {self.measureTimeResolution} ms
223 CPU and mem monitoring = {self.CPUMemDuringExecStr}
224 input unpickling and ev load from disk time = {self.inputTimeStr}
225 output serialization and ev write to disk time = {self.outputTimeStr}
226 input memory size before exec (MemoryPeak 2x) = {self.inputMemStr}
227 input memory size from HDD = {self.inputHDDMemStr}
228 output memory size after exec (MemoryPeak 2x) = {self.outputMemStr}
229 output memory size from HDD = {self.outputHDDMemStr}
230 Start position in log = {self.tracePosStart}
231 End position in log = {self.tracePosStop}""".format(**locals())
232
233 class ScriptExecInfoDeco:
234   def __init__(self, eff, father):
235     self._eff = eff
236     self._father = father
237   @property
238   def father(self):
239     return self._father
240   def get(self):
241     return self._eff
242   def __getitem__(self,i):
243     return self._eff[i]
244   def __str__(self):
245     return self._eff.__str__()
246   def __repr__(self):
247     return self._eff.__repr__()
248   def log(self):
249     with open(self.father.father.logfile,"rb") as f:
250        cont = f.read()
251     return cont[self._eff.tracePosStart:self._eff.tracePosStop].decode()
252
253 class ScriptInfoAbstract:
254   def __init__(self, scriptPtr):
255       self._node_name = scriptPtr.getName()
256       self._code = scriptPtr.getCode()
257       self._exec = [pickle.loads(elt.getObj()) for elt in scriptPtr.listOfExecs()]
258
259   @property
260   def execs(self):
261       return self._exec
262
263   @property
264   def nodeName(self):
265       return self._node_name
266
267   @property
268   def code(self):
269       return self._code
270   
271   @code.setter
272   def code(self,value):
273       self._code = value
274
275   def __len__(self):
276       return len( self._exec )
277   
278   def __getitem__(self,i):
279       return self._exec[i]
280
281   def __str__(self):
282       return """name = {self.nodeName}\ncode = {self.code}\nexecs = {self.execs}""".format(**locals())
283   
284   def __repr__(self):
285       return """ScriptInfo \"{self.nodeName}\"""".format(**locals())
286   
287 class ScriptInfoClt(ScriptInfoAbstract):
288   def __init__(self, scriptPtr):
289       self._node_name = scriptPtr.getName()
290       self._code = scriptPtr.getCode()
291       self._exec = [pickle.loads(elt.getObj()) for elt in scriptPtr.listOfExecs()]
292
293 class ScriptInfo(ScriptInfoAbstract):
294   def __init__(self, nodeName, code, execs):
295       self._node_name = nodeName
296       self._code = code
297       self._exec = execs
298
299 class ScriptInfoDeco:
300   def __init__(self, eff, father):
301     self._eff = eff
302     self._father = father
303   @property
304   def father(self):
305     return self._father
306   def get(self):
307     return self._eff
308   def __getitem__(self,i):
309     return ScriptExecInfoDeco( self._eff[i], self )
310   def __len__(self):
311     return self._eff.__len__()
312   def __str__(self):
313     return self._eff.__str__()
314   def __repr__(self):
315     return self._eff.__repr__()
316
317 class ContainerLogInfoAbstract:
318     
319   def log(self):
320     with open(self.logfile,"rb") as f:
321        cont = f.read()
322     return cont.decode()
323
324   @property
325   def ns_entry(self):
326     return self._ns_entry
327   
328   @property
329   def logfile(self):
330     return self._log_file
331   
332   def __len__(self):
333     return len( self._scripts )
334   
335   def __getitem__(self,i):
336     return ScriptInfoDeco( self._scripts[i], self)
337   
338   def __str__(self):
339      return """NS entry = {self.ns_entry} LogFile = {self.logfile}""".format(**locals())
340   
341 class ContainerLogInfoClt(ContainerLogInfoAbstract):
342   def __init__(self,contLogPtr):
343     self._log_file = contLogPtr.getLogFile()
344     self._ns_entry = contLogPtr.getContainerEntryInNS()
345     self._scripts = [ScriptInfoClt(elt) for elt in contLogPtr.listOfScripts()]
346     
347 class ContainerLogInfo(ContainerLogInfoAbstract):
348   def __init__(self, nsEntry, logFile, scripts):
349      self._log_file = logFile
350      self._ns_entry = nsEntry
351      self._scripts = scripts
352
353 from abc import ABC, abstractmethod
354
355 class InOutputObjVisitorAbstract(ABC):
356   def __init__(self):
357       self._cur_obj = None
358       self._data = []
359
360   def enter(self):
361       self._cur_obj = ObjMemModel()
362       return self._cur_obj
363   
364   def leave(self):
365       self._data.append( self._cur_obj )
366       self._cur_obj = None
367
368   def getSizeOfFileRead(self):
369       return sum( [elt.getSizeOfFileRead() for elt in self._data] )
370       
371   def visitor(self):
372       return self
373
374   def setHDDMem(self, v):
375       pass
376   
377   def setFileName(self, fileName):
378       pass
379
380   @abstractmethod
381   def getRepr(self):
382       pass
383
384 class InOutputObjVisitorIter:
385   def __init__(self, visitor):
386       self._visitor = visitor
387       self._current = 0
388
389   def __next__(self):
390       if self._current >= len(self._visitor._data):
391             raise StopIteration
392       else:
393         ret = self._visitor._data[ self._current ]
394         self._current += 1
395         return ret
396
397 class InOutputObjVisitor(InOutputObjVisitorAbstract):
398   def __init__(self):
399       super().__init__()
400       
401   def getRepr(self):
402       return self.getSizeOfFileRead()
403   
404   def __iter__(self):
405      return InOutputObjVisitorIter(self)
406
407 class ObjMemModel(InOutputObjVisitorAbstract):
408   def __init__(self):
409       super().__init__()
410       self._hdd_mem = 0
411       self._file_name = None
412       
413   def setHDDMem(self, v):
414       self._hdd_mem = v
415       del self._data
416
417   def setFileName(self, fileName):
418       self._file_name = fileName
419       pass
420       
421   def getSizeOfFileRead(self):
422       if hasattr(self,"_data"):
423         return super().getSizeOfFileRead()
424       else:
425         return self._hdd_mem
426   
427   def getRepr(self):
428       return self.getSizeOfFileRead()
429
430 class FakeObjVisitor:
431   def setHDDMem(self, v):
432       pass
433     
434   def visitor(self):
435       return None
436
437 class InOutputObjVisitorCM:
438   def __init__(self, visitor):
439      self._visitor = visitor
440   def __enter__(self):
441       if self._visitor:
442         r = self._visitor.enter()
443         return r
444       else:
445         return FakeObjVisitor()
446   def __exit__(self,exctype, exc, tb):
447       if self._visitor:
448         self._visitor.leave()
449       pass
450   
451 class OffsetType:
452   def __init__(self,i):
453     self._i = i
454   def __int__(self):
455     return self._i
456   def __iadd__(self,delta):
457     self._i += delta
458     return self
459
460 def unserializeInt(structData, offset):
461     from ctypes import c_int
462     sz_of_cint = 4
463     sz = c_int.from_buffer_copy( structData[int(offset):int(offset)+sz_of_cint] ).value
464     offset += sz_of_cint
465     return sz
466
467 def unserializeString(structData,offset):
468     sz = unserializeInt(structData,offset)
469     ret = structData[int(offset):int(offset)+sz].decode()
470     offset += sz
471     return ret
472
473 def unserializeContainerScriptExecPerfLog(structData, offset):
474     import pickle
475     sz = unserializeInt(structData,offset)
476     inst = None
477     if sz > 0:
478         inst = pickle.loads( structData[int(offset):int(offset)+sz] )
479     offset += sz
480     return inst
481
482 def unserializeContainerScriptPerfLog(structData, offset):
483     name = unserializeString(structData,offset)
484     code = unserializeString(structData,offset)
485     numberOfSessions = unserializeInt(structData,offset)
486     sessions = []
487     for _ in range(numberOfSessions):
488         session = unserializeContainerScriptExecPerfLog(structData,offset)
489         sessions.append( session )
490     return ScriptInfo(name,code,sessions)
491
492 def unserializeContainerPerfLog(structData, offset):
493     nsEntry = unserializeString(structData,offset)
494     logFile = unserializeString(structData,offset)
495     scripts = []
496     nbScripts = unserializeInt(structData,offset)
497     for _ in range(nbScripts):
498         script = unserializeContainerScriptPerfLog(structData,offset)
499         scripts.append( script )
500     return ContainerLogInfo(nsEntry,logFile,scripts)
501
502 def unserializeLogManager(structData):
503     offset = OffsetType(0)
504     numberOfScripts = unserializeInt(structData,offset)
505     logManagerInst = []
506     for _ in range(numberOfScripts):
507         containerPerfLogInst = unserializeContainerPerfLog(structData,offset)
508         logManagerInst.append( containerPerfLogInst )
509     if int(offset) != len(structData):
510         raise RuntimeError("Something went wrong during unserialization phase.")
511     return logManagerInst