1#!/usr/bin/env python3
2
3# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Cross-platform lib for process and system monitoring in Python."""
8
9from __future__ import print_function
10import contextlib
11import io
12import os
13import platform
14import re
15import shutil
16import struct
17import subprocess
18import sys
19import tempfile
20import warnings
21
22with warnings.catch_warnings():
23    warnings.simplefilter("ignore")
24    try:
25        import setuptools
26        from setuptools import setup, Extension
27    except ImportError:
28        setuptools = None
29        from distutils.core import setup, Extension
30
31HERE = os.path.abspath(os.path.dirname(__file__))
32
33# ...so we can import _common.py and _compat.py
34sys.path.insert(0, os.path.join(HERE, "psutil"))
35
36from _common import AIX  # NOQA
37from _common import BSD  # NOQA
38from _common import FREEBSD  # NOQA
39from _common import hilite  # NOQA
40from _common import LINUX  # NOQA
41from _common import MACOS  # NOQA
42from _common import NETBSD  # NOQA
43from _common import OPENBSD  # NOQA
44from _common import DRAGONFLY  # NOQA
45from _common import POSIX  # NOQA
46from _common import SUNOS  # NOQA
47from _common import WINDOWS  # NOQA
48from _compat import PY3  # NOQA
49from _compat import which  # NOQA
50
51
52PYPY = '__pypy__' in sys.builtin_module_names
53macros = []
54if POSIX:
55    macros.append(("PSUTIL_POSIX", 1))
56if BSD:
57    macros.append(("PSUTIL_BSD", 1))
58
59# Needed to determine _Py_PARSE_PID in case it's missing (Python 2, PyPy).
60# Taken from Lib/test/test_fcntl.py.
61# XXX: not bullet proof as the (long long) case is missing.
62if struct.calcsize('l') <= 8:
63    macros.append(('PSUTIL_SIZEOF_PID_T', '4'))  # int
64else:
65    macros.append(('PSUTIL_SIZEOF_PID_T', '8'))  # long
66
67
68sources = ['psutil/_psutil_common.c']
69if POSIX:
70    sources.append('psutil/_psutil_posix.c')
71
72
73extras_require = {"test": [
74    "enum34; python_version <= '3.4'",
75    "ipaddress; python_version < '3.0'",
76    "mock; python_version < '3.0'",
77    "unittest2; python_version < '3.0'",
78]}
79if not PYPY:
80    extras_require['test'].extend([
81        "pywin32; sys.platform == 'win32'",
82        "wmi; sys.platform == 'win32'"])
83
84
85def get_version():
86    INIT = os.path.join(HERE, 'psutil/__init__.py')
87    with open(INIT, 'r') as f:
88        for line in f:
89            if line.startswith('__version__'):
90                ret = eval(line.strip().split(' = ')[1])
91                assert ret.count('.') == 2, ret
92                for num in ret.split('.'):
93                    assert num.isdigit(), ret
94                return ret
95        raise ValueError("couldn't find version string")
96
97
98VERSION = get_version()
99macros.append(('PSUTIL_VERSION', int(VERSION.replace('.', ''))))
100
101
102def get_description():
103    script = os.path.join(HERE, "scripts", "internal", "convert_readme.py")
104    readme = os.path.join(HERE, 'README.rst')
105    p = subprocess.Popen([sys.executable, script, readme],
106                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
107    stdout, stderr = p.communicate()
108    if p.returncode != 0:
109        raise RuntimeError(stderr)
110    data = stdout.decode('utf8')
111    if WINDOWS:
112        data = data.replace('\r\n', '\n')
113    return data
114
115
116@contextlib.contextmanager
117def silenced_output(stream_name):
118    class DummyFile(io.BytesIO):
119        # see: https://github.com/giampaolo/psutil/issues/678
120        errors = "ignore"
121
122        def write(self, s):
123            pass
124
125    orig = getattr(sys, stream_name)
126    try:
127        setattr(sys, stream_name, DummyFile())
128        yield
129    finally:
130        setattr(sys, stream_name, orig)
131
132
133def missdeps(msg):
134    s = hilite("C compiler or Python headers are not installed ", color="red")
135    s += hilite("on this system. Try to run:\n", color="red")
136    s += hilite(msg, color="red", bold=True)
137    print(s, file=sys.stderr)
138
139
140if WINDOWS:
141    def get_winver():
142        maj, min = sys.getwindowsversion()[0:2]
143        return '0x0%s' % ((maj * 100) + min)
144
145    if sys.getwindowsversion()[0] < 6:
146        msg = "this Windows version is too old (< Windows Vista); "
147        msg += "psutil 3.4.2 is the latest version which supports Windows "
148        msg += "2000, XP and 2003 server"
149        raise RuntimeError(msg)
150
151    macros.append(("PSUTIL_WINDOWS", 1))
152    macros.extend([
153        # be nice to mingw, see:
154        # http://www.mingw.org/wiki/Use_more_recent_defined_functions
155        ('_WIN32_WINNT', get_winver()),
156        ('_AVAIL_WINVER_', get_winver()),
157        ('_CRT_SECURE_NO_WARNINGS', None),
158        # see: https://github.com/giampaolo/psutil/issues/348
159        ('PSAPI_VERSION', 1),
160    ])
161
162    ext = Extension(
163        'psutil._psutil_windows',
164        sources=sources + [
165            'psutil/_psutil_windows.c',
166            'psutil/arch/windows/process_utils.c',
167            'psutil/arch/windows/process_info.c',
168            'psutil/arch/windows/process_handles.c',
169            'psutil/arch/windows/disk.c',
170            'psutil/arch/windows/net.c',
171            'psutil/arch/windows/cpu.c',
172            'psutil/arch/windows/security.c',
173            'psutil/arch/windows/services.c',
174            'psutil/arch/windows/socks.c',
175            'psutil/arch/windows/wmi.c',
176        ],
177        define_macros=macros,
178        libraries=[
179            "psapi", "kernel32", "advapi32", "shell32", "netapi32",
180            "ws2_32", "PowrProf", "pdh",
181        ],
182        # extra_compile_args=["/W 4"],
183        # extra_link_args=["/DEBUG"]
184    )
185
186elif MACOS:
187    macros.append(("PSUTIL_OSX", 1))
188    ext = Extension(
189        'psutil._psutil_osx',
190        sources=sources + [
191            'psutil/_psutil_osx.c',
192            'psutil/arch/osx/process_info.c',
193        ],
194        define_macros=macros,
195        extra_link_args=[
196            '-framework', 'CoreFoundation', '-framework', 'IOKit'
197        ])
198
199elif FREEBSD:
200    macros.append(("PSUTIL_FREEBSD", 1))
201    ext = Extension(
202        'psutil._psutil_bsd',
203        sources=sources + [
204            'psutil/_psutil_bsd.c',
205            'psutil/arch/freebsd/specific.c',
206            'psutil/arch/freebsd/sys_socks.c',
207            'psutil/arch/freebsd/proc_socks.c',
208        ],
209        define_macros=macros,
210        libraries=["devstat"])
211
212elif OPENBSD:
213    macros.append(("PSUTIL_OPENBSD", 1))
214    ext = Extension(
215        'psutil._psutil_bsd',
216        sources=sources + [
217            'psutil/_psutil_bsd.c',
218            'psutil/arch/openbsd/specific.c',
219        ],
220        define_macros=macros,
221        libraries=["kvm"])
222
223elif NETBSD:
224    macros.append(("PSUTIL_NETBSD", 1))
225    ext = Extension(
226        'psutil._psutil_bsd',
227        sources=sources + [
228            'psutil/_psutil_bsd.c',
229            'psutil/arch/netbsd/specific.c',
230            'psutil/arch/netbsd/socks.c',
231        ],
232        define_macros=macros,
233        libraries=["kvm"])
234
235elif DRAGONFLY:
236    macros.append(("PSUTIL_DRAGONFLY", 1))
237    ext = Extension(
238        'psutil._psutil_bsd',
239        sources=sources + [
240            'psutil/_psutil_bsd.c',
241            'psutil/arch/bsd/dragonfly.c',
242        ],
243        define_macros=macros,
244        libraries=["kvm"])
245
246elif LINUX:
247    def get_ethtool_macro():
248        # see: https://github.com/giampaolo/psutil/issues/659
249        from distutils.unixccompiler import UnixCCompiler
250        from distutils.errors import CompileError
251
252        with tempfile.NamedTemporaryFile(
253                suffix='.c', delete=False, mode="wt") as f:
254            f.write("#include <linux/ethtool.h>")
255
256        output_dir = tempfile.mkdtemp()
257        try:
258            compiler = UnixCCompiler()
259            # https://github.com/giampaolo/psutil/pull/1568
260            if os.getenv('CC'):
261                compiler.set_executable('compiler_so', os.getenv('CC'))
262            with silenced_output('stderr'):
263                with silenced_output('stdout'):
264                    compiler.compile([f.name], output_dir=output_dir)
265        except CompileError:
266            return ("PSUTIL_ETHTOOL_MISSING_TYPES", 1)
267        else:
268            return None
269        finally:
270            os.remove(f.name)
271            shutil.rmtree(output_dir)
272
273    macros.append(("PSUTIL_LINUX", 1))
274    ETHTOOL_MACRO = get_ethtool_macro()
275    if ETHTOOL_MACRO is not None:
276        macros.append(ETHTOOL_MACRO)
277    ext = Extension(
278        'psutil._psutil_linux',
279        sources=sources + ['psutil/_psutil_linux.c'],
280        define_macros=macros)
281
282elif SUNOS:
283    macros.append(("PSUTIL_SUNOS", 1))
284    ext = Extension(
285        'psutil._psutil_sunos',
286        sources=sources + [
287            'psutil/_psutil_sunos.c',
288            'psutil/arch/solaris/v10/ifaddrs.c',
289            'psutil/arch/solaris/environ.c'
290        ],
291        define_macros=macros,
292        libraries=['kstat', 'nsl', 'socket'])
293
294elif AIX:
295    macros.append(("PSUTIL_AIX", 1))
296    ext = Extension(
297        'psutil._psutil_aix',
298        sources=sources + [
299            'psutil/_psutil_aix.c',
300            'psutil/arch/aix/net_connections.c',
301            'psutil/arch/aix/common.c',
302            'psutil/arch/aix/ifaddrs.c'],
303        libraries=['perfstat'],
304        define_macros=macros)
305
306else:
307    sys.exit('platform %s is not supported' % sys.platform)
308
309
310if POSIX:
311    posix_extension = Extension(
312        'psutil._psutil_posix',
313        define_macros=macros,
314        sources=sources)
315    if SUNOS:
316        def get_sunos_update():
317            # See https://serverfault.com/q/524883
318            # for an explanation of Solaris /etc/release
319            with open('/etc/release') as f:
320                update = re.search(r'(?<=s10s_u)[0-9]{1,2}', f.readline())
321                if update is None:
322                    return 0
323                else:
324                    return int(update.group(0))
325
326        posix_extension.libraries.append('socket')
327        if platform.release() == '5.10':
328            # Detect Solaris 5.10, update >= 4, see:
329            # https://github.com/giampaolo/psutil/pull/1638
330            if get_sunos_update() >= 4:
331                # MIB compliancy starts with SunOS 5.10 Update 4:
332                posix_extension.define_macros.append(('NEW_MIB_COMPLIANT', 1))
333            posix_extension.sources.append('psutil/arch/solaris/v10/ifaddrs.c')
334            posix_extension.define_macros.append(('PSUTIL_SUNOS10', 1))
335        else:
336            # Other releases are by default considered to be new mib compliant.
337            posix_extension.define_macros.append(('NEW_MIB_COMPLIANT', 1))
338    elif AIX:
339        posix_extension.sources.append('psutil/arch/aix/ifaddrs.c')
340
341    extensions = [ext, posix_extension]
342else:
343    extensions = [ext]
344
345
346def main():
347    kwargs = dict(
348        name='psutil',
349        version=VERSION,
350        description=__doc__ .replace('\n', ' ').strip() if __doc__ else '',
351        long_description=get_description(),
352        long_description_content_type='text/x-rst',
353        keywords=[
354            'ps', 'top', 'kill', 'free', 'lsof', 'netstat', 'nice', 'tty',
355            'ionice', 'uptime', 'taskmgr', 'process', 'df', 'iotop', 'iostat',
356            'ifconfig', 'taskset', 'who', 'pidof', 'pmap', 'smem', 'pstree',
357            'monitoring', 'ulimit', 'prlimit', 'smem', 'performance',
358            'metrics', 'agent', 'observability',
359        ],
360        author='Giampaolo Rodola',
361        author_email='g.rodola@gmail.com',
362        url='https://github.com/giampaolo/psutil',
363        platforms='Platform Independent',
364        license='BSD',
365        packages=['psutil', 'psutil.tests'],
366        ext_modules=extensions,
367        classifiers=[
368            'Development Status :: 5 - Production/Stable',
369            'Environment :: Console',
370            'Environment :: Win32 (MS Windows)',
371            'Intended Audience :: Developers',
372            'Intended Audience :: Information Technology',
373            'Intended Audience :: System Administrators',
374            'License :: OSI Approved :: BSD License',
375            'Operating System :: MacOS :: MacOS X',
376            'Operating System :: Microsoft :: Windows :: Windows 10',
377            'Operating System :: Microsoft :: Windows :: Windows 7',
378            'Operating System :: Microsoft :: Windows :: Windows 8',
379            'Operating System :: Microsoft :: Windows :: Windows 8.1',
380            'Operating System :: Microsoft :: Windows :: Windows Server 2003',
381            'Operating System :: Microsoft :: Windows :: Windows Server 2008',
382            'Operating System :: Microsoft :: Windows :: Windows Vista',
383            'Operating System :: Microsoft',
384            'Operating System :: OS Independent',
385            'Operating System :: POSIX :: AIX',
386            'Operating System :: POSIX :: BSD :: FreeBSD',
387            'Operating System :: POSIX :: BSD :: NetBSD',
388            'Operating System :: POSIX :: BSD :: OpenBSD',
389            'Operating System :: POSIX :: BSD',
390            'Operating System :: POSIX :: Linux',
391            'Operating System :: POSIX :: SunOS/Solaris',
392            'Operating System :: POSIX',
393            'Programming Language :: C',
394            'Programming Language :: Python :: 2',
395            'Programming Language :: Python :: 2.6',
396            'Programming Language :: Python :: 2.7',
397            'Programming Language :: Python :: 3',
398            'Programming Language :: Python :: Implementation :: CPython',
399            'Programming Language :: Python :: Implementation :: PyPy',
400            'Programming Language :: Python',
401            'Topic :: Software Development :: Libraries :: Python Modules',
402            'Topic :: Software Development :: Libraries',
403            'Topic :: System :: Benchmark',
404            'Topic :: System :: Hardware :: Hardware Drivers',
405            'Topic :: System :: Hardware',
406            'Topic :: System :: Monitoring',
407            'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
408            'Topic :: System :: Networking :: Monitoring',
409            'Topic :: System :: Networking',
410            'Topic :: System :: Operating System',
411            'Topic :: System :: Systems Administration',
412            'Topic :: Utilities',
413        ],
414    )
415    if setuptools is not None:
416        kwargs.update(
417            python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
418            extras_require=extras_require,
419            zip_safe=False,
420        )
421    success = False
422    try:
423        setup(**kwargs)
424        success = True
425    finally:
426        if not success and POSIX and not which('gcc'):
427            py3 = "3" if PY3 else ""
428            if LINUX:
429                if which('dpkg'):
430                    missdeps("sudo apt-get install gcc python%s-dev" % py3)
431                elif which('rpm'):
432                    missdeps("sudo yum install gcc python%s-devel" % py3)
433            elif MACOS:
434                print(hilite("XCode (https://developer.apple.com/xcode/) "
435                             "is not installed"), color="red", file=sys.stderr)
436            elif FREEBSD:
437                missdeps("pkg install gcc python%s" % py3)
438            elif OPENBSD:
439                missdeps("pkg_add -v gcc python%s" % py3)
440            elif NETBSD:
441                missdeps("pkgin install gcc python%s" % py3)
442            elif SUNOS:
443                missdeps("sudo ln -s /usr/bin/gcc /usr/local/bin/cc && "
444                         "pkg install gcc")
445
446
447if __name__ == '__main__':
448    main()
449