]> SALOME platform Git repositories - modules/kernel.git/blob - bin/salome_utils.py
Salome HOME
Merge branch 'occ/psutil'
[modules/kernel.git] / bin / salome_utils.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2021  CEA/DEN, EDF R&D, 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 ## @package salome_utils
22 #  @brief Set of utility functions used by SALOME python scripts.
23
24 """
25 Various utilities for SALOME.
26 """
27
28 # pragma pylint: disable=invalid-name
29
30 import os
31 import os.path as osp
32 import re
33 import shutil
34 import socket
35 import sys
36 import tempfile
37 from contextlib import suppress
38
39 import psutil
40
41 def _try_bool(arg):
42     """
43     Convert given `arg` to a boolean value.
44     String values like 'True', 'TRUE', 'YES', 'Yes', 'y', 'NO', 'false', 'n', etc.
45     are supported.
46     If `arg` does not represent a boolean, an exception is raised.
47     :param arg : value being converted
48     :return result of conversion: `True` or `False`
49     """
50     if isinstance(arg, bool):
51         return arg
52     if isinstance(arg, bytes):
53         arg = arg.decode('utf-8', errors='ignore')
54     if isinstance(arg, str):
55         if arg.lower() in ('yes', 'y', 'true', 't'):
56             return True
57         if arg.lower() in ('no', 'n', 'false', 'f'):
58             return False
59     raise ValueError('Not a boolean value')
60
61 # ---
62
63 def getORBcfgInfo():
64     """
65     Get current omniORB configuration.
66
67     The information is retrieved from the omniORB configuration file defined
68     by the OMNIORB_CONFIG environment variable.
69     If omniORB configuration file can not be accessed, a tuple of three empty
70     strings is returned.
71
72     :return tuple of three strings: (orb_version, host_name, port_number)
73     """
74     orb_version = ''
75     hostname = ''
76     port_number = ''
77     with suppress(Exception), open(os.getenv('OMNIORB_CONFIG')) as forb:
78         regvar = re.compile(r'(ORB)?InitRef.*corbaname::(.*):(\d+)\s*$')
79         for line in forb.readlines():
80             match = regvar.match(line)
81             if match:
82                 orb_version = '4' if match.group(1) is None else '3'
83                 hostname = match.group(2)
84                 port_number = match.group(3)
85                 break
86     return orb_version, hostname, port_number
87
88 # ---
89
90 def getHostFromORBcfg():
91     """
92     Get current omniORB host name.
93     :return host name
94     """
95     return getORBcfgInfo()[1]
96
97 # ---
98
99 def getPortFromORBcfg():
100     """
101     Get current omniORB port.
102     :return port number
103     """
104     return getORBcfgInfo()[2]
105
106 # ---
107
108 def getUserName():
109     """
110     Get user name.
111
112     The following procedure is perfomed to deduce user name:
113     1. try USER (USERNAME on Windows) environment variable.
114     2. if (1) fails, try LOGNAME (un*x only).
115     3. if (2) fails, return 'unknown' as default user name
116
117     :return user name
118     """
119     return os.getenv('USERNAME', 'unknown') if sys.platform == 'win32' \
120         else os.getenv('USER', os.getenv('LOGNAME', 'unknown'))
121
122 # ---
123
124 def getHostName():
125     """
126     Get host name.
127
128     The following procedure is perfomed to deduce host name:
129     1. try socket python module, gethostname() function
130     2. if (1) fails, try HOSTNAME environment variable
131     3. if (2) fails, try HOST environment variable
132     4. if (3) fails, tries 'unknown' as default host name
133     5. finally, checks that IP is configured for hostname; if not, returns 'localhost'
134
135     :return host name
136     """
137     host = None
138     with suppress(Exception):
139         host = socket.gethostname()
140     if not host:
141         host = os.getenv('HOSTNAME', os.getenv('HOST', 'unknown'))
142     try:
143         # the following line just checks that IP is configured for hostname
144         socket.gethostbyname(host)
145     except (TypeError, OSError):
146         host = 'localhost'
147     return host
148
149 # ---
150
151 def getShortHostName():
152     """
153     Get short host name (with domain stripped).
154     See `getHostName()` for more details.
155     :return short host name
156     """
157     with suppress(AttributeError, IndexError):
158         return getHostName().split('.')[0]
159     return 'unknown' # default host name
160
161 # ---
162
163 def getAppName():
164     """
165     Get application name.
166     The following procedure is perfomed to deduce application name:
167     1. try APPNAME environment variable
168     2. if (1) fails, return 'SALOME' as default application name
169     :return application name
170     """
171     return os.getenv('APPNAME', 'SALOME') # 'SALOME' is default user name
172
173 # ---
174
175 def getPortNumber(use_default=True):
176     """
177     Get currently used omniORB port.
178     The following procedure is perfomed to deduce port number:
179     1. try NSPORT environment variable
180     2. if (1) fails, try to parse config file defined by OMNIORB_CONFIG environment variable
181     3. if (2) fails, return 2809 as default port number (if use_default is `True`) or `None`
182        (if use_default is `False`)
183     :return port number
184     """
185     with suppress(TypeError, ValueError):
186         return int(os.getenv('NSPORT'))
187     with suppress(TypeError, ValueError):
188         port = int(getPortFromORBcfg())
189         if port:
190             return port
191     return 2809 if use_default else None
192
193 # ---
194
195 def getHomeDir():
196     """
197     Get home directory.
198     :return home directory path
199     """
200     return osp.realpath(osp.expanduser('~'))
201
202 # ---
203
204 def getLogDir():
205     """
206     Get directory that stores log files.
207     :return path to the log directory
208     """
209     return osp.join(getTmpDir(), 'logs', getUserName())
210
211 # ---
212
213 def getTmpDir():
214     """
215     Get directory to store temporary files.
216     :return temporary directory path
217     """
218     with tempfile.NamedTemporaryFile() as tmp:
219         return osp.dirname(tmp.name)
220     return None
221
222 # ---
223
224 # pragma pylint: disable=too-many-arguments
225 def generateFileName(path, prefix=None, suffix=None, extension=None,
226                      unique=False, separator='_', hidden=False, **kwargs):
227     """
228     Generate file name.
229
230     :param path      : directory path
231     :param prefix    : file name prefix (none by default)
232     :param suffix    : file name suffix (none by default)
233     :param extension : file extension (none by default)
234     :param unique    : if `True`, function generates unique file name -
235                        in this case, if file with the generated name already
236                        exists in `path` directory, an integer suffix is appended
237                        to the file name (`False` by default)
238     :param separator : words separator ('_' by default)
239     :param hidden    : if `True`, file name is prepended with dot symbol
240                        (`False` by default)
241     :param kwargs    : additional keywrods arguments (see below)
242     :return generated file name
243
244     Additionally supported keyword parameters:
245     - with_username : use user name:
246     - with_hostname : use host name:
247     - with_port : use port number:
248     - with_app      : use application name:
249
250     Any of these keyword arguments can accept either explicit string value,
251     or `True` to automatically deduce value from current configuration.
252     """
253     filename = []
254
255     def _with_str(_str):
256         _str = '' if _str is None else str(_str)
257         if _str:
258             filename.append(_str)
259
260     def _with_kwarg(_kwarg, _func):
261         _value = kwargs.get(_kwarg, False)
262         try:
263             if _try_bool(_value):
264                 filename.append(str(_func()))
265         except ValueError:
266             _with_str(_value)
267
268     _with_str(prefix)
269     _with_kwarg('with_username', getUserName)
270     _with_kwarg('with_hostname', getShortHostName)
271     _with_kwarg('with_port', getPortNumber)
272     _with_kwarg('with_app', getAppName)
273     _with_str(suffix)
274
275     # raise an exception if file name is empty
276     if not filename:
277         raise ValueError('Empty file name')
278
279     # extension
280     extension = '' if extension is None else str(extension)
281     if extension.startswith('.'):
282         extension = extension[1:]
283
284     # separator
285     separator = '' if separator is None else str(separator)
286
287     def _generate(_index=None):
288         # join all components together, add index if necessary
289         if _index is not None:
290             _name = separator.join(filename+[str(_index)])
291         else:
292             _name = separator.join(filename)
293         # prepend with dot if necessary
294         if hidden:
295             _name = '.' + _name
296         # append extension if ncessary
297         if extension:
298             _name = _name + '.' + extension
299         # now get full path
300         return osp.join(path, _name)
301
302     name = _generate()
303     if unique:
304         index = 0
305         while osp.exists(name):
306             index = index + 1
307             name = _generate(index)
308     return osp.normpath(name)
309
310 # ---
311
312 def cleanDir(path):
313     """
314     Clear contents of directory.
315     :param path directory path
316     """
317     if osp.exists(path):
318         for filename in os.listdir(path):
319             file_path = osp.join(path, filename)
320             with suppress(OSError):
321                 if osp.isdir(file_path):
322                     shutil.rmtree(file_path)
323                 else:
324                     os.unlink(file_path)
325
326 # ---
327
328 def makeTmpDir(path, mode=0o777):
329     """
330     Make temporary directory with the specified path.
331     If the directory exists, clear all its contents.
332     :param path : directory path
333     :param mode : access mode
334     """
335     with suppress(OSError):
336         os.makedirs(path, mode=mode, exist_ok=True)
337     cleanDir(path)
338
339 # ---
340
341 def uniteFiles(src_file, dest_file):
342     """
343     Join contents of `src_file` and `dest_file` and put result to `dest_file`.
344     File `dest_file` may not exist.
345     :param src_file  : source file path
346     :param dest_file : destination file path
347     """
348     if not osp.exists(src_file):
349         return
350
351     if osp.exists(dest_file):
352         with suppress(OSError), open(src_file, 'rb') as src, open(dest_file, 'ab') as dest:
353             dest.write(b'\n')
354             dest.write(src.read())
355     else:
356         with suppress(OSError):
357             shutil.copy(src_file, dest_file)
358
359 # --
360
361 def verbose():
362     """
363     Get current verbosity level.
364
365     Default verbosity level is specified via the environment variable SALOME_VERBOSE,
366     e.g. in bash:
367
368         $ export SALOME_VERBOSE=1
369
370     The function `setVerbose()` can be used to explicitly set verbosity level.
371
372     :return current verbosity level
373     """
374     if not hasattr(verbose, 'verbosity_level'):
375         verbose.verbosity_level = 0 # default value
376         with suppress(TypeError, ValueError):
377             # from SALOME_VERBOSE environment variable
378             verbose.verbosity_level = int(os.getenv('SALOME_VERBOSE', '0'))
379     return verbose.verbosity_level
380 # --
381
382 def setVerbose(level):
383     """
384     Change verbosity level.
385     The function `verbose()` can be used to get current verbosity level.
386     :param level : verbosity level
387     """
388     with suppress(TypeError, ValueError):
389         verbose.verbosity_level = int(level)
390 # --
391
392 def killPid(pid, sig=9):
393     """
394     Send signal `sig` to the process with given `pid`.
395
396     :param pid : PID of the process
397     :param sig : signal to send; some of possible values:
398        - 9 : kill process
399        - 0 : do nothing, just check process existence (see below)
400        NOTE: other values are not processed on Windows
401     :return result of execution:
402        -  1 : success
403        -  0 : fail, no such process
404        - -1 : fail, another reason
405     """
406     if not pid:
407         return -1
408
409     with suppress(ValueError):
410         pid = int(pid)
411
412     if sig == 0:
413         ret = 1 if psutil.pid_exists(pid) else 0
414     else:
415         if verbose():
416             print("######## killPid pid = ", pid)
417         try:
418             process = psutil.Process(pid)
419             process.terminate()
420             _, alive = psutil.wait_procs([process], timeout=5)
421             for proc in alive:
422                 proc.kill()
423             ret = 1
424         except psutil.NoSuchProcess:
425             ret = 0
426         except OSError:
427             ret = -1
428     return ret
429 # --
430
431 def getOmniNamesPid(port):
432     """
433     Get PID of omniNames process running on given `port`.
434     :param port : port number
435     :return omniNames process's PID
436     """
437     processes = {p.info['pid']: p.info['name'] for p in psutil.process_iter(['pid', 'name'])}
438     return next((c.pid for c in psutil.net_connections(kind='inet') \
439                      if c.laddr.port == port and processes.get(c.pid) == 'omniNames'), None)
440 # --
441
442 def killOmniNames(port):
443     """
444     Kill omniNames process running on given `port`.
445     :param port : port number
446     """
447     with suppress(Exception):
448         killPid(getOmniNamesPid(port))
449 # --