Salome HOME
Documentation, style and code performance update
[modules/adao.git] / src / daComposant / daCore / PlatformInfo.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2008-2024 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()))  # noqa: E128
100             elif hasattr(platform, 'dist'):
101                 __msg += "\n%s%30s : %s"%(__prefix,
102                     "platform.dist", str(platform.dist()))  # noqa: E128
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',         # noqa: E241,E203
108                     '3' : 'Panther',      '4' : 'Tiger',       '5' : 'Leopard',        # noqa: E241,E203
109                     '6' : 'Snow Leopard', '7' : 'Lion',        '8' : 'Mountain Lion',  # noqa: E241,E203
110                     '9' : 'Mavericks',    '10': 'Yosemite',    '11': 'El Capitan',     # noqa: E241,E203
111                     '12': 'Sierra',       '13': 'High Sierra', '14': 'Mojave',         # noqa: E241,E203
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] + ")"))  # noqa: E128
119                 __macosxv11 = {
120                     '11': 'Big Sur',      '12': 'Monterey',    '13': 'Ventura',  # noqa: E241
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] + ")"))  # noqa: E128
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 précision 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: str, __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"%__date)
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 st in ("array", "matrix", "list", "tuple", "[", "]", "(", ")"):
395         __strvect = __strvect.replace(st, "")  # Rien
396     for st in (",", ";"):
397         __strvect = __strvect.replace(st, " ")  # 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 st in ("array", "matrix", "list", "tuple", "[", "(", "'", '"'):
407         __strvect = __strvect.replace(st, "")  # Rien
408     __strvect = __strvect.replace(",", " ")  # Blanc
409     for st in ("]", ")"):
410         __strvect = __strvect.replace(st, ";")  # "]" 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 = (
420             " For some shared or older file systems on Windows, a file " + \
421             "name longer than 256 characters can lead to access problems." + \
422             "\n  The name of the file in question is the following:" + \
423             "\n  %s")%(__filename,)
424         if __warnInsteadOfPrint:
425             logging.warning(__msg)
426         else:
427             print(__msg)
428     else:
429         __conform = True
430     #
431     return __conform
432
433 def checkFileNameImportability( __filename, __warnInsteadOfPrint=True ):
434     if str(__filename).count(".") > 1:
435         __conform = False
436         __msg = (
437             " The file name contains %i point(s) before the extension " + \
438             "separator, which can potentially lead to problems when " + \
439             "importing this file into Python, as it can then be recognized " + \
440             "as a sub-module (generating a \"ModuleNotFoundError\"). If it " + \
441             "is intentional, make sure that there is no module with the " + \
442             "same name as the part before the first point, and that there is " + \
443             "no \"__init__.py\" file in the same directory." + \
444             "\n  The name of the file in question is the following:" + \
445             "\n  %s")%(int(str(__filename).count(".") - 1), __filename)
446         if __warnInsteadOfPrint is None:
447             pass
448         elif __warnInsteadOfPrint:
449             logging.warning(__msg)
450         else:
451             print(__msg)
452     else:
453         __conform = True
454     #
455     return __conform
456
457 # ==============================================================================
458 class PathManagement(object):
459     """
460     Mise à jour du path système pour les répertoires d'outils
461     """
462     __slots__ = ("__paths")
463
464     def __init__(self):
465         "Déclaration des répertoires statiques"
466         parent = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
467         self.__paths = {}
468         self.__paths["daNumerics"] = os.path.join(parent, "daNumerics")
469         #
470         for v in self.__paths.values():
471             if os.path.isdir(v):
472                 sys.path.insert(0, v )
473         #
474         # Conserve en unique exemplaire chaque chemin
475         sys.path = uniq( sys.path )
476         del parent
477
478     def getpaths(self):
479         """
480         Renvoie le dictionnaire des chemins ajoutés
481         """
482         return self.__paths
483
484 # ==============================================================================
485 class SystemUsage(object):
486     """
487     Permet de récupérer les différentes tailles mémoires du process courant
488     """
489     __slots__ = ()
490     #
491     # Le module resource renvoie 0 pour les tailles mémoire. On utilise donc
492     # plutôt : http://code.activestate.com/recipes/286222/ et Wikipedia
493     #
494     _proc_status = '/proc/%d/status' % os.getpid()
495     _memo_status = '/proc/meminfo'
496     _scale = {
497         'o'  : 1.0,     # Multiples SI de l'octet          # noqa: E203
498         'ko' : 1.e3,                                       # noqa: E203
499         'Mo' : 1.e6,                                       # noqa: E203
500         'Go' : 1.e9,                                       # noqa: E203
501         'kio': 1024.0,  # Multiples binaires de l'octet    # noqa: E203
502         'Mio': 1024.0 * 1024.0,                            # noqa: E203
503         'Gio': 1024.0 * 1024.0 * 1024.0,                   # noqa: E203
504         'B'  : 1.0,     # Multiples binaires du byte=octet # noqa: E203
505         'kB' : 1024.0,                                     # noqa: E203
506         'MB' : 1024.0 * 1024.0,                            # noqa: E203
507         'GB' : 1024.0 * 1024.0 * 1024.0,                   # noqa: E203
508     }
509
510     def __init__(self):
511         "Sans effet"
512         pass
513
514     def _VmA(self, VmKey, unit):
515         "Lecture des paramètres mémoire de la machine"
516         try:
517             t = open(self._memo_status)
518             v = t.read()
519             t.close()
520         except IOError:
521             return 0.0            # non-Linux?
522         i = v.index(VmKey)        # get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
523         v = v[i:].split(None, 3)  # whitespace
524         if len(v) < 3:
525             return 0.0            # invalid format?
526         # convert Vm value to bytes
527         mem = float(v[1]) * self._scale[v[2]]
528         return mem / self._scale[unit]
529
530     def getAvailablePhysicalMemory(self, unit="o"):
531         "Renvoie la mémoire physique utilisable en octets"
532         return self._VmA('MemTotal:', unit)
533
534     def getAvailableSwapMemory(self, unit="o"):
535         "Renvoie la mémoire swap utilisable en octets"
536         return self._VmA('SwapTotal:', unit)
537
538     def getAvailableMemory(self, unit="o"):
539         "Renvoie la mémoire totale (physique+swap) utilisable en octets"
540         return self._VmA('MemTotal:', unit) + self._VmA('SwapTotal:', unit)
541
542     def getUsableMemory(self, unit="o"):
543         """Renvoie la mémoire utilisable en octets
544         Rq : il n'est pas sûr que ce décompte soit juste...
545         """
546         return self._VmA('MemFree:', unit) + self._VmA('SwapFree:', unit) + \
547             self._VmA('Cached:', unit) + self._VmA('SwapCached:', unit)
548
549     def _VmB(self, VmKey, unit):
550         "Lecture des paramètres mémoire du processus"
551         try:
552             t = open(self._proc_status)
553             v = t.read()
554             t.close()
555         except IOError:
556             return 0.0            # non-Linux?
557         i = v.index(VmKey)        # get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
558         v = v[i:].split(None, 3)  # whitespace
559         if len(v) < 3:
560             return 0.0            # invalid format?
561         # convert Vm value to bytes
562         mem = float(v[1]) * self._scale[v[2]]
563         return mem / self._scale[unit]
564
565     def getUsedMemory(self, unit="o"):
566         "Renvoie la mémoire résidente utilisée en octets"
567         return self._VmB('VmRSS:', unit)
568
569     def getVirtualMemory(self, unit="o"):
570         "Renvoie la mémoire totale utilisée en octets"
571         return self._VmB('VmSize:', unit)
572
573     def getUsedStacksize(self, unit="o"):
574         "Renvoie la taille du stack utilisé en octets"
575         return self._VmB('VmStk:', unit)
576
577     def getMaxUsedMemory(self, unit="o"):
578         "Renvoie la mémoire résidente maximale mesurée"
579         return self._VmB('VmHWM:', unit)
580
581     def getMaxVirtualMemory(self, unit="o"):
582         "Renvoie la mémoire totale maximale mesurée"
583         return self._VmB('VmPeak:', unit)
584
585 # ==============================================================================
586 # Tests d'importation de modules locaux
587
588 PathManagement()
589 try:
590     import Gnuplot
591     has_gnuplot = True
592 except ImportError:
593     has_gnuplot = False
594
595 # ==============================================================================
596 if __name__ == "__main__":
597     print("\n AUTODIAGNOSTIC\n")