Salome HOME
Minor documentation and code review corrections (41)
[modules/adao.git] / src / daComposant / daCore / PlatformInfo.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2008-2023 EDF R&D
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 # Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
22
23 """
24     Informations sur le code et la plateforme, et mise à jour des chemins.
25
26     La classe "PlatformInfo" permet de récupérer les informations générales sur
27     le code et la plateforme sous forme de strings, ou d'afficher directement
28     les informations disponibles par les méthodes. L'impression directe d'un
29     objet de cette classe affiche les informations minimales. Par exemple :
30         print(PlatformInfo())
31         print(PlatformInfo().getVersion())
32         created = PlatformInfo().getDate()
33
34     La classe "PathManagement" permet de mettre à jour les chemins système pour
35     ajouter les outils numériques, matrices... On l'utilise en instanciant
36     simplement cette classe, sans même récupérer d'objet :
37         PathManagement()
38
39     La classe "SystemUsage" permet de  sous Unix les différentes tailles
40     mémoires du process courant. Ces tailles peuvent être assez variables et
41     dépendent de la fiabilité des informations du système dans le suivi des
42     process.
43 """
44 __author__ = "Jean-Philippe ARGAUD"
45 __all__ = []
46
47 import os
48 import sys
49 import platform
50 import socket
51 import locale
52 import logging
53 import re
54
55 # ==============================================================================
56 class PlatformInfo(object):
57     """
58     Rassemblement des informations sur le code et la plateforme
59     """
60     __slots__ = ()
61     #
62     def __init__(self):
63         "Sans effet"
64         pass
65     #
66     def getName(self):
67         "Retourne le nom de l'application"
68         import daCore.version as dav
69         return dav.name
70     #
71     def getVersion(self):
72         "Retourne le numéro de la version"
73         import daCore.version as dav
74         return dav.version
75     #
76     def getDate(self):
77         "Retourne la date de création de la version"
78         import daCore.version as dav
79         return dav.date
80     #
81     def getYear(self):
82         "Retourne l'année de création de la version"
83         import daCore.version as dav
84         return dav.year
85     #
86     def getSystemInformation(self, __prefix=""):
87         __msg  = ""
88         __msg += "\n%s%30s : %s" %(__prefix,"platform.system",platform.system())
89         __msg += "\n%s%30s : %s" %(__prefix,"sys.platform",sys.platform)
90         __msg += "\n%s%30s : %s" %(__prefix,"platform.version",platform.version())
91         __msg += "\n%s%30s : %s" %(__prefix,"platform.platform",platform.platform())
92         __msg += "\n%s%30s : %s" %(__prefix,"platform.machine",platform.machine())
93         if len(platform.processor())>0:
94             __msg += "\n%s%30s : %s" %(__prefix,"platform.processor",platform.processor())
95         #
96         if sys.platform.startswith('linux'):
97             if hasattr(platform, 'linux_distribution'):
98                 __msg += "\n%s%30s : %s" %(__prefix,
99                     "platform.linux_distribution",str(platform.linux_distribution()))
100             elif hasattr(platform, 'dist'):
101                 __msg += "\n%s%30s : %s" %(__prefix,
102                     "platform.dist",str(platform.dist()))
103         elif sys.platform.startswith('darwin'):
104             if hasattr(platform, 'mac_ver'):
105                 # https://fr.wikipedia.org/wiki/MacOS
106                 __macosxv10 = {
107                     '0' : 'Cheetah',      '1' : 'Puma',        '2' : 'Jaguar',
108                     '3' : 'Panther',      '4' : 'Tiger',       '5' : 'Leopard',
109                     '6' : 'Snow Leopard', '7' : 'Lion',        '8' : 'Mountain Lion',
110                     '9' : 'Mavericks',    '10': 'Yosemite',    '11': 'El Capitan',
111                     '12': 'Sierra',       '13': 'High Sierra', '14': 'Mojave',
112                     '15': 'Catalina',
113                     }
114                 for key in __macosxv10:
115                     __details = platform.mac_ver()[0].split('.')
116                     if (len(__details)>0) and (__details[1] == key):
117                         __msg += "\n%s%30s : %s" %(__prefix,
118                             "platform.mac_ver",str(platform.mac_ver()[0]+"(" + __macosxv10[key]+")"))
119                 __macosxv11 = {
120                     '11': 'Big Sur',      '12': 'Monterey',    '13': 'Ventura',
121                     '14': 'Sonoma',
122                     }
123                 for key in __macosxv11:
124                     __details = platform.mac_ver()[0].split('.')
125                     if (__details[0] == key):
126                         __msg += "\n%s%30s : %s" %(__prefix,
127                             "platform.mac_ver",str(platform.mac_ver()[0]+"(" + __macosxv11[key]+")"))
128             elif hasattr(platform, 'dist'):
129                 __msg += "\n%s%30s : %s" %(__prefix,"platform.dist",str(platform.dist()))
130         elif os.name == 'nt':
131             __msg += "\n%s%30s : %s" %(__prefix,"platform.win32_ver",platform.win32_ver()[1])
132         #
133         __msg += "\n"
134         __msg += "\n%s%30s : %s" %(__prefix,"platform.python_implementation",platform.python_implementation())
135         __msg += "\n%s%30s : %s" %(__prefix,"sys.executable",sys.executable)
136         __msg += "\n%s%30s : %s" %(__prefix,"sys.version",sys.version.replace('\n',''))
137         __msg += "\n%s%30s : %s" %(__prefix,"sys.getfilesystemencoding",str(sys.getfilesystemencoding()))
138         if  sys.version_info.major == 3 and sys.version_info.minor < 11: # Python 3.10
139             __msg += "\n%s%30s : %s" %(__prefix,"locale.getdefaultlocale",str(locale.getdefaultlocale()))
140         else:
141             __msg += "\n%s%30s : %s" %(__prefix,"locale.getlocale",str(locale.getlocale()))
142         __msg += "\n"
143         __msg += "\n%s%30s : %s" %(__prefix,"os.cpu_count",os.cpu_count())
144         if hasattr(os, 'sched_getaffinity'):
145             __msg += "\n%s%30s : %s" %(__prefix,"len(os.sched_getaffinity(0))",len(os.sched_getaffinity(0)))
146         else:
147             __msg += "\n%s%30s : %s" %(__prefix,"len(os.sched_getaffinity(0))","Unsupported on this platform")
148         __msg += "\n"
149         __msg += "\n%s%30s : %s" %(__prefix,"platform.node",platform.node())
150         __msg += "\n%s%30s : %s" %(__prefix,"socket.getfqdn",socket.getfqdn())
151         __msg += "\n%s%30s : %s" %(__prefix,"os.path.expanduser",os.path.expanduser('~'))
152         return __msg
153     #
154     def getApplicationInformation(self, __prefix=""):
155         __msg  = ""
156         __msg += "\n%s%30s : %s" %(__prefix,"ADAO version",self.getVersion())
157         __msg += "\n"
158         __msg += "\n%s%30s : %s" %(__prefix,"Python version",self.getPythonVersion())
159         __msg += "\n%s%30s : %s" %(__prefix,"Numpy version",self.getNumpyVersion())
160         __msg += "\n%s%30s : %s" %(__prefix,"Scipy version",self.getScipyVersion())
161         __msg += "\n%s%30s : %s" %(__prefix,"NLopt version",self.getNloptVersion())
162         __msg += "\n%s%30s : %s" %(__prefix,"MatplotLib version",self.getMatplotlibVersion())
163         __msg += "\n%s%30s : %s" %(__prefix,"GnuplotPy version",self.getGnuplotVersion())
164         __msg += "\n%s%30s : %s" %(__prefix,"Sphinx version",self.getSphinxVersion())
165         __msg += "\n%s%30s : %s" %(__prefix,"Fmpy version",self.getFmpyVersion())
166         return __msg
167     #
168     def getAllInformation(self, __prefix="", __title="Whole system information"):
169         __msg  = ""
170         if len(__title)>0:
171             __msg += "\n"+"="*80+"\n"+__title+"\n"+"="*80+"\n"
172         __msg += self.getSystemInformation(__prefix)
173         __msg += "\n"
174         __msg += self.getApplicationInformation(__prefix)
175         return __msg
176     #
177     def getPythonVersion(self):
178         "Retourne la version de python disponible"
179         return ".".join([str(x) for x in sys.version_info[0:3]]) # map(str,sys.version_info[0:3]))
180     #
181     def getNumpyVersion(self):
182         "Retourne la version de numpy disponible"
183         import numpy.version
184         return numpy.version.version
185     #
186     def getScipyVersion(self):
187         "Retourne la version de scipy disponible"
188         if has_scipy:
189             __version = scipy.version.version
190         else:
191             __version = "0.0.0"
192         return __version
193     #
194     def getMatplotlibVersion(self):
195         "Retourne la version de matplotlib disponible"
196         if has_matplotlib:
197             __version = matplotlib.__version__
198         else:
199             __version = "0.0.0"
200         return __version
201     #
202     def getGnuplotVersion(self):
203         "Retourne la version de gnuplotpy disponible"
204         if has_gnuplot:
205             __version = Gnuplot.__version__
206         else:
207             __version = "0.0"
208         return __version
209     #
210     def getSphinxVersion(self):
211         "Retourne la version de sphinx disponible"
212         if has_sphinx:
213             __version = sphinx.__version__
214         else:
215             __version = "0.0.0"
216         return __version
217     #
218     def getNloptVersion(self):
219         "Retourne la version de nlopt disponible"
220         if has_nlopt:
221             __version = "%s.%s.%s"%(
222                 nlopt.version_major(),
223                 nlopt.version_minor(),
224                 nlopt.version_bugfix(),
225                 )
226         else:
227             __version = "0.0.0"
228         return __version
229     #
230     def getSdfVersion(self):
231         "Retourne la version de sdf disponible"
232         if has_sdf:
233             __version = sdf.__version__
234         else:
235             __version = "0.0.0"
236         return __version
237     #
238     def getFmpyVersion(self):
239         "Retourne la version de fmpy disponible"
240         if has_fmpy:
241             __version = fmpy.__version__
242         else:
243             __version = "0.0.0"
244         return __version
245     #
246     def getCurrentMemorySize(self):
247         "Retourne la taille mémoire courante utilisée"
248         return 1
249     #
250     def MaximumPrecision(self):
251         "Retourne la precision maximale flottante pour Numpy"
252         import numpy
253         try:
254             numpy.array([1.,], dtype='float128')
255             mfp = 'float128'
256         except Exception:
257             mfp = 'float64'
258         return mfp
259     #
260     def MachinePrecision(self):
261         # Alternative sans module :
262         # eps = 2.38
263         # while eps > 0:
264         #     old_eps = eps
265         #     eps = (1.0 + eps/2) - 1.0
266         return sys.float_info.epsilon
267     #
268     def __str__(self):
269         import daCore.version as dav
270         return "%s %s (%s)"%(dav.name,dav.version,dav.date)
271
272 # ==============================================================================
273 # Tests d'importation de modules système
274
275 try:
276     import numpy
277     has_numpy = True
278 except ImportError:
279     raise ImportError("Numpy is not available, despites the fact it is mandatory.")
280
281 try:
282     import scipy
283     import scipy.version
284     import scipy.optimize
285     has_scipy = True
286 except ImportError:
287     has_scipy = False
288
289 try:
290     import matplotlib
291     has_matplotlib = True
292 except ImportError:
293     has_matplotlib = False
294
295 try:
296     import sphinx
297     has_sphinx = True
298 except ImportError:
299     has_sphinx = False
300
301 try:
302     import nlopt
303     has_nlopt = True
304 except ImportError:
305     has_nlopt = False
306
307 try:
308     import sdf
309     has_sdf = True
310 except ImportError:
311     has_sdf = False
312
313 try:
314     import fmpy
315     has_fmpy = True
316 except ImportError:
317     has_fmpy = False
318
319 has_salome = bool( "SALOME_ROOT_DIR" in os.environ )
320 has_yacs   = bool(   "YACS_ROOT_DIR" in os.environ )
321 has_adao   = bool(   "ADAO_ROOT_DIR" in os.environ )
322 has_eficas = bool( "EFICAS_ROOT_DIR" in os.environ )
323
324 # ==============================================================================
325 def uniq( __sequence ):
326     """
327     Fonction pour rendre unique chaque élément d'une liste, en préservant l'ordre
328     """
329     __seen = set()
330     return [x for x in __sequence if x not in __seen and not __seen.add(x)]
331
332 def vt( __version ):
333     "Version transformée pour comparaison robuste, obtenue comme un tuple"
334     serie = []
335     for sv in re.split("[_.+-]", __version):
336         serie.append(sv.zfill(6))
337     return tuple(serie)
338
339 def isIterable( __sequence, __check = False, __header = "" ):
340     """
341     Vérification que l'argument est un itérable interne.
342     Remarque : pour permettre le test correct en MultiFonctions,
343     - Ne pas accepter comme itérable un "numpy.ndarray"
344     - Ne pas accepter comme itérable avec hasattr(__sequence, "__iter__")
345     """
346     if  isinstance( __sequence, (list, tuple, map, dict) ):
347         __isOk = True
348     elif type(__sequence).__name__ in ('generator','range'):
349         __isOk = True
350     elif "_iterator" in type(__sequence).__name__:
351         __isOk = True
352     elif "itertools" in str(type(__sequence)):
353         __isOk = True
354     else:
355         __isOk = False
356     if __check and not __isOk:
357         raise TypeError("Not iterable or unkown input type%s: %s"%(__header, type(__sequence),))
358     return __isOk
359
360 def date2int( __date, __lang="FR" ):
361     """
362     Fonction de secours, conversion pure : dd/mm/yy hh:mm ---> int(yyyymmddhhmm)
363     """
364     __date = __date.strip()
365     if __date.count('/') == 2 and __date.count(':') == 0 and __date.count(' ') == 0:
366         d,m,y = __date.split("/")
367         __number = (10**4)*int(y)+(10**2)*int(m)+int(d)
368     elif __date.count('/') == 2 and __date.count(':') == 1 and __date.count(' ') > 0:
369         part1, part2 = __date.split()
370         d,m,y = part1.strip().split("/")
371         h,n   = part2.strip().split(":")
372         __number = (10**8)*int(y)+(10**6)*int(m)+(10**4)*int(d)+(10**2)*int(h)+int(n)
373     else:
374         raise ValueError("Cannot convert \"%s\" as a D/M/Y H:M date"%d)
375     return __number
376
377 def vfloat(__value :numpy.ndarray):
378     """
379     Conversion en flottant d'un vecteur de taille 1 et de dimensions quelconques
380     """
381     if hasattr(__value,"size") and __value.size == 1:
382         return float(__value.flat[0])
383     elif isinstance(__value, (float,int)):
384         return float(__value)
385     else:
386         raise ValueError("Error in converting multiple float values from array when waiting for only one")
387
388 def strvect2liststr( __strvect ):
389     """
390     Fonction de secours, conversion d'une chaîne de caractères de
391     représentation de vecteur en une liste de chaînes de caractères de
392     représentation de flottants
393     """
394     for s in ("array", "matrix", "list", "tuple", "[", "]", "(", ")"):
395         __strvect = __strvect.replace(s,"")  # Rien
396     for s in (",", ";"):
397         __strvect = __strvect.replace(s," ") # Blanc
398     return __strvect.split()
399
400 def strmatrix2liststr( __strvect ):
401     """
402     Fonction de secours, conversion d'une chaîne de caractères de
403     représentation de matrice en une liste de chaînes de caractères de
404     représentation de flottants
405     """
406     for s in ("array", "matrix", "list", "tuple", "[", "(", "'", '"'):
407         __strvect = __strvect.replace(s,"")  # Rien
408     __strvect = __strvect.replace(","," ") # Blanc
409     for s in ("]", ")"):
410         __strvect = __strvect.replace(s,";") # "]" et ")" par ";"
411     __strvect = re.sub(r';\s*;',r';',__strvect)
412     __strvect = __strvect.rstrip(";") # Après ^ et avant v
413     __strmat = [__l.split() for __l in __strvect.split(";")]
414     return __strmat
415
416 def checkFileNameConformity( __filename, __warnInsteadOfPrint=True ):
417     if sys.platform.startswith("win") and len(__filename) > 256:
418         __conform = False
419         __msg = (" For some shared or older file systems on Windows, a file "+\
420             "name longer than 256 characters can lead to access problems."+\
421             "\n  The name of the file in question is the following:"+\
422             "\n  %s")%(__filename,)
423         if __warnInsteadOfPrint: logging.warning(__msg)
424         else:                    print(__msg)
425     else:
426         __conform = True
427     #
428     return __conform
429
430 def checkFileNameImportability( __filename, __warnInsteadOfPrint=True ):
431     if str(__filename).count(".") > 1:
432         __conform = False
433         __msg = (" The file name contains %i point(s) before the extension "+\
434             "separator, which can potentially lead to problems when "+\
435             "importing this file into Python, as it can then be recognized "+\
436             "as a sub-module (generating a \"ModuleNotFoundError\"). If it "+\
437             "is intentional, make sure that there is no module with the "+\
438             "same name as the part before the first point, and that there is "+\
439             "no \"__init__.py\" file in the same directory."+\
440             "\n  The name of the file in question is the following:"+\
441             "\n  %s")%(int(str(__filename).count(".")-1), __filename)
442         if __warnInsteadOfPrint is None: pass
443         elif __warnInsteadOfPrint:       logging.warning(__msg)
444         else:                            print(__msg)
445     else:
446         __conform = True
447     #
448     return __conform
449
450 # ==============================================================================
451 class PathManagement(object):
452     """
453     Mise à jour du path système pour les répertoires d'outils
454     """
455     __slots__ = ("__paths")
456     #
457     def __init__(self):
458         "Déclaration des répertoires statiques"
459         parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
460         self.__paths = {}
461         self.__paths["daNumerics"]  = os.path.join(parent,"daNumerics")
462         #
463         for v in self.__paths.values():
464             if os.path.isdir(v): sys.path.insert(0, v )
465         #
466         # Conserve en unique exemplaire chaque chemin
467         sys.path = uniq( sys.path )
468         del parent
469     #
470     def getpaths(self):
471         """
472         Renvoie le dictionnaire des chemins ajoutés
473         """
474         return self.__paths
475
476 # ==============================================================================
477 class SystemUsage(object):
478     """
479     Permet de récupérer les différentes tailles mémoires du process courant
480     """
481     __slots__ = ()
482     #
483     # Le module resource renvoie 0 pour les tailles mémoire. On utilise donc
484     # plutôt : http://code.activestate.com/recipes/286222/ et Wikipedia
485     #
486     _proc_status = '/proc/%d/status' % os.getpid()
487     _memo_status = '/proc/meminfo'
488     _scale = {
489         'o'  : 1.0,     # Multiples SI de l'octet
490         'ko' : 1.e3,
491         'Mo' : 1.e6,
492         'Go' : 1.e9,
493         'kio': 1024.0,  # Multiples binaires de l'octet
494         'Mio': 1024.0*1024.0,
495         'Gio': 1024.0*1024.0*1024.0,
496         'B':     1.0,   # Multiples binaires du byte=octet
497         'kB' : 1024.0,
498         'MB' : 1024.0*1024.0,
499         'GB' : 1024.0*1024.0*1024.0,
500         }
501     #
502     def __init__(self):
503         "Sans effet"
504         pass
505     #
506     def _VmA(self, VmKey, unit):
507         "Lecture des paramètres mémoire de la machine"
508         try:
509             t = open(self._memo_status)
510             v = t.read()
511             t.close()
512         except IOError:
513             return 0.0           # non-Linux?
514         i = v.index(VmKey)       # get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
515         v = v[i:].split(None, 3) # whitespace
516         if len(v) < 3:
517             return 0.0           # invalid format?
518         # convert Vm value to bytes
519         mem = float(v[1]) * self._scale[v[2]]
520         return mem / self._scale[unit]
521     #
522     def getAvailablePhysicalMemory(self, unit="o"):
523         "Renvoie la mémoire physique utilisable en octets"
524         return self._VmA('MemTotal:', unit)
525     #
526     def getAvailableSwapMemory(self, unit="o"):
527         "Renvoie la mémoire swap utilisable en octets"
528         return self._VmA('SwapTotal:', unit)
529     #
530     def getAvailableMemory(self, unit="o"):
531         "Renvoie la mémoire totale (physique+swap) utilisable en octets"
532         return self._VmA('MemTotal:', unit) + self._VmA('SwapTotal:', unit)
533     #
534     def getUsableMemory(self, unit="o"):
535         """Renvoie la mémoire utilisable en octets
536         Rq : il n'est pas sûr que ce décompte soit juste...
537         """
538         return self._VmA('MemFree:', unit) + self._VmA('SwapFree:', unit) + \
539                self._VmA('Cached:', unit) + self._VmA('SwapCached:', unit)
540     #
541     def _VmB(self, VmKey, unit):
542         "Lecture des paramètres mémoire du processus"
543         try:
544             t = open(self._proc_status)
545             v = t.read()
546             t.close()
547         except IOError:
548             return 0.0           # non-Linux?
549         i = v.index(VmKey)       # get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
550         v = v[i:].split(None, 3) # whitespace
551         if len(v) < 3:
552             return 0.0           # invalid format?
553         # convert Vm value to bytes
554         mem = float(v[1]) * self._scale[v[2]]
555         return mem / self._scale[unit]
556     #
557     def getUsedMemory(self, unit="o"):
558         "Renvoie la mémoire résidente utilisée en octets"
559         return self._VmB('VmRSS:', unit)
560     #
561     def getVirtualMemory(self, unit="o"):
562         "Renvoie la mémoire totale utilisée en octets"
563         return self._VmB('VmSize:', unit)
564     #
565     def getUsedStacksize(self, unit="o"):
566         "Renvoie la taille du stack utilisé en octets"
567         return self._VmB('VmStk:', unit)
568     #
569     def getMaxUsedMemory(self, unit="o"):
570         "Renvoie la mémoire résidente maximale mesurée"
571         return self._VmB('VmHWM:', unit)
572     #
573     def getMaxVirtualMemory(self, unit="o"):
574         "Renvoie la mémoire totale maximale mesurée"
575         return self._VmB('VmPeak:', unit)
576
577 # ==============================================================================
578 # Tests d'importation de modules locaux
579
580 PathManagement()
581 try:
582     import Gnuplot
583     has_gnuplot = True
584 except ImportError:
585     has_gnuplot = False
586
587 # ==============================================================================
588 if __name__ == "__main__":
589     print("\n AUTODIAGNOSTIC\n")