From 9f79fd54c48ca550a0fd93e4c3eddffe4102adbc Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Tue, 31 Oct 2023 13:48:37 -0400 Subject: [PATCH] chore: re-vendor psutil and wrapt (#7332) Reverts #6651 Fixes #7316 Closes #7202 ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. --------- Co-authored-by: Zachary Groves <32471391+ZStriker19@users.noreply.github.com> Co-authored-by: Brett Langdon --- ddtrace/_monkey.py | 2 +- ddtrace/appsec/_handlers.py | 3 +- ddtrace/appsec/_iast/_patch.py | 5 +- ddtrace/appsec/_iast/_patch_modules.py | 2 +- ddtrace/contrib/aiobotocore/patch.py | 2 +- ddtrace/contrib/aiohttp/patch.py | 2 +- ddtrace/contrib/aiomysql/patch.py | 2 +- ddtrace/contrib/aiopg/connection.py | 2 +- ddtrace/contrib/aiopg/patch.py | 2 +- ddtrace/contrib/aioredis/patch.py | 2 +- ddtrace/contrib/algoliasearch/patch.py | 3 +- ddtrace/contrib/aredis/patch.py | 2 +- ddtrace/contrib/asyncio/patch.py | 4 +- ddtrace/contrib/asyncpg/patch.py | 2 +- ddtrace/contrib/boto/patch.py | 4 +- ddtrace/contrib/botocore/patch.py | 2 +- ddtrace/contrib/bottle/patch.py | 2 +- ddtrace/contrib/cassandra/session.py | 3 +- ddtrace/contrib/consul/patch.py | 2 +- ddtrace/contrib/dbapi/__init__.py | 2 +- ddtrace/contrib/django/patch.py | 5 +- ddtrace/contrib/django/restframework.py | 3 +- ddtrace/contrib/django/utils.py | 2 +- ddtrace/contrib/dogpile_cache/patch.py | 3 +- ddtrace/contrib/elasticsearch/patch.py | 3 +- ddtrace/contrib/falcon/patch.py | 2 +- ddtrace/contrib/fastapi/patch.py | 4 +- ddtrace/contrib/flask/patch.py | 3 +- ddtrace/contrib/flask/wrappers.py | 2 +- ddtrace/contrib/flask_login/patch.py | 2 +- .../contrib/grpc/aio_server_interceptor.py | 2 +- ddtrace/contrib/grpc/client_interceptor.py | 2 +- ddtrace/contrib/grpc/patch.py | 2 +- ddtrace/contrib/grpc/server_interceptor.py | 2 +- ddtrace/contrib/httplib/patch.py | 2 +- ddtrace/contrib/httpx/patch.py | 5 +- ddtrace/contrib/jinja2/patch.py | 2 +- ddtrace/contrib/kombu/patch.py | 2 +- ddtrace/contrib/langchain/patch.py | 2 +- ddtrace/contrib/logging/patch.py | 2 +- ddtrace/contrib/loguru/patch.py | 2 +- ddtrace/contrib/mariadb/patch.py | 2 +- ddtrace/contrib/molten/patch.py | 4 +- ddtrace/contrib/molten/wrappers.py | 2 +- ddtrace/contrib/mongoengine/trace.py | 3 +- ddtrace/contrib/mysql/patch.py | 2 +- ddtrace/contrib/mysqldb/patch.py | 2 +- ddtrace/contrib/psycopg/extensions.py | 3 +- ddtrace/contrib/psycopg/patch.py | 3 +- ddtrace/contrib/pylibmc/client.py | 2 +- ddtrace/contrib/pylons/patch.py | 2 +- ddtrace/contrib/pylons/renderer.py | 2 +- ddtrace/contrib/pymemcache/client.py | 2 +- ddtrace/contrib/pymongo/client.py | 2 +- ddtrace/contrib/pymongo/patch.py | 2 +- ddtrace/contrib/pymysql/patch.py | 2 +- ddtrace/contrib/pynamodb/patch.py | 2 +- ddtrace/contrib/pyramid/patch.py | 2 +- ddtrace/contrib/pyramid/trace.py | 2 +- ddtrace/contrib/redis/patch.py | 2 +- ddtrace/contrib/rediscluster/patch.py | 2 +- ddtrace/contrib/requests/patch.py | 2 +- ddtrace/contrib/requests/session.py | 2 +- ddtrace/contrib/sanic/patch.py | 4 +- ddtrace/contrib/snowflake/patch.py | 3 +- ddtrace/contrib/sqlalchemy/patch.py | 3 +- ddtrace/contrib/sqlite3/patch.py | 3 +- ddtrace/contrib/starlette/patch.py | 4 +- ddtrace/contrib/structlog/patch.py | 2 +- ddtrace/contrib/tornado/patch.py | 2 +- ddtrace/contrib/trace_utils.py | 3 +- ddtrace/contrib/unittest/patch.py | 3 +- ddtrace/contrib/urllib3/patch.py | 2 +- ddtrace/contrib/vertica/patch.py | 3 +- ddtrace/contrib/wsgi/wsgi.py | 2 +- ddtrace/contrib/yaaredis/patch.py | 2 +- ddtrace/debugging/_function/discovery.py | 3 +- ddtrace/internal/compat.py | 5 +- ddtrace/internal/forksafe.py | 2 +- ddtrace/internal/runtime/metric_collectors.py | 4 +- ddtrace/internal/safety.py | 3 +- ddtrace/internal/utils/wrappers.py | 2 +- ddtrace/pin.py | 3 +- ddtrace/profiling/collector/_lock.py | 4 +- ddtrace/profiling/collector/_task.pyx | 2 +- ddtrace/settings/profiling.py | 2 +- ddtrace/sourcecode/setuptools_auto.py | 2 +- ddtrace/tracing/trace_handlers.py | 3 +- ddtrace/vendor/__init__.py | 20 + ddtrace/vendor/debtcollector/moves.py | 2 +- ddtrace/vendor/debtcollector/removals.py | 2 +- ddtrace/vendor/debtcollector/renames.py | 2 +- ddtrace/vendor/debtcollector/updating.py | 2 +- ddtrace/vendor/psutil/__init__.py | 2516 +++++++++++ ddtrace/vendor/psutil/_common.py | 651 +++ ddtrace/vendor/psutil/_compat.py | 332 ++ ddtrace/vendor/psutil/_psaix.py | 554 +++ ddtrace/vendor/psutil/_psbsd.py | 905 ++++ ddtrace/vendor/psutil/_pslinux.py | 2096 ++++++++++ ddtrace/vendor/psutil/_psosx.py | 568 +++ ddtrace/vendor/psutil/_psposix.py | 179 + ddtrace/vendor/psutil/_pssunos.py | 720 ++++ ddtrace/vendor/psutil/_psutil_aix.c | 1137 +++++ ddtrace/vendor/psutil/_psutil_bsd.c | 1093 +++++ ddtrace/vendor/psutil/_psutil_common.c | 132 + ddtrace/vendor/psutil/_psutil_common.h | 31 + ddtrace/vendor/psutil/_psutil_linux.c | 668 +++ ddtrace/vendor/psutil/_psutil_osx.c | 1905 +++++++++ ddtrace/vendor/psutil/_psutil_posix.c | 691 +++ ddtrace/vendor/psutil/_psutil_posix.h | 8 + ddtrace/vendor/psutil/_psutil_sunos.c | 1776 ++++++++ ddtrace/vendor/psutil/_psutil_windows.c | 3723 +++++++++++++++++ ddtrace/vendor/psutil/_pswindows.py | 1127 +++++ ddtrace/vendor/psutil/arch/aix/common.c | 79 + ddtrace/vendor/psutil/arch/aix/common.h | 31 + ddtrace/vendor/psutil/arch/aix/ifaddrs.c | 149 + ddtrace/vendor/psutil/arch/aix/ifaddrs.h | 35 + .../vendor/psutil/arch/aix/net_connections.c | 287 ++ .../vendor/psutil/arch/aix/net_connections.h | 15 + .../psutil/arch/aix/net_kernel_structs.h | 111 + .../vendor/psutil/arch/freebsd/proc_socks.c | 368 ++ .../vendor/psutil/arch/freebsd/proc_socks.h | 9 + ddtrace/vendor/psutil/arch/freebsd/specific.c | 1115 +++++ ddtrace/vendor/psutil/arch/freebsd/specific.h | 34 + .../vendor/psutil/arch/freebsd/sys_socks.c | 362 ++ .../vendor/psutil/arch/freebsd/sys_socks.h | 10 + ddtrace/vendor/psutil/arch/netbsd/socks.c | 447 ++ ddtrace/vendor/psutil/arch/netbsd/socks.h | 10 + ddtrace/vendor/psutil/arch/netbsd/specific.c | 684 +++ ddtrace/vendor/psutil/arch/netbsd/specific.h | 29 + ddtrace/vendor/psutil/arch/openbsd/specific.c | 791 ++++ ddtrace/vendor/psutil/arch/openbsd/specific.h | 27 + ddtrace/vendor/psutil/arch/osx/process_info.c | 382 ++ ddtrace/vendor/psutil/arch/osx/process_info.h | 18 + ddtrace/vendor/psutil/arch/solaris/environ.c | 405 ++ ddtrace/vendor/psutil/arch/solaris/environ.h | 19 + .../vendor/psutil/arch/solaris/v10/ifaddrs.c | 125 + .../vendor/psutil/arch/solaris/v10/ifaddrs.h | 26 + ddtrace/vendor/psutil/arch/windows/global.c | 234 ++ ddtrace/vendor/psutil/arch/windows/global.h | 77 + .../vendor/psutil/arch/windows/inet_ntop.c | 45 + .../vendor/psutil/arch/windows/inet_ntop.h | 17 + ddtrace/vendor/psutil/arch/windows/ntextapi.h | 507 +++ .../psutil/arch/windows/process_handles.c | 513 +++ .../psutil/arch/windows/process_handles.h | 10 + .../vendor/psutil/arch/windows/process_info.c | 1027 +++++ .../vendor/psutil/arch/windows/process_info.h | 31 + ddtrace/vendor/psutil/arch/windows/security.c | 138 + ddtrace/vendor/psutil/arch/windows/security.h | 13 + ddtrace/vendor/psutil/arch/windows/services.c | 485 +++ ddtrace/vendor/psutil/arch/windows/services.h | 17 + ddtrace/vendor/psutil/arch/windows/wmi.c | 115 + ddtrace/vendor/psutil/arch/windows/wmi.h | 12 + ddtrace/vendor/psutil/setup.py | 227 + ddtrace/vendor/wrapt/LICENSE | 24 + ddtrace/vendor/wrapt/__init__.py | 27 + ddtrace/vendor/wrapt/_wrappers.c | 3199 ++++++++++++++ ddtrace/vendor/wrapt/arguments.py | 38 + ddtrace/vendor/wrapt/decorators.py | 541 +++ ddtrace/vendor/wrapt/importer.py | 286 ++ ddtrace/vendor/wrapt/setup.py | 7 + ddtrace/vendor/wrapt/wrappers.py | 1001 +++++ docs/contributing-integrations.rst | 2 +- pyproject.toml | 2 - .../revendor-psutil-1d24b2b3a27cf4a5.yaml | 4 + setup.cfg | 2 +- setup.py | 4 +- tests/appsec/appsec_utils.py | 3 +- tests/appsec/iast/test_iast_mem_check.py | 3 +- tests/contrib/aioredis/test_aioredis.py | 2 +- tests/contrib/aredis/test_aredis.py | 2 +- tests/contrib/asyncio/test_patch.py | 2 +- tests/contrib/asynctest/test_asynctest.py | 2 +- tests/contrib/consul/test.py | 2 +- tests/contrib/django/test_django.py | 2 +- tests/contrib/flask/__init__.py | 2 +- tests/contrib/flask/test_idempotency.py | 2 +- .../flask_autopatch/test_flask_autopatch.py | 2 +- tests/contrib/httplib/test_httplib.py | 2 +- .../httplib/test_httplib_distributed.py | 3 +- tests/contrib/httpx/test_httpx.py | 2 +- tests/contrib/httpx/test_httpx_pre_0_11.py | 2 +- tests/contrib/logging/test_logging.py | 2 +- tests/contrib/patch.py | 5 +- .../contrib/psycopg/test_psycopg_snapshot.py | 2 +- .../contrib/psycopg2/test_psycopg_snapshot.py | 2 +- tests/contrib/pymemcache/autopatch/test.py | 3 +- tests/contrib/pymemcache/test_client.py | 2 +- tests/contrib/redis/test_redis_asyncio.py | 2 +- .../tornado/test_executor_decorator.py | 2 +- tests/contrib/vertica/test_vertica.py | 2 +- tests/contrib/yaaredis/test_yaaredis.py | 2 +- tests/debugging/function/test_discovery.py | 2 +- tests/debugging/test_debugger.py | 2 +- .../tracer/runtime/test_metric_collectors.py | 2 +- tests/utils.py | 2 +- 196 files changed, 35138 insertions(+), 164 deletions(-) create mode 100644 ddtrace/vendor/psutil/__init__.py create mode 100644 ddtrace/vendor/psutil/_common.py create mode 100644 ddtrace/vendor/psutil/_compat.py create mode 100644 ddtrace/vendor/psutil/_psaix.py create mode 100644 ddtrace/vendor/psutil/_psbsd.py create mode 100644 ddtrace/vendor/psutil/_pslinux.py create mode 100644 ddtrace/vendor/psutil/_psosx.py create mode 100644 ddtrace/vendor/psutil/_psposix.py create mode 100644 ddtrace/vendor/psutil/_pssunos.py create mode 100644 ddtrace/vendor/psutil/_psutil_aix.c create mode 100644 ddtrace/vendor/psutil/_psutil_bsd.c create mode 100644 ddtrace/vendor/psutil/_psutil_common.c create mode 100644 ddtrace/vendor/psutil/_psutil_common.h create mode 100644 ddtrace/vendor/psutil/_psutil_linux.c create mode 100644 ddtrace/vendor/psutil/_psutil_osx.c create mode 100644 ddtrace/vendor/psutil/_psutil_posix.c create mode 100644 ddtrace/vendor/psutil/_psutil_posix.h create mode 100644 ddtrace/vendor/psutil/_psutil_sunos.c create mode 100644 ddtrace/vendor/psutil/_psutil_windows.c create mode 100644 ddtrace/vendor/psutil/_pswindows.py create mode 100644 ddtrace/vendor/psutil/arch/aix/common.c create mode 100644 ddtrace/vendor/psutil/arch/aix/common.h create mode 100644 ddtrace/vendor/psutil/arch/aix/ifaddrs.c create mode 100644 ddtrace/vendor/psutil/arch/aix/ifaddrs.h create mode 100644 ddtrace/vendor/psutil/arch/aix/net_connections.c create mode 100644 ddtrace/vendor/psutil/arch/aix/net_connections.h create mode 100644 ddtrace/vendor/psutil/arch/aix/net_kernel_structs.h create mode 100644 ddtrace/vendor/psutil/arch/freebsd/proc_socks.c create mode 100644 ddtrace/vendor/psutil/arch/freebsd/proc_socks.h create mode 100644 ddtrace/vendor/psutil/arch/freebsd/specific.c create mode 100644 ddtrace/vendor/psutil/arch/freebsd/specific.h create mode 100644 ddtrace/vendor/psutil/arch/freebsd/sys_socks.c create mode 100644 ddtrace/vendor/psutil/arch/freebsd/sys_socks.h create mode 100644 ddtrace/vendor/psutil/arch/netbsd/socks.c create mode 100644 ddtrace/vendor/psutil/arch/netbsd/socks.h create mode 100644 ddtrace/vendor/psutil/arch/netbsd/specific.c create mode 100644 ddtrace/vendor/psutil/arch/netbsd/specific.h create mode 100644 ddtrace/vendor/psutil/arch/openbsd/specific.c create mode 100644 ddtrace/vendor/psutil/arch/openbsd/specific.h create mode 100644 ddtrace/vendor/psutil/arch/osx/process_info.c create mode 100644 ddtrace/vendor/psutil/arch/osx/process_info.h create mode 100644 ddtrace/vendor/psutil/arch/solaris/environ.c create mode 100644 ddtrace/vendor/psutil/arch/solaris/environ.h create mode 100644 ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.c create mode 100644 ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.h create mode 100644 ddtrace/vendor/psutil/arch/windows/global.c create mode 100644 ddtrace/vendor/psutil/arch/windows/global.h create mode 100644 ddtrace/vendor/psutil/arch/windows/inet_ntop.c create mode 100644 ddtrace/vendor/psutil/arch/windows/inet_ntop.h create mode 100644 ddtrace/vendor/psutil/arch/windows/ntextapi.h create mode 100644 ddtrace/vendor/psutil/arch/windows/process_handles.c create mode 100644 ddtrace/vendor/psutil/arch/windows/process_handles.h create mode 100644 ddtrace/vendor/psutil/arch/windows/process_info.c create mode 100644 ddtrace/vendor/psutil/arch/windows/process_info.h create mode 100644 ddtrace/vendor/psutil/arch/windows/security.c create mode 100644 ddtrace/vendor/psutil/arch/windows/security.h create mode 100644 ddtrace/vendor/psutil/arch/windows/services.c create mode 100644 ddtrace/vendor/psutil/arch/windows/services.h create mode 100644 ddtrace/vendor/psutil/arch/windows/wmi.c create mode 100644 ddtrace/vendor/psutil/arch/windows/wmi.h create mode 100644 ddtrace/vendor/psutil/setup.py create mode 100644 ddtrace/vendor/wrapt/LICENSE create mode 100644 ddtrace/vendor/wrapt/__init__.py create mode 100644 ddtrace/vendor/wrapt/_wrappers.c create mode 100644 ddtrace/vendor/wrapt/arguments.py create mode 100644 ddtrace/vendor/wrapt/decorators.py create mode 100644 ddtrace/vendor/wrapt/importer.py create mode 100644 ddtrace/vendor/wrapt/setup.py create mode 100644 ddtrace/vendor/wrapt/wrappers.py create mode 100644 releasenotes/notes/revendor-psutil-1d24b2b3a27cf4a5.yaml diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index dc5e831a647..731db710179 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -3,7 +3,7 @@ import threading from typing import TYPE_CHECKING -from wrapt.importer import when_imported +from ddtrace.vendor.wrapt.importer import when_imported from .internal.compat import PY2 from .internal.logger import get_logger diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index aa9dcf529c5..8e0013b626f 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -2,8 +2,6 @@ import io import json -from wrapt import wrap_function_wrapper as _w -from wrapt.importer import when_imported import xmltodict from ddtrace import config @@ -15,6 +13,7 @@ from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_patch.py b/ddtrace/appsec/_iast/_patch.py index ce80d4f3efd..13fbcbd96bb 100644 --- a/ddtrace/appsec/_iast/_patch.py +++ b/ddtrace/appsec/_iast/_patch.py @@ -3,10 +3,9 @@ import sys from typing import TYPE_CHECKING -from wrapt import FunctionWrapper -from wrapt import resolve_path - from ddtrace.internal.logger import get_logger +from ddtrace.vendor.wrapt import FunctionWrapper +from ddtrace.vendor.wrapt import resolve_path from ._utils import _is_iast_enabled diff --git a/ddtrace/appsec/_iast/_patch_modules.py b/ddtrace/appsec/_iast/_patch_modules.py index 0b4e42953e8..05a6900be83 100644 --- a/ddtrace/appsec/_iast/_patch_modules.py +++ b/ddtrace/appsec/_iast/_patch_modules.py @@ -1,4 +1,4 @@ -from wrapt.importer import when_imported +from ddtrace.vendor.wrapt.importer import when_imported IAST_PATCH = { diff --git a/ddtrace/contrib/aiobotocore/patch.py b/ddtrace/contrib/aiobotocore/patch.py index 64b214b78f0..9e52417947a 100644 --- a/ddtrace/contrib/aiobotocore/patch.py +++ b/ddtrace/contrib/aiobotocore/patch.py @@ -1,11 +1,11 @@ import os import aiobotocore.client -import wrapt from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.utils.version import parse_version +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/aiohttp/patch.py b/ddtrace/contrib/aiohttp/patch.py index b84dd9af267..5cf0bdd24c7 100644 --- a/ddtrace/contrib/aiohttp/patch.py +++ b/ddtrace/contrib/aiohttp/patch.py @@ -1,7 +1,6 @@ import os import aiohttp -import wrapt from yarl import URL from ddtrace import config @@ -11,6 +10,7 @@ from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool +from ddtrace.vendor import wrapt from ...ext import SpanKind from ...ext import SpanTypes diff --git a/ddtrace/contrib/aiomysql/patch.py b/ddtrace/contrib/aiomysql/patch.py index 588f6b0f98b..56a9636b4f3 100644 --- a/ddtrace/contrib/aiomysql/patch.py +++ b/ddtrace/contrib/aiomysql/patch.py @@ -1,5 +1,4 @@ import aiomysql -import wrapt from ddtrace import Pin from ddtrace import config @@ -10,6 +9,7 @@ from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_database_operation from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.vendor import wrapt from ...ext import SpanKind from ...ext import SpanTypes diff --git a/ddtrace/contrib/aiopg/connection.py b/ddtrace/contrib/aiopg/connection.py index a9eeb9c5c3f..3373405d26d 100644 --- a/ddtrace/contrib/aiopg/connection.py +++ b/ddtrace/contrib/aiopg/connection.py @@ -2,7 +2,6 @@ from aiopg import __version__ from aiopg.utils import _ContextManager -import wrapt from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -17,6 +16,7 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.version import parse_version from ddtrace.pin import Pin +from ddtrace.vendor import wrapt AIOPG_VERSION = parse_version(__version__) diff --git a/ddtrace/contrib/aiopg/patch.py b/ddtrace/contrib/aiopg/patch.py index d0b03bc7891..1e15d54f6d7 100644 --- a/ddtrace/contrib/aiopg/patch.py +++ b/ddtrace/contrib/aiopg/patch.py @@ -3,13 +3,13 @@ import aiopg.connection import psycopg2.extensions -import wrapt from ddtrace.contrib.aiopg.connection import AIOTracedConnection from ddtrace.contrib.psycopg.connection import patch_conn as psycopg_patch_conn from ddtrace.contrib.psycopg.extensions import _patch_extensions from ddtrace.contrib.psycopg.extensions import _unpatch_extensions from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor import wrapt def get_version(): diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py index f02be37c907..c249f88d340 100644 --- a/ddtrace/contrib/aioredis/patch.py +++ b/ddtrace/contrib/aioredis/patch.py @@ -3,12 +3,12 @@ import sys import aioredis -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/algoliasearch/patch.py b/ddtrace/contrib/algoliasearch/patch.py index 4529dc97f90..b576e4d1082 100644 --- a/ddtrace/contrib/algoliasearch/patch.py +++ b/ddtrace/contrib/algoliasearch/patch.py @@ -1,5 +1,3 @@ -from wrapt import wrap_function_wrapper as _w - from ddtrace import config from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -8,6 +6,7 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import SPAN_KIND from ...constants import SPAN_MEASURED_KEY diff --git a/ddtrace/contrib/aredis/patch.py b/ddtrace/contrib/aredis/patch.py index c4cd452c480..040a82b8dea 100644 --- a/ddtrace/contrib/aredis/patch.py +++ b/ddtrace/contrib/aredis/patch.py @@ -1,9 +1,9 @@ import os import aredis -import wrapt from ddtrace import config +from ddtrace.vendor import wrapt from ...internal.schema import schematize_service_name from ...internal.utils.formats import CMD_MAX_LEN diff --git a/ddtrace/contrib/asyncio/patch.py b/ddtrace/contrib/asyncio/patch.py index 3475daed889..5dfcc8b7684 100644 --- a/ddtrace/contrib/asyncio/patch.py +++ b/ddtrace/contrib/asyncio/patch.py @@ -1,8 +1,8 @@ import asyncio import sys -from wrapt import ObjectProxy -from wrapt import wrap_function_wrapper as _w +from ddtrace.vendor.wrapt import ObjectProxy +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ..trace_utils import unwrap as _u from .wrappers import wrapped_create_task diff --git a/ddtrace/contrib/asyncpg/patch.py b/ddtrace/contrib/asyncpg/patch.py index dd6e52d29ae..757113df43c 100644 --- a/ddtrace/contrib/asyncpg/patch.py +++ b/ddtrace/contrib/asyncpg/patch.py @@ -1,11 +1,11 @@ from typing import TYPE_CHECKING import asyncpg -import wrapt from ddtrace import Pin from ddtrace import config from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt from ...constants import SPAN_KIND from ...constants import SPAN_MEASURED_KEY diff --git a/ddtrace/contrib/boto/patch.py b/ddtrace/contrib/boto/patch.py index 207ad14b4c9..4db66fbb6b2 100644 --- a/ddtrace/contrib/boto/patch.py +++ b/ddtrace/contrib/boto/patch.py @@ -3,7 +3,6 @@ from boto import __version__ import boto.connection -import wrapt from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -16,6 +15,7 @@ from ddtrace.internal.constants import COMPONENT from ddtrace.internal.utils.wrappers import unwrap from ddtrace.pin import Pin +from ddtrace.vendor import wrapt from ...internal.schema import schematize_cloud_api_operation from ...internal.schema import schematize_service_name @@ -140,7 +140,7 @@ def patched_auth_request(original_func, instance, args, kwargs): # Go up the stack until we get the first non-ddtrace module # DEV: For `lambda.list_functions()` this should be: # - ddtrace.contrib.boto.patch - # - wrapt.wrappers + # - ddtrace.vendor.wrapt.wrappers # - boto.awslambda.layer1 (make_request) # - boto.awslambda.layer1 (list_functions) # But can vary depending on Python versions; that's why we use an heuristic diff --git a/ddtrace/contrib/botocore/patch.py b/ddtrace/contrib/botocore/patch.py index 4a21ddc556f..557378bd652 100644 --- a/ddtrace/contrib/botocore/patch.py +++ b/ddtrace/contrib/botocore/patch.py @@ -18,11 +18,11 @@ from botocore import __version__ import botocore.client import botocore.exceptions -import wrapt from ddtrace import config from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.settings.config import Config +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/bottle/patch.py b/ddtrace/contrib/bottle/patch.py index bf5f8908c1f..d22f076e38e 100644 --- a/ddtrace/contrib/bottle/patch.py +++ b/ddtrace/contrib/bottle/patch.py @@ -1,9 +1,9 @@ import os import bottle -import wrapt from ddtrace import config +from ddtrace.vendor import wrapt from ...internal.utils.formats import asbool from .trace import TracePlugin diff --git a/ddtrace/contrib/cassandra/session.py b/ddtrace/contrib/cassandra/session.py index 136d5f7b632..dd53e5bd8e6 100644 --- a/ddtrace/contrib/cassandra/session.py +++ b/ddtrace/contrib/cassandra/session.py @@ -11,8 +11,6 @@ except AttributeError: from cassandra import cluster as cassandra_cluster -import wrapt - from ddtrace import config from ddtrace.internal.constants import COMPONENT @@ -34,6 +32,7 @@ from ...internal.utils import get_argument_value from ...internal.utils.formats import deep_getattr from ...pin import Pin +from ...vendor import wrapt log = get_logger(__name__) diff --git a/ddtrace/contrib/consul/patch.py b/ddtrace/contrib/consul/patch.py index 4cf00dbac44..41873407c67 100644 --- a/ddtrace/contrib/consul/patch.py +++ b/ddtrace/contrib/consul/patch.py @@ -1,9 +1,9 @@ import consul -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/dbapi/__init__.py b/ddtrace/contrib/dbapi/__init__.py index 2d637671311..43e8d3fd994 100644 --- a/ddtrace/contrib/dbapi/__init__.py +++ b/ddtrace/contrib/dbapi/__init__.py @@ -2,7 +2,6 @@ Generic dbapi tracing code. """ import six -import wrapt from ddtrace import config from ddtrace.appsec._iast._utils import _is_iast_enabled @@ -22,6 +21,7 @@ from ...internal.utils import ArgumentError from ...internal.utils import get_argument_value from ...pin import Pin +from ...vendor import wrapt from ..trace_utils import ext_service from ..trace_utils import iswrapped diff --git a/ddtrace/contrib/django/patch.py b/ddtrace/contrib/django/patch.py index 6116b4b53b7..c7924c37b05 100644 --- a/ddtrace/contrib/django/patch.py +++ b/ddtrace/contrib/django/patch.py @@ -12,9 +12,6 @@ from inspect import isfunction import os -import wrapt -from wrapt.importer import when_imported - from ddtrace import Pin from ddtrace import config from ddtrace.constants import SPAN_KIND @@ -40,6 +37,8 @@ from ddtrace.internal.utils.formats import asbool from ddtrace.settings.asm import config as asm_config from ddtrace.settings.integration import IntegrationConfig +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt.importer import when_imported from ...appsec._utils import _UserInfoRetriever from ...internal.utils import get_argument_value diff --git a/ddtrace/contrib/django/restframework.py b/ddtrace/contrib/django/restframework.py index c4af8b44409..ed0c789efa9 100644 --- a/ddtrace/contrib/django/restframework.py +++ b/ddtrace/contrib/django/restframework.py @@ -1,5 +1,6 @@ import rest_framework.views -from wrapt import wrap_function_wrapper as wrap + +from ddtrace.vendor.wrapt import wrap_function_wrapper as wrap from ..trace_utils import iswrapped from ..trace_utils import with_traced_module diff --git a/ddtrace/contrib/django/utils.py b/ddtrace/contrib/django/utils.py index 4fac3141d06..97490d6a022 100644 --- a/ddtrace/contrib/django/utils.py +++ b/ddtrace/contrib/django/utils.py @@ -9,7 +9,6 @@ import django from django.utils.functional import SimpleLazyObject import six -from wrapt import FunctionWrapper import xmltodict from ddtrace import config @@ -25,6 +24,7 @@ from ...internal import core from ...internal.logger import get_logger from ...internal.utils.formats import stringify_cache_args +from ...vendor.wrapt import FunctionWrapper from .. import trace_utils from .compat import get_resolver from .compat import user_is_authenticated diff --git a/ddtrace/contrib/dogpile_cache/patch.py b/ddtrace/contrib/dogpile_cache/patch.py index f4f41284a29..162151231be 100644 --- a/ddtrace/contrib/dogpile_cache/patch.py +++ b/ddtrace/contrib/dogpile_cache/patch.py @@ -5,12 +5,11 @@ from dogpile import cache as dogpile_cache from dogpile import lock as dogpile_lock -from wrapt import wrap_function_wrapper as _w - from ddtrace.internal.schema import schematize_service_name from ddtrace.pin import _DD_PIN_NAME from ddtrace.pin import _DD_PIN_PROXY_NAME from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from .lock import _wrap_lock_ctor from .region import _wrap_get_create diff --git a/ddtrace/contrib/elasticsearch/patch.py b/ddtrace/contrib/elasticsearch/patch.py index c6991f050e9..161081fdf9c 100644 --- a/ddtrace/contrib/elasticsearch/patch.py +++ b/ddtrace/contrib/elasticsearch/patch.py @@ -1,8 +1,6 @@ from importlib import import_module from typing import List -from wrapt import wrap_function_wrapper as _w - from ddtrace import config from ddtrace._tracing import _limits from ddtrace.contrib.trace_utils import ext_service @@ -10,6 +8,7 @@ from ddtrace.ext import net from ddtrace.internal.constants import COMPONENT from ddtrace.internal.logger import get_logger +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/falcon/patch.py b/ddtrace/contrib/falcon/patch.py index 3a153c147cf..d8deeb6cdb3 100644 --- a/ddtrace/contrib/falcon/patch.py +++ b/ddtrace/contrib/falcon/patch.py @@ -1,10 +1,10 @@ import os import falcon -import wrapt from ddtrace import config from ddtrace import tracer +from ddtrace.vendor import wrapt from ...internal.utils.formats import asbool from ...internal.utils.version import parse_version diff --git a/ddtrace/contrib/fastapi/patch.py b/ddtrace/contrib/fastapi/patch.py index ec7cb748907..051896c81d4 100644 --- a/ddtrace/contrib/fastapi/patch.py +++ b/ddtrace/contrib/fastapi/patch.py @@ -1,7 +1,5 @@ import fastapi import fastapi.routing -from wrapt import ObjectProxy -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config @@ -10,6 +8,8 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor.wrapt import ObjectProxy +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w log = get_logger(__name__) diff --git a/ddtrace/contrib/flask/patch.py b/ddtrace/contrib/flask/patch.py index 053ee7531a8..5a180eec7f6 100644 --- a/ddtrace/contrib/flask/patch.py +++ b/ddtrace/contrib/flask/patch.py @@ -22,10 +22,9 @@ except ImportError: _HAS_JSON_MIXIN = False -from wrapt import wrap_function_wrapper as _w - from ddtrace import Pin from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...contrib.wsgi.wsgi import _DDWSGIMiddlewareBase from ...internal.logger import get_logger diff --git a/ddtrace/contrib/flask/wrappers.py b/ddtrace/contrib/flask/wrappers.py index feeb20ab4b3..8c2628f6eb6 100644 --- a/ddtrace/contrib/flask/wrappers.py +++ b/ddtrace/contrib/flask/wrappers.py @@ -1,8 +1,8 @@ import flask -from wrapt import function_wrapper from ddtrace import config from ddtrace.internal import core +from ddtrace.vendor.wrapt import function_wrapper from ...internal.logger import get_logger from ...internal.utils.importlib import func_name diff --git a/ddtrace/contrib/flask_login/patch.py b/ddtrace/contrib/flask_login/patch.py index 0f92cf8112d..f87bf961b55 100644 --- a/ddtrace/contrib/flask_login/patch.py +++ b/ddtrace/contrib/flask_login/patch.py @@ -1,6 +1,5 @@ import flask import flask_login -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config @@ -8,6 +7,7 @@ from ddtrace.appsec.trace_utils import track_user_login_success_event from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...appsec._utils import _UserInfoRetriever from ...ext import SpanTypes diff --git a/ddtrace/contrib/grpc/aio_server_interceptor.py b/ddtrace/contrib/grpc/aio_server_interceptor.py index d7493ded328..2803d1e4fda 100644 --- a/ddtrace/contrib/grpc/aio_server_interceptor.py +++ b/ddtrace/contrib/grpc/aio_server_interceptor.py @@ -11,7 +11,6 @@ from grpc.aio._typing import RequestType from grpc.aio._typing import ResponseIterableType from grpc.aio._typing import ResponseType -import wrapt from ddtrace import Pin from ddtrace import Span @@ -19,6 +18,7 @@ from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import ERROR_MSG diff --git a/ddtrace/contrib/grpc/client_interceptor.py b/ddtrace/contrib/grpc/client_interceptor.py index 0cc3ff6fc2f..74f2c9d1ba4 100644 --- a/ddtrace/contrib/grpc/client_interceptor.py +++ b/ddtrace/contrib/grpc/client_interceptor.py @@ -1,7 +1,6 @@ import collections import grpc -import wrapt from ddtrace import config from ddtrace.ext import SpanKind @@ -10,6 +9,7 @@ from ddtrace.internal.compat import to_unicode from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import ERROR_MSG diff --git a/ddtrace/contrib/grpc/patch.py b/ddtrace/contrib/grpc/patch.py index 0e4d16b4911..25bf4dff4a6 100644 --- a/ddtrace/contrib/grpc/patch.py +++ b/ddtrace/contrib/grpc/patch.py @@ -1,9 +1,9 @@ import grpc -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ..trace_utils import unwrap as _u from . import constants diff --git a/ddtrace/contrib/grpc/server_interceptor.py b/ddtrace/contrib/grpc/server_interceptor.py index 20b227626b7..0691cd942d0 100644 --- a/ddtrace/contrib/grpc/server_interceptor.py +++ b/ddtrace/contrib/grpc/server_interceptor.py @@ -1,11 +1,11 @@ import grpc -import wrapt from ddtrace import config from ddtrace.internal.compat import to_unicode from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import ERROR_MSG diff --git a/ddtrace/contrib/httplib/patch.py b/ddtrace/contrib/httplib/patch.py index f2a5e373dbe..29de519f26d 100644 --- a/ddtrace/contrib/httplib/patch.py +++ b/ddtrace/contrib/httplib/patch.py @@ -2,11 +2,11 @@ import sys import six -import wrapt from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/httpx/patch.py b/ddtrace/contrib/httpx/patch.py index a459d3510ca..3dbad1ca4c3 100644 --- a/ddtrace/contrib/httpx/patch.py +++ b/ddtrace/contrib/httpx/patch.py @@ -4,7 +4,6 @@ import httpx from six import ensure_binary from six import ensure_text -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -24,12 +23,12 @@ from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.pin import Pin from ddtrace.propagation.http import HTTPPropagator +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w if typing.TYPE_CHECKING: # pragma: no cover - from wrapt import BoundFunctionWrapper - from ddtrace import Span + from ddtrace.vendor.wrapt import BoundFunctionWrapper HTTPX_VERSION = parse_version(httpx.__version__) diff --git a/ddtrace/contrib/jinja2/patch.py b/ddtrace/contrib/jinja2/patch.py index 7e705b5830d..2ffd2f6a55f 100644 --- a/ddtrace/contrib/jinja2/patch.py +++ b/ddtrace/contrib/jinja2/patch.py @@ -1,10 +1,10 @@ import os import jinja2 -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import SPAN_MEASURED_KEY from ...ext import SpanTypes diff --git a/ddtrace/contrib/kombu/patch.py b/ddtrace/contrib/kombu/patch.py index 4ddfc01d781..835b384344a 100644 --- a/ddtrace/contrib/kombu/patch.py +++ b/ddtrace/contrib/kombu/patch.py @@ -2,13 +2,13 @@ # 3p import kombu -import wrapt from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_messaging_operation from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/langchain/patch.py b/ddtrace/contrib/langchain/patch.py index dd66f3d4bc5..1e0b8a8ae3c 100644 --- a/ddtrace/contrib/langchain/patch.py +++ b/ddtrace/contrib/langchain/patch.py @@ -7,7 +7,6 @@ import langchain from langchain.callbacks.openai_info import get_openai_token_cost_for_model -import wrapt from ddtrace import config from ddtrace.constants import ERROR_TYPE @@ -31,6 +30,7 @@ from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import deep_getattr from ddtrace.pin import Pin +from ddtrace.vendor import wrapt if TYPE_CHECKING: diff --git a/ddtrace/contrib/logging/patch.py b/ddtrace/contrib/logging/patch.py index 643f126d93a..59a7f74755d 100644 --- a/ddtrace/contrib/logging/patch.py +++ b/ddtrace/contrib/logging/patch.py @@ -1,12 +1,12 @@ import logging import attr -from wrapt import wrap_function_wrapper as _w import ddtrace from ddtrace import config from ...internal.utils import get_argument_value +from ...vendor.wrapt import wrap_function_wrapper as _w from ..trace_utils import unwrap as _u from .constants import RECORD_ATTR_ENV from .constants import RECORD_ATTR_SERVICE diff --git a/ddtrace/contrib/loguru/patch.py b/ddtrace/contrib/loguru/patch.py index b2c84e1dbd3..b2f558b2f1e 100644 --- a/ddtrace/contrib/loguru/patch.py +++ b/ddtrace/contrib/loguru/patch.py @@ -1,12 +1,12 @@ import json import loguru -from wrapt import wrap_function_wrapper as _w import ddtrace from ddtrace import config from ...internal.utils import get_argument_value +from ...vendor.wrapt import wrap_function_wrapper as _w from ..logging.constants import RECORD_ATTR_ENV from ..logging.constants import RECORD_ATTR_SERVICE from ..logging.constants import RECORD_ATTR_SPAN_ID diff --git a/ddtrace/contrib/mariadb/patch.py b/ddtrace/contrib/mariadb/patch.py index 85db3678de2..771bb0555be 100644 --- a/ddtrace/contrib/mariadb/patch.py +++ b/ddtrace/contrib/mariadb/patch.py @@ -1,7 +1,6 @@ import os import mariadb -import wrapt from ddtrace import Pin from ddtrace import config @@ -11,6 +10,7 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.vendor import wrapt config._add( diff --git a/ddtrace/contrib/molten/patch.py b/ddtrace/contrib/molten/patch.py index 895ee5d27f7..5a2b11dcc3d 100644 --- a/ddtrace/contrib/molten/patch.py +++ b/ddtrace/contrib/molten/patch.py @@ -1,11 +1,11 @@ import os import molten -import wrapt -from wrapt import wrap_function_wrapper as _w from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ... import Pin from ... import config diff --git a/ddtrace/contrib/molten/wrappers.py b/ddtrace/contrib/molten/wrappers.py index 086d0a07f75..c8c6b442dfa 100644 --- a/ddtrace/contrib/molten/wrappers.py +++ b/ddtrace/contrib/molten/wrappers.py @@ -1,9 +1,9 @@ import molten -import wrapt from ddtrace import config from ddtrace.constants import SPAN_KIND from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt from ... import Pin from ...ext import SpanKind diff --git a/ddtrace/contrib/mongoengine/trace.py b/ddtrace/contrib/mongoengine/trace.py index bc5ea7ffefb..17add796b9c 100644 --- a/ddtrace/contrib/mongoengine/trace.py +++ b/ddtrace/contrib/mongoengine/trace.py @@ -1,11 +1,10 @@ # 3p # project -import wrapt - import ddtrace from ddtrace.contrib.pymongo.client import TracedMongoClient from ddtrace.ext import mongo as mongox from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor import wrapt # TODO(Benjamin): we should instrument register_connection instead, because more generic diff --git a/ddtrace/contrib/mysql/patch.py b/ddtrace/contrib/mysql/patch.py index 1e50d464baa..911bea92528 100644 --- a/ddtrace/contrib/mysql/patch.py +++ b/ddtrace/contrib/mysql/patch.py @@ -1,11 +1,11 @@ import os import mysql.connector -import wrapt from ddtrace import Pin from ddtrace import config from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.vendor import wrapt from ...ext import db from ...ext import net diff --git a/ddtrace/contrib/mysqldb/patch.py b/ddtrace/contrib/mysqldb/patch.py index cb94e588316..878280d16a0 100644 --- a/ddtrace/contrib/mysqldb/patch.py +++ b/ddtrace/contrib/mysqldb/patch.py @@ -1,7 +1,6 @@ import os import MySQLdb -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config @@ -11,6 +10,7 @@ from ddtrace.contrib.trace_utils import ext_service from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_database_operation +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...ext import SpanKind from ...ext import SpanTypes diff --git a/ddtrace/contrib/psycopg/extensions.py b/ddtrace/contrib/psycopg/extensions.py index df9955b4d0d..094cb8e8a9b 100644 --- a/ddtrace/contrib/psycopg/extensions.py +++ b/ddtrace/contrib/psycopg/extensions.py @@ -3,11 +3,10 @@ """ import functools -import wrapt - from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_database_operation +from ddtrace.vendor import wrapt from ...constants import SPAN_KIND from ...constants import SPAN_MEASURED_KEY diff --git a/ddtrace/contrib/psycopg/patch.py b/ddtrace/contrib/psycopg/patch.py index f93388438e2..e971b5e04b2 100644 --- a/ddtrace/contrib/psycopg/patch.py +++ b/ddtrace/contrib/psycopg/patch.py @@ -15,8 +15,6 @@ # catch async function syntax errors when using Python<3.7 with no async support except SyntaxError: pass -from wrapt import wrap_function_wrapper as _w - from ddtrace.contrib.psycopg.connection import patched_connect_factory from ddtrace.contrib.psycopg.cursor import Psycopg3FetchTracedCursor from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor @@ -24,6 +22,7 @@ from ddtrace.contrib.psycopg.extensions import _unpatch_extensions from ddtrace.contrib.psycopg.extensions import get_psycopg2_extensions from ddtrace.propagation._database_monitoring import default_sql_injector as _default_sql_injector +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.schema import schematize_database_operation from ...internal.schema import schematize_service_name diff --git a/ddtrace/contrib/pylibmc/client.py b/ddtrace/contrib/pylibmc/client.py index 8966fd0639f..832356b9c10 100644 --- a/ddtrace/contrib/pylibmc/client.py +++ b/ddtrace/contrib/pylibmc/client.py @@ -2,7 +2,6 @@ import random import pylibmc -from wrapt import ObjectProxy # project import ddtrace @@ -21,6 +20,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_cache_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor.wrapt import ObjectProxy # Original Client class diff --git a/ddtrace/contrib/pylons/patch.py b/ddtrace/contrib/pylons/patch.py index 2a7838b45b1..f1456c5a7ca 100644 --- a/ddtrace/contrib/pylons/patch.py +++ b/ddtrace/contrib/pylons/patch.py @@ -2,11 +2,11 @@ import pylons import pylons.wsgiapp -import wrapt from ddtrace import Pin from ddtrace import config from ddtrace import tracer +from ddtrace.vendor import wrapt from ...internal.utils.formats import asbool from ...internal.utils.wrappers import unwrap as _u diff --git a/ddtrace/contrib/pylons/renderer.py b/ddtrace/contrib/pylons/renderer.py index 84b34564471..5658970664f 100644 --- a/ddtrace/contrib/pylons/renderer.py +++ b/ddtrace/contrib/pylons/renderer.py @@ -1,9 +1,9 @@ import pylons from pylons import config -from wrapt import wrap_function_wrapper as _w from ddtrace import config as ddconfig from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.utils import get_argument_value from .compat import legacy_pylons diff --git a/ddtrace/contrib/pymemcache/client.py b/ddtrace/contrib/pymemcache/client.py index 052cac513a1..16e5f2889b7 100644 --- a/ddtrace/contrib/pymemcache/client.py +++ b/ddtrace/contrib/pymemcache/client.py @@ -11,11 +11,11 @@ from pymemcache.exceptions import MemcacheServerError from pymemcache.exceptions import MemcacheUnknownCommandError from pymemcache.exceptions import MemcacheUnknownError -import wrapt # 3p from ddtrace import config from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt # project from ...constants import ANALYTICS_SAMPLE_RATE_KEY diff --git a/ddtrace/contrib/pymongo/client.py b/ddtrace/contrib/pymongo/client.py index 9d72e7c6e89..16ef93d50fd 100644 --- a/ddtrace/contrib/pymongo/client.py +++ b/ddtrace/contrib/pymongo/client.py @@ -5,12 +5,12 @@ # 3p import pymongo -from wrapt import ObjectProxy # project import ddtrace from ddtrace import config from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import ObjectProxy from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/pymongo/patch.py b/ddtrace/contrib/pymongo/patch.py index 963a6428b8e..13ee461689e 100644 --- a/ddtrace/contrib/pymongo/patch.py +++ b/ddtrace/contrib/pymongo/patch.py @@ -1,12 +1,12 @@ import contextlib import pymongo -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config from ddtrace.contrib import trace_utils from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import SPAN_KIND from ...constants import SPAN_MEASURED_KEY diff --git a/ddtrace/contrib/pymysql/patch.py b/ddtrace/contrib/pymysql/patch.py index b08bcfa3207..fe2fdcec843 100644 --- a/ddtrace/contrib/pymysql/patch.py +++ b/ddtrace/contrib/pymysql/patch.py @@ -1,11 +1,11 @@ import os import pymysql -import wrapt from ddtrace import Pin from ddtrace import config from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.vendor import wrapt from ...ext import db from ...ext import net diff --git a/ddtrace/contrib/pynamodb/patch.py b/ddtrace/contrib/pynamodb/patch.py index c4a9eab47f3..c1bc61468cd 100644 --- a/ddtrace/contrib/pynamodb/patch.py +++ b/ddtrace/contrib/pynamodb/patch.py @@ -3,12 +3,12 @@ """ import pynamodb.connection.base -import wrapt from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_cloud_api_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/pyramid/patch.py b/ddtrace/contrib/pyramid/patch.py index c7b48b24c98..2a5f9421909 100644 --- a/ddtrace/contrib/pyramid/patch.py +++ b/ddtrace/contrib/pyramid/patch.py @@ -2,9 +2,9 @@ import pyramid import pyramid.config -import wrapt from ddtrace import config +from ddtrace.vendor import wrapt from ...internal.utils.formats import asbool from .constants import SETTINGS_ANALYTICS_ENABLED diff --git a/ddtrace/contrib/pyramid/trace.py b/ddtrace/contrib/pyramid/trace.py index d3d84b3800f..1cf3b537739 100644 --- a/ddtrace/contrib/pyramid/trace.py +++ b/ddtrace/contrib/pyramid/trace.py @@ -1,13 +1,13 @@ from pyramid.httpexceptions import HTTPException import pyramid.renderers from pyramid.settings import asbool -import wrapt # project import ddtrace from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/redis/patch.py b/ddtrace/contrib/redis/patch.py index a162c9973ca..b1d9fafcf9a 100644 --- a/ddtrace/contrib/redis/patch.py +++ b/ddtrace/contrib/redis/patch.py @@ -2,9 +2,9 @@ import redis from six import PY3 -import wrapt from ddtrace import config +from ddtrace.vendor import wrapt from ...internal.schema import schematize_service_name from ...internal.utils.formats import CMD_MAX_LEN diff --git a/ddtrace/contrib/rediscluster/patch.py b/ddtrace/contrib/rediscluster/patch.py index 6c67f192654..5b5c624c43c 100644 --- a/ddtrace/contrib/rediscluster/patch.py +++ b/ddtrace/contrib/rediscluster/patch.py @@ -2,7 +2,6 @@ # 3p import rediscluster -import wrapt # project from ddtrace import config @@ -22,6 +21,7 @@ from ddtrace.internal.utils.formats import stringify_cache_args from ddtrace.internal.utils.wrappers import unwrap from ddtrace.pin import Pin +from ddtrace.vendor import wrapt from .. import trace_utils diff --git a/ddtrace/contrib/requests/patch.py b/ddtrace/contrib/requests/patch.py index d09f014a398..6cb93f375dd 100644 --- a/ddtrace/contrib/requests/patch.py +++ b/ddtrace/contrib/requests/patch.py @@ -1,9 +1,9 @@ import os import requests -from wrapt import wrap_function_wrapper as _w from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.schema import schematize_service_name from ...internal.utils.formats import asbool diff --git a/ddtrace/contrib/requests/session.py b/ddtrace/contrib/requests/session.py index 9551c70226c..1e603fb39e5 100644 --- a/ddtrace/contrib/requests/session.py +++ b/ddtrace/contrib/requests/session.py @@ -1,8 +1,8 @@ import requests -from wrapt import wrap_function_wrapper as _w from ddtrace import Pin from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from .connection import _wrap_send diff --git a/ddtrace/contrib/sanic/patch.py b/ddtrace/contrib/sanic/patch.py index 5c190b182d6..6fcf002cf82 100644 --- a/ddtrace/contrib/sanic/patch.py +++ b/ddtrace/contrib/sanic/patch.py @@ -1,8 +1,6 @@ import asyncio import sanic -import wrapt -from wrapt import wrap_function_wrapper as _w import ddtrace from ddtrace import config @@ -16,6 +14,8 @@ from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.pin import Pin +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.logger import get_logger from .. import trace_utils diff --git a/ddtrace/contrib/snowflake/patch.py b/ddtrace/contrib/snowflake/patch.py index 24b1581c0b6..607854f3a0d 100644 --- a/ddtrace/contrib/snowflake/patch.py +++ b/ddtrace/contrib/snowflake/patch.py @@ -1,9 +1,8 @@ import os -import wrapt - from ddtrace import Pin from ddtrace import config +from ddtrace.vendor import wrapt from ...ext import db from ...ext import net diff --git a/ddtrace/contrib/sqlalchemy/patch.py b/ddtrace/contrib/sqlalchemy/patch.py index c6162c19006..edd1c43192c 100644 --- a/ddtrace/contrib/sqlalchemy/patch.py +++ b/ddtrace/contrib/sqlalchemy/patch.py @@ -1,5 +1,6 @@ import sqlalchemy -from wrapt import wrap_function_wrapper as _w + +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ..trace_utils import unwrap from .engine import _wrap_create_engine diff --git a/ddtrace/contrib/sqlite3/patch.py b/ddtrace/contrib/sqlite3/patch.py index 49ae92f519c..b5e357b3a22 100644 --- a/ddtrace/contrib/sqlite3/patch.py +++ b/ddtrace/contrib/sqlite3/patch.py @@ -3,9 +3,8 @@ import sqlite3.dbapi2 import sys -import wrapt - from ddtrace import config +from ddtrace.vendor import wrapt from ...contrib.dbapi import FetchTracedCursor from ...contrib.dbapi import TracedConnection diff --git a/ddtrace/contrib/starlette/patch.py b/ddtrace/contrib/starlette/patch.py index 4d966c64345..55c5cf74f2d 100644 --- a/ddtrace/contrib/starlette/patch.py +++ b/ddtrace/contrib/starlette/patch.py @@ -5,8 +5,6 @@ import starlette from starlette.middleware import Middleware -from wrapt import ObjectProxy -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.contrib.asgi.middleware import TraceMiddleware @@ -16,6 +14,8 @@ from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.span import Span +from ddtrace.vendor.wrapt import ObjectProxy +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w log = get_logger(__name__) diff --git a/ddtrace/contrib/structlog/patch.py b/ddtrace/contrib/structlog/patch.py index fe43e5a7aeb..9a3f45def47 100644 --- a/ddtrace/contrib/structlog/patch.py +++ b/ddtrace/contrib/structlog/patch.py @@ -1,5 +1,4 @@ import structlog -from wrapt import wrap_function_wrapper as _w import ddtrace from ddtrace import config @@ -12,6 +11,7 @@ from ..logging.constants import RECORD_ATTR_VALUE_ZERO from ..logging.constants import RECORD_ATTR_VERSION from ..trace_utils import unwrap as _u +from ..trace_utils import wrap as _w config._add( diff --git a/ddtrace/contrib/tornado/patch.py b/ddtrace/contrib/tornado/patch.py index 429b074d3e7..7e7a1b2f864 100644 --- a/ddtrace/contrib/tornado/patch.py +++ b/ddtrace/contrib/tornado/patch.py @@ -1,10 +1,10 @@ import os import tornado -from wrapt import wrap_function_wrapper as _w import ddtrace from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.utils.formats import asbool from ...internal.utils.wrappers import unwrap as _u diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py index b8ff6144869..ff73c7f92c6 100644 --- a/ddtrace/contrib/trace_utils.py +++ b/ddtrace/contrib/trace_utils.py @@ -17,8 +17,6 @@ from typing import Union from typing import cast -import wrapt - from ddtrace import Pin from ddtrace import config from ddtrace.ext import SpanTypes @@ -37,6 +35,7 @@ import ddtrace.internal.utils.wrappers from ddtrace.propagation.http import HTTPPropagator from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor import wrapt if TYPE_CHECKING: # pragma: no cover diff --git a/ddtrace/contrib/unittest/patch.py b/ddtrace/contrib/unittest/patch.py index 3bbc27c7015..4c2a00255c5 100644 --- a/ddtrace/contrib/unittest/patch.py +++ b/ddtrace/contrib/unittest/patch.py @@ -3,8 +3,6 @@ from typing import Union import unittest -import wrapt - import ddtrace from ddtrace import config from ddtrace.constants import SPAN_KIND @@ -30,6 +28,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor import wrapt log = get_logger(__name__) diff --git a/ddtrace/contrib/urllib3/patch.py b/ddtrace/contrib/urllib3/patch.py index a46e44bfcea..a2ae3df30e9 100644 --- a/ddtrace/contrib/urllib3/patch.py +++ b/ddtrace/contrib/urllib3/patch.py @@ -1,12 +1,12 @@ import os import urllib3 -from wrapt import wrap_function_wrapper as _w from ddtrace import config from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/vertica/patch.py b/ddtrace/contrib/vertica/patch.py index 58925cc240c..66cebc3b30b 100644 --- a/ddtrace/contrib/vertica/patch.py +++ b/ddtrace/contrib/vertica/patch.py @@ -1,10 +1,9 @@ import importlib -import wrapt - import ddtrace from ddtrace import config from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY from ...constants import SPAN_KIND diff --git a/ddtrace/contrib/wsgi/wsgi.py b/ddtrace/contrib/wsgi/wsgi.py index b85c43ca9ff..5a50346ed95 100644 --- a/ddtrace/contrib/wsgi/wsgi.py +++ b/ddtrace/contrib/wsgi/wsgi.py @@ -17,7 +17,6 @@ from ddtrace.settings import Config from six.moves.urllib.parse import quote -import wrapt import ddtrace from ddtrace import config @@ -27,6 +26,7 @@ from ddtrace.propagation._utils import from_wsgi_header from ddtrace.propagation.http import HTTPPropagator from ddtrace.tracing import trace_handlers +from ddtrace.vendor import wrapt from ...internal import core diff --git a/ddtrace/contrib/yaaredis/patch.py b/ddtrace/contrib/yaaredis/patch.py index 09d5193ed64..daa54169bbf 100644 --- a/ddtrace/contrib/yaaredis/patch.py +++ b/ddtrace/contrib/yaaredis/patch.py @@ -1,9 +1,9 @@ import os -import wrapt import yaaredis from ddtrace import config +from ddtrace.vendor import wrapt from ...internal.schema import schematize_service_name from ...internal.utils.formats import CMD_MAX_LEN diff --git a/ddtrace/debugging/_function/discovery.py b/ddtrace/debugging/_function/discovery.py index ff358e632bb..c57a87aba33 100644 --- a/ddtrace/debugging/_function/discovery.py +++ b/ddtrace/debugging/_function/discovery.py @@ -3,7 +3,8 @@ from functools import partial from six import PY2 -from wrapt.wrappers import FunctionWrapper + +from ddtrace.vendor.wrapt.wrappers import FunctionWrapper try: diff --git a/ddtrace/internal/compat.py b/ddtrace/internal/compat.py index 9ea3ab435fb..69caf9a1e1c 100644 --- a/ddtrace/internal/compat.py +++ b/ddtrace/internal/compat.py @@ -23,8 +23,9 @@ import warnings import six -from wrapt.wrappers import BoundFunctionWrapper -from wrapt.wrappers import FunctionWrapper + +from ddtrace.vendor.wrapt.wrappers import BoundFunctionWrapper +from ddtrace.vendor.wrapt.wrappers import FunctionWrapper __all__ = [ diff --git a/ddtrace/internal/forksafe.py b/ddtrace/internal/forksafe.py index 12abee3be2d..8f1a2846088 100644 --- a/ddtrace/internal/forksafe.py +++ b/ddtrace/internal/forksafe.py @@ -7,7 +7,7 @@ import typing import weakref -import wrapt +from ddtrace.vendor import wrapt log = logging.getLogger(__name__) diff --git a/ddtrace/internal/runtime/metric_collectors.py b/ddtrace/internal/runtime/metric_collectors.py index 8dc4e597543..a250a8e21f9 100644 --- a/ddtrace/internal/runtime/metric_collectors.py +++ b/ddtrace/internal/runtime/metric_collectors.py @@ -49,7 +49,7 @@ class PSUtilRuntimeMetricCollector(RuntimeMetricCollector): for more information. """ - required_modules = ["psutil"] + required_modules = ["ddtrace.vendor.psutil"] delta_funs = { CPU_TIME_SYS: lambda p: p.cpu_times().system, CPU_TIME_USER: lambda p: p.cpu_times().user, @@ -63,7 +63,7 @@ class PSUtilRuntimeMetricCollector(RuntimeMetricCollector): } def _on_modules_load(self): - self.proc = self.modules["psutil"].Process(os.getpid()) + self.proc = self.modules["ddtrace.vendor.psutil"].Process(os.getpid()) self.stored_values = {key: 0 for key in self.delta_funs.keys()} def collect_fn(self, keys): diff --git a/ddtrace/internal/safety.py b/ddtrace/internal/safety.py index 3371d99d803..1b7c1f34eda 100644 --- a/ddtrace/internal/safety.py +++ b/ddtrace/internal/safety.py @@ -7,13 +7,12 @@ from typing import Type from typing import Union -import wrapt - from ddtrace.internal.compat import BUILTIN from ddtrace.internal.compat import PY3 from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.utils.attrdict import AttrDict from ddtrace.internal.utils.cache import cached +from ddtrace.vendor import wrapt NoneType = type(None) diff --git a/ddtrace/internal/utils/wrappers.py b/ddtrace/internal/utils/wrappers.py index fa7c51adadd..146329ff596 100644 --- a/ddtrace/internal/utils/wrappers.py +++ b/ddtrace/internal/utils/wrappers.py @@ -3,7 +3,7 @@ from typing import Optional from typing import TypeVar -import wrapt +from ddtrace.vendor import wrapt F = TypeVar("F", bound=Callable[..., Any]) diff --git a/ddtrace/pin.py b/ddtrace/pin.py index 529a1e6b2c5..cb0cc434d34 100644 --- a/ddtrace/pin.py +++ b/ddtrace/pin.py @@ -3,11 +3,10 @@ from typing import Dict from typing import Optional -import wrapt - import ddtrace from .internal.logger import get_logger +from .vendor import wrapt if TYPE_CHECKING: # pragma: no cover diff --git a/ddtrace/profiling/collector/_lock.py b/ddtrace/profiling/collector/_lock.py index 0620800fc72..a008ed2af78 100644 --- a/ddtrace/profiling/collector/_lock.py +++ b/ddtrace/profiling/collector/_lock.py @@ -7,7 +7,6 @@ import attr from six.moves import _thread -import wrapt from ddtrace.internal import compat from ddtrace.profiling import _threading @@ -16,6 +15,7 @@ from ddtrace.profiling.collector import _task from ddtrace.profiling.collector import _traceback from ddtrace.settings.profiling import config +from ddtrace.vendor import wrapt @event.event_class @@ -52,7 +52,7 @@ def _current_thread(): WRAPT_C_EXT = False else: try: - import wrapt._wrappers as _w # noqa: F401 + import ddtrace.vendor.wrapt._wrappers as _w # noqa: F401 except ImportError: WRAPT_C_EXT = False else: diff --git a/ddtrace/profiling/collector/_task.pyx b/ddtrace/profiling/collector/_task.pyx index d5d50b2b247..8aaa24f2c1a 100644 --- a/ddtrace/profiling/collector/_task.pyx +++ b/ddtrace/profiling/collector/_task.pyx @@ -2,7 +2,7 @@ import sys from types import ModuleType import weakref -from wrapt.importer import when_imported +from ddtrace.vendor.wrapt.importer import when_imported from .. import _asyncio from .. import _threading diff --git a/ddtrace/settings/profiling.py b/ddtrace/settings/profiling.py index facf3f9d8a2..b6c8819ebc8 100644 --- a/ddtrace/settings/profiling.py +++ b/ddtrace/settings/profiling.py @@ -21,7 +21,7 @@ def _derive_default_heap_sample_size(heap_config, default_heap_sample_size=1024 return 0 try: - import psutil + from ddtrace.vendor import psutil total_mem = psutil.swap_memory().total + psutil.virtual_memory().total except Exception: diff --git a/ddtrace/sourcecode/setuptools_auto.py b/ddtrace/sourcecode/setuptools_auto.py index a47ed5d1d0c..9289c4b6b03 100644 --- a/ddtrace/sourcecode/setuptools_auto.py +++ b/ddtrace/sourcecode/setuptools_auto.py @@ -1,4 +1,4 @@ -from wrapt import wrap_function_wrapper as _w +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w try: diff --git a/ddtrace/tracing/trace_handlers.py b/ddtrace/tracing/trace_handlers.py index 8805aaff3b7..0ab04dcac1e 100644 --- a/ddtrace/tracing/trace_handlers.py +++ b/ddtrace/tracing/trace_handlers.py @@ -1,8 +1,6 @@ import functools import sys -import wrapt - from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY from ddtrace.constants import SPAN_KIND from ddtrace.constants import SPAN_MEASURED_KEY @@ -23,6 +21,7 @@ from ddtrace.internal.constants import RESPONSE_HEADERS from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import http as http_utils +from ddtrace.vendor import wrapt log = get_logger(__name__) diff --git a/ddtrace/vendor/__init__.py b/ddtrace/vendor/__init__.py index 58cf2013714..1150cf0aa3b 100644 --- a/ddtrace/vendor/__init__.py +++ b/ddtrace/vendor/__init__.py @@ -8,6 +8,17 @@ Dependencies ============ +wrapt +----- + +Website: https://wrapt.readthedocs.io/en/latest/ +Source: https://github.com/GrahamDumpleton/wrapt/ +Version: 1.14.1 +License: BSD 2-Clause "Simplified" License + +Notes: + `setup.py` will attempt to build the `wrapt/_wrappers.c` C module + dogstatsd --------- @@ -52,6 +63,15 @@ Removed dependency on `pbr` and manually set `__version__` +psutil +------ + +Website: https://github.com/giampaolo/psutil +Source: https://github.com/giampaolo/psutil +Version: 5.6.7 +License: BSD 3 + + contextvars ------------- diff --git a/ddtrace/vendor/debtcollector/moves.py b/ddtrace/vendor/debtcollector/moves.py index d3c7f18ac5c..8fac7543ff6 100644 --- a/ddtrace/vendor/debtcollector/moves.py +++ b/ddtrace/vendor/debtcollector/moves.py @@ -17,7 +17,7 @@ import inspect import six -import wrapt +from .. import wrapt from . import _utils diff --git a/ddtrace/vendor/debtcollector/removals.py b/ddtrace/vendor/debtcollector/removals.py index bfd357e1973..ea2b4903005 100644 --- a/ddtrace/vendor/debtcollector/removals.py +++ b/ddtrace/vendor/debtcollector/removals.py @@ -16,7 +16,7 @@ import inspect import six -import wrapt +from .. import wrapt from . import _utils diff --git a/ddtrace/vendor/debtcollector/renames.py b/ddtrace/vendor/debtcollector/renames.py index df66e843066..d31853aa6fe 100644 --- a/ddtrace/vendor/debtcollector/renames.py +++ b/ddtrace/vendor/debtcollector/renames.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -import wrapt +from .. import wrapt from . import _utils diff --git a/ddtrace/vendor/debtcollector/updating.py b/ddtrace/vendor/debtcollector/updating.py index aab252298d1..67e5f847492 100644 --- a/ddtrace/vendor/debtcollector/updating.py +++ b/ddtrace/vendor/debtcollector/updating.py @@ -15,7 +15,7 @@ # under the License. import six -import wrapt +from .. import wrapt if six.PY3: import inspect Parameter = inspect.Parameter diff --git a/ddtrace/vendor/psutil/__init__.py b/ddtrace/vendor/psutil/__init__.py new file mode 100644 index 00000000000..b267239e285 --- /dev/null +++ b/ddtrace/vendor/psutil/__init__.py @@ -0,0 +1,2516 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""psutil is a cross-platform library for retrieving information on +running processes and system utilization (CPU, memory, disks, network, +sensors) in Python. Supported platforms: + + - Linux + - Windows + - macOS + - FreeBSD + - OpenBSD + - NetBSD + - Sun Solaris + - AIX + +Works with Python versions from 2.6 to 3.4+. +""" + +from __future__ import division + +import collections +import contextlib +import datetime +import functools +import os +import signal +import subprocess +import sys +import threading +import time +try: + import pwd +except ImportError: + pwd = None + +from . import _common +from ._common import deprecated_method +from ._common import memoize +from ._common import memoize_when_activated +from ._common import wrap_numbers as _wrap_numbers +from ._compat import long +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import PY3 as _PY3 + +from ._common import STATUS_DEAD +from ._common import STATUS_DISK_SLEEP +from ._common import STATUS_IDLE +from ._common import STATUS_LOCKED +from ._common import STATUS_PARKED +from ._common import STATUS_RUNNING +from ._common import STATUS_SLEEPING +from ._common import STATUS_STOPPED +from ._common import STATUS_TRACING_STOP +from ._common import STATUS_WAITING +from ._common import STATUS_WAKING +from ._common import STATUS_ZOMBIE + +from ._common import CONN_CLOSE +from ._common import CONN_CLOSE_WAIT +from ._common import CONN_CLOSING +from ._common import CONN_ESTABLISHED +from ._common import CONN_FIN_WAIT1 +from ._common import CONN_FIN_WAIT2 +from ._common import CONN_LAST_ACK +from ._common import CONN_LISTEN +from ._common import CONN_NONE +from ._common import CONN_SYN_RECV +from ._common import CONN_SYN_SENT +from ._common import CONN_TIME_WAIT +from ._common import NIC_DUPLEX_FULL +from ._common import NIC_DUPLEX_HALF +from ._common import NIC_DUPLEX_UNKNOWN + +from ._common import AIX +from ._common import BSD +from ._common import FREEBSD # NOQA +from ._common import LINUX +from ._common import MACOS +from ._common import NETBSD # NOQA +from ._common import OPENBSD # NOQA +from ._common import OSX # deprecated alias +from ._common import POSIX # NOQA +from ._common import SUNOS +from ._common import WINDOWS + +if LINUX: + # This is public API and it will be retrieved from _pslinux.py + # via sys.modules. + PROCFS_PATH = "/proc" + + from . import _pslinux as _psplatform + + from ._pslinux import IOPRIO_CLASS_BE # NOQA + from ._pslinux import IOPRIO_CLASS_IDLE # NOQA + from ._pslinux import IOPRIO_CLASS_NONE # NOQA + from ._pslinux import IOPRIO_CLASS_RT # NOQA + # Linux >= 2.6.36 + if _psplatform.HAS_PRLIMIT: + from ._psutil_linux import RLIM_INFINITY # NOQA + from ._psutil_linux import RLIMIT_AS # NOQA + from ._psutil_linux import RLIMIT_CORE # NOQA + from ._psutil_linux import RLIMIT_CPU # NOQA + from ._psutil_linux import RLIMIT_DATA # NOQA + from ._psutil_linux import RLIMIT_FSIZE # NOQA + from ._psutil_linux import RLIMIT_LOCKS # NOQA + from ._psutil_linux import RLIMIT_MEMLOCK # NOQA + from ._psutil_linux import RLIMIT_NOFILE # NOQA + from ._psutil_linux import RLIMIT_NPROC # NOQA + from ._psutil_linux import RLIMIT_RSS # NOQA + from ._psutil_linux import RLIMIT_STACK # NOQA + # Kinda ugly but considerably faster than using hasattr() and + # setattr() against the module object (we are at import time: + # speed matters). + from . import _psutil_linux + try: + RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE + except AttributeError: + pass + try: + RLIMIT_NICE = _psutil_linux.RLIMIT_NICE + except AttributeError: + pass + try: + RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO + except AttributeError: + pass + try: + RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME + except AttributeError: + pass + try: + RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING + except AttributeError: + pass + +elif WINDOWS: + from . import _pswindows as _psplatform + from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS # NOQA + from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS # NOQA + from ._psutil_windows import HIGH_PRIORITY_CLASS # NOQA + from ._psutil_windows import IDLE_PRIORITY_CLASS # NOQA + from ._psutil_windows import NORMAL_PRIORITY_CLASS # NOQA + from ._psutil_windows import REALTIME_PRIORITY_CLASS # NOQA + from ._pswindows import CONN_DELETE_TCB # NOQA + from ._pswindows import IOPRIO_VERYLOW # NOQA + from ._pswindows import IOPRIO_LOW # NOQA + from ._pswindows import IOPRIO_NORMAL # NOQA + from ._pswindows import IOPRIO_HIGH # NOQA + +elif MACOS: + from . import _psosx as _psplatform + +elif BSD: + from . import _psbsd as _psplatform + +elif SUNOS: + from . import _pssunos as _psplatform + from ._pssunos import CONN_BOUND # NOQA + from ._pssunos import CONN_IDLE # NOQA + + # This is public writable API which is read from _pslinux.py and + # _pssunos.py via sys.modules. + PROCFS_PATH = "/proc" + +elif AIX: + from . import _psaix as _psplatform + + # This is public API and it will be retrieved from _pslinux.py + # via sys.modules. + PROCFS_PATH = "/proc" + +else: # pragma: no cover + raise NotImplementedError('platform %s is not supported' % sys.platform) + + +__all__ = [ + # exceptions + "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", + "TimeoutExpired", + + # constants + "version_info", "__version__", + + "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP", + "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD", + "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED", + "STATUS_PARKED", + + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE", + + "AF_LINK", + + "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN", + + "POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED", + + "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX", + "SUNOS", "WINDOWS", "AIX", + + # classes + "Process", "Popen", + + # functions + "pid_exists", "pids", "process_iter", "wait_procs", # proc + "virtual_memory", "swap_memory", # memory + "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu + "cpu_stats", # "cpu_freq", "getloadavg" + "net_io_counters", "net_connections", "net_if_addrs", # network + "net_if_stats", + "disk_io_counters", "disk_partitions", "disk_usage", # disk + # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors + "users", "boot_time", # others +] + + +__all__.extend(_psplatform.__extra__all__) +__author__ = "Giampaolo Rodola'" +__version__ = "5.6.7" +version_info = tuple([int(num) for num in __version__.split('.')]) + +_timer = getattr(time, 'monotonic', time.time) +AF_LINK = _psplatform.AF_LINK +POWER_TIME_UNLIMITED = _common.POWER_TIME_UNLIMITED +POWER_TIME_UNKNOWN = _common.POWER_TIME_UNKNOWN +_TOTAL_PHYMEM = None +_LOWEST_PID = None + +# Sanity check in case the user messed up with psutil installation +# or did something weird with sys.path. In this case we might end +# up importing a python module using a C extension module which +# was compiled for a different version of psutil. +# We want to prevent that by failing sooner rather than later. +# See: https://github.com/giampaolo/psutil/issues/564 +if (int(__version__.replace('.', '')) != + getattr(_psplatform.cext, 'version', None)): + msg = "version conflict: %r C extension module was built for another " \ + "version of psutil" % getattr(_psplatform.cext, "__file__") + if hasattr(_psplatform.cext, 'version'): + msg += " (%s instead of %s)" % ( + '.'.join([x for x in str(_psplatform.cext.version)]), __version__) + else: + msg += " (different than %s)" % __version__ + msg += "; you may try to 'pip uninstall psutil', manually remove %s" % ( + getattr(_psplatform.cext, "__file__", + "the existing psutil install directory")) + msg += " or clean the virtual env somehow, then reinstall" + raise ImportError(msg) + + +# ===================================================================== +# --- Exceptions +# ===================================================================== + + +class Error(Exception): + """Base exception class. All other psutil exceptions inherit + from this one. + """ + + def __init__(self, msg=""): + Exception.__init__(self, msg) + self.msg = msg + + def __repr__(self): + ret = "psutil.%s %s" % (self.__class__.__name__, self.msg) + return ret.strip() + + __str__ = __repr__ + + +class NoSuchProcess(Error): + """Exception raised when a process with a certain PID doesn't + or no longer exists. + """ + + def __init__(self, pid, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process no longer exists " + details + + +class ZombieProcess(NoSuchProcess): + """Exception raised when querying a zombie process. This is + raised on macOS, BSD and Solaris only, and not always: depending + on the query the OS may be able to succeed anyway. + On Linux all zombie processes are querable (hence this is never + raised). Windows doesn't have zombie processes. + """ + + def __init__(self, pid, name=None, ppid=None, msg=None): + NoSuchProcess.__init__(self, msg) + self.pid = pid + self.ppid = ppid + self.name = name + self.msg = msg + if msg is None: + args = ["pid=%s" % pid] + if name: + args.append("name=%s" % repr(self.name)) + if ppid: + args.append("ppid=%s" % self.ppid) + details = "(%s)" % ", ".join(args) + self.msg = "process still exists but it's a zombie " + details + + +class AccessDenied(Error): + """Exception raised when permission to perform an action is denied.""" + + def __init__(self, pid=None, name=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.name = name + self.msg = msg + if msg is None: + if (pid is not None) and (name is not None): + self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg = "(pid=%s)" % self.pid + else: + self.msg = "" + + +class TimeoutExpired(Error): + """Raised on Process.wait(timeout) if timeout expires and process + is still alive. + """ + + def __init__(self, seconds, pid=None, name=None): + Error.__init__(self, "timeout after %s seconds" % seconds) + self.seconds = seconds + self.pid = pid + self.name = name + if (pid is not None) and (name is not None): + self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) + elif (pid is not None): + self.msg += " (pid=%s)" % self.pid + + +# Push exception classes into platform specific module namespace. +_psplatform.NoSuchProcess = NoSuchProcess +_psplatform.ZombieProcess = ZombieProcess +_psplatform.AccessDenied = AccessDenied +_psplatform.TimeoutExpired = TimeoutExpired +if POSIX: + from . import _psposix + _psposix.TimeoutExpired = TimeoutExpired + + +# ===================================================================== +# --- Utils +# ===================================================================== + + +if hasattr(_psplatform, 'ppid_map'): + # Faster version (Windows and Linux). + _ppid_map = _psplatform.ppid_map +else: + def _ppid_map(): + """Return a {pid: ppid, ...} dict for all running processes in + one shot. Used to speed up Process.children(). + """ + ret = {} + for pid in pids(): + try: + ret[pid] = _psplatform.Process(pid).ppid() + except (NoSuchProcess, ZombieProcess): + pass + return ret + + +def _assert_pid_not_reused(fun): + """Decorator which raises NoSuchProcess in case a process is no + longer running or its PID has been reused. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + return fun(self, *args, **kwargs) + return wrapper + + +def _pprint_secs(secs): + """Format seconds in a human readable form.""" + now = time.time() + secs_ago = int(now - secs) + if secs_ago < 60 * 60 * 24: + fmt = "%H:%M:%S" + else: + fmt = "%Y-%m-%d %H:%M:%S" + return datetime.datetime.fromtimestamp(secs).strftime(fmt) + + +# ===================================================================== +# --- Process class +# ===================================================================== + + +class Process(object): + """Represents an OS process with the given PID. + If PID is omitted current process PID (os.getpid()) is used. + Raise NoSuchProcess if PID does not exist. + + Note that most of the methods of this class do not make sure + the PID of the process being queried has been reused over time. + That means you might end up retrieving an information referring + to another process in case the original one this instance + refers to is gone in the meantime. + + The only exceptions for which process identity is pre-emptively + checked and guaranteed are: + + - parent() + - children() + - nice() (set) + - ionice() (set) + - rlimit() (set) + - cpu_affinity (set) + - suspend() + - resume() + - send_signal() + - terminate() + - kill() + + To prevent this problem for all other methods you can: + - use is_running() before querying the process + - if you're continuously iterating over a set of Process + instances use process_iter() which pre-emptively checks + process identity for every yielded instance + """ + + def __init__(self, pid=None): + self._init(pid) + + def _init(self, pid, _ignore_nsp=False): + if pid is None: + pid = os.getpid() + else: + if not _PY3 and not isinstance(pid, (int, long)): + raise TypeError('pid must be an integer (got %r)' % pid) + if pid < 0: + raise ValueError('pid must be a positive integer (got %s)' + % pid) + self._pid = pid + self._name = None + self._exe = None + self._create_time = None + self._gone = False + self._hash = None + self._lock = threading.RLock() + # used for caching on Windows only (on POSIX ppid may change) + self._ppid = None + # platform-specific modules define an _psplatform.Process + # implementation class + self._proc = _psplatform.Process(pid) + self._last_sys_cpu_times = None + self._last_proc_cpu_times = None + # cache creation time for later use in is_running() method + try: + self.create_time() + except AccessDenied: + # We should never get here as AFAIK we're able to get + # process creation time on all platforms even as a + # limited user. + pass + except ZombieProcess: + # Zombies can still be queried by this class (although + # not always) and pids() return them so just go on. + pass + except NoSuchProcess: + if not _ignore_nsp: + msg = 'no process found with pid %s' % pid + raise NoSuchProcess(pid, None, msg) + else: + self._gone = True + # This pair is supposed to indentify a Process instance + # univocally over time (the PID alone is not enough as + # it might refer to a process whose PID has been reused). + # This will be used later in __eq__() and is_running(). + self._ident = (self.pid, self._create_time) + + def __str__(self): + try: + info = collections.OrderedDict() + except AttributeError: + info = {} # Python 2.6 + info["pid"] = self.pid + try: + info["name"] = self.name() + if self._create_time: + info['started'] = _pprint_secs(self._create_time) + except ZombieProcess: + info["status"] = "zombie" + except NoSuchProcess: + info["status"] = "terminated" + except AccessDenied: + pass + return "%s.%s(%s)" % ( + self.__class__.__module__, + self.__class__.__name__, + ", ".join(["%s=%r" % (k, v) for k, v in info.items()])) + + __repr__ = __str__ + + def __eq__(self, other): + # Test for equality with another Process object based + # on PID and creation time. + if not isinstance(other, Process): + return NotImplemented + return self._ident == other._ident + + def __ne__(self, other): + return not self == other + + def __hash__(self): + if self._hash is None: + self._hash = hash(self._ident) + return self._hash + + @property + def pid(self): + """The process PID.""" + return self._pid + + # --- utility methods + + @contextlib.contextmanager + def oneshot(self): + """Utility context manager which considerably speeds up the + retrieval of multiple process information at the same time. + + Internally different process info (e.g. name, ppid, uids, + gids, ...) may be fetched by using the same routine, but + only one information is returned and the others are discarded. + When using this context manager the internal routine is + executed once (in the example below on name()) and the + other info are cached. + + The cache is cleared when exiting the context manager block. + The advice is to use this every time you retrieve more than + one information about the process. If you're lucky, you'll + get a hell of a speedup. + + >>> import psutil + >>> p = psutil.Process() + >>> with p.oneshot(): + ... p.name() # collect multiple info + ... p.cpu_times() # return cached value + ... p.cpu_percent() # return cached value + ... p.create_time() # return cached value + ... + >>> + """ + with self._lock: + if hasattr(self, "_cache"): + # NOOP: this covers the use case where the user enters the + # context twice: + # + # >>> with p.oneshot(): + # ... with p.oneshot(): + # ... + # + # Also, since as_dict() internally uses oneshot() + # I expect that the code below will be a pretty common + # "mistake" that the user will make, so let's guard + # against that: + # + # >>> with p.oneshot(): + # ... p.as_dict() + # ... + yield + else: + try: + # cached in case cpu_percent() is used + self.cpu_times.cache_activate(self) + # cached in case memory_percent() is used + self.memory_info.cache_activate(self) + # cached in case parent() is used + self.ppid.cache_activate(self) + # cached in case username() is used + if POSIX: + self.uids.cache_activate(self) + # specific implementation cache + self._proc.oneshot_enter() + yield + finally: + self.cpu_times.cache_deactivate(self) + self.memory_info.cache_deactivate(self) + self.ppid.cache_deactivate(self) + if POSIX: + self.uids.cache_deactivate(self) + self._proc.oneshot_exit() + + def as_dict(self, attrs=None, ad_value=None): + """Utility method returning process information as a + hashable dictionary. + If *attrs* is specified it must be a list of strings + reflecting available Process class' attribute names + (e.g. ['cpu_times', 'name']) else all public (read + only) attributes are assumed. + *ad_value* is the value which gets assigned in case + AccessDenied or ZombieProcess exception is raised when + retrieving that particular process information. + """ + valid_names = _as_dict_attrnames + if attrs is not None: + if not isinstance(attrs, (list, tuple, set, frozenset)): + raise TypeError("invalid attrs type %s" % type(attrs)) + attrs = set(attrs) + invalid_names = attrs - valid_names + if invalid_names: + raise ValueError("invalid attr name%s %s" % ( + "s" if len(invalid_names) > 1 else "", + ", ".join(map(repr, invalid_names)))) + + retdict = dict() + ls = attrs or valid_names + with self.oneshot(): + for name in ls: + try: + if name == 'pid': + ret = self.pid + else: + meth = getattr(self, name) + ret = meth() + except (AccessDenied, ZombieProcess): + ret = ad_value + except NotImplementedError: + # in case of not implemented functionality (may happen + # on old or exotic systems) we want to crash only if + # the user explicitly asked for that particular attr + if attrs: + raise + continue + retdict[name] = ret + return retdict + + def parent(self): + """Return the parent process as a Process object pre-emptively + checking whether PID has been reused. + If no parent is known return None. + """ + lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0] + if self.pid == lowest_pid: + return None + ppid = self.ppid() + if ppid is not None: + ctime = self.create_time() + try: + parent = Process(ppid) + if parent.create_time() <= ctime: + return parent + # ...else ppid has been reused by another process + except NoSuchProcess: + pass + + def parents(self): + """Return the parents of this process as a list of Process + instances. If no parents are known return an empty list. + """ + parents = [] + proc = self.parent() + while proc is not None: + parents.append(proc) + proc = proc.parent() + return parents + + def is_running(self): + """Return whether this process is running. + It also checks if PID has been reused by another process in + which case return False. + """ + if self._gone: + return False + try: + # Checking if PID is alive is not enough as the PID might + # have been reused by another process: we also want to + # verify process identity. + # Process identity / uniqueness over time is guaranteed by + # (PID + creation time) and that is verified in __eq__. + return self == Process(self.pid) + except ZombieProcess: + # We should never get here as it's already handled in + # Process.__init__; here just for extra safety. + return True + except NoSuchProcess: + self._gone = True + return False + + # --- actual API + + @memoize_when_activated + def ppid(self): + """The process parent PID. + On Windows the return value is cached after first call. + """ + # On POSIX we don't want to cache the ppid as it may unexpectedly + # change to 1 (init) in case this process turns into a zombie: + # https://github.com/giampaolo/psutil/issues/321 + # http://stackoverflow.com/questions/356722/ + + # XXX should we check creation time here rather than in + # Process.parent()? + if POSIX: + return self._proc.ppid() + else: # pragma: no cover + self._ppid = self._ppid or self._proc.ppid() + return self._ppid + + def name(self): + """The process name. The return value is cached after first call.""" + # Process name is only cached on Windows as on POSIX it may + # change, see: + # https://github.com/giampaolo/psutil/issues/692 + if WINDOWS and self._name is not None: + return self._name + name = self._proc.name() + if POSIX and len(name) >= 15: + # On UNIX the name gets truncated to the first 15 characters. + # If it matches the first part of the cmdline we return that + # one instead because it's usually more explicative. + # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". + try: + cmdline = self.cmdline() + except AccessDenied: + pass + else: + if cmdline: + extended_name = os.path.basename(cmdline[0]) + if extended_name.startswith(name): + name = extended_name + self._name = name + self._proc._name = name + return name + + def exe(self): + """The process executable as an absolute path. + May also be an empty string. + The return value is cached after first call. + """ + def guess_it(fallback): + # try to guess exe from cmdline[0] in absence of a native + # exe representation + cmdline = self.cmdline() + if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'): + exe = cmdline[0] # the possible exe + # Attempt to guess only in case of an absolute path. + # It is not safe otherwise as the process might have + # changed cwd. + if (os.path.isabs(exe) and + os.path.isfile(exe) and + os.access(exe, os.X_OK)): + return exe + if isinstance(fallback, AccessDenied): + raise fallback + return fallback + + if self._exe is None: + try: + exe = self._proc.exe() + except AccessDenied as err: + return guess_it(fallback=err) + else: + if not exe: + # underlying implementation can legitimately return an + # empty string; if that's the case we don't want to + # raise AD while guessing from the cmdline + try: + exe = guess_it(fallback=exe) + except AccessDenied: + pass + self._exe = exe + return self._exe + + def cmdline(self): + """The command line this process has been called with.""" + return self._proc.cmdline() + + def status(self): + """The process current status as a STATUS_* constant.""" + try: + return self._proc.status() + except ZombieProcess: + return STATUS_ZOMBIE + + def username(self): + """The name of the user that owns the process. + On UNIX this is calculated by using *real* process uid. + """ + if POSIX: + if pwd is None: + # might happen if python was installed from sources + raise ImportError( + "requires pwd module shipped with standard python") + real_uid = self.uids().real + try: + return pwd.getpwuid(real_uid).pw_name + except KeyError: + # the uid can't be resolved by the system + return str(real_uid) + else: + return self._proc.username() + + def create_time(self): + """The process creation time as a floating point number + expressed in seconds since the epoch, in UTC. + The return value is cached after first call. + """ + if self._create_time is None: + self._create_time = self._proc.create_time() + return self._create_time + + def cwd(self): + """Process current working directory as an absolute path.""" + return self._proc.cwd() + + def nice(self, value=None): + """Get or set process niceness (priority).""" + if value is None: + return self._proc.nice_get() + else: + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + self._proc.nice_set(value) + + if POSIX: + + @memoize_when_activated + def uids(self): + """Return process UIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.uids() + + def gids(self): + """Return process GIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.gids() + + def terminal(self): + """The terminal associated with this process, if any, + else None. + """ + return self._proc.terminal() + + def num_fds(self): + """Return the number of file descriptors opened by this + process (POSIX only). + """ + return self._proc.num_fds() + + # Linux, BSD, AIX and Windows only + if hasattr(_psplatform.Process, "io_counters"): + + def io_counters(self): + """Return process I/O statistics as a + (read_count, write_count, read_bytes, write_bytes) + namedtuple. + Those are the number of read/write calls performed and the + amount of bytes read and written by the process. + """ + return self._proc.io_counters() + + # Linux and Windows >= Vista only + if hasattr(_psplatform.Process, "ionice_get"): + + def ionice(self, ioclass=None, value=None): + """Get or set process I/O niceness (priority). + + On Linux *ioclass* is one of the IOPRIO_CLASS_* constants. + *value* is a number which goes from 0 to 7. The higher the + value, the lower the I/O priority of the process. + + On Windows only *ioclass* is used and it can be set to 2 + (normal), 1 (low) or 0 (very low). + + Available on Linux and Windows > Vista only. + """ + if ioclass is None: + if value is not None: + raise ValueError("'ioclass' argument must be specified") + return self._proc.ionice_get() + else: + return self._proc.ionice_set(ioclass, value) + + # Linux only + if hasattr(_psplatform.Process, "rlimit"): + + def rlimit(self, resource, limits=None): + """Get or set process resource limits as a (soft, hard) + tuple. + + *resource* is one of the RLIMIT_* constants. + *limits* is supposed to be a (soft, hard) tuple. + + See "man prlimit" for further info. + Available on Linux only. + """ + if limits is None: + return self._proc.rlimit(resource) + else: + return self._proc.rlimit(resource, limits) + + # Windows, Linux and FreeBSD only + if hasattr(_psplatform.Process, "cpu_affinity_get"): + + def cpu_affinity(self, cpus=None): + """Get or set process CPU affinity. + If specified, *cpus* must be a list of CPUs for which you + want to set the affinity (e.g. [0, 1]). + If an empty list is passed, all egible CPUs are assumed + (and set). + (Windows, Linux and BSD only). + """ + if cpus is None: + return list(set(self._proc.cpu_affinity_get())) + else: + if not cpus: + if hasattr(self._proc, "_get_eligible_cpus"): + cpus = self._proc._get_eligible_cpus() + else: + cpus = tuple(range(len(cpu_times(percpu=True)))) + self._proc.cpu_affinity_set(list(set(cpus))) + + # Linux, FreeBSD, SunOS + if hasattr(_psplatform.Process, "cpu_num"): + + def cpu_num(self): + """Return what CPU this process is currently running on. + The returned number should be <= psutil.cpu_count() + and <= len(psutil.cpu_percent(percpu=True)). + It may be used in conjunction with + psutil.cpu_percent(percpu=True) to observe the system + workload distributed across CPUs. + """ + return self._proc.cpu_num() + + # Linux, macOS, Windows, Solaris, AIX + if hasattr(_psplatform.Process, "environ"): + + def environ(self): + """The environment variables of the process as a dict. Note: this + might not reflect changes made after the process started. """ + return self._proc.environ() + + if WINDOWS: + + def num_handles(self): + """Return the number of handles opened by this process + (Windows only). + """ + return self._proc.num_handles() + + def num_ctx_switches(self): + """Return the number of voluntary and involuntary context + switches performed by this process. + """ + return self._proc.num_ctx_switches() + + def num_threads(self): + """Return the number of threads used by this process.""" + return self._proc.num_threads() + + if hasattr(_psplatform.Process, "threads"): + + def threads(self): + """Return threads opened by process as a list of + (id, user_time, system_time) namedtuples representing + thread id and thread CPU times (user/system). + On OpenBSD this method requires root access. + """ + return self._proc.threads() + + @_assert_pid_not_reused + def children(self, recursive=False): + """Return the children of this process as a list of Process + instances, pre-emptively checking whether PID has been reused. + If *recursive* is True return all the parent descendants. + + Example (A == this process): + + A ─┐ + │ + ├─ B (child) ─┐ + │ └─ X (grandchild) ─┐ + │ └─ Y (great grandchild) + ├─ C (child) + └─ D (child) + + >>> import psutil + >>> p = psutil.Process() + >>> p.children() + B, C, D + >>> p.children(recursive=True) + B, X, Y, C, D + + Note that in the example above if process X disappears + process Y won't be listed as the reference to process A + is lost. + """ + ppid_map = _ppid_map() + ret = [] + if not recursive: + for pid, ppid in ppid_map.items(): + if ppid == self.pid: + try: + child = Process(pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= child.create_time(): + ret.append(child) + except (NoSuchProcess, ZombieProcess): + pass + else: + # Construct a {pid: [child pids]} dict + reverse_ppid_map = collections.defaultdict(list) + for pid, ppid in ppid_map.items(): + reverse_ppid_map[ppid].append(pid) + # Recursively traverse that dict, starting from self.pid, + # such that we only call Process() on actual children + seen = set() + stack = [self.pid] + while stack: + pid = stack.pop() + if pid in seen: + # Since pids can be reused while the ppid_map is + # constructed, there may be rare instances where + # there's a cycle in the recorded process "tree". + continue + seen.add(pid) + for child_pid in reverse_ppid_map[pid]: + try: + child = Process(child_pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + intime = self.create_time() <= child.create_time() + if intime: + ret.append(child) + stack.append(child_pid) + except (NoSuchProcess, ZombieProcess): + pass + return ret + + def cpu_percent(self, interval=None): + """Return a float representing the current process CPU + utilization as a percentage. + + When *interval* is 0.0 or None (default) compares process times + to system CPU times elapsed since last call, returning + immediately (non-blocking). That means that the first time + this is called it will return a meaningful 0.0 value. + + When *interval* is > 0.0 compares process times to system CPU + times elapsed before and after the interval (blocking). + + In this case is recommended for accuracy that this function + be called with at least 0.1 seconds between calls. + + A value > 100.0 can be returned in case of processes running + multiple threads on different CPU cores. + + The returned value is explicitly NOT split evenly between + all available logical CPUs. This means that a busy loop process + running on a system with 2 logical CPUs will be reported as + having 100% CPU utilization instead of 50%. + + Examples: + + >>> import psutil + >>> p = psutil.Process(os.getpid()) + >>> # blocking + >>> p.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> p.cpu_percent(interval=None) + 2.9 + >>> + """ + blocking = interval is not None and interval > 0.0 + if interval is not None and interval < 0: + raise ValueError("interval is not positive (got %r)" % interval) + num_cpus = cpu_count() or 1 + + def timer(): + return _timer() * num_cpus + + if blocking: + st1 = timer() + pt1 = self._proc.cpu_times() + time.sleep(interval) + st2 = timer() + pt2 = self._proc.cpu_times() + else: + st1 = self._last_sys_cpu_times + pt1 = self._last_proc_cpu_times + st2 = timer() + pt2 = self._proc.cpu_times() + if st1 is None or pt1 is None: + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + return 0.0 + + delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) + delta_time = st2 - st1 + # reset values for next call in case of interval == None + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + + try: + # This is the utilization split evenly between all CPUs. + # E.g. a busy loop process on a 2-CPU-cores system at this + # point is reported as 50% instead of 100%. + overall_cpus_percent = ((delta_proc / delta_time) * 100) + except ZeroDivisionError: + # interval was too low + return 0.0 + else: + # Note 1: + # in order to emulate "top" we multiply the value for the num + # of CPU cores. This way the busy process will be reported as + # having 100% (or more) usage. + # + # Note 2: + # taskmgr.exe on Windows differs in that it will show 50% + # instead. + # + # Note 3: + # a percentage > 100 is legitimate as it can result from a + # process with multiple threads running on different CPU + # cores (top does the same), see: + # http://stackoverflow.com/questions/1032357 + # https://github.com/giampaolo/psutil/issues/474 + single_cpu_percent = overall_cpus_percent * num_cpus + return round(single_cpu_percent, 1) + + @memoize_when_activated + def cpu_times(self): + """Return a (user, system, children_user, children_system) + namedtuple representing the accumulated process time, in + seconds. + This is similar to os.times() but per-process. + On macOS and Windows children_user and children_system are + always set to 0. + """ + return self._proc.cpu_times() + + @memoize_when_activated + def memory_info(self): + """Return a namedtuple with variable fields depending on the + platform, representing memory information about the process. + + The "portable" fields available on all plaforms are `rss` and `vms`. + + All numbers are expressed in bytes. + """ + return self._proc.memory_info() + + @deprecated_method(replacement="memory_info") + def memory_info_ex(self): + return self.memory_info() + + def memory_full_info(self): + """This method returns the same information as memory_info(), + plus, on some platform (Linux, macOS, Windows), also provides + additional metrics (USS, PSS and swap). + The additional metrics provide a better representation of actual + process memory usage. + + Namely USS is the memory which is unique to a process and which + would be freed if the process was terminated right now. + + It does so by passing through the whole process address. + As such it usually requires higher user privileges than + memory_info() and is considerably slower. + """ + return self._proc.memory_full_info() + + def memory_percent(self, memtype="rss"): + """Compare process memory to total physical system memory and + calculate process memory utilization as a percentage. + *memtype* argument is a string that dictates what type of + process memory you want to compare against (defaults to "rss"). + The list of available strings can be obtained like this: + + >>> psutil.Process().memory_info()._fields + ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss') + """ + valid_types = list(_psplatform.pfullmem._fields) + if memtype not in valid_types: + raise ValueError("invalid memtype %r; valid types are %r" % ( + memtype, tuple(valid_types))) + fun = self.memory_info if memtype in _psplatform.pmem._fields else \ + self.memory_full_info + metrics = fun() + value = getattr(metrics, memtype) + + # use cached value if available + total_phymem = _TOTAL_PHYMEM or virtual_memory().total + if not total_phymem > 0: + # we should never get here + raise ValueError( + "can't calculate process memory percent because " + "total physical system memory is not positive (%r)" + % total_phymem) + return (value / float(total_phymem)) * 100 + + if hasattr(_psplatform.Process, "memory_maps"): + def memory_maps(self, grouped=True): + """Return process' mapped memory regions as a list of namedtuples + whose fields are variable depending on the platform. + + If *grouped* is True the mapped regions with the same 'path' + are grouped together and the different memory fields are summed. + + If *grouped* is False every mapped region is shown as a single + entity and the namedtuple will also include the mapped region's + address space ('addr') and permission set ('perms'). + """ + it = self._proc.memory_maps() + if grouped: + d = {} + for tupl in it: + path = tupl[2] + nums = tupl[3:] + try: + d[path] = map(lambda x, y: x + y, d[path], nums) + except KeyError: + d[path] = nums + nt = _psplatform.pmmap_grouped + return [nt(path, *d[path]) for path in d] # NOQA + else: + nt = _psplatform.pmmap_ext + return [nt(*x) for x in it] + + def open_files(self): + """Return files opened by process as a list of + (path, fd) namedtuples including the absolute file name + and file descriptor number. + """ + return self._proc.open_files() + + def connections(self, kind='inet'): + """Return socket connections opened by process as a list of + (fd, family, type, laddr, raddr, status) namedtuples. + The *kind* parameter filters for connections that match the + following criteria: + + +------------+----------------------------------------------------+ + | Kind Value | Connections using | + +------------+----------------------------------------------------+ + | inet | IPv4 and IPv6 | + | inet4 | IPv4 | + | inet6 | IPv6 | + | tcp | TCP | + | tcp4 | TCP over IPv4 | + | tcp6 | TCP over IPv6 | + | udp | UDP | + | udp4 | UDP over IPv4 | + | udp6 | UDP over IPv6 | + | unix | UNIX socket (both UDP and TCP protocols) | + | all | the sum of all the possible families and protocols | + +------------+----------------------------------------------------+ + """ + return self._proc.connections(kind) + + # --- signals + + if POSIX: + def _send_signal(self, sig): + assert not self.pid < 0, self.pid + if self.pid == 0: + # see "man 2 kill" + raise ValueError( + "preventing sending signal to process with PID 0 as it " + "would affect every process in the process group of the " + "calling process (os.getpid()) instead of PID 0") + try: + os.kill(self.pid, sig) + except ProcessLookupError: + if OPENBSD and pid_exists(self.pid): + # We do this because os.kill() lies in case of + # zombie processes. + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + self._gone = True + raise NoSuchProcess(self.pid, self._name) + except PermissionError: + raise AccessDenied(self.pid, self._name) + + @_assert_pid_not_reused + def send_signal(self, sig): + """Send a signal *sig* to process pre-emptively checking + whether PID has been reused (see signal module constants) . + On Windows only SIGTERM is valid and is treated as an alias + for kill(). + """ + if POSIX: + self._send_signal(sig) + else: # pragma: no cover + if sig == signal.SIGTERM: + self._proc.kill() + # py >= 2.7 + elif sig in (getattr(signal, "CTRL_C_EVENT", object()), + getattr(signal, "CTRL_BREAK_EVENT", object())): + self._proc.send_signal(sig) + else: + raise ValueError( + "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals " + "are supported on Windows") + + @_assert_pid_not_reused + def suspend(self): + """Suspend process execution with SIGSTOP pre-emptively checking + whether PID has been reused. + On Windows this has the effect ot suspending all process threads. + """ + if POSIX: + self._send_signal(signal.SIGSTOP) + else: # pragma: no cover + self._proc.suspend() + + @_assert_pid_not_reused + def resume(self): + """Resume process execution with SIGCONT pre-emptively checking + whether PID has been reused. + On Windows this has the effect of resuming all process threads. + """ + if POSIX: + self._send_signal(signal.SIGCONT) + else: # pragma: no cover + self._proc.resume() + + @_assert_pid_not_reused + def terminate(self): + """Terminate the process with SIGTERM pre-emptively checking + whether PID has been reused. + On Windows this is an alias for kill(). + """ + if POSIX: + self._send_signal(signal.SIGTERM) + else: # pragma: no cover + self._proc.kill() + + @_assert_pid_not_reused + def kill(self): + """Kill the current process with SIGKILL pre-emptively checking + whether PID has been reused. + """ + if POSIX: + self._send_signal(signal.SIGKILL) + else: # pragma: no cover + self._proc.kill() + + def wait(self, timeout=None): + """Wait for process to terminate and, if process is a children + of os.getpid(), also return its exit code, else None. + + If the process is already terminated immediately return None + instead of raising NoSuchProcess. + + If *timeout* (in seconds) is specified and process is still + alive raise TimeoutExpired. + + To wait for multiple Process(es) use psutil.wait_procs(). + """ + if timeout is not None and not timeout >= 0: + raise ValueError("timeout must be a positive integer") + return self._proc.wait(timeout) + + +# ===================================================================== +# --- Popen class +# ===================================================================== + + +class Popen(Process): + """A more convenient interface to stdlib subprocess.Popen class. + It starts a sub process and deals with it exactly as when using + subprocess.Popen class but in addition also provides all the + properties and methods of psutil.Process class as a unified + interface: + + >>> import psutil + >>> from subprocess import PIPE + >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE) + >>> p.name() + 'python' + >>> p.uids() + user(real=1000, effective=1000, saved=1000) + >>> p.username() + 'giampaolo' + >>> p.communicate() + ('hi\n', None) + >>> p.terminate() + >>> p.wait(timeout=2) + 0 + >>> + + For method names common to both classes such as kill(), terminate() + and wait(), psutil.Process implementation takes precedence. + + Unlike subprocess.Popen this class pre-emptively checks whether PID + has been reused on send_signal(), terminate() and kill() so that + you don't accidentally terminate another process, fixing + http://bugs.python.org/issue6973. + + For a complete documentation refer to: + http://docs.python.org/3/library/subprocess.html + """ + + def __init__(self, *args, **kwargs): + # Explicitly avoid to raise NoSuchProcess in case the process + # spawned by subprocess.Popen terminates too quickly, see: + # https://github.com/giampaolo/psutil/issues/193 + self.__subproc = subprocess.Popen(*args, **kwargs) + self._init(self.__subproc.pid, _ignore_nsp=True) + + def __dir__(self): + return sorted(set(dir(Popen) + dir(subprocess.Popen))) + + def __enter__(self): + if hasattr(self.__subproc, '__enter__'): + self.__subproc.__enter__() + return self + + def __exit__(self, *args, **kwargs): + if hasattr(self.__subproc, '__exit__'): + return self.__subproc.__exit__(*args, **kwargs) + else: + if self.stdout: + self.stdout.close() + if self.stderr: + self.stderr.close() + try: + # Flushing a BufferedWriter may raise an error. + if self.stdin: + self.stdin.close() + finally: + # Wait for the process to terminate, to avoid zombies. + self.wait() + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + try: + return object.__getattribute__(self.__subproc, name) + except AttributeError: + raise AttributeError("%s instance has no attribute '%s'" + % (self.__class__.__name__, name)) + + def wait(self, timeout=None): + if self.__subproc.returncode is not None: + return self.__subproc.returncode + ret = super(Popen, self).wait(timeout) + self.__subproc.returncode = ret + return ret + + +# The valid attr names which can be processed by Process.as_dict(). +_as_dict_attrnames = set( + [x for x in dir(Process) if not x.startswith('_') and x not in + ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit', + 'memory_info_ex', 'oneshot']]) + + +# ===================================================================== +# --- system processes related functions +# ===================================================================== + + +def pids(): + """Return a list of current running PIDs.""" + global _LOWEST_PID + ret = sorted(_psplatform.pids()) + _LOWEST_PID = ret[0] + return ret + + +def pid_exists(pid): + """Return True if given PID exists in the current process list. + This is faster than doing "pid in psutil.pids()" and + should be preferred. + """ + if pid < 0: + return False + elif pid == 0 and POSIX: + # On POSIX we use os.kill() to determine PID existence. + # According to "man 2 kill" PID 0 has a special meaning + # though: it refers to <> and that is not we want + # to do here. + return pid in pids() + else: + return _psplatform.pid_exists(pid) + + +_pmap = {} +_lock = threading.Lock() + + +def process_iter(attrs=None, ad_value=None): + """Return a generator yielding a Process instance for all + running processes. + + Every new Process instance is only created once and then cached + into an internal table which is updated every time this is used. + + Cached Process instances are checked for identity so that you're + safe in case a PID has been reused by another process, in which + case the cached instance is updated. + + The sorting order in which processes are yielded is based on + their PIDs. + + *attrs* and *ad_value* have the same meaning as in + Process.as_dict(). If *attrs* is specified as_dict() is called + and the resulting dict is stored as a 'info' attribute attached + to returned Process instance. + If *attrs* is an empty list it will retrieve all process info + (slow). + """ + def add(pid): + proc = Process(pid) + if attrs is not None: + proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value) + with _lock: + _pmap[proc.pid] = proc + return proc + + def remove(pid): + with _lock: + _pmap.pop(pid, None) + + a = set(pids()) + b = set(_pmap.keys()) + new_pids = a - b + gone_pids = b - a + for pid in gone_pids: + remove(pid) + + with _lock: + ls = sorted(list(_pmap.items()) + + list(dict.fromkeys(new_pids).items())) + + for pid, proc in ls: + try: + if proc is None: # new process + yield add(pid) + else: + # use is_running() to check whether PID has been reused by + # another process in which case yield a new Process instance + if proc.is_running(): + if attrs is not None: + proc.info = proc.as_dict( + attrs=attrs, ad_value=ad_value) + yield proc + else: + yield add(pid) + except NoSuchProcess: + remove(pid) + except AccessDenied: + # Process creation time can't be determined hence there's + # no way to tell whether the pid of the cached process + # has been reused. Just return the cached version. + if proc is None and pid in _pmap: + try: + yield _pmap[pid] + except KeyError: + # If we get here it is likely that 2 threads were + # using process_iter(). + pass + else: + raise + + +def wait_procs(procs, timeout=None, callback=None): + """Convenience function which waits for a list of processes to + terminate. + + Return a (gone, alive) tuple indicating which processes + are gone and which ones are still alive. + + The gone ones will have a new *returncode* attribute indicating + process exit status (may be None). + + *callback* is a function which gets called every time a process + terminates (a Process instance is passed as callback argument). + + Function will return as soon as all processes terminate or when + *timeout* occurs. + Differently from Process.wait() it will not raise TimeoutExpired if + *timeout* occurs. + + Typical use case is: + + - send SIGTERM to a list of processes + - give them some time to terminate + - send SIGKILL to those ones which are still alive + + Example: + + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> for p in procs: + ... p.terminate() + ... + >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) + >>> for p in alive: + ... p.kill() + """ + def check_gone(proc, timeout): + try: + returncode = proc.wait(timeout=timeout) + except TimeoutExpired: + pass + else: + if returncode is not None or not proc.is_running(): + proc.returncode = returncode + gone.add(proc) + if callback is not None: + callback(proc) + + if timeout is not None and not timeout >= 0: + msg = "timeout must be a positive integer, got %s" % timeout + raise ValueError(msg) + gone = set() + alive = set(procs) + if callback is not None and not callable(callback): + raise TypeError("callback %r is not a callable" % callable) + if timeout is not None: + deadline = _timer() + timeout + + while alive: + if timeout is not None and timeout <= 0: + break + for proc in alive: + # Make sure that every complete iteration (all processes) + # will last max 1 sec. + # We do this because we don't want to wait too long on a + # single process: in case it terminates too late other + # processes may disappear in the meantime and their PID + # reused. + max_timeout = 1.0 / len(alive) + if timeout is not None: + timeout = min((deadline - _timer()), max_timeout) + if timeout <= 0: + break + check_gone(proc, timeout) + else: + check_gone(proc, max_timeout) + alive = alive - gone + + if alive: + # Last attempt over processes survived so far. + # timeout == 0 won't make this function wait any further. + for proc in alive: + check_gone(proc, 0) + alive = alive - gone + + return (list(gone), list(alive)) + + +# ===================================================================== +# --- CPU related functions +# ===================================================================== + + +def cpu_count(logical=True): + """Return the number of logical CPUs in the system (same as + os.cpu_count() in Python 3.4). + + If *logical* is False return the number of physical cores only + (e.g. hyper thread CPUs are excluded). + + Return None if undetermined. + + The return value is cached after first call. + If desired cache can be cleared like this: + + >>> psutil.cpu_count.cache_clear() + """ + if logical: + ret = _psplatform.cpu_count_logical() + else: + ret = _psplatform.cpu_count_physical() + if ret is not None and ret < 1: + ret = None + return ret + + +def cpu_times(percpu=False): + """Return system-wide CPU times as a namedtuple. + Every CPU time represents the seconds the CPU has spent in the + given mode. The namedtuple's fields availability varies depending on the + platform: + + - user + - system + - idle + - nice (UNIX) + - iowait (Linux) + - irq (Linux, FreeBSD) + - softirq (Linux) + - steal (Linux >= 2.6.11) + - guest (Linux >= 2.6.24) + - guest_nice (Linux >= 3.2.0) + + When *percpu* is True return a list of namedtuples for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + """ + if not percpu: + return _psplatform.cpu_times() + else: + return _psplatform.per_cpu_times() + + +try: + _last_cpu_times = cpu_times() +except Exception: + # Don't want to crash at import time. + _last_cpu_times = None + +try: + _last_per_cpu_times = cpu_times(percpu=True) +except Exception: + # Don't want to crash at import time. + _last_per_cpu_times = None + + +def _cpu_tot_time(times): + """Given a cpu_time() ntuple calculates the total CPU time + (including idle time). + """ + tot = sum(times) + if LINUX: + # On Linux guest times are already accounted in "user" or + # "nice" times, so we subtract them from total. + # Htop does the same. References: + # https://github.com/giampaolo/psutil/pull/940 + # http://unix.stackexchange.com/questions/178045 + # https://github.com/torvalds/linux/blob/ + # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/ + # cputime.c#L158 + tot -= getattr(times, "guest", 0) # Linux 2.6.24+ + tot -= getattr(times, "guest_nice", 0) # Linux 3.2.0+ + return tot + + +def _cpu_busy_time(times): + """Given a cpu_time() ntuple calculates the busy CPU time. + We do so by subtracting all idle CPU times. + """ + busy = _cpu_tot_time(times) + busy -= times.idle + # Linux: "iowait" is time during which the CPU does not do anything + # (waits for IO to complete). On Linux IO wait is *not* accounted + # in "idle" time so we subtract it. Htop does the same. + # References: + # https://github.com/torvalds/linux/blob/ + # 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/cputime.c#L244 + busy -= getattr(times, "iowait", 0) + return busy + + +def _cpu_times_deltas(t1, t2): + assert t1._fields == t2._fields, (t1, t2) + field_deltas = [] + for field in _psplatform.scputimes._fields: + field_delta = getattr(t2, field) - getattr(t1, field) + # CPU times are always supposed to increase over time + # or at least remain the same and that's because time + # cannot go backwards. + # Surprisingly sometimes this might not be the case (at + # least on Windows and Linux), see: + # https://github.com/giampaolo/psutil/issues/392 + # https://github.com/giampaolo/psutil/issues/645 + # https://github.com/giampaolo/psutil/issues/1210 + # Trim negative deltas to zero to ignore decreasing fields. + # top does the same. Reference: + # https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063 + field_delta = max(0, field_delta) + field_deltas.append(field_delta) + return _psplatform.scputimes(*field_deltas) + + +def cpu_percent(interval=None, percpu=False): + """Return a float representing the current system-wide CPU + utilization as a percentage. + + When *interval* is > 0.0 compares system CPU times elapsed before + and after the interval (blocking). + + When *interval* is 0.0 or None compares system CPU times elapsed + since last call or module import, returning immediately (non + blocking). That means the first time this is called it will + return a meaningless 0.0 value which you should ignore. + In this case is recommended for accuracy that this function be + called with at least 0.1 seconds between calls. + + When *percpu* is True returns a list of floats representing the + utilization as a percentage for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + + Examples: + + >>> # blocking, system-wide + >>> psutil.cpu_percent(interval=1) + 2.0 + >>> + >>> # blocking, per-cpu + >>> psutil.cpu_percent(interval=1, percpu=True) + [2.0, 1.0] + >>> + >>> # non-blocking (percentage since last call) + >>> psutil.cpu_percent(interval=None) + 2.9 + >>> + """ + global _last_cpu_times + global _last_per_cpu_times + blocking = interval is not None and interval > 0.0 + if interval is not None and interval < 0: + raise ValueError("interval is not positive (got %r)" % interval) + + def calculate(t1, t2): + times_delta = _cpu_times_deltas(t1, t2) + + all_delta = _cpu_tot_time(times_delta) + busy_delta = _cpu_busy_time(times_delta) + + try: + busy_perc = (busy_delta / all_delta) * 100 + except ZeroDivisionError: + return 0.0 + else: + return round(busy_perc, 1) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times + if t1 is None: + # Something bad happened at import time. We'll + # get a meaningful result on the next call. See: + # https://github.com/giampaolo/psutil/pull/715 + t1 = cpu_times() + _last_cpu_times = cpu_times() + return calculate(t1, _last_cpu_times) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times + if tot1 is None: + # Something bad happened at import time. We'll + # get a meaningful result on the next call. See: + # https://github.com/giampaolo/psutil/pull/715 + tot1 = cpu_times(percpu=True) + _last_per_cpu_times = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times): + ret.append(calculate(t1, t2)) + return ret + + +# Use separate global vars for cpu_times_percent() so that it's +# independent from cpu_percent() and they can both be used within +# the same program. +_last_cpu_times_2 = _last_cpu_times +_last_per_cpu_times_2 = _last_per_cpu_times + + +def cpu_times_percent(interval=None, percpu=False): + """Same as cpu_percent() but provides utilization percentages + for each specific CPU time as is returned by cpu_times(). + For instance, on Linux we'll get: + + >>> cpu_times_percent() + cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0, + irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + + *interval* and *percpu* arguments have the same meaning as in + cpu_percent(). + """ + global _last_cpu_times_2 + global _last_per_cpu_times_2 + blocking = interval is not None and interval > 0.0 + if interval is not None and interval < 0: + raise ValueError("interval is not positive (got %r)" % interval) + + def calculate(t1, t2): + nums = [] + times_delta = _cpu_times_deltas(t1, t2) + all_delta = _cpu_tot_time(times_delta) + # "scale" is the value to multiply each delta with to get percentages. + # We use "max" to avoid division by zero (if all_delta is 0, then all + # fields are 0 so percentages will be 0 too. all_delta cannot be a + # fraction because cpu times are integers) + scale = 100.0 / max(1, all_delta) + for field_delta in times_delta: + field_perc = field_delta * scale + field_perc = round(field_perc, 1) + # make sure we don't return negative values or values over 100% + field_perc = min(max(0.0, field_perc), 100.0) + nums.append(field_perc) + return _psplatform.scputimes(*nums) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times_2 + if t1 is None: + # Something bad happened at import time. We'll + # get a meaningful result on the next call. See: + # https://github.com/giampaolo/psutil/pull/715 + t1 = cpu_times() + _last_cpu_times_2 = cpu_times() + return calculate(t1, _last_cpu_times_2) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times_2 + if tot1 is None: + # Something bad happened at import time. We'll + # get a meaningful result on the next call. See: + # https://github.com/giampaolo/psutil/pull/715 + tot1 = cpu_times(percpu=True) + _last_per_cpu_times_2 = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times_2): + ret.append(calculate(t1, t2)) + return ret + + +def cpu_stats(): + """Return CPU statistics.""" + return _psplatform.cpu_stats() + + +if hasattr(_psplatform, "cpu_freq"): + + def cpu_freq(percpu=False): + """Return CPU frequency as a nameduple including current, + min and max frequency expressed in Mhz. + + If *percpu* is True and the system supports per-cpu frequency + retrieval (Linux only) a list of frequencies is returned for + each CPU. If not a list with one element is returned. + """ + ret = _psplatform.cpu_freq() + if percpu: + return ret + else: + num_cpus = float(len(ret)) + if num_cpus == 0: + return None + elif num_cpus == 1: + return ret[0] + else: + currs, mins, maxs = 0.0, 0.0, 0.0 + set_none = False + for cpu in ret: + currs += cpu.current + # On Linux if /proc/cpuinfo is used min/max are set + # to None. + if LINUX and cpu.min is None: + set_none = True + continue + mins += cpu.min + maxs += cpu.max + + current = currs / num_cpus + + if set_none: + min_ = max_ = None + else: + min_ = mins / num_cpus + max_ = maxs / num_cpus + + return _common.scpufreq(current, min_, max_) + + __all__.append("cpu_freq") + + +if hasattr(os, "getloadavg") or hasattr(_psplatform, "getloadavg"): + # Perform this hasattr check once on import time to either use the + # platform based code or proxy straight from the os module. + if hasattr(os, "getloadavg"): + getloadavg = os.getloadavg + else: + getloadavg = _psplatform.getloadavg + + __all__.append("getloadavg") + + +# ===================================================================== +# --- system memory related functions +# ===================================================================== + + +def virtual_memory(): + """Return statistics about system memory usage as a namedtuple + including the following fields, expressed in bytes: + + - total: + total physical memory available. + + - available: + the memory that can be given instantly to processes without the + system going into swap. + This is calculated by summing different memory values depending + on the platform and it is supposed to be used to monitor actual + memory usage in a cross platform fashion. + + - percent: + the percentage usage calculated as (total - available) / total * 100 + + - used: + memory used, calculated differently depending on the platform and + designed for informational purposes only: + macOS: active + wired + BSD: active + wired + cached + Linux: total - free + + - free: + memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available + (use 'available' instead) + + Platform-specific fields: + + - active (UNIX): + memory currently in use or very recently used, and so it is in RAM. + + - inactive (UNIX): + memory that is marked as not used. + + - buffers (BSD, Linux): + cache for things like file system metadata. + + - cached (BSD, macOS): + cache for various things. + + - wired (macOS, BSD): + memory that is marked to always stay in RAM. It is never moved to disk. + + - shared (BSD): + memory that may be simultaneously accessed by multiple processes. + + The sum of 'used' and 'available' does not necessarily equal total. + On Windows 'available' and 'free' are the same. + """ + global _TOTAL_PHYMEM + ret = _psplatform.virtual_memory() + # cached for later use in Process.memory_percent() + _TOTAL_PHYMEM = ret.total + return ret + + +def swap_memory(): + """Return system swap memory statistics as a namedtuple including + the following fields: + + - total: total swap memory in bytes + - used: used swap memory in bytes + - free: free swap memory in bytes + - percent: the percentage usage + - sin: no. of bytes the system has swapped in from disk (cumulative) + - sout: no. of bytes the system has swapped out from disk (cumulative) + + 'sin' and 'sout' on Windows are meaningless and always set to 0. + """ + return _psplatform.swap_memory() + + +# ===================================================================== +# --- disks/paritions related functions +# ===================================================================== + + +def disk_usage(path): + """Return disk usage statistics about the given *path* as a + namedtuple including total, used and free space expressed in bytes + plus the percentage usage. + """ + return _psplatform.disk_usage(path) + + +def disk_partitions(all=False): + """Return mounted partitions as a list of + (device, mountpoint, fstype, opts) namedtuple. + 'opts' field is a raw string separated by commas indicating mount + options which may vary depending on the platform. + + If *all* parameter is False return physical devices only and ignore + all others. + """ + return _psplatform.disk_partitions(all) + + +def disk_io_counters(perdisk=False, nowrap=True): + """Return system disk I/O statistics as a namedtuple including + the following fields: + + - read_count: number of reads + - write_count: number of writes + - read_bytes: number of bytes read + - write_bytes: number of bytes written + - read_time: time spent reading from disk (in ms) + - write_time: time spent writing to disk (in ms) + + Platform specific: + + - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms) + - read_merged_count (Linux): number of merged reads + - write_merged_count (Linux): number of merged writes + + If *perdisk* is True return the same information for every + physical disk installed on the system as a dictionary + with partition names as the keys and the namedtuple + described above as the values. + + If *nowrap* is True it detects and adjust the numbers which overflow + and wrap (restart from 0) and add "old value" to "new value" so that + the returned numbers will always be increasing or remain the same, + but never decrease. + "disk_io_counters.cache_clear()" can be used to invalidate the + cache. + + On recent Windows versions 'diskperf -y' command may need to be + executed first otherwise this function won't find any disk. + """ + kwargs = dict(perdisk=perdisk) if LINUX else {} + rawdict = _psplatform.disk_io_counters(**kwargs) + if not rawdict: + return {} if perdisk else None + if nowrap: + rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters') + nt = getattr(_psplatform, "sdiskio", _common.sdiskio) + if perdisk: + for disk, fields in rawdict.items(): + rawdict[disk] = nt(*fields) + return rawdict + else: + return nt(*[sum(x) for x in zip(*rawdict.values())]) + + +disk_io_counters.cache_clear = functools.partial( + _wrap_numbers.cache_clear, 'psutil.disk_io_counters') +disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" + + +# ===================================================================== +# --- network related functions +# ===================================================================== + + +def net_io_counters(pernic=False, nowrap=True): + """Return network I/O statistics as a namedtuple including + the following fields: + + - bytes_sent: number of bytes sent + - bytes_recv: number of bytes received + - packets_sent: number of packets sent + - packets_recv: number of packets received + - errin: total number of errors while receiving + - errout: total number of errors while sending + - dropin: total number of incoming packets which were dropped + - dropout: total number of outgoing packets which were dropped + (always 0 on macOS and BSD) + + If *pernic* is True return the same information for every + network interface installed on the system as a dictionary + with network interface names as the keys and the namedtuple + described above as the values. + + If *nowrap* is True it detects and adjust the numbers which overflow + and wrap (restart from 0) and add "old value" to "new value" so that + the returned numbers will always be increasing or remain the same, + but never decrease. + "disk_io_counters.cache_clear()" can be used to invalidate the + cache. + """ + rawdict = _psplatform.net_io_counters() + if not rawdict: + return {} if pernic else None + if nowrap: + rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters') + if pernic: + for nic, fields in rawdict.items(): + rawdict[nic] = _common.snetio(*fields) + return rawdict + else: + return _common.snetio(*[sum(x) for x in zip(*rawdict.values())]) + + +net_io_counters.cache_clear = functools.partial( + _wrap_numbers.cache_clear, 'psutil.net_io_counters') +net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" + + +def net_connections(kind='inet'): + """Return system-wide socket connections as a list of + (fd, family, type, laddr, raddr, status, pid) namedtuples. + In case of limited privileges 'fd' and 'pid' may be set to -1 + and None respectively. + The *kind* parameter filters for connections that fit the + following criteria: + + +------------+----------------------------------------------------+ + | Kind Value | Connections using | + +------------+----------------------------------------------------+ + | inet | IPv4 and IPv6 | + | inet4 | IPv4 | + | inet6 | IPv6 | + | tcp | TCP | + | tcp4 | TCP over IPv4 | + | tcp6 | TCP over IPv6 | + | udp | UDP | + | udp4 | UDP over IPv4 | + | udp6 | UDP over IPv6 | + | unix | UNIX socket (both UDP and TCP protocols) | + | all | the sum of all the possible families and protocols | + +------------+----------------------------------------------------+ + + On macOS this function requires root privileges. + """ + return _psplatform.net_connections(kind) + + +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system as a dictionary whose keys are the + NIC names and value is a list of namedtuples for each address + assigned to the NIC. Each namedtuple includes 5 fields: + + - family: can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + - address: is the primary address and it is always set. + - netmask: and 'broadcast' and 'ptp' may be None. + - ptp: stands for "point to point" and references the + destination address on a point to point interface + (typically a VPN). + - broadcast: and *ptp* are mutually exclusive. + + Note: you can have more than one address of the same family + associated with each interface. + """ + has_enums = sys.version_info >= (3, 4) + if has_enums: + import socket + rawlist = _psplatform.net_if_addrs() + rawlist.sort(key=lambda x: x[1]) # sort by family + ret = collections.defaultdict(list) + for name, fam, addr, mask, broadcast, ptp in rawlist: + if has_enums: + try: + fam = socket.AddressFamily(fam) + except ValueError: + if WINDOWS and fam == -1: + fam = _psplatform.AF_LINK + elif (hasattr(_psplatform, "AF_LINK") and + _psplatform.AF_LINK == fam): + # Linux defines AF_LINK as an alias for AF_PACKET. + # We re-set the family here so that repr(family) + # will show AF_LINK rather than AF_PACKET + fam = _psplatform.AF_LINK + if fam == _psplatform.AF_LINK: + # The underlying C function may return an incomplete MAC + # address in which case we fill it with null bytes, see: + # https://github.com/giampaolo/psutil/issues/786 + separator = ":" if POSIX else "-" + while addr.count(separator) < 5: + addr += "%s00" % separator + ret[name].append(_common.snicaddr(fam, addr, mask, broadcast, ptp)) + return dict(ret) + + +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system as a dictionary whose keys are the + NIC names and value is a namedtuple with the following fields: + + - isup: whether the interface is up (bool) + - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or + NIC_DUPLEX_UNKNOWN + - speed: the NIC speed expressed in mega bits (MB); if it can't + be determined (e.g. 'localhost') it will be set to 0. + - mtu: the maximum transmission unit expressed in bytes. + """ + return _psplatform.net_if_stats() + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +# Linux, macOS +if hasattr(_psplatform, "sensors_temperatures"): + + def sensors_temperatures(fahrenheit=False): + """Return hardware temperatures. Each entry is a namedtuple + representing a certain hardware sensor (it may be a CPU, an + hard disk or something else, depending on the OS and its + configuration). + All temperatures are expressed in celsius unless *fahrenheit* + is set to True. + """ + def convert(n): + if n is not None: + return (float(n) * 9 / 5) + 32 if fahrenheit else n + + ret = collections.defaultdict(list) + rawdict = _psplatform.sensors_temperatures() + + for name, values in rawdict.items(): + while values: + label, current, high, critical = values.pop(0) + current = convert(current) + high = convert(high) + critical = convert(critical) + + if high and not critical: + critical = high + elif critical and not high: + high = critical + + ret[name].append( + _common.shwtemp(label, current, high, critical)) + + return dict(ret) + + __all__.append("sensors_temperatures") + + +# Linux, macOS +if hasattr(_psplatform, "sensors_fans"): + + def sensors_fans(): + """Return fans speed. Each entry is a namedtuple + representing a certain hardware sensor. + All speed are expressed in RPM (rounds per minute). + """ + return _psplatform.sensors_fans() + + __all__.append("sensors_fans") + + +# Linux, Windows, FreeBSD, macOS +if hasattr(_psplatform, "sensors_battery"): + + def sensors_battery(): + """Return battery information. If no battery is installed + returns None. + + - percent: battery power left as a percentage. + - secsleft: a rough approximation of how many seconds are left + before the battery runs out of power. May be + POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED. + - power_plugged: True if the AC power cable is connected. + """ + return _psplatform.sensors_battery() + + __all__.append("sensors_battery") + + +# ===================================================================== +# --- other system related functions +# ===================================================================== + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + # Note: we are not caching this because it is subject to + # system clock updates. + return _psplatform.boot_time() + + +def users(): + """Return users currently connected on the system as a list of + namedtuples including the following fields. + + - user: the name of the user + - terminal: the tty or pseudo-tty associated with the user, if any. + - host: the host name associated with the entry, if any. + - started: the creation time as a floating point number expressed in + seconds since the epoch. + """ + return _psplatform.users() + + +# ===================================================================== +# --- Windows services +# ===================================================================== + + +if WINDOWS: + + def win_service_iter(): + """Return a generator yielding a WindowsService instance for all + Windows services installed. + """ + return _psplatform.win_service_iter() + + def win_service_get(name): + """Get a Windows service by *name*. + Raise NoSuchProcess if no service with such name exists. + """ + return _psplatform.win_service_get(name) + + +# ===================================================================== + + +def test(): # pragma: no cover + from ._common import bytes2human + from ._compat import get_terminal_size + + today_day = datetime.date.today() + templ = "%-10s %5s %5s %7s %7s %5s %6s %6s %6s %s" + attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times', + 'create_time', 'memory_info', 'status', 'nice', 'username'] + print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", + "STATUS", "START", "TIME", "CMDLINE")) + for p in process_iter(attrs, ad_value=None): + if p.info['create_time']: + ctime = datetime.datetime.fromtimestamp(p.info['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") + else: + ctime = ctime.strftime("%b%d") + else: + ctime = '' + if p.info['cpu_times']: + cputime = time.strftime("%M:%S", + time.localtime(sum(p.info['cpu_times']))) + else: + cputime = '' + + user = p.info['username'] or '' + if not user and POSIX: + try: + user = p.uids()[0] + except Error: + pass + if user and WINDOWS and '\\' in user: + user = user.split('\\')[1] + user = user[:9] + vms = bytes2human(p.info['memory_info'].vms) if \ + p.info['memory_info'] is not None else '' + rss = bytes2human(p.info['memory_info'].rss) if \ + p.info['memory_info'] is not None else '' + memp = round(p.info['memory_percent'], 1) if \ + p.info['memory_percent'] is not None else '' + nice = int(p.info['nice']) if p.info['nice'] else '' + if p.info['cmdline']: + cmdline = ' '.join(p.info['cmdline']) + else: + cmdline = p.info['name'] + status = p.info['status'][:5] if p.info['status'] else '' + + line = templ % ( + user[:10], + p.info['pid'], + memp, + vms, + rss, + nice, + status, + ctime, + cputime, + cmdline) + print(line[:get_terminal_size()[0]]) + + +del memoize, memoize_when_activated, division, deprecated_method +if sys.version_info[0] < 3: + del num, x + +if __name__ == "__main__": + test() diff --git a/ddtrace/vendor/psutil/_common.py b/ddtrace/vendor/psutil/_common.py new file mode 100644 index 00000000000..a0f5e751b83 --- /dev/null +++ b/ddtrace/vendor/psutil/_common.py @@ -0,0 +1,651 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Common objects shared by __init__.py and _ps*.py modules.""" + +# Note: this module is imported by setup.py so it should not import +# psutil or third-party modules. + +from __future__ import division + +import contextlib +import errno +import functools +import os +import socket +import stat +import sys +import threading +import warnings +from collections import defaultdict +from collections import namedtuple +from socket import AF_INET +from socket import SOCK_DGRAM +from socket import SOCK_STREAM +try: + from socket import AF_INET6 +except ImportError: + AF_INET6 = None +try: + from socket import AF_UNIX +except ImportError: + AF_UNIX = None + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + +# can't take it from _common.py as this script is imported by setup.py +PY3 = sys.version_info[0] == 3 + +__all__ = [ + # constants + 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX', + 'SUNOS', 'WINDOWS', + 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', + # connection constants + 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', + 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', + 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', + # net constants + 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', + # process status constants + 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', + 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', + 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', + 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', + # named tuples + 'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile', + 'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart', + 'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser', + # utility functions + 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', + 'parse_environ_block', 'path_exists_strict', 'usage_percent', + 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", + 'bytes2human', 'conn_to_ntuple', +] + + +# =================================================================== +# --- OS constants +# =================================================================== + + +POSIX = os.name == "posix" +WINDOWS = os.name == "nt" +LINUX = sys.platform.startswith("linux") +MACOS = sys.platform.startswith("darwin") +OSX = MACOS # deprecated alias +FREEBSD = sys.platform.startswith("freebsd") +OPENBSD = sys.platform.startswith("openbsd") +NETBSD = sys.platform.startswith("netbsd") +BSD = FREEBSD or OPENBSD or NETBSD +SUNOS = sys.platform.startswith(("sunos", "solaris")) +AIX = sys.platform.startswith("aix") + + +# =================================================================== +# --- API constants +# =================================================================== + + +# Process.status() +STATUS_RUNNING = "running" +STATUS_SLEEPING = "sleeping" +STATUS_DISK_SLEEP = "disk-sleep" +STATUS_STOPPED = "stopped" +STATUS_TRACING_STOP = "tracing-stop" +STATUS_ZOMBIE = "zombie" +STATUS_DEAD = "dead" +STATUS_WAKE_KILL = "wake-kill" +STATUS_WAKING = "waking" +STATUS_IDLE = "idle" # Linux, macOS, FreeBSD +STATUS_LOCKED = "locked" # FreeBSD +STATUS_WAITING = "waiting" # FreeBSD +STATUS_SUSPENDED = "suspended" # NetBSD +STATUS_PARKED = "parked" # Linux + +# Process.connections() and psutil.net_connections() +CONN_ESTABLISHED = "ESTABLISHED" +CONN_SYN_SENT = "SYN_SENT" +CONN_SYN_RECV = "SYN_RECV" +CONN_FIN_WAIT1 = "FIN_WAIT1" +CONN_FIN_WAIT2 = "FIN_WAIT2" +CONN_TIME_WAIT = "TIME_WAIT" +CONN_CLOSE = "CLOSE" +CONN_CLOSE_WAIT = "CLOSE_WAIT" +CONN_LAST_ACK = "LAST_ACK" +CONN_LISTEN = "LISTEN" +CONN_CLOSING = "CLOSING" +CONN_NONE = "NONE" + +# net_if_stats() +if enum is None: + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 +else: + class NicDuplex(enum.IntEnum): + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 + + globals().update(NicDuplex.__members__) + +# sensors_battery() +if enum is None: + POWER_TIME_UNKNOWN = -1 + POWER_TIME_UNLIMITED = -2 +else: + class BatteryTime(enum.IntEnum): + POWER_TIME_UNKNOWN = -1 + POWER_TIME_UNLIMITED = -2 + + globals().update(BatteryTime.__members__) + +# --- others + +ENCODING = sys.getfilesystemencoding() +if not PY3: + ENCODING_ERRS = "replace" +else: + try: + ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 + except AttributeError: + ENCODING_ERRS = "surrogateescape" if POSIX else "replace" + + +# =================================================================== +# --- namedtuples +# =================================================================== + +# --- for system functions + +# psutil.swap_memory() +sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', + 'sout']) +# psutil.disk_usage() +sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent']) +# psutil.disk_io_counters() +sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time']) +# psutil.disk_partitions() +sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts']) +# psutil.net_io_counters() +snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv', + 'packets_sent', 'packets_recv', + 'errin', 'errout', + 'dropin', 'dropout']) +# psutil.users() +suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid']) +# psutil.net_connections() +sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status', 'pid']) +# psutil.net_if_addrs() +snicaddr = namedtuple('snicaddr', + ['family', 'address', 'netmask', 'broadcast', 'ptp']) +# psutil.net_if_stats() +snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) +# psutil.cpu_stats() +scpustats = namedtuple( + 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) +# psutil.cpu_freq() +scpufreq = namedtuple('scpufreq', ['current', 'min', 'max']) +# psutil.sensors_temperatures() +shwtemp = namedtuple( + 'shwtemp', ['label', 'current', 'high', 'critical']) +# psutil.sensors_battery() +sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged']) +# psutil.sensors_fans() +sfan = namedtuple('sfan', ['label', 'current']) + +# --- for Process methods + +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', + ['user', 'system', 'children_user', 'children_system']) +# psutil.Process.open_files() +popenfile = namedtuple('popenfile', ['path', 'fd']) +# psutil.Process.threads() +pthread = namedtuple('pthread', ['id', 'user_time', 'system_time']) +# psutil.Process.uids() +puids = namedtuple('puids', ['real', 'effective', 'saved']) +# psutil.Process.gids() +pgids = namedtuple('pgids', ['real', 'effective', 'saved']) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes']) +# psutil.Process.ionice() +pionice = namedtuple('pionice', ['ioclass', 'value']) +# psutil.Process.ctx_switches() +pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary']) +# psutil.Process.connections() +pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status']) + +# psutil.connections() and psutil.Process.connections() +addr = namedtuple('addr', ['ip', 'port']) + + +# =================================================================== +# --- Process.connections() 'kind' parameter mapping +# =================================================================== + + +conn_tmap = { + "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]), + "tcp4": ([AF_INET], [SOCK_STREAM]), + "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]), + "udp4": ([AF_INET], [SOCK_DGRAM]), + "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), + "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]), + "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), +} + +if AF_INET6 is not None: + conn_tmap.update({ + "tcp6": ([AF_INET6], [SOCK_STREAM]), + "udp6": ([AF_INET6], [SOCK_DGRAM]), + }) + +if AF_UNIX is not None: + conn_tmap.update({ + "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + }) + + +# =================================================================== +# --- utils +# =================================================================== + + +def usage_percent(used, total, round_=None): + """Calculate percentage usage of 'used' against 'total'.""" + try: + ret = (float(used) / total) * 100 + except ZeroDivisionError: + return 0.0 + else: + if round_ is not None: + ret = round(ret, round_) + return ret + + +def memoize(fun): + """A simple memoize decorator for functions supporting (hashable) + positional arguments. + It also provides a cache_clear() function for clearing the cache: + + >>> @memoize + ... def foo() + ... return 1 + ... + >>> foo() + 1 + >>> foo.cache_clear() + >>> + """ + @functools.wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + return ret + + def cache_clear(): + """Clear cache.""" + cache.clear() + + cache = {} + wrapper.cache_clear = cache_clear + return wrapper + + +def memoize_when_activated(fun): + """A memoize decorator which is disabled by default. It can be + activated and deactivated on request. + For efficiency reasons it can be used only against class methods + accepting no arguments. + + >>> class Foo: + ... @memoize + ... def foo() + ... print(1) + ... + >>> f = Foo() + >>> # deactivated (default) + >>> foo() + 1 + >>> foo() + 1 + >>> + >>> # activated + >>> foo.cache_activate(self) + >>> foo() + 1 + >>> foo() + >>> foo() + >>> + """ + @functools.wraps(fun) + def wrapper(self): + try: + # case 1: we previously entered oneshot() ctx + ret = self._cache[fun] + except AttributeError: + # case 2: we never entered oneshot() ctx + return fun(self) + except KeyError: + # case 3: we entered oneshot() ctx but there's no cache + # for this entry yet + ret = self._cache[fun] = fun(self) + return ret + + def cache_activate(proc): + """Activate cache. Expects a Process instance. Cache will be + stored as a "_cache" instance attribute.""" + proc._cache = {} + + def cache_deactivate(proc): + """Deactivate and clear cache.""" + try: + del proc._cache + except AttributeError: + pass + + wrapper.cache_activate = cache_activate + wrapper.cache_deactivate = cache_deactivate + return wrapper + + +def isfile_strict(path): + """Same as os.path.isfile() but does not swallow EACCES / EPERM + exceptions, see: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html + """ + try: + st = os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return stat.S_ISREG(st.st_mode) + + +def path_exists_strict(path): + """Same as os.path.exists() but does not swallow EACCES / EPERM + exceptions, see: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html + """ + try: + os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return True + + +@memoize +def supports_ipv6(): + """Return True if IPv6 is supported on this platform.""" + if not socket.has_ipv6 or AF_INET6 is None: + return False + try: + sock = socket.socket(AF_INET6, socket.SOCK_STREAM) + with contextlib.closing(sock): + sock.bind(("::1", 0)) + return True + except socket.error: + return False + + +def parse_environ_block(data): + """Parse a C environ block of environment variables into a dictionary.""" + # The block is usually raw data from the target process. It might contain + # trailing garbage and lines that do not look like assignments. + ret = {} + pos = 0 + + # localize global variable to speed up access. + WINDOWS_ = WINDOWS + while True: + next_pos = data.find("\0", pos) + # nul byte at the beginning or double nul byte means finish + if next_pos <= pos: + break + # there might not be an equals sign + equal_pos = data.find("=", pos, next_pos) + if equal_pos > pos: + key = data[pos:equal_pos] + value = data[equal_pos + 1:next_pos] + # Windows expects environment variables to be uppercase only + if WINDOWS_: + key = key.upper() + ret[key] = value + pos = next_pos + 1 + + return ret + + +def sockfam_to_enum(num): + """Convert a numeric socket family value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + else: # pragma: no cover + try: + return socket.AddressFamily(num) + except ValueError: + return num + + +def socktype_to_enum(num): + """Convert a numeric socket type value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + else: # pragma: no cover + try: + return socket.SocketKind(num) + except ValueError: + return num + + +def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None): + """Convert a raw connection tuple to a proper ntuple.""" + if fam in (socket.AF_INET, AF_INET6): + if laddr: + laddr = addr(*laddr) + if raddr: + raddr = addr(*raddr) + if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6): + status = status_map.get(status, CONN_NONE) + else: + status = CONN_NONE # ignore whatever C returned to us + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if pid is None: + return pconn(fd, fam, type_, laddr, raddr, status) + else: + return sconn(fd, fam, type_, laddr, raddr, status, pid) + + +def deprecated_method(replacement): + """A decorator which can be used to mark a method as deprecated + 'replcement' is the method name which will be called instead. + """ + def outer(fun): + msg = "%s() is deprecated and will be removed; use %s() instead" % ( + fun.__name__, replacement) + if fun.__doc__ is None: + fun.__doc__ = msg + + @functools.wraps(fun) + def inner(self, *args, **kwargs): + warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + return getattr(self, replacement)(*args, **kwargs) + return inner + return outer + + +class _WrapNumbers: + """Watches numbers so that they don't overflow and wrap + (reset to zero). + """ + + def __init__(self): + self.lock = threading.Lock() + self.cache = {} + self.reminders = {} + self.reminder_keys = {} + + def _add_dict(self, input_dict, name): + assert name not in self.cache + assert name not in self.reminders + assert name not in self.reminder_keys + self.cache[name] = input_dict + self.reminders[name] = defaultdict(int) + self.reminder_keys[name] = defaultdict(set) + + def _remove_dead_reminders(self, input_dict, name): + """In case the number of keys changed between calls (e.g. a + disk disappears) this removes the entry from self.reminders. + """ + old_dict = self.cache[name] + gone_keys = set(old_dict.keys()) - set(input_dict.keys()) + for gone_key in gone_keys: + for remkey in self.reminder_keys[name][gone_key]: + del self.reminders[name][remkey] + del self.reminder_keys[name][gone_key] + + def run(self, input_dict, name): + """Cache dict and sum numbers which overflow and wrap. + Return an updated copy of `input_dict` + """ + if name not in self.cache: + # This was the first call. + self._add_dict(input_dict, name) + return input_dict + + self._remove_dead_reminders(input_dict, name) + + old_dict = self.cache[name] + new_dict = {} + for key in input_dict.keys(): + input_tuple = input_dict[key] + try: + old_tuple = old_dict[key] + except KeyError: + # The input dict has a new key (e.g. a new disk or NIC) + # which didn't exist in the previous call. + new_dict[key] = input_tuple + continue + + bits = [] + for i in range(len(input_tuple)): + input_value = input_tuple[i] + old_value = old_tuple[i] + remkey = (key, i) + if input_value < old_value: + # it wrapped! + self.reminders[name][remkey] += old_value + self.reminder_keys[name][key].add(remkey) + bits.append(input_value + self.reminders[name][remkey]) + + new_dict[key] = tuple(bits) + + self.cache[name] = input_dict + return new_dict + + def cache_clear(self, name=None): + """Clear the internal cache, optionally only for function 'name'.""" + with self.lock: + if name is None: + self.cache.clear() + self.reminders.clear() + self.reminder_keys.clear() + else: + self.cache.pop(name, None) + self.reminders.pop(name, None) + self.reminder_keys.pop(name, None) + + def cache_info(self): + """Return internal cache dicts as a tuple of 3 elements.""" + with self.lock: + return (self.cache, self.reminders, self.reminder_keys) + + +def wrap_numbers(input_dict, name): + """Given an `input_dict` and a function `name`, adjust the numbers + which "wrap" (restart from zero) across different calls by adding + "old value" to "new value" and return an updated dict. + """ + with _wn.lock: + return _wn.run(input_dict, name) + + +_wn = _WrapNumbers() +wrap_numbers.cache_clear = _wn.cache_clear +wrap_numbers.cache_info = _wn.cache_info + + +def open_binary(fname, **kwargs): + return open(fname, "rb", **kwargs) + + +def open_text(fname, **kwargs): + """On Python 3 opens a file in text mode by using fs encoding and + a proper en/decoding errors handler. + On Python 2 this is just an alias for open(name, 'rt'). + """ + if PY3: + # See: + # https://github.com/giampaolo/psutil/issues/675 + # https://github.com/giampaolo/psutil/pull/733 + kwargs.setdefault('encoding', ENCODING) + kwargs.setdefault('errors', ENCODING_ERRS) + return open(fname, "rt", **kwargs) + + +def bytes2human(n, format="%(value).1f%(symbol)s"): + """Used by various scripts. See: + http://goo.gl/zeJZl + + >>> bytes2human(10000) + '9.8K' + >>> bytes2human(100001221) + '95.4M' + """ + symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols[1:]): + prefix[s] = 1 << (i + 1) * 10 + for symbol in reversed(symbols[1:]): + if n >= prefix[symbol]: + value = float(n) / prefix[symbol] + return format % locals() + return format % dict(symbol=symbols[0], value=n) + + +def get_procfs_path(): + """Return updated psutil.PROCFS_PATH constant.""" + return sys.modules['ddtrace.vendor.psutil'].PROCFS_PATH + + +if PY3: + def decode(s): + return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) +else: + def decode(s): + return s diff --git a/ddtrace/vendor/psutil/_compat.py b/ddtrace/vendor/psutil/_compat.py new file mode 100644 index 00000000000..07ab909a84f --- /dev/null +++ b/ddtrace/vendor/psutil/_compat.py @@ -0,0 +1,332 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Module which provides compatibility with older Python versions.""" + +import collections +import errno +import functools +import os +import sys + +__all__ = ["PY3", "long", "xrange", "unicode", "basestring", "u", "b", + "lru_cache", "which", "get_terminal_size", + "FileNotFoundError", "PermissionError", "ProcessLookupError", + "InterruptedError", "ChildProcessError", "FileExistsError"] + +PY3 = sys.version_info[0] == 3 + +if PY3: + long = int + xrange = range + unicode = str + basestring = str + + def u(s): + return s + + def b(s): + return s.encode("latin-1") +else: + long = long + xrange = xrange + unicode = unicode + basestring = basestring + + def u(s): + return unicode(s, "unicode_escape") + + def b(s): + return s + + +# --- exceptions + + +if PY3: + FileNotFoundError = FileNotFoundError # NOQA + PermissionError = PermissionError # NOQA + ProcessLookupError = ProcessLookupError # NOQA + InterruptedError = InterruptedError # NOQA + ChildProcessError = ChildProcessError # NOQA + FileExistsError = FileExistsError # NOQA +else: + # https://github.com/PythonCharmers/python-future/blob/exceptions/ + # src/future/types/exceptions/pep3151.py + + def instance_checking_exception(base_exception=Exception): + def wrapped(instance_checker): + class TemporaryClass(base_exception): + + def __init__(self, *args, **kwargs): + if len(args) == 1 and isinstance(args[0], TemporaryClass): + unwrap_me = args[0] + for attr in dir(unwrap_me): + if not attr.startswith('__'): + setattr(self, attr, getattr(unwrap_me, attr)) + else: + super(TemporaryClass, self).__init__(*args, **kwargs) + + class __metaclass__(type): + def __instancecheck__(cls, inst): + return instance_checker(inst) + + def __subclasscheck__(cls, classinfo): + value = sys.exc_info()[1] + return isinstance(value, cls) + + TemporaryClass.__name__ = instance_checker.__name__ + TemporaryClass.__doc__ = instance_checker.__doc__ + return TemporaryClass + + return wrapped + + @instance_checking_exception(EnvironmentError) + def FileNotFoundError(inst): + return getattr(inst, 'errno', object()) == errno.ENOENT + + @instance_checking_exception(EnvironmentError) + def ProcessLookupError(inst): + return getattr(inst, 'errno', object()) == errno.ESRCH + + @instance_checking_exception(EnvironmentError) + def PermissionError(inst): + return getattr(inst, 'errno', object()) in ( + errno.EACCES, errno.EPERM) + + @instance_checking_exception(EnvironmentError) + def InterruptedError(inst): + return getattr(inst, 'errno', object()) == errno.EINTR + + @instance_checking_exception(EnvironmentError) + def ChildProcessError(inst): + return getattr(inst, 'errno', object()) == errno.ECHILD + + @instance_checking_exception(EnvironmentError) + def FileExistsError(inst): + return getattr(inst, 'errno', object()) == errno.EEXIST + + +# --- stdlib additions + + +# py 3.2 functools.lru_cache +# Taken from: http://code.activestate.com/recipes/578078 +# Credit: Raymond Hettinger +try: + from functools import lru_cache +except ImportError: + try: + from threading import RLock + except ImportError: + from dummy_threading import RLock + + _CacheInfo = collections.namedtuple( + "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + + class _HashedSeq(list): + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + def __hash__(self): + return self.hashvalue + + def _make_key(args, kwds, typed, + kwd_mark=(object(), ), + fasttypes=set((int, str, frozenset, type(None))), + sorted=sorted, tuple=tuple, type=type, len=len): + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + + def lru_cache(maxsize=100, typed=False): + """Least-recently-used cache decorator, see: + http://docs.python.org/3/library/functools.html#functools.lru_cache + """ + def decorating_function(user_function): + cache = dict() + stats = [0, 0] + HITS, MISSES = 0, 1 + make_key = _make_key + cache_get = cache.get + _len = len + lock = RLock() + root = [] + root[:] = [root, root, None, None] + nonlocal_root = [root] + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 + if maxsize == 0: + def wrapper(*args, **kwds): + result = user_function(*args, **kwds) + stats[MISSES] += 1 + return result + elif maxsize is None: + def wrapper(*args, **kwds): + key = make_key(args, kwds, typed) + result = cache_get(key, root) + if result is not root: + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + stats[MISSES] += 1 + return result + else: + def wrapper(*args, **kwds): + if kwds or typed: + key = make_key(args, kwds, typed) + else: + key = args + lock.acquire() + try: + link = cache_get(key) + if link is not None: + root, = nonlocal_root + link_prev, link_next, key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + stats[HITS] += 1 + return result + finally: + lock.release() + result = user_function(*args, **kwds) + lock.acquire() + try: + root, = nonlocal_root + if key in cache: + pass + elif _len(cache) >= maxsize: + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + root = nonlocal_root[0] = oldroot[NEXT] + oldkey = root[KEY] + root[KEY] = root[RESULT] = None + del cache[oldkey] + cache[key] = oldroot + else: + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + stats[MISSES] += 1 + finally: + lock.release() + return result + + def cache_info(): + """Report cache statistics""" + lock.acquire() + try: + return _CacheInfo(stats[HITS], stats[MISSES], maxsize, + len(cache)) + finally: + lock.release() + + def cache_clear(): + """Clear the cache and cache statistics""" + lock.acquire() + try: + cache.clear() + root = nonlocal_root[0] + root[:] = [root, root, None, None] + stats[:] = [0, 0] + finally: + lock.release() + + wrapper.__wrapped__ = user_function + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return functools.update_wrapper(wrapper, user_function) + + return decorating_function + + +# python 3.3 +try: + from shutil import which +except ImportError: + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + """ + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) and + not os.path.isdir(fn)) + + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + if os.curdir not in path: + path.insert(0, os.curdir) + + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if normdir not in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +# python 3.3 +try: + from shutil import get_terminal_size +except ImportError: + def get_terminal_size(fallback=(80, 24)): + try: + import fcntl + import termios + import struct + except ImportError: + return fallback + else: + try: + # This should work on Linux. + res = struct.unpack( + 'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234')) + return (res[1], res[0]) + except Exception: + return fallback diff --git a/ddtrace/vendor/psutil/_psaix.py b/ddtrace/vendor/psutil/_psaix.py new file mode 100644 index 00000000000..79e3be15a19 --- /dev/null +++ b/ddtrace/vendor/psutil/_psaix.py @@ -0,0 +1,554 @@ +# Copyright (c) 2009, Giampaolo Rodola' +# Copyright (c) 2017, Arnon Yaari +# All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""AIX platform implementation.""" + +import functools +import glob +import os +import re +import subprocess +import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_aix as cext +from . import _psutil_posix as cext_posix +from ._common import conn_to_ntuple +from ._common import get_procfs_path +from ._common import memoize_when_activated +from ._common import NIC_DUPLEX_FULL +from ._common import NIC_DUPLEX_HALF +from ._common import NIC_DUPLEX_UNKNOWN +from ._common import usage_percent +from ._compat import FileNotFoundError +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import PY3 + + +__extra__all__ = ["PROCFS_PATH"] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +HAS_THREADS = hasattr(cext, "proc_threads") +HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters") +HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters") + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SACTIVE: _common.STATUS_RUNNING, + cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this? + cext.SSTOP: _common.STATUS_STOPPED, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +proc_info_map = dict( + ppid=0, + rss=1, + vms=2, + create_time=3, + nice=4, + num_threads=5, + status=6, + ttynr=7) + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +# psutil.Process.memory_full_info() +pfullmem = pmem +# psutil.Process.cpu_times() +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +# psutil.virtual_memory() +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + total, avail, free, pinned, inuse = cext.virtual_mem() + percent = usage_percent((total - avail), total, round_=1) + return svmem(total, avail, percent, inuse, free) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, free, sin, sout = cext.swap_mem() + used = total - free + percent = usage_percent(used, total, round_=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system-wide CPU times as a named tuple""" + ret = cext.per_cpu_times() + return scputimes(*[sum(x) for x in zip(*ret)]) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + cmd = "lsdev -Cc processor" + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode != 0: + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + processors = stdout.strip().splitlines() + return len(processors) or None + + +def cpu_stats(): + """Return various CPU stats as a named tuple.""" + ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats() + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls) + + +# ===================================================================== +# --- disks +# ===================================================================== + + +disk_io_counters = cext.disk_io_counters +disk_usage = _psposix.disk_usage + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_if_addrs = cext_posix.net_if_addrs + +if HAS_NET_IO_COUNTERS: + net_io_counters = cext.net_io_counters + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + """ + cmap = _common.conn_tmap + if kind not in cmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid) + ret = [] + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + nt = conn_to_ntuple(fd, fam, type_, laddr, raddr, status, + TCP_STATUSES, pid=pid if _pid == -1 else None) + ret.append(nt) + return ret + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {"Full": NIC_DUPLEX_FULL, + "Half": NIC_DUPLEX_HALF} + names = set([x[0] for x in net_if_addrs()]) + ret = {} + for name in names: + isup, mtu = cext.net_if_stats(name) + + # try to get speed and duplex + # TODO: rewrite this in C (entstat forks, so use truss -f to follow. + # looks like it is using an undocumented ioctl?) + duplex = "" + speed = 0 + p = subprocess.Popen(["/usr/bin/entstat", "-d", name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode == 0: + re_result = re.search( + r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout) + if re_result is not None: + speed = int(re_result.group(1)) + duplex = re_result.group(2) + + duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process, pid = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp, pid) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- processes +# ===================================================================== + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo")) + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except (FileNotFoundError, ProcessLookupError): + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + except PermissionError: + raise AccessDenied(self.pid, self._name) + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + self._procfs_path = get_procfs_path() + + def oneshot_enter(self): + self._proc_basic_info.cache_activate(self) + self._proc_cred.cache_activate(self) + + def oneshot_exit(self): + self._proc_basic_info.cache_deactivate(self) + self._proc_cred.cache_deactivate(self) + + @wrap_exceptions + @memoize_when_activated + def _proc_basic_info(self): + return cext.proc_basic_info(self.pid, self._procfs_path) + + @wrap_exceptions + @memoize_when_activated + def _proc_cred(self): + return cext.proc_cred(self.pid, self._procfs_path) + + @wrap_exceptions + def name(self): + if self.pid == 0: + return "swapper" + # note: max 16 characters + return cext.proc_name(self.pid, self._procfs_path).rstrip("\x00") + + @wrap_exceptions + def exe(self): + # there is no way to get executable path in AIX other than to guess, + # and guessing is more complex than what's in the wrapping class + cmdline = self.cmdline() + if not cmdline: + return '' + exe = cmdline[0] + if os.path.sep in exe: + # relative or absolute path + if not os.path.isabs(exe): + # if cwd has changed, we're out of luck - this may be wrong! + exe = os.path.abspath(os.path.join(self.cwd(), exe)) + if (os.path.isabs(exe) and + os.path.isfile(exe) and + os.access(exe, os.X_OK)): + return exe + # not found, move to search in PATH using basename only + exe = os.path.basename(exe) + # search for exe name PATH + for path in os.environ["PATH"].split(":"): + possible_exe = os.path.abspath(os.path.join(path, exe)) + if (os.path.isfile(possible_exe) and + os.access(possible_exe, os.X_OK)): + return possible_exe + return '' + + @wrap_exceptions + def cmdline(self): + return cext.proc_args(self.pid) + + @wrap_exceptions + def environ(self): + return cext.proc_environ(self.pid) + + @wrap_exceptions + def create_time(self): + return self._proc_basic_info()[proc_info_map['create_time']] + + @wrap_exceptions + def num_threads(self): + return self._proc_basic_info()[proc_info_map['num_threads']] + + if HAS_THREADS: + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + # The underlying C implementation retrieves all OS threads + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not retlist: + # will raise NSP if process is gone + os.stat('%s/%s' % (self._procfs_path, self.pid)) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + # will raise NSP if process is gone + os.stat('%s/%s' % (self._procfs_path, self.pid)) + return ret + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + self._ppid = self._proc_basic_info()[proc_info_map['ppid']] + return self._ppid + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = self._proc_cred() + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = self._proc_cred() + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + cpu_times = cext.proc_cpu_times(self.pid, self._procfs_path) + return _common.pcputimes(*cpu_times) + + @wrap_exceptions + def terminal(self): + ttydev = self._proc_basic_info()[proc_info_map['ttynr']] + # convert from 64-bit dev_t to 32-bit dev_t and then map the device + ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)) + # try to match rdev of /dev/pts/* files ttydev + for dev in glob.glob("/dev/**/*"): + if os.stat(dev).st_rdev == ttydev: + return dev + return None + + @wrap_exceptions + def cwd(self): + procfs_path = self._procfs_path + try: + result = os.readlink("%s/%s/cwd" % (procfs_path, self.pid)) + return result.rstrip('/') + except FileNotFoundError: + os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD + return None + + @wrap_exceptions + def memory_info(self): + ret = self._proc_basic_info() + rss = ret[proc_info_map['rss']] * 1024 + vms = ret[proc_info_map['vms']] * 1024 + return pmem(rss, vms) + + memory_full_info = memory_info + + @wrap_exceptions + def status(self): + code = self._proc_basic_info()[proc_info_map['status']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + def open_files(self): + # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then + # find matching name of the inode) + p = subprocess.Popen(["/usr/bin/procfiles", "-n", str(self.pid)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if "no such process" in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + procfiles = re.findall(r"(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout) + retlist = [] + for fd, path in procfiles: + path = path.strip() + if path.startswith("//"): + path = path[1:] + if path.lower() == "cannot be retrieved": + continue + retlist.append(_common.popenfile(path, int(fd))) + return retlist + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: # no /proc/0/fd + return 0 + return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw( + *cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + if HAS_PROC_IO_COUNTERS: + @wrap_exceptions + def io_counters(self): + try: + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + except OSError: + # if process is terminated, proc_io_counters returns OSError + # instead of NSP + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + raise + return _common.pio(rc, wc, rb, wb) diff --git a/ddtrace/vendor/psutil/_psbsd.py b/ddtrace/vendor/psutil/_psbsd.py new file mode 100644 index 00000000000..2f41dc0be9f --- /dev/null +++ b/ddtrace/vendor/psutil/_psbsd.py @@ -0,0 +1,905 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""FreeBSD, OpenBSD and NetBSD platforms implementation.""" + +import contextlib +import errno +import functools +import os +import xml.etree.ElementTree as ET +from collections import namedtuple +from collections import defaultdict + +from . import _common +from . import _psposix +from . import _psutil_bsd as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap +from ._common import conn_to_ntuple +from ._common import FREEBSD +from ._common import memoize +from ._common import memoize_when_activated +from ._common import NETBSD +from ._common import OPENBSD +from ._common import usage_percent +from ._compat import FileNotFoundError +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import which + + +__extra__all__ = [] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +if FREEBSD: + PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SWAIT: _common.STATUS_WAITING, + cext.SLOCK: _common.STATUS_LOCKED, + } +elif OPENBSD or NETBSD: + PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + # According to /usr/include/sys/proc.h SZOMB is unused. + # test_zombie_process() shows that SDEAD is the right + # equivalent. Also it appears there's no equivalent of + # psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE. + # cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SDEAD: _common.STATUS_ZOMBIE, + cext.SZOMB: _common.STATUS_ZOMBIE, + # From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt + # OpenBSD has SRUN and SONPROC: SRUN indicates that a process + # is runnable but *not* yet running, i.e. is on a run queue. + # SONPROC indicates that the process is actually executing on + # a CPU, i.e. it is no longer on a run queue. + # As such we'll map SRUN to STATUS_WAKING and SONPROC to + # STATUS_RUNNING + cext.SRUN: _common.STATUS_WAKING, + cext.SONPROC: _common.STATUS_RUNNING, + } +elif NETBSD: + PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SACTIVE: _common.STATUS_RUNNING, + cext.SDYING: _common.STATUS_ZOMBIE, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SDEAD: _common.STATUS_DEAD, + cext.SSUSPENDED: _common.STATUS_SUSPENDED, # unique to NetBSD + } + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +if NETBSD: + PAGESIZE = os.sysconf("SC_PAGESIZE") +else: + PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +HAS_PER_CPU_TIMES = hasattr(cext, "per_cpu_times") +HAS_PROC_NUM_THREADS = hasattr(cext, "proc_num_threads") +HAS_PROC_OPEN_FILES = hasattr(cext, 'proc_open_files') +HAS_PROC_NUM_FDS = hasattr(cext, 'proc_num_fds') + +kinfo_proc_map = dict( + ppid=0, + status=1, + real_uid=2, + effective_uid=3, + saved_uid=4, + real_gid=5, + effective_gid=6, + saved_gid=7, + ttynr=8, + create_time=9, + ctx_switches_vol=10, + ctx_switches_unvol=11, + read_io_count=12, + write_io_count=13, + user_time=14, + sys_time=15, + ch_user_time=16, + ch_sys_time=17, + rss=18, + vms=19, + memtext=20, + memdata=21, + memstack=22, + cpunum=23, + name=24, +) + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.virtual_memory() +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached', 'shared', 'wired']) +# psutil.cpu_times() +scputimes = namedtuple( + 'scputimes', ['user', 'nice', 'system', 'idle', 'irq']) +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack']) +# psutil.Process.memory_full_info() +pfullmem = pmem +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', + ['user', 'system', 'children_user', 'children_system']) +# psutil.Process.memory_maps(grouped=True) +pmmap_grouped = namedtuple( + 'pmmap_grouped', 'path rss, private, ref_count, shadow_count') +# psutil.Process.memory_maps(grouped=False) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count') +# psutil.disk_io_counters() +if FREEBSD: + sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time', + 'busy_time']) +else: + sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes']) + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + total, free, active, inactive, wired, cached, buffers, shared = mem + if NETBSD: + # On NetBSD buffers and shared mem is determined via /proc. + # The C ext set them to 0. + with open('/proc/meminfo', 'rb') as f: + for line in f: + if line.startswith(b'Buffers:'): + buffers = int(line.split()[1]) * 1024 + elif line.startswith(b'MemShared:'): + shared = int(line.split()[1]) * 1024 + avail = inactive + cached + free + used = active + wired + cached + percent = usage_percent((total - avail), total, round_=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached, shared, wired) + + +def swap_memory(): + """System swap memory as (total, used, free, sin, sout) namedtuple.""" + total, used, free, sin, sout = cext.swap_mem() + percent = usage_percent(used, total, round_=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system per-CPU times as a namedtuple""" + user, nice, system, idle, irq = cext.cpu_times() + return scputimes(user, nice, system, idle, irq) + + +if HAS_PER_CPU_TIMES: + def per_cpu_times(): + """Return system CPU times as a namedtuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle, irq = cpu_t + item = scputimes(user, nice, system, idle, irq) + ret.append(item) + return ret +else: + # XXX + # Ok, this is very dirty. + # On FreeBSD < 8 we cannot gather per-cpu information, see: + # https://github.com/giampaolo/psutil/issues/226 + # If num cpus > 1, on first call we return single cpu times to avoid a + # crash at psutil import time. + # Next calls will fail with NotImplementedError + def per_cpu_times(): + """Return system CPU times as a namedtuple""" + if cpu_count_logical() == 1: + return [cpu_times()] + if per_cpu_times.__called__: + raise NotImplementedError("supported only starting from FreeBSD 8") + per_cpu_times.__called__ = True + return [cpu_times()] + + per_cpu_times.__called__ = False + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +if OPENBSD or NETBSD: + def cpu_count_physical(): + # OpenBSD and NetBSD do not implement this. + return 1 if cpu_count_logical() == 1 else None +else: + def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + # From the C module we'll get an XML string similar to this: + # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html + # We may get None in case "sysctl kern.sched.topology_spec" + # is not supported on this BSD version, in which case we'll mimic + # os.cpu_count() and return None. + ret = None + s = cext.cpu_count_phys() + if s is not None: + # get rid of padding chars appended at the end of the string + index = s.rfind("") + if index != -1: + s = s[:index + 9] + root = ET.fromstring(s) + try: + ret = len(root.findall('group/children/group/cpu')) or None + finally: + # needed otherwise it will memleak + root.clear() + if not ret: + # If logical CPUs are 1 it's obvious we'll have only 1 + # physical CPU. + if cpu_count_logical() == 1: + return 1 + return ret + + +def cpu_stats(): + """Return various CPU stats as a named tuple.""" + if FREEBSD: + # Note: the C ext is returning some metrics we are not exposing: + # traps. + ctxsw, intrs, soft_intrs, syscalls, traps = cext.cpu_stats() + elif NETBSD: + # XXX + # Note about intrs: the C extension returns 0. intrs + # can be determined via /proc/stat; it has the same value as + # soft_intrs thought so the kernel is faking it (?). + # + # Note about syscalls: the C extension always sets it to 0 (?). + # + # Note: the C ext is returning some metrics we are not exposing: + # traps, faults and forks. + ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \ + cext.cpu_stats() + with open('/proc/stat', 'rb') as f: + for line in f: + if line.startswith(b'intr'): + intrs = int(line.split()[1]) + elif OPENBSD: + # Note: the C ext is returning some metrics we are not exposing: + # traps, faults and forks. + ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \ + cext.cpu_stats() + return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls) + + +# ===================================================================== +# --- disks +# ===================================================================== + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples. + 'all' argument is ignored, see: + https://github.com/giampaolo/psutil/issues/906 + """ + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +disk_usage = _psposix.disk_usage +disk_io_counters = cext.disk_io_counters + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_io_counters = cext.net_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + try: + mtu = cext_posix.net_if_mtu(name) + isup = cext_posix.net_if_flags(name) + duplex, speed = cext_posix.net_if_duplex_speed(name) + except OSError as err: + # https://github.com/giampaolo/psutil/issues/1279 + if err.errno != errno.ENODEV: + raise + else: + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def net_connections(kind): + """System-wide network connections.""" + if OPENBSD: + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except (NoSuchProcess, ZombieProcess): + continue + else: + for conn in cons: + conn = list(conn) + conn.append(pid) + ret.append(_common.sconn(*conn)) + return ret + + if kind not in _common.conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + ret = set() + if NETBSD: + rawlist = cext.net_connections(-1) + else: + rawlist = cext.net_connections() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + # TODO: apply filter at C level + if fam in families and type in types: + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, + TCP_STATUSES, pid) + ret.add(nt) + return list(ret) + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +if FREEBSD: + + def sensors_battery(): + """Return battery info.""" + try: + percent, minsleft, power_plugged = cext.sensors_battery() + except NotImplementedError: + # See: https://github.com/giampaolo/psutil/issues/1074 + return None + power_plugged = power_plugged == 1 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + elif minsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = minsleft * 60 + return _common.sbattery(percent, secsleft, power_plugged) + + def sensors_temperatures(): + "Return CPU cores temperatures if available, else an empty dict." + ret = defaultdict(list) + num_cpus = cpu_count_logical() + for cpu in range(num_cpus): + try: + current, high = cext.sensors_cpu_temperature(cpu) + if high <= 0: + high = None + name = "Core %s" % cpu + ret["coretemp"].append( + _common.shwtemp(name, current, high, high)) + except NotImplementedError: + pass + + return ret + + def cpu_freq(): + """Return frequency metrics for CPUs. As of Dec 2018 only + CPU 0 appears to be supported by FreeBSD and all other cores + match the frequency of CPU 0. + """ + ret = [] + num_cpus = cpu_count_logical() + for cpu in range(num_cpus): + try: + current, available_freq = cext.cpu_frequency(cpu) + except NotImplementedError: + continue + if available_freq: + try: + min_freq = int(available_freq.split(" ")[-1].split("/")[0]) + except(IndexError, ValueError): + min_freq = None + try: + max_freq = int(available_freq.split(" ")[0].split("/")[0]) + except(IndexError, ValueError): + max_freq = None + ret.append(_common.scpufreq(current, min_freq, max_freq)) + return ret + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, pid = item + if pid == -1: + assert OPENBSD + pid = None + if tty == '~': + continue # reboot or shutdown + nt = _common.suser(user, tty or None, hostname, tstamp, pid) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- processes +# ===================================================================== + + +@memoize +def _pid_0_exists(): + try: + Process(0).name() + except NoSuchProcess: + return False + except AccessDenied: + return True + else: + return True + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + ret = cext.pids() + if OPENBSD and (0 not in ret) and _pid_0_exists(): + # On OpenBSD the kernel does not return PID 0 (neither does + # ps) but it's actually querable (Process(0) will succeed). + ret.insert(0, 0) + return ret + + +if OPENBSD or NETBSD: + def pid_exists(pid): + """Return True if pid exists.""" + exists = _psposix.pid_exists(pid) + if not exists: + # We do this because _psposix.pid_exists() lies in case of + # zombie processes. + return pid in pids() + else: + return True +else: + pid_exists = _psposix.pid_exists + + +def is_zombie(pid): + try: + st = cext.proc_oneshot_info(pid)[kinfo_proc_map['status']] + return st == cext.SZOMB + except Exception: + return False + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except ProcessLookupError: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + except PermissionError: + raise AccessDenied(self.pid, self._name) + except OSError: + if self.pid == 0: + if 0 in pids(): + raise AccessDenied(self.pid, self._name) + else: + raise + raise + return wrapper + + +@contextlib.contextmanager +def wrap_exceptions_procfs(inst): + """Same as above, for routines relying on reading /proc fs.""" + try: + yield + except (ProcessLookupError, FileNotFoundError): + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if not pid_exists(inst.pid): + raise NoSuchProcess(inst.pid, inst._name) + else: + raise ZombieProcess(inst.pid, inst._name, inst._ppid) + except PermissionError: + raise AccessDenied(inst.pid, inst._name) + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + def _assert_alive(self): + """Raise NSP if the process disappeared on us.""" + # For those C function who do not raise NSP, possibly returning + # incorrect or incomplete result. + cext.proc_name(self.pid) + + @wrap_exceptions + @memoize_when_activated + def oneshot(self): + """Retrieves multiple process info in one shot as a raw tuple.""" + ret = cext.proc_oneshot_info(self.pid) + assert len(ret) == len(kinfo_proc_map) + return ret + + def oneshot_enter(self): + self.oneshot.cache_activate(self) + + def oneshot_exit(self): + self.oneshot.cache_deactivate(self) + + @wrap_exceptions + def name(self): + name = self.oneshot()[kinfo_proc_map['name']] + return name if name is not None else cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + if FREEBSD: + return cext.proc_exe(self.pid) + elif NETBSD: + if self.pid == 0: + # /proc/0 dir exists but /proc/0/exe doesn't + return "" + with wrap_exceptions_procfs(self): + return os.readlink("/proc/%s/exe" % self.pid) + else: + # OpenBSD: exe cannot be determined; references: + # https://chromium.googlesource.com/chromium/src/base/+/ + # master/base_paths_posix.cc + # We try our best guess by using which against the first + # cmdline arg (may return None). + cmdline = self.cmdline() + if cmdline: + return which(cmdline[0]) + else: + return "" + + @wrap_exceptions + def cmdline(self): + if OPENBSD and self.pid == 0: + return [] # ...else it crashes + elif NETBSD: + # XXX - most of the times the underlying sysctl() call on Net + # and Open BSD returns a truncated string. + # Also /proc/pid/cmdline behaves the same so it looks + # like this is a kernel bug. + try: + return cext.proc_cmdline(self.pid) + except OSError as err: + if err.errno == errno.EINVAL: + if is_zombie(self.pid): + raise ZombieProcess(self.pid, self._name, self._ppid) + elif not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name, self._ppid) + else: + # XXX: this happens with unicode tests. It means the C + # routine is unable to decode invalid unicode chars. + return [] + else: + raise + else: + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def terminal(self): + tty_nr = self.oneshot()[kinfo_proc_map['ttynr']] + tmap = _psposix.get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def ppid(self): + self._ppid = self.oneshot()[kinfo_proc_map['ppid']] + return self._ppid + + @wrap_exceptions + def uids(self): + rawtuple = self.oneshot() + return _common.puids( + rawtuple[kinfo_proc_map['real_uid']], + rawtuple[kinfo_proc_map['effective_uid']], + rawtuple[kinfo_proc_map['saved_uid']]) + + @wrap_exceptions + def gids(self): + rawtuple = self.oneshot() + return _common.pgids( + rawtuple[kinfo_proc_map['real_gid']], + rawtuple[kinfo_proc_map['effective_gid']], + rawtuple[kinfo_proc_map['saved_gid']]) + + @wrap_exceptions + def cpu_times(self): + rawtuple = self.oneshot() + return _common.pcputimes( + rawtuple[kinfo_proc_map['user_time']], + rawtuple[kinfo_proc_map['sys_time']], + rawtuple[kinfo_proc_map['ch_user_time']], + rawtuple[kinfo_proc_map['ch_sys_time']]) + + if FREEBSD: + @wrap_exceptions + def cpu_num(self): + return self.oneshot()[kinfo_proc_map['cpunum']] + + @wrap_exceptions + def memory_info(self): + rawtuple = self.oneshot() + return pmem( + rawtuple[kinfo_proc_map['rss']], + rawtuple[kinfo_proc_map['vms']], + rawtuple[kinfo_proc_map['memtext']], + rawtuple[kinfo_proc_map['memdata']], + rawtuple[kinfo_proc_map['memstack']]) + + memory_full_info = memory_info + + @wrap_exceptions + def create_time(self): + return self.oneshot()[kinfo_proc_map['create_time']] + + @wrap_exceptions + def num_threads(self): + if HAS_PROC_NUM_THREADS: + # FreeBSD + return cext.proc_num_threads(self.pid) + else: + return len(self.threads()) + + @wrap_exceptions + def num_ctx_switches(self): + rawtuple = self.oneshot() + return _common.pctxsw( + rawtuple[kinfo_proc_map['ctx_switches_vol']], + rawtuple[kinfo_proc_map['ctx_switches_unvol']]) + + @wrap_exceptions + def threads(self): + # Note: on OpenSBD this (/dev/mem) requires root access. + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + if OPENBSD: + self._assert_alive() + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + + if NETBSD: + families, types = conn_tmap[kind] + ret = [] + rawlist = cext.net_connections(self.pid) + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + assert pid == self.pid + if fam in families and type in types: + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, + TCP_STATUSES) + ret.append(nt) + self._assert_alive() + return list(ret) + + families, types = conn_tmap[kind] + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, + TCP_STATUSES) + ret.append(nt) + + if OPENBSD: + self._assert_alive() + + return ret + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = self.oneshot()[kinfo_proc_map['status']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def io_counters(self): + rawtuple = self.oneshot() + return _common.pio( + rawtuple[kinfo_proc_map['read_io_count']], + rawtuple[kinfo_proc_map['write_io_count']], + -1, + -1) + + @wrap_exceptions + def cwd(self): + """Return process current working directory.""" + # sometimes we get an empty string, in which case we turn + # it into None + if OPENBSD and self.pid == 0: + return None # ...else it would raise EINVAL + elif NETBSD or HAS_PROC_OPEN_FILES: + # FreeBSD < 8 does not support functions based on + # kinfo_getfile() and kinfo_getvmmap() + return cext.proc_cwd(self.pid) or None + else: + raise NotImplementedError( + "supported only starting from FreeBSD 8" if + FREEBSD else "") + + nt_mmap_grouped = namedtuple( + 'mmap', 'path rss, private, ref_count, shadow_count') + nt_mmap_ext = namedtuple( + 'mmap', 'addr, perms path rss, private, ref_count, shadow_count') + + def _not_implemented(self): + raise NotImplementedError + + # FreeBSD < 8 does not support functions based on kinfo_getfile() + # and kinfo_getvmmap() + if HAS_PROC_OPEN_FILES: + @wrap_exceptions + def open_files(self): + """Return files opened by process as a list of namedtuples.""" + rawlist = cext.proc_open_files(self.pid) + return [_common.popenfile(path, fd) for path, fd in rawlist] + else: + open_files = _not_implemented + + # FreeBSD < 8 does not support functions based on kinfo_getfile() + # and kinfo_getvmmap() + if HAS_PROC_NUM_FDS: + @wrap_exceptions + def num_fds(self): + """Return the number of file descriptors opened by this process.""" + ret = cext.proc_num_fds(self.pid) + if NETBSD: + self._assert_alive() + return ret + else: + num_fds = _not_implemented + + # --- FreeBSD only APIs + + if FREEBSD: + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + # Pre-emptively check if CPUs are valid because the C + # function has a weird behavior in case of invalid CPUs, + # see: https://github.com/giampaolo/psutil/issues/586 + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + # 'man cpuset_setaffinity' about EDEADLK: + # <> + if err.errno in (errno.EINVAL, errno.EDEADLK): + for cpu in cpus: + if cpu not in allcpus: + raise ValueError( + "invalid CPU #%i (choose between %s)" % ( + cpu, allcpus)) + raise + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) diff --git a/ddtrace/vendor/psutil/_pslinux.py b/ddtrace/vendor/psutil/_pslinux.py new file mode 100644 index 00000000000..a56ead36985 --- /dev/null +++ b/ddtrace/vendor/psutil/_pslinux.py @@ -0,0 +1,2096 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Linux platform implementation.""" + +from __future__ import division + +import base64 +import collections +import errno +import functools +import glob +import os +import re +import socket +import struct +import sys +import traceback +import warnings +from collections import defaultdict +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_linux as cext +from . import _psutil_posix as cext_posix +from ._common import decode +from ._common import get_procfs_path +from ._common import isfile_strict +from ._common import memoize +from ._common import memoize_when_activated +from ._common import NIC_DUPLEX_FULL +from ._common import NIC_DUPLEX_HALF +from ._common import NIC_DUPLEX_UNKNOWN +from ._common import open_binary +from ._common import open_text +from ._common import parse_environ_block +from ._common import path_exists_strict +from ._common import supports_ipv6 +from ._common import usage_percent +from ._compat import b +from ._compat import basestring +from ._compat import FileNotFoundError +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import PY3 + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +__extra__all__ = [ + # + 'PROCFS_PATH', + # io prio constants + "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", + "IOPRIO_CLASS_IDLE", + # connection status constants + "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", + "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +POWER_SUPPLY_PATH = "/sys/class/power_supply" +HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) +HAS_PRLIMIT = hasattr(cext, "linux_prlimit") +HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") +HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") +_DEFAULT = object() + +# RLIMIT_* constants, not guaranteed to be present on all kernels +if HAS_PRLIMIT: + for name in dir(cext): + if name.startswith('RLIM'): + __extra__all__.append(name) + +# Number of clock ticks per second +CLOCK_TICKS = os.sysconf("SC_CLK_TCK") +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +BOOT_TIME = None # set later +# Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*. +# On Python 2, using a buffer with open() for such files may result in a +# speedup, see: https://github.com/giampaolo/psutil/issues/708 +BIGFILE_BUFFERING = -1 if PY3 else 8192 +LITTLE_ENDIAN = sys.byteorder == 'little' + +# "man iostat" states that sectors are equivalent with blocks and have +# a size of 512 bytes. Despite this value can be queried at runtime +# via /sys/block/{DISK}/queue/hw_sector_size and results may vary +# between 1k, 2k, or 4k... 512 appears to be a magic constant used +# throughout Linux source code: +# * https://stackoverflow.com/a/38136179/376587 +# * https://lists.gt.net/linux/kernel/2241060 +# * https://github.com/giampaolo/psutil/issues/1305 +# * https://github.com/torvalds/linux/blob/ +# 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99 +# * https://lkml.org/lkml/2015/8/17/234 +DISK_SECTOR_SIZE = 512 + +if enum is None: + AF_LINK = socket.AF_PACKET +else: + AddressFamily = enum.IntEnum('AddressFamily', + {'AF_LINK': int(socket.AF_PACKET)}) + AF_LINK = AddressFamily.AF_LINK + +# ioprio_* constants http://linux.die.net/man/2/ioprio_get +if enum is None: + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 +else: + class IOPriority(enum.IntEnum): + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 + + globals().update(IOPriority.__members__) + +# See: +# https://github.com/torvalds/linux/blame/master/fs/proc/array.c +# ...and (TASK_* constants): +# https://github.com/torvalds/linux/blob/master/include/linux/sched.h +PROC_STATUSES = { + "R": _common.STATUS_RUNNING, + "S": _common.STATUS_SLEEPING, + "D": _common.STATUS_DISK_SLEEP, + "T": _common.STATUS_STOPPED, + "t": _common.STATUS_TRACING_STOP, + "Z": _common.STATUS_ZOMBIE, + "X": _common.STATUS_DEAD, + "x": _common.STATUS_DEAD, + "K": _common.STATUS_WAKE_KILL, + "W": _common.STATUS_WAKING, + "I": _common.STATUS_IDLE, + "P": _common.STATUS_PARKED, +} + +# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h +TCP_STATUSES = { + "01": _common.CONN_ESTABLISHED, + "02": _common.CONN_SYN_SENT, + "03": _common.CONN_SYN_RECV, + "04": _common.CONN_FIN_WAIT1, + "05": _common.CONN_FIN_WAIT2, + "06": _common.CONN_TIME_WAIT, + "07": _common.CONN_CLOSE, + "08": _common.CONN_CLOSE_WAIT, + "09": _common.CONN_LAST_ACK, + "0A": _common.CONN_LISTEN, + "0B": _common.CONN_CLOSING +} + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.virtual_memory() +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached', 'shared', 'slab']) +# psutil.disk_io_counters() +sdiskio = namedtuple( + 'sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time', + 'read_merged_count', 'write_merged_count', + 'busy_time']) +# psutil.Process().open_files() +popenfile = namedtuple( + 'popenfile', ['path', 'fd', 'position', 'mode', 'flags']) +# psutil.Process().memory_info() +pmem = namedtuple('pmem', 'rss vms shared text lib data dirty') +# psutil.Process().memory_full_info() +pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap')) +# psutil.Process().memory_maps(grouped=True) +pmmap_grouped = namedtuple( + 'pmmap_grouped', + ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty', + 'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap']) +# psutil.Process().memory_maps(grouped=False) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_chars', 'write_chars']) +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', + ['user', 'system', 'children_user', 'children_system', + 'iowait']) + + +# ===================================================================== +# --- utils +# ===================================================================== + + +def readlink(path): + """Wrapper around os.readlink().""" + assert isinstance(path, basestring), path + path = os.readlink(path) + # readlink() might return paths containing null bytes ('\x00') + # resulting in "TypeError: must be encoded string without NULL + # bytes, not str" errors when the string is passed to other + # fs-related functions (os.*, open(), ...). + # Apparently everything after '\x00' is garbage (we can have + # ' (deleted)', 'new' and possibly others), see: + # https://github.com/giampaolo/psutil/issues/717 + path = path.split('\x00')[0] + # Certain paths have ' (deleted)' appended. Usually this is + # bogus as the file actually exists. Even if it doesn't we + # don't care. + if path.endswith(' (deleted)') and not path_exists_strict(path): + path = path[:-10] + return path + + +def file_flags_to_mode(flags): + """Convert file's open() flags into a readable string. + Used by Process.open_files(). + """ + modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'} + mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)] + if flags & os.O_APPEND: + mode = mode.replace('w', 'a', 1) + mode = mode.replace('w+', 'r+') + # possible values: r, w, a, r+, a+ + return mode + + +def is_storage_device(name): + """Return True if the given name refers to a root device (e.g. + "sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1", + "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram") + return True. + """ + # Readapted from iostat source code, see: + # https://github.com/sysstat/sysstat/blob/ + # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 + # Some devices may have a slash in their name (e.g. cciss/c0d0...). + name = name.replace('/', '!') + including_virtual = True + if including_virtual: + path = "/sys/block/%s" % name + else: + path = "/sys/block/%s/device" % name + return os.access(path, os.F_OK) + + +@memoize +def set_scputimes_ntuple(procfs_path): + """Set a namedtuple of variable fields depending on the CPU times + available on this Linux kernel version which may be: + (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, + [guest_nice]]]) + Used by cpu_times() function. + """ + global scputimes + with open_binary('%s/stat' % procfs_path) as f: + values = f.readline().split()[1:] + fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] + vlen = len(values) + if vlen >= 8: + # Linux >= 2.6.11 + fields.append('steal') + if vlen >= 9: + # Linux >= 2.6.24 + fields.append('guest') + if vlen >= 10: + # Linux >= 3.2.0 + fields.append('guest_nice') + scputimes = namedtuple('scputimes', fields) + + +def cat(fname, fallback=_DEFAULT, binary=True): + """Return file content. + fallback: the value returned in case the file does not exist or + cannot be read + binary: whether to open the file in binary or text mode. + """ + try: + with open_binary(fname) if binary else open_text(fname) as f: + return f.read().strip() + except (IOError, OSError): + if fallback is not _DEFAULT: + return fallback + else: + raise + + +try: + set_scputimes_ntuple("/proc") +except Exception: + # Don't want to crash at import time. + traceback.print_exc() + scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) + + +# ===================================================================== +# --- system memory +# ===================================================================== + + +def calculate_avail_vmem(mems): + """Fallback for kernels < 3.14 where /proc/meminfo does not provide + "MemAvailable:" column, see: + https://blog.famzah.net/2014/09/24/ + This code reimplements the algorithm outlined here: + https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ + commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + + XXX: on recent kernels this calculation differs by ~1.5% than + "MemAvailable:" as it's calculated slightly differently, see: + https://gitlab.com/procps-ng/procps/issues/42 + https://github.com/famzah/linux-memavailable-procfs/issues/2 + It is still way more realistic than doing (free + cached) though. + """ + # Fallback for very old distros. According to + # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ + # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + # ...long ago "avail" was calculated as (free + cached). + # We might fallback in such cases: + # "Active(file)" not available: 2.6.28 / Dec 2008 + # "Inactive(file)" not available: 2.6.28 / Dec 2008 + # "SReclaimable:" not available: 2.6.19 / Nov 2006 + # /proc/zoneinfo not available: 2.6.13 / Aug 2005 + free = mems[b'MemFree:'] + fallback = free + mems.get(b"Cached:", 0) + try: + lru_active_file = mems[b'Active(file):'] + lru_inactive_file = mems[b'Inactive(file):'] + slab_reclaimable = mems[b'SReclaimable:'] + except KeyError: + return fallback + try: + f = open_binary('%s/zoneinfo' % get_procfs_path()) + except IOError: + return fallback # kernel 2.6.13 + + watermark_low = 0 + with f: + for line in f: + line = line.strip() + if line.startswith(b'low'): + watermark_low += int(line.split()[1]) + watermark_low *= PAGESIZE + watermark_low = watermark_low + + avail = free - watermark_low + pagecache = lru_active_file + lru_inactive_file + pagecache -= min(pagecache / 2, watermark_low) + avail += pagecache + avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low) + return int(avail) + + +def virtual_memory(): + """Report virtual memory stats. + This implementation matches "free" and "vmstat -s" cmdline + utility values and procps-ng-3.3.12 source was used as a reference + (2016-09-18): + https://gitlab.com/procps-ng/procps/blob/ + 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c + For reference, procps-ng-3.3.10 is the version available on Ubuntu + 16.04. + + Note about "available" memory: up until psutil 4.3 it was + calculated as "avail = (free + buffers + cached)". Now + "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as + it's more accurate. + That matches "available" column in newer versions of "free". + """ + missing_fields = [] + mems = {} + with open_binary('%s/meminfo' % get_procfs_path()) as f: + for line in f: + fields = line.split() + mems[fields[0]] = int(fields[1]) * 1024 + + # /proc doc states that the available fields in /proc/meminfo vary + # by architecture and compile options, but these 3 values are also + # returned by sysinfo(2); as such we assume they are always there. + total = mems[b'MemTotal:'] + free = mems[b'MemFree:'] + try: + buffers = mems[b'Buffers:'] + except KeyError: + # https://github.com/giampaolo/psutil/issues/1010 + buffers = 0 + missing_fields.append('buffers') + try: + cached = mems[b"Cached:"] + except KeyError: + cached = 0 + missing_fields.append('cached') + else: + # "free" cmdline utility sums reclaimable to cached. + # Older versions of procps used to add slab memory instead. + # This got changed in: + # https://gitlab.com/procps-ng/procps/commit/ + # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e + cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19 + + try: + shared = mems[b'Shmem:'] # since kernel 2.6.32 + except KeyError: + try: + shared = mems[b'MemShared:'] # kernels 2.4 + except KeyError: + shared = 0 + missing_fields.append('shared') + + try: + active = mems[b"Active:"] + except KeyError: + active = 0 + missing_fields.append('active') + + try: + inactive = mems[b"Inactive:"] + except KeyError: + try: + inactive = \ + mems[b"Inact_dirty:"] + \ + mems[b"Inact_clean:"] + \ + mems[b"Inact_laundry:"] + except KeyError: + inactive = 0 + missing_fields.append('inactive') + + try: + slab = mems[b"Slab:"] + except KeyError: + slab = 0 + + used = total - free - cached - buffers + if used < 0: + # May be symptomatic of running within a LCX container where such + # values will be dramatically distorted over those of the host. + used = total - free + + # - starting from 4.4.0 we match free's "available" column. + # Before 4.4.0 we calculated it as (free + buffers + cached) + # which matched htop. + # - free and htop available memory differs as per: + # http://askubuntu.com/a/369589 + # http://unix.stackexchange.com/a/65852/168884 + # - MemAvailable has been introduced in kernel 3.14 + try: + avail = mems[b'MemAvailable:'] + except KeyError: + avail = calculate_avail_vmem(mems) + + if avail < 0: + avail = 0 + missing_fields.append('available') + + # If avail is greater than total or our calculation overflows, + # that's symptomatic of running within a LCX container where such + # values will be dramatically distorted over those of the host. + # https://gitlab.com/procps-ng/procps/blob/ + # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 + if avail > total: + avail = free + + percent = usage_percent((total - avail), total, round_=1) + + # Warn about missing metrics which are set to 0. + if missing_fields: + msg = "%s memory stats couldn't be determined and %s set to 0" % ( + ", ".join(missing_fields), + "was" if len(missing_fields) == 1 else "were") + warnings.warn(msg, RuntimeWarning) + + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached, shared, slab) + + +def swap_memory(): + """Return swap memory metrics.""" + mems = {} + with open_binary('%s/meminfo' % get_procfs_path()) as f: + for line in f: + fields = line.split() + mems[fields[0]] = int(fields[1]) * 1024 + # We prefer /proc/meminfo over sysinfo() syscall so that + # psutil.PROCFS_PATH can be used in order to allow retrieval + # for linux containers, see: + # https://github.com/giampaolo/psutil/issues/1015 + try: + total = mems[b'SwapTotal:'] + free = mems[b'SwapFree:'] + except KeyError: + _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() + total *= unit_multiplier + free *= unit_multiplier + + used = total - free + percent = usage_percent(used, total, round_=1) + # get pgin/pgouts + try: + f = open_binary("%s/vmstat" % get_procfs_path()) + except IOError as err: + # see https://github.com/giampaolo/psutil/issues/722 + msg = "'sin' and 'sout' swap memory stats couldn't " \ + "be determined and were set to 0 (%s)" % str(err) + warnings.warn(msg, RuntimeWarning) + sin = sout = 0 + else: + with f: + sin = sout = None + for line in f: + # values are expressed in 4 kilo bytes, we want + # bytes instead + if line.startswith(b'pswpin'): + sin = int(line.split(b' ')[1]) * 4 * 1024 + elif line.startswith(b'pswpout'): + sout = int(line.split(b' ')[1]) * 4 * 1024 + if sin is not None and sout is not None: + break + else: + # we might get here when dealing with exotic Linux + # flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'sin' and 'sout' swap memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + sin = sout = 0 + return _common.sswap(total, used, free, percent, sin, sout) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return a named tuple representing the following system-wide + CPU times: + (user, nice, system, idle, iowait, irq, softirq [steal, [guest, + [guest_nice]]]) + Last 3 fields may not be available on all Linux kernel versions. + """ + procfs_path = get_procfs_path() + set_scputimes_ntuple(procfs_path) + with open_binary('%s/stat' % procfs_path) as f: + values = f.readline().split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + return scputimes(*fields) + + +def per_cpu_times(): + """Return a list of namedtuple representing the CPU times + for every CPU available on the system. + """ + procfs_path = get_procfs_path() + set_scputimes_ntuple(procfs_path) + cpus = [] + with open_binary('%s/stat' % procfs_path) as f: + # get rid of the first line which refers to system wide CPU stats + f.readline() + for line in f: + if line.startswith(b'cpu'): + values = line.split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + entry = scputimes(*fields) + cpus.append(entry) + return cpus + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # as a second fallback we try to parse /proc/cpuinfo + num = 0 + with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + for line in f: + if line.lower().startswith(b'processor'): + num += 1 + + # unknown format (e.g. amrel/sparc architectures), see: + # https://github.com/giampaolo/psutil/issues/200 + # try to parse /proc/stat as a last resort + if num == 0: + search = re.compile(r'cpu\d') + with open_text('%s/stat' % get_procfs_path()) as f: + for line in f: + line = line.split(' ')[0] + if search.match(line): + num += 1 + + if num == 0: + # mimic os.cpu_count() + return None + return num + + +def cpu_count_physical(): + """Return the number of physical cores in the system.""" + # Method #1 + core_ids = set() + for path in glob.glob( + "/sys/devices/system/cpu/cpu[0-9]*/topology/core_id"): + with open_binary(path) as f: + core_ids.add(int(f.read())) + result = len(core_ids) + if result != 0: + return result + + # Method #2 + mapping = {} + current_info = {} + with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + for line in f: + line = line.strip().lower() + if not line: + # new section + if (b'physical id' in current_info and + b'cpu cores' in current_info): + mapping[current_info[b'physical id']] = \ + current_info[b'cpu cores'] + current_info = {} + else: + # ongoing section + if (line.startswith(b'physical id') or + line.startswith(b'cpu cores')): + key, value = line.split(b'\t:', 1) + current_info[key] = int(value) + + result = sum(mapping.values()) + return result or None # mimic os.cpu_count() + + +def cpu_stats(): + """Return various CPU stats as a named tuple.""" + with open_binary('%s/stat' % get_procfs_path()) as f: + ctx_switches = None + interrupts = None + soft_interrupts = None + for line in f: + if line.startswith(b'ctxt'): + ctx_switches = int(line.split()[1]) + elif line.startswith(b'intr'): + interrupts = int(line.split()[1]) + elif line.startswith(b'softirq'): + soft_interrupts = int(line.split()[1]) + if ctx_switches is not None and soft_interrupts is not None \ + and interrupts is not None: + break + syscalls = 0 + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls) + + +if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \ + os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): + def cpu_freq(): + """Return frequency metrics for all CPUs. + Contrarily to other OSes, Linux updates these values in + real-time. + """ + def get_path(num): + for p in ("/sys/devices/system/cpu/cpufreq/policy%s" % num, + "/sys/devices/system/cpu/cpu%s/cpufreq" % num): + if os.path.exists(p): + return p + + ret = [] + for n in range(cpu_count_logical()): + path = get_path(n) + if not path: + continue + + pjoin = os.path.join + curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None) + if curr is None: + # Likely an old RedHat, see: + # https://github.com/giampaolo/psutil/issues/1071 + curr = cat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) + if curr is None: + raise NotImplementedError( + "can't find current frequency file") + curr = int(curr) / 1000 + max_ = int(cat(pjoin(path, "scaling_max_freq"))) / 1000 + min_ = int(cat(pjoin(path, "scaling_min_freq"))) / 1000 + ret.append(_common.scpufreq(curr, min_, max_)) + return ret + +elif os.path.exists("/proc/cpuinfo"): + def cpu_freq(): + """Alternate implementation using /proc/cpuinfo. + min and max frequencies are not available and are set to None. + """ + ret = [] + with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + for line in f: + if line.lower().startswith(b'cpu mhz'): + key, value = line.split(b'\t:', 1) + ret.append(_common.scpufreq(float(value), 0., 0.)) + return ret + +else: + def cpu_freq(): + """Dummy implementation when none of the above files are present. + """ + return [] + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_if_addrs = cext_posix.net_if_addrs + + +class _Ipv6UnsupportedError(Exception): + pass + + +class Connections: + """A wrapper on top of /proc/net/* files, retrieving per-process + and system-wide open connections (TCP, UDP, UNIX) similarly to + "netstat -an". + + Note: in case of UNIX sockets we're only able to determine the + local endpoint/path, not the one it's connected to. + According to [1] it would be possible but not easily. + + [1] http://serverfault.com/a/417946 + """ + + def __init__(self): + tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) + tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) + udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) + udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) + unix = ("unix", socket.AF_UNIX, None) + self.tmap = { + "all": (tcp4, tcp6, udp4, udp6, unix), + "tcp": (tcp4, tcp6), + "tcp4": (tcp4,), + "tcp6": (tcp6,), + "udp": (udp4, udp6), + "udp4": (udp4,), + "udp6": (udp6,), + "unix": (unix,), + "inet": (tcp4, tcp6, udp4, udp6), + "inet4": (tcp4, udp4), + "inet6": (tcp6, udp6), + } + self._procfs_path = None + + def get_proc_inodes(self, pid): + inodes = defaultdict(list) + for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)): + try: + inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd)) + except (FileNotFoundError, ProcessLookupError): + # ENOENT == file which is gone in the meantime; + # os.stat('/proc/%s' % self.pid) will be done later + # to force NSP (if it's the case) + continue + except OSError as err: + if err.errno == errno.EINVAL: + # not a link + continue + raise + else: + if inode.startswith('socket:['): + # the process is using a socket + inode = inode[8:][:-1] + inodes[inode].append((pid, int(fd))) + return inodes + + def get_all_inodes(self): + inodes = {} + for pid in pids(): + try: + inodes.update(self.get_proc_inodes(pid)) + except (FileNotFoundError, ProcessLookupError, PermissionError): + # os.listdir() is gonna raise a lot of access denied + # exceptions in case of unprivileged user; that's fine + # as we'll just end up returning a connection with PID + # and fd set to None anyway. + # Both netstat -an and lsof does the same so it's + # unlikely we can do any better. + # ENOENT just means a PID disappeared on us. + continue + return inodes + + @staticmethod + def decode_address(addr, family): + """Accept an "ip:port" address as displayed in /proc/net/* + and convert it into a human readable form, like: + + "0500000A:0016" -> ("10.0.0.5", 22) + "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) + + The IP address portion is a little or big endian four-byte + hexadecimal number; that is, the least significant byte is listed + first, so we need to reverse the order of the bytes to convert it + to an IP address. + The port is represented as a two-byte hexadecimal number. + + Reference: + http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html + """ + ip, port = addr.split(':') + port = int(port, 16) + # this usually refers to a local socket in listen mode with + # no end-points connected + if not port: + return () + if PY3: + ip = ip.encode('ascii') + if family == socket.AF_INET: + # see: https://github.com/giampaolo/psutil/issues/201 + if LITTLE_ENDIAN: + ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) + else: + ip = socket.inet_ntop(family, base64.b16decode(ip)) + else: # IPv6 + # old version - let's keep it, just in case... + # ip = ip.decode('hex') + # return socket.inet_ntop(socket.AF_INET6, + # ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4))) + ip = base64.b16decode(ip) + try: + # see: https://github.com/giampaolo/psutil/issues/201 + if LITTLE_ENDIAN: + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('>4I', *struct.unpack('<4I', ip))) + else: + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('<4I', *struct.unpack('<4I', ip))) + except ValueError: + # see: https://github.com/giampaolo/psutil/issues/623 + if not supports_ipv6(): + raise _Ipv6UnsupportedError + else: + raise + return _common.addr(ip, port) + + @staticmethod + def process_inet(file, family, type_, inodes, filter_pid=None): + """Parse /proc/net/tcp* and /proc/net/udp* files.""" + if file.endswith('6') and not os.path.exists(file): + # IPv6 not supported + return + with open_text(file, buffering=BIGFILE_BUFFERING) as f: + f.readline() # skip the first line + for lineno, line in enumerate(f, 1): + try: + _, laddr, raddr, status, _, _, _, _, _, inode = \ + line.split()[:10] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %s %r" % ( + file, lineno, line)) + if inode in inodes: + # # We assume inet sockets are unique, so we error + # # out if there are multiple references to the + # # same inode. We won't do this for UNIX sockets. + # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: + # raise ValueError("ambiguos inode with multiple " + # "PIDs references") + pid, fd = inodes[inode][0] + else: + pid, fd = None, -1 + if filter_pid is not None and filter_pid != pid: + continue + else: + if type_ == socket.SOCK_STREAM: + status = TCP_STATUSES[status] + else: + status = _common.CONN_NONE + try: + laddr = Connections.decode_address(laddr, family) + raddr = Connections.decode_address(raddr, family) + except _Ipv6UnsupportedError: + continue + yield (fd, family, type_, laddr, raddr, status, pid) + + @staticmethod + def process_unix(file, family, inodes, filter_pid=None): + """Parse /proc/net/unix files.""" + with open_text(file, buffering=BIGFILE_BUFFERING) as f: + f.readline() # skip the first line + for line in f: + tokens = line.split() + try: + _, _, _, _, type_, _, inode = tokens[0:7] + except ValueError: + if ' ' not in line: + # see: https://github.com/giampaolo/psutil/issues/766 + continue + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # With UNIX sockets we can have a single inode + # referencing many file descriptors. + pairs = inodes[inode] + else: + pairs = [(None, -1)] + for pid, fd in pairs: + if filter_pid is not None and filter_pid != pid: + continue + else: + if len(tokens) == 8: + path = tokens[-1] + else: + path = "" + type_ = _common.socktype_to_enum(int(type_)) + # XXX: determining the remote endpoint of a + # UNIX socket on Linux is not possible, see: + # https://serverfault.com/questions/252723/ + raddr = "" + status = _common.CONN_NONE + yield (fd, family, type_, path, raddr, status, pid) + + def retrieve(self, kind, pid=None): + if kind not in self.tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in self.tmap]))) + self._procfs_path = get_procfs_path() + if pid is not None: + inodes = self.get_proc_inodes(pid) + if not inodes: + # no connections for this process + return [] + else: + inodes = self.get_all_inodes() + ret = set() + for f, family, type_ in self.tmap[kind]: + if family in (socket.AF_INET, socket.AF_INET6): + ls = self.process_inet( + "%s/net/%s" % (self._procfs_path, f), + family, type_, inodes, filter_pid=pid) + else: + ls = self.process_unix( + "%s/net/%s" % (self._procfs_path, f), + family, inodes, filter_pid=pid) + for fd, family, type_, laddr, raddr, status, bound_pid in ls: + if pid: + conn = _common.pconn(fd, family, type_, laddr, raddr, + status) + else: + conn = _common.sconn(fd, family, type_, laddr, raddr, + status, bound_pid) + ret.add(conn) + return list(ret) + + +_connections = Connections() + + +def net_connections(kind='inet'): + """Return system-wide open connections.""" + return _connections.retrieve(kind) + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + with open_text("%s/net/dev" % get_procfs_path()) as f: + lines = f.readlines() + retdict = {} + for line in lines[2:]: + colon = line.rfind(':') + assert colon > 0, repr(line) + name = line[:colon].strip() + fields = line[colon + 1:].strip().split() + + # in + (bytes_recv, + packets_recv, + errin, + dropin, + fifoin, # unused + framein, # unused + compressedin, # unused + multicastin, # unused + # out + bytes_sent, + packets_sent, + errout, + dropout, + fifoout, # unused + collisionsout, # unused + carrierout, # unused + compressedout) = map(int, fields) + + retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, + errin, errout, dropin, dropout) + return retdict + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, + cext.DUPLEX_HALF: NIC_DUPLEX_HALF, + cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} + names = net_io_counters().keys() + ret = {} + for name in names: + try: + mtu = cext_posix.net_if_mtu(name) + isup = cext_posix.net_if_flags(name) + duplex, speed = cext.net_if_duplex_speed(name) + except OSError as err: + # https://github.com/giampaolo/psutil/issues/1279 + if err.errno != errno.ENODEV: + raise + else: + ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu) + return ret + + +# ===================================================================== +# --- disks +# ===================================================================== + + +disk_usage = _psposix.disk_usage + + +def disk_io_counters(perdisk=False): + """Return disk I/O statistics for every disk installed on the + system as a dict of raw tuples. + """ + def read_procfs(): + # OK, this is a bit confusing. The format of /proc/diskstats can + # have 3 variations. + # On Linux 2.4 each line has always 15 fields, e.g.: + # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" + # On Linux 2.6+ each line *usually* has 14 fields, and the disk + # name is in another position, like this: + # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" + # ...unless (Linux 2.6) the line refers to a partition instead + # of a disk, in which case the line has less fields (7): + # "3 1 hda1 8 8 8 8" + # 4.18+ has 4 fields added: + # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" + # See: + # https://www.kernel.org/doc/Documentation/iostats.txt + # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats + with open_text("%s/diskstats" % get_procfs_path()) as f: + lines = f.readlines() + for line in lines: + fields = line.split() + flen = len(fields) + if flen == 15: + # Linux 2.4 + name = fields[3] + reads = int(fields[2]) + (reads_merged, rbytes, rtime, writes, writes_merged, + wbytes, wtime, _, busy_time, _) = map(int, fields[4:14]) + elif flen == 14 or flen == 18: + # Linux 2.6+, line referring to a disk + name = fields[2] + (reads, reads_merged, rbytes, rtime, writes, writes_merged, + wbytes, wtime, _, busy_time, _) = map(int, fields[3:14]) + elif flen == 7: + # Linux 2.6+, line referring to a partition + name = fields[2] + reads, rbytes, writes, wbytes = map(int, fields[3:]) + rtime = wtime = reads_merged = writes_merged = busy_time = 0 + else: + raise ValueError("not sure how to interpret line %r" % line) + yield (name, reads, writes, rbytes, wbytes, rtime, wtime, + reads_merged, writes_merged, busy_time) + + def read_sysfs(): + for block in os.listdir('/sys/block'): + for root, _, files in os.walk(os.path.join('/sys/block', block)): + if 'stat' not in files: + continue + with open_text(os.path.join(root, 'stat')) as f: + fields = f.read().strip().split() + name = os.path.basename(root) + (reads, reads_merged, rbytes, rtime, writes, writes_merged, + wbytes, wtime, _, busy_time, _) = map(int, fields) + yield (name, reads, writes, rbytes, wbytes, rtime, + wtime, reads_merged, writes_merged, busy_time) + + if os.path.exists('%s/diskstats' % get_procfs_path()): + gen = read_procfs() + elif os.path.exists('/sys/block'): + gen = read_sysfs() + else: + raise NotImplementedError( + "%s/diskstats nor /sys/block filesystem are available on this " + "system" % get_procfs_path()) + + retdict = {} + for entry in gen: + (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, + writes_merged, busy_time) = entry + if not perdisk and not is_storage_device(name): + # perdisk=False means we want to calculate totals so we skip + # partitions (e.g. 'sda1', 'nvme0n1p1') and only include + # base disk devices (e.g. 'sda', 'nvme0n1'). Base disks + # include a total of all their partitions + some extra size + # of their own: + # $ cat /proc/diskstats + # 259 0 sda 10485760 ... + # 259 1 sda1 5186039 ... + # 259 1 sda2 5082039 ... + # See: + # https://github.com/giampaolo/psutil/pull/1313 + continue + + rbytes *= DISK_SECTOR_SIZE + wbytes *= DISK_SECTOR_SIZE + retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime, + reads_merged, writes_merged, busy_time) + + return retdict + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples.""" + fstypes = set() + procfs_path = get_procfs_path() + with open_text("%s/filesystems" % procfs_path) as f: + for line in f: + line = line.strip() + if not line.startswith("nodev"): + fstypes.add(line.strip()) + else: + # ignore all lines starting with "nodev" except "nodev zfs" + fstype = line.split("\t")[1] + if fstype == "zfs": + fstypes.add("zfs") + + # See: https://github.com/giampaolo/psutil/issues/1307 + if procfs_path == "/proc" and os.path.isfile('/etc/mtab'): + mounts_path = os.path.realpath("/etc/mtab") + else: + mounts_path = os.path.realpath("%s/self/mounts" % procfs_path) + + retlist = [] + partitions = cext.disk_partitions(mounts_path) + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if device == '' or fstype not in fstypes: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_temperatures(): + """Return hardware (CPU and others) temperatures as a dict + including hardware name, label, current, max and critical + temperatures. + + Implementation notes: + - /sys/class/hwmon looks like the most recent interface to + retrieve this info, and this implementation relies on it + only (old distros will probably use something else) + - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon + - /sys/class/thermal/thermal_zone* is another one but it's more + difficult to parse + """ + ret = collections.defaultdict(list) + basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*') + # CentOS has an intermediate /device directory: + # https://github.com/giampaolo/psutil/issues/971 + # https://github.com/nicolargo/glances/issues/1060 + basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*')) + basenames = sorted(set([x.split('_')[0] for x in basenames])) + + for base in basenames: + try: + path = base + '_input' + current = float(cat(path)) / 1000.0 + path = os.path.join(os.path.dirname(base), 'name') + unit_name = cat(path, binary=False) + except (IOError, OSError, ValueError) as err: + # A lot of things can go wrong here, so let's just skip the + # whole entry. Sure thing is Linux's /sys/class/hwmon really + # is a stinky broken mess. + # https://github.com/giampaolo/psutil/issues/1009 + # https://github.com/giampaolo/psutil/issues/1101 + # https://github.com/giampaolo/psutil/issues/1129 + # https://github.com/giampaolo/psutil/issues/1245 + # https://github.com/giampaolo/psutil/issues/1323 + warnings.warn("ignoring %r for file %r" % (err, path), + RuntimeWarning) + continue + + high = cat(base + '_max', fallback=None) + critical = cat(base + '_crit', fallback=None) + label = cat(base + '_label', fallback='', binary=False) + + if high is not None: + try: + high = float(high) / 1000.0 + except ValueError: + high = None + if critical is not None: + try: + critical = float(critical) / 1000.0 + except ValueError: + critical = None + + ret[unit_name].append((label, current, high, critical)) + + # Indication that no sensors were detected in /sys/class/hwmon/ + if not basenames: + basenames = glob.glob('/sys/class/thermal/thermal_zone*') + basenames = sorted(set(basenames)) + + for base in basenames: + try: + path = os.path.join(base, 'temp') + current = float(cat(path)) / 1000.0 + path = os.path.join(base, 'type') + unit_name = cat(path, binary=False) + except (IOError, OSError, ValueError) as err: + warnings.warn("ignoring %r for file %r" % (err, path), + RuntimeWarning) + continue + + trip_paths = glob.glob(base + '/trip_point*') + trip_points = set(['_'.join( + os.path.basename(p).split('_')[0:3]) for p in trip_paths]) + critical = None + high = None + for trip_point in trip_points: + path = os.path.join(base, trip_point + "_type") + trip_type = cat(path, fallback='', binary=False) + if trip_type == 'critical': + critical = cat(os.path.join(base, trip_point + "_temp"), + fallback=None) + elif trip_type == 'high': + high = cat(os.path.join(base, trip_point + "_temp"), + fallback=None) + + if high is not None: + try: + high = float(high) / 1000.0 + except ValueError: + high = None + if critical is not None: + try: + critical = float(critical) / 1000.0 + except ValueError: + critical = None + + ret[unit_name].append(('', current, high, critical)) + + return dict(ret) + + +def sensors_fans(): + """Return hardware fans info (for CPU and other peripherals) as a + dict including hardware label and current speed. + + Implementation notes: + - /sys/class/hwmon looks like the most recent interface to + retrieve this info, and this implementation relies on it + only (old distros will probably use something else) + - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon + """ + ret = collections.defaultdict(list) + basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*') + if not basenames: + # CentOS has an intermediate /device directory: + # https://github.com/giampaolo/psutil/issues/971 + basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*') + + basenames = sorted(set([x.split('_')[0] for x in basenames])) + for base in basenames: + try: + current = int(cat(base + '_input')) + except (IOError, OSError) as err: + warnings.warn("ignoring %r" % err, RuntimeWarning) + continue + unit_name = cat(os.path.join(os.path.dirname(base), 'name'), + binary=False) + label = cat(base + '_label', fallback='', binary=False) + ret[unit_name].append(_common.sfan(label, current)) + + return dict(ret) + + +def sensors_battery(): + """Return battery information. + Implementation note: it appears /sys/class/power_supply/BAT0/ + directory structure may vary and provide files with the same + meaning but under different names, see: + https://github.com/giampaolo/psutil/issues/966 + """ + null = object() + + def multi_cat(*paths): + """Attempt to read the content of multiple files which may + not exist. If none of them exist return None. + """ + for path in paths: + ret = cat(path, fallback=null) + if ret != null: + return int(ret) if ret.isdigit() else ret + return None + + bats = [x for x in os.listdir(POWER_SUPPLY_PATH) if x.startswith('BAT')] + if not bats: + return None + # Get the first available battery. Usually this is "BAT0", except + # some rare exceptions: + # https://github.com/giampaolo/psutil/issues/1238 + root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0]) + + # Base metrics. + energy_now = multi_cat( + root + "/energy_now", + root + "/charge_now") + power_now = multi_cat( + root + "/power_now", + root + "/current_now") + energy_full = multi_cat( + root + "/energy_full", + root + "/charge_full") + if energy_now is None or power_now is None: + return None + + # Percent. If we have energy_full the percentage will be more + # accurate compared to reading /capacity file (float vs. int). + if energy_full is not None: + try: + percent = 100.0 * energy_now / energy_full + except ZeroDivisionError: + percent = 0.0 + else: + percent = int(cat(root + "/capacity", fallback=-1)) + if percent == -1: + return None + + # Is AC power cable plugged in? + # Note: AC0 is not always available and sometimes (e.g. CentOS7) + # it's called "AC". + power_plugged = None + online = multi_cat( + os.path.join(POWER_SUPPLY_PATH, "AC0/online"), + os.path.join(POWER_SUPPLY_PATH, "AC/online")) + if online is not None: + power_plugged = online == 1 + else: + status = cat(root + "/status", fallback="", binary=False).lower() + if status == "discharging": + power_plugged = False + elif status in ("charging", "full"): + power_plugged = True + + # Seconds left. + # Note to self: we may also calculate the charging ETA as per: + # https://github.com/thialfihar/dotfiles/blob/ + # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + else: + try: + secsleft = int(energy_now / power_now * 3600) + except ZeroDivisionError: + secsleft = _common.POWER_TIME_UNKNOWN + + return _common.sbattery(percent, secsleft, power_plugged) + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, user_process, pid = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in (':0.0', ':0'): + hostname = 'localhost' + nt = _common.suser(user, tty or None, hostname, tstamp, pid) + retlist.append(nt) + return retlist + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + global BOOT_TIME + path = '%s/stat' % get_procfs_path() + with open_binary(path) as f: + for line in f: + if line.startswith(b'btime'): + ret = float(line.strip().split()[1]) + BOOT_TIME = ret + return ret + raise RuntimeError( + "line 'btime' not found in %s" % path) + + +# ===================================================================== +# --- processes +# ===================================================================== + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix PID. Linux TIDs are not + supported (always return False). + """ + if not _psposix.pid_exists(pid): + return False + else: + # Linux's apparently does not distinguish between PIDs and TIDs + # (thread IDs). + # listdir("/proc") won't show any TID (only PIDs) but + # os.stat("/proc/{tid}") will succeed if {tid} exists. + # os.kill() can also be passed a TID. This is quite confusing. + # In here we want to enforce this distinction and support PIDs + # only, see: + # https://github.com/giampaolo/psutil/issues/687 + try: + # Note: already checked that this is faster than using a + # regular expr. Also (a lot) faster than doing + # 'return pid in pids()' + path = "%s/%s/status" % (get_procfs_path(), pid) + with open_binary(path) as f: + for line in f: + if line.startswith(b"Tgid:"): + tgid = int(line.split()[1]) + # If tgid and pid are the same then we're + # dealing with a process PID. + return tgid == pid + raise ValueError("'Tgid' line not found in %s" % path) + except (EnvironmentError, ValueError): + return pid in pids() + + +def ppid_map(): + """Obtain a {pid: ppid, ...} dict for all running processes in + one shot. Used to speed up Process.children(). + """ + ret = {} + procfs_path = get_procfs_path() + for pid in pids(): + try: + with open_binary("%s/%s/stat" % (procfs_path, pid)) as f: + data = f.read() + except (FileNotFoundError, ProcessLookupError): + # Note: we should be able to access /stat for all processes + # aka it's unlikely we'll bump into EPERM, which is good. + pass + else: + rpar = data.rfind(b')') + dset = data[rpar + 2:].split() + ppid = int(dset[1]) + ret[pid] = ppid + return ret + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and IOError exceptions + into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except PermissionError: + raise AccessDenied(self.pid, self._name) + except ProcessLookupError: + raise NoSuchProcess(self.pid, self._name) + except FileNotFoundError: + if not os.path.exists("%s/%s" % (self._procfs_path, self.pid)): + raise NoSuchProcess(self.pid, self._name) + # Note: zombies will keep existing under /proc until they're + # gone so there's no way to distinguish them in here. + raise + return wrapper + + +class Process(object): + """Linux process implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + self._procfs_path = get_procfs_path() + + def _assert_alive(self): + """Raise NSP if the process disappeared on us.""" + # For those C function who do not raise NSP, possibly returning + # incorrect or incomplete result. + os.stat('%s/%s' % (self._procfs_path, self.pid)) + + @wrap_exceptions + @memoize_when_activated + def _parse_stat_file(self): + """Parse /proc/{pid}/stat file and return a dict with various + process info. + Using "man proc" as a reference: where "man proc" refers to + position N always substract 3 (e.g ppid position 4 in + 'man proc' == position 1 in here). + The return value is cached in case oneshot() ctx manager is + in use. + """ + with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: + data = f.read() + # Process name is between parentheses. It can contain spaces and + # other parentheses. This is taken into account by looking for + # the first occurrence of "(" and the last occurence of ")". + rpar = data.rfind(b')') + name = data[data.find(b'(') + 1:rpar] + fields = data[rpar + 2:].split() + + ret = {} + ret['name'] = name + ret['status'] = fields[0] + ret['ppid'] = fields[1] + ret['ttynr'] = fields[4] + ret['utime'] = fields[11] + ret['stime'] = fields[12] + ret['children_utime'] = fields[13] + ret['children_stime'] = fields[14] + ret['create_time'] = fields[19] + ret['cpu_num'] = fields[36] + ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks' + + return ret + + @wrap_exceptions + @memoize_when_activated + def _read_status_file(self): + """Read /proc/{pid}/stat file and return its content. + The return value is cached in case oneshot() ctx manager is + in use. + """ + with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f: + return f.read() + + @wrap_exceptions + @memoize_when_activated + def _read_smaps_file(self): + with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid), + buffering=BIGFILE_BUFFERING) as f: + return f.read().strip() + + def oneshot_enter(self): + self._parse_stat_file.cache_activate(self) + self._read_status_file.cache_activate(self) + self._read_smaps_file.cache_activate(self) + + def oneshot_exit(self): + self._parse_stat_file.cache_deactivate(self) + self._read_status_file.cache_deactivate(self) + self._read_smaps_file.cache_deactivate(self) + + @wrap_exceptions + def name(self): + name = self._parse_stat_file()['name'] + if PY3: + name = decode(name) + # XXX - gets changed later and probably needs refactoring + return name + + def exe(self): + try: + return readlink("%s/%s/exe" % (self._procfs_path, self.pid)) + except (FileNotFoundError, ProcessLookupError): + # no such file error; might be raised also if the + # path actually exists for system processes with + # low pids (about 0-20) + if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)): + return "" + else: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + except PermissionError: + raise AccessDenied(self.pid, self._name) + + @wrap_exceptions + def cmdline(self): + with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f: + data = f.read() + if not data: + # may happen in case of zombie process + return [] + # 'man proc' states that args are separated by null bytes '\0' + # and last char is supposed to be a null byte. Nevertheless + # some processes may change their cmdline after being started + # (via setproctitle() or similar), they are usually not + # compliant with this rule and use spaces instead. Google + # Chrome process is an example. See: + # https://github.com/giampaolo/psutil/issues/1179 + sep = '\x00' if data.endswith('\x00') else ' ' + if data.endswith(sep): + data = data[:-1] + cmdline = data.split(sep) + # Sometimes last char is a null byte '\0' but the args are + # separated by spaces, see: https://github.com/giampaolo/psutil/ + # issues/1179#issuecomment-552984549 + if sep == '\x00' and len(cmdline) == 1 and ' ' in data: + cmdline = data.split(' ') + return cmdline + + @wrap_exceptions + def environ(self): + with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f: + data = f.read() + return parse_environ_block(data) + + @wrap_exceptions + def terminal(self): + tty_nr = int(self._parse_stat_file()['ttynr']) + tmap = _psposix.get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + # May not be available on old kernels. + if os.path.exists('/proc/%s/io' % os.getpid()): + @wrap_exceptions + def io_counters(self): + fname = "%s/%s/io" % (self._procfs_path, self.pid) + fields = {} + with open_binary(fname) as f: + for line in f: + # https://github.com/giampaolo/psutil/issues/1004 + line = line.strip() + if line: + try: + name, value = line.split(b': ') + except ValueError: + # https://github.com/giampaolo/psutil/issues/1004 + continue + else: + fields[name] = int(value) + if not fields: + raise RuntimeError("%s file was empty" % fname) + try: + return pio( + fields[b'syscr'], # read syscalls + fields[b'syscw'], # write syscalls + fields[b'read_bytes'], # read bytes + fields[b'write_bytes'], # write bytes + fields[b'rchar'], # read chars + fields[b'wchar'], # write chars + ) + except KeyError as err: + raise ValueError("%r field was not found in %s; found fields " + "are %r" % (err[0], fname, fields)) + + @wrap_exceptions + def cpu_times(self): + values = self._parse_stat_file() + utime = float(values['utime']) / CLOCK_TICKS + stime = float(values['stime']) / CLOCK_TICKS + children_utime = float(values['children_utime']) / CLOCK_TICKS + children_stime = float(values['children_stime']) / CLOCK_TICKS + iowait = float(values['blkio_ticks']) / CLOCK_TICKS + return pcputimes(utime, stime, children_utime, children_stime, iowait) + + @wrap_exceptions + def cpu_num(self): + """What CPU the process is on.""" + return int(self._parse_stat_file()['cpu_num']) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + @wrap_exceptions + def create_time(self): + ctime = float(self._parse_stat_file()['create_time']) + # According to documentation, starttime is in field 21 and the + # unit is jiffies (clock ticks). + # We first divide it for clock ticks and then add uptime returning + # seconds since the epoch, in UTC. + # Also use cached value if available. + bt = BOOT_TIME or boot_time() + return (ctime / CLOCK_TICKS) + bt + + @wrap_exceptions + def memory_info(self): + # ============================================================ + # | FIELD | DESCRIPTION | AKA | TOP | + # ============================================================ + # | rss | resident set size | | RES | + # | vms | total program size | size | VIRT | + # | shared | shared pages (from shared mappings) | | SHR | + # | text | text ('code') | trs | CODE | + # | lib | library (unused in Linux 2.6) | lrs | | + # | data | data + stack | drs | DATA | + # | dirty | dirty pages (unused in Linux 2.6) | dt | | + # ============================================================ + with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: + vms, rss, shared, text, lib, data, dirty = \ + [int(x) * PAGESIZE for x in f.readline().split()[:7]] + return pmem(rss, vms, shared, text, lib, data, dirty) + + # /proc/pid/smaps does not exist on kernels < 2.6.14 or if + # CONFIG_MMU kernel configuration option is not enabled. + if HAS_SMAPS: + + @wrap_exceptions + def memory_full_info( + self, + # Gets Private_Clean, Private_Dirty, Private_Hugetlb. + _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"), + _pss_re=re.compile(br"\nPss\:\s+(\d+)"), + _swap_re=re.compile(br"\nSwap\:\s+(\d+)")): + basic_mem = self.memory_info() + # Note: using 3 regexes is faster than reading the file + # line by line. + # XXX: on Python 3 the 2 regexes are 30% slower than on + # Python 2 though. Figure out why. + # + # You might be tempted to calculate USS by subtracting + # the "shared" value from the "resident" value in + # /proc//statm. But at least on Linux, statm's "shared" + # value actually counts pages backed by files, which has + # little to do with whether the pages are actually shared. + # /proc/self/smaps on the other hand appears to give us the + # correct information. + smaps_data = self._read_smaps_file() + # Note: smaps file can be empty for certain processes. + # The code below will not crash though and will result to 0. + uss = sum(map(int, _private_re.findall(smaps_data))) * 1024 + pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024 + swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024 + return pfullmem(*basic_mem + (uss, pss, swap)) + + else: + memory_full_info = memory_info + + if HAS_SMAPS: + + @wrap_exceptions + def memory_maps(self): + """Return process's mapped memory regions as a list of named + tuples. Fields are explained in 'man proc'; here is an updated + (Apr 2012) version: http://goo.gl/fmebo + + /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if + CONFIG_MMU kernel configuration option is not enabled. + """ + def get_blocks(lines, current_block): + data = {} + for line in lines: + fields = line.split(None, 5) + if not fields[0].endswith(b':'): + # new block section + yield (current_block.pop(), data) + current_block.append(line) + else: + try: + data[fields[0]] = int(fields[1]) * 1024 + except ValueError: + if fields[0].startswith(b'VmFlags:'): + # see issue #369 + continue + else: + raise ValueError("don't know how to inte" + "rpret line %r" % line) + yield (current_block.pop(), data) + + data = self._read_smaps_file() + # Note: smaps file can be empty for certain processes. + if not data: + return [] + lines = data.split(b'\n') + ls = [] + first_line = lines.pop(0) + current_block = [first_line] + for header, data in get_blocks(lines, current_block): + hfields = header.split(None, 5) + try: + addr, perms, offset, dev, inode, path = hfields + except ValueError: + addr, perms, offset, dev, inode, path = \ + hfields + [''] + if not path: + path = '[anon]' + else: + if PY3: + path = decode(path) + path = path.strip() + if (path.endswith(' (deleted)') and not + path_exists_strict(path)): + path = path[:-10] + ls.append(( + decode(addr), decode(perms), path, + data[b'Rss:'], + data.get(b'Size:', 0), + data.get(b'Pss:', 0), + data.get(b'Shared_Clean:', 0), + data.get(b'Shared_Dirty:', 0), + data.get(b'Private_Clean:', 0), + data.get(b'Private_Dirty:', 0), + data.get(b'Referenced:', 0), + data.get(b'Anonymous:', 0), + data.get(b'Swap:', 0) + )) + return ls + + @wrap_exceptions + def cwd(self): + try: + return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) + except (FileNotFoundError, ProcessLookupError): + # https://github.com/giampaolo/psutil/issues/986 + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + + @wrap_exceptions + def num_ctx_switches(self, + _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')): + data = self._read_status_file() + ctxsw = _ctxsw_re.findall(data) + if not ctxsw: + raise NotImplementedError( + "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" + "lines were not found in %s/%s/status; the kernel is " + "probably older than 2.6.23" % ( + self._procfs_path, self.pid)) + else: + return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) + + @wrap_exceptions + def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')): + # Note: on Python 3 using a re is faster than iterating over file + # line by line. On Python 2 is the exact opposite, and iterating + # over a file on Python 3 is slower than on Python 2. + data = self._read_status_file() + return int(_num_threads_re.findall(data)[0]) + + @wrap_exceptions + def threads(self): + thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid)) + thread_ids.sort() + retlist = [] + hit_enoent = False + for thread_id in thread_ids: + fname = "%s/%s/task/%s/stat" % ( + self._procfs_path, self.pid, thread_id) + try: + with open_binary(fname) as f: + st = f.read().strip() + except FileNotFoundError: + # no such file or directory; it means thread + # disappeared on us + hit_enoent = True + continue + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + ntuple = _common.pthread(int(thread_id), utime, stime) + retlist.append(ntuple) + if hit_enoent: + self._assert_alive() + return retlist + + @wrap_exceptions + def nice_get(self): + # with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f: + # data = f.read() + # return int(data.split()[18]) + + # Use C implementation + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + # starting from CentOS 6. + if HAS_CPU_AFFINITY: + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + def _get_eligible_cpus( + self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")): + # See: https://github.com/giampaolo/psutil/issues/956 + data = self._read_status_file() + match = _re.findall(data) + if match: + return list(range(int(match[0][0]), int(match[0][1]) + 1)) + else: + return list(range(len(per_cpu_times()))) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except (OSError, ValueError) as err: + if isinstance(err, ValueError) or err.errno == errno.EINVAL: + eligible_cpus = self._get_eligible_cpus() + all_cpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in all_cpus: + raise ValueError( + "invalid CPU number %r; choose between %s" % ( + cpu, eligible_cpus)) + if cpu not in eligible_cpus: + raise ValueError( + "CPU number %r is not eligible; choose " + "between %s" % (cpu, eligible_cpus)) + raise + + # only starting from kernel 2.6.13 + if HAS_PROC_IO_PRIORITY: + + @wrap_exceptions + def ionice_get(self): + ioclass, value = cext.proc_ioprio_get(self.pid) + if enum is not None: + ioclass = IOPriority(ioclass) + return _common.pionice(ioclass, value) + + @wrap_exceptions + def ionice_set(self, ioclass, value): + if value is None: + value = 0 + if value and ioclass in (IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE): + raise ValueError("%r ioclass accepts no value" % ioclass) + if value < 0 or value > 7: + raise ValueError("value not in 0-7 range") + return cext.proc_ioprio_set(self.pid, ioclass, value) + + if HAS_PRLIMIT: + + @wrap_exceptions + def rlimit(self, resource, limits=None): + # If pid is 0 prlimit() applies to the calling process and + # we don't want that. We should never get here though as + # PID 0 is not supported on Linux. + if self.pid == 0: + raise ValueError("can't use prlimit() against PID 0 process") + try: + if limits is None: + # get + return cext.linux_prlimit(self.pid, resource) + else: + # set + if len(limits) != 2: + raise ValueError( + "second argument must be a (soft, hard) tuple, " + "got %s" % repr(limits)) + soft, hard = limits + cext.linux_prlimit(self.pid, resource, soft, hard) + except OSError as err: + if err.errno == errno.ENOSYS and pid_exists(self.pid): + # I saw this happening on Travis: + # https://travis-ci.org/giampaolo/psutil/jobs/51368273 + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise + + @wrap_exceptions + def status(self): + letter = self._parse_stat_file()['status'] + if PY3: + letter = letter.decode() + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(letter, '?') + + @wrap_exceptions + def open_files(self): + retlist = [] + files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)) + hit_enoent = False + for fd in files: + file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd) + try: + path = readlink(file) + except (FileNotFoundError, ProcessLookupError): + # ENOENT == file which is gone in the meantime + hit_enoent = True + continue + except OSError as err: + if err.errno == errno.EINVAL: + # not a link + continue + raise + else: + # If path is not an absolute there's no way to tell + # whether it's a regular file or not, so we skip it. + # A regular file is always supposed to be have an + # absolute path though. + if path.startswith('/') and isfile_strict(path): + # Get file position and flags. + file = "%s/%s/fdinfo/%s" % ( + self._procfs_path, self.pid, fd) + try: + with open_binary(file) as f: + pos = int(f.readline().split()[1]) + flags = int(f.readline().split()[1], 8) + except FileNotFoundError: + # fd gone in the meantime; process may + # still be alive + hit_enoent = True + else: + mode = file_flags_to_mode(flags) + ntuple = popenfile( + path, int(fd), int(pos), mode, flags) + retlist.append(ntuple) + if hit_enoent: + self._assert_alive() + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = _connections.retrieve(kind, self.pid) + self._assert_alive() + return ret + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + + @wrap_exceptions + def ppid(self): + return int(self._parse_stat_file()['ppid']) + + @wrap_exceptions + def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')): + data = self._read_status_file() + real, effective, saved = _uids_re.findall(data)[0] + return _common.puids(int(real), int(effective), int(saved)) + + @wrap_exceptions + def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')): + data = self._read_status_file() + real, effective, saved = _gids_re.findall(data)[0] + return _common.pgids(int(real), int(effective), int(saved)) diff --git a/ddtrace/vendor/psutil/_psosx.py b/ddtrace/vendor/psutil/_psosx.py new file mode 100644 index 00000000000..7f28447bb18 --- /dev/null +++ b/ddtrace/vendor/psutil/_psosx.py @@ -0,0 +1,568 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""macOS platform implementation.""" + +import contextlib +import errno +import functools +import os +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_osx as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap +from ._common import conn_to_ntuple +from ._common import isfile_strict +from ._common import memoize_when_activated +from ._common import parse_environ_block +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._common import usage_percent + + +__extra__all__ = [] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +kinfo_proc_map = dict( + ppid=0, + ruid=1, + euid=2, + suid=3, + rgid=4, + egid=5, + sgid=6, + ttynr=7, + ctime=8, + status=9, + name=10, +) + +pidtaskinfo_map = dict( + cpuutime=0, + cpustime=1, + rss=2, + vms=3, + pfaults=4, + pageins=5, + numthreads=6, + volctxsw=7, +) + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.cpu_times() +scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) +# psutil.virtual_memory() +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'wired']) +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins']) +# psutil.Process.memory_full_info() +pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', )) + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + total, active, inactive, wired, free, speculative = cext.virtual_mem() + # This is how Zabbix calculate avail and used mem: + # https://github.com/zabbix/zabbix/blob/trunk/src/libs/zbxsysinfo/ + # osx/memory.c + # Also see: https://github.com/giampaolo/psutil/issues/1277 + avail = inactive + free + used = active + wired + # This is NOT how Zabbix calculates free mem but it matches "free" + # cmdline utility. + free -= speculative + percent = usage_percent((total - avail), total, round_=1) + return svmem(total, avail, percent, used, free, + active, inactive, wired) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, used, free, sin, sout = cext.swap_mem() + percent = usage_percent(used, total, round_=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system CPU times as a namedtuple.""" + user, nice, system, idle = cext.cpu_times() + return scputimes(user, nice, system, idle) + + +def per_cpu_times(): + """Return system CPU times as a named tuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle = cpu_t + item = scputimes(user, nice, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def cpu_stats(): + ctx_switches, interrupts, soft_interrupts, syscalls, traps = \ + cext.cpu_stats() + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls) + + +def cpu_freq(): + """Return CPU frequency. + On macOS per-cpu frequency is not supported. + Also, the returned frequency never changes, see: + https://arstechnica.com/civis/viewtopic.php?f=19&t=465002 + """ + curr, min_, max_ = cext.cpu_freq() + return [_common.scpufreq(curr, min_, max_)] + + +# ===================================================================== +# --- disks +# ===================================================================== + + +disk_usage = _psposix.disk_usage +disk_io_counters = cext.disk_io_counters + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples.""" + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_battery(): + """Return battery information.""" + try: + percent, minsleft, power_plugged = cext.sensors_battery() + except NotImplementedError: + # no power source - return None according to interface + return None + power_plugged = power_plugged == 1 + if power_plugged: + secsleft = _common.POWER_TIME_UNLIMITED + elif minsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + else: + secsleft = minsleft * 60 + return _common.sbattery(percent, secsleft, power_plugged) + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_io_counters = cext.net_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def net_connections(kind='inet'): + """System-wide network connections.""" + # Note: on macOS this will fail with AccessDenied unless + # the process is owned by root. + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except NoSuchProcess: + continue + else: + if cons: + for c in cons: + c = list(c) + [pid] + ret.append(_common.sconn(*c)) + return ret + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + try: + mtu = cext_posix.net_if_mtu(name) + isup = cext_posix.net_if_flags(name) + duplex, speed = cext_posix.net_if_duplex_speed(name) + except OSError as err: + # https://github.com/giampaolo/psutil/issues/1279 + if err.errno != errno.ENODEV: + raise + else: + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, pid = item + if tty == '~': + continue # reboot or shutdown + if not tstamp: + continue + nt = _common.suser(user, tty or None, hostname or None, tstamp, pid) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- processes +# ===================================================================== + + +def pids(): + ls = cext.pids() + if 0 not in ls: + # On certain macOS versions pids() C doesn't return PID 0 but + # "ps" does and the process is querable via sysctl(): + # https://travis-ci.org/giampaolo/psutil/jobs/309619941 + try: + Process(0).create_time() + ls.insert(0, 0) + except NoSuchProcess: + pass + except AccessDenied: + ls.insert(0, 0) + return ls + + +pid_exists = _psposix.pid_exists + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except ProcessLookupError: + raise NoSuchProcess(self.pid, self._name) + except PermissionError: + raise AccessDenied(self.pid, self._name) + except cext.ZombieProcessError: + raise ZombieProcess(self.pid, self._name, self._ppid) + return wrapper + + +@contextlib.contextmanager +def catch_zombie(proc): + """There are some poor C APIs which incorrectly raise ESRCH when + the process is still alive or it's a zombie, or even RuntimeError + (those who don't set errno). This is here in order to solve: + https://github.com/giampaolo/psutil/issues/1044 + """ + try: + yield + except (OSError, RuntimeError) as err: + if isinstance(err, RuntimeError) or err.errno == errno.ESRCH: + try: + # status() is not supposed to lie and correctly detect + # zombies so if it raises ESRCH it's true. + status = proc.status() + except NoSuchProcess: + raise err + else: + if status == _common.STATUS_ZOMBIE: + raise ZombieProcess(proc.pid, proc._name, proc._ppid) + else: + raise AccessDenied(proc.pid, proc._name) + else: + raise + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + @memoize_when_activated + def _get_kinfo_proc(self): + # Note: should work with all PIDs without permission issues. + ret = cext.proc_kinfo_oneshot(self.pid) + assert len(ret) == len(kinfo_proc_map) + return ret + + @wrap_exceptions + @memoize_when_activated + def _get_pidtaskinfo(self): + # Note: should work for PIDs owned by user only. + with catch_zombie(self): + ret = cext.proc_pidtaskinfo_oneshot(self.pid) + assert len(ret) == len(pidtaskinfo_map) + return ret + + def oneshot_enter(self): + self._get_kinfo_proc.cache_activate(self) + self._get_pidtaskinfo.cache_activate(self) + + def oneshot_exit(self): + self._get_kinfo_proc.cache_deactivate(self) + self._get_pidtaskinfo.cache_deactivate(self) + + @wrap_exceptions + def name(self): + name = self._get_kinfo_proc()[kinfo_proc_map['name']] + return name if name is not None else cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + with catch_zombie(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + with catch_zombie(self): + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def environ(self): + with catch_zombie(self): + return parse_environ_block(cext.proc_environ(self.pid)) + + @wrap_exceptions + def ppid(self): + self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']] + return self._ppid + + @wrap_exceptions + def cwd(self): + with catch_zombie(self): + return cext.proc_cwd(self.pid) + + @wrap_exceptions + def uids(self): + rawtuple = self._get_kinfo_proc() + return _common.puids( + rawtuple[kinfo_proc_map['ruid']], + rawtuple[kinfo_proc_map['euid']], + rawtuple[kinfo_proc_map['suid']]) + + @wrap_exceptions + def gids(self): + rawtuple = self._get_kinfo_proc() + return _common.puids( + rawtuple[kinfo_proc_map['rgid']], + rawtuple[kinfo_proc_map['egid']], + rawtuple[kinfo_proc_map['sgid']]) + + @wrap_exceptions + def terminal(self): + tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']] + tmap = _psposix.get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def memory_info(self): + rawtuple = self._get_pidtaskinfo() + return pmem( + rawtuple[pidtaskinfo_map['rss']], + rawtuple[pidtaskinfo_map['vms']], + rawtuple[pidtaskinfo_map['pfaults']], + rawtuple[pidtaskinfo_map['pageins']], + ) + + @wrap_exceptions + def memory_full_info(self): + basic_mem = self.memory_info() + uss = cext.proc_memory_uss(self.pid) + return pfullmem(*basic_mem + (uss, )) + + @wrap_exceptions + def cpu_times(self): + rawtuple = self._get_pidtaskinfo() + return _common.pcputimes( + rawtuple[pidtaskinfo_map['cpuutime']], + rawtuple[pidtaskinfo_map['cpustime']], + # children user / system times are not retrievable (set to 0) + 0.0, 0.0) + + @wrap_exceptions + def create_time(self): + return self._get_kinfo_proc()[kinfo_proc_map['ctime']] + + @wrap_exceptions + def num_ctx_switches(self): + # Unvoluntary value seems not to be available; + # getrusage() numbers seems to confirm this theory. + # We set it to 0. + vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']] + return _common.pctxsw(vol, 0) + + @wrap_exceptions + def num_threads(self): + return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']] + + @wrap_exceptions + def open_files(self): + if self.pid == 0: + return [] + files = [] + with catch_zombie(self): + rawlist = cext.proc_open_files(self.pid) + for path, fd in rawlist: + if isfile_strict(path): + ntuple = _common.popenfile(path, fd) + files.append(ntuple) + return files + + @wrap_exceptions + def connections(self, kind='inet'): + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + with catch_zombie(self): + rawlist = cext.proc_connections(self.pid, families, types) + ret = [] + for item in rawlist: + fd, fam, type, laddr, raddr, status = item + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, + TCP_STATUSES) + ret.append(nt) + return ret + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: + return 0 + with catch_zombie(self): + return cext.proc_num_fds(self.pid) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) + + @wrap_exceptions + def nice_get(self): + with catch_zombie(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + with catch_zombie(self): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = self._get_kinfo_proc()[kinfo_proc_map['status']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist diff --git a/ddtrace/vendor/psutil/_psposix.py b/ddtrace/vendor/psutil/_psposix.py new file mode 100644 index 00000000000..24570224fe9 --- /dev/null +++ b/ddtrace/vendor/psutil/_psposix.py @@ -0,0 +1,179 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Routines common to all posix systems.""" + +import glob +import os +import sys +import time + +from ._common import memoize +from ._common import sdiskusage +from ._common import usage_percent +from ._compat import ChildProcessError +from ._compat import FileNotFoundError +from ._compat import InterruptedError +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import PY3 +from ._compat import unicode + + +__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map'] + + +# This object gets set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +TimeoutExpired = None + + +def pid_exists(pid): + """Check whether pid exists in the current process table.""" + if pid == 0: + # According to "man 2 kill" PID 0 has a special meaning: + # it refers to <> so we don't want to go any further. + # If we get here it means this UNIX platform *does* have + # a process with id 0. + return True + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + # EPERM clearly means there's a process to deny access to + return True + # According to "man 2 kill" possible error values are + # (EINVAL, EPERM, ESRCH) + else: + return True + + +def wait_pid(pid, timeout=None, proc_name=None): + """Wait for process with pid 'pid' to terminate and return its + exit status code as an integer. + + If pid is not a children of os.getpid() (current process) just + waits until the process disappears and return None. + + If pid does not exist at all return None immediately. + + Raise TimeoutExpired on timeout expired. + """ + def check_timeout(delay): + if timeout is not None: + if timer() >= stop_at: + raise TimeoutExpired(timeout, pid=pid, name=proc_name) + time.sleep(delay) + return min(delay * 2, 0.04) + + timer = getattr(time, 'monotonic', time.time) + if timeout is not None: + def waitcall(): + return os.waitpid(pid, os.WNOHANG) + stop_at = timer() + timeout + else: + def waitcall(): + return os.waitpid(pid, 0) + + delay = 0.0001 + while True: + try: + retpid, status = waitcall() + except InterruptedError: + delay = check_timeout(delay) + except ChildProcessError: + # This has two meanings: + # - pid is not a child of os.getpid() in which case + # we keep polling until it's gone + # - pid never existed in the first place + # In both cases we'll eventually return None as we + # can't determine its exit status code. + while True: + if pid_exists(pid): + delay = check_timeout(delay) + else: + return + else: + if retpid == 0: + # WNOHANG was used, pid is still running + delay = check_timeout(delay) + continue + # process exited due to a signal; return the integer of + # that signal + if os.WIFSIGNALED(status): + return -os.WTERMSIG(status) + # process exited using exit(2) system call; return the + # integer exit(2) system call has been called with + elif os.WIFEXITED(status): + return os.WEXITSTATUS(status) + else: + # should never happen + raise ValueError("unknown process exit status %r" % status) + + +def disk_usage(path): + """Return disk usage associated with path. + Note: UNIX usually reserves 5% disk space which is not accessible + by user. In this function "total" and "used" values reflect the + total and used disk space whereas "free" and "percent" represent + the "free" and "used percent" user disk space. + """ + if PY3: + st = os.statvfs(path) + else: + # os.statvfs() does not support unicode on Python 2: + # - https://github.com/giampaolo/psutil/issues/416 + # - http://bugs.python.org/issue18695 + try: + st = os.statvfs(path) + except UnicodeEncodeError: + if isinstance(path, unicode): + try: + path = path.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + pass + st = os.statvfs(path) + else: + raise + + # Total space which is only available to root (unless changed + # at system level). + total = (st.f_blocks * st.f_frsize) + # Remaining free space usable by root. + avail_to_root = (st.f_bfree * st.f_frsize) + # Remaining free space usable by user. + avail_to_user = (st.f_bavail * st.f_frsize) + # Total space being used in general. + used = (total - avail_to_root) + # Total space which is available to user (same as 'total' but + # for the user). + total_user = used + avail_to_user + # User usage percent compared to the total amount of space + # the user can use. This number would be higher if compared + # to root's because the user has less space (usually -5%). + usage_percent_user = usage_percent(used, total_user, round_=1) + + # NB: the percentage is -5% than what shown by df due to + # reserved blocks that we are currently not considering: + # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462 + return sdiskusage( + total=total, used=used, free=avail_to_user, percent=usage_percent_user) + + +@memoize +def get_terminal_map(): + """Get a map of device-id -> path as a dict. + Used by Process.terminal() + """ + ret = {} + ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') + for name in ls: + assert name not in ret, name + try: + ret[os.stat(name).st_rdev] = name + except FileNotFoundError: + pass + return ret diff --git a/ddtrace/vendor/psutil/_pssunos.py b/ddtrace/vendor/psutil/_pssunos.py new file mode 100644 index 00000000000..2aa2a86615d --- /dev/null +++ b/ddtrace/vendor/psutil/_pssunos.py @@ -0,0 +1,720 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Sun OS Solaris platform implementation.""" + +import errno +import functools +import os +import socket +import subprocess +import sys +from collections import namedtuple +from socket import AF_INET + +from . import _common +from . import _psposix +from . import _psutil_posix as cext_posix +from . import _psutil_sunos as cext +from ._common import AF_INET6 +from ._common import get_procfs_path +from ._common import isfile_strict +from ._common import memoize_when_activated +from ._common import sockfam_to_enum +from ._common import socktype_to_enum +from ._common import usage_percent +from ._compat import b +from ._compat import FileNotFoundError +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import PY3 + + +__extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"] + + +# ===================================================================== +# --- globals +# ===================================================================== + + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK +IS_64_BIT = sys.maxsize > 2**32 + +CONN_IDLE = "IDLE" +CONN_BOUND = "BOUND" + +PROC_STATUSES = { + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SIDL: _common.STATUS_IDLE, + cext.SONPROC: _common.STATUS_RUNNING, # same as run + cext.SWAIT: _common.STATUS_WAITING, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, + cext.TCPS_IDLE: CONN_IDLE, # sunos specific + cext.TCPS_BOUND: CONN_BOUND, # sunos specific +} + +proc_info_map = dict( + ppid=0, + rss=1, + vms=2, + create_time=3, + nice=4, + num_threads=5, + status=6, + ttynr=7, + uid=8, + euid=9, + gid=10, + egid=11) + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.cpu_times() +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +# psutil.cpu_times(percpu=True) +pcputimes = namedtuple('pcputimes', + ['user', 'system', 'children_user', 'children_system']) +# psutil.virtual_memory() +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +pfullmem = pmem +# psutil.Process.memory_maps(grouped=True) +pmmap_grouped = namedtuple('pmmap_grouped', + ['path', 'rss', 'anonymous', 'locked']) +# psutil.Process.memory_maps(grouped=False) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + """Report virtual memory metrics.""" + # we could have done this with kstat, but IMHO this is good enough + total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE + # note: there's no difference on Solaris + free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE + used = total - free + percent = usage_percent(used, total, round_=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + """Report swap memory metrics.""" + sin, sout = cext.swap_mem() + # XXX + # we are supposed to get total/free by doing so: + # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/ + # usr/src/cmd/swap/swap.c + # ...nevertheless I can't manage to obtain the same numbers as 'swap' + # cmdline utility, so let's parse its output (sigh!) + p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' % + os.environ['PATH'], 'swap', '-l'], + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout = stdout.decode(sys.stdout.encoding) + if p.returncode != 0: + raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode) + + lines = stdout.strip().split('\n')[1:] + if not lines: + raise RuntimeError('no swap device(s) configured') + total = free = 0 + for line in lines: + line = line.split() + t, f = line[-2:] + total += int(int(t) * 512) + free += int(int(f) * 512) + used = total - free + percent = usage_percent(used, total, round_=1) + return _common.sswap(total, used, free, percent, + sin * PAGE_SIZE, sout * PAGE_SIZE) + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system-wide CPU times as a named tuple""" + ret = cext.per_cpu_times() + return scputimes(*[sum(x) for x in zip(*ret)]) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def cpu_stats(): + """Return various CPU stats as a named tuple.""" + ctx_switches, interrupts, syscalls, traps = cext.cpu_stats() + soft_interrupts = 0 + return _common.scpustats(ctx_switches, interrupts, soft_interrupts, + syscalls) + + +# ===================================================================== +# --- disks +# ===================================================================== + + +disk_io_counters = cext.disk_io_counters +disk_usage = _psposix.disk_usage + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +# ===================================================================== +# --- network +# ===================================================================== + + +net_io_counters = cext.net_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + Only INET sockets are returned (UNIX are not). + """ + cmap = _common.conn_tmap.copy() + if _pid == -1: + cmap.pop('unix', 0) + if kind not in cmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid) + ret = set() + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + # TODO: refactor and use _common.conn_to_ntuple. + if fam in (AF_INET, AF_INET6): + if laddr: + laddr = _common.addr(*laddr) + if raddr: + raddr = _common.addr(*raddr) + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if _pid == -1: + nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type_, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = cext.net_if_stats() + for name, items in ret.items(): + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process, pid = item + # note: the underlying C function includes entries about + # system boot, run level and others. We might want + # to use them in the future. + if not user_process: + continue + if hostname in localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp, pid) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- processes +# ===================================================================== + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except (FileNotFoundError, ProcessLookupError): + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + except PermissionError: + raise AccessDenied(self.pid, self._name) + except OSError: + if self.pid == 0: + if 0 in pids(): + raise AccessDenied(self.pid, self._name) + else: + raise + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + self._procfs_path = get_procfs_path() + + def _assert_alive(self): + """Raise NSP if the process disappeared on us.""" + # For those C function who do not raise NSP, possibly returning + # incorrect or incomplete result. + os.stat('%s/%s' % (self._procfs_path, self.pid)) + + def oneshot_enter(self): + self._proc_name_and_args.cache_activate(self) + self._proc_basic_info.cache_activate(self) + self._proc_cred.cache_activate(self) + + def oneshot_exit(self): + self._proc_name_and_args.cache_deactivate(self) + self._proc_basic_info.cache_deactivate(self) + self._proc_cred.cache_deactivate(self) + + @wrap_exceptions + @memoize_when_activated + def _proc_name_and_args(self): + return cext.proc_name_and_args(self.pid, self._procfs_path) + + @wrap_exceptions + @memoize_when_activated + def _proc_basic_info(self): + ret = cext.proc_basic_info(self.pid, self._procfs_path) + assert len(ret) == len(proc_info_map) + return ret + + @wrap_exceptions + @memoize_when_activated + def _proc_cred(self): + return cext.proc_cred(self.pid, self._procfs_path) + + @wrap_exceptions + def name(self): + # note: max len == 15 + return self._proc_name_and_args()[0] + + @wrap_exceptions + def exe(self): + try: + return os.readlink( + "%s/%s/path/a.out" % (self._procfs_path, self.pid)) + except OSError: + pass # continue and guess the exe name from the cmdline + # Will be guessed later from cmdline but we want to explicitly + # invoke cmdline here in order to get an AccessDenied + # exception if the user has not enough privileges. + self.cmdline() + return "" + + @wrap_exceptions + def cmdline(self): + return self._proc_name_and_args()[1].split(' ') + + @wrap_exceptions + def environ(self): + return cext.proc_environ(self.pid, self._procfs_path) + + @wrap_exceptions + def create_time(self): + return self._proc_basic_info()[proc_info_map['create_time']] + + @wrap_exceptions + def num_threads(self): + return self._proc_basic_info()[proc_info_map['num_threads']] + + @wrap_exceptions + def nice_get(self): + # Note #1: getpriority(3) doesn't work for realtime processes. + # Psinfo is what ps uses, see: + # https://github.com/giampaolo/psutil/issues/1194 + return self._proc_basic_info()[proc_info_map['nice']] + + @wrap_exceptions + def nice_set(self, value): + if self.pid in (2, 3): + # Special case PIDs: internally setpriority(3) return ESRCH + # (no such process), no matter what. + # The process actually exists though, as it has a name, + # creation time, etc. + raise AccessDenied(self.pid, self._name) + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + self._ppid = self._proc_basic_info()[proc_info_map['ppid']] + return self._ppid + + @wrap_exceptions + def uids(self): + try: + real, effective, saved, _, _, _ = self._proc_cred() + except AccessDenied: + real = self._proc_basic_info()[proc_info_map['uid']] + effective = self._proc_basic_info()[proc_info_map['euid']] + saved = None + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + try: + _, _, _, real, effective, saved = self._proc_cred() + except AccessDenied: + real = self._proc_basic_info()[proc_info_map['gid']] + effective = self._proc_basic_info()[proc_info_map['egid']] + saved = None + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + try: + times = cext.proc_cpu_times(self.pid, self._procfs_path) + except OSError as err: + if err.errno == errno.EOVERFLOW and not IS_64_BIT: + # We may get here if we attempt to query a 64bit process + # with a 32bit python. + # Error originates from read() and also tools like "cat" + # fail in the same way (!). + # Since there simply is no way to determine CPU times we + # return 0.0 as a fallback. See: + # https://github.com/giampaolo/psutil/issues/857 + times = (0.0, 0.0, 0.0, 0.0) + else: + raise + return _common.pcputimes(*times) + + @wrap_exceptions + def cpu_num(self): + return cext.proc_cpu_num(self.pid, self._procfs_path) + + @wrap_exceptions + def terminal(self): + procfs_path = self._procfs_path + hit_enoent = False + tty = wrap_exceptions( + self._proc_basic_info()[proc_info_map['ttynr']]) + if tty != cext.PRNODEV: + for x in (0, 1, 2, 255): + try: + return os.readlink( + '%s/%d/path/%d' % (procfs_path, self.pid, x)) + except FileNotFoundError: + hit_enoent = True + continue + if hit_enoent: + self._assert_alive() + + @wrap_exceptions + def cwd(self): + # /proc/PID/path/cwd may not be resolved by readlink() even if + # it exists (ls shows it). If that's the case and the process + # is still alive return None (we can return None also on BSD). + # Reference: http://goo.gl/55XgO + procfs_path = self._procfs_path + try: + return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid)) + except FileNotFoundError: + os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD + return None + + @wrap_exceptions + def memory_info(self): + ret = self._proc_basic_info() + rss = ret[proc_info_map['rss']] * 1024 + vms = ret[proc_info_map['vms']] * 1024 + return pmem(rss, vms) + + memory_full_info = memory_info + + @wrap_exceptions + def status(self): + code = self._proc_basic_info()[proc_info_map['status']] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + procfs_path = self._procfs_path + ret = [] + tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid)) + hit_enoent = False + for tid in tids: + tid = int(tid) + try: + utime, stime = cext.query_process_thread( + self.pid, tid, procfs_path) + except EnvironmentError as err: + if err.errno == errno.EOVERFLOW and not IS_64_BIT: + # We may get here if we attempt to query a 64bit process + # with a 32bit python. + # Error originates from read() and also tools like "cat" + # fail in the same way (!). + # Since there simply is no way to determine CPU times we + # return 0.0 as a fallback. See: + # https://github.com/giampaolo/psutil/issues/857 + continue + # ENOENT == thread gone in meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + nt = _common.pthread(tid, utime, stime) + ret.append(nt) + if hit_enoent: + self._assert_alive() + return ret + + @wrap_exceptions + def open_files(self): + retlist = [] + hit_enoent = False + procfs_path = self._procfs_path + pathdir = '%s/%d/path' % (procfs_path, self.pid) + for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)): + path = os.path.join(pathdir, fd) + if os.path.islink(path): + try: + file = os.readlink(path) + except FileNotFoundError: + hit_enoent = True + continue + else: + if isfile_strict(file): + retlist.append(_common.popenfile(file, int(fd))) + if hit_enoent: + self._assert_alive() + return retlist + + def _get_unix_sockets(self, pid): + """Get UNIX sockets used by process by parsing 'pfiles' output.""" + # TODO: rewrite this in C (...but the damn netstat source code + # does not include this part! Argh!!) + cmd = "pfiles %s" % pid + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout, stderr = [x.decode(sys.stdout.encoding) + for x in (stdout, stderr)] + if p.returncode != 0: + if 'permission denied' in stderr.lower(): + raise AccessDenied(self.pid, self._name) + if 'no such process' in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + + lines = stdout.split('\n')[2:] + for i, line in enumerate(lines): + line = line.lstrip() + if line.startswith('sockname: AF_UNIX'): + path = line.split(' ', 2)[2] + type = lines[i - 2].strip() + if type == 'SOCK_STREAM': + type = socket.SOCK_STREAM + elif type == 'SOCK_DGRAM': + type = socket.SOCK_DGRAM + else: + type = -1 + yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE) + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + # will raise NSP if process is gone + os.stat('%s/%s' % (self._procfs_path, self.pid)) + + # UNIX sockets + if kind in ('all', 'unix'): + ret.extend([_common.pconn(*conn) for conn in + self._get_unix_sockets(self.pid)]) + return ret + + nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked') + nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked') + + @wrap_exceptions + def memory_maps(self): + def toaddr(start, end): + return '%s-%s' % (hex(start)[2:].strip('L'), + hex(end)[2:].strip('L')) + + procfs_path = self._procfs_path + retlist = [] + try: + rawlist = cext.proc_memory_maps(self.pid, procfs_path) + except OSError as err: + if err.errno == errno.EOVERFLOW and not IS_64_BIT: + # We may get here if we attempt to query a 64bit process + # with a 32bit python. + # Error originates from read() and also tools like "cat" + # fail in the same way (!). + # Since there simply is no way to determine CPU times we + # return 0.0 as a fallback. See: + # https://github.com/giampaolo/psutil/issues/857 + return [] + else: + raise + hit_enoent = False + for item in rawlist: + addr, addrsize, perm, name, rss, anon, locked = item + addr = toaddr(addr, addrsize) + if not name.startswith('['): + try: + name = os.readlink( + '%s/%s/path/%s' % (procfs_path, self.pid, name)) + except OSError as err: + if err.errno == errno.ENOENT: + # sometimes the link may not be resolved by + # readlink() even if it exists (ls shows it). + # If that's the case we just return the + # unresolved link path. + # This seems an incosistency with /proc similar + # to: http://goo.gl/55XgO + name = '%s/%s/path/%s' % (procfs_path, self.pid, name) + hit_enoent = True + else: + raise + retlist.append((addr, perm, name, rss, anon, locked)) + if hit_enoent: + self._assert_alive() + return retlist + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw( + *cext.proc_num_ctx_switches(self.pid, self._procfs_path)) + + @wrap_exceptions + def wait(self, timeout=None): + return _psposix.wait_pid(self.pid, timeout, self._name) diff --git a/ddtrace/vendor/psutil/_psutil_aix.c b/ddtrace/vendor/psutil/_psutil_aix.c new file mode 100644 index 00000000000..9f58f606b0c --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_aix.c @@ -0,0 +1,1137 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola' + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * AIX support is experimental at this time. + * The following functions and methods are unsupported on the AIX platform: + * - psutil.Process.memory_maps + * + * Known limitations: + * - psutil.Process.io_counters read count is always 0 + * - psutil.Process.io_counters may not be available on older AIX versions + * - psutil.Process.threads may not be available on older AIX versions + # - psutil.net_io_counters may not be available on older AIX versions + * - reading basic process info may fail or return incorrect values when + * process is starting (see IBM APAR IV58499 - fixed in newer AIX versions) + * - sockets and pipes may not be counted in num_fds (fixed in newer AIX + * versions) + * + * Useful resources: + * - proc filesystem: http://www-01.ibm.com/support/knowledgecenter/ + * ssw_aix_72/com.ibm.aix.files/proc.htm + * - libperfstat: http://www-01.ibm.com/support/knowledgecenter/ + * ssw_aix_72/com.ibm.aix.files/libperfstat.h.htm + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arch/aix/ifaddrs.h" +#include "arch/aix/net_connections.h" +#include "arch/aix/common.h" +#include "_psutil_common.h" +#include "_psutil_posix.h" + + +#define TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) + +/* + * Read a file content and fills a C structure with it. + */ +int +psutil_file_to_struct(char *path, void *fstruct, size_t size) { + int fd; + size_t nbytes; + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return 0; + } + nbytes = read(fd, fstruct, size); + if (nbytes <= 0) { + close(fd); + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + if (nbytes != size) { + close(fd); + PyErr_SetString(PyExc_RuntimeError, "structure size mismatch"); + return 0; + } + close(fd); + return nbytes; +} + + +/* + * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty + * as a Python tuple. + */ +static PyObject * +psutil_proc_basic_info(PyObject *self, PyObject *args) { + int pid; + char path[100]; + psinfo_t info; + pstatus_t status; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + if (info.pr_nlwp == 0 && info.pr_lwp.pr_lwpid == 0) { + // From the /proc docs: "If the process is a zombie, the pr_nlwp + // and pr_lwp.pr_lwpid flags are zero." + status.pr_stat = SZOMB; + } else if (info.pr_flag & SEXIT) { + // "exiting" processes don't have /proc//status + // There are other "exiting" processes that 'ps' shows as "active" + status.pr_stat = SACTIVE; + } else { + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&status, sizeof(status))) + return NULL; + } + + return Py_BuildValue("KKKdiiiK", + (unsigned long long) info.pr_ppid, // parent pid + (unsigned long long) info.pr_rssize, // rss + (unsigned long long) info.pr_size, // vms + TV2DOUBLE(info.pr_start), // create time + (int) info.pr_lwp.pr_nice, // nice + (int) info.pr_nlwp, // no. of threads + (int) status.pr_stat, // status code + (unsigned long long)info.pr_ttydev // tty nr + ); +} + + +/* + * Return process name as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + int pid; + char path[100]; + psinfo_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + return PyUnicode_DecodeFSDefaultAndSize(info.pr_fname, PRFNSZ); +} + + +/* + * Return process command line arguments as a Python list + */ +static PyObject * +psutil_proc_args(PyObject *self, PyObject *args) { + int pid; + PyObject *py_retlist = PyList_New(0); + PyObject *py_arg = NULL; + struct procsinfo procbuf; + long arg_max; + char *argbuf = NULL; + char *curarg = NULL; + int ret; + + if (py_retlist == NULL) + return NULL; + if (!PyArg_ParseTuple(args, "i", &pid)) + goto error; + arg_max = sysconf(_SC_ARG_MAX); + argbuf = malloc(arg_max); + if (argbuf == NULL) { + PyErr_NoMemory(); + goto error; + } + + procbuf.pi_pid = pid; + ret = getargs(&procbuf, sizeof(procbuf), argbuf, ARG_MAX); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + curarg = argbuf; + /* getargs will always append an extra NULL to end the arg list, + * even if the buffer is not big enough (even though it is supposed + * to be) so the following 'while' is safe */ + while (*curarg != '\0') { + py_arg = PyUnicode_DecodeFSDefault(curarg); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + curarg = strchr(curarg, '\0') + 1; + } + + free(argbuf); + + return py_retlist; + +error: + if (argbuf != NULL) + free(argbuf); + Py_XDECREF(py_retlist); + Py_XDECREF(py_arg); + return NULL; +} + + +/* + * Return process environment variables as a Python dict + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + int pid; + PyObject *py_retdict = PyDict_New(); + PyObject *py_key = NULL; + PyObject *py_val = NULL; + struct procsinfo procbuf; + long env_max; + char *envbuf = NULL; + char *curvar = NULL; + char *separator = NULL; + int ret; + + if (py_retdict == NULL) + return NULL; + if (!PyArg_ParseTuple(args, "i", &pid)) + goto error; + env_max = sysconf(_SC_ARG_MAX); + envbuf = malloc(env_max); + if (envbuf == NULL) { + PyErr_NoMemory(); + goto error; + } + + procbuf.pi_pid = pid; + ret = getevars(&procbuf, sizeof(procbuf), envbuf, ARG_MAX); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + curvar = envbuf; + /* getevars will always append an extra NULL to end the arg list, + * even if the buffer is not big enough (even though it is supposed + * to be) so the following 'while' is safe */ + while (*curvar != '\0') { + separator = strchr(curvar, '='); + if (separator != NULL) { + py_key = PyUnicode_DecodeFSDefaultAndSize( + curvar, + (Py_ssize_t)(separator - curvar) + ); + if (!py_key) + goto error; + py_val = PyUnicode_DecodeFSDefault(separator + 1); + if (!py_val) + goto error; + if (PyDict_SetItem(py_retdict, py_key, py_val)) + goto error; + Py_CLEAR(py_key); + Py_CLEAR(py_val); + } + curvar = strchr(curvar, '\0') + 1; + } + + free(envbuf); + + return py_retdict; + +error: + if (envbuf != NULL) + free(envbuf); + Py_XDECREF(py_retdict); + Py_XDECREF(py_key); + Py_XDECREF(py_val); + return NULL; +} + + +#ifdef CURR_VERSION_THREAD + +/* + * Retrieves all threads used by process returning a list of tuples + * including thread id, user time and system time. + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + perfstat_thread_t *threadt = NULL; + perfstat_id_t id; + int i, rc, thread_count; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + /* Get the count of threads */ + thread_count = perfstat_thread(NULL, NULL, sizeof(perfstat_thread_t), 0); + if (thread_count <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* Allocate enough memory */ + threadt = (perfstat_thread_t *)calloc(thread_count, + sizeof(perfstat_thread_t)); + if (threadt == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_thread(&id, threadt, sizeof(perfstat_thread_t), + thread_count); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < thread_count; i++) { + if (threadt[i].pid != pid) + continue; + + py_tuple = Py_BuildValue("Idd", + threadt[i].tid, + threadt[i].ucpu_time, + threadt[i].scpu_time); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(threadt); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (threadt != NULL) + free(threadt); + return NULL; +} + +#endif + + +#ifdef CURR_VERSION_PROCESS + +static PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) { + long pid; + int rc; + perfstat_process_t procinfo; + perfstat_id_t id; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + snprintf(id.name, sizeof(id.name), "%ld", pid); + rc = perfstat_process(&id, &procinfo, sizeof(perfstat_process_t), 1); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("(KKKK)", + procinfo.inOps, // XXX always 0 + procinfo.outOps, + procinfo.inBytes, // XXX always 0 + procinfo.outBytes); +} + +#endif + + +/* + * Return process user and system CPU times as a Python tuple. + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) { + int pid; + char path[100]; + pstatus_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + // results are more precise than os.times() + return Py_BuildValue("dddd", + TV2DOUBLE(info.pr_utime), + TV2DOUBLE(info.pr_stime), + TV2DOUBLE(info.pr_cutime), + TV2DOUBLE(info.pr_cstime)); +} + + +/* + * Return process uids/gids as a Python tuple. + */ +static PyObject * +psutil_proc_cred(PyObject *self, PyObject *args) { + int pid; + char path[100]; + prcred_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/cred", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("iiiiii", + info.pr_ruid, info.pr_euid, info.pr_suid, + info.pr_rgid, info.pr_egid, info.pr_sgid); +} + + +/* + * Return process voluntary and involuntary context switches as a Python tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { + PyObject *py_tuple = NULL; + pid32_t requested_pid; + pid32_t pid = 0; + int np = 0; + struct procentry64 *processes = (struct procentry64 *)NULL; + struct procentry64 *p; + + if (! PyArg_ParseTuple(args, "i", &requested_pid)) + return NULL; + + processes = psutil_read_process_table(&np); + if (!processes) + return NULL; + + /* Loop through processes */ + for (p = processes; np > 0; np--, p++) { + pid = p->pi_pid; + if (requested_pid != pid) + continue; + py_tuple = Py_BuildValue("LL", + (long long) p->pi_ru.ru_nvcsw, /* voluntary context switches */ + (long long) p->pi_ru.ru_nivcsw); /* involuntary */ + free(processes); + return py_tuple; + } + + /* finished iteration without finding requested pid */ + free(processes); + return NoSuchProcess(""); +} + + +/* + * Return users currently connected on the system. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmpx *ut; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + + if (py_retlist == NULL) + return NULL; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == USER_PROCESS) + py_user_proc = Py_True; + else + py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + endutxent(); + + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (ut != NULL) + endutxent(); + return NULL; +} + + +/* + * Return disk mounted partitions as a list of tuples including device, + * mount point and filesystem type. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file = NULL; + struct mntent * mt = NULL; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + file = setmntent(MNTTAB, "rb"); + if (file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + mt = getmntent(file); + while (mt != NULL) { + py_dev = PyUnicode_DecodeFSDefault(mt->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(mt->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + mt->mnt_type, // fs type + mt->mnt_opts); // options + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + mt = getmntent(file); + } + endmntent(file); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (file != NULL) + endmntent(file); + return NULL; +} + + +#if defined(CURR_VERSION_NETINTERFACE) && CURR_VERSION_NETINTERFACE >= 3 + +/* + * Return a list of tuples for network I/O statistics. + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + perfstat_netinterface_t *statp = NULL; + int tot, i; + perfstat_id_t first; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + + /* check how many perfstat_netinterface_t structures are available */ + tot = perfstat_netinterface( + NULL, NULL, sizeof(perfstat_netinterface_t), 0); + if (tot == 0) { + // no network interfaces - return empty dict + return py_retdict; + } + if (tot < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + statp = (perfstat_netinterface_t *) + malloc(tot * sizeof(perfstat_netinterface_t)); + if (statp == NULL) { + PyErr_NoMemory(); + goto error; + } + strcpy(first.name, FIRST_NETINTERFACE); + tot = perfstat_netinterface(&first, statp, + sizeof(perfstat_netinterface_t), tot); + if (tot < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < tot; i++) { + py_ifc_info = Py_BuildValue("(KKKKKKKK)", + statp[i].obytes, /* number of bytes sent on interface */ + statp[i].ibytes, /* number of bytes received on interface */ + statp[i].opackets, /* number of packets sent on interface */ + statp[i].ipackets, /* number of packets received on interface */ + statp[i].ierrors, /* number of input errors on interface */ + statp[i].oerrors, /* number of output errors on interface */ + statp[i].if_iqdrops, /* Dropped on input, this interface */ + statp[i].xmitdrops /* number of packets not transmitted */ + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, statp[i].name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + } + + free(statp); + return py_retdict; + +error: + if (statp != NULL) + free(statp); + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + return NULL; +} + +#endif + + +static PyObject* +psutil_net_if_stats(PyObject* self, PyObject* args) { + char *nic_name; + int sock = 0; + int ret; + int mtu; + struct ifreq ifr; + PyObject *py_is_up = NULL; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // is up? + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + goto error; + if ((ifr.ifr_flags & IFF_UP) != 0) + py_is_up = Py_True; + else + py_is_up = Py_False; + Py_INCREF(py_is_up); + + // MTU + ret = ioctl(sock, SIOCGIFMTU, &ifr); + if (ret == -1) + goto error; + mtu = ifr.ifr_mtu; + + close(sock); + py_retlist = Py_BuildValue("[Oi]", py_is_up, mtu); + if (!py_retlist) + goto error; + Py_DECREF(py_is_up); + return py_retlist; + +error: + Py_XDECREF(py_is_up); + if (sock != 0) + close(sock); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + float boot_time = 0.0; + struct utmpx *ut; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == BOOT_TIME) { + boot_time = (float)ut->ut_tv.tv_sec; + break; + } + } + endutxent(); + if (boot_time == 0.0) { + /* could not find BOOT_TIME in getutxent loop */ + PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); + return NULL; + } + return Py_BuildValue("f", boot_time); +} + + +/* + * Return a Python list of tuple representing per-cpu times + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + int ncpu, rc, i; + long ticks; + perfstat_cpu_t *cpu = NULL; + perfstat_id_t id; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + /* get the number of ticks per second */ + ticks = sysconf(_SC_CLK_TCK); + if (ticks < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* get the number of cpus in ncpu */ + ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0); + if (ncpu <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* allocate enough memory to hold the ncpu structures */ + cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t)); + if (cpu == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu); + + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + py_cputime = Py_BuildValue( + "(dddd)", + (double)cpu[i].user / ticks, + (double)cpu[i].sys / ticks, + (double)cpu[i].idle / ticks, + (double)cpu[i].wait / ticks); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + free(cpu); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (cpu != NULL) + free(cpu); + return NULL; +} + + +/* + * Return disk IO statistics. + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + perfstat_disk_t *diskt = NULL; + perfstat_id_t id; + int i, rc, disk_count; + + if (py_retdict == NULL) + return NULL; + + /* Get the count of disks */ + disk_count = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0); + if (disk_count <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* Allocate enough memory */ + diskt = (perfstat_disk_t *)calloc(disk_count, + sizeof(perfstat_disk_t)); + if (diskt == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, FIRST_DISK); + rc = perfstat_disk(&id, diskt, sizeof(perfstat_disk_t), + disk_count); + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < disk_count; i++) { + py_disk_info = Py_BuildValue( + "KKKKKK", + diskt[i].__rxfers, + diskt[i].xfers - diskt[i].__rxfers, + diskt[i].rblks * diskt[i].bsize, + diskt[i].wblks * diskt[i].bsize, + diskt[i].rserv / 1000 / 1000, // from nano to milli secs + diskt[i].wserv / 1000 / 1000 // from nano to milli secs + ); + if (py_disk_info == NULL) + goto error; + if (PyDict_SetItemString(py_retdict, diskt[i].name, + py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + free(diskt); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (diskt != NULL) + free(diskt); + return NULL; +} + + +/* + * Return virtual memory usage statistics. + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + int rc; + int pagesize = getpagesize(); + perfstat_memory_total_t memory; + + rc = perfstat_memory_total( + NULL, &memory, sizeof(perfstat_memory_total_t), 1); + if (rc <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKKK", + (unsigned long long) memory.real_total * pagesize, + (unsigned long long) memory.real_avail * pagesize, + (unsigned long long) memory.real_free * pagesize, + (unsigned long long) memory.real_pinned * pagesize, + (unsigned long long) memory.real_inuse * pagesize + ); +} + + +/* + * Return stats about swap memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + int rc; + int pagesize = getpagesize(); + perfstat_memory_total_t memory; + + rc = perfstat_memory_total( + NULL, &memory, sizeof(perfstat_memory_total_t), 1); + if (rc <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKK", + (unsigned long long) memory.pgsp_total * pagesize, + (unsigned long long) memory.pgsp_free * pagesize, + (unsigned long long) memory.pgins * pagesize, + (unsigned long long) memory.pgouts * pagesize + ); +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + int ncpu, rc, i; + // perfstat_cpu_total_t doesn't have invol/vol cswitch, only pswitch + // which is apparently something else. We have to sum over all cpus + perfstat_cpu_t *cpu = NULL; + perfstat_id_t id; + u_longlong_t cswitches = 0; + u_longlong_t devintrs = 0; + u_longlong_t softintrs = 0; + u_longlong_t syscall = 0; + + /* get the number of cpus in ncpu */ + ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0); + if (ncpu <= 0){ + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + /* allocate enough memory to hold the ncpu structures */ + cpu = (perfstat_cpu_t *) malloc(ncpu * sizeof(perfstat_cpu_t)); + if (cpu == NULL) { + PyErr_NoMemory(); + goto error; + } + + strcpy(id.name, ""); + rc = perfstat_cpu(&id, cpu, sizeof(perfstat_cpu_t), ncpu); + + if (rc <= 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + cswitches += cpu[i].invol_cswitch + cpu[i].vol_cswitch; + devintrs += cpu[i].devintrs; + softintrs += cpu[i].softintrs; + syscall += cpu[i].syscall; + } + + free(cpu); + + return Py_BuildValue( + "KKKK", + cswitches, + devintrs, + softintrs, + syscall + ); + +error: + if (cpu != NULL) + free(cpu); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- process-related functions + {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, + "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name."}, + {"proc_args", psutil_proc_args, METH_VARARGS, + "Return process command line arguments."}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment variables."}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return process user and system CPU times."}, + {"proc_cred", psutil_proc_cred, METH_VARARGS, + "Return process uids/gids."}, +#ifdef CURR_VERSION_THREAD + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads"}, +#endif +#ifdef CURR_VERSION_PROCESS + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, + "Get process I/O counters."}, +#endif + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Get process I/O counters."}, + + // --- system-related functions + {"users", psutil_users, METH_VARARGS, + "Return currently connected users."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk partitions."}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return system boot time in seconds since the EPOCH."}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O statistics."}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory usage statistics"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return stats about swap memory, in bytes"}, +#if defined(CURR_VERSION_NETINTERFACE) && CURR_VERSION_NETINTERFACE >= 3 + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return a Python dict of tuples for network I/O statistics."}, +#endif + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide connections"}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, mtu)"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_aix_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_aix_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_aix", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_aix_traverse, + psutil_aix_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_aix(void) + +#else +#define INITERROR return + +void init_psutil_aix(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_aix", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + PyModule_AddIntConstant(module, "SACTIVE", SACTIVE); + PyModule_AddIntConstant(module, "SSWAP", SSWAP); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RECEIVED); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + psutil_setup(); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} + +#ifdef __cplusplus +} +#endif diff --git a/ddtrace/vendor/psutil/_psutil_bsd.c b/ddtrace/vendor/psutil/_psutil_bsd.c new file mode 100644 index 00000000000..723d6198c35 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_bsd.c @@ -0,0 +1,1093 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil (OpenBSD). + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Platform-specific module methods for FreeBSD and OpenBSD. + + * OpenBSD references: + * - OpenBSD source code: https://github.com/openbsd/src + * + * OpenBSD / NetBSD: missing APIs compared to FreeBSD implementation: + * - psutil.net_connections() + * - psutil.Process.get/set_cpu_affinity() (not supported natively) + * - psutil.Process.memory_maps() + */ + +#if defined(PSUTIL_NETBSD) + #define _KMEMUSER +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(__NetBSD__) +#include +#endif +#include +#include +#include +#include +#include // for struct xsocket +#include +#include +// for xinpcb struct +#include +#include +#include +#include +#include +#include +#include +#include // for struct xtcpcb +#include // for TCP connection states +#include // for inet_ntop() + +#include + +#include // net io counters +#include +#include + +#include // process open files/connections +#include + +#include "_psutil_common.h" +#include "_psutil_posix.h" + +#ifdef PSUTIL_FREEBSD + #include "arch/freebsd/specific.h" + #include "arch/freebsd/sys_socks.h" + #include "arch/freebsd/proc_socks.h" + + #include + #include // get io counters + #include // process open files, shared libs (kinfo_getvmmap) + #if __FreeBSD_version < 900000 + #include // system users + #else + #include + #endif +#elif PSUTIL_OPENBSD + #include "arch/openbsd/specific.h" + + #include + #include // for VREG + #define _KERNEL // for DTYPE_VNODE + #include + #undef _KERNEL + #include // for CPUSTATES & CP_* +#elif PSUTIL_NETBSD + #include "arch/netbsd/specific.h" + #include "arch/netbsd/socks.h" + + #include + #include // for VREG + #include // for CPUSTATES & CP_* + #ifndef DTYPE_VNODE + #define DTYPE_VNODE 1 + #endif +#endif + + + +// convert a timeval struct to a double +#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + +#ifdef PSUTIL_FREEBSD + // convert a bintime struct to milliseconds + #define PSUTIL_BT2MSEC(bt) (bt.sec * 1000 + (((uint64_t) 1000000000 * \ + (uint32_t) (bt.frac >> 32) ) >> 32 ) / 1000000) +#endif + +#if defined(PSUTIL_OPENBSD) || defined (PSUTIL_NETBSD) + #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) +#endif + + +/* + * Return a Python list of all the PIDs running on the system. + */ +static PyObject * +psutil_pids(PyObject *self, PyObject *args) { + kinfo_proc *proclist = NULL; + kinfo_proc *orig_address = NULL; + size_t num_processes; + size_t idx; + PyObject *py_retlist = PyList_New(0); + PyObject *py_pid = NULL; + + if (py_retlist == NULL) + return NULL; + + // TODO: RuntimeError is inappropriate here; we could return the + // original error instead. + if (psutil_get_proc_list(&proclist, &num_processes) != 0) { + if (errno != 0) { + PyErr_SetFromErrno(PyExc_OSError); + } + else { + PyErr_SetString(PyExc_RuntimeError, + "failed to retrieve process list"); + } + goto error; + } + + if (num_processes > 0) { + orig_address = proclist; // save so we can free it after we're done + for (idx = 0; idx < num_processes; idx++) { +#ifdef PSUTIL_FREEBSD + py_pid = Py_BuildValue("i", proclist->ki_pid); +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) + py_pid = Py_BuildValue("i", proclist->p_pid); +#endif + if (!py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + proclist++; + } + free(orig_address); + } + + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * Return a Python float indicating the system boot time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + // fetch sysctl "kern.boottime" + static int request[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval boottime; + size_t len = sizeof(boottime); + + if (sysctl(request, 2, &boottime, &len, NULL, 0) == -1) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("d", (double)boottime.tv_sec); +} + + +/* + * Collect different info about a process in one shot and return + * them as a big Python tuple. + */ +static PyObject * +psutil_proc_oneshot_info(PyObject *self, PyObject *args) { + long pid; + long rss; + long vms; + long memtext; + long memdata; + long memstack; + int oncpu; + kinfo_proc kp; + long pagesize = sysconf(_SC_PAGESIZE); + char str[1000]; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + + // Process +#ifdef PSUTIL_FREEBSD + sprintf(str, "%s", kp.ki_comm); +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) + sprintf(str, "%s", kp.p_comm); +#endif + py_name = PyUnicode_DecodeFSDefault(str); + if (! py_name) { + // Likely a decoding error. We don't want to fail the whole + // operation. The python module may retry with proc_name(). + PyErr_Clear(); + py_name = Py_None; + } + // Py_INCREF(py_name); + + // Calculate memory. +#ifdef PSUTIL_FREEBSD + rss = (long)kp.ki_rssize * pagesize; + vms = (long)kp.ki_size; + memtext = (long)kp.ki_tsize * pagesize; + memdata = (long)kp.ki_dsize * pagesize; + memstack = (long)kp.ki_ssize * pagesize; +#else + rss = (long)kp.p_vm_rssize * pagesize; + #ifdef PSUTIL_OPENBSD + // VMS, this is how ps determines it on OpenBSD: + // https://github.com/openbsd/src/blob/ + // 588f7f8c69786211f2d16865c552afb91b1c7cba/bin/ps/print.c#L505 + vms = (long)(kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * pagesize; + #elif PSUTIL_NETBSD + // VMS, this is how top determines it on NetBSD: + // https://github.com/IIJ-NetBSD/netbsd-src/blob/master/external/ + // bsd/top/dist/machine/m_netbsd.c + vms = (long)kp.p_vm_msize * pagesize; + #endif + memtext = (long)kp.p_vm_tsize * pagesize; + memdata = (long)kp.p_vm_dsize * pagesize; + memstack = (long)kp.p_vm_ssize * pagesize; +#endif + +#ifdef PSUTIL_FREEBSD + // what CPU we're on; top was used as an example: + // https://svnweb.freebsd.org/base/head/usr.bin/top/machine.c? + // view=markup&pathrev=273835 + // XXX - note: for "intr" PID this is -1. + if (kp.ki_stat == SRUN && kp.ki_oncpu != NOCPU) + oncpu = kp.ki_oncpu; + else + oncpu = kp.ki_lastcpu; +#else + // On Net/OpenBSD we have kp.p_cpuid but it appears it's always + // set to KI_NOCPU. Even if it's not, ki_lastcpu does not exist + // so there's no way to determine where "sleeping" processes + // were. Not supported. + oncpu = -1; +#endif + + // Return a single big tuple with all process info. + py_retlist = Py_BuildValue( + "(lillllllidllllddddlllllbO)", +#ifdef PSUTIL_FREEBSD + // + (long)kp.ki_ppid, // (long) ppid + (int)kp.ki_stat, // (int) status + // UIDs + (long)kp.ki_ruid, // (long) real uid + (long)kp.ki_uid, // (long) effective uid + (long)kp.ki_svuid, // (long) saved uid + // GIDs + (long)kp.ki_rgid, // (long) real gid + (long)kp.ki_groups[0], // (long) effective gid + (long)kp.ki_svuid, // (long) saved gid + // + kp.ki_tdev, // (int) tty nr + PSUTIL_TV2DOUBLE(kp.ki_start), // (double) create time + // ctx switches + kp.ki_rusage.ru_nvcsw, // (long) ctx switches (voluntary) + kp.ki_rusage.ru_nivcsw, // (long) ctx switches (unvoluntary) + // IO count + kp.ki_rusage.ru_inblock, // (long) read io count + kp.ki_rusage.ru_oublock, // (long) write io count + // CPU times: convert from micro seconds to seconds. + PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_utime), // (double) user time + PSUTIL_TV2DOUBLE(kp.ki_rusage.ru_stime), // (double) sys time + PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_utime), // (double) children utime + PSUTIL_TV2DOUBLE(kp.ki_rusage_ch.ru_stime), // (double) children stime + // memory + rss, // (long) rss + vms, // (long) vms + memtext, // (long) mem text + memdata, // (long) mem data + memstack, // (long) mem stack + // others + oncpu, // (int) the CPU we are on +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) + // + (long)kp.p_ppid, // (long) ppid + (int)kp.p_stat, // (int) status + // UIDs + (long)kp.p_ruid, // (long) real uid + (long)kp.p_uid, // (long) effective uid + (long)kp.p_svuid, // (long) saved uid + // GIDs + (long)kp.p_rgid, // (long) real gid + (long)kp.p_groups[0], // (long) effective gid + (long)kp.p_svuid, // (long) saved gid + // + kp.p_tdev, // (int) tty nr + PSUTIL_KPT2DOUBLE(kp.p_ustart), // (double) create time + // ctx switches + kp.p_uru_nvcsw, // (long) ctx switches (voluntary) + kp.p_uru_nivcsw, // (long) ctx switches (unvoluntary) + // IO count + kp.p_uru_inblock, // (long) read io count + kp.p_uru_oublock, // (long) write io count + // CPU times: convert from micro seconds to seconds. + PSUTIL_KPT2DOUBLE(kp.p_uutime), // (double) user time + PSUTIL_KPT2DOUBLE(kp.p_ustime), // (double) sys time + // OpenBSD and NetBSD provide children user + system times summed + // together (no distinction). + kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0, // (double) ch utime + kp.p_uctime_sec + kp.p_uctime_usec / 1000000.0, // (double) ch stime + // memory + rss, // (long) rss + vms, // (long) vms + memtext, // (long) mem text + memdata, // (long) mem data + memstack, // (long) mem stack + // others + oncpu, // (int) the CPU we are on +#endif + py_name // (pystr) name + ); + + Py_DECREF(py_name); + return py_retlist; +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + long pid; + kinfo_proc kp; + char str[1000]; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + +#ifdef PSUTIL_FREEBSD + sprintf(str, "%s", kp.ki_comm); +#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD) + sprintf(str, "%s", kp.p_comm); +#endif + return PyUnicode_DecodeFSDefault(str); +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + py_retlist = psutil_get_cmdline(pid); + if (py_retlist == NULL) + return NULL; + return Py_BuildValue("N", py_retlist); +} + + +/* + * Return the number of logical CPUs in the system. + * XXX this could be shared with macOS + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + int mib[2]; + int ncpu; + size_t len; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", ncpu); +} + + +/* + * Return a Python tuple representing user, kernel and idle CPU times + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { +#ifdef PSUTIL_NETBSD + u_int64_t cpu_time[CPUSTATES]; +#else + long cpu_time[CPUSTATES]; +#endif + size_t size = sizeof(cpu_time); + int ret; + +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) + ret = sysctlbyname("kern.cp_time", &cpu_time, &size, NULL, 0); +#elif PSUTIL_OPENBSD + int mib[] = {CTL_KERN, KERN_CPTIME}; + ret = sysctl(mib, 2, &cpu_time, &size, NULL, 0); +#endif + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC + ); +} + + + /* + * Return files opened by process as a list of (path, fd) tuples. + * TODO: this is broken as it may report empty paths. 'procstat' + * utility has the same problem see: + * https://github.com/giampaolo/psutil/issues/595 + */ +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 800000) || PSUTIL_OPENBSD || defined(PSUTIL_NETBSD) +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + long pid; + int i; + int cnt; + int regular; + int fd; + char *path; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + kinfo_proc kipp; + PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; + +#ifdef PSUTIL_FREEBSD + regular = (kif->kf_type == KF_TYPE_VNODE) && \ + (kif->kf_vnode_type == KF_VTYPE_VREG); + fd = kif->kf_fd; + path = kif->kf_path; +#elif PSUTIL_OPENBSD + regular = (kif->f_type == DTYPE_VNODE) && (kif->v_type == VREG); + fd = kif->fd_fd; + // XXX - it appears path is not exposed in the kinfo_file struct. + path = ""; +#elif PSUTIL_NETBSD + regular = (kif->ki_ftype == DTYPE_VNODE) && (kif->ki_vtype == VREG); + fd = kif->ki_fd; + // XXX - it appears path is not exposed in the kinfo_file struct. + path = ""; +#endif + if (regular == 1) { + py_path = PyUnicode_DecodeFSDefault(path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue("(Oi)", py_path, fd); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_path); + Py_CLEAR(py_tuple); + } + } + free(freep); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +/* + * Return a list of tuples including device, mount point and fs type + * for all partitions mounted on the system. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + int num; + int i; + long len; + uint64_t flags; + char opts[200]; +#ifdef PSUTIL_NETBSD + struct statvfs *fs = NULL; +#else + struct statfs *fs = NULL; +#endif + PyObject *py_retlist = PyList_New(0); + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + + // get the number of mount points + Py_BEGIN_ALLOW_THREADS +#ifdef PSUTIL_NETBSD + num = getvfsstat(NULL, 0, MNT_NOWAIT); +#else + num = getfsstat(NULL, 0, MNT_NOWAIT); +#endif + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + len = sizeof(*fs) * num; + fs = malloc(len); + if (fs == NULL) { + PyErr_NoMemory(); + goto error; + } + + Py_BEGIN_ALLOW_THREADS +#ifdef PSUTIL_NETBSD + num = getvfsstat(fs, len, MNT_NOWAIT); +#else + num = getfsstat(fs, len, MNT_NOWAIT); +#endif + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < num; i++) { + py_tuple = NULL; + opts[0] = 0; +#ifdef PSUTIL_NETBSD + flags = fs[i].f_flag; +#else + flags = fs[i].f_flags; +#endif + + // see sys/mount.h + if (flags & MNT_RDONLY) + strlcat(opts, "ro", sizeof(opts)); + else + strlcat(opts, "rw", sizeof(opts)); + if (flags & MNT_SYNCHRONOUS) + strlcat(opts, ",sync", sizeof(opts)); + if (flags & MNT_NOEXEC) + strlcat(opts, ",noexec", sizeof(opts)); + if (flags & MNT_NOSUID) + strlcat(opts, ",nosuid", sizeof(opts)); + if (flags & MNT_ASYNC) + strlcat(opts, ",async", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); + if (flags & MNT_SOFTDEP) + strlcat(opts, ",softdep", sizeof(opts)); +#ifdef PSUTIL_FREEBSD + if (flags & MNT_UNION) + strlcat(opts, ",union", sizeof(opts)); + if (flags & MNT_SUIDDIR) + strlcat(opts, ",suiddir", sizeof(opts)); + if (flags & MNT_SOFTDEP) + strlcat(opts, ",softdep", sizeof(opts)); + if (flags & MNT_NOSYMFOLLOW) + strlcat(opts, ",nosymfollow", sizeof(opts)); + if (flags & MNT_GJOURNAL) + strlcat(opts, ",gjournal", sizeof(opts)); + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); + if (flags & MNT_ACLS) + strlcat(opts, ",acls", sizeof(opts)); + if (flags & MNT_NOCLUSTERR) + strlcat(opts, ",noclusterr", sizeof(opts)); + if (flags & MNT_NOCLUSTERW) + strlcat(opts, ",noclusterw", sizeof(opts)); + if (flags & MNT_NFS4ACLS) + strlcat(opts, ",nfs4acls", sizeof(opts)); +#elif PSUTIL_NETBSD + if (flags & MNT_NODEV) + strlcat(opts, ",nodev", sizeof(opts)); + if (flags & MNT_UNION) + strlcat(opts, ",union", sizeof(opts)); + if (flags & MNT_NOCOREDUMP) + strlcat(opts, ",nocoredump", sizeof(opts)); +#ifdef MNT_RELATIME + if (flags & MNT_RELATIME) + strlcat(opts, ",relatime", sizeof(opts)); +#endif + if (flags & MNT_IGNORE) + strlcat(opts, ",ignore", sizeof(opts)); +#ifdef MNT_DISCARD + if (flags & MNT_DISCARD) + strlcat(opts, ",discard", sizeof(opts)); +#endif +#ifdef MNT_EXTATTR + if (flags & MNT_EXTATTR) + strlcat(opts, ",extattr", sizeof(opts)); +#endif + if (flags & MNT_LOG) + strlcat(opts, ",log", sizeof(opts)); + if (flags & MNT_SYMPERM) + strlcat(opts, ",symperm", sizeof(opts)); + if (flags & MNT_NODEVMTIME) + strlcat(opts, ",nodevmtime", sizeof(opts)); +#endif + py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point + fs[i].f_fstypename, // fs type + opts); // options + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + char *buf = NULL, *lim, *next; + struct if_msghdr *ifm; + int mib[6]; + size_t len; + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + if (py_retdict == NULL) + return NULL; + + mib[0] = CTL_NET; // networking subsystem + mib[1] = PF_ROUTE; // type of information + mib[2] = 0; // protocol (IPPROTO_xxx) + mib[3] = 0; // address family + mib[4] = NET_RT_IFLIST; // operation + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + lim = buf + len; + + for (next = buf; next < lim; ) { + py_ifc_info = NULL; + ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + + if (ifm->ifm_type == RTM_IFINFO) { + struct if_msghdr *if2m = (struct if_msghdr *)ifm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); + char ifc_name[32]; + + strncpy(ifc_name, sdl->sdl_data, sdl->sdl_nlen); + ifc_name[sdl->sdl_nlen] = 0; + // XXX: ignore usbus interfaces: + // http://lists.freebsd.org/pipermail/freebsd-current/ + // 2011-October/028752.html + // 'ifconfig -a' doesn't show them, nor do we. + if (strncmp(ifc_name, "usbus", 5) == 0) + continue; + + py_ifc_info = Py_BuildValue("(kkkkkkki)", + if2m->ifm_data.ifi_obytes, + if2m->ifm_data.ifi_ibytes, + if2m->ifm_data.ifi_opackets, + if2m->ifm_data.ifi_ipackets, + if2m->ifm_data.ifi_ierrors, + if2m->ifm_data.ifi_oerrors, + if2m->ifm_data.ifi_iqdrops, +#ifdef _IFI_OQDROPS + if2m->ifm_data.ifi_oqdrops +#else + 0 +#endif + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ifc_name, py_ifc_info)) + goto error; + Py_CLEAR(py_ifc_info); + } + else { + continue; + } + } + + free(buf); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (buf != NULL) + free(buf); + return NULL; +} + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + PyObject *py_retlist = PyList_New(0); + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + +#if (defined(__FreeBSD_version) && (__FreeBSD_version < 900000)) || PSUTIL_OPENBSD + struct utmp ut; + FILE *fp; + + Py_BEGIN_ALLOW_THREADS + fp = fopen(_PATH_UTMP, "r"); + Py_END_ALLOW_THREADS + if (fp == NULL) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, _PATH_UTMP); + goto error; + } + + while (fread(&ut, sizeof(ut), 1, fp) == 1) { + if (*ut.ut_name == '\0') + continue; + py_username = PyUnicode_DecodeFSDefault(ut.ut_name); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut.ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut.ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut.ut_time, // start time +#ifdef PSUTIL_OPENBSD + -1 // process id (set to None later) +#else + ut.ut_pid // process id +#endif + ); + if (!py_tuple) { + fclose(fp); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + fclose(fp); + goto error; + } + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + + fclose(fp); +#else + struct utmpx *utx; + setutxent(); + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + py_username = PyUnicode_DecodeFSDefault(utx->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)utx->ut_tv.tv_sec, // start time +#ifdef PSUTIL_OPENBSD + -1 // process id (set to None later) +#else + utx->ut_pid // process id +#endif + ); + + if (!py_tuple) { + endutxent(); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + endutxent(); + goto error; + } + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + + endutxent(); +#endif + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef mod_methods[] = { + // --- per-process functions + + {"proc_oneshot_info", psutil_proc_oneshot_info, METH_VARARGS, + "Return multiple info about a process"}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads"}, +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Return connections opened by process"}, +#endif + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory."}, +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || PSUTIL_OPENBSD || defined(PSUTIL_NETBSD) + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, + "Return the number of file descriptors opened by this process"}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process as a list of (path, fd) tuples"}, +#endif +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) + {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS, + "Return number of threads used by process"}, +#endif +#if defined(PSUTIL_FREEBSD) + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return process pathname executable"}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return a list of tuples for every process's memory map"}, + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, + "Return process CPU affinity."}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, + "Set process CPU affinity."}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return an XML string to determine the number physical CPUs."}, +#endif + + // --- system-related functions + + {"pids", psutil_pids, METH_VARARGS, + "Returns a list of PIDs currently running on the system"}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, + "Return number of logical CPUs on the system"}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory usage statistics"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return swap mem stats"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return the system boot time expressed in seconds since the epoch."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return a list of tuples including device, mount point and " + "fs type for all partitions mounted on the system."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return dict of tuples of networks I/O information."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O information"}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, +#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD) + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide open connections."}, +#endif +#if defined(PSUTIL_FREEBSD) + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, + {"sensors_cpu_temperature", psutil_sensors_cpu_temperature, METH_VARARGS, + "Return temperature information for a given CPU core number."}, + {"cpu_frequency", psutil_cpu_freq, METH_VARARGS, + "Return frequency of a given CPU"}, +#endif + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + +#if PY_MAJOR_VERSION >= 3 + #define INITERR return NULL + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_bsd", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL + }; + + PyObject *PyInit__psutil_bsd(void) +#else /* PY_MAJOR_VERSION */ + #define INITERR return + + void init_psutil_bsd(void) +#endif /* PY_MAJOR_VERSION */ +{ + PyObject *v; +#if PY_MAJOR_VERSION >= 3 + PyObject *mod = PyModule_Create(&moduledef); +#else + PyObject *mod = Py_InitModule("_psutil_bsd", mod_methods); +#endif + if (mod == NULL) + INITERR; + + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR; + // process status constants + +#ifdef PSUTIL_FREEBSD + if (PyModule_AddIntConstant(mod, "SIDL", SIDL)) INITERR; + if (PyModule_AddIntConstant(mod, "SRUN", SRUN)) INITERR; + if (PyModule_AddIntConstant(mod, "SSLEEP", SSLEEP)) INITERR; + if (PyModule_AddIntConstant(mod, "SSTOP", SSTOP)) INITERR; + if (PyModule_AddIntConstant(mod, "SZOMB", SZOMB)) INITERR; + if (PyModule_AddIntConstant(mod, "SWAIT", SWAIT)) INITERR; + if (PyModule_AddIntConstant(mod, "SLOCK", SLOCK)) INITERR; +#elif PSUTIL_OPENBSD + if (PyModule_AddIntConstant(mod, "SIDL", SIDL)) INITERR; + if (PyModule_AddIntConstant(mod, "SRUN", SRUN)) INITERR; + if (PyModule_AddIntConstant(mod, "SSLEEP", SSLEEP)) INITERR; + if (PyModule_AddIntConstant(mod, "SSTOP", SSTOP)) INITERR; + if (PyModule_AddIntConstant(mod, "SZOMB", SZOMB)) INITERR; // unused + if (PyModule_AddIntConstant(mod, "SDEAD", SDEAD)) INITERR; + if (PyModule_AddIntConstant(mod, "SONPROC", SONPROC)) INITERR; +#elif defined(PSUTIL_NETBSD) + if (PyModule_AddIntConstant(mod, "SIDL", LSIDL)) INITERR; + if (PyModule_AddIntConstant(mod, "SRUN", LSRUN)) INITERR; + if (PyModule_AddIntConstant(mod, "SSLEEP", LSSLEEP)) INITERR; + if (PyModule_AddIntConstant(mod, "SSTOP", LSSTOP)) INITERR; + if (PyModule_AddIntConstant(mod, "SZOMB", LSZOMB)) INITERR; + if (PyModule_AddIntConstant(mod, "SDEAD", LSDEAD)) INITERR; + if (PyModule_AddIntConstant(mod, "SONPROC", LSONPROC)) INITERR; + // unique to NetBSD + if (PyModule_AddIntConstant(mod, "SSUSPENDED", LSSUSPENDED)) INITERR; +#endif + + // connection status constants + if (PyModule_AddIntConstant(mod, "TCPS_CLOSED", TCPS_CLOSED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSING", TCPS_CLOSING)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LISTEN", TCPS_LISTEN)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_ESTABLISHED", TCPS_ESTABLISHED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_SENT", TCPS_SYN_SENT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LAST_ACK", TCPS_LAST_ACK)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_TIME_WAIT", TCPS_TIME_WAIT)) + INITERR; + // PSUTIL_CONN_NONE + if (PyModule_AddIntConstant(mod, "PSUTIL_CONN_NONE", 128)) INITERR; + + psutil_setup(); + + if (mod == NULL) + INITERR; +#if PY_MAJOR_VERSION >= 3 + return mod; +#endif +} diff --git a/ddtrace/vendor/psutil/_psutil_common.c b/ddtrace/vendor/psutil/_psutil_common.c new file mode 100644 index 00000000000..c6e37bc22ed --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_common.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Routines common to all platforms. + */ + +#include +#ifdef _WIN32 +#include +#endif + +#include "_psutil_common.h" + +// Global vars. +int PSUTIL_DEBUG = 0; +int PSUTIL_TESTING = 0; + + +/* + * Backport of unicode FS APIs from Python 3. + * On Python 2 we just return a plain byte string + * which is never supposed to raise decoding errors. + * See: https://github.com/giampaolo/psutil/issues/1040 + */ +#if PY_MAJOR_VERSION < 3 +PyObject * +PyUnicode_DecodeFSDefault(char *s) { + return PyString_FromString(s); +} + + +PyObject * +PyUnicode_DecodeFSDefaultAndSize(char *s, Py_ssize_t size) { + return PyString_FromStringAndSize(s, size); +} +#endif + + +/* + * Set OSError(errno=ESRCH, strerror="No such process") Python exception. + * If msg != "" the exception message will change in accordance. + */ +PyObject * +NoSuchProcess(const char *msg) { + PyObject *exc; + exc = PyObject_CallFunction( + PyExc_OSError, "(is)", ESRCH, strlen(msg) ? msg : strerror(ESRCH)); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); + return NULL; +} + + +/* + * Same as PyErr_SetFromErrno(0) but adds the syscall to the exception + * message. + */ +PyObject * +PyErr_SetFromOSErrnoWithSyscall(const char *syscall) { + char fullmsg[1024]; + +#ifdef _WIN32 + sprintf(fullmsg, "(originated from %s)", syscall); + PyErr_SetFromWindowsErrWithFilename(GetLastError(), fullmsg); +#else + PyObject *exc; + sprintf(fullmsg, "%s (originated from %s)", strerror(errno), syscall); + exc = PyObject_CallFunction(PyExc_OSError, "(is)", errno, fullmsg); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); +#endif + return NULL; +} + + +/* + * Set OSError(errno=EACCES, strerror="Permission denied") Python exception. + * If msg != "" the exception message will change in accordance. + */ +PyObject * +AccessDenied(const char *msg) { + PyObject *exc; + exc = PyObject_CallFunction( + PyExc_OSError, "(is)", EACCES, strlen(msg) ? msg : strerror(EACCES)); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); + return NULL; +} + + +/* + * Enable testing mode. This has the same effect as setting PSUTIL_TESTING + * env var. This dual method exists because updating os.environ on + * Windows has no effect. Called on unit tests setup. + */ +PyObject * +psutil_set_testing(PyObject *self, PyObject *args) { + PSUTIL_TESTING = 1; + Py_INCREF(Py_None); + return Py_None; +} + + +/* + * Print a debug message on stderr. No-op if PSUTIL_DEBUG env var is not set. + */ +void +psutil_debug(const char* format, ...) { + va_list argptr; + if (PSUTIL_DEBUG) { + va_start(argptr, format); + fprintf(stderr, "psutil-debug> "); + vfprintf(stderr, format, argptr); + fprintf(stderr, "\n"); + va_end(argptr); + } +} + + +/* + * Called on module import on all platforms. + */ +int +psutil_setup(void) { + if (getenv("PSUTIL_DEBUG") != NULL) + PSUTIL_DEBUG = 1; + if (getenv("PSUTIL_TESTING") != NULL) + PSUTIL_TESTING = 1; + return 0; +} diff --git a/ddtrace/vendor/psutil/_psutil_common.h b/ddtrace/vendor/psutil/_psutil_common.h new file mode 100644 index 00000000000..7f58ad17382 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_common.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef PSUTIL_PSUTIL_COMMON_H +#define PSUTIL_PSUTIL_COMMON_H + +#include + +extern int PSUTIL_TESTING; +extern int PSUTIL_DEBUG; + +// a signaler for connections without an actual status +static const int PSUTIL_CONN_NONE = 128; + +#if PY_MAJOR_VERSION < 3 +PyObject* PyUnicode_DecodeFSDefault(char *s); +PyObject* PyUnicode_DecodeFSDefaultAndSize(char *s, Py_ssize_t size); +#endif + +PyObject* AccessDenied(const char *msg); +PyObject* NoSuchProcess(const char *msg); +PyObject* PyErr_SetFromOSErrnoWithSyscall(const char *syscall); + +PyObject* psutil_set_testing(PyObject *self, PyObject *args); +void psutil_debug(const char* format, ...); +int psutil_setup(void); + +#endif // PSUTIL_PSUTIL_COMMON_H diff --git a/ddtrace/vendor/psutil/_psutil_linux.c b/ddtrace/vendor/psutil/_psutil_linux.c new file mode 100644 index 00000000000..0d16eb42761 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_linux.c @@ -0,0 +1,668 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Linux-specific functions. + */ + +#ifndef _GNU_SOURCE + #define _GNU_SOURCE 1 +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// see: https://github.com/giampaolo/psutil/issues/659 +#ifdef PSUTIL_ETHTOOL_MISSING_TYPES + #include + typedef __u64 u64; + typedef __u32 u32; + typedef __u16 u16; + typedef __u8 u8; +#endif +/* Avoid redefinition of struct sysinfo with musl libc */ +#define _LINUX_SYSINFO_H +#include + +/* The minimum number of CPUs allocated in a cpu_set_t */ +static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT; + +// Linux >= 2.6.13 +#define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set) + +// Linux >= 2.6.36 (supposedly) and glibc >= 13 +#define PSUTIL_HAVE_PRLIMIT \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) && \ + (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 13) && \ + defined(__NR_prlimit64) + +#if PSUTIL_HAVE_PRLIMIT + #define _FILE_OFFSET_BITS 64 + #include + #include +#endif + +// Should exist starting from CentOS 6 (year 2011). +#ifdef CPU_ALLOC + #define PSUTIL_HAVE_CPU_AFFINITY +#endif + +#include "_psutil_common.h" +#include "_psutil_posix.h" + +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif + + +#if PSUTIL_HAVE_IOPRIO +enum { + IOPRIO_WHO_PROCESS = 1, +}; + +static inline int +ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +static inline int +ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +#define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + + +/* + * Return a (ioclass, iodata) Python tuple representing process I/O priority. + */ +static PyObject * +psutil_proc_ioprio_get(PyObject *self, PyObject *args) { + long pid; + int ioprio, ioclass, iodata; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); + if (ioprio == -1) + return PyErr_SetFromErrno(PyExc_OSError); + ioclass = IOPRIO_PRIO_CLASS(ioprio); + iodata = IOPRIO_PRIO_DATA(ioprio); + return Py_BuildValue("ii", ioclass, iodata); +} + + +/* + * A wrapper around ioprio_set(); sets process I/O priority. + * ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE + * or 0. iodata goes from 0 to 7 depending on ioclass specified. + */ +static PyObject * +psutil_proc_ioprio_set(PyObject *self, PyObject *args) { + long pid; + int ioprio, ioclass, iodata; + int retval; + + if (! PyArg_ParseTuple(args, "lii", &pid, &ioclass, &iodata)) + return NULL; + ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); + retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); + if (retval == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} +#endif + + +#if PSUTIL_HAVE_PRLIMIT +/* + * A wrapper around prlimit(2); sets process resource limits. + * This can be used for both get and set, in which case extra + * 'soft' and 'hard' args must be provided. + */ +static PyObject * +psutil_linux_prlimit(PyObject *self, PyObject *args) { + long pid; + int ret, resource; + struct rlimit old, new; + struct rlimit *newp = NULL; + PyObject *py_soft = NULL; + PyObject *py_hard = NULL; + + if (! PyArg_ParseTuple(args, "li|OO", &pid, &resource, &py_soft, &py_hard)) + return NULL; + + // get + if (py_soft == NULL && py_hard == NULL) { + ret = prlimit(pid, resource, NULL, &old); + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); +#if defined(PSUTIL_HAVE_LONG_LONG) + if (sizeof(old.rlim_cur) > sizeof(long)) { + return Py_BuildValue("LL", + (PY_LONG_LONG)old.rlim_cur, + (PY_LONG_LONG)old.rlim_max); + } +#endif + return Py_BuildValue("ll", (long)old.rlim_cur, (long)old.rlim_max); + } + + // set + else { +#if defined(PSUTIL_HAVE_LARGEFILE_SUPPORT) + new.rlim_cur = PyLong_AsLongLong(py_soft); + if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; + new.rlim_max = PyLong_AsLongLong(py_hard); + if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; +#else + new.rlim_cur = PyLong_AsLong(py_soft); + if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; + new.rlim_max = PyLong_AsLong(py_hard); + if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; +#endif + newp = &new; + ret = prlimit(pid, resource, newp, &old); + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; + } +} +#endif + + +/* + * Return disk mounted partitions as a list of tuples including device, + * mount point and filesystem type + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file = NULL; + struct mntent *entry; + const char *mtab_path; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (!PyArg_ParseTuple(args, "s", &mtab_path)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + file = setmntent(mtab_path, "r"); + Py_END_ALLOW_THREADS + if ((file == 0) || (file == NULL)) { + psutil_debug("setmntent() failed"); + PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path); + goto error; + } + + while ((entry = getmntent(file))) { + if (entry == NULL) { + PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); + goto error; + } + py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point + entry->mnt_type, // fs type + entry->mnt_opts); // options + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + endmntent(file); + return py_retlist; + +error: + if (file != NULL) + endmntent(file); + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * A wrapper around sysinfo(), return system memory usage statistics. + */ +static PyObject * +psutil_linux_sysinfo(PyObject *self, PyObject *args) { + struct sysinfo info; + + if (sysinfo(&info) != 0) + return PyErr_SetFromErrno(PyExc_OSError); + // note: boot time might also be determined from here + return Py_BuildValue( + "(kkkkkkI)", + info.totalram, // total + info.freeram, // free + info.bufferram, // buffer + info.sharedram, // shared + info.totalswap, // swap tot + info.freeswap, // swap free + info.mem_unit // multiplier + ); +} + + +/* + * Return process CPU affinity as a Python list + */ +#ifdef PSUTIL_HAVE_CPU_AFFINITY + +static PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { + int cpu, ncpus, count, cpucount_s; + long pid; + size_t setsize; + cpu_set_t *mask = NULL; + PyObject *py_list = NULL; + + if (!PyArg_ParseTuple(args, "l", &pid)) + return NULL; + ncpus = NCPUS_START; + while (1) { + setsize = CPU_ALLOC_SIZE(ncpus); + mask = CPU_ALLOC(ncpus); + if (mask == NULL) { + psutil_debug("CPU_ALLOC() failed"); + return PyErr_NoMemory(); + } + if (sched_getaffinity(pid, setsize, mask) == 0) + break; + CPU_FREE(mask); + if (errno != EINVAL) + return PyErr_SetFromErrno(PyExc_OSError); + if (ncpus > INT_MAX / 2) { + PyErr_SetString(PyExc_OverflowError, "could not allocate " + "a large enough CPU set"); + return NULL; + } + ncpus = ncpus * 2; + } + + py_list = PyList_New(0); + if (py_list == NULL) + goto error; + + cpucount_s = CPU_COUNT_S(setsize, mask); + for (cpu = 0, count = cpucount_s; count; cpu++) { + if (CPU_ISSET_S(cpu, setsize, mask)) { +#if PY_MAJOR_VERSION >= 3 + PyObject *cpu_num = PyLong_FromLong(cpu); +#else + PyObject *cpu_num = PyInt_FromLong(cpu); +#endif + if (cpu_num == NULL) + goto error; + if (PyList_Append(py_list, cpu_num)) { + Py_DECREF(cpu_num); + goto error; + } + Py_DECREF(cpu_num); + --count; + } + } + CPU_FREE(mask); + return py_list; + +error: + if (mask) + CPU_FREE(mask); + Py_XDECREF(py_list); + return NULL; +} + + +/* + * Set process CPU affinity; expects a bitmask + */ +static PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + cpu_set_t cpu_set; + size_t len; + long pid; + int i, seq_len; + PyObject *py_cpu_set; + PyObject *py_cpu_seq = NULL; + + if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set)) + return NULL; + + if (!PySequence_Check(py_cpu_set)) { + PyErr_Format(PyExc_TypeError, "sequence argument expected, got %s", + Py_TYPE(py_cpu_set)->tp_name); + goto error; + } + + py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); + if (!py_cpu_seq) + goto error; + seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); + CPU_ZERO(&cpu_set); + for (i = 0; i < seq_len; i++) { + PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); +#if PY_MAJOR_VERSION >= 3 + long value = PyLong_AsLong(item); +#else + long value = PyInt_AsLong(item); +#endif + if ((value == -1) || PyErr_Occurred()) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, "invalid CPU value"); + goto error; + } + CPU_SET(value, &cpu_set); + } + + len = sizeof(cpu_set); + if (sched_setaffinity(pid, len, &cpu_set)) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + Py_DECREF(py_cpu_seq); + Py_RETURN_NONE; + +error: + if (py_cpu_seq != NULL) + Py_DECREF(py_cpu_seq); + return NULL; +} +#endif /* PSUTIL_HAVE_CPU_AFFINITY */ + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmp *ut; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + + if (py_retlist == NULL) + return NULL; + setutent(); + while (NULL != (ut = getutent())) { + py_tuple = NULL; + py_user_proc = NULL; + if (ut->ut_type == USER_PROCESS) + py_user_proc = Py_True; + else + py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + endutent(); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + endutent(); + return NULL; +} + + +/* + * Return stats about a particular network + * interface. References: + * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject* +psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { + char *nic_name; + int sock = 0; + int ret; + int duplex; + int speed; + struct ifreq ifr; + struct ethtool_cmd ethcmd; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + return PyErr_SetFromOSErrnoWithSyscall("socket()"); + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // duplex and speed + memset(ðcmd, 0, sizeof ethcmd); + ethcmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (void *)ðcmd; + ret = ioctl(sock, SIOCETHTOOL, &ifr); + + if (ret != -1) { + duplex = ethcmd.duplex; + speed = ethcmd.speed; + } + else { + if ((errno == EOPNOTSUPP) || (errno == EINVAL)) { + // EOPNOTSUPP may occur in case of wi-fi cards. + // For EINVAL see: + // https://github.com/giampaolo/psutil/issues/797 + // #issuecomment-202999532 + duplex = DUPLEX_UNKNOWN; + speed = 0; + } + else { + PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); + goto error; + } + } + + py_retlist = Py_BuildValue("[ii]", duplex, speed); + if (!py_retlist) + goto error; + close(sock); + return py_retlist; + +error: + if (sock != -1) + close(sock); + return NULL; +} + + +/* + * Module init. + */ + +static PyMethodDef mod_methods[] = { + // --- per-process functions + +#ifdef PSUTIL_HAVE_IOPRIO + {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS, + "Get process I/O priority"}, + {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS, + "Set process I/O priority"}, +#endif +#ifdef PSUTIL_HAVE_CPU_AFFINITY + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, + "Return process CPU affinity as a Python long (the bitmask)."}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, + "Set process CPU affinity; expects a bitmask."}, +#endif + + // --- system related functions + + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk mounted partitions as a list of tuples including " + "device, mount point and filesystem type"}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, + {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, + "Return duplex and speed info about a NIC"}, + + // --- linux specific + + {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS, + "A wrapper around sysinfo(), return system memory usage statistics"}, +#if PSUTIL_HAVE_PRLIMIT + {"linux_prlimit", psutil_linux_prlimit, METH_VARARGS, + "Get or set process resource limits."}, +#endif + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +#if PY_MAJOR_VERSION >= 3 + #define INITERR return NULL + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_linux", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL + }; + + PyObject *PyInit__psutil_linux(void) +#else /* PY_MAJOR_VERSION */ + #define INITERR return + + void init_psutil_linux(void) +#endif /* PY_MAJOR_VERSION */ +{ + PyObject *v; +#if PY_MAJOR_VERSION >= 3 + PyObject *mod = PyModule_Create(&moduledef); +#else + PyObject *mod = Py_InitModule("_psutil_linux", mod_methods); +#endif + if (mod == NULL) + INITERR; + + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR; +#if PSUTIL_HAVE_PRLIMIT + if (PyModule_AddIntConstant(mod, "RLIMIT_AS", RLIMIT_AS)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_CORE", RLIMIT_CORE)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_CPU", RLIMIT_CPU)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_DATA", RLIMIT_DATA)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_FSIZE", RLIMIT_FSIZE)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_LOCKS", RLIMIT_LOCKS)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_NOFILE", RLIMIT_NOFILE)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_NPROC", RLIMIT_NPROC)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_RSS", RLIMIT_RSS)) INITERR; + if (PyModule_AddIntConstant(mod, "RLIMIT_STACK", RLIMIT_STACK)) INITERR; + +#if defined(HAVE_LONG_LONG) + if (sizeof(RLIM_INFINITY) > sizeof(long)) { + v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY); + } else +#endif + { + v = PyLong_FromLong((long) RLIM_INFINITY); + } + if (v) { + PyModule_AddObject(mod, "RLIM_INFINITY", v); + } + +#ifdef RLIMIT_MSGQUEUE + if (PyModule_AddIntConstant(mod, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE)) INITERR; +#endif +#ifdef RLIMIT_NICE + if (PyModule_AddIntConstant(mod, "RLIMIT_NICE", RLIMIT_NICE)) INITERR; +#endif +#ifdef RLIMIT_RTPRIO + if (PyModule_AddIntConstant(mod, "RLIMIT_RTPRIO", RLIMIT_RTPRIO)) INITERR; +#endif +#ifdef RLIMIT_RTTIME + if (PyModule_AddIntConstant(mod, "RLIMIT_RTTIME", RLIMIT_RTTIME)) INITERR; +#endif +#ifdef RLIMIT_SIGPENDING + if (PyModule_AddIntConstant(mod, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING)) + INITERR; +#endif +#endif + if (PyModule_AddIntConstant(mod, "DUPLEX_HALF", DUPLEX_HALF)) INITERR; + if (PyModule_AddIntConstant(mod, "DUPLEX_FULL", DUPLEX_FULL)) INITERR; + if (PyModule_AddIntConstant(mod, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN)) INITERR; + + if (mod == NULL) + INITERR; +#if PY_MAJOR_VERSION >= 3 + return mod; +#endif +} diff --git a/ddtrace/vendor/psutil/_psutil_osx.c b/ddtrace/vendor/psutil/_psutil_osx.c new file mode 100644 index 00000000000..67b7e122c5a --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_osx.c @@ -0,0 +1,1905 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * macOS platform-specific module methods. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "_psutil_common.h" +#include "_psutil_posix.h" +#include "arch/osx/process_info.h" + + +#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + +static PyObject *ZombieProcessError; + + +/* + * A wrapper around host_statistics() invoked with HOST_VM_INFO. + */ +int +psutil_sys_vminfo(vm_statistics_data_t *vmstat) { + kern_return_t ret; + mach_msg_type_number_t count = sizeof(*vmstat) / sizeof(integer_t); + mach_port_t mport = mach_host_self(); + + ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)vmstat, &count); + if (ret != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_VM_INFO) syscall failed: %s", + mach_error_string(ret)); + return 0; + } + mach_port_deallocate(mach_task_self(), mport); + return 1; +} + + +/* + * A wrapper around task_for_pid() which sucks big time: + * - it's not documented + * - errno is set only sometimes + * - sometimes errno is ENOENT (?!?) + * - for PIDs != getpid() or PIDs which are not members of the procmod + * it requires root + * As such we can only guess what the heck went wrong and fail either + * with NoSuchProcess, ZombieProcessError or giveup with AccessDenied. + * Here's some history: + * https://github.com/giampaolo/psutil/issues/1181 + * https://github.com/giampaolo/psutil/issues/1209 + * https://github.com/giampaolo/psutil/issues/1291#issuecomment-396062519 + */ +int +psutil_task_for_pid(long pid, mach_port_t *task) +{ + // See: https://github.com/giampaolo/psutil/issues/1181 + kern_return_t err = KERN_SUCCESS; + + err = task_for_pid(mach_task_self(), (pid_t)pid, task); + if (err != KERN_SUCCESS) { + if (psutil_pid_exists(pid) == 0) + NoSuchProcess("task_for_pid() failed"); + else if (psutil_is_zombie(pid) == 1) + PyErr_SetString(ZombieProcessError, "task_for_pid() failed"); + else { + psutil_debug( + "task_for_pid() failed (pid=%ld, err=%i, errno=%i, msg='%s'); " + "setting AccessDenied()", + pid, err, errno, mach_error_string(err)); + AccessDenied("task_for_pid() failed"); + } + return 1; + } + return 0; +} + + +/* + * Return a Python list of all the PIDs running on the system. + */ +static PyObject * +psutil_pids(PyObject *self, PyObject *args) { + kinfo_proc *proclist = NULL; + kinfo_proc *orig_address = NULL; + size_t num_processes; + size_t idx; + PyObject *py_pid = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (psutil_get_proc_list(&proclist, &num_processes) != 0) + goto error; + + // save the address of proclist so we can free it later + orig_address = proclist; + for (idx = 0; idx < num_processes; idx++) { + py_pid = Py_BuildValue("i", proclist->kp_proc.p_pid); + if (! py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + proclist++; + } + free(orig_address); + + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using sysctl() and filling up a kinfo_proc struct. + * It should be possible to do this for all processes without + * incurring into permission (EPERM) errors. + * This will also succeed for zombie processes returning correct + * information. + */ +static PyObject * +psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + + py_name = PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); + if (! py_name) { + // Likely a decoding error. We don't want to fail the whole + // operation. The python module may retry with proc_name(). + PyErr_Clear(); + py_name = Py_None; + } + + py_retlist = Py_BuildValue( + "lllllllidiO", + (long)kp.kp_eproc.e_ppid, // (long) ppid + (long)kp.kp_eproc.e_pcred.p_ruid, // (long) real uid + (long)kp.kp_eproc.e_ucred.cr_uid, // (long) effective uid + (long)kp.kp_eproc.e_pcred.p_svuid, // (long) saved uid + (long)kp.kp_eproc.e_pcred.p_rgid, // (long) real gid + (long)kp.kp_eproc.e_ucred.cr_groups[0], // (long) effective gid + (long)kp.kp_eproc.e_pcred.p_svgid, // (long) saved gid + kp.kp_eproc.e_tdev, // (int) tty nr + PSUTIL_TV2DOUBLE(kp.kp_proc.p_starttime), // (double) create time + (int)kp.kp_proc.p_stat, // (int) status + py_name // (pystr) name + ); + + if (py_retlist != NULL) { + // XXX shall we decref() also in case of Py_BuildValue() error? + Py_DECREF(py_name); + } + return py_retlist; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo + * struct. + * Contrarily from proc_kinfo above this function will fail with + * EACCES for PIDs owned by another user and with ESRCH for zombie + * processes. + */ +static PyObject * +psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) { + long pid; + struct proc_taskinfo pti; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) + return NULL; + + return Py_BuildValue( + "(ddKKkkkk)", + (float)pti.pti_total_user / 1000000000.0, // (float) cpu user time + (float)pti.pti_total_system / 1000000000.0, // (float) cpu sys time + // Note about memory: determining other mem stats on macOS is a mess: + // http://www.opensource.apple.com/source/top/top-67/libtop.c?txt + // I just give up. + // struct proc_regioninfo pri; + // psutil_proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, sizeof(pri)) + pti.pti_resident_size, // (uns long long) rss + pti.pti_virtual_size, // (uns long long) vms + pti.pti_faults, // (uns long) number of page faults (pages) + pti.pti_pageins, // (uns long) number of actual pageins (pages) + pti.pti_threadnum, // (uns long) num threads + // Unvoluntary value seems not to be available; + // pti.pti_csw probably refers to the sum of the two; + // getrusage() numbers seems to confirm this theory. + pti.pti_csw // (uns long) voluntary ctx switches + ); +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); +} + + +/* + * Return process current working directory. + * Raises NSP in case of zombie process. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + long pid; + struct proc_vnodepathinfo pathinfo; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + if (psutil_proc_pidinfo( + pid, PROC_PIDVNODEPATHINFO, 0, &pathinfo, sizeof(pathinfo)) <= 0) + { + return NULL; + } + + return PyUnicode_DecodeFSDefault(pathinfo.pvi_cdir.vip_path); +} + + +/* + * Return path of the process executable. + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + long pid; + char buf[PATH_MAX]; + int ret; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + errno = 0; + ret = proc_pidpath((pid_t)pid, &buf, sizeof(buf)); + if (ret == 0) { + if (pid == 0) + AccessDenied(""); + else + psutil_raise_for_pid(pid, "proc_pidpath()"); + return NULL; + } + return PyUnicode_DecodeFSDefault(buf); +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // get the commandline, defined in arch/osx/process_info.c + py_retlist = psutil_get_cmdline(pid); + return py_retlist; +} + + +/* + * Return process environment as a Python string. + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retdict = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // get the environment block, defined in arch/osx/process_info.c + py_retdict = psutil_get_environ(pid); + return py_retdict; +} + + +/* + * Return the number of logical CPUs in the system. + * XXX this could be shared with BSD. + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + /* + int mib[2]; + int ncpu; + size_t len; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", ncpu); + */ + int num; + size_t size = sizeof(int); + + if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2)) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", num); +} + + +/* + * Return the number of physical CPUs in the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + int num; + size_t size = sizeof(int); + + if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0)) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", num); +} + + +/* + * Indicates if the given virtual address on the given architecture is in the + * shared VM region. + */ +static bool +psutil_in_shared_region(mach_vm_address_t addr, cpu_type_t type) { + mach_vm_address_t base; + mach_vm_address_t size; + + switch (type) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + default: + return false; + } + + return base <= addr && addr < (base + size); +} + + +/* + * Returns the USS (unique set size) of the process. Reference: + * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ + * nsMemoryReporterManager.cpp + */ +static PyObject * +psutil_proc_memory_uss(PyObject *self, PyObject *args) { + long pid; + size_t len; + cpu_type_t cpu_type; + size_t private_pages = 0; + mach_vm_size_t size = 0; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + kern_return_t kr; + vm_size_t page_size; + mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; + mach_port_t task = MACH_PORT_NULL; + vm_region_top_info_data_t info; + mach_port_t object_name; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + if (psutil_task_for_pid(pid, &task) != 0) + return NULL; + + len = sizeof(cpu_type); + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('sysctl.proc_cputype')"); + } + + // Roughly based on libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c + for (addr = 0; ; addr += size) { + kr = mach_vm_region( + task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, + &info_count, &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } + else if (kr != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "mach_vm_region(VM_REGION_TOP_INFO) syscall failed"); + return NULL; + } + + if (psutil_in_shared_region(addr, cpu_type) && + info.share_mode != SM_PRIVATE) { + continue; + } + + switch (info.share_mode) { +#ifdef SM_LARGE_PAGE + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. +#endif + case SM_PRIVATE: + private_pages += info.private_pages_resident; + private_pages += info.shared_pages_resident; + break; + case SM_COW: + private_pages += info.private_pages_resident; + if (info.ref_count == 1) { + // Treat copy-on-write pages as private if they only + // have one reference. + private_pages += info.shared_pages_resident; + } + break; + case SM_SHARED: + default: + break; + } + } + + mach_port_deallocate(mach_task_self(), task); + + if (host_page_size(mach_host_self(), &page_size) != KERN_SUCCESS) + page_size = PAGE_SIZE; + + return Py_BuildValue("K", private_pages * page_size); +} + + +/* + * Return system virtual memory stats. + * See: + * https://opensource.apple.com/source/system_cmds/system_cmds-790/ + * vm_stat.tproj/vm_stat.c.auto.html + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + int mib[2]; + uint64_t total; + size_t len = sizeof(total); + vm_statistics_data_t vm; + int pagesize = PAGE_SIZE; + // physical mem + mib[0] = CTL_HW; + mib[1] = HW_MEMSIZE; + + // This is also available as sysctlbyname("hw.memsize"). + if (sysctl(mib, 2, &total, &len, NULL, 0)) { + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_Format( + PyExc_RuntimeError, "sysctl(HW_MEMSIZE) syscall failed"); + return NULL; + } + + // vm + if (!psutil_sys_vminfo(&vm)) + return NULL; + + return Py_BuildValue( + "KKKKKK", + total, + (unsigned long long) vm.active_count * pagesize, // active + (unsigned long long) vm.inactive_count * pagesize, // inactive + (unsigned long long) vm.wire_count * pagesize, // wired + (unsigned long long) vm.free_count * pagesize, // free + (unsigned long long) vm.speculative_count * pagesize // speculative + ); +} + + +/* + * Return stats about swap memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + int mib[2]; + size_t size; + struct xsw_usage totals; + vm_statistics_data_t vmstat; + int pagesize = PAGE_SIZE; + + mib[0] = CTL_VM; + mib[1] = VM_SWAPUSAGE; + size = sizeof(totals); + if (sysctl(mib, 2, &totals, &size, NULL, 0) == -1) { + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_Format( + PyExc_RuntimeError, "sysctl(VM_SWAPUSAGE) syscall failed"); + return NULL; + } + if (!psutil_sys_vminfo(&vmstat)) + return NULL; + + return Py_BuildValue( + "LLLKK", + totals.xsu_total, + totals.xsu_used, + totals.xsu_avail, + (unsigned long long)vmstat.pageins * pagesize, + (unsigned long long)vmstat.pageouts * pagesize); +} + + +/* + * Return a Python tuple representing user, kernel and idle CPU times + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t error; + host_cpu_load_info_data_t r_load; + + mach_port_t host_port = mach_host_self(); + error = host_statistics(host_port, HOST_CPU_LOAD_INFO, + (host_info_t)&r_load, &count); + if (error != KERN_SUCCESS) { + return PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_CPU_LOAD_INFO) syscall failed: %s", + mach_error_string(error)); + } + mach_port_deallocate(mach_task_self(), host_port); + + return Py_BuildValue( + "(dddd)", + (double)r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK + ); +} + + +/* + * Return a Python list of tuple representing per-cpu times + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + natural_t cpu_count; + natural_t i; + processor_info_array_t info_array; + mach_msg_type_number_t info_count; + kern_return_t error; + processor_cpu_load_info_data_t *cpu_load_info = NULL; + int ret; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + mach_port_t host_port = mach_host_self(); + error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, + &cpu_count, &info_array, &info_count); + if (error != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_processor_info(PROCESSOR_CPU_LOAD_INFO) syscall failed: %s", + mach_error_string(error)); + goto error; + } + mach_port_deallocate(mach_task_self(), host_port); + + cpu_load_info = (processor_cpu_load_info_data_t *) info_array; + + for (i = 0; i < cpu_count; i++) { + py_cputime = Py_BuildValue( + "(dddd)", + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_USER] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_NICE] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_IDLE] / CLK_TCK + ); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_CLEAR(py_cputime); + } + + ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, + info_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (cpu_load_info != NULL) { + ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, + info_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + } + return NULL; +} + + +/* + * Retrieve CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + int64_t curr; + int64_t min; + int64_t max; + size_t size = sizeof(int64_t); + + if (sysctlbyname("hw.cpufrequency", &curr, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('hw.cpufrequency')"); + } + if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('hw.cpufrequency_min')"); + } + if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('hw.cpufrequency_max')"); + } + + return Py_BuildValue( + "KKK", + curr / 1000 / 1000, + min / 1000 / 1000, + max / 1000 / 1000); +} + + +/* + * Return a Python float indicating the system boot time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + // fetch sysctl "kern.boottime" + static int request[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval result; + size_t result_len = sizeof result; + time_t boot_time = 0; + + if (sysctl(request, 2, &result, &result_len, NULL, 0) == -1) + return PyErr_SetFromErrno(PyExc_OSError); + boot_time = result.tv_sec; + return Py_BuildValue("f", (float)boot_time); +} + + +/* + * Return a list of tuples including device, mount point and fs type + * for all partitions mounted on the system. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + int num; + int i; + int len; + uint64_t flags; + char opts[400]; + struct statfs *fs = NULL; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + // get the number of mount points + Py_BEGIN_ALLOW_THREADS + num = getfsstat(NULL, 0, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + len = sizeof(*fs) * num; + fs = malloc(len); + if (fs == NULL) { + PyErr_NoMemory(); + goto error; + } + + Py_BEGIN_ALLOW_THREADS + num = getfsstat(fs, len, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < num; i++) { + opts[0] = 0; + flags = fs[i].f_flags; + + // see sys/mount.h + if (flags & MNT_RDONLY) + strlcat(opts, "ro", sizeof(opts)); + else + strlcat(opts, "rw", sizeof(opts)); + if (flags & MNT_SYNCHRONOUS) + strlcat(opts, ",sync", sizeof(opts)); + if (flags & MNT_NOEXEC) + strlcat(opts, ",noexec", sizeof(opts)); + if (flags & MNT_NOSUID) + strlcat(opts, ",nosuid", sizeof(opts)); + if (flags & MNT_UNION) + strlcat(opts, ",union", sizeof(opts)); + if (flags & MNT_ASYNC) + strlcat(opts, ",async", sizeof(opts)); + if (flags & MNT_EXPORTED) + strlcat(opts, ",exported", sizeof(opts)); + if (flags & MNT_QUARANTINE) + strlcat(opts, ",quarantine", sizeof(opts)); + if (flags & MNT_LOCAL) + strlcat(opts, ",local", sizeof(opts)); + if (flags & MNT_QUOTA) + strlcat(opts, ",quota", sizeof(opts)); + if (flags & MNT_ROOTFS) + strlcat(opts, ",rootfs", sizeof(opts)); + if (flags & MNT_DOVOLFS) + strlcat(opts, ",dovolfs", sizeof(opts)); + if (flags & MNT_DONTBROWSE) + strlcat(opts, ",dontbrowse", sizeof(opts)); + if (flags & MNT_IGNORE_OWNERSHIP) + strlcat(opts, ",ignore-ownership", sizeof(opts)); + if (flags & MNT_AUTOMOUNTED) + strlcat(opts, ",automounted", sizeof(opts)); + if (flags & MNT_JOURNALED) + strlcat(opts, ",journaled", sizeof(opts)); + if (flags & MNT_NOUSERXATTR) + strlcat(opts, ",nouserxattr", sizeof(opts)); + if (flags & MNT_DEFWRITE) + strlcat(opts, ",defwrite", sizeof(opts)); + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); + if (flags & MNT_UPDATE) + strlcat(opts, ",update", sizeof(opts)); + if (flags & MNT_RELOAD) + strlcat(opts, ",reload", sizeof(opts)); + if (flags & MNT_FORCE) + strlcat(opts, ",force", sizeof(opts)); + if (flags & MNT_CMDFLAGS) + strlcat(opts, ",cmdflags", sizeof(opts)); + + py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + fs[i].f_fstypename, // fs type + opts); // options + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +/* + * Return process threads + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + long pid; + int err, ret; + kern_return_t kr; + unsigned int info_count = TASK_BASIC_INFO_COUNT; + mach_port_t task = MACH_PORT_NULL; + struct task_basic_info tasks_info; + thread_act_port_array_t thread_list = NULL; + thread_info_data_t thinfo_basic; + thread_basic_info_t basic_info_th; + mach_msg_type_number_t thread_count, thread_info_count, j; + + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + if (psutil_task_for_pid(pid, &task) != 0) + goto error; + + info_count = TASK_BASIC_INFO_COUNT; + err = task_info(task, TASK_BASIC_INFO, (task_info_t)&tasks_info, + &info_count); + if (err != KERN_SUCCESS) { + // errcode 4 is "invalid argument" (access denied) + if (err == 4) { + AccessDenied(""); + } + else { + // otherwise throw a runtime error with appropriate error code + PyErr_Format(PyExc_RuntimeError, + "task_info(TASK_BASIC_INFO) syscall failed"); + } + goto error; + } + + err = task_threads(task, &thread_list, &thread_count); + if (err != KERN_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "task_threads() syscall failed"); + goto error; + } + + for (j = 0; j < thread_count; j++) { + thread_info_count = THREAD_INFO_MAX; + kr = thread_info(thread_list[j], THREAD_BASIC_INFO, + (thread_info_t)thinfo_basic, &thread_info_count); + if (kr != KERN_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, + "thread_info(THREAD_BASIC_INFO) syscall failed"); + goto error; + } + + basic_info_th = (thread_basic_info_t)thinfo_basic; + py_tuple = Py_BuildValue( + "Iff", + j + 1, + basic_info_th->user_time.seconds + \ + (float)basic_info_th->user_time.microseconds / 1000000.0, + basic_info_th->system_time.seconds + \ + (float)basic_info_th->system_time.microseconds / 1000000.0 + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + + ret = vm_deallocate(task, (vm_address_t)thread_list, + thread_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + + mach_port_deallocate(mach_task_self(), task); + + return py_retlist; + +error: + if (task != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), task); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (thread_list != NULL) { + ret = vm_deallocate(task, (vm_address_t)thread_list, + thread_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + } + return NULL; +} + + +/* + * Return process open files as a Python tuple. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/m78fd + * - /usr/include/sys/proc_info.h + */ +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + long pid; + int pidinfo_result; + int iterations; + int i; + unsigned long nb; + + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct vnode_fdinfowithpath vi; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + pidinfo_result = psutil_proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (pidinfo_result <= 0) + goto error; + + fds_pointer = malloc(pidinfo_result); + if (fds_pointer == NULL) { + PyErr_NoMemory(); + goto error; + } + pidinfo_result = psutil_proc_pidinfo( + pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); + if (pidinfo_result <= 0) + goto error; + + iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); + + for (i = 0; i < iterations; i++) { + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) { + errno = 0; + nb = proc_pidfdinfo((pid_t)pid, + fdp_pointer->proc_fd, + PROC_PIDFDVNODEPATHINFO, + &vi, + sizeof(vi)); + + // --- errors checking + if ((nb <= 0) || nb < sizeof(vi)) { + if ((errno == ENOENT) || (errno == EBADF)) { + // no such file or directory or bad file descriptor; + // let's assume the file has been closed or removed + continue; + } + else { + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDVNODEPATHINFO)"); + goto error; + } + } + // --- /errors checking + + // --- construct python list + py_path = PyUnicode_DecodeFSDefault(vi.pvip.vip_path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue( + "(Oi)", + py_path, + (int)fdp_pointer->proc_fd); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_path); + // --- /construct python list + } + } + + free(fds_pointer); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_path); + Py_DECREF(py_retlist); + if (fds_pointer != NULL) + free(fds_pointer); + return NULL; // exception has already been set earlier +} + + +/* + * Return process TCP and UDP connections as a list of tuples. + * Raises NSP in case of zombie process. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0 + * - /usr/include/sys/proc_info.h + */ +static PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + long pid; + int pidinfo_result; + int iterations; + int i; + unsigned long nb; + + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct socket_fdinfo si; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) + goto error; + + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + if (pid == 0) + return py_retlist; + pidinfo_result = psutil_proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (pidinfo_result <= 0) + goto error; + + fds_pointer = malloc(pidinfo_result); + if (fds_pointer == NULL) { + PyErr_NoMemory(); + goto error; + } + + pidinfo_result = psutil_proc_pidinfo( + pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); + if (pidinfo_result <= 0) + goto error; + + iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); + for (i = 0; i < iterations; i++) { + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_SOCKET) { + errno = 0; + nb = proc_pidfdinfo((pid_t)pid, fdp_pointer->proc_fd, + PROC_PIDFDSOCKETINFO, &si, sizeof(si)); + + // --- errors checking + if ((nb <= 0) || (nb < sizeof(si))) { + if (errno == EBADF) { + // let's assume socket has been closed + continue; + } + else { + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDSOCKETINFO)"); + goto error; + } + } + // --- /errors checking + + // + int fd, family, type, lport, rport, state; + char lip[200], rip[200]; + int inseq; + PyObject *py_family; + PyObject *py_type; + + fd = (int)fdp_pointer->proc_fd; + family = si.psi.soi_family; + type = si.psi.soi_type; + + // apply filters + py_family = PyLong_FromLong((long)family); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + py_type = PyLong_FromLong((long)type); + inseq = PySequence_Contains(py_type_filter, py_type); + Py_DECREF(py_type); + if (inseq == 0) + continue; + + if (errno != 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + if ((family == AF_INET) || (family == AF_INET6)) { + if (family == AF_INET) { + inet_ntop(AF_INET, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_laddr.ina_46.i46a_addr4, + lip, + sizeof(lip)); + inet_ntop(AF_INET, + &si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr. \ + ina_46.i46a_addr4, + rip, + sizeof(rip)); + } + else { + inet_ntop(AF_INET6, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_laddr.ina_6, + lip, sizeof(lip)); + inet_ntop(AF_INET6, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_faddr.ina_6, + rip, sizeof(rip)); + } + + // check for inet_ntop failures + if (errno != 0) { + PyErr_SetFromOSErrnoWithSyscall("inet_ntop()"); + goto error; + } + + lport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport); + rport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport); + if (type == SOCK_STREAM) + state = (int)si.psi.soi_proto.pri_tcp.tcpsi_state; + else + state = PSUTIL_CONN_NONE; + + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + + // construct the python list + py_tuple = Py_BuildValue( + "(iiiNNi)", fd, family, type, py_laddr, py_raddr, state); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + else if (family == AF_UNIX) { + py_laddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path); + if (!py_laddr) + goto error; + py_raddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path); + if (!py_raddr) + goto error; + // construct the python list + py_tuple = Py_BuildValue( + "(iiiOOi)", + fd, family, type, + py_laddr, + py_raddr, + PSUTIL_CONN_NONE); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_laddr); + Py_CLEAR(py_raddr); + } + } + } + + free(fds_pointer); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (fds_pointer != NULL) + free(fds_pointer); + return NULL; +} + + +/* + * Return number of file descriptors opened by process. + * Raises NSP in case of zombie process. + */ +static PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int pidinfo_result; + int num; + struct proc_fdinfo *fds_pointer; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + pidinfo_result = proc_pidinfo((pid_t)pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (pidinfo_result <= 0) + return PyErr_SetFromErrno(PyExc_OSError); + + fds_pointer = malloc(pidinfo_result); + if (fds_pointer == NULL) + return PyErr_NoMemory(); + pidinfo_result = proc_pidinfo((pid_t)pid, PROC_PIDLISTFDS, 0, fds_pointer, + pidinfo_result); + if (pidinfo_result <= 0) { + free(fds_pointer); + return PyErr_SetFromErrno(PyExc_OSError); + } + + num = (pidinfo_result / PROC_PIDLISTFD_SIZE); + free(fds_pointer); + return Py_BuildValue("i", num); +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + char *buf = NULL, *lim, *next; + struct if_msghdr *ifm; + int mib[6]; + mib[0] = CTL_NET; // networking subsystem + mib[1] = PF_ROUTE; // type of information + mib[2] = 0; // protocol (IPPROTO_xxx) + mib[3] = 0; // address family + mib[4] = NET_RT_IFLIST2; // operation + mib[5] = 0; + size_t len; + PyObject *py_ifc_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + lim = buf + len; + + for (next = buf; next < lim; ) { + ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + + if (ifm->ifm_type == RTM_IFINFO2) { + py_ifc_info = NULL; + struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); + char ifc_name[32]; + + strncpy(ifc_name, sdl->sdl_data, sdl->sdl_nlen); + ifc_name[sdl->sdl_nlen] = 0; + + py_ifc_info = Py_BuildValue( + "(KKKKKKKi)", + if2m->ifm_data.ifi_obytes, + if2m->ifm_data.ifi_ibytes, + if2m->ifm_data.ifi_opackets, + if2m->ifm_data.ifi_ipackets, + if2m->ifm_data.ifi_ierrors, + if2m->ifm_data.ifi_oerrors, + if2m->ifm_data.ifi_iqdrops, + 0); // dropout not supported + + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ifc_name, py_ifc_info)) + goto error; + Py_CLEAR(py_ifc_info); + } + else { + continue; + } + } + + free(buf); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (buf != NULL) + free(buf); + return NULL; +} + + +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + CFDictionaryRef parent_dict; + CFDictionaryRef props_dict; + CFDictionaryRef stats_dict; + io_registry_entry_t parent; + io_registry_entry_t disk; + io_iterator_t disk_list; + PyObject *py_disk_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + + // Get list of disks + if (IOServiceGetMatchingServices(kIOMasterPortDefault, + IOServiceMatching(kIOMediaClass), + &disk_list) != kIOReturnSuccess) { + PyErr_SetString( + PyExc_RuntimeError, "unable to get the list of disks."); + goto error; + } + + // Iterate over disks + while ((disk = IOIteratorNext(disk_list)) != 0) { + py_disk_info = NULL; + parent_dict = NULL; + props_dict = NULL; + stats_dict = NULL; + + if (IORegistryEntryGetParentEntry(disk, kIOServicePlane, &parent) + != kIOReturnSuccess) { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the disk's parent."); + IOObjectRelease(disk); + goto error; + } + + if (IOObjectConformsTo(parent, "IOBlockStorageDriver")) { + if (IORegistryEntryCreateCFProperties( + disk, + (CFMutableDictionaryRef *) &parent_dict, + kCFAllocatorDefault, + kNilOptions + ) != kIOReturnSuccess) + { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the parent's properties."); + IOObjectRelease(disk); + IOObjectRelease(parent); + goto error; + } + + if (IORegistryEntryCreateCFProperties( + parent, + (CFMutableDictionaryRef *) &props_dict, + kCFAllocatorDefault, + kNilOptions + ) != kIOReturnSuccess) + { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the disk properties."); + CFRelease(props_dict); + IOObjectRelease(disk); + IOObjectRelease(parent); + goto error; + } + + const int kMaxDiskNameSize = 64; + CFStringRef disk_name_ref = (CFStringRef)CFDictionaryGetValue( + parent_dict, CFSTR(kIOBSDNameKey)); + char disk_name[kMaxDiskNameSize]; + + CFStringGetCString(disk_name_ref, + disk_name, + kMaxDiskNameSize, + CFStringGetSystemEncoding()); + + stats_dict = (CFDictionaryRef)CFDictionaryGetValue( + props_dict, CFSTR(kIOBlockStorageDriverStatisticsKey)); + + if (stats_dict == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Unable to get disk stats."); + goto error; + } + + CFNumberRef number; + int64_t reads = 0; + int64_t writes = 0; + int64_t read_bytes = 0; + int64_t write_bytes = 0; + int64_t read_time = 0; + int64_t write_time = 0; + + // Get disk reads/writes + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &reads); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &writes); + } + + // Get disk bytes read/written + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &read_bytes); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &write_bytes); + } + + // Get disk time spent reading/writing (nanoseconds) + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &read_time); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &write_time); + } + + // Read/Write time on macOS comes back in nanoseconds and in psutil + // we've standardized on milliseconds so do the conversion. + py_disk_info = Py_BuildValue( + "(KKKKKK)", + reads, + writes, + read_bytes, + write_bytes, + read_time / 1000 / 1000, + write_time / 1000 / 1000); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) + goto error; + Py_CLEAR(py_disk_info); + + CFRelease(parent_dict); + IOObjectRelease(parent); + CFRelease(props_dict); + IOObjectRelease(disk); + } + } + + IOObjectRelease (disk_list); + + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + return NULL; +} + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmpx *utx; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + py_username = PyUnicode_DecodeFSDefault(utx->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)utx->ut_tv.tv_sec, // start time + utx->ut_pid // process id + ); + if (!py_tuple) { + endutxent(); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + endutxent(); + goto error; + } + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + + endutxent(); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + struct vmmeter vmstat; + kern_return_t ret; + mach_msg_type_number_t count = sizeof(vmstat) / sizeof(integer_t); + mach_port_t mport = mach_host_self(); + + ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)&vmstat, &count); + if (ret != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_VM_INFO) failed: %s", + mach_error_string(ret)); + return NULL; + } + mach_port_deallocate(mach_task_self(), mport); + + return Py_BuildValue( + "IIIII", + vmstat.v_swtch, // ctx switches + vmstat.v_intr, // interrupts + vmstat.v_soft, // software interrupts + vmstat.v_syscall, // syscalls + vmstat.v_trap // traps + ); +} + + +/* + * Return battery information. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + PyObject *py_tuple = NULL; + CFTypeRef power_info = NULL; + CFArrayRef power_sources_list = NULL; + CFDictionaryRef power_sources_information = NULL; + CFNumberRef capacity_ref = NULL; + CFNumberRef time_to_empty_ref = NULL; + CFStringRef ps_state_ref = NULL; + uint32_t capacity; /* units are percent */ + int time_to_empty; /* units are minutes */ + int is_power_plugged; + + power_info = IOPSCopyPowerSourcesInfo(); + + if (!power_info) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesInfo() syscall failed"); + goto error; + } + + power_sources_list = IOPSCopyPowerSourcesList(power_info); + if (!power_sources_list) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesList() syscall failed"); + goto error; + } + + /* Should only get one source. But in practice, check for > 0 sources */ + if (!CFArrayGetCount(power_sources_list)) { + PyErr_SetString(PyExc_NotImplementedError, "no battery"); + goto error; + } + + power_sources_information = IOPSGetPowerSourceDescription( + power_info, CFArrayGetValueAtIndex(power_sources_list, 0)); + + capacity_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSCurrentCapacityKey)); + if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) { + PyErr_SetString(PyExc_RuntimeError, + "No battery capacity infomration in power sources info"); + goto error; + } + + ps_state_ref = (CFStringRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSPowerSourceStateKey)); + is_power_plugged = CFStringCompare( + ps_state_ref, CFSTR(kIOPSACPowerValue), 0) + == kCFCompareEqualTo; + + time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSTimeToEmptyKey)); + if (!CFNumberGetValue(time_to_empty_ref, + kCFNumberIntType, &time_to_empty)) { + /* This value is recommended for non-Apple power sources, so it's not + * an error if it doesn't exist. We'll return -1 for "unknown" */ + /* A value of -1 indicates "Still Calculating the Time" also for + * apple power source */ + time_to_empty = -1; + } + + py_tuple = Py_BuildValue("Iii", + capacity, time_to_empty, is_power_plugged); + if (!py_tuple) { + goto error; + } + + CFRelease(power_info); + CFRelease(power_sources_list); + /* Caller should NOT release power_sources_information */ + + return py_tuple; + +error: + if (power_info) + CFRelease(power_info); + if (power_sources_list) + CFRelease(power_sources_list); + Py_XDECREF(py_tuple); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef mod_methods[] = { + // --- per-process functions + + {"proc_kinfo_oneshot", psutil_proc_kinfo_oneshot, METH_VARARGS, + "Return multiple process info."}, + {"proc_pidtaskinfo_oneshot", psutil_proc_pidtaskinfo_oneshot, METH_VARARGS, + "Return multiple process info."}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment data"}, + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return path of the process executable"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory."}, + {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS, + "Return process USS memory"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads as a list of tuples"}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process as a list of tuples"}, + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, + "Return the number of fds opened by process."}, + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Get process TCP and UDP connections as a list of tuples"}, + + // --- system-related functions + + {"pids", psutil_pids, METH_VARARGS, + "Returns a list of PIDs currently running on the system"}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, + "Return number of logical CPUs on the system"}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return number of physical CPUs on the system"}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory stats"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return stats about swap memory, in bytes"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return cpu current frequency"}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return the system boot time expressed in seconds since the epoch."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return a list of tuples including device, mount point and " + "fs type for all partitions mounted on the system."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return dict of tuples of networks I/O information."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return dict of tuples of disks I/O information."}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery information."}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +#if PY_MAJOR_VERSION >= 3 + #define INITERR return NULL + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_osx", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL + }; + + PyObject *PyInit__psutil_osx(void) +#else /* PY_MAJOR_VERSION */ + #define INITERR return + + void init_psutil_osx(void) +#endif /* PY_MAJOR_VERSION */ +{ + PyObject *v; +#if PY_MAJOR_VERSION >= 3 + PyObject *mod = PyModule_Create(&moduledef); +#else + PyObject *mod = Py_InitModule("_psutil_osx", mod_methods); +#endif + if (mod == NULL) + INITERR; + + if (psutil_setup() != 0) + INITERR; + + if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) + INITERR; + // process status constants, defined in: + // http://fxr.watson.org/fxr/source/bsd/sys/proc.h?v=xnu-792.6.70#L149 + if (PyModule_AddIntConstant(mod, "SIDL", SIDL)) + INITERR; + if (PyModule_AddIntConstant(mod, "SRUN", SRUN)) + INITERR; + if (PyModule_AddIntConstant(mod, "SSLEEP", SSLEEP)) + INITERR; + if (PyModule_AddIntConstant(mod, "SSTOP", SSTOP)) + INITERR; + if (PyModule_AddIntConstant(mod, "SZOMB", SZOMB)) + INITERR; + // connection status constants + if (PyModule_AddIntConstant(mod, "TCPS_CLOSED", TCPS_CLOSED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSING", TCPS_CLOSING)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LISTEN", TCPS_LISTEN)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_ESTABLISHED", TCPS_ESTABLISHED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_SENT", TCPS_SYN_SENT)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_LAST_ACK", TCPS_LAST_ACK)) + INITERR; + if (PyModule_AddIntConstant(mod, "TCPS_TIME_WAIT", TCPS_TIME_WAIT)) + INITERR; + if (PyModule_AddIntConstant(mod, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE)) + INITERR; + + // Exception. + ZombieProcessError = PyErr_NewException( + "_psutil_osx.ZombieProcessError", NULL, NULL); + if (ZombieProcessError == NULL) + INITERR; + Py_INCREF(ZombieProcessError); + if (PyModule_AddObject(mod, "ZombieProcessError", ZombieProcessError)) { + Py_DECREF(ZombieProcessError); + INITERR; + } + + if (mod == NULL) + INITERR; +#if PY_MAJOR_VERSION >= 3 + return mod; +#endif +} diff --git a/ddtrace/vendor/psutil/_psutil_posix.c b/ddtrace/vendor/psutil/_psutil_posix.c new file mode 100644 index 00000000000..aa600849176 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_posix.c @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Functions specific to all POSIX compliant platforms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PSUTIL_SUNOS10 + #include "arch/solaris/v10/ifaddrs.h" +#elif PSUTIL_AIX + #include "arch/aix/ifaddrs.h" +#else + #include +#endif + +#if defined(PSUTIL_LINUX) + #include + #include + #include +#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX) + #include + #include + #include + #include + #include + #include +#elif defined(PSUTIL_SUNOS) + #include + #include +#elif defined(PSUTIL_AIX) + #include +#endif + +#include "_psutil_common.h" + +/* + * Check if PID exists. Return values: + * 1: exists + * 0: does not exist + * -1: error (Python exception is set) + */ +int +psutil_pid_exists(long pid) { + int ret; + + // No negative PID exists, plus -1 is an alias for sending signal + // too all processes except system ones. Not what we want. + if (pid < 0) + return 0; + + // As per "man 2 kill" PID 0 is an alias for sending the signal to + // every process in the process group of the calling process. + // Not what we want. Some platforms have PID 0, some do not. + // We decide that at runtime. + if (pid == 0) { +#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD) + return 0; +#else + return 1; +#endif + } + +#if defined(PSUTIL_OSX) + ret = kill((pid_t)pid , 0); +#else + ret = kill(pid , 0); +#endif + + if (ret == 0) + return 1; + else { + if (errno == ESRCH) { + // ESRCH == No such process + return 0; + } + else if (errno == EPERM) { + // EPERM clearly indicates there's a process to deny + // access to. + return 1; + } + else { + // According to "man 2 kill" possible error values are + // (EINVAL, EPERM, ESRCH) therefore we should never get + // here. If we do let's be explicit in considering this + // an error. + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + } +} + + +/* + * Utility used for those syscalls which do not return a meaningful + * error that we can translate into an exception which makes sense. + * As such, we'll have to guess. + * On UNIX, if errno is set, we return that one (OSError). + * Else, if PID does not exist we assume the syscall failed because + * of that so we raise NoSuchProcess. + * If none of this is true we giveup and raise RuntimeError(msg). + * This will always set a Python exception and return NULL. + */ +int +psutil_raise_for_pid(long pid, char *syscall_name) { + // Set exception to AccessDenied if pid exists else NoSuchProcess. + if (errno != 0) { + // Unlikely we get here. + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + else if (psutil_pid_exists(pid) == 0) { + psutil_debug("%s syscall failed and PID %i no longer exists; " + "assume NoSuchProcess", syscall_name, pid); + NoSuchProcess(""); + } + else { + PyErr_Format(PyExc_RuntimeError, "%s syscall failed", syscall_name); + } + return 0; +} + + +/* + * Given a PID return process priority as a Python integer. + */ +static PyObject * +psutil_posix_getpriority(PyObject *self, PyObject *args) { + long pid; + int priority; + errno = 0; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + +#ifdef PSUTIL_OSX + priority = getpriority(PRIO_PROCESS, (id_t)pid); +#else + priority = getpriority(PRIO_PROCESS, pid); +#endif + if (errno != 0) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("i", priority); +} + + +/* + * Given a PID and a value change process priority. + */ +static PyObject * +psutil_posix_setpriority(PyObject *self, PyObject *args) { + long pid; + int priority; + int retval; + + if (! PyArg_ParseTuple(args, "li", &pid, &priority)) + return NULL; + +#ifdef PSUTIL_OSX + retval = setpriority(PRIO_PROCESS, (id_t)pid, priority); +#else + retval = setpriority(PRIO_PROCESS, pid, priority); +#endif + if (retval == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} + + +/* + * Translate a sockaddr struct into a Python string. + * Return None if address family is not AF_INET* or AF_PACKET. + */ +static PyObject * +psutil_convert_ipaddr(struct sockaddr *addr, int family) { + char buf[NI_MAXHOST]; + int err; + int addrlen; + size_t n; + size_t len; + const char *data; + char *ptr; + + if (addr == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + else if (family == AF_INET || family == AF_INET6) { + if (family == AF_INET) + addrlen = sizeof(struct sockaddr_in); + else + addrlen = sizeof(struct sockaddr_in6); + err = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST); + if (err != 0) { + // XXX we get here on FreeBSD when processing 'lo' / AF_INET6 + // broadcast. Not sure what to do other than returning None. + // ifconfig does not show anything BTW. + //PyErr_Format(PyExc_RuntimeError, gai_strerror(err)); + //return NULL; + Py_INCREF(Py_None); + return Py_None; + } + else { + return Py_BuildValue("s", buf); + } + } +#ifdef PSUTIL_LINUX + else if (family == AF_PACKET) { + struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr; + len = lladdr->sll_halen; + data = (const char *)lladdr->sll_addr; + } +#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX) + else if (addr->sa_family == AF_LINK) { + // Note: prior to Python 3.4 socket module does not expose + // AF_LINK so we'll do. + struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr; + len = dladdr->sdl_alen; + data = LLADDR(dladdr); + } +#endif + else { + // unknown family + Py_INCREF(Py_None); + return Py_None; + } + + // AF_PACKET or AF_LINK + if (len > 0) { + ptr = buf; + for (n = 0; n < len; ++n) { + sprintf(ptr, "%02x:", data[n] & 0xff); + ptr += 3; + } + *--ptr = '\0'; + return Py_BuildValue("s", buf); + } + else { + Py_INCREF(Py_None); + return Py_None; + } +} + + +/* + * Return NICs information a-la ifconfig as a list of tuples. + * TODO: on Solaris we won't get any MAC address. + */ +static PyObject* +psutil_net_if_addrs(PyObject* self, PyObject* args) { + struct ifaddrs *ifaddr, *ifa; + int family; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_netmask = NULL; + PyObject *py_broadcast = NULL; + PyObject *py_ptp = NULL; + + if (py_retlist == NULL) + return NULL; + if (getifaddrs(&ifaddr) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + family = ifa->ifa_addr->sa_family; + py_address = psutil_convert_ipaddr(ifa->ifa_addr, family); + // If the primary address can't be determined just skip it. + // I've never seen this happen on Linux but I did on FreeBSD. + if (py_address == Py_None) + continue; + if (py_address == NULL) + goto error; + py_netmask = psutil_convert_ipaddr(ifa->ifa_netmask, family); + if (py_netmask == NULL) + goto error; + + if (ifa->ifa_flags & IFF_BROADCAST) { + py_broadcast = psutil_convert_ipaddr(ifa->ifa_broadaddr, family); + Py_INCREF(Py_None); + py_ptp = Py_None; + } + else if (ifa->ifa_flags & IFF_POINTOPOINT) { + py_ptp = psutil_convert_ipaddr(ifa->ifa_dstaddr, family); + Py_INCREF(Py_None); + py_broadcast = Py_None; + } + else { + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_broadcast = Py_None; + py_ptp = Py_None; + } + + if ((py_broadcast == NULL) || (py_ptp == NULL)) + goto error; + py_tuple = Py_BuildValue( + "(siOOOO)", + ifa->ifa_name, + family, + py_address, + py_netmask, + py_broadcast, + py_ptp + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_address); + Py_CLEAR(py_netmask); + Py_CLEAR(py_broadcast); + Py_CLEAR(py_ptp); + } + + freeifaddrs(ifaddr); + return py_retlist; + +error: + if (ifaddr != NULL) + freeifaddrs(ifaddr); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_XDECREF(py_netmask); + Py_XDECREF(py_broadcast); + Py_XDECREF(py_ptp); + return NULL; +} + + +/* + * Return NIC MTU. References: + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject * +psutil_net_if_mtu(PyObject *self, PyObject *args) { + char *nic_name; + int sock = -1; + int ret; +#ifdef PSUTIL_SUNOS10 + struct lifreq lifr; +#else + struct ifreq ifr; +#endif + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + +#ifdef PSUTIL_SUNOS10 + strncpy(lifr.lifr_name, nic_name, sizeof(lifr.lifr_name)); + ret = ioctl(sock, SIOCGIFMTU, &lifr); +#else + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + ret = ioctl(sock, SIOCGIFMTU, &ifr); +#endif + if (ret == -1) + goto error; + close(sock); + +#ifdef PSUTIL_SUNOS10 + return Py_BuildValue("i", lifr.lifr_mtu); +#else + return Py_BuildValue("i", ifr.ifr_mtu); +#endif + +error: + if (sock != -1) + close(sock); + return PyErr_SetFromErrno(PyExc_OSError); +} + + +/* + * Inspect NIC flags, returns a bool indicating whether the NIC is + * running. References: + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject * +psutil_net_if_flags(PyObject *self, PyObject *args) { + char *nic_name; + int sock = -1; + int ret; + struct ifreq ifr; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + goto error; + + close(sock); + if ((ifr.ifr_flags & IFF_UP) != 0) + return Py_BuildValue("O", Py_True); + else + return Py_BuildValue("O", Py_False); + +error: + if (sock != -1) + close(sock); + return PyErr_SetFromErrno(PyExc_OSError); +} + + +/* + * net_if_stats() macOS/BSD implementation. + */ +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) + +int psutil_get_nic_speed(int ifm_active) { + // Determine NIC speed. Taken from: + // http://www.i-scream.org/libstatgrab/ + // Assuming only ETHER devices + switch(IFM_TYPE(ifm_active)) { + case IFM_ETHER: + switch(IFM_SUBTYPE(ifm_active)) { +#if defined(IFM_HPNA_1) && ((!defined(IFM_10G_LR)) \ + || (IFM_10G_LR != IFM_HPNA_1)) + // HomePNA 1.0 (1Mb/s) + case(IFM_HPNA_1): + return 1; +#endif + // 10 Mbit + case(IFM_10_T): // 10BaseT - RJ45 + case(IFM_10_2): // 10Base2 - Thinnet + case(IFM_10_5): // 10Base5 - AUI + case(IFM_10_STP): // 10BaseT over shielded TP + case(IFM_10_FL): // 10baseFL - Fiber + return 10; + // 100 Mbit + case(IFM_100_TX): // 100BaseTX - RJ45 + case(IFM_100_FX): // 100BaseFX - Fiber + case(IFM_100_T4): // 100BaseT4 - 4 pair cat 3 + case(IFM_100_VG): // 100VG-AnyLAN + case(IFM_100_T2): // 100BaseT2 + return 100; + // 1000 Mbit + case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber + case(IFM_1000_LX): // 1000baseLX - single-mode fiber + case(IFM_1000_CX): // 1000baseCX - 150ohm STP +#if defined(IFM_1000_TX) && !defined(PSUTIL_OPENBSD) + // FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h + case(IFM_1000_TX): +#endif +#ifdef IFM_1000_FX + case(IFM_1000_FX): +#endif +#ifdef IFM_1000_T + case(IFM_1000_T): +#endif + return 1000; +#if defined(IFM_10G_SR) || defined(IFM_10G_LR) || defined(IFM_10G_CX4) \ + || defined(IFM_10G_T) +#ifdef IFM_10G_SR + case(IFM_10G_SR): +#endif +#ifdef IFM_10G_LR + case(IFM_10G_LR): +#endif +#ifdef IFM_10G_CX4 + case(IFM_10G_CX4): +#endif +#ifdef IFM_10G_TWINAX + case(IFM_10G_TWINAX): +#endif +#ifdef IFM_10G_TWINAX_LONG + case(IFM_10G_TWINAX_LONG): +#endif +#ifdef IFM_10G_T + case(IFM_10G_T): +#endif + return 10000; +#endif +#if defined(IFM_2500_SX) +#ifdef IFM_2500_SX + case(IFM_2500_SX): +#endif + return 2500; +#endif // any 2.5GBit stuff... + // We don't know what it is + default: + return 0; + } + break; + +#ifdef IFM_TOKEN + case IFM_TOKEN: + switch(IFM_SUBTYPE(ifm_active)) { + case IFM_TOK_STP4: // Shielded twisted pair 4m - DB9 + case IFM_TOK_UTP4: // Unshielded twisted pair 4m - RJ45 + return 4; + case IFM_TOK_STP16: // Shielded twisted pair 16m - DB9 + case IFM_TOK_UTP16: // Unshielded twisted pair 16m - RJ45 + return 16; +#if defined(IFM_TOK_STP100) || defined(IFM_TOK_UTP100) +#ifdef IFM_TOK_STP100 + case IFM_TOK_STP100: // Shielded twisted pair 100m - DB9 +#endif +#ifdef IFM_TOK_UTP100 + case IFM_TOK_UTP100: // Unshielded twisted pair 100m - RJ45 +#endif + return 100; +#endif + // We don't know what it is + default: + return 0; + } + break; +#endif + +#ifdef IFM_FDDI + case IFM_FDDI: + switch(IFM_SUBTYPE(ifm_active)) { + // We don't know what it is + default: + return 0; + } + break; +#endif + case IFM_IEEE80211: + switch(IFM_SUBTYPE(ifm_active)) { + case IFM_IEEE80211_FH1: // Frequency Hopping 1Mbps + case IFM_IEEE80211_DS1: // Direct Sequence 1Mbps + return 1; + case IFM_IEEE80211_FH2: // Frequency Hopping 2Mbps + case IFM_IEEE80211_DS2: // Direct Sequence 2Mbps + return 2; + case IFM_IEEE80211_DS5: // Direct Sequence 5Mbps + return 5; + case IFM_IEEE80211_DS11: // Direct Sequence 11Mbps + return 11; + case IFM_IEEE80211_DS22: // Direct Sequence 22Mbps + return 22; + // We don't know what it is + default: + return 0; + } + break; + + default: + return 0; + } +} + + +/* + * Return stats about a particular network interface. + * References: + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject * +psutil_net_if_duplex_speed(PyObject *self, PyObject *args) { + char *nic_name; + int sock = -1; + int ret; + int duplex; + int speed; + struct ifreq ifr; + struct ifmediareq ifmed; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + return PyErr_SetFromErrno(PyExc_OSError); + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // speed / duplex + memset(&ifmed, 0, sizeof(struct ifmediareq)); + strlcpy(ifmed.ifm_name, nic_name, sizeof(ifmed.ifm_name)); + ret = ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifmed); + if (ret == -1) { + speed = 0; + duplex = 0; + } + else { + speed = psutil_get_nic_speed(ifmed.ifm_active); + if ((ifmed.ifm_active | IFM_FDX) == ifmed.ifm_active) + duplex = 2; + else if ((ifmed.ifm_active | IFM_HDX) == ifmed.ifm_active) + duplex = 1; + else + duplex = 0; + } + + close(sock); + return Py_BuildValue("[ii]", duplex, speed); +} +#endif // net_if_stats() macOS/BSD implementation + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef mod_methods[] = { + {"getpriority", psutil_posix_getpriority, METH_VARARGS, + "Return process priority"}, + {"setpriority", psutil_posix_setpriority, METH_VARARGS, + "Set process priority"}, + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, + "Retrieve NICs information"}, + {"net_if_mtu", psutil_net_if_mtu, METH_VARARGS, + "Retrieve NIC MTU"}, + {"net_if_flags", psutil_net_if_flags, METH_VARARGS, + "Retrieve NIC flags"}, +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) + {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, + "Return NIC stats."}, +#endif + {NULL, NULL, 0, NULL} +}; + + +#if PY_MAJOR_VERSION >= 3 + #define INITERR return NULL + + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_psutil_posix", + NULL, + -1, + mod_methods, + NULL, + NULL, + NULL, + NULL + }; + + PyObject *PyInit__psutil_posix(void) +#else /* PY_MAJOR_VERSION */ + #define INITERR return + + void init_psutil_posix(void) +#endif /* PY_MAJOR_VERSION */ +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *mod = PyModule_Create(&moduledef); +#else + PyObject *mod = Py_InitModule("_psutil_posix", mod_methods); +#endif + if (mod == NULL) + INITERR; + +#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS) || defined(PSUTIL_AIX) + if (PyModule_AddIntConstant(mod, "AF_LINK", AF_LINK)) INITERR; +#endif + + if (mod == NULL) + INITERR; +#if PY_MAJOR_VERSION >= 3 + return mod; +#endif +} + +#ifdef __cplusplus +} +#endif diff --git a/ddtrace/vendor/psutil/_psutil_posix.h b/ddtrace/vendor/psutil/_psutil_posix.h new file mode 100644 index 00000000000..fe25b366950 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_posix.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +int psutil_pid_exists(long pid); +void psutil_raise_for_pid(long pid, char *msg); diff --git a/ddtrace/vendor/psutil/_psutil_sunos.c b/ddtrace/vendor/psutil/_psutil_sunos.c new file mode 100644 index 00000000000..31d6f364fbc --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_sunos.c @@ -0,0 +1,1776 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Functions specific to Sun OS Solaris platforms. + * + * Thanks to Justin Venus who originally wrote a consistent part of + * this in Cython which I later on translated in C. + */ + +/* fix compilation issue on SunOS 5.10, see: + * https://github.com/giampaolo/psutil/issues/421 + * https://github.com/giampaolo/psutil/issues/1077 + * http://us-east.manta.joyent.com/jmc/public/opensolaris/ARChive/PSARC/2010/111/materials/s10ceval.txt + * + * Because LEGACY_MIB_SIZE defined in the same file there is no way to make autoconfiguration =\ +*/ + +#define NEW_MIB_COMPLIANT 1 +#define _STRUCTURED_PROC 1 + +#include + +#if !defined(_LP64) && _FILE_OFFSET_BITS == 64 +# undef _FILE_OFFSET_BITS +# undef _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include +#include +#include // for MNTTAB +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // fabs() + +#include "_psutil_common.h" +#include "_psutil_posix.h" + +#include "arch/solaris/environ.h" + +#define PSUTIL_TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) + + +/* + * Read a file content and fills a C structure with it. + */ +static int +psutil_file_to_struct(char *path, void *fstruct, size_t size) { + int fd; + ssize_t nbytes; + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return 0; + } + nbytes = read(fd, fstruct, size); + if (nbytes == -1) { + close(fd); + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + if (nbytes != (ssize_t) size) { + close(fd); + PyErr_SetString( + PyExc_RuntimeError, "read() file structure size mismatch"); + return 0; + } + close(fd); + return nbytes; +} + + +/* + * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty + * as a Python tuple. + */ +static PyObject * +psutil_proc_basic_info(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + psinfo_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue( + "ikkdiiikiiii", + info.pr_ppid, // parent pid + info.pr_rssize, // rss + info.pr_size, // vms + PSUTIL_TV2DOUBLE(info.pr_start), // create time + info.pr_lwp.pr_nice, // nice + info.pr_nlwp, // no. of threads + info.pr_lwp.pr_state, // status code + info.pr_ttydev, // tty nr + (int)info.pr_uid, // real user id + (int)info.pr_euid, // effective user id + (int)info.pr_gid, // real group id + (int)info.pr_egid // effective group id + ); +} + +/* + * Join array of C strings to C string with delemiter dm. + * Omit empty records. + */ +static int +cstrings_array_to_string(char **joined, char ** array, size_t count, char dm) { + int i; + size_t total_length = 0; + size_t item_length = 0; + char *result = NULL; + char *last = NULL; + + if (!array || !joined) + return 0; + + for (i=0; i 0) { + py_args = PyUnicode_DecodeFSDefault(argv_plain); + free(argv_plain); + } else if (joined < 0) { + goto error; + } + + psutil_free_cstrings_array(argv, argc); + } + } + + /* If we can't read process memory or can't decode the result + * then return args from /proc. */ + if (!py_args) { + PyErr_Clear(); + py_args = PyUnicode_DecodeFSDefault(info.pr_psargs); + } + + /* Both methods has been failed. */ + if (!py_args) + goto error; + + py_retlist = Py_BuildValue("OO", py_name, py_args); + if (!py_retlist) + goto error; + + Py_DECREF(py_name); + Py_DECREF(py_args); + return py_retlist; + +error: + Py_XDECREF(py_name); + Py_XDECREF(py_args); + Py_XDECREF(py_retlist); + return NULL; +} + + +/* + * Return process environ block. + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + psinfo_t info; + const char *procfs_path; + char **env = NULL; + ssize_t env_count = -1; + char *dm; + int i = 0; + PyObject *py_envname = NULL; + PyObject *py_envval = NULL; + PyObject *py_retdict = PyDict_New(); + + if (! py_retdict) + return PyErr_NoMemory(); + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/psinfo", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + goto error; + + if (! info.pr_envp) { + AccessDenied(""); + goto error; + } + + env = psutil_read_raw_env(info, procfs_path, &env_count); + if (! env && env_count != 0) + goto error; + + for (i=0; i= 0) + psutil_free_cstrings_array(env, env_count); + + Py_XDECREF(py_envname); + Py_XDECREF(py_envval); + Py_XDECREF(py_retdict); + return NULL; +} + + +/* + * Return process user and system CPU times as a Python tuple. + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + pstatus_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + // results are more precise than os.times() + return Py_BuildValue( + "(dddd)", + PSUTIL_TV2DOUBLE(info.pr_utime), + PSUTIL_TV2DOUBLE(info.pr_stime), + PSUTIL_TV2DOUBLE(info.pr_cutime), + PSUTIL_TV2DOUBLE(info.pr_cstime) + ); +} + + +/* + * Return what CPU the process is running on. + */ +static PyObject * +psutil_proc_cpu_num(PyObject *self, PyObject *args) { + int fd = NULL; + int pid; + char path[1000]; + struct prheader header; + struct lwpsinfo *lwp = NULL; + int nent; + int size; + int proc_num; + ssize_t nbytes; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + + sprintf(path, "%s/%i/lpsinfo", procfs_path, pid); + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; + } + + // read header + nbytes = pread(fd, &header, sizeof(header), 0); + if (nbytes == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (nbytes != sizeof(header)) { + PyErr_SetString( + PyExc_RuntimeError, "read() file structure size mismatch"); + goto error; + } + + // malloc + nent = header.pr_nent; + size = header.pr_entsize * nent; + lwp = malloc(size); + if (lwp == NULL) { + PyErr_NoMemory(); + goto error; + } + + // read the rest + nbytes = pread(fd, lwp, size, sizeof(header)); + if (nbytes == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (nbytes != size) { + PyErr_SetString( + PyExc_RuntimeError, "read() file structure size mismatch"); + goto error; + } + + // done + proc_num = lwp->pr_onpro; + close(fd); + free(lwp); + return Py_BuildValue("i", proc_num); + +error: + if (fd != -1) + close(fd); + free(lwp); + return NULL; +} + + +/* + * Return process uids/gids as a Python tuple. + */ +static PyObject * +psutil_proc_cred(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + prcred_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/cred", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("iiiiii", + info.pr_ruid, info.pr_euid, info.pr_suid, + info.pr_rgid, info.pr_egid, info.pr_sgid); +} + + +/* + * Return process voluntary and involuntary context switches as a Python tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { + int pid; + char path[1000]; + prusage_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/usage", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("kk", info.pr_vctx, info.pr_ictx); +} + + +/* + * Process IO counters. + * + * Commented out and left here as a reminder. Apparently we cannot + * retrieve process IO stats because: + * - 'pr_ioch' is a sum of chars read and written, with no distinction + * - 'pr_inblk' and 'pr_oublk', which should be the number of bytes + * read and written, hardly increase and according to: + * http://www.brendangregg.com/Solaris/paper_diskubyp1.pdf + * ...they should be meaningless anyway. + * +static PyObject* +proc_io_counters(PyObject* self, PyObject* args) { + int pid; + char path[1000]; + prusage_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/usage", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + // On Solaris we only have 'pr_ioch' which accounts for bytes read + // *and* written. + // 'pr_inblk' and 'pr_oublk' should be expressed in blocks of + // 8KB according to: + // http://www.brendangregg.com/Solaris/paper_diskubyp1.pdf (pag. 8) + return Py_BuildValue("kkkk", + info.pr_ioch, + info.pr_ioch, + info.pr_inblk, + info.pr_oublk); +} + */ + + +/* + * Return information about a given process thread. + */ +static PyObject * +psutil_proc_query_thread(PyObject *self, PyObject *args) { + int pid, tid; + char path[1000]; + lwpstatus_t info; + const char *procfs_path; + + if (! PyArg_ParseTuple(args, "iis", &pid, &tid, &procfs_path)) + return NULL; + sprintf(path, "%s/%i/lwp/%i/lwpstatus", procfs_path, pid, tid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("dd", + PSUTIL_TV2DOUBLE(info.pr_utime), + PSUTIL_TV2DOUBLE(info.pr_stime)); +} + + +/* + * Return information about system virtual memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { +// XXX (arghhh!) +// total/free swap mem: commented out as for some reason I can't +// manage to get the same results shown by "swap -l", despite the +// code below is exactly the same as: +// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/ +// cmd/swap/swap.c +// We're going to parse "swap -l" output from Python (sigh!) + +/* + struct swaptable *st; + struct swapent *swapent; + int i; + struct stat64 statbuf; + char *path; + char fullpath[MAXPATHLEN+1]; + int num; + + if ((num = swapctl(SC_GETNSWP, NULL)) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (num == 0) { + PyErr_SetString(PyExc_RuntimeError, "no swap devices configured"); + return NULL; + } + if ((st = malloc(num * sizeof(swapent_t) + sizeof (int))) == NULL) { + PyErr_SetString(PyExc_RuntimeError, "malloc failed"); + return NULL; + } + if ((path = malloc(num * MAXPATHLEN)) == NULL) { + PyErr_SetString(PyExc_RuntimeError, "malloc failed"); + return NULL; + } + swapent = st->swt_ent; + for (i = 0; i < num; i++, swapent++) { + swapent->ste_path = path; + path += MAXPATHLEN; + } + st->swt_n = num; + if ((num = swapctl(SC_LIST, st)) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + swapent = st->swt_ent; + long t = 0, f = 0; + for (i = 0; i < num; i++, swapent++) { + int diskblks_per_page =(int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT); + t += (long)swapent->ste_pages; + f += (long)swapent->ste_free; + } + + free(st); + return Py_BuildValue("(kk)", t, f); +*/ + + kstat_ctl_t *kc; + kstat_t *k; + cpu_stat_t *cpu; + int cpu_count = 0; + int flag = 0; + uint_t sin = 0; + uint_t sout = 0; + + kc = kstat_open(); + if (kc == NULL) + return PyErr_SetFromErrno(PyExc_OSError);; + + k = kc->kc_chain; + while (k != NULL) { + if ((strncmp(k->ks_name, "cpu_stat", 8) == 0) && \ + (kstat_read(kc, k, NULL) != -1) ) + { + flag = 1; + cpu = (cpu_stat_t *) k->ks_data; + sin += cpu->cpu_vminfo.pgswapin; // num pages swapped in + sout += cpu->cpu_vminfo.pgswapout; // num pages swapped out + } + cpu_count += 1; + k = k->ks_next; + } + kstat_close(kc); + if (!flag) { + PyErr_SetString(PyExc_RuntimeError, "no swap device was found"); + return NULL; + } + return Py_BuildValue("(II)", sin, sout); +} + + +/* + * Return users currently connected on the system. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmpx *ut; + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_user_proc = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == USER_PROCESS) + py_user_proc = Py_True; + else + py_user_proc = Py_False; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOfOi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + py_user_proc, // (bool) user process + ut->ut_pid // process id + ); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + endutxent(); + + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + endutxent(); + return NULL; +} + + +/* + * Return disk mounted partitions as a list of tuples including device, + * mount point and filesystem type. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file; + struct mnttab mt; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + file = fopen(MNTTAB, "rb"); + if (file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + while (getmntent(file, &mt) == 0) { + py_dev = PyUnicode_DecodeFSDefault(mt.mnt_special); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(mt.mnt_mountp); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + mt.mnt_fstype, // fs type + mt.mnt_mntopts); // options + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + fclose(file); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (file != NULL) + fclose(file); + return NULL; +} + + +/* + * Return system-wide CPU times. + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + kstat_ctl_t *kc; + kstat_t *ksp; + cpu_stat_t cs; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + kc = kstat_open(); + if (kc == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_stat") == 0) { + if (kstat_read(kc, ksp, &cs) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + py_cputime = Py_BuildValue("ffff", + (float)cs.cpu_sysinfo.cpu[CPU_USER], + (float)cs.cpu_sysinfo.cpu[CPU_KERNEL], + (float)cs.cpu_sysinfo.cpu[CPU_IDLE], + (float)cs.cpu_sysinfo.cpu[CPU_WAIT]); + if (py_cputime == NULL) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_CLEAR(py_cputime); + } + } + + kstat_close(kc); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +/* + * Return disk IO statistics. + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_io_t kio; + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) { + PyErr_SetFromErrno(PyExc_OSError);; + goto error; + } + ksp = kc->kc_chain; + while (ksp != NULL) { + if (ksp->ks_type == KSTAT_TYPE_IO) { + if (strcmp(ksp->ks_class, "disk") == 0) { + if (kstat_read(kc, ksp, &kio) == -1) { + kstat_close(kc); + return PyErr_SetFromErrno(PyExc_OSError);; + } + py_disk_info = Py_BuildValue( + "(IIKKLL)", + kio.reads, + kio.writes, + kio.nread, + kio.nwritten, + kio.rtime / 1000 / 1000, // from nano to milli secs + kio.wtime / 1000 / 1000 // from nano to milli secs + ); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, + py_disk_info)) + goto error; + Py_CLEAR(py_disk_info); + } + } + ksp = ksp->ks_next; + } + kstat_close(kc); + + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +/* + * Return process memory mappings. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) { + int pid; + int fd = -1; + char path[1000]; + char perms[10]; + const char *name; + struct stat st; + pstatus_t status; + + prxmap_t *xmap = NULL, *p; + off_t size; + size_t nread; + int nmap; + uintptr_t pr_addr_sz; + uintptr_t stk_base_sz, brk_base_sz; + const char *procfs_path; + + PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path)) + goto error; + + sprintf(path, "%s/%i/status", procfs_path, pid); + if (! psutil_file_to_struct(path, (void *)&status, sizeof(status))) + goto error; + + sprintf(path, "%s/%i/xmap", procfs_path, pid); + if (stat(path, &st) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + size = st.st_size; + + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + xmap = (prxmap_t *)malloc(size); + if (xmap == NULL) { + PyErr_NoMemory(); + goto error; + } + + nread = pread(fd, xmap, size, 0); + nmap = nread / sizeof(prxmap_t); + p = xmap; + + while (nmap) { + nmap -= 1; + if (p == NULL) { + p += 1; + continue; + } + + perms[0] = '\0'; + pr_addr_sz = p->pr_vaddr + p->pr_size; + + // perms + sprintf(perms, "%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-', + p->pr_mflags & MA_WRITE ? 'w' : '-', + p->pr_mflags & MA_EXEC ? 'x' : '-', + p->pr_mflags & MA_SHARED ? 's' : '-'); + + // name + if (strlen(p->pr_mapname) > 0) { + name = p->pr_mapname; + } + else { + if ((p->pr_mflags & MA_ISM) || (p->pr_mflags & MA_SHM)) { + name = "[shmid]"; + } + else { + stk_base_sz = status.pr_stkbase + status.pr_stksize; + brk_base_sz = status.pr_brkbase + status.pr_brksize; + + if ((pr_addr_sz > status.pr_stkbase) && + (p->pr_vaddr < stk_base_sz)) { + name = "[stack]"; + } + else if ((p->pr_mflags & MA_ANON) && \ + (pr_addr_sz > status.pr_brkbase) && \ + (p->pr_vaddr < brk_base_sz)) { + name = "[heap]"; + } + else { + name = "[anon]"; + } + } + } + + py_path = PyUnicode_DecodeFSDefault(name); + if (! py_path) + goto error; + py_tuple = Py_BuildValue( + "kksOkkk", + (unsigned long)p->pr_vaddr, + (unsigned long)pr_addr_sz, + perms, + py_path, + (unsigned long)p->pr_rss * p->pr_pagesize, + (unsigned long)p->pr_anon * p->pr_pagesize, + (unsigned long)p->pr_locked * p->pr_pagesize); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_path); + Py_CLEAR(py_tuple); + + // increment pointer + p += 1; + } + + close(fd); + free(xmap); + return py_retlist; + +error: + if (fd != -1) + close(fd); + Py_XDECREF(py_tuple); + Py_XDECREF(py_path); + Py_DECREF(py_retlist); + if (xmap != NULL) + free(xmap); + return NULL; +} + + +/* + * Return a list of tuples for network I/O statistics. + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + kstat_ctl_t *kc = NULL; + kstat_t *ksp; + kstat_named_t *rbytes, *wbytes, *rpkts, *wpkts, *ierrs, *oerrs; + int ret; + int sock = -1; + struct lifreq ifr; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) + goto error; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + ksp = kc->kc_chain; + while (ksp != NULL) { + if (ksp->ks_type != KSTAT_TYPE_NAMED) + goto next; + if (strcmp(ksp->ks_class, "net") != 0) + goto next; + // skip 'lo' (localhost) because it doesn't have the statistics we need + // and it makes kstat_data_lookup() fail + if (strcmp(ksp->ks_module, "lo") == 0) + goto next; + + // check if this is a network interface by sending a ioctl + strncpy(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name)); + ret = ioctl(sock, SIOCGLIFFLAGS, &ifr); + if (ret == -1) + goto next; + + if (kstat_read(kc, ksp, NULL) == -1) { + errno = 0; + goto next; + } + + rbytes = (kstat_named_t *)kstat_data_lookup(ksp, "rbytes"); + wbytes = (kstat_named_t *)kstat_data_lookup(ksp, "obytes"); + rpkts = (kstat_named_t *)kstat_data_lookup(ksp, "ipackets"); + wpkts = (kstat_named_t *)kstat_data_lookup(ksp, "opackets"); + ierrs = (kstat_named_t *)kstat_data_lookup(ksp, "ierrors"); + oerrs = (kstat_named_t *)kstat_data_lookup(ksp, "oerrors"); + + if ((rbytes == NULL) || (wbytes == NULL) || (rpkts == NULL) || + (wpkts == NULL) || (ierrs == NULL) || (oerrs == NULL)) + { + PyErr_SetString(PyExc_RuntimeError, "kstat_data_lookup() failed"); + goto error; + } + + if (rbytes->data_type == KSTAT_DATA_UINT64) + { + py_ifc_info = Py_BuildValue("(KKKKIIii)", + wbytes->value.ui64, + rbytes->value.ui64, + wpkts->value.ui64, + rpkts->value.ui64, + ierrs->value.ui32, + oerrs->value.ui32, + 0, // dropin not supported + 0 // dropout not supported + ); + } + else + { + py_ifc_info = Py_BuildValue("(IIIIIIii)", + wbytes->value.ui32, + rbytes->value.ui32, + wpkts->value.ui32, + rpkts->value.ui32, + ierrs->value.ui32, + oerrs->value.ui32, + 0, // dropin not supported + 0 // dropout not supported + ); + } + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info)) + goto error; + Py_CLEAR(py_ifc_info); + goto next; + +next: + ksp = ksp->ks_next; + } + + kstat_close(kc); + close(sock); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (kc != NULL) + kstat_close(kc); + if (sock != -1) { + close(sock); + } + return NULL; +} + + +/* + * Return TCP and UDP connections opened by process. + * UNIX sockets are excluded. + * + * Thanks to: + * https://github.com/DavidGriffith/finx/blob/master/ + * nxsensor-3.5.0-1/src/sysdeps/solaris.c + * ...and: + * https://hg.java.net/hg/solaris~on-src/file/tip/usr/src/cmd/ + * cmd-inet/usr.bin/netstat/netstat.c + */ +static PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + long pid; + int sd = 0; + mib2_tcpConnEntry_t tp; + mib2_udpEntry_t ude; +#if defined(AF_INET6) + mib2_tcp6ConnEntry_t tp6; + mib2_udp6Entry_t ude6; +#endif + char buf[512]; + int i, flags, getcode, num_ent, state, ret; + char lip[INET6_ADDRSTRLEN], rip[INET6_ADDRSTRLEN]; + int lport, rport; + int processed_pid; + int databuf_init = 0; + struct strbuf ctlbuf, databuf; + struct T_optmgmt_req tor = {0}; + struct T_optmgmt_ack toa = {0}; + struct T_error_ack tea = {0}; + struct opthdr mibhdr = {0}; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + sd = open("/dev/arp", O_RDWR); + if (sd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, "/dev/arp"); + goto error; + } + + ret = ioctl(sd, I_PUSH, "tcp"); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + ret = ioctl(sd, I_PUSH, "udp"); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + // + // OK, this mess is basically copied and pasted from nxsensor project + // which copied and pasted it from netstat source code, mibget() + // function. Also see: + // http://stackoverflow.com/questions/8723598/ + tor.PRIM_type = T_SVR4_OPTMGMT_REQ; + tor.OPT_offset = sizeof (struct T_optmgmt_req); + tor.OPT_length = sizeof (struct opthdr); + tor.MGMT_flags = T_CURRENT; + mibhdr.level = MIB2_IP; + mibhdr.name = 0; + +#ifdef NEW_MIB_COMPLIANT + mibhdr.len = 1; +#else + mibhdr.len = 0; +#endif + memcpy(buf, &tor, sizeof tor); + memcpy(buf + tor.OPT_offset, &mibhdr, sizeof mibhdr); + + ctlbuf.buf = buf; + ctlbuf.len = tor.OPT_offset + tor.OPT_length; + flags = 0; // request to be sent in non-priority + + if (putmsg(sd, &ctlbuf, (struct strbuf *)0, flags) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + ctlbuf.maxlen = sizeof (buf); + for (;;) { + flags = 0; + getcode = getmsg(sd, &ctlbuf, (struct strbuf *)0, &flags); + memcpy(&toa, buf, sizeof toa); + memcpy(&tea, buf, sizeof tea); + + if (getcode != MOREDATA || + ctlbuf.len < (int)sizeof (struct T_optmgmt_ack) || + toa.PRIM_type != T_OPTMGMT_ACK || + toa.MGMT_flags != T_SUCCESS) + { + break; + } + if (ctlbuf.len >= (int)sizeof (struct T_error_ack) && + tea.PRIM_type == T_ERROR_ACK) + { + PyErr_SetString(PyExc_RuntimeError, "ERROR_ACK"); + goto error; + } + if (getcode == 0 && + ctlbuf.len >= (int)sizeof (struct T_optmgmt_ack) && + toa.PRIM_type == T_OPTMGMT_ACK && + toa.MGMT_flags == T_SUCCESS) + { + PyErr_SetString(PyExc_RuntimeError, "ERROR_T_OPTMGMT_ACK"); + goto error; + } + + memset(&mibhdr, 0x0, sizeof(mibhdr)); + memcpy(&mibhdr, buf + toa.OPT_offset, toa.OPT_length); + + databuf.maxlen = mibhdr.len; + databuf.len = 0; + databuf.buf = (char *)malloc((int)mibhdr.len); + if (!databuf.buf) { + PyErr_NoMemory(); + goto error; + } + databuf_init = 1; + + flags = 0; + getcode = getmsg(sd, (struct strbuf *)0, &databuf, &flags); + if (getcode < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // TCPv4 + if (mibhdr.level == MIB2_TCP && mibhdr.name == MIB2_TCP_13) { + num_ent = mibhdr.len / sizeof(mib2_tcpConnEntry_t); + for (i = 0; i < num_ent; i++) { + memcpy(&tp, databuf.buf + i * sizeof tp, sizeof tp); +#ifdef NEW_MIB_COMPLIANT + processed_pid = tp.tcpConnCreationProcess; +#else + processed_pid = 0; +#endif + if (pid != -1 && processed_pid != pid) + continue; + // construct local/remote addresses + inet_ntop(AF_INET, &tp.tcpConnLocalAddress, lip, sizeof(lip)); + inet_ntop(AF_INET, &tp.tcpConnRemAddress, rip, sizeof(rip)); + lport = tp.tcpConnLocalPort; + rport = tp.tcpConnRemPort; + + // contruct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else { + py_raddr = Py_BuildValue("()"); + } + if (!py_raddr) + goto error; + state = tp.tcpConnEntryInfo.ce_state; + + // add item + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_STREAM, + py_laddr, py_raddr, state, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + } +#if defined(AF_INET6) + // TCPv6 + else if (mibhdr.level == MIB2_TCP6 && mibhdr.name == MIB2_TCP6_CONN) + { + num_ent = mibhdr.len / sizeof(mib2_tcp6ConnEntry_t); + + for (i = 0; i < num_ent; i++) { + memcpy(&tp6, databuf.buf + i * sizeof tp6, sizeof tp6); +#ifdef NEW_MIB_COMPLIANT + processed_pid = tp6.tcp6ConnCreationProcess; +#else + processed_pid = 0; +#endif + if (pid != -1 && processed_pid != pid) + continue; + // construct local/remote addresses + inet_ntop(AF_INET6, &tp6.tcp6ConnLocalAddress, lip, sizeof(lip)); + inet_ntop(AF_INET6, &tp6.tcp6ConnRemAddress, rip, sizeof(rip)); + lport = tp6.tcp6ConnLocalPort; + rport = tp6.tcp6ConnRemPort; + + // contruct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + state = tp6.tcp6ConnEntryInfo.ce_state; + + // add item + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_STREAM, + py_laddr, py_raddr, state, processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + } +#endif + // UDPv4 + else if (mibhdr.level == MIB2_UDP || mibhdr.level == MIB2_UDP_ENTRY) { + num_ent = mibhdr.len / sizeof(mib2_udpEntry_t); + assert(num_ent * sizeof(mib2_udpEntry_t) == mibhdr.len); + for (i = 0; i < num_ent; i++) { + memcpy(&ude, databuf.buf + i * sizeof ude, sizeof ude); +#ifdef NEW_MIB_COMPLIANT + processed_pid = ude.udpCreationProcess; +#else + processed_pid = 0; +#endif + if (pid != -1 && processed_pid != pid) + continue; + // XXX Very ugly hack! It seems we get here only the first + // time we bump into a UDPv4 socket. PID is a very high + // number (clearly impossible) and the address does not + // belong to any valid interface. Not sure what else + // to do other than skipping. + if (processed_pid > 131072) + continue; + inet_ntop(AF_INET, &ude.udpLocalAddress, lip, sizeof(lip)); + lport = ude.udpLocalPort; + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_DGRAM, + py_laddr, py_raddr, PSUTIL_CONN_NONE, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + } +#if defined(AF_INET6) + // UDPv6 + else if (mibhdr.level == MIB2_UDP6 || + mibhdr.level == MIB2_UDP6_ENTRY) + { + num_ent = mibhdr.len / sizeof(mib2_udp6Entry_t); + for (i = 0; i < num_ent; i++) { + memcpy(&ude6, databuf.buf + i * sizeof ude6, sizeof ude6); +#ifdef NEW_MIB_COMPLIANT + processed_pid = ude6.udp6CreationProcess; +#else + processed_pid = 0; +#endif + if (pid != -1 && processed_pid != pid) + continue; + inet_ntop(AF_INET6, &ude6.udp6LocalAddress, lip, sizeof(lip)); + lport = ude6.udp6LocalPort; + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_DGRAM, + py_laddr, py_raddr, PSUTIL_CONN_NONE, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + } +#endif + free(databuf.buf); + } + + close(sd); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (databuf_init == 1) + free(databuf.buf); + if (sd != 0) + close(sd); + return NULL; +} + + +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + float boot_time = 0.0; + struct utmpx *ut; + + setutxent(); + while (NULL != (ut = getutxent())) { + if (ut->ut_type == BOOT_TIME) { + boot_time = (float)ut->ut_tv.tv_sec; + break; + } + } + endutxent(); + if (fabs(boot_time) < 0.000001) { + /* could not find BOOT_TIME in getutxent loop */ + PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); + return NULL; + } + return Py_BuildValue("f", boot_time); +} + + +/* + * Return the number of physical CPU cores on the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + kstat_ctl_t *kc; + kstat_t *ksp; + int ncpus = 0; + + kc = kstat_open(); + if (kc == NULL) + goto error; + ksp = kstat_lookup(kc, "cpu_info", -1, NULL); + if (ksp == NULL) + goto error; + + for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_info") != 0) + continue; + if (kstat_read(kc, ksp, NULL) == -1) + goto error; + ncpus += 1; + } + + kstat_close(kc); + if (ncpus > 0) + return Py_BuildValue("i", ncpus); + else + goto error; + +error: + // mimic os.cpu_count() + if (kc != NULL) + kstat_close(kc); + Py_RETURN_NONE; +} + + +/* + * Return stats about a particular network + * interface. References: + * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject* +psutil_net_if_stats(PyObject* self, PyObject* args) { + kstat_ctl_t *kc = NULL; + kstat_t *ksp; + kstat_named_t *knp; + int ret; + int sock = -1; + int duplex; + int speed; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + PyObject *py_is_up = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) + goto error; + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_class, "net") == 0) { + struct lifreq ifr; + + kstat_read(kc, ksp, NULL); + if (ksp->ks_type != KSTAT_TYPE_NAMED) + continue; + if (strcmp(ksp->ks_class, "net") != 0) + continue; + + strncpy(ifr.lifr_name, ksp->ks_name, sizeof(ifr.lifr_name)); + ret = ioctl(sock, SIOCGLIFFLAGS, &ifr); + if (ret == -1) + continue; // not a network interface + + // is up? + if ((ifr.lifr_flags & IFF_UP) != 0) { + if ((knp = kstat_data_lookup(ksp, "link_up")) != NULL) { + if (knp->value.ui32 != 0u) + py_is_up = Py_True; + else + py_is_up = Py_False; + } + else { + py_is_up = Py_True; + } + } + else { + py_is_up = Py_False; + } + Py_INCREF(py_is_up); + + // duplex + duplex = 0; // unknown + if ((knp = kstat_data_lookup(ksp, "link_duplex")) != NULL) { + if (knp->value.ui32 == 1) + duplex = 1; // half + else if (knp->value.ui32 == 2) + duplex = 2; // full + } + + // speed + if ((knp = kstat_data_lookup(ksp, "ifspeed")) != NULL) + // expressed in bits per sec, we want mega bits per sec + speed = (int)knp->value.ui64 / 1000000; + else + speed = 0; + + // mtu + ret = ioctl(sock, SIOCGLIFMTU, &ifr); + if (ret == -1) + goto error; + + py_ifc_info = Py_BuildValue("(Oiii)", py_is_up, duplex, speed, + ifr.lifr_mtu); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info)) + goto error; + Py_CLEAR(py_ifc_info); + } + } + + close(sock); + kstat_close(kc); + return py_retdict; + +error: + Py_XDECREF(py_is_up); + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (sock != -1) + close(sock); + if (kc != NULL) + kstat_close(kc); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + kstat_ctl_t *kc; + kstat_t *ksp; + cpu_stat_t cs; + unsigned int ctx_switches = 0; + unsigned int interrupts = 0; + unsigned int traps = 0; + unsigned int syscalls = 0; + + kc = kstat_open(); + if (kc == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_stat") == 0) { + if (kstat_read(kc, ksp, &cs) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + // voluntary + involuntary + ctx_switches += cs.cpu_sysinfo.pswitch + cs.cpu_sysinfo.inv_swtch; + interrupts += cs.cpu_sysinfo.intr; + traps += cs.cpu_sysinfo.trap; + syscalls += cs.cpu_sysinfo.syscall; + } + } + + kstat_close(kc); + return Py_BuildValue( + "IIII", ctx_switches, interrupts, syscalls, traps); + +error: + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = { + // --- process-related functions + {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, + "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, + {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS, + "Return process name and args."}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment."}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return process user and system CPU times."}, + {"proc_cred", psutil_proc_cred, METH_VARARGS, + "Return process uids/gids."}, + {"query_process_thread", psutil_proc_query_thread, METH_VARARGS, + "Return info about a process thread"}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return process memory mappings"}, + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Return the number of context switches performed by process"}, + {"proc_cpu_num", psutil_proc_cpu_num, METH_VARARGS, + "Return what CPU the process is on"}, + + // --- system-related functions + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return information about system swap memory."}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk partitions."}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-CPU times."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O statistics."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return a Python dict of tuples for network I/O statistics."}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return system boot time in seconds since the EPOCH."}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return the number of physical CPUs on the system."}, + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return TCP and UDP syste-wide open connections."}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, duplex, speed, mtu)"}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return CPU statistics"}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_sunos_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_sunos_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_sunos", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_sunos_traverse, + psutil_sunos_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_sunos(void) + +#else +#define INITERROR return + +void init_psutil_sunos(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_sunos", PsutilMethods); +#endif + if (module == NULL) + INITERROR; + + if (psutil_setup() != 0) + INITERROR; + + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SONPROC", SONPROC); + PyModule_AddIntConstant(module, "SWAIT", SWAIT); + + PyModule_AddIntConstant(module, "PRNODEV", PRNODEV); // for process tty + + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RCVD); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + // sunos specific + PyModule_AddIntConstant(module, "TCPS_IDLE", TCPS_IDLE); + // sunos specific + PyModule_AddIntConstant(module, "TCPS_BOUND", TCPS_BOUND); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/ddtrace/vendor/psutil/_psutil_windows.c b/ddtrace/vendor/psutil/_psutil_windows.c new file mode 100644 index 00000000000..f35d0076d29 --- /dev/null +++ b/ddtrace/vendor/psutil/_psutil_windows.c @@ -0,0 +1,3723 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Windows platform-specific module methods for _psutil_windows. + * + * List of undocumented Windows NT APIs which are used in here and in + * other modules: + * - NtQuerySystemInformation + * - NtQueryInformationProcess + * - NtQueryObject + * - NtSuspendProcess + * - NtResumeProcess + */ + +// Fixes clash between winsock2.h and windows.h +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include // disk_io_counters() +#include +#include +#include // users() +#include // cpu_freq() +#if (_WIN32_WINNT >= 0x0600) // Windows >= Vista +#include // net_connections() +#endif + +// Link with Iphlpapi.lib +#pragma comment(lib, "IPHLPAPI.lib") + +#include "arch/windows/ntextapi.h" +#include "arch/windows/global.h" +#include "arch/windows/security.h" +#include "arch/windows/process_info.h" +#include "arch/windows/process_handles.h" +#include "arch/windows/inet_ntop.h" +#include "arch/windows/services.h" +#include "arch/windows/wmi.h" +#include "_psutil_common.h" + + +/* + * ============================================================================ + * Utilities + * ============================================================================ + */ + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) +#define LO_T 1e-7 +#define HI_T 429.4967296 +#define BYTESWAP_USHORT(x) ((((USHORT)(x) << 8) | ((USHORT)(x) >> 8)) & 0xffff) +#ifndef AF_INET6 +#define AF_INET6 23 +#endif + + +PIP_ADAPTER_ADDRESSES +psutil_get_nic_addresses() { + // allocate a 15 KB buffer to start with + int outBufLen = 15000; + DWORD dwRetVal = 0; + ULONG attempts = 0; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + + do { + pAddresses = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen); + if (pAddresses == NULL) { + PyErr_NoMemory(); + return NULL; + } + + dwRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses, + &outBufLen); + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + free(pAddresses); + pAddresses = NULL; + } + else { + break; + } + + attempts++; + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (attempts < 3)); + + if (dwRetVal != NO_ERROR) { + PyErr_SetString( + PyExc_RuntimeError, "GetAdaptersAddresses() syscall failed."); + return NULL; + } + + return pAddresses; +} + + +/* + * Return the number of logical, active CPUs. Return 0 if undetermined. + * See discussion at: https://bugs.python.org/issue33166#msg314631 + */ +unsigned int +psutil_get_num_cpus(int fail_on_err) { + unsigned int ncpus = 0; + + // Minimum requirement: Windows 7 + if (psutil_GetActiveProcessorCount != NULL) { + ncpus = psutil_GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); + if ((ncpus == 0) && (fail_on_err == 1)) { + PyErr_SetFromWindowsErr(0); + } + } + else { + psutil_debug("GetActiveProcessorCount() not available; " + "using GetSystemInfo()"); + ncpus = (unsigned int)PSUTIL_SYSTEM_INFO.dwNumberOfProcessors; + if ((ncpus <= 0) && (fail_on_err == 1)) { + PyErr_SetString( + PyExc_RuntimeError, + "GetSystemInfo() failed to retrieve CPU count"); + } + } + return ncpus; +} + + +/* + * ============================================================================ + * Public Python API + * ============================================================================ + */ + +// Raised by Process.wait(). +static PyObject *TimeoutExpired; +static PyObject *TimeoutAbandoned; + +/* + * Return a Python float representing the system uptime expressed in seconds + * since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + ULONGLONG uptime; + time_t pt; + FILETIME fileTime; + ULONGLONG ll; + + GetSystemTimeAsFileTime(&fileTime); + /* + HUGE thanks to: + http://johnstewien.spaces.live.com/blog/cns!E6885DB5CEBABBC8!831.entry + + This function converts the FILETIME structure to the 32 bit + Unix time structure. + The time_t is a 32-bit value for the number of seconds since + January 1, 1970. A FILETIME is a 64-bit for the number of + 100-nanosecond periods since January 1, 1601. Convert by + subtracting the number of 100-nanosecond period between 01-01-1970 + and 01-01-1601, from time_t the divide by 1e+7 to get to the same + base granularity. + */ + ll = (((ULONGLONG) + (fileTime.dwHighDateTime)) << 32) + fileTime.dwLowDateTime; + pt = (time_t)((ll - 116444736000000000ull) / 10000000ull); + + if (psutil_GetTickCount64 != NULL) { + // Windows >= Vista + uptime = psutil_GetTickCount64() / 1000ull; + } + else { + // Windows XP. + // GetTickCount() time will wrap around to zero if the + // system is run continuously for 49.7 days. + uptime = (ULONGLONG)GetTickCount() / 1000ull; + } + return Py_BuildValue("K", pt - uptime); +} + + +/* + * Return 1 if PID exists in the current process list, else 0. + */ +static PyObject * +psutil_pid_exists(PyObject *self, PyObject *args) { + long pid; + int status; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + status = psutil_pid_is_running(pid); + if (-1 == status) + return NULL; // exception raised in psutil_pid_is_running() + return PyBool_FromLong(status); +} + + +/* + * Return a Python list of all the PIDs running on the system. + */ +static PyObject * +psutil_pids(PyObject *self, PyObject *args) { + DWORD *proclist = NULL; + DWORD numberOfReturnedPIDs; + DWORD i; + PyObject *py_pid = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + goto error; + + for (i = 0; i < numberOfReturnedPIDs; i++) { + py_pid = Py_BuildValue("I", proclist[i]); + if (!py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + } + + // free C array allocated for PIDs + free(proclist); + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (proclist != NULL) + free(proclist); + return NULL; +} + + +/* + * Kill a process given its PID. + */ +static PyObject * +psutil_proc_kill(PyObject *self, PyObject *args) { + HANDLE hProcess; + long pid; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (pid == 0) + return AccessDenied(""); + + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // see https://github.com/giampaolo/psutil/issues/24 + psutil_debug("OpenProcess -> ERROR_INVALID_PARAMETER turned " + "into NoSuchProcess"); + NoSuchProcess(""); + } + else { + PyErr_SetFromWindowsErr(0); + } + return NULL; + } + + if (! TerminateProcess(hProcess, SIGTERM)) { + // ERROR_ACCESS_DENIED may happen if the process already died. See: + // https://github.com/giampaolo/psutil/issues/1099 + if (GetLastError() != ERROR_ACCESS_DENIED) { + PyErr_SetFromOSErrnoWithSyscall("TerminateProcess"); + return NULL; + } + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Wait for process to terminate and return its exit code. + */ +static PyObject * +psutil_proc_wait(PyObject *self, PyObject *args) { + HANDLE hProcess; + DWORD ExitCode; + DWORD retVal; + long pid; + long timeout; + + if (! PyArg_ParseTuple(args, "ll", &pid, &timeout)) + return NULL; + if (pid == 0) + return AccessDenied(""); + + hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, pid); + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // no such process; we do not want to raise NSP but + // return None instead. + Py_RETURN_NONE; + } + else + return PyErr_SetFromWindowsErr(0); + } + + // wait until the process has terminated + Py_BEGIN_ALLOW_THREADS + retVal = WaitForSingleObject(hProcess, timeout); + Py_END_ALLOW_THREADS + + // handle return code + if (retVal == WAIT_FAILED) { + PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); + CloseHandle(hProcess); + return NULL; + } + if (retVal == WAIT_TIMEOUT) { + PyErr_SetString(TimeoutExpired, + "WaitForSingleObject() returned WAIT_TIMEOUT"); + CloseHandle(hProcess); + return NULL; + } + if (retVal == WAIT_ABANDONED) { + psutil_debug("WaitForSingleObject() -> WAIT_ABANDONED"); + PyErr_SetString(TimeoutAbandoned, + "WaitForSingleObject() returned WAIT_ABANDONED"); + CloseHandle(hProcess); + return NULL; + } + + // WaitForSingleObject() returned WAIT_OBJECT_0. It means the + // process is gone so we can get its process exit code. The PID + // may still stick around though but we'll handle that from Python. + if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { + PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong((long) ExitCode); +#else + return PyInt_FromLong((long) ExitCode); +#endif +} + + +/* + * Return a Python tuple (user_time, kernel_time) + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) { + long pid; + HANDLE hProcess; + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + + if (hProcess == NULL) + return NULL; + if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { + if (GetLastError() == ERROR_ACCESS_DENIED) { + // usually means the process has died so we throw a NoSuchProcess + // here + NoSuchProcess(""); + } + else { + PyErr_SetFromWindowsErr(0); + } + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + + /* + * User and kernel times are represented as a FILETIME structure + * which contains a 64-bit value representing the number of + * 100-nanosecond intervals since January 1, 1601 (UTC): + * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx + * To convert it into a float representing the seconds that the + * process has executed in user/kernel mode I borrowed the code + * below from Python's Modules/posixmodule.c + */ + return Py_BuildValue( + "(dd)", + (double)(ftUser.dwHighDateTime * 429.4967296 + \ + ftUser.dwLowDateTime * 1e-7), + (double)(ftKernel.dwHighDateTime * 429.4967296 + \ + ftKernel.dwLowDateTime * 1e-7) + ); +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_create_time(PyObject *self, PyObject *args) { + long pid; + long long unix_time; + HANDLE hProcess; + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // special case for PIDs 0 and 4, return system boot time + if (0 == pid || 4 == pid) + return psutil_boot_time(NULL, NULL); + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { + if (GetLastError() == ERROR_ACCESS_DENIED) { + // usually means the process has died so we throw a + // NoSuchProcess here + NoSuchProcess(""); + } + else { + PyErr_SetFromWindowsErr(0); + } + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + + /* + // Make sure the process is not gone as OpenProcess alone seems to be + // unreliable in doing so (it seems a previous call to p.wait() makes + // it unreliable). + // This check is important as creation time is used to make sure the + // process is still running. + ret = GetExitCodeProcess(hProcess, &exitCode); + CloseHandle(hProcess); + if (ret != 0) { + if (exitCode != STILL_ACTIVE) + return NoSuchProcess(""); + } + else { + // Ignore access denied as it means the process is still alive. + // For all other errors, we want an exception. + if (GetLastError() != ERROR_ACCESS_DENIED) + return PyErr_SetFromWindowsErr(0); + } + */ + + // Convert the FILETIME structure to a Unix time. + // It's the best I could find by googling and borrowing code here + // and there. The time returned has a precision of 1 second. + unix_time = ((LONGLONG)ftCreate.dwHighDateTime) << 32; + unix_time += ftCreate.dwLowDateTime - 116444736000000000LL; + unix_time /= 10000000; + return Py_BuildValue("d", (double)unix_time); +} + + +/* + * Return the number of active, logical CPUs. + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + unsigned int ncpus; + + ncpus = psutil_get_num_cpus(0); + if (ncpus != 0) + return Py_BuildValue("I", ncpus); + else + Py_RETURN_NONE; // mimick os.cpu_count() +} + + +/* + * Return the number of physical CPU cores (hyper-thread CPUs count + * is excluded). + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + DWORD rc; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = NULL; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ptr = NULL; + DWORD length = 0; + DWORD offset = 0; + DWORD ncpus = 0; + DWORD prev_processor_info_size = 0; + + // GetLogicalProcessorInformationEx() is available from Windows 7 + // onward. Differently from GetLogicalProcessorInformation() + // it supports process groups, meaning this is able to report more + // than 64 CPUs. See: + // https://bugs.python.org/issue33166 + if (psutil_GetLogicalProcessorInformationEx == NULL) { + psutil_debug("Win < 7; cpu_count_phys() forced to None"); + Py_RETURN_NONE; + } + + while (1) { + rc = psutil_GetLogicalProcessorInformationEx( + RelationAll, buffer, &length); + if (rc == FALSE) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (buffer) { + free(buffer); + } + buffer = \ + (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(length); + if (NULL == buffer) { + PyErr_NoMemory(); + return NULL; + } + } + else { + psutil_debug("GetLogicalProcessorInformationEx() returned ", + GetLastError()); + goto return_none; + } + } + else { + break; + } + } + + ptr = buffer; + while (offset < length) { + // Advance ptr by the size of the previous + // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct. + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)\ + (((char*)ptr) + prev_processor_info_size); + + if (ptr->Relationship == RelationProcessorCore) { + ncpus += 1; + } + + // When offset == length, we've reached the last processor + // info struct in the buffer. + offset += ptr->Size; + prev_processor_info_size = ptr->Size; + } + + free(buffer); + if (ncpus != 0) { + return Py_BuildValue("I", ncpus); + } + else { + psutil_debug("GetLogicalProcessorInformationEx() count was 0"); + Py_RETURN_NONE; // mimick os.cpu_count() + } + +return_none: + if (buffer != NULL) + free(buffer); + Py_RETURN_NONE; +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict) { + long pid; + int pid_return; + int use_peb; + PyObject *py_usepeb = Py_True; + static char *keywords[] = {"pid", "use_peb", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "i|O", + keywords, &pid, &py_usepeb)) { + return NULL; + } + if ((pid == 0) || (pid == 4)) + return Py_BuildValue("[]"); + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess(""); + if (pid_return == -1) + return NULL; + + use_peb = (py_usepeb == Py_True) ? 1 : 0; + return psutil_get_cmdline(pid, use_peb); +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + long pid; + int pid_return; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if ((pid == 0) || (pid == 4)) + return Py_BuildValue("s", ""); + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess(""); + if (pid_return == -1) + return NULL; + + return psutil_get_environ(pid); +} + + +/* + * Return process executable path. + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + long pid; + HANDLE hProcess; + wchar_t exe[MAX_PATH]; +#if (_WIN32_WINNT >= 0x0600) // >= Vista + unsigned int size = sizeof(exe); +#endif + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + + // Here we differentiate between XP and Vista+ because + // QueryFullProcessImageNameW is better than GetProcessImageFileNameW + // (avoid using QueryDosDevice on the returned path), see: + // https://github.com/giampaolo/psutil/issues/1394 +#if (_WIN32_WINNT >= 0x0600) // Windows >= Vista + memset(exe, 0, MAX_PATH); + if (QueryFullProcessImageNameW(hProcess, 0, exe, &size) == 0) { + PyErr_SetFromOSErrnoWithSyscall("QueryFullProcessImageNameW"); + CloseHandle(hProcess); + return NULL; + } +#else // Windows XP + if (GetProcessImageFileNameW(hProcess, exe, MAX_PATH) == 0) { + // see: https://github.com/giampaolo/psutil/issues/1394 + if (GetLastError() == 0) + PyErr_SetFromWindowsErr(ERROR_ACCESS_DENIED); + else + PyErr_SetFromOSErrnoWithSyscall("GetProcessImageFileNameW"); + CloseHandle(hProcess); + return NULL; + } +#endif + CloseHandle(hProcess); + return PyUnicode_FromWideChar(exe, wcslen(exe)); +} + + +/* + * Return process base name. + * Note: psutil_proc_exe() is attempted first because it's faster + * but it raise AccessDenied for processes owned by other users + * in which case we fall back on using this. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + long pid; + int ok; + PROCESSENTRY32W pentry; + HANDLE hSnapShot; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pid); + if (hSnapShot == INVALID_HANDLE_VALUE) + return PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); + pentry.dwSize = sizeof(PROCESSENTRY32W); + ok = Process32FirstW(hSnapShot, &pentry); + if (! ok) { + PyErr_SetFromOSErrnoWithSyscall("Process32FirstW"); + CloseHandle(hSnapShot); + return NULL; + } + while (ok) { + if (pentry.th32ProcessID == pid) { + CloseHandle(hSnapShot); + return PyUnicode_FromWideChar( + pentry.szExeFile, wcslen(pentry.szExeFile)); + } + ok = Process32NextW(hSnapShot, &pentry); + } + + CloseHandle(hSnapShot); + NoSuchProcess(""); + return NULL; +} + + +/* + * Return process memory information as a Python tuple. + */ +static PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) { + HANDLE hProcess; + DWORD pid; +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + PROCESS_MEMORY_COUNTERS_EX cnt; +#else + PROCESS_MEMORY_COUNTERS cnt; +#endif + SIZE_T private = 0; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + + if (! GetProcessMemoryInfo(hProcess, (PPROCESS_MEMORY_COUNTERS)&cnt, + sizeof(cnt))) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + private = cnt.PrivateUsage; +#endif + + CloseHandle(hProcess); + + // PROCESS_MEMORY_COUNTERS values are defined as SIZE_T which on 64bits + // is an (unsigned long long) and on 32bits is an (unsigned int). + // "_WIN64" is defined if we're running a 64bit Python interpreter not + // exclusively if the *system* is 64bit. +#if defined(_WIN64) + return Py_BuildValue( + "(kKKKKKKKKK)", + cnt.PageFaultCount, // unsigned long + (unsigned long long)cnt.PeakWorkingSetSize, + (unsigned long long)cnt.WorkingSetSize, + (unsigned long long)cnt.QuotaPeakPagedPoolUsage, + (unsigned long long)cnt.QuotaPagedPoolUsage, + (unsigned long long)cnt.QuotaPeakNonPagedPoolUsage, + (unsigned long long)cnt.QuotaNonPagedPoolUsage, + (unsigned long long)cnt.PagefileUsage, + (unsigned long long)cnt.PeakPagefileUsage, + (unsigned long long)private); +#else + return Py_BuildValue( + "(kIIIIIIIII)", + cnt.PageFaultCount, // unsigned long + (unsigned int)cnt.PeakWorkingSetSize, + (unsigned int)cnt.WorkingSetSize, + (unsigned int)cnt.QuotaPeakPagedPoolUsage, + (unsigned int)cnt.QuotaPagedPoolUsage, + (unsigned int)cnt.QuotaPeakNonPagedPoolUsage, + (unsigned int)cnt.QuotaNonPagedPoolUsage, + (unsigned int)cnt.PagefileUsage, + (unsigned int)cnt.PeakPagefileUsage, + (unsigned int)private); +#endif +} + + +static int +psutil_GetProcWsetInformation( + DWORD pid, + HANDLE hProcess, + PMEMORY_WORKING_SET_INFORMATION *wSetInfo) +{ + NTSTATUS status; + PVOID buffer; + SIZE_T bufferSize; + + bufferSize = 0x8000; + buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufferSize); + + while ((status = psutil_NtQueryVirtualMemory( + hProcess, + NULL, + MemoryWorkingSetInformation, + buffer, + bufferSize, + NULL)) == STATUS_INFO_LENGTH_MISMATCH) + { + HeapFree(GetProcessHeap(), 0, buffer); + bufferSize *= 2; + psutil_debug("NtQueryVirtualMemory increase bufsize %zd", bufferSize); + // Fail if we're resizing the buffer to something very large. + if (bufferSize > 256 * 1024 * 1024) { + PyErr_SetString(PyExc_RuntimeError, + "NtQueryVirtualMemory bufsize is too large"); + return 1; + } + buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufferSize); + } + + if (!NT_SUCCESS(status)) { + if (status == STATUS_ACCESS_DENIED) { + AccessDenied("originated from NtQueryVirtualMemory"); + } + else if (psutil_pid_is_running(pid) == 0) { + NoSuchProcess(""); + } + else { + PyErr_Clear(); + psutil_SetFromNTStatusErr( + status, "NtQueryVirtualMemory(MemoryWorkingSetInformation)"); + } + HeapFree(GetProcessHeap(), 0, buffer); + return 1; + } + + *wSetInfo = (PMEMORY_WORKING_SET_INFORMATION)buffer; + return 0; +} + + +/* + * Returns the USS of the process. + * Reference: + * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ + * nsMemoryReporterManager.cpp + */ +static PyObject * +psutil_proc_memory_uss(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + PSUTIL_PROCESS_WS_COUNTERS wsCounters; + PMEMORY_WORKING_SET_INFORMATION wsInfo; + ULONG_PTR i; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + if (psutil_GetProcWsetInformation(pid, hProcess, &wsInfo) != 0) { + CloseHandle(hProcess); + return NULL; + } + memset(&wsCounters, 0, sizeof(PSUTIL_PROCESS_WS_COUNTERS)); + + for (i = 0; i < wsInfo->NumberOfEntries; i++) { + // This is what ProcessHacker does. + /* + wsCounters.NumberOfPages++; + if (wsInfo->WorkingSetInfo[i].ShareCount > 1) + wsCounters.NumberOfSharedPages++; + if (wsInfo->WorkingSetInfo[i].ShareCount == 0) + wsCounters.NumberOfPrivatePages++; + if (wsInfo->WorkingSetInfo[i].Shared) + wsCounters.NumberOfShareablePages++; + */ + + // This is what we do: count shared pages that only one process + // is using as private (USS). + if (!wsInfo->WorkingSetInfo[i].Shared || + wsInfo->WorkingSetInfo[i].ShareCount <= 1) { + wsCounters.NumberOfPrivatePages++; + } + } + + HeapFree(GetProcessHeap(), 0, wsInfo); + CloseHandle(hProcess); + + return Py_BuildValue("I", wsCounters.NumberOfPrivatePages); +} + + +/* + * Return a Python integer indicating the total amount of physical memory + * in bytes. + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + MEMORYSTATUSEX memInfo; + memInfo.dwLength = sizeof(MEMORYSTATUSEX); + + if (! GlobalMemoryStatusEx(&memInfo)) + return PyErr_SetFromWindowsErr(0); + return Py_BuildValue("(LLLLLL)", + memInfo.ullTotalPhys, // total + memInfo.ullAvailPhys, // avail + memInfo.ullTotalPageFile, // total page file + memInfo.ullAvailPageFile, // avail page file + memInfo.ullTotalVirtual, // total virtual + memInfo.ullAvailVirtual); // avail virtual +} + +/* + * Retrieves system CPU timing information as a (user, system, idle) + * tuple. On a multiprocessor system, the values returned are the + * sum of the designated times across all processors. + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { + double idle, kernel, user, system; + FILETIME idle_time, kernel_time, user_time; + + if (!GetSystemTimes(&idle_time, &kernel_time, &user_time)) + return PyErr_SetFromWindowsErr(0); + + idle = (double)((HI_T * idle_time.dwHighDateTime) + \ + (LO_T * idle_time.dwLowDateTime)); + user = (double)((HI_T * user_time.dwHighDateTime) + \ + (LO_T * user_time.dwLowDateTime)); + kernel = (double)((HI_T * kernel_time.dwHighDateTime) + \ + (LO_T * kernel_time.dwLowDateTime)); + + // Kernel time includes idle time. + // We return only busy kernel time subtracting idle time from + // kernel time. + system = (kernel - idle); + return Py_BuildValue("(ddd)", user, system, idle); +} + + +/* + * Same as above but for all system CPUs. + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + double idle, kernel, systemt, user, interrupt, dpc; + NTSTATUS status; + _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *sppi = NULL; + UINT i; + unsigned int ncpus; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + // retrieves number of processors + ncpus = psutil_get_num_cpus(1); + if (ncpus == 0) + goto error; + + // allocates an array of _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION + // structures, one per processor + sppi = (_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *) \ + malloc(ncpus * sizeof(_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)); + if (sppi == NULL) { + PyErr_NoMemory(); + goto error; + } + + // gets cpu time informations + status = psutil_NtQuerySystemInformation( + SystemProcessorPerformanceInformation, + sppi, + ncpus * sizeof(_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), + NULL); + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, + "NtQuerySystemInformation(SystemProcessorPerformanceInformation)" + ); + goto error; + } + + // computes system global times summing each + // processor value + idle = user = kernel = interrupt = dpc = 0; + for (i = 0; i < ncpus; i++) { + py_tuple = NULL; + user = (double)((HI_T * sppi[i].UserTime.HighPart) + + (LO_T * sppi[i].UserTime.LowPart)); + idle = (double)((HI_T * sppi[i].IdleTime.HighPart) + + (LO_T * sppi[i].IdleTime.LowPart)); + kernel = (double)((HI_T * sppi[i].KernelTime.HighPart) + + (LO_T * sppi[i].KernelTime.LowPart)); + interrupt = (double)((HI_T * sppi[i].InterruptTime.HighPart) + + (LO_T * sppi[i].InterruptTime.LowPart)); + dpc = (double)((HI_T * sppi[i].DpcTime.HighPart) + + (LO_T * sppi[i].DpcTime.LowPart)); + + // kernel time includes idle time on windows + // we return only busy kernel time subtracting + // idle time from kernel time + systemt = kernel - idle; + py_tuple = Py_BuildValue( + "(ddddd)", + user, + systemt, + idle, + interrupt, + dpc + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + + free(sppi); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (sppi) + free(sppi); + return NULL; +} + + +/* + * Return process current working directory as a Python string. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + long pid; + int pid_return; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess(""); + if (pid_return == -1) + return NULL; + + return psutil_get_cwd(pid); +} + + +/* + * Resume or suspends a process + */ +static PyObject * +psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) { + long pid; + NTSTATUS status; + HANDLE hProcess; + PyObject* suspend; + + if (! PyArg_ParseTuple(args, "lO", &pid, &suspend)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_SUSPEND_RESUME); + if (hProcess == NULL) + return NULL; + + if (PyObject_IsTrue(suspend)) + status = psutil_NtSuspendProcess(hProcess); + else + status = psutil_NtResumeProcess(hProcess); + + if (! NT_SUCCESS(status)) { + CloseHandle(hProcess); + return psutil_SetFromNTStatusErr(status, "NtSuspend|ResumeProcess"); + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + HANDLE hThread; + THREADENTRY32 te32 = {0}; + long pid; + int pid_return; + int rc; + FILETIME ftDummy, ftKernel, ftUser; + HANDLE hThreadSnap = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (pid == 0) { + // raise AD instead of returning 0 as procexp is able to + // retrieve useful information somehow + AccessDenied(""); + goto error; + } + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) { + NoSuchProcess(""); + goto error; + } + if (pid_return == -1) + goto error; + + hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hThreadSnap == INVALID_HANDLE_VALUE) { + PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); + goto error; + } + + // Fill in the size of the structure before using it + te32.dwSize = sizeof(THREADENTRY32); + + if (! Thread32First(hThreadSnap, &te32)) { + PyErr_SetFromOSErrnoWithSyscall("Thread32First"); + goto error; + } + + // Walk the thread snapshot to find all threads of the process. + // If the thread belongs to the process, increase the counter. + do { + if (te32.th32OwnerProcessID == pid) { + py_tuple = NULL; + hThread = NULL; + hThread = OpenThread(THREAD_QUERY_INFORMATION, + FALSE, te32.th32ThreadID); + if (hThread == NULL) { + // thread has disappeared on us + continue; + } + + rc = GetThreadTimes(hThread, &ftDummy, &ftDummy, &ftKernel, + &ftUser); + if (rc == 0) { + PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes"); + goto error; + } + + /* + * User and kernel times are represented as a FILETIME structure + * which contains a 64-bit value representing the number of + * 100-nanosecond intervals since January 1, 1601 (UTC): + * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx + * To convert it into a float representing the seconds that the + * process has executed in user/kernel mode I borrowed the code + * below from Python's Modules/posixmodule.c + */ + py_tuple = Py_BuildValue( + "kdd", + te32.th32ThreadID, + (double)(ftUser.dwHighDateTime * 429.4967296 + \ + ftUser.dwLowDateTime * 1e-7), + (double)(ftKernel.dwHighDateTime * 429.4967296 + \ + ftKernel.dwLowDateTime * 1e-7)); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + + CloseHandle(hThread); + } + } while (Thread32Next(hThreadSnap, &te32)); + + CloseHandle(hThreadSnap); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (hThread != NULL) + CloseHandle(hThread); + if (hThreadSnap != NULL) + CloseHandle(hThreadSnap); + return NULL; +} + + +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + long pid; + HANDLE processHandle; + DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + processHandle = psutil_handle_from_pid(pid, access); + if (processHandle == NULL) + return NULL; + + py_retlist = psutil_get_open_files(pid, processHandle); + if (py_retlist == NULL) { + PyErr_SetFromWindowsErr(0); + CloseHandle(processHandle); + return NULL; + } + + CloseHandle(processHandle); + return py_retlist; +} + + +/* + Accept a filename's drive in native format like "\Device\HarddiskVolume1\" + and return the corresponding drive letter (e.g. "C:\\"). + If no match is found return an empty string. +*/ +static PyObject * +psutil_win32_QueryDosDevice(PyObject *self, PyObject *args) { + LPCTSTR lpDevicePath; + TCHAR d = TEXT('A'); + TCHAR szBuff[5]; + + if (!PyArg_ParseTuple(args, "s", &lpDevicePath)) + return NULL; + + while (d <= TEXT('Z')) { + TCHAR szDeviceName[3] = {d, TEXT(':'), TEXT('\0')}; + TCHAR szTarget[512] = {0}; + if (QueryDosDevice(szDeviceName, szTarget, 511) != 0) { + if (_tcscmp(lpDevicePath, szTarget) == 0) { + _stprintf_s(szBuff, _countof(szBuff), TEXT("%c:"), d); + return Py_BuildValue("s", szBuff); + } + } + d++; + } + return Py_BuildValue("s", ""); +} + + +/* + * Return process username as a "DOMAIN//USERNAME" string. + */ +static PyObject * +psutil_proc_username(PyObject *self, PyObject *args) { + long pid; + HANDLE processHandle = NULL; + HANDLE tokenHandle = NULL; + PTOKEN_USER user = NULL; + ULONG bufferSize; + WCHAR *name = NULL; + WCHAR *domainName = NULL; + ULONG nameSize; + ULONG domainNameSize; + SID_NAME_USE nameUse; + PyObject *py_username = NULL; + PyObject *py_domain = NULL; + PyObject *py_tuple = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + processHandle = psutil_handle_from_pid( + pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (processHandle == NULL) + return NULL; + + if (!OpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle)) { + PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + goto error; + } + + CloseHandle(processHandle); + processHandle = NULL; + + // Get the user SID. + bufferSize = 0x100; + while (1) { + user = malloc(bufferSize); + if (user == NULL) { + PyErr_NoMemory(); + goto error; + } + if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, + &bufferSize)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(user); + continue; + } + else { + PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation"); + goto error; + } + } + break; + } + + CloseHandle(tokenHandle); + tokenHandle = NULL; + + // resolve the SID to a name + nameSize = 0x100; + domainNameSize = 0x100; + while (1) { + name = malloc(nameSize * sizeof(WCHAR)); + if (name == NULL) { + PyErr_NoMemory(); + goto error; + } + domainName = malloc(domainNameSize * sizeof(WCHAR)); + if (domainName == NULL) { + PyErr_NoMemory(); + goto error; + } + if (!LookupAccountSidW(NULL, user->User.Sid, name, &nameSize, + domainName, &domainNameSize, &nameUse)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(name); + free(domainName); + continue; + } + else { + PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW"); + goto error; + } + } + break; + } + + py_domain = PyUnicode_FromWideChar(domainName, wcslen(domainName)); + if (! py_domain) + goto error; + py_username = PyUnicode_FromWideChar(name, wcslen(name)); + if (! py_username) + goto error; + py_tuple = Py_BuildValue("OO", py_domain, py_username); + if (! py_tuple) + goto error; + Py_DECREF(py_domain); + Py_DECREF(py_username); + + free(name); + free(domainName); + free(user); + + return py_tuple; + +error: + if (processHandle != NULL) + CloseHandle(processHandle); + if (tokenHandle != NULL) + CloseHandle(tokenHandle); + if (name != NULL) + free(name); + if (domainName != NULL) + free(domainName); + if (user != NULL) + free(user); + Py_XDECREF(py_domain); + Py_XDECREF(py_username); + Py_XDECREF(py_tuple); + return NULL; +} + + +// https://msdn.microsoft.com/library/aa365928.aspx +// TODO properly handle return code +static DWORD __GetExtendedTcpTable(_GetExtendedTcpTable call, + ULONG address_family, + PVOID * data, DWORD * size) +{ + // Due to other processes being active on the machine, it's possible + // that the size of the table increases between the moment where we + // query the size and the moment where we query the data. Therefore, it's + // important to call this in a loop to retry if that happens. + // See https://github.com/giampaolo/psutil/pull/1335 concerning 0xC0000001 error + // and https://github.com/giampaolo/psutil/issues/1294 + DWORD error = ERROR_INSUFFICIENT_BUFFER; + *size = 0; + *data = NULL; + error = call(NULL, size, FALSE, address_family, + TCP_TABLE_OWNER_PID_ALL, 0); + while (error == ERROR_INSUFFICIENT_BUFFER || error == 0xC0000001) + { + *data = malloc(*size); + if (*data == NULL) { + error = ERROR_NOT_ENOUGH_MEMORY; + continue; + } + error = call(*data, size, FALSE, address_family, + TCP_TABLE_OWNER_PID_ALL, 0); + if (error != NO_ERROR) { + free(*data); + *data = NULL; + } + } + return error; +} + + +// https://msdn.microsoft.com/library/aa365930.aspx +// TODO properly check return value +static DWORD __GetExtendedUdpTable(_GetExtendedUdpTable call, + ULONG address_family, + PVOID * data, DWORD * size) +{ + // Due to other processes being active on the machine, it's possible + // that the size of the table increases between the moment where we + // query the size and the moment where we query the data. Therefore, it's + // important to call this in a loop to retry if that happens. + // See https://github.com/giampaolo/psutil/pull/1335 concerning 0xC0000001 error + // and https://github.com/giampaolo/psutil/issues/1294 + DWORD error = ERROR_INSUFFICIENT_BUFFER; + *size = 0; + *data = NULL; + error = call(NULL, size, FALSE, address_family, + UDP_TABLE_OWNER_PID, 0); + while (error == ERROR_INSUFFICIENT_BUFFER || error == 0xC0000001) + { + *data = malloc(*size); + if (*data == NULL) { + error = ERROR_NOT_ENOUGH_MEMORY; + continue; + } + error = call(*data, size, FALSE, address_family, + UDP_TABLE_OWNER_PID, 0); + if (error != NO_ERROR) { + free(*data); + *data = NULL; + } + } + + if (error == ERROR_NOT_ENOUGH_MEMORY) { + PyErr_NoMemory(); + return 1; + } + if (error != NO_ERROR) { + PyErr_SetFromWindowsErr(error); + return 1; + } + return 0; +} + + +#define psutil_conn_decref_objs() \ + Py_DECREF(_AF_INET); \ + Py_DECREF(_AF_INET6);\ + Py_DECREF(_SOCK_STREAM);\ + Py_DECREF(_SOCK_DGRAM); + + +/* + * Return a list of network connections opened by a process + */ +static PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + static long null_address[4] = { 0, 0, 0, 0 }; + unsigned long pid; + int pid_return; + PVOID table = NULL; + DWORD tableSize; + DWORD error; + PMIB_TCPTABLE_OWNER_PID tcp4Table; + PMIB_UDPTABLE_OWNER_PID udp4Table; + PMIB_TCP6TABLE_OWNER_PID tcp6Table; + PMIB_UDP6TABLE_OWNER_PID udp6Table; + ULONG i; + CHAR addressBufferLocal[65]; + CHAR addressBufferRemote[65]; + + PyObject *py_retlist; + PyObject *py_conn_tuple = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + PyObject *py_addr_tuple_local = NULL; + PyObject *py_addr_tuple_remote = NULL; + PyObject *_AF_INET = PyLong_FromLong((long)AF_INET); + PyObject *_AF_INET6 = PyLong_FromLong((long)AF_INET6); + PyObject *_SOCK_STREAM = PyLong_FromLong((long)SOCK_STREAM); + PyObject *_SOCK_DGRAM = PyLong_FromLong((long)SOCK_DGRAM); + + // Import some functions. + if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) + goto error; + + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + psutil_conn_decref_objs(); + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + return NULL; + } + + if (pid != -1) { + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) { + psutil_conn_decref_objs(); + return NoSuchProcess(""); + } + else if (pid_return == -1) { + psutil_conn_decref_objs(); + return NULL; + } + } + + py_retlist = PyList_New(0); + if (py_retlist == NULL) { + psutil_conn_decref_objs(); + return NULL; + } + + // TCP IPv4 + + if ((PySequence_Contains(py_af_filter, _AF_INET) == 1) && + (PySequence_Contains(py_type_filter, _SOCK_STREAM) == 1)) + { + table = NULL; + py_conn_tuple = NULL; + py_addr_tuple_local = NULL; + py_addr_tuple_remote = NULL; + tableSize = 0; + + error = __GetExtendedTcpTable(psutil_GetExtendedTcpTable, + AF_INET, &table, &tableSize); + if (error != 0) + goto error; + tcp4Table = table; + for (i = 0; i < tcp4Table->dwNumEntries; i++) { + if (pid != -1) { + if (tcp4Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (tcp4Table->table[i].dwLocalAddr != 0 || + tcp4Table->table[i].dwLocalPort != 0) + { + struct in_addr addr; + + addr.S_un.S_addr = tcp4Table->table[i].dwLocalAddr; + psutil_rtlIpv4AddressToStringA(&addr, addressBufferLocal); + py_addr_tuple_local = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(tcp4Table->table[i].dwLocalPort)); + } + else { + py_addr_tuple_local = PyTuple_New(0); + } + + if (py_addr_tuple_local == NULL) + goto error; + + // On Windows <= XP, remote addr is filled even if socket + // is in LISTEN mode in which case we just ignore it. + if ((tcp4Table->table[i].dwRemoteAddr != 0 || + tcp4Table->table[i].dwRemotePort != 0) && + (tcp4Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) + { + struct in_addr addr; + + addr.S_un.S_addr = tcp4Table->table[i].dwRemoteAddr; + psutil_rtlIpv4AddressToStringA(&addr, addressBufferRemote); + py_addr_tuple_remote = Py_BuildValue( + "(si)", + addressBufferRemote, + BYTESWAP_USHORT(tcp4Table->table[i].dwRemotePort)); + } + else + { + py_addr_tuple_remote = PyTuple_New(0); + } + + if (py_addr_tuple_remote == NULL) + goto error; + + py_conn_tuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET, + SOCK_STREAM, + py_addr_tuple_local, + py_addr_tuple_remote, + tcp4Table->table[i].dwState, + tcp4Table->table[i].dwOwningPid); + if (!py_conn_tuple) + goto error; + if (PyList_Append(py_retlist, py_conn_tuple)) + goto error; + Py_CLEAR(py_conn_tuple); + } + + free(table); + table = NULL; + tableSize = 0; + } + + // TCP IPv6 + if ((PySequence_Contains(py_af_filter, _AF_INET6) == 1) && + (PySequence_Contains(py_type_filter, _SOCK_STREAM) == 1) && + (psutil_rtlIpv6AddressToStringA != NULL)) + { + table = NULL; + py_conn_tuple = NULL; + py_addr_tuple_local = NULL; + py_addr_tuple_remote = NULL; + tableSize = 0; + + error = __GetExtendedTcpTable(psutil_GetExtendedTcpTable, + AF_INET6, &table, &tableSize); + if (error != 0) + goto error; + tcp6Table = table; + for (i = 0; i < tcp6Table->dwNumEntries; i++) + { + if (pid != -1) { + if (tcp6Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (memcmp(tcp6Table->table[i].ucLocalAddr, null_address, 16) + != 0 || tcp6Table->table[i].dwLocalPort != 0) + { + struct in6_addr addr; + + memcpy(&addr, tcp6Table->table[i].ucLocalAddr, 16); + psutil_rtlIpv6AddressToStringA(&addr, addressBufferLocal); + py_addr_tuple_local = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(tcp6Table->table[i].dwLocalPort)); + } + else { + py_addr_tuple_local = PyTuple_New(0); + } + + if (py_addr_tuple_local == NULL) + goto error; + + // On Windows <= XP, remote addr is filled even if socket + // is in LISTEN mode in which case we just ignore it. + if ((memcmp(tcp6Table->table[i].ucRemoteAddr, null_address, 16) + != 0 || + tcp6Table->table[i].dwRemotePort != 0) && + (tcp6Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) + { + struct in6_addr addr; + + memcpy(&addr, tcp6Table->table[i].ucRemoteAddr, 16); + psutil_rtlIpv6AddressToStringA(&addr, addressBufferRemote); + py_addr_tuple_remote = Py_BuildValue( + "(si)", + addressBufferRemote, + BYTESWAP_USHORT(tcp6Table->table[i].dwRemotePort)); + } + else { + py_addr_tuple_remote = PyTuple_New(0); + } + + if (py_addr_tuple_remote == NULL) + goto error; + + py_conn_tuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET6, + SOCK_STREAM, + py_addr_tuple_local, + py_addr_tuple_remote, + tcp6Table->table[i].dwState, + tcp6Table->table[i].dwOwningPid); + if (!py_conn_tuple) + goto error; + if (PyList_Append(py_retlist, py_conn_tuple)) + goto error; + Py_CLEAR(py_conn_tuple); + } + + free(table); + table = NULL; + tableSize = 0; + } + + // UDP IPv4 + + if ((PySequence_Contains(py_af_filter, _AF_INET) == 1) && + (PySequence_Contains(py_type_filter, _SOCK_DGRAM) == 1)) + { + table = NULL; + py_conn_tuple = NULL; + py_addr_tuple_local = NULL; + py_addr_tuple_remote = NULL; + tableSize = 0; + error = __GetExtendedUdpTable(psutil_GetExtendedUdpTable, + AF_INET, &table, &tableSize); + if (error != 0) + goto error; + udp4Table = table; + for (i = 0; i < udp4Table->dwNumEntries; i++) + { + if (pid != -1) { + if (udp4Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (udp4Table->table[i].dwLocalAddr != 0 || + udp4Table->table[i].dwLocalPort != 0) + { + struct in_addr addr; + + addr.S_un.S_addr = udp4Table->table[i].dwLocalAddr; + psutil_rtlIpv4AddressToStringA(&addr, addressBufferLocal); + py_addr_tuple_local = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(udp4Table->table[i].dwLocalPort)); + } + else { + py_addr_tuple_local = PyTuple_New(0); + } + + if (py_addr_tuple_local == NULL) + goto error; + + py_conn_tuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET, + SOCK_DGRAM, + py_addr_tuple_local, + PyTuple_New(0), + PSUTIL_CONN_NONE, + udp4Table->table[i].dwOwningPid); + if (!py_conn_tuple) + goto error; + if (PyList_Append(py_retlist, py_conn_tuple)) + goto error; + Py_CLEAR(py_conn_tuple); + } + + free(table); + table = NULL; + tableSize = 0; + } + + // UDP IPv6 + + if ((PySequence_Contains(py_af_filter, _AF_INET6) == 1) && + (PySequence_Contains(py_type_filter, _SOCK_DGRAM) == 1) && + (psutil_rtlIpv6AddressToStringA != NULL)) + { + table = NULL; + py_conn_tuple = NULL; + py_addr_tuple_local = NULL; + py_addr_tuple_remote = NULL; + tableSize = 0; + error = __GetExtendedUdpTable(psutil_GetExtendedUdpTable, + AF_INET6, &table, &tableSize); + if (error != 0) + goto error; + udp6Table = table; + for (i = 0; i < udp6Table->dwNumEntries; i++) { + if (pid != -1) { + if (udp6Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (memcmp(udp6Table->table[i].ucLocalAddr, null_address, 16) + != 0 || udp6Table->table[i].dwLocalPort != 0) + { + struct in6_addr addr; + + memcpy(&addr, udp6Table->table[i].ucLocalAddr, 16); + psutil_rtlIpv6AddressToStringA(&addr, addressBufferLocal); + py_addr_tuple_local = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(udp6Table->table[i].dwLocalPort)); + } + else { + py_addr_tuple_local = PyTuple_New(0); + } + + if (py_addr_tuple_local == NULL) + goto error; + + py_conn_tuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET6, + SOCK_DGRAM, + py_addr_tuple_local, + PyTuple_New(0), + PSUTIL_CONN_NONE, + udp6Table->table[i].dwOwningPid); + if (!py_conn_tuple) + goto error; + if (PyList_Append(py_retlist, py_conn_tuple)) + goto error; + Py_CLEAR(py_conn_tuple); + } + + free(table); + table = NULL; + tableSize = 0; + } + + psutil_conn_decref_objs(); + return py_retlist; + +error: + psutil_conn_decref_objs(); + Py_XDECREF(py_conn_tuple); + Py_XDECREF(py_addr_tuple_local); + Py_XDECREF(py_addr_tuple_remote); + Py_DECREF(py_retlist); + if (table != NULL) + free(table); + return NULL; +} + + +/* + * Get process priority as a Python integer. + */ +static PyObject * +psutil_proc_priority_get(PyObject *self, PyObject *args) { + long pid; + DWORD priority; + HANDLE hProcess; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + priority = GetPriorityClass(hProcess); + if (priority == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + CloseHandle(hProcess); + return Py_BuildValue("i", priority); +} + + +/* + * Set process priority. + */ +static PyObject * +psutil_proc_priority_set(PyObject *self, PyObject *args) { + long pid; + int priority; + int retval; + HANDLE hProcess; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + + if (! PyArg_ParseTuple(args, "li", &pid, &priority)) + return NULL; + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + retval = SetPriorityClass(hProcess, priority); + if (retval == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista +/* + * Get process IO priority as a Python integer. + */ +static PyObject * +psutil_proc_io_priority_get(PyObject *self, PyObject *args) { + long pid; + HANDLE hProcess; + DWORD IoPriority; + NTSTATUS status; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + status = psutil_NtQueryInformationProcess( + hProcess, + ProcessIoPriority, + &IoPriority, + sizeof(DWORD), + NULL + ); + + CloseHandle(hProcess); + if (! NT_SUCCESS(status)) + return psutil_SetFromNTStatusErr(status, "NtQueryInformationProcess"); + return Py_BuildValue("i", IoPriority); +} + + +/* + * Set process IO priority. + */ +static PyObject * +psutil_proc_io_priority_set(PyObject *self, PyObject *args) { + long pid; + DWORD prio; + HANDLE hProcess; + NTSTATUS status; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + + if (! PyArg_ParseTuple(args, "li", &pid, &prio)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + status = psutil_NtSetInformationProcess( + hProcess, + ProcessIoPriority, + (PVOID)&prio, + sizeof(DWORD) + ); + + CloseHandle(hProcess); + if (! NT_SUCCESS(status)) + return psutil_SetFromNTStatusErr(status, "NtSetInformationProcess"); + Py_RETURN_NONE; +} +#endif + + +/* + * Return a Python tuple referencing process I/O counters. + */ +static PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + IO_COUNTERS IoCounters; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + + if (! GetProcessIoCounters(hProcess, &IoCounters)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + return Py_BuildValue("(KKKKKK)", + IoCounters.ReadOperationCount, + IoCounters.WriteOperationCount, + IoCounters.ReadTransferCount, + IoCounters.WriteTransferCount, + IoCounters.OtherOperationCount, + IoCounters.OtherTransferCount); +} + + +/* + * Return process CPU affinity as a bitmask + */ +static PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD_PTR proc_mask; + DWORD_PTR system_mask; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) { + return NULL; + } + if (GetProcessAffinityMask(hProcess, &proc_mask, &system_mask) == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); +#ifdef _WIN64 + return Py_BuildValue("K", (unsigned long long)proc_mask); +#else + return Py_BuildValue("k", (unsigned long)proc_mask); +#endif +} + + +/* + * Set process CPU affinity + */ +static PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + DWORD_PTR mask; + +#ifdef _WIN64 + if (! PyArg_ParseTuple(args, "lK", &pid, &mask)) +#else + if (! PyArg_ParseTuple(args, "lk", &pid, &mask)) +#endif + { + return NULL; + } + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + if (SetProcessAffinityMask(hProcess, mask) == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Return True if all process threads are in waiting/suspended state. + */ +static PyObject * +psutil_proc_is_suspended(PyObject *self, PyObject *args) { + DWORD pid; + ULONG i; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_get_proc_info(pid, &process, &buffer)) + return NULL; + for (i = 0; i < process->NumberOfThreads; i++) { + if (process->Threads[i].ThreadState != Waiting || + process->Threads[i].WaitReason != Suspended) + { + free(buffer); + Py_RETURN_FALSE; + } + } + free(buffer); + Py_RETURN_TRUE; +} + + +/* + * Return path's disk total and free as a Python tuple. + */ +static PyObject * +psutil_disk_usage(PyObject *self, PyObject *args) { + BOOL retval; + ULARGE_INTEGER _, total, free; + char *path; + + if (PyArg_ParseTuple(args, "u", &path)) { + Py_BEGIN_ALLOW_THREADS + retval = GetDiskFreeSpaceExW((LPCWSTR)path, &_, &total, &free); + Py_END_ALLOW_THREADS + goto return_; + } + + // on Python 2 we also want to accept plain strings other + // than Unicode +#if PY_MAJOR_VERSION <= 2 + PyErr_Clear(); // drop the argument parsing error + if (PyArg_ParseTuple(args, "s", &path)) { + Py_BEGIN_ALLOW_THREADS + retval = GetDiskFreeSpaceEx(path, &_, &total, &free); + Py_END_ALLOW_THREADS + goto return_; + } +#endif + + return NULL; + +return_: + if (retval == 0) + return PyErr_SetFromWindowsErrWithFilename(0, path); + else + return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + DWORD dwRetVal = 0; + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + MIB_IF_ROW2 *pIfRow = NULL; +#else // Windows XP + MIB_IFROW *pIfRow = NULL; +#endif + + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PyObject *py_retdict = PyDict_New(); + PyObject *py_nic_info = NULL; + PyObject *py_nic_name = NULL; + + if (py_retdict == NULL) + return NULL; + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + pCurrAddresses = pAddresses; + + while (pCurrAddresses) { + py_nic_name = NULL; + py_nic_info = NULL; + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + pIfRow = (MIB_IF_ROW2 *) malloc(sizeof(MIB_IF_ROW2)); +#else // Windows XP + pIfRow = (MIB_IFROW *) malloc(sizeof(MIB_IFROW)); +#endif + + if (pIfRow == NULL) { + PyErr_NoMemory(); + goto error; + } + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + SecureZeroMemory((PVOID)pIfRow, sizeof(MIB_IF_ROW2)); + pIfRow->InterfaceIndex = pCurrAddresses->IfIndex; + dwRetVal = GetIfEntry2(pIfRow); +#else // Windows XP + pIfRow->dwIndex = pCurrAddresses->IfIndex; + dwRetVal = GetIfEntry(pIfRow); +#endif + + if (dwRetVal != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, + "GetIfEntry() or GetIfEntry2() syscalls failed."); + goto error; + } + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + py_nic_info = Py_BuildValue("(KKKKKKKK)", + pIfRow->OutOctets, + pIfRow->InOctets, + (pIfRow->OutUcastPkts + pIfRow->OutNUcastPkts), + (pIfRow->InUcastPkts + pIfRow->InNUcastPkts), + pIfRow->InErrors, + pIfRow->OutErrors, + pIfRow->InDiscards, + pIfRow->OutDiscards); +#else // Windows XP + py_nic_info = Py_BuildValue("(kkkkkkkk)", + pIfRow->dwOutOctets, + pIfRow->dwInOctets, + (pIfRow->dwOutUcastPkts + pIfRow->dwOutNUcastPkts), + (pIfRow->dwInUcastPkts + pIfRow->dwInNUcastPkts), + pIfRow->dwInErrors, + pIfRow->dwOutErrors, + pIfRow->dwInDiscards, + pIfRow->dwOutDiscards); +#endif + + if (!py_nic_info) + goto error; + + py_nic_name = PyUnicode_FromWideChar( + pCurrAddresses->FriendlyName, + wcslen(pCurrAddresses->FriendlyName)); + + if (py_nic_name == NULL) + goto error; + if (PyDict_SetItem(py_retdict, py_nic_name, py_nic_info)) + goto error; + Py_CLEAR(py_nic_name); + Py_CLEAR(py_nic_info); + + free(pIfRow); + pCurrAddresses = pCurrAddresses->Next; + } + + free(pAddresses); + return py_retdict; + +error: + Py_XDECREF(py_nic_name); + Py_XDECREF(py_nic_info); + Py_DECREF(py_retdict); + if (pAddresses != NULL) + free(pAddresses); + if (pIfRow != NULL) + free(pIfRow); + return NULL; +} + + +/* + * Return a Python dict of tuples for disk I/O information. This may + * require running "diskperf -y" command first. + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + DISK_PERFORMANCE diskPerformance; + DWORD dwSize; + HANDLE hDevice = NULL; + char szDevice[MAX_PATH]; + char szDeviceDisplay[MAX_PATH]; + int devNum; + int i; + DWORD ioctrlSize; + BOOL ret; + PyObject *py_retdict = PyDict_New(); + PyObject *py_tuple = NULL; + + if (py_retdict == NULL) + return NULL; + // Apparently there's no way to figure out how many times we have + // to iterate in order to find valid drives. + // Let's assume 32, which is higher than 26, the number of letters + // in the alphabet (from A:\ to Z:\). + for (devNum = 0; devNum <= 32; ++devNum) { + py_tuple = NULL; + sprintf_s(szDevice, MAX_PATH, "\\\\.\\PhysicalDrive%d", devNum); + hDevice = CreateFile(szDevice, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (hDevice == INVALID_HANDLE_VALUE) + continue; + + // DeviceIoControl() sucks! + i = 0; + ioctrlSize = sizeof(diskPerformance); + while (1) { + i += 1; + ret = DeviceIoControl( + hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, &diskPerformance, + ioctrlSize, &dwSize, NULL); + if (ret != 0) + break; // OK! + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Retry with a bigger buffer (+ limit for retries). + if (i <= 1024) { + ioctrlSize *= 2; + continue; + } + } + else if (GetLastError() == ERROR_INVALID_FUNCTION) { + // This happens on AppVeyor: + // https://ci.appveyor.com/project/giampaolo/psutil/build/ + // 1364/job/ascpdi271b06jle3 + // Assume it means we're dealing with some exotic disk + // and go on. + psutil_debug("DeviceIoControl -> ERROR_INVALID_FUNCTION; " + "ignore PhysicalDrive%i", devNum); + goto next; + } + else if (GetLastError() == ERROR_NOT_SUPPORTED) { + // Again, let's assume we're dealing with some exotic disk. + psutil_debug("DeviceIoControl -> ERROR_NOT_SUPPORTED; " + "ignore PhysicalDrive%i", devNum); + goto next; + } + // XXX: it seems we should also catch ERROR_INVALID_PARAMETER: + // https://sites.ualberta.ca/dept/aict/uts/software/openbsd/ + // ports/4.1/i386/openafs/w-openafs-1.4.14-transarc/ + // openafs-1.4.14/src/usd/usd_nt.c + + // XXX: we can also bump into ERROR_MORE_DATA in which case + // (quoting doc) we're supposed to retry with a bigger buffer + // and specify a new "starting point", whatever it means. + PyErr_SetFromWindowsErr(0); + goto error; + } + + sprintf_s(szDeviceDisplay, MAX_PATH, "PhysicalDrive%i", devNum); + py_tuple = Py_BuildValue( + "(IILLKK)", + diskPerformance.ReadCount, + diskPerformance.WriteCount, + diskPerformance.BytesRead, + diskPerformance.BytesWritten, + // convert to ms: + // https://github.com/giampaolo/psutil/issues/1012 + (unsigned long long) + (diskPerformance.ReadTime.QuadPart) / 10000000, + (unsigned long long) + (diskPerformance.WriteTime.QuadPart) / 10000000); + if (!py_tuple) + goto error; + if (PyDict_SetItemString(py_retdict, szDeviceDisplay, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + +next: + CloseHandle(hDevice); + } + + return py_retdict; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retdict); + if (hDevice != NULL) + CloseHandle(hDevice); + return NULL; +} + + +static char *psutil_get_drive_type(int type) { + switch (type) { + case DRIVE_FIXED: + return "fixed"; + case DRIVE_CDROM: + return "cdrom"; + case DRIVE_REMOVABLE: + return "removable"; + case DRIVE_UNKNOWN: + return "unknown"; + case DRIVE_NO_ROOT_DIR: + return "unmounted"; + case DRIVE_REMOTE: + return "remote"; + case DRIVE_RAMDISK: + return "ramdisk"; + default: + return "?"; + } +} + + +#ifndef _ARRAYSIZE +#define _ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) +#endif + + +/* + * Return disk partitions as a list of tuples such as + * (drive_letter, drive_letter, type, "") + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + DWORD num_bytes; + char drive_strings[255]; + char *drive_letter = drive_strings; + char mp_buf[MAX_PATH]; + char mp_path[MAX_PATH]; + int all; + int type; + int ret; + unsigned int old_mode = 0; + char opts[20]; + HANDLE mp_h; + BOOL mp_flag= TRUE; + LPTSTR fs_type[MAX_PATH + 1] = { 0 }; + DWORD pflags = 0; + PyObject *py_all; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) { + return NULL; + } + + // avoid to visualize a message box in case something goes wrong + // see https://github.com/giampaolo/psutil/issues/264 + old_mode = SetErrorMode(SEM_FAILCRITICALERRORS); + + if (! PyArg_ParseTuple(args, "O", &py_all)) + goto error; + all = PyObject_IsTrue(py_all); + + Py_BEGIN_ALLOW_THREADS + num_bytes = GetLogicalDriveStrings(254, drive_letter); + Py_END_ALLOW_THREADS + + if (num_bytes == 0) { + PyErr_SetFromWindowsErr(0); + goto error; + } + + while (*drive_letter != 0) { + py_tuple = NULL; + opts[0] = 0; + fs_type[0] = 0; + + Py_BEGIN_ALLOW_THREADS + type = GetDriveType(drive_letter); + Py_END_ALLOW_THREADS + + // by default we only show hard drives and cd-roms + if (all == 0) { + if ((type == DRIVE_UNKNOWN) || + (type == DRIVE_NO_ROOT_DIR) || + (type == DRIVE_REMOTE) || + (type == DRIVE_RAMDISK)) { + goto next; + } + // floppy disk: skip it by default as it introduces a + // considerable slowdown. + if ((type == DRIVE_REMOVABLE) && + (strcmp(drive_letter, "A:\\") == 0)) { + goto next; + } + } + + ret = GetVolumeInformation( + (LPCTSTR)drive_letter, NULL, _ARRAYSIZE(drive_letter), + NULL, NULL, &pflags, (LPTSTR)fs_type, _ARRAYSIZE(fs_type)); + if (ret == 0) { + // We might get here in case of a floppy hard drive, in + // which case the error is (21, "device not ready"). + // Let's pretend it didn't happen as we already have + // the drive name and type ('removable'). + strcat_s(opts, _countof(opts), ""); + SetLastError(0); + } + else { + if (pflags & FILE_READ_ONLY_VOLUME) + strcat_s(opts, _countof(opts), "ro"); + else + strcat_s(opts, _countof(opts), "rw"); + if (pflags & FILE_VOLUME_IS_COMPRESSED) + strcat_s(opts, _countof(opts), ",compressed"); + + // Check for mount points on this volume and add/get info + // (checks first to know if we can even have mount points) + if (pflags & FILE_SUPPORTS_REPARSE_POINTS) { + + mp_h = FindFirstVolumeMountPoint(drive_letter, mp_buf, MAX_PATH); + if (mp_h != INVALID_HANDLE_VALUE) { + while (mp_flag) { + + // Append full mount path with drive letter + strcpy_s(mp_path, _countof(mp_path), drive_letter); + strcat_s(mp_path, _countof(mp_path), mp_buf); + + py_tuple = Py_BuildValue( + "(ssss)", + drive_letter, + mp_path, + fs_type, // Typically NTFS + opts); + + if (!py_tuple || PyList_Append(py_retlist, py_tuple) == -1) { + FindVolumeMountPointClose(mp_h); + goto error; + } + + Py_CLEAR(py_tuple); + + // Continue looking for more mount points + mp_flag = FindNextVolumeMountPoint(mp_h, mp_buf, MAX_PATH); + } + FindVolumeMountPointClose(mp_h); + } + + } + } + + if (strlen(opts) > 0) + strcat_s(opts, _countof(opts), ","); + strcat_s(opts, _countof(opts), psutil_get_drive_type(type)); + + py_tuple = Py_BuildValue( + "(ssss)", + drive_letter, + drive_letter, + fs_type, // either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS + opts); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + goto next; + +next: + drive_letter = strchr(drive_letter, 0) + 1; + } + + SetErrorMode(old_mode); + return py_retlist; + +error: + SetErrorMode(old_mode); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; + WCHAR *buffer_user = NULL; + LPTSTR buffer_addr = NULL; + PWTS_SESSION_INFO sessions = NULL; + DWORD count; + DWORD i; + DWORD sessionId; + DWORD bytes; + PWTS_CLIENT_ADDRESS address; + char address_str[50]; + long long unix_time; + WINSTATION_INFO station_info; + ULONG returnLen; + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_username = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (WTSEnumerateSessions(hServer, 0, 1, &sessions, &count) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessions"); + goto error; + } + + for (i = 0; i < count; i++) { + py_address = NULL; + py_tuple = NULL; + sessionId = sessions[i].SessionId; + if (buffer_user != NULL) + WTSFreeMemory(buffer_user); + if (buffer_addr != NULL) + WTSFreeMemory(buffer_addr); + + buffer_user = NULL; + buffer_addr = NULL; + + // username + bytes = 0; + if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, + &buffer_user, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + goto error; + } + if (bytes <= 2) + continue; + + // address + bytes = 0; + if (WTSQuerySessionInformation(hServer, sessionId, WTSClientAddress, + &buffer_addr, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformation"); + goto error; + } + + address = (PWTS_CLIENT_ADDRESS)buffer_addr; + if (address->AddressFamily == 0) { // AF_INET + sprintf_s(address_str, + _countof(address_str), + "%u.%u.%u.%u", + address->Address[0], + address->Address[1], + address->Address[2], + address->Address[3]); + py_address = Py_BuildValue("s", address_str); + if (!py_address) + goto error; + } + else { + py_address = Py_None; + } + + // login time + if (! psutil_WinStationQueryInformationW( + hServer, + sessionId, + WinStationInformation, + &station_info, + sizeof(station_info), + &returnLen)) + { + PyErr_SetFromOSErrnoWithSyscall("WinStationQueryInformationW"); + goto error; + } + + unix_time = ((LONGLONG)station_info.ConnectTime.dwHighDateTime) << 32; + unix_time += \ + station_info.ConnectTime.dwLowDateTime - 116444736000000000LL; + unix_time /= 10000000; + + py_username = PyUnicode_FromWideChar(buffer_user, wcslen(buffer_user)); + if (py_username == NULL) + goto error; + py_tuple = Py_BuildValue("OOd", + py_username, + py_address, + (double)unix_time); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_address); + Py_CLEAR(py_tuple); + } + + WTSFreeMemory(sessions); + WTSFreeMemory(buffer_user); + WTSFreeMemory(buffer_addr); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_DECREF(py_retlist); + + if (sessions != NULL) + WTSFreeMemory(sessions); + if (buffer_user != NULL) + WTSFreeMemory(buffer_user); + if (buffer_addr != NULL) + WTSFreeMemory(buffer_addr); + return NULL; +} + + +/* + * Return the number of handles opened by process. + */ +static PyObject * +psutil_proc_num_handles(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD handleCount; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + if (! GetProcessHandleCount(hProcess, &handleCount)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + CloseHandle(hProcess); + return Py_BuildValue("k", handleCount); +} + + +/* + * Get various process information by using NtQuerySystemInformation. + * We use this as a fallback when faster functions fail with access + * denied. This is slower because it iterates over all processes. + * Returned tuple includes the following process info: + * + * - num_threads() + * - ctx_switches() + * - num_handles() (fallback) + * - cpu_times() (fallback) + * - create_time() (fallback) + * - io_counters() (fallback) + * - memory_info() (fallback) + */ +static PyObject * +psutil_proc_info(PyObject *self, PyObject *args) { + DWORD pid; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + ULONG i; + ULONG ctx_switches = 0; + double user_time; + double kernel_time; + long long create_time; + SIZE_T mem_private; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_get_proc_info(pid, &process, &buffer)) + return NULL; + + for (i = 0; i < process->NumberOfThreads; i++) + ctx_switches += process->Threads[i].ContextSwitches; + user_time = (double)process->UserTime.HighPart * HI_T + \ + (double)process->UserTime.LowPart * LO_T; + kernel_time = (double)process->KernelTime.HighPart * HI_T + \ + (double)process->KernelTime.LowPart * LO_T; + + // Convert the LARGE_INTEGER union to a Unix time. + // It's the best I could find by googling and borrowing code here + // and there. The time returned has a precision of 1 second. + if (0 == pid || 4 == pid) { + // the python module will translate this into BOOT_TIME later + create_time = 0; + } + else { + create_time = ((LONGLONG)process->CreateTime.HighPart) << 32; + create_time += process->CreateTime.LowPart - 116444736000000000LL; + create_time /= 10000000; + } + +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + mem_private = process->PrivatePageCount; +#else + mem_private = 0; +#endif + + py_retlist = Py_BuildValue( +#if defined(_WIN64) + "kkdddiKKKKKK" "kKKKKKKKKK", +#else + "kkdddiKKKKKK" "kIIIIIIIII", +#endif + process->HandleCount, // num handles + ctx_switches, // num ctx switches + user_time, // cpu user time + kernel_time, // cpu kernel time + (double)create_time, // create time + (int)process->NumberOfThreads, // num threads + // IO counters + process->ReadOperationCount.QuadPart, // io rcount + process->WriteOperationCount.QuadPart, // io wcount + process->ReadTransferCount.QuadPart, // io rbytes + process->WriteTransferCount.QuadPart, // io wbytes + process->OtherOperationCount.QuadPart, // io others count + process->OtherTransferCount.QuadPart, // io others bytes + // memory + process->PageFaultCount, // num page faults + process->PeakWorkingSetSize, // peak wset + process->WorkingSetSize, // wset + process->QuotaPeakPagedPoolUsage, // peak paged pool + process->QuotaPagedPoolUsage, // paged pool + process->QuotaPeakNonPagedPoolUsage, // peak non paged pool + process->QuotaNonPagedPoolUsage, // non paged pool + process->PagefileUsage, // pagefile + process->PeakPagefileUsage, // peak pagefile + mem_private // private + ); + + free(buffer); + return py_retlist; +} + + +static char *get_region_protection_string(ULONG protection) { + switch (protection & 0xff) { + case PAGE_NOACCESS: + return ""; + case PAGE_READONLY: + return "r"; + case PAGE_READWRITE: + return "rw"; + case PAGE_WRITECOPY: + return "wc"; + case PAGE_EXECUTE: + return "x"; + case PAGE_EXECUTE_READ: + return "xr"; + case PAGE_EXECUTE_READWRITE: + return "xrw"; + case PAGE_EXECUTE_WRITECOPY: + return "xwc"; + default: + return "?"; + } +} + + +/* + * Return a list of process's memory mappings. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) { + MEMORY_BASIC_INFORMATION basicInfo; + DWORD pid; + HANDLE hProcess = NULL; + PVOID baseAddress; + ULONGLONG previousAllocationBase; + WCHAR mappedFileName[MAX_PATH]; + LPVOID maxAddr; + // required by GetMappedFileNameW + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_str = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + hProcess = psutil_handle_from_pid(pid, access); + if (NULL == hProcess) + goto error; + + maxAddr = PSUTIL_SYSTEM_INFO.lpMaximumApplicationAddress; + baseAddress = NULL; + + while (VirtualQueryEx(hProcess, baseAddress, &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION))) + { + py_tuple = NULL; + if (baseAddress > maxAddr) + break; + if (GetMappedFileNameW(hProcess, baseAddress, mappedFileName, + sizeof(mappedFileName))) + { + py_str = PyUnicode_FromWideChar(mappedFileName, + wcslen(mappedFileName)); + if (py_str == NULL) + goto error; +#ifdef _WIN64 + py_tuple = Py_BuildValue( + "(KsOI)", + (unsigned long long)baseAddress, +#else + py_tuple = Py_BuildValue( + "(ksOI)", + (unsigned long)baseAddress, +#endif + get_region_protection_string(basicInfo.Protect), + py_str, + basicInfo.RegionSize); + + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_str); + } + previousAllocationBase = (ULONGLONG)basicInfo.AllocationBase; + baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize; + } + + CloseHandle(hProcess); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_str); + Py_DECREF(py_retlist); + if (hProcess != NULL) + CloseHandle(hProcess); + return NULL; +} + + +/* + * Return a {pid:ppid, ...} dict for all running processes. + */ +static PyObject * +psutil_ppid_map(PyObject *self, PyObject *args) { + PyObject *py_pid = NULL; + PyObject *py_ppid = NULL; + PyObject *py_retdict = PyDict_New(); + HANDLE handle = NULL; + PROCESSENTRY32 pe = {0}; + pe.dwSize = sizeof(PROCESSENTRY32); + + if (py_retdict == NULL) + return NULL; + handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + Py_DECREF(py_retdict); + return NULL; + } + + if (Process32First(handle, &pe)) { + do { + py_pid = Py_BuildValue("I", pe.th32ProcessID); + if (py_pid == NULL) + goto error; + py_ppid = Py_BuildValue("I", pe.th32ParentProcessID); + if (py_ppid == NULL) + goto error; + if (PyDict_SetItem(py_retdict, py_pid, py_ppid)) + goto error; + Py_CLEAR(py_pid); + Py_CLEAR(py_ppid); + } while (Process32Next(handle, &pe)); + } + + CloseHandle(handle); + return py_retdict; + +error: + Py_XDECREF(py_pid); + Py_XDECREF(py_ppid); + Py_DECREF(py_retdict); + CloseHandle(handle); + return NULL; +} + + +/* + * Return NICs addresses. + */ + +static PyObject * +psutil_net_if_addrs(PyObject *self, PyObject *args) { + unsigned int i = 0; + ULONG family; + PCTSTR intRet; + PCTSTR netmaskIntRet; + char *ptr; + char buff_addr[1024]; + char buff_macaddr[1024]; + char buff_netmask[1024]; + DWORD dwRetVal = 0; +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + ULONG converted_netmask; + UINT netmask_bits; + struct in_addr in_netmask; +#endif + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_mac_address = NULL; + PyObject *py_nic_name = NULL; + PyObject *py_netmask = NULL; + + if (py_retlist == NULL) + return NULL; + + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + pCurrAddresses = pAddresses; + + while (pCurrAddresses) { + pUnicast = pCurrAddresses->FirstUnicastAddress; + + netmaskIntRet = NULL; + py_nic_name = NULL; + py_nic_name = PyUnicode_FromWideChar( + pCurrAddresses->FriendlyName, + wcslen(pCurrAddresses->FriendlyName)); + if (py_nic_name == NULL) + goto error; + + // MAC address + if (pCurrAddresses->PhysicalAddressLength != 0) { + ptr = buff_macaddr; + *ptr = '\0'; + for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) { + if (i == (pCurrAddresses->PhysicalAddressLength - 1)) { + sprintf_s(ptr, _countof(buff_macaddr), "%.2X\n", + (int)pCurrAddresses->PhysicalAddress[i]); + } + else { + sprintf_s(ptr, _countof(buff_macaddr), "%.2X-", + (int)pCurrAddresses->PhysicalAddress[i]); + } + ptr += 3; + } + *--ptr = '\0'; + + py_mac_address = Py_BuildValue("s", buff_macaddr); + if (py_mac_address == NULL) + goto error; + + Py_INCREF(Py_None); + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(OiOOOO)", + py_nic_name, + -1, // this will be converted later to AF_LINK + py_mac_address, + Py_None, // netmask (not supported) + Py_None, // broadcast (not supported) + Py_None // ptp (not supported on Windows) + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_mac_address); + } + + // find out the IP address associated with the NIC + if (pUnicast != NULL) { + for (i = 0; pUnicast != NULL; i++) { + family = pUnicast->Address.lpSockaddr->sa_family; + if (family == AF_INET) { + struct sockaddr_in *sa_in = (struct sockaddr_in *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET, &(sa_in->sin_addr), buff_addr, + sizeof(buff_addr)); + if (!intRet) + goto error; +#if (_WIN32_WINNT >= 0x0600) // Windows Vista and above + netmask_bits = pUnicast->OnLinkPrefixLength; + dwRetVal = ConvertLengthToIpv4Mask(netmask_bits, &converted_netmask); + if (dwRetVal == NO_ERROR) { + in_netmask.s_addr = converted_netmask; + netmaskIntRet = inet_ntop( + AF_INET, &in_netmask, buff_netmask, + sizeof(buff_netmask)); + if (!netmaskIntRet) + goto error; + } +#endif + } + else if (family == AF_INET6) { + struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET6, &(sa_in6->sin6_addr), + buff_addr, sizeof(buff_addr)); + if (!intRet) + goto error; + } + else { + // we should never get here + pUnicast = pUnicast->Next; + continue; + } + +#if PY_MAJOR_VERSION >= 3 + py_address = PyUnicode_FromString(buff_addr); +#else + py_address = PyString_FromString(buff_addr); +#endif + if (py_address == NULL) + goto error; + + if (netmaskIntRet != NULL) { +#if PY_MAJOR_VERSION >= 3 + py_netmask = PyUnicode_FromString(buff_netmask); +#else + py_netmask = PyString_FromString(buff_netmask); +#endif + } else { + Py_INCREF(Py_None); + py_netmask = Py_None; + } + + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(OiOOOO)", + py_nic_name, + family, + py_address, + py_netmask, + Py_None, // broadcast (not supported) + Py_None // ptp (not supported on Windows) + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_address); + Py_CLEAR(py_netmask); + + pUnicast = pUnicast->Next; + } + } + Py_CLEAR(py_nic_name); + pCurrAddresses = pCurrAddresses->Next; + } + + free(pAddresses); + return py_retlist; + +error: + if (pAddresses) + free(pAddresses); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_XDECREF(py_nic_name); + Py_XDECREF(py_netmask); + return NULL; +} + + +/* + * Provides stats about NIC interfaces installed on the system. + * TODO: get 'duplex' (currently it's hard coded to '2', aka + 'full duplex') + */ +static PyObject * +psutil_net_if_stats(PyObject *self, PyObject *args) { + int i; + DWORD dwSize = 0; + DWORD dwRetVal = 0; + MIB_IFTABLE *pIfTable; + MIB_IFROW *pIfRow; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + char descr[MAX_PATH]; + int ifname_found; + + PyObject *py_nic_name = NULL; + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + PyObject *py_is_up = NULL; + + if (py_retdict == NULL) + return NULL; + + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + + pIfTable = (MIB_IFTABLE *) malloc(sizeof (MIB_IFTABLE)); + if (pIfTable == NULL) { + PyErr_NoMemory(); + goto error; + } + dwSize = sizeof(MIB_IFTABLE); + if (GetIfTable(pIfTable, &dwSize, FALSE) == ERROR_INSUFFICIENT_BUFFER) { + free(pIfTable); + pIfTable = (MIB_IFTABLE *) malloc(dwSize); + if (pIfTable == NULL) { + PyErr_NoMemory(); + goto error; + } + } + // Make a second call to GetIfTable to get the actual + // data we want. + if ((dwRetVal = GetIfTable(pIfTable, &dwSize, FALSE)) != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "GetIfTable() syscall failed"); + goto error; + } + + for (i = 0; i < (int) pIfTable->dwNumEntries; i++) { + pIfRow = (MIB_IFROW *) & pIfTable->table[i]; + + // GetIfTable is not able to give us NIC with "friendly names" + // so we determine them via GetAdapterAddresses() which + // provides friendly names *and* descriptions and find the + // ones that match. + ifname_found = 0; + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + sprintf_s(descr, MAX_PATH, "%wS", pCurrAddresses->Description); + if (lstrcmp(descr, pIfRow->bDescr) == 0) { + py_nic_name = PyUnicode_FromWideChar( + pCurrAddresses->FriendlyName, + wcslen(pCurrAddresses->FriendlyName)); + if (py_nic_name == NULL) + goto error; + ifname_found = 1; + break; + } + pCurrAddresses = pCurrAddresses->Next; + } + if (ifname_found == 0) { + // Name not found means GetAdapterAddresses() doesn't list + // this NIC, only GetIfTable, meaning it's not really a NIC + // interface so we skip it. + continue; + } + + // is up? + if((pIfRow->dwOperStatus == MIB_IF_OPER_STATUS_CONNECTED || + pIfRow->dwOperStatus == MIB_IF_OPER_STATUS_OPERATIONAL) && + pIfRow->dwAdminStatus == 1 ) { + py_is_up = Py_True; + } + else { + py_is_up = Py_False; + } + Py_INCREF(py_is_up); + + py_ifc_info = Py_BuildValue( + "(Oikk)", + py_is_up, + 2, // there's no way to know duplex so let's assume 'full' + pIfRow->dwSpeed / 1000000, // expressed in bytes, we want Mb + pIfRow->dwMtu + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItem(py_retdict, py_nic_name, py_ifc_info)) + goto error; + Py_CLEAR(py_nic_name); + Py_CLEAR(py_ifc_info); + } + + free(pIfTable); + free(pAddresses); + return py_retdict; + +error: + Py_XDECREF(py_is_up); + Py_XDECREF(py_ifc_info); + Py_XDECREF(py_nic_name); + Py_DECREF(py_retdict); + if (pIfTable != NULL) + free(pIfTable); + if (pAddresses != NULL) + free(pAddresses); + return NULL; +} + + +/* + * Return CPU statistics. + */ +static PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + NTSTATUS status; + _SYSTEM_PERFORMANCE_INFORMATION *spi = NULL; + _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *sppi = NULL; + _SYSTEM_INTERRUPT_INFORMATION *InterruptInformation = NULL; + unsigned int ncpus; + UINT i; + ULONG64 dpcs = 0; + ULONG interrupts = 0; + + // retrieves number of processors + ncpus = psutil_get_num_cpus(1); + if (ncpus == 0) + goto error; + + // get syscalls / ctx switches + spi = (_SYSTEM_PERFORMANCE_INFORMATION *) \ + malloc(ncpus * sizeof(_SYSTEM_PERFORMANCE_INFORMATION)); + if (spi == NULL) { + PyErr_NoMemory(); + goto error; + } + status = psutil_NtQuerySystemInformation( + SystemPerformanceInformation, + spi, + ncpus * sizeof(_SYSTEM_PERFORMANCE_INFORMATION), + NULL); + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQuerySystemInformation(SystemPerformanceInformation)"); + goto error; + } + + // get DPCs + InterruptInformation = \ + malloc(sizeof(_SYSTEM_INTERRUPT_INFORMATION) * ncpus); + if (InterruptInformation == NULL) { + PyErr_NoMemory(); + goto error; + } + + status = psutil_NtQuerySystemInformation( + SystemInterruptInformation, + InterruptInformation, + ncpus * sizeof(SYSTEM_INTERRUPT_INFORMATION), + NULL); + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQuerySystemInformation(SystemInterruptInformation)"); + goto error; + } + for (i = 0; i < ncpus; i++) { + dpcs += InterruptInformation[i].DpcCount; + } + + // get interrupts + sppi = (_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *) \ + malloc(ncpus * sizeof(_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)); + if (sppi == NULL) { + PyErr_NoMemory(); + goto error; + } + + status = psutil_NtQuerySystemInformation( + SystemProcessorPerformanceInformation, + sppi, + ncpus * sizeof(_SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), + NULL); + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, + "NtQuerySystemInformation(SystemProcessorPerformanceInformation)"); + goto error; + } + + for (i = 0; i < ncpus; i++) { + interrupts += sppi[i].InterruptCount; + } + + // done + free(spi); + free(InterruptInformation); + free(sppi); + return Py_BuildValue( + "kkkk", + spi->ContextSwitches, + interrupts, + (unsigned long)dpcs, + spi->SystemCalls + ); + +error: + if (spi) + free(spi); + if (InterruptInformation) + free(InterruptInformation); + if (sppi) + free(sppi); + return NULL; +} + + +/* + * Return CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + PROCESSOR_POWER_INFORMATION *ppi; + NTSTATUS ret; + ULONG size; + LPBYTE pBuffer = NULL; + ULONG current; + ULONG max; + unsigned int ncpus; + + // Get the number of CPUs. + ncpus = psutil_get_num_cpus(1); + if (ncpus == 0) + return NULL; + + // Allocate size. + size = ncpus * sizeof(PROCESSOR_POWER_INFORMATION); + pBuffer = (BYTE*)LocalAlloc(LPTR, size); + if (! pBuffer) + return PyErr_SetFromWindowsErr(0); + + // Syscall. + ret = CallNtPowerInformation( + ProcessorInformation, NULL, 0, pBuffer, size); + if (ret != 0) { + PyErr_SetString(PyExc_RuntimeError, + "CallNtPowerInformation syscall failed"); + goto error; + } + + // Results. + ppi = (PROCESSOR_POWER_INFORMATION *)pBuffer; + max = ppi->MaxMhz; + current = ppi->CurrentMhz; + LocalFree(pBuffer); + + return Py_BuildValue("kk", current, max); + +error: + if (pBuffer != NULL) + LocalFree(pBuffer); + return NULL; +} + + +/* + * Return battery usage stats. + */ +static PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + SYSTEM_POWER_STATUS sps; + + if (GetSystemPowerStatus(&sps) == 0) + return PyErr_SetFromWindowsErr(0); + return Py_BuildValue( + "iiiI", + sps.ACLineStatus, // whether AC is connected: 0=no, 1=yes, 255=unknown + // status flag: + // 1, 2, 4 = high, low, critical + // 8 = charging + // 128 = no battery + sps.BatteryFlag, + sps.BatteryLifePercent, // percent + sps.BatteryLifeTime // remaining secs + ); +} + + +/* + * System memory page size as an int. + */ +static PyObject * +psutil_getpagesize(PyObject *self, PyObject *args) { + // XXX: we may want to use GetNativeSystemInfo to differentiate + // page size for WoW64 processes (but am not sure). + return Py_BuildValue("I", PSUTIL_SYSTEM_INFO.dwPageSize); +} + + +// ------------------------ Python init --------------------------- + +static PyMethodDef +PsutilMethods[] = { + // --- per-process functions + {"proc_cmdline", (PyCFunction)(void(*)(void))psutil_proc_cmdline, + METH_VARARGS | METH_KEYWORDS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_environ", psutil_proc_environ, METH_VARARGS, + "Return process environment data"}, + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return path of the process executable"}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_kill", psutil_proc_kill, METH_VARARGS, + "Kill the process identified by the given PID"}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return tuple of user/kern time for the given PID"}, + {"proc_create_time", psutil_proc_create_time, METH_VARARGS, + "Return a float indicating the process create time expressed in " + "seconds since the epoch"}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, + "Return a tuple of process memory information"}, + {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS, + "Return the USS of the process"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory"}, + {"proc_suspend_or_resume", psutil_proc_suspend_or_resume, METH_VARARGS, + "Suspend or resume a process"}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process"}, + {"proc_username", psutil_proc_username, METH_VARARGS, + "Return the username of a process"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads information as a list of tuple"}, + {"proc_wait", psutil_proc_wait, METH_VARARGS, + "Wait for process to terminate and return its exit code."}, + {"proc_priority_get", psutil_proc_priority_get, METH_VARARGS, + "Return process priority."}, + {"proc_priority_set", psutil_proc_priority_set, METH_VARARGS, + "Set process priority."}, +#if (_WIN32_WINNT >= 0x0600) // Windows Vista + {"proc_io_priority_get", psutil_proc_io_priority_get, METH_VARARGS, + "Return process IO priority."}, + {"proc_io_priority_set", psutil_proc_io_priority_set, METH_VARARGS, + "Set process IO priority."}, +#endif + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, + "Return process CPU affinity as a bitmask."}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, + "Set process CPU affinity."}, + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, + "Get process I/O counters."}, + {"proc_is_suspended", psutil_proc_is_suspended, METH_VARARGS, + "Return True if one of the process threads is in a suspended state"}, + {"proc_num_handles", psutil_proc_num_handles, METH_VARARGS, + "Return the number of handles opened by process."}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return a list of process's memory mappings"}, + + // --- alternative pinfo interface + {"proc_info", psutil_proc_info, METH_VARARGS, + "Various process information"}, + + // --- system-related functions + {"pids", psutil_pids, METH_VARARGS, + "Returns a list of PIDs currently running on the system"}, + {"ppid_map", psutil_ppid_map, METH_VARARGS, + "Return a {pid:ppid, ...} dict for all running processes"}, + {"pid_exists", psutil_pid_exists, METH_VARARGS, + "Determine if the process exists in the current process list."}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, + "Returns the number of logical CPUs on the system"}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Returns the number of physical CPUs on the system"}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return the system boot time expressed in seconds since the epoch."}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return the total amount of physical memory, in bytes"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a list"}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"disk_usage", psutil_disk_usage, METH_VARARGS, + "Return path's disk total and free as a Python tuple."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return dict of tuples of networks I/O information."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return dict of tuples of disks I/O information."}, + {"users", psutil_users, METH_VARARGS, + "Return a list of currently connected users."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk partitions."}, + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide connections"}, + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, + "Return NICs addresses."}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NICs stats."}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS, + "Return NICs stats."}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return CPU frequency."}, +#if (_WIN32_WINNT >= 0x0600) // Windows Vista + {"init_loadavg_counter", (PyCFunction)psutil_init_loadavg_counter, + METH_VARARGS, + "Initializes the emulated load average calculator."}, + {"getloadavg", (PyCFunction)psutil_get_loadavg, METH_VARARGS, + "Returns the emulated POSIX-like load average."}, +#endif + {"sensors_battery", psutil_sensors_battery, METH_VARARGS, + "Return battery metrics usage."}, + {"getpagesize", psutil_getpagesize, METH_VARARGS, + "Return system memory page size."}, + + // --- windows services + {"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS, + "List all services"}, + {"winservice_query_config", psutil_winservice_query_config, METH_VARARGS, + "Return service config"}, + {"winservice_query_status", psutil_winservice_query_status, METH_VARARGS, + "Return service config"}, + {"winservice_query_descr", psutil_winservice_query_descr, METH_VARARGS, + "Return the description of a service"}, + {"winservice_start", psutil_winservice_start, METH_VARARGS, + "Start a service"}, + {"winservice_stop", psutil_winservice_stop, METH_VARARGS, + "Stop a service"}, + + // --- windows API bindings + {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS, + "QueryDosDevice binding"}, + + // --- others + {"set_testing", psutil_set_testing, METH_NOARGS, + "Set psutil in testing mode"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +static struct module_state _state; +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int psutil_windows_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int psutil_windows_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_windows", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_windows_traverse, + psutil_windows_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_windows(void) + +#else +#define INITERROR return +void init_psutil_windows(void) +#endif +{ + struct module_state *st = NULL; +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_windows", PsutilMethods); +#endif + if (module == NULL) + INITERROR; + + if (psutil_setup() != 0) + INITERROR; + if (psutil_load_globals() != 0) + INITERROR; + if (psutil_set_se_debug() != 0) + INITERROR; + + st = GETSTATE(module); + st->error = PyErr_NewException("_psutil_windows.Error", NULL, NULL); + if (st->error == NULL) { + Py_DECREF(module); + INITERROR; + } + + // Exceptions. + TimeoutExpired = PyErr_NewException( + "_psutil_windows.TimeoutExpired", NULL, NULL); + Py_INCREF(TimeoutExpired); + PyModule_AddObject(module, "TimeoutExpired", TimeoutExpired); + + TimeoutAbandoned = PyErr_NewException( + "_psutil_windows.TimeoutAbandoned", NULL, NULL); + Py_INCREF(TimeoutAbandoned); + PyModule_AddObject(module, "TimeoutAbandoned", TimeoutAbandoned); + + // version constant + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + // process status constants + // http://msdn.microsoft.com/en-us/library/ms683211(v=vs.85).aspx + PyModule_AddIntConstant( + module, "ABOVE_NORMAL_PRIORITY_CLASS", ABOVE_NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "BELOW_NORMAL_PRIORITY_CLASS", BELOW_NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "HIGH_PRIORITY_CLASS", HIGH_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "IDLE_PRIORITY_CLASS", IDLE_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "NORMAL_PRIORITY_CLASS", NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "REALTIME_PRIORITY_CLASS", REALTIME_PRIORITY_CLASS); + + // connection status constants + // http://msdn.microsoft.com/en-us/library/cc669305.aspx + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSED", MIB_TCP_STATE_CLOSED); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSING", MIB_TCP_STATE_CLOSING); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSE_WAIT", MIB_TCP_STATE_CLOSE_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_LISTEN", MIB_TCP_STATE_LISTEN); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_ESTAB", MIB_TCP_STATE_ESTAB); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_SYN_SENT", MIB_TCP_STATE_SYN_SENT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_SYN_RCVD", MIB_TCP_STATE_SYN_RCVD); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_FIN_WAIT1", MIB_TCP_STATE_FIN_WAIT1); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_FIN_WAIT2", MIB_TCP_STATE_FIN_WAIT2); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_LAST_ACK", MIB_TCP_STATE_LAST_ACK); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_TIME_WAIT", MIB_TCP_STATE_TIME_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_TIME_WAIT", MIB_TCP_STATE_TIME_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_DELETE_TCB", MIB_TCP_STATE_DELETE_TCB); + PyModule_AddIntConstant( + module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + // service status constants + /* + PyModule_AddIntConstant( + module, "SERVICE_CONTINUE_PENDING", SERVICE_CONTINUE_PENDING); + PyModule_AddIntConstant( + module, "SERVICE_PAUSE_PENDING", SERVICE_PAUSE_PENDING); + PyModule_AddIntConstant( + module, "SERVICE_PAUSED", SERVICE_PAUSED); + PyModule_AddIntConstant( + module, "SERVICE_RUNNING", SERVICE_RUNNING); + PyModule_AddIntConstant( + module, "SERVICE_START_PENDING", SERVICE_START_PENDING); + PyModule_AddIntConstant( + module, "SERVICE_STOP_PENDING", SERVICE_STOP_PENDING); + PyModule_AddIntConstant( + module, "SERVICE_STOPPED", SERVICE_STOPPED); + */ + + // ...for internal use in _psutil_windows.py + PyModule_AddIntConstant( + module, "INFINITE", INFINITE); + PyModule_AddIntConstant( + module, "ERROR_ACCESS_DENIED", ERROR_ACCESS_DENIED); + PyModule_AddIntConstant( + module, "ERROR_INVALID_NAME", ERROR_INVALID_NAME); + PyModule_AddIntConstant( + module, "ERROR_SERVICE_DOES_NOT_EXIST", ERROR_SERVICE_DOES_NOT_EXIST); + PyModule_AddIntConstant( + module, "ERROR_PRIVILEGE_NOT_HELD", ERROR_PRIVILEGE_NOT_HELD); + PyModule_AddIntConstant( + module, "WINVER", PSUTIL_WINVER); + PyModule_AddIntConstant( + module, "WINDOWS_XP", PSUTIL_WINDOWS_XP); + PyModule_AddIntConstant( + module, "WINDOWS_SERVER_2003", PSUTIL_WINDOWS_SERVER_2003); + PyModule_AddIntConstant( + module, "WINDOWS_VISTA", PSUTIL_WINDOWS_VISTA); + PyModule_AddIntConstant( + module, "WINDOWS_7", PSUTIL_WINDOWS_7); + PyModule_AddIntConstant( + module, "WINDOWS_8", PSUTIL_WINDOWS_8); + PyModule_AddIntConstant( + module, "WINDOWS_8_1", PSUTIL_WINDOWS_8_1); + PyModule_AddIntConstant( + module, "WINDOWS_10", PSUTIL_WINDOWS_10); + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/ddtrace/vendor/psutil/_pswindows.py b/ddtrace/vendor/psutil/_pswindows.py new file mode 100644 index 00000000000..636b0af905c --- /dev/null +++ b/ddtrace/vendor/psutil/_pswindows.py @@ -0,0 +1,1127 @@ +# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Windows platform implementation.""" + +import contextlib +import errno +import functools +import os +import sys +import time +from collections import namedtuple + +from . import _common +try: + from . import _psutil_windows as cext +except ImportError as err: + if str(err).lower().startswith("dll load failed") and \ + sys.getwindowsversion()[0] < 6: + # We may get here if: + # 1) we are on an old Windows version + # 2) psutil was installed via pip + wheel + # See: https://github.com/giampaolo/psutil/issues/811 + # It must be noted that psutil can still (kind of) work + # on outdated systems if compiled / installed from sources, + # but if we get here it means this this was a wheel (or exe). + msg = "this Windows version is too old (< Windows Vista); " + msg += "psutil 3.4.2 is the latest version which supports Windows " + msg += "2000, XP and 2003 server" + raise RuntimeError(msg) + else: + raise + +from ._common import conn_tmap +from ._common import conn_to_ntuple +from ._common import ENCODING +from ._common import ENCODING_ERRS +from ._common import isfile_strict +from ._common import memoize +from ._common import memoize_when_activated +from ._common import parse_environ_block +from ._common import usage_percent +from ._compat import long +from ._compat import lru_cache +from ._compat import PY3 +from ._compat import unicode +from ._compat import xrange +from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS +from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS +from ._psutil_windows import HIGH_PRIORITY_CLASS +from ._psutil_windows import IDLE_PRIORITY_CLASS +from ._psutil_windows import NORMAL_PRIORITY_CLASS +from ._psutil_windows import REALTIME_PRIORITY_CLASS + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + +# process priority constants, import from __init__.py: +# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx +__extra__all__ = [ + "win_service_iter", "win_service_get", + # Process priority + "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", + "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS", + "REALTIME_PRIORITY_CLASS", + # IO priority + "IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH", + # others + "CONN_DELETE_TCB", "AF_LINK", +] + + +# ===================================================================== +# --- globals +# ===================================================================== + +CONN_DELETE_TCB = "DELETE_TCB" +HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_io_priority_get") +HAS_GETLOADAVG = hasattr(cext, "getloadavg") +ERROR_PARTIAL_COPY = 299 + + +if enum is None: + AF_LINK = -1 +else: + AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) + AF_LINK = AddressFamily.AF_LINK + +TCP_STATUSES = { + cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, + cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT, + cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV, + cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1, + cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2, + cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE, + cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK, + cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN, + cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING, + cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +if enum is not None: + class Priority(enum.IntEnum): + ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS + HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS + + globals().update(Priority.__members__) + +if enum is None: + IOPRIO_VERYLOW = 0 + IOPRIO_LOW = 1 + IOPRIO_NORMAL = 2 + IOPRIO_HIGH = 3 +else: + class IOPriority(enum.IntEnum): + IOPRIO_VERYLOW = 0 + IOPRIO_LOW = 1 + IOPRIO_NORMAL = 2 + IOPRIO_HIGH = 3 + globals().update(IOPriority.__members__) + +pinfo_map = dict( + num_handles=0, + ctx_switches=1, + user_time=2, + kernel_time=3, + create_time=4, + num_threads=5, + io_rcount=6, + io_wcount=7, + io_rbytes=8, + io_wbytes=9, + io_count_others=10, + io_bytes_others=11, + num_page_faults=12, + peak_wset=13, + wset=14, + peak_paged_pool=15, + paged_pool=16, + peak_non_paged_pool=17, + non_paged_pool=18, + pagefile=19, + peak_pagefile=20, + mem_private=21, +) + +# These objects get set on "import psutil" from the __init__.py +# file, see: https://github.com/giampaolo/psutil/issues/1402 +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + +# More values at: https://stackoverflow.com/a/20804735/376587 +WIN_10 = (10, 0) +WIN_8 = (6, 2) +WIN_7 = (6, 1) +WIN_SERVER_2008 = (6, 0) +WIN_VISTA = (6, 0) +WIN_SERVER_2003 = (5, 2) +WIN_XP = (5, 1) + + +@lru_cache() +def get_winver(): + """Usage: + >>> if get_winver() <= WIN_VISTA: + ... ... + """ + wv = sys.getwindowsversion() + return (wv.major, wv.minor) + + +IS_WIN_XP = get_winver() < WIN_VISTA + + +# ===================================================================== +# --- named tuples +# ===================================================================== + + +# psutil.cpu_times() +scputimes = namedtuple('scputimes', + ['user', 'system', 'idle', 'interrupt', 'dpc']) +# psutil.virtual_memory() +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +# psutil.Process.memory_info() +pmem = namedtuple( + 'pmem', ['rss', 'vms', + 'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', + 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', + 'pagefile', 'peak_pagefile', 'private']) +# psutil.Process.memory_full_info() +pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', )) +# psutil.Process.memory_maps(grouped=True) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) +# psutil.Process.memory_maps(grouped=False) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +# psutil.Process.io_counters() +pio = namedtuple('pio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'other_count', 'other_bytes']) + + +# ===================================================================== +# --- utils +# ===================================================================== + + +@lru_cache(maxsize=512) +def convert_dos_path(s): + r"""Convert paths using native DOS format like: + "\Device\HarddiskVolume1\Windows\systemew\file.txt" + into: + "C:\Windows\systemew\file.txt" + """ + rawdrive = '\\'.join(s.split('\\')[:3]) + driveletter = cext.win32_QueryDosDevice(rawdrive) + return os.path.join(driveletter, s[len(rawdrive):]) + + +def py2_strencode(s): + """Encode a unicode string to a byte string by using the default fs + encoding + "replace" error handler. + """ + if PY3: + return s + else: + if isinstance(s, str): + return s + else: + return s.encode(ENCODING, ENCODING_ERRS) + + +@memoize +def getpagesize(): + return cext.getpagesize() + + +# ===================================================================== +# --- memory +# ===================================================================== + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem + # + total = totphys + avail = availphys + free = availphys + used = total - avail + percent = usage_percent((total - avail), total, round_=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + mem = cext.virtual_mem() + total = mem[2] + free = mem[3] + used = total - free + percent = usage_percent(used, total, round_=1) + return _common.sswap(total, used, free, percent, 0, 0) + + +# ===================================================================== +# --- disk +# ===================================================================== + + +disk_io_counters = cext.disk_io_counters + + +def disk_usage(path): + """Return disk usage associated with path.""" + if PY3 and isinstance(path, bytes): + # XXX: do we want to use "strict"? Probably yes, in order + # to fail immediately. After all we are accepting input here... + path = path.decode(ENCODING, errors="strict") + total, free = cext.disk_usage(path) + used = total - free + percent = usage_percent(used, total, round_=1) + return _common.sdiskusage(total, used, free, percent) + + +def disk_partitions(all): + """Return disk partitions.""" + rawlist = cext.disk_partitions(all) + return [_common.sdiskpart(*x) for x in rawlist] + + +# ===================================================================== +# --- CPU +# ===================================================================== + + +def cpu_times(): + """Return system CPU times as a named tuple.""" + user, system, idle = cext.cpu_times() + # Internally, GetSystemTimes() is used, and it doesn't return + # interrupt and dpc times. cext.per_cpu_times() does, so we + # rely on it to get those only. + percpu_summed = scputimes(*[sum(n) for n in zip(*cext.per_cpu_times())]) + return scputimes(user, system, idle, + percpu_summed.interrupt, percpu_summed.dpc) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples.""" + ret = [] + for user, system, idle, interrupt, dpc in cext.per_cpu_times(): + item = scputimes(user, system, idle, interrupt, dpc) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPU cores in the system.""" + return cext.cpu_count_phys() + + +def cpu_stats(): + """Return CPU statistics.""" + ctx_switches, interrupts, dpcs, syscalls = cext.cpu_stats() + soft_interrupts = 0 + return _common.scpustats(ctx_switches, interrupts, soft_interrupts, + syscalls) + + +def cpu_freq(): + """Return CPU frequency. + On Windows per-cpu frequency is not supported. + """ + curr, max_ = cext.cpu_freq() + min_ = 0.0 + return [_common.scpufreq(float(curr), min_, float(max_))] + + +if HAS_GETLOADAVG: + _loadavg_inititialized = False + + def getloadavg(): + """Return the number of processes in the system run queue averaged + over the last 1, 5, and 15 minutes respectively as a tuple""" + global _loadavg_inititialized + + if not _loadavg_inititialized: + cext.init_loadavg_counter() + _loadavg_inititialized = True + + # Drop to 2 decimal points which is what Linux does + raw_loads = cext.getloadavg() + return tuple([round(load, 2) for load in raw_loads]) + + +# ===================================================================== +# --- network +# ===================================================================== + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + """ + if kind not in conn_tmap: + raise ValueError("invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap]))) + families, types = conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type, laddr, raddr, status, pid = item + nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, TCP_STATUSES, + pid=pid if _pid == -1 else None) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = {} + rawdict = cext.net_if_stats() + for name, items in rawdict.items(): + if not PY3: + assert isinstance(name, unicode), type(name) + name = py2_strencode(name) + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + ret = cext.net_io_counters() + return dict([(py2_strencode(k), v) for k, v in ret.items()]) + + +def net_if_addrs(): + """Return the addresses associated to each NIC.""" + ret = [] + for items in cext.net_if_addrs(): + items = list(items) + items[0] = py2_strencode(items[0]) + ret.append(items) + return ret + + +# ===================================================================== +# --- sensors +# ===================================================================== + + +def sensors_battery(): + """Return battery information.""" + # For constants meaning see: + # https://msdn.microsoft.com/en-us/library/windows/desktop/ + # aa373232(v=vs.85).aspx + acline_status, flags, percent, secsleft = cext.sensors_battery() + power_plugged = acline_status == 1 + no_battery = bool(flags & 128) + charging = bool(flags & 8) + + if no_battery: + return None + if power_plugged or charging: + secsleft = _common.POWER_TIME_UNLIMITED + elif secsleft == -1: + secsleft = _common.POWER_TIME_UNKNOWN + + return _common.sbattery(percent, secsleft, power_plugged) + + +# ===================================================================== +# --- other system functions +# ===================================================================== + + +_last_btime = 0 + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + # This dirty hack is to adjust the precision of the returned + # value which may have a 1 second fluctuation, see: + # https://github.com/giampaolo/psutil/issues/1007 + global _last_btime + ret = float(cext.boot_time()) + if abs(ret - _last_btime) <= 1: + return _last_btime + else: + _last_btime = ret + return ret + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, hostname, tstamp = item + user = py2_strencode(user) + nt = _common.suser(user, None, hostname, tstamp, None) + retlist.append(nt) + return retlist + + +# ===================================================================== +# --- Windows services +# ===================================================================== + + +def win_service_iter(): + """Yields a list of WindowsService instances.""" + for name, display_name in cext.winservice_enumerate(): + yield WindowsService(py2_strencode(name), py2_strencode(display_name)) + + +def win_service_get(name): + """Open a Windows service and return it as a WindowsService instance.""" + service = WindowsService(name, None) + service._display_name = service._query_config()['display_name'] + return service + + +class WindowsService(object): + """Represents an installed Windows service.""" + + def __init__(self, name, display_name): + self._name = name + self._display_name = display_name + + def __str__(self): + details = "(name=%r, display_name=%r)" % ( + self._name, self._display_name) + return "%s%s" % (self.__class__.__name__, details) + + def __repr__(self): + return "<%s at %s>" % (self.__str__(), id(self)) + + def __eq__(self, other): + # Test for equality with another WindosService object based + # on name. + if not isinstance(other, WindowsService): + return NotImplemented + return self._name == other._name + + def __ne__(self, other): + return not self == other + + def _query_config(self): + with self._wrap_exceptions(): + display_name, binpath, username, start_type = \ + cext.winservice_query_config(self._name) + # XXX - update _self.display_name? + return dict( + display_name=py2_strencode(display_name), + binpath=py2_strencode(binpath), + username=py2_strencode(username), + start_type=py2_strencode(start_type)) + + def _query_status(self): + with self._wrap_exceptions(): + status, pid = cext.winservice_query_status(self._name) + if pid == 0: + pid = None + return dict(status=status, pid=pid) + + @contextlib.contextmanager + def _wrap_exceptions(self): + """Ctx manager which translates bare OSError and WindowsError + exceptions into NoSuchProcess and AccessDenied. + """ + try: + yield + except OSError as err: + if is_permission_err(err): + raise AccessDenied( + pid=None, name=self._name, + msg="service %r is not querable (not enough privileges)" % + self._name) + elif err.winerror in (cext.ERROR_INVALID_NAME, + cext.ERROR_SERVICE_DOES_NOT_EXIST): + raise NoSuchProcess( + pid=None, name=self._name, + msg="service %r does not exist)" % self._name) + else: + raise + + # config query + + def name(self): + """The service name. This string is how a service is referenced + and can be passed to win_service_get() to get a new + WindowsService instance. + """ + return self._name + + def display_name(self): + """The service display name. The value is cached when this class + is instantiated. + """ + return self._display_name + + def binpath(self): + """The fully qualified path to the service binary/exe file as + a string, including command line arguments. + """ + return self._query_config()['binpath'] + + def username(self): + """The name of the user that owns this service.""" + return self._query_config()['username'] + + def start_type(self): + """A string which can either be "automatic", "manual" or + "disabled". + """ + return self._query_config()['start_type'] + + # status query + + def pid(self): + """The process PID, if any, else None. This can be passed + to Process class to control the service's process. + """ + return self._query_status()['pid'] + + def status(self): + """Service status as a string.""" + return self._query_status()['status'] + + def description(self): + """Service long description.""" + return py2_strencode(cext.winservice_query_descr(self.name())) + + # utils + + def as_dict(self): + """Utility method retrieving all the information above as a + dictionary. + """ + d = self._query_config() + d.update(self._query_status()) + d['name'] = self.name() + d['display_name'] = self.display_name() + d['description'] = self.description() + return d + + # actions + # XXX: the necessary C bindings for start() and stop() are + # implemented but for now I prefer not to expose them. + # I may change my mind in the future. Reasons: + # - they require Administrator privileges + # - can't implement a timeout for stop() (unless by using a thread, + # which sucks) + # - would require adding ServiceAlreadyStarted and + # ServiceAlreadyStopped exceptions, adding two new APIs. + # - we might also want to have modify(), which would basically mean + # rewriting win32serviceutil.ChangeServiceConfig, which involves a + # lot of stuff (and API constants which would pollute the API), see: + # http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/ + # win32/lib/win32serviceutil.py.html#0175 + # - psutil is typically about "read only" monitoring stuff; + # win_service_* APIs should only be used to retrieve a service and + # check whether it's running + + # def start(self, timeout=None): + # with self._wrap_exceptions(): + # cext.winservice_start(self.name()) + # if timeout: + # giveup_at = time.time() + timeout + # while True: + # if self.status() == "running": + # return + # else: + # if time.time() > giveup_at: + # raise TimeoutExpired(timeout) + # else: + # time.sleep(.1) + + # def stop(self): + # # Note: timeout is not implemented because it's just not + # # possible, see: + # # http://stackoverflow.com/questions/11973228/ + # with self._wrap_exceptions(): + # return cext.winservice_stop(self.name()) + + +# ===================================================================== +# --- processes +# ===================================================================== + + +pids = cext.pids +pid_exists = cext.pid_exists +ppid_map = cext.ppid_map # used internally by Process.children() + + +def is_permission_err(exc): + """Return True if this is a permission error.""" + assert isinstance(exc, OSError), exc + # On Python 2 OSError doesn't always have 'winerror'. Sometimes + # it does, in which case the original exception was WindowsError + # (which is a subclass of OSError). + return exc.errno in (errno.EPERM, errno.EACCES) or \ + getattr(exc, "winerror", -1) in (cext.ERROR_ACCESS_DENIED, + cext.ERROR_PRIVILEGE_NOT_HELD) + + +def convert_oserror(exc, pid=None, name=None): + """Convert OSError into NoSuchProcess or AccessDenied.""" + assert isinstance(exc, OSError), exc + if is_permission_err(exc): + return AccessDenied(pid=pid, name=name) + if exc.errno == errno.ESRCH: + return NoSuchProcess(pid=pid, name=name) + raise exc + + +def wrap_exceptions(fun): + """Decorator which converts OSError into NoSuchProcess or AccessDenied.""" + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + raise convert_oserror(err, pid=self.pid, name=self._name) + return wrapper + + +def retry_error_partial_copy(fun): + """Workaround for https://github.com/giampaolo/psutil/issues/875. + See: https://stackoverflow.com/questions/4457745#4457745 + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + delay = 0.0001 + times = 33 + for x in range(times): # retries for roughly 1 second + try: + return fun(self, *args, **kwargs) + except WindowsError as _: + err = _ + if err.winerror == ERROR_PARTIAL_COPY: + time.sleep(delay) + delay = min(delay * 2, 0.04) + continue + else: + raise + else: + msg = "%s retried %s times, converted to AccessDenied as it's " \ + "still returning %r" % (fun, times, err) + raise AccessDenied(pid=self.pid, name=self._name, msg=msg) + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid", "_cache"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + # --- oneshot() stuff + + def oneshot_enter(self): + self.oneshot_info.cache_activate(self) + + def oneshot_exit(self): + self.oneshot_info.cache_deactivate(self) + + @wrap_exceptions + @memoize_when_activated + def oneshot_info(self): + """Return multiple information about this process as a + raw tuple. + """ + ret = cext.proc_info(self.pid) + assert len(ret) == len(pinfo_map) + return ret + + @wrap_exceptions + def name(self): + """Return process name, which on Windows is always the final + part of the executable. + """ + # This is how PIDs 0 and 4 are always represented in taskmgr + # and process-hacker. + if self.pid == 0: + return "System Idle Process" + elif self.pid == 4: + return "System" + else: + try: + # Note: this will fail with AD for most PIDs owned + # by another user but it's faster. + return py2_strencode(os.path.basename(self.exe())) + except AccessDenied: + return py2_strencode(cext.proc_name(self.pid)) + + @wrap_exceptions + def exe(self): + # Dual implementation, see: + # https://github.com/giampaolo/psutil/pull/1413 + if not IS_WIN_XP: + exe = cext.proc_exe(self.pid) + else: + if self.pid in (0, 4): + # https://github.com/giampaolo/psutil/issues/414 + # https://github.com/giampaolo/psutil/issues/528 + raise AccessDenied(self.pid, self._name) + exe = cext.proc_exe(self.pid) + exe = convert_dos_path(exe) + return py2_strencode(exe) + + @wrap_exceptions + @retry_error_partial_copy + def cmdline(self): + if cext.WINVER >= cext.WINDOWS_8_1: + # PEB method detects cmdline changes but requires more + # privileges: https://github.com/giampaolo/psutil/pull/1398 + try: + ret = cext.proc_cmdline(self.pid, use_peb=True) + except OSError as err: + if is_permission_err(err): + ret = cext.proc_cmdline(self.pid, use_peb=False) + else: + raise + else: + ret = cext.proc_cmdline(self.pid, use_peb=True) + if PY3: + return ret + else: + return [py2_strencode(s) for s in ret] + + @wrap_exceptions + @retry_error_partial_copy + def environ(self): + ustr = cext.proc_environ(self.pid) + if ustr and not PY3: + assert isinstance(ustr, unicode), type(ustr) + return parse_environ_block(py2_strencode(ustr)) + + def ppid(self): + try: + return ppid_map()[self.pid] + except KeyError: + raise NoSuchProcess(self.pid, self._name) + + def _get_raw_meminfo(self): + try: + return cext.proc_memory_info(self.pid) + except OSError as err: + if is_permission_err(err): + # TODO: the C ext can probably be refactored in order + # to get this from cext.proc_info() + info = self.oneshot_info() + return ( + info[pinfo_map['num_page_faults']], + info[pinfo_map['peak_wset']], + info[pinfo_map['wset']], + info[pinfo_map['peak_paged_pool']], + info[pinfo_map['paged_pool']], + info[pinfo_map['peak_non_paged_pool']], + info[pinfo_map['non_paged_pool']], + info[pinfo_map['pagefile']], + info[pinfo_map['peak_pagefile']], + info[pinfo_map['mem_private']], + ) + raise + + @wrap_exceptions + def memory_info(self): + # on Windows RSS == WorkingSetSize and VSM == PagefileUsage. + # Underlying C function returns fields of PROCESS_MEMORY_COUNTERS + # struct. + t = self._get_raw_meminfo() + rss = t[2] # wset + vms = t[7] # pagefile + return pmem(*(rss, vms, ) + t) + + @wrap_exceptions + def memory_full_info(self): + basic_mem = self.memory_info() + uss = cext.proc_memory_uss(self.pid) + uss *= getpagesize() + return pfullmem(*basic_mem + (uss, )) + + def memory_maps(self): + try: + raw = cext.proc_memory_maps(self.pid) + except OSError as err: + # XXX - can't use wrap_exceptions decorator as we're + # returning a generator; probably needs refactoring. + raise convert_oserror(err, self.pid, self._name) + else: + for addr, perm, path, rss in raw: + path = convert_dos_path(path) + if not PY3: + assert isinstance(path, unicode), type(path) + path = py2_strencode(path) + addr = hex(addr) + yield (addr, perm, path, rss) + + @wrap_exceptions + def kill(self): + return cext.proc_kill(self.pid) + + @wrap_exceptions + def send_signal(self, sig): + os.kill(self.pid, sig) + + @wrap_exceptions + def wait(self, timeout=None): + if timeout is None: + cext_timeout = cext.INFINITE + else: + # WaitForSingleObject() expects time in milliseconds. + cext_timeout = int(timeout * 1000) + + timer = getattr(time, 'monotonic', time.time) + stop_at = timer() + timeout if timeout is not None else None + + try: + # Exit code is supposed to come from GetExitCodeProcess(). + # May also be None if OpenProcess() failed with + # ERROR_INVALID_PARAMETER, meaning PID is already gone. + exit_code = cext.proc_wait(self.pid, cext_timeout) + except cext.TimeoutExpired: + # WaitForSingleObject() returned WAIT_TIMEOUT. Just raise. + raise TimeoutExpired(timeout, self.pid, self._name) + except cext.TimeoutAbandoned: + # WaitForSingleObject() returned WAIT_ABANDONED, see: + # https://github.com/giampaolo/psutil/issues/1224 + # We'll just rely on the internal polling and return None + # when the PID disappears. Subprocess module does the same + # (return None): + # https://github.com/python/cpython/blob/ + # be50a7b627d0aa37e08fa8e2d5568891f19903ce/ + # Lib/subprocess.py#L1193-L1194 + exit_code = None + + # At this point WaitForSingleObject() returned WAIT_OBJECT_0, + # meaning the process is gone. Stupidly there are cases where + # its PID may still stick around so we do a further internal + # polling. + delay = 0.0001 + while True: + if not pid_exists(self.pid): + return exit_code + if stop_at and timer() >= stop_at: + raise TimeoutExpired(timeout, pid=self.pid, name=self._name) + time.sleep(delay) + delay = min(delay * 2, 0.04) # incremental delay + + @wrap_exceptions + def username(self): + if self.pid in (0, 4): + return 'NT AUTHORITY\\SYSTEM' + domain, user = cext.proc_username(self.pid) + return py2_strencode(domain) + '\\' + py2_strencode(user) + + @wrap_exceptions + def create_time(self): + # special case for kernel process PIDs; return system boot time + if self.pid in (0, 4): + return boot_time() + try: + return cext.proc_create_time(self.pid) + except OSError as err: + if is_permission_err(err): + return self.oneshot_info()[pinfo_map['create_time']] + raise + + @wrap_exceptions + def num_threads(self): + return self.oneshot_info()[pinfo_map['num_threads']] + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def cpu_times(self): + try: + user, system = cext.proc_cpu_times(self.pid) + except OSError as err: + if not is_permission_err(err): + raise + info = self.oneshot_info() + user = info[pinfo_map['user_time']] + system = info[pinfo_map['kernel_time']] + # Children user/system times are not retrievable (set to 0). + return _common.pcputimes(user, system, 0.0, 0.0) + + @wrap_exceptions + def suspend(self): + cext.proc_suspend_or_resume(self.pid, True) + + @wrap_exceptions + def resume(self): + cext.proc_suspend_or_resume(self.pid, False) + + @wrap_exceptions + @retry_error_partial_copy + def cwd(self): + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + # return a normalized pathname since the native C function appends + # "\\" at the and of the path + path = cext.proc_cwd(self.pid) + return py2_strencode(os.path.normpath(path)) + + @wrap_exceptions + def open_files(self): + if self.pid in (0, 4): + return [] + ret = set() + # Filenames come in in native format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # Convert the first part in the corresponding drive letter + # (e.g. "C:\") by using Windows's QueryDosDevice() + raw_file_names = cext.proc_open_files(self.pid) + for _file in raw_file_names: + _file = convert_dos_path(_file) + if isfile_strict(_file): + if not PY3: + _file = py2_strencode(_file) + ntuple = _common.popenfile(_file, -1) + ret.add(ntuple) + return list(ret) + + @wrap_exceptions + def connections(self, kind='inet'): + return net_connections(kind, _pid=self.pid) + + @wrap_exceptions + def nice_get(self): + value = cext.proc_priority_get(self.pid) + if enum is not None: + value = Priority(value) + return value + + @wrap_exceptions + def nice_set(self, value): + return cext.proc_priority_set(self.pid, value) + + # available on Windows >= Vista + if HAS_PROC_IO_PRIORITY: + @wrap_exceptions + def ionice_get(self): + ret = cext.proc_io_priority_get(self.pid) + if enum is not None: + ret = IOPriority(ret) + return ret + + @wrap_exceptions + def ionice_set(self, ioclass, value): + if value: + raise TypeError("value argument not accepted on Windows") + if ioclass not in (IOPRIO_VERYLOW, IOPRIO_LOW, IOPRIO_NORMAL, + IOPRIO_HIGH): + raise ValueError("%s is not a valid priority" % ioclass) + cext.proc_io_priority_set(self.pid, ioclass) + + @wrap_exceptions + def io_counters(self): + try: + ret = cext.proc_io_counters(self.pid) + except OSError as err: + if not is_permission_err(err): + raise + info = self.oneshot_info() + ret = ( + info[pinfo_map['io_rcount']], + info[pinfo_map['io_wcount']], + info[pinfo_map['io_rbytes']], + info[pinfo_map['io_wbytes']], + info[pinfo_map['io_count_others']], + info[pinfo_map['io_bytes_others']], + ) + return pio(*ret) + + @wrap_exceptions + def status(self): + suspended = cext.proc_is_suspended(self.pid) + if suspended: + return _common.STATUS_STOPPED + else: + return _common.STATUS_RUNNING + + @wrap_exceptions + def cpu_affinity_get(self): + def from_bitmask(x): + return [i for i in xrange(64) if (1 << i) & x] + bitmask = cext.proc_cpu_affinity_get(self.pid) + return from_bitmask(bitmask) + + @wrap_exceptions + def cpu_affinity_set(self, value): + def to_bitmask(l): + if not l: + raise ValueError("invalid argument %r" % l) + out = 0 + for b in l: + out |= 2 ** b + return out + + # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER + # is returned for an invalid CPU but this seems not to be true, + # therefore we check CPUs validy beforehand. + allcpus = list(range(len(per_cpu_times()))) + for cpu in value: + if cpu not in allcpus: + if not isinstance(cpu, (int, long)): + raise TypeError( + "invalid CPU %r; an integer is required" % cpu) + else: + raise ValueError("invalid CPU %r" % cpu) + + bitmask = to_bitmask(value) + cext.proc_cpu_affinity_set(self.pid, bitmask) + + @wrap_exceptions + def num_handles(self): + try: + return cext.proc_num_handles(self.pid) + except OSError as err: + if is_permission_err(err): + return self.oneshot_info()[pinfo_map['num_handles']] + raise + + @wrap_exceptions + def num_ctx_switches(self): + ctx_switches = self.oneshot_info()[pinfo_map['ctx_switches']] + # only voluntary ctx switches are supported + return _common.pctxsw(ctx_switches, 0) diff --git a/ddtrace/vendor/psutil/arch/aix/common.c b/ddtrace/vendor/psutil/arch/aix/common.c new file mode 100644 index 00000000000..6115a15db51 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/common.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include "common.h" + +/* psutil_kread() - read from kernel memory */ +int +psutil_kread( + int Kd, /* kernel memory file descriptor */ + KA_T addr, /* kernel memory address */ + char *buf, /* buffer to receive data */ + size_t len) { /* length to read */ + int br; + + if (lseek64(Kd, (off64_t)addr, L_SET) == (off64_t)-1) { + PyErr_SetFromErrno(PyExc_OSError); + return 1; + } + br = read(Kd, buf, len); + if (br == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return 1; + } + if (br != len) { + PyErr_SetString(PyExc_RuntimeError, + "size mismatch when reading kernel memory fd"); + return 1; + } + return 0; +} + +struct procentry64 * +psutil_read_process_table(int * num) { + size_t msz; + pid32_t pid = 0; + struct procentry64 *processes = (struct procentry64 *)NULL; + struct procentry64 *p; + int Np = 0; /* number of processes allocated in 'processes' */ + int np = 0; /* number of processes read into 'processes' */ + int i; /* number of processes read in current iteration */ + + msz = (size_t)(PROCSIZE * PROCINFO_INCR); + processes = (struct procentry64 *)malloc(msz); + if (!processes) { + PyErr_NoMemory(); + return NULL; + } + Np = PROCINFO_INCR; + p = processes; + while ((i = getprocs64(p, PROCSIZE, (struct fdsinfo64 *)NULL, 0, &pid, + PROCINFO_INCR)) + == PROCINFO_INCR) { + np += PROCINFO_INCR; + if (np >= Np) { + msz = (size_t)(PROCSIZE * (Np + PROCINFO_INCR)); + processes = (struct procentry64 *)realloc((char *)processes, msz); + if (!processes) { + PyErr_NoMemory(); + return NULL; + } + Np += PROCINFO_INCR; + } + p = (struct procentry64 *)((char *)processes + (np * PROCSIZE)); + } + + /* add the number of processes read in the last iteration */ + if (i > 0) + np += i; + + *num = np; + return processes; +} \ No newline at end of file diff --git a/ddtrace/vendor/psutil/arch/aix/common.h b/ddtrace/vendor/psutil/arch/aix/common.h new file mode 100644 index 00000000000..b677d8c29ee --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/common.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __PSUTIL_AIX_COMMON_H__ +#define __PSUTIL_AIX_COMMON_H__ + +#include + +#define PROCINFO_INCR (256) +#define PROCSIZE (sizeof(struct procentry64)) +#define FDSINFOSIZE (sizeof(struct fdsinfo64)) +#define KMEM "/dev/kmem" + +typedef u_longlong_t KA_T; + +/* psutil_kread() - read from kernel memory */ +int psutil_kread(int Kd, /* kernel memory file descriptor */ + KA_T addr, /* kernel memory address */ + char *buf, /* buffer to receive data */ + size_t len); /* length to read */ + +struct procentry64 * +psutil_read_process_table( + int * num /* out - number of processes read */ +); + +#endif /* __PSUTIL_AIX_COMMON_H__ */ diff --git a/ddtrace/vendor/psutil/arch/aix/ifaddrs.c b/ddtrace/vendor/psutil/arch/aix/ifaddrs.c new file mode 100644 index 00000000000..1a819365ab8 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/ifaddrs.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/*! Based on code from + https://lists.samba.org/archive/samba-technical/2009-February/063079.html +!*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ifaddrs.h" + +#define MAX(x,y) ((x)>(y)?(x):(y)) +#define SIZE(p) MAX((p).sa_len,sizeof(p)) + + +static struct sockaddr * +sa_dup(struct sockaddr *sa1) +{ + struct sockaddr *sa2; + size_t sz = sa1->sa_len; + sa2 = (struct sockaddr *) calloc(1, sz); + if (sa2 == NULL) + return NULL; + memcpy(sa2, sa1, sz); + return sa2; +} + + +void freeifaddrs(struct ifaddrs *ifp) +{ + if (NULL == ifp) return; + free(ifp->ifa_name); + free(ifp->ifa_addr); + free(ifp->ifa_netmask); + free(ifp->ifa_dstaddr); + freeifaddrs(ifp->ifa_next); + free(ifp); +} + + +int getifaddrs(struct ifaddrs **ifap) +{ + int sd, ifsize; + char *ccp, *ecp; + struct ifconf ifc; + struct ifreq *ifr; + struct ifaddrs *cifa = NULL; /* current */ + struct ifaddrs *pifa = NULL; /* previous */ + const size_t IFREQSZ = sizeof(struct ifreq); + int fam; + + *ifap = NULL; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd == -1) + goto error; + + /* find how much memory to allocate for the SIOCGIFCONF call */ + if (ioctl(sd, SIOCGSIZIFCONF, (caddr_t)&ifsize) < 0) + goto error; + + ifc.ifc_req = (struct ifreq *) calloc(1, ifsize); + if (ifc.ifc_req == NULL) + goto error; + ifc.ifc_len = ifsize; + + if (ioctl(sd, SIOCGIFCONF, &ifc) < 0) + goto error; + + ccp = (char *)ifc.ifc_req; + ecp = ccp + ifsize; + + while (ccp < ecp) { + + ifr = (struct ifreq *) ccp; + ifsize = sizeof(ifr->ifr_name) + SIZE(ifr->ifr_addr); + fam = ifr->ifr_addr.sa_family; + + if (fam == AF_INET || fam == AF_INET6) { + cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs)); + if (cifa == NULL) + goto error; + cifa->ifa_next = NULL; + + if (pifa == NULL) *ifap = cifa; /* first one */ + else pifa->ifa_next = cifa; + + cifa->ifa_name = strdup(ifr->ifr_name); + if (cifa->ifa_name == NULL) + goto error; + cifa->ifa_flags = 0; + cifa->ifa_dstaddr = NULL; + + cifa->ifa_addr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_addr == NULL) + goto error; + + if (fam == AF_INET) { + if (ioctl(sd, SIOCGIFNETMASK, ifr, IFREQSZ) < 0) + goto error; + cifa->ifa_netmask = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_netmask == NULL) + goto error; + } + + if (0 == ioctl(sd, SIOCGIFFLAGS, ifr)) /* optional */ + cifa->ifa_flags = ifr->ifr_flags; + + if (fam == AF_INET) { + if (ioctl(sd, SIOCGIFDSTADDR, ifr, IFREQSZ) < 0) { + if (0 == ioctl(sd, SIOCGIFBRDADDR, ifr, IFREQSZ)) { + cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_dstaddr == NULL) + goto error; + } + } + else { + cifa->ifa_dstaddr = sa_dup(&ifr->ifr_addr); + if (cifa->ifa_dstaddr == NULL) + goto error; + } + } + pifa = cifa; + } + + ccp += ifsize; + } + free(ifc.ifc_req); + close(sd); + return 0; +error: + if (ifc.ifc_req != NULL) + free(ifc.ifc_req); + if (sd != -1) + close(sd); + freeifaddrs(*ifap); + return (-1); +} \ No newline at end of file diff --git a/ddtrace/vendor/psutil/arch/aix/ifaddrs.h b/ddtrace/vendor/psutil/arch/aix/ifaddrs.h new file mode 100644 index 00000000000..3920c1ccca7 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/ifaddrs.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/*! Based on code from + https://lists.samba.org/archive/samba-technical/2009-February/063079.html +!*/ + + +#ifndef GENERIC_AIX_IFADDRS_H +#define GENERIC_AIX_IFADDRS_H + +#include +#include + +#undef ifa_dstaddr +#undef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; +}; + +extern int getifaddrs(struct ifaddrs **); +extern void freeifaddrs(struct ifaddrs *); + +#endif \ No newline at end of file diff --git a/ddtrace/vendor/psutil/arch/aix/net_connections.c b/ddtrace/vendor/psutil/arch/aix/net_connections.c new file mode 100644 index 00000000000..69b43892012 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/net_connections.c @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Baded on code from lsof: + * http://www.ibm.com/developerworks/aix/library/au-lsof.html + * - dialects/aix/dproc.c:gather_proc_info + * - lib/prfp.c:process_file + * - dialects/aix/dsock.c:process_socket + * - dialects/aix/dproc.c:get_kernel_access +*/ + +#include +#include +#include +#define _KERNEL +#include +#undef _KERNEL +#include +#include +#include +#include +#include +#include + +#include "../../_psutil_common.h" +#include "net_kernel_structs.h" +#include "net_connections.h" +#include "common.h" + +#define NO_SOCKET (PyObject *)(-1) + +static int +read_unp_addr( + int Kd, + KA_T unp_addr, + char *buf, + size_t buflen +) { + struct sockaddr_un *ua = (struct sockaddr_un *)NULL; + struct sockaddr_un un; + struct mbuf64 mb; + int uo; + + if (psutil_kread(Kd, unp_addr, (char *)&mb, sizeof(mb))) { + return 1; + } + + uo = (int)(mb.m_hdr.mh_data - unp_addr); + if ((uo + sizeof(struct sockaddr)) <= sizeof(mb)) + ua = (struct sockaddr_un *)((char *)&mb + uo); + else { + if (psutil_kread(Kd, (KA_T)mb.m_hdr.mh_data, + (char *)&un, sizeof(un))) { + return 1; + } + ua = &un; + } + if (ua && ua->sun_path[0]) { + if (mb.m_len > sizeof(struct sockaddr_un)) + mb.m_len = sizeof(struct sockaddr_un); + *((char *)ua + mb.m_len - 1) = '\0'; + snprintf(buf, buflen, "%s", ua->sun_path); + } + return 0; +} + +static PyObject * +process_file(int Kd, pid32_t pid, int fd, KA_T fp) { + struct file64 f; + struct socket64 s; + struct protosw64 p; + struct domain d; + struct inpcb64 inp; + int fam; + struct tcpcb64 t; + int state = PSUTIL_CONN_NONE; + unsigned char *laddr = (unsigned char *)NULL; + unsigned char *raddr = (unsigned char *)NULL; + int rport, lport; + char laddr_str[INET6_ADDRSTRLEN]; + char raddr_str[INET6_ADDRSTRLEN]; + struct unpcb64 unp; + char unix_laddr_str[PATH_MAX] = { 0 }; + char unix_raddr_str[PATH_MAX] = { 0 }; + + /* Read file structure */ + if (psutil_kread(Kd, fp, (char *)&f, sizeof(f))) { + return NULL; + } + if (!f.f_count || f.f_type != DTYPE_SOCKET) { + return NO_SOCKET; + } + + if (psutil_kread(Kd, (KA_T) f.f_data, (char *) &s, sizeof(s))) { + return NULL; + } + + if (!s.so_type) { + return NO_SOCKET; + } + + if (!s.so_proto) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol handle"); + return NULL; + } + if (psutil_kread(Kd, (KA_T)s.so_proto, (char *)&p, sizeof(p))) { + return NULL; + } + + if (!p.pr_domain) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket protocol domain"); + return NULL; + } + if (psutil_kread(Kd, (KA_T)p.pr_domain, (char *)&d, sizeof(d))) { + return NULL; + } + + fam = d.dom_family; + if (fam == AF_INET || fam == AF_INET6) { + /* Read protocol control block */ + if (!s.so_pcb) { + PyErr_SetString(PyExc_RuntimeError, "invalid socket PCB"); + return NULL; + } + if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *) &inp, sizeof(inp))) { + return NULL; + } + + if (p.pr_protocol == IPPROTO_TCP) { + /* If this is a TCP socket, read its control block */ + if (inp.inp_ppcb + && !psutil_kread(Kd, (KA_T)inp.inp_ppcb, + (char *)&t, sizeof(t))) + state = t.t_state; + } + + if (fam == AF_INET6) { + laddr = (unsigned char *)&inp.inp_laddr6; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp.inp_faddr6)) { + raddr = (unsigned char *)&inp.inp_faddr6; + rport = (int)ntohs(inp.inp_fport); + } + } + if (fam == AF_INET) { + laddr = (unsigned char *)&inp.inp_laddr; + if (inp.inp_faddr.s_addr != INADDR_ANY || inp.inp_fport != 0) { + raddr = (unsigned char *)&inp.inp_faddr; + rport = (int)ntohs(inp.inp_fport); + } + } + lport = (int)ntohs(inp.inp_lport); + + inet_ntop(fam, laddr, laddr_str, sizeof(laddr_str)); + + if (raddr != NULL) { + inet_ntop(fam, raddr, raddr_str, sizeof(raddr_str)); + return Py_BuildValue("(iii(si)(si)ii)", fd, fam, + s.so_type, laddr_str, lport, raddr_str, + rport, state, pid); + } + else { + return Py_BuildValue("(iii(si)()ii)", fd, fam, + s.so_type, laddr_str, lport, state, + pid); + } + } + + + if (fam == AF_UNIX) { + if (psutil_kread(Kd, (KA_T) s.so_pcb, (char *)&unp, sizeof(unp))) { + return NULL; + } + if ((KA_T) f.f_data != (KA_T) unp.unp_socket) { + PyErr_SetString(PyExc_RuntimeError, "unp_socket mismatch"); + return NULL; + } + + if (unp.unp_addr) { + if (read_unp_addr(Kd, unp.unp_addr, unix_laddr_str, + sizeof(unix_laddr_str))) { + return NULL; + } + } + + if (unp.unp_conn) { + if (psutil_kread(Kd, (KA_T) unp.unp_conn, (char *)&unp, + sizeof(unp))) { + return NULL; + } + if (read_unp_addr(Kd, unp.unp_addr, unix_raddr_str, + sizeof(unix_raddr_str))) { + return NULL; + } + } + + return Py_BuildValue("(iiissii)", fd, d.dom_family, + s.so_type, unix_laddr_str, unix_raddr_str, PSUTIL_CONN_NONE, + pid); + } + return NO_SOCKET; +} + +PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + KA_T fp; + int Kd = -1; + int i, np; + struct procentry64 *p; + struct fdsinfo64 *fds = (struct fdsinfo64 *)NULL; + pid32_t requested_pid; + pid32_t pid; + struct procentry64 *processes = (struct procentry64 *)NULL; + /* the process table */ + + if (py_retlist == NULL) + goto error; + if (! PyArg_ParseTuple(args, "i", &requested_pid)) + goto error; + + Kd = open(KMEM, O_RDONLY, 0); + if (Kd < 0) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, KMEM); + goto error; + } + + processes = psutil_read_process_table(&np); + if (!processes) + goto error; + + /* Loop through processes */ + for (p = processes; np > 0; np--, p++) { + pid = p->pi_pid; + if (requested_pid != -1 && requested_pid != pid) + continue; + if (p->pi_state == 0 || p->pi_state == SZOMB) + continue; + + if (!fds) { + fds = (struct fdsinfo64 *)malloc((size_t)FDSINFOSIZE); + if (!fds) { + PyErr_NoMemory(); + goto error; + } + } + if (getprocs64((struct procentry64 *)NULL, PROCSIZE, fds, FDSINFOSIZE, + &pid, 1) + != 1) + continue; + + /* loop over file descriptors */ + for (i = 0; i < p->pi_maxofile; i++) { + fp = (KA_T)fds->pi_ufd[i].fp; + if (fp) { + py_tuple = process_file(Kd, p->pi_pid, i, fp); + if (py_tuple == NULL) + goto error; + if (py_tuple != NO_SOCKET) { + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } + } + } + close(Kd); + free(processes); + if (fds != NULL) + free(fds); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (Kd > 0) + close(Kd); + if (processes != NULL) + free(processes); + if (fds != NULL) + free(fds); + return NULL; +} diff --git a/ddtrace/vendor/psutil/arch/aix/net_connections.h b/ddtrace/vendor/psutil/arch/aix/net_connections.h new file mode 100644 index 00000000000..222bcaf354e --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/net_connections.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __NET_CONNECTIONS_H__ +#define __NET_CONNECTIONS_H__ + +#include + +PyObject* psutil_net_connections(PyObject *self, PyObject *args); + +#endif /* __NET_CONNECTIONS_H__ */ \ No newline at end of file diff --git a/ddtrace/vendor/psutil/arch/aix/net_kernel_structs.h b/ddtrace/vendor/psutil/arch/aix/net_kernel_structs.h new file mode 100644 index 00000000000..4e7a088c143 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/aix/net_kernel_structs.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2017, Arnon Yaari + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* The kernel is always 64 bit but Python is usually compiled as a 32 bit + * process. We're reading the kernel memory to get the network connections, + * so we need the structs we read to be defined with 64 bit "pointers". + * Here are the partial definitions of the structs we use, taken from the + * header files, with data type sizes converted to their 64 bit counterparts, + * and unused data truncated. */ + +#ifdef __64BIT__ +/* In case we're in a 64 bit process after all */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define file64 file +#define socket64 socket +#define protosw64 protosw +#define inpcb64 inpcb +#define tcpcb64 tcpcb +#define unpcb64 unpcb +#define mbuf64 mbuf +#else /* __64BIT__ */ + struct file64 { + int f_flag; + int f_count; + int f_options; + int f_type; + u_longlong_t f_data; + }; + + struct socket64 { + short so_type; /* generic type, see socket.h */ + short so_options; /* from socket call, see socket.h */ + ushort so_linger; /* time to linger while closing */ + short so_state; /* internal state flags SS_*, below */ + u_longlong_t so_pcb; /* protocol control block */ + u_longlong_t so_proto; /* protocol handle */ + }; + + struct protosw64 { + short pr_type; /* socket type used for */ + u_longlong_t pr_domain; /* domain protocol a member of */ + short pr_protocol; /* protocol number */ + short pr_flags; /* see below */ + }; + + struct inpcb64 { + u_longlong_t inp_next,inp_prev; + /* pointers to other pcb's */ + u_longlong_t inp_head; /* pointer back to chain of inpcb's + for this protocol */ + u_int32_t inp_iflowinfo; /* input flow label */ + u_short inp_fport; /* foreign port */ + u_int16_t inp_fatype; /* foreign address type */ + union in_addr_6 inp_faddr_6; /* foreign host table entry */ + u_int32_t inp_oflowinfo; /* output flow label */ + u_short inp_lport; /* local port */ + u_int16_t inp_latype; /* local address type */ + union in_addr_6 inp_laddr_6; /* local host table entry */ + u_longlong_t inp_socket; /* back pointer to socket */ + u_longlong_t inp_ppcb; /* pointer to per-protocol pcb */ + u_longlong_t space_rt; + struct sockaddr_in6 spare_dst; + u_longlong_t inp_ifa; /* interface address to use */ + int inp_flags; /* generic IP/datagram flags */ +}; + +struct tcpcb64 { + u_longlong_t seg__next; + u_longlong_t seg__prev; + short t_state; /* state of this connection */ +}; + +struct unpcb64 { + u_longlong_t unp_socket; /* pointer back to socket */ + u_longlong_t unp_vnode; /* if associated with file */ + ino_t unp_vno; /* fake vnode number */ + u_longlong_t unp_conn; /* control block of connected socket */ + u_longlong_t unp_refs; /* referencing socket linked list */ + u_longlong_t unp_nextref; /* link in unp_refs list */ + u_longlong_t unp_addr; /* bound address of socket */ +}; + +struct m_hdr64 +{ + u_longlong_t mh_next; /* next buffer in chain */ + u_longlong_t mh_nextpkt; /* next chain in queue/record */ + long mh_len; /* amount of data in this mbuf */ + u_longlong_t mh_data; /* location of data */ +}; + +struct mbuf64 +{ + struct m_hdr64 m_hdr; +}; + +#define m_len m_hdr.mh_len + +#endif /* __64BIT__ */ \ No newline at end of file diff --git a/ddtrace/vendor/psutil/arch/freebsd/proc_socks.c b/ddtrace/vendor/psutil/arch/freebsd/proc_socks.c new file mode 100644 index 00000000000..a458a01e531 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/proc_socks.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Retrieves per-process open socket connections. + */ + +#include +#include +#include // for struct xsocket +#include +#include +#include // for xinpcb struct +#include +#include // for struct xtcpcb +#include // for inet_ntop() +#include + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +// The tcplist fetching and walking is borrowed from netstat/inet.c. +static char * +psutil_fetch_tcplist(void) { + char *buf; + size_t len; + + for (;;) { + if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { + free(buf); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return buf; + } +} + + +static int +psutil_sockaddr_port(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (sin->sin_port); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (sin6->sin6_port); + } +} + + +static void * +psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (&sin->sin_addr); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (&sin6->sin6_addr); + } +} + + +static socklen_t +psutil_sockaddr_addrlen(int family) { + if (family == AF_INET) + return (sizeof(struct in_addr)); + else + return (sizeof(struct in6_addr)); +} + + +static int +psutil_sockaddr_matches(int family, int port, void *pcb_addr, + struct sockaddr_storage *ss) { + if (psutil_sockaddr_port(family, ss) != port) + return (0); + return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr, + psutil_sockaddr_addrlen(family)) == 0); +} + + +#if __FreeBSD_version >= 1200026 +static struct xtcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) { + struct xtcpcb *tp; + struct xinpcb *inp; +#else +static struct tcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) { + struct tcpcb *tp; + struct inpcb *inp; +#endif + struct xinpgen *xig, *oxig; + struct xsocket *so; + + oxig = xig = (struct xinpgen *)buf; + for (xig = (struct xinpgen *)((char *)xig + xig->xig_len); + xig->xig_len > sizeof(struct xinpgen); + xig = (struct xinpgen *)((char *)xig + xig->xig_len)) { + +#if __FreeBSD_version >= 1200026 + tp = (struct xtcpcb *)xig; + inp = &tp->xt_inp; + so = &inp->xi_socket; +#else + tp = &((struct xtcpcb *)xig)->xt_tp; + inp = &((struct xtcpcb *)xig)->xt_inp; + so = &((struct xtcpcb *)xig)->xt_socket; +#endif + + if (so->so_type != kif->kf_sock_type || + so->xso_family != kif->kf_sock_domain || + so->xso_protocol != kif->kf_sock_protocol) + continue; + + if (kif->kf_sock_domain == AF_INET) { + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_lport, &inp->inp_laddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)) +#else + &kif->kf_un.kf_sock.kf_sa_local)) +#endif + continue; + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_fport, &inp->inp_faddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)) +#else + &kif->kf_un.kf_sock.kf_sa_peer)) +#endif + continue; + } else { + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_lport, &inp->in6p_laddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)) +#else + &kif->kf_un.kf_sock.kf_sa_local)) +#endif + continue; + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_fport, &inp->in6p_faddr, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)) +#else + &kif->kf_un.kf_sock.kf_sa_peer)) +#endif + continue; + } + + return (tp); + } + return NULL; +} + + + +PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + // Return connections opened by process. + long pid; + int i; + int cnt; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; +#if __FreeBSD_version >= 1200026 + struct xtcpcb *tcp; +#else + struct tcpcb *tcp; +#endif + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + PyObject *py_family = NULL; + PyObject *py_type = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) + goto error; + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + goto error; + } + + tcplist = psutil_fetch_tcplist(); + if (tcplist == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < cnt; i++) { + int lport, rport, state; + char lip[200], rip[200]; + char path[PATH_MAX]; + int inseq; + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + + kif = &freep[i]; + if (kif->kf_type == KF_TYPE_SOCKET) { + // apply filters + py_family = PyLong_FromLong((long)kif->kf_sock_domain); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + py_type = PyLong_FromLong((long)kif->kf_sock_type); + inseq = PySequence_Contains(py_type_filter, py_type); + Py_DECREF(py_type); + if (inseq == 0) + continue; + // IPv4 / IPv6 socket + if ((kif->kf_sock_domain == AF_INET) || + (kif->kf_sock_domain == AF_INET6)) { + // fill status + state = PSUTIL_CONN_NONE; + if (kif->kf_sock_type == SOCK_STREAM) { + tcp = psutil_search_tcplist(tcplist, kif); + if (tcp != NULL) + state = (int)tcp->t_state; + } + + // build addr and port + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local), +#else + &kif->kf_un.kf_sock.kf_sa_local), +#endif + lip, + sizeof(lip)); + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer), +#else + &kif->kf_un.kf_sock.kf_sa_peer), +#endif + rip, + sizeof(rip)); + lport = htons(psutil_sockaddr_port(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_local)); +#else + &kif->kf_un.kf_sock.kf_sa_local)); +#endif + rport = htons(psutil_sockaddr_port(kif->kf_sock_domain, +#if __FreeBSD_version < 1200031 + &kif->kf_sa_peer)); +#else + &kif->kf_un.kf_sock.kf_sa_peer)); +#endif + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + py_laddr, + py_raddr, + state + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + // UNIX socket. + // Note: remote path cannot be determined. + else if (kif->kf_sock_domain == AF_UNIX) { + struct sockaddr_un *sun; + +#if __FreeBSD_version < 1200031 + sun = (struct sockaddr_un *)&kif->kf_sa_local; +#else + sun = (struct sockaddr_un *)&kif->kf_un.kf_sock.kf_sa_local; +#endif + snprintf( + path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + py_laddr = PyUnicode_DecodeFSDefault(path); + if (! py_laddr) + goto error; + + py_tuple = Py_BuildValue( + "(iiiOsi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + py_laddr, + "", // raddr can't be determined + PSUTIL_CONN_NONE + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_laddr); + } + } + } + free(freep); + free(tcplist); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + if (tcplist != NULL) + free(tcplist); + return NULL; +} diff --git a/ddtrace/vendor/psutil/arch/freebsd/proc_socks.h b/ddtrace/vendor/psutil/arch/freebsd/proc_socks.h new file mode 100644 index 00000000000..a7996b10749 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/proc_socks.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject* psutil_proc_connections(PyObject* self, PyObject* args); diff --git a/ddtrace/vendor/psutil/arch/freebsd/specific.c b/ddtrace/vendor/psutil/arch/freebsd/specific.c new file mode 100644 index 00000000000..26b802422db --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/specific.c @@ -0,0 +1,1115 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Helper functions specific to FreeBSD. + * Used by _psutil_bsd module methods. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // needed for vmtotal struct +#include // for swap mem +#include // process open files, shared libs (kinfo_getvmmap), cwd +#include + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) +#define PSUTIL_BT2MSEC(bt) (bt.sec * 1000 + (((uint64_t) 1000000000 * (uint32_t) \ + (bt.frac >> 32) ) >> 32 ) / 1000000) +#define DECIKELVIN_2_CELCIUS(t) (t - 2731) / 10 +#ifndef _PATH_DEVNULL +#define _PATH_DEVNULL "/dev/null" +#endif + + +// ============================================================================ +// Utility functions +// ============================================================================ + + +int +psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc) { + // Fills a kinfo_proc struct based on process pid. + int mib[4]; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + size = sizeof(struct kinfo_proc); + if (sysctl((int *)mib, 4, proc, &size, NULL, 0) == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_PID)"); + return -1; + } + + // sysctl stores 0 in the size if we can't find the process information. + if (size == 0) { + NoSuchProcess(""); + return -1; + } + return 0; +} + + +// remove spaces from string +static void psutil_remove_spaces(char *str) { + char *p1 = str; + char *p2 = str; + do + while (*p2 == ' ') + p2++; + while ((*p1++ = *p2++)); +} + + +// ============================================================================ +// APIS +// ============================================================================ + +int +psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { + // Returns a list of all BSD processes on the system. This routine + // allocates the list and puts it in *procList and a count of the + // number of entries in *procCount. You are responsible for freeing + // this list (use "free" from System framework). + // On success, the function returns 0. + // On error, the function returns a BSD errno value. + int err; + struct kinfo_proc *result; + int done; + int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 }; + size_t length; + + assert( procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + *procCount = 0; + + /* + * We start by calling sysctl with result == NULL and length == 0. + * That will succeed, and set length to the appropriate length. + * We then allocate a buffer of that size and call sysctl again + * with that buffer. If that succeeds, we're done. If that fails + * with ENOMEM, we have to throw away our buffer and loop. Note + * that the loop causes use to call sysctl with NULL again; this + * is necessary because the ENOMEM failure case sets length to + * the amount of data returned, not the amount of data that + * could have been returned. + */ + result = NULL; + done = 0; + do { + assert(result == NULL); + // Call sysctl with a NULL buffer. + length = 0; + err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, + NULL, &length, NULL, 0); + if (err == -1) + err = errno; + + // Allocate an appropriately sized buffer based on the results + // from the previous call. + if (err == 0) { + result = malloc(length); + if (result == NULL) + err = ENOMEM; + } + + // Call sysctl again with the new buffer. If we get an ENOMEM + // error, toss away our buffer and start again. + if (err == 0) { + err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, + result, &length, NULL, 0); + if (err == -1) + err = errno; + if (err == 0) { + done = 1; + } + else if (err == ENOMEM) { + assert(result != NULL); + free(result); + result = NULL; + err = 0; + } + } + } while (err == 0 && ! done); + + // Clean up and establish post conditions. + if (err != 0 && result != NULL) { + free(result); + result = NULL; + } + + *procList = result; + *procCount = length / sizeof(struct kinfo_proc); + + assert((err == 0) == (*procList != NULL)); + return err; +} + + +/* + * XXX no longer used; it probably makese sense to remove it. + * Borrowed from psi Python System Information project + * + * Get command arguments and environment variables. + * + * Based on code from ps. + * + * Returns: + * 0 for success; + * -1 for failure (Exception raised); + * 1 for insufficient privileges. + */ +static char +*psutil_get_cmd_args(long pid, size_t *argsize) { + int mib[4]; + int argmax; + size_t size = sizeof(argmax); + char *procargs = NULL; + + // Get the maximum process arguments size. + mib[0] = CTL_KERN; + mib[1] = KERN_ARGMAX; + + size = sizeof(argmax); + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) + return NULL; + + // Allocate space for the arguments. + procargs = (char *)malloc(argmax); + if (procargs == NULL) { + PyErr_NoMemory(); + return NULL; + } + + // Make a sysctl() call to get the raw argument space of the process. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ARGS; + mib[3] = pid; + + size = argmax; + if (sysctl(mib, 4, procargs, &size, NULL, 0) == -1) { + free(procargs); + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ARGS)"); + return NULL; + } + + // return string and set the length of arguments + *argsize = size; + return procargs; +} + + +// returns the command line as a python list object +PyObject * +psutil_get_cmdline(long pid) { + char *argstr = NULL; + size_t pos = 0; + size_t argsize = 0; + PyObject *py_retlist = Py_BuildValue("[]"); + PyObject *py_arg = NULL; + + if (pid < 0) + return py_retlist; + argstr = psutil_get_cmd_args(pid, &argsize); + if (argstr == NULL) + goto error; + + // args are returned as a flattened string with \0 separators between + // arguments add each string to the list then step forward to the next + // separator + if (argsize > 0) { + while (pos < argsize) { + py_arg = PyUnicode_DecodeFSDefault(&argstr[pos]); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + pos = pos + strlen(&argstr[pos]) + 1; + } + } + + free(argstr); + return py_retlist; + +error: + Py_XDECREF(py_arg); + Py_DECREF(py_retlist); + if (argstr != NULL) + free(argstr); + return NULL; +} + + +/* + * Return process pathname executable. + * Thanks to Robert N. M. Watson: + * http://fxr.googlebit.com/source/usr.bin/procstat/procstat_bin.c?v=8-CURRENT + */ +PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + long pid; + char pathname[PATH_MAX]; + int error; + int mib[4]; + int ret; + size_t size; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = pid; + + size = sizeof(pathname); + error = sysctl(mib, 4, pathname, &size, NULL, 0); + if (error == -1) { + // see: https://github.com/giampaolo/psutil/issues/907 + if (errno == ENOENT) { + return PyUnicode_DecodeFSDefault(""); + } + else { + return \ + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_PATHNAME)"); + } + } + if (size == 0 || strlen(pathname) == 0) { + ret = psutil_pid_exists(pid); + if (ret == -1) + return NULL; + else if (ret == 0) + return NoSuchProcess(""); + else + strcpy(pathname, ""); + } + + return PyUnicode_DecodeFSDefault(pathname); +} + + +PyObject * +psutil_proc_num_threads(PyObject *self, PyObject *args) { + // Return number of threads used by process as a Python integer. + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.ki_numthreads); +} + + +PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + // Retrieves all threads used by process returning a list of tuples + // including thread id, user time and system time. + // Thanks to Robert N. M. Watson: + // http://code.metager.de/source/xref/freebsd/usr.bin/procstat/ + // procstat_threads.c + long pid; + int mib[4]; + struct kinfo_proc *kip = NULL; + struct kinfo_proc *kipp = NULL; + int error; + unsigned int i; + size_t size; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + // we need to re-query for thread information, so don't use *kipp + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID | KERN_PROC_INC_THREAD; + mib[3] = pid; + + size = 0; + error = sysctl(mib, 4, NULL, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); + goto error; + } + if (size == 0) { + NoSuchProcess(""); + goto error; + } + + kip = malloc(size); + if (kip == NULL) { + PyErr_NoMemory(); + goto error; + } + + error = sysctl(mib, 4, kip, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_INC_THREAD)"); + goto error; + } + if (size == 0) { + NoSuchProcess(""); + goto error; + } + + for (i = 0; i < size / sizeof(*kipp); i++) { + kipp = &kip[i]; + py_tuple = Py_BuildValue("Idd", + kipp->ki_tid, + PSUTIL_TV2DOUBLE(kipp->ki_rusage.ru_utime), + PSUTIL_TV2DOUBLE(kipp->ki_rusage.ru_stime)); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(kip); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (kip != NULL) + free(kip); + return NULL; +} + + +PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + // Return an XML string from which we'll determine the number of + // physical CPU cores in the system. + void *topology = NULL; + size_t size = 0; + PyObject *py_str; + + if (sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0)) + goto error; + + topology = malloc(size); + if (!topology) { + PyErr_NoMemory(); + return NULL; + } + + if (sysctlbyname("kern.sched.topology_spec", topology, &size, NULL, 0)) + goto error; + + py_str = Py_BuildValue("s", topology); + free(topology); + return py_str; + +error: + if (topology != NULL) + free(topology); + Py_RETURN_NONE; +} + + +/* + * Return virtual memory usage statistics. + */ +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + unsigned long total; + unsigned int active, inactive, wired, cached, free; + size_t size = sizeof(total); + struct vmtotal vm; + int mib[] = {CTL_VM, VM_METER}; + long pagesize = getpagesize(); +#if __FreeBSD_version > 702101 + long buffers; +#else + int buffers; +#endif + size_t buffers_size = sizeof(buffers); + + if (sysctlbyname("hw.physmem", &total, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('hw.physmem')"); + } + if (sysctlbyname("vm.stats.vm.v_active_count", &active, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_active_count')"); + } + if (sysctlbyname("vm.stats.vm.v_inactive_count", &inactive, &size, NULL, 0)) + { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_inactive_count')"); + } + if (sysctlbyname("vm.stats.vm.v_wire_count", &wired, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_wire_count')"); + } + // https://github.com/giampaolo/psutil/issues/997 + if (sysctlbyname("vm.stats.vm.v_cache_count", &cached, &size, NULL, 0)) { + cached = 0; + } + if (sysctlbyname("vm.stats.vm.v_free_count", &free, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_free_count')"); + } + if (sysctlbyname("vfs.bufspace", &buffers, &buffers_size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('vfs.bufspace')"); + } + + size = sizeof(vm); + if (sysctl(mib, 2, &vm, &size, NULL, 0) != 0) { + return PyErr_SetFromOSErrnoWithSyscall("sysctl(CTL_VM | VM_METER)"); + } + + return Py_BuildValue("KKKKKKKK", + (unsigned long long) total, + (unsigned long long) free * pagesize, + (unsigned long long) active * pagesize, + (unsigned long long) inactive * pagesize, + (unsigned long long) wired * pagesize, + (unsigned long long) cached * pagesize, + (unsigned long long) buffers, + (unsigned long long) (vm.t_vmshr + vm.t_rmshr) * pagesize // shared + ); +} + + +PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + // Return swap memory stats (see 'swapinfo' cmdline tool) + kvm_t *kd; + struct kvm_swap kvmsw[1]; + unsigned int swapin, swapout, nodein, nodeout; + size_t size = sizeof(unsigned int); + int pagesize; + + kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open failed"); + if (kd == NULL) { + PyErr_SetString(PyExc_RuntimeError, "kvm_open() syscall failed"); + return NULL; + } + + if (kvm_getswapinfo(kd, kvmsw, 1, 0) < 0) { + kvm_close(kd); + PyErr_SetString(PyExc_RuntimeError, + "kvm_getswapinfo() syscall failed"); + return NULL; + } + + kvm_close(kd); + + if (sysctlbyname("vm.stats.vm.v_swapin", &swapin, &size, NULL, 0) == -1) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_swapin)'"); + } + if (sysctlbyname("vm.stats.vm.v_swapout", &swapout, &size, NULL, 0) == -1){ + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_swapout)'"); + } + if (sysctlbyname("vm.stats.vm.v_vnodein", &nodein, &size, NULL, 0) == -1) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_vnodein)'"); + } + if (sysctlbyname("vm.stats.vm.v_vnodeout", &nodeout, &size, NULL, 0) == -1) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.vm.v_vnodeout)'"); + } + + pagesize = getpagesize(); + if (pagesize <= 0) { + PyErr_SetString(PyExc_ValueError, "invalid getpagesize()"); + return NULL; + } + + return Py_BuildValue( + "(KKKII)", + (unsigned long long)kvmsw[0].ksw_total * pagesize, // total + (unsigned long long)kvmsw[0].ksw_used * pagesize, // used + (unsigned long long)kvmsw[0].ksw_total * pagesize - // free + kvmsw[0].ksw_used * pagesize, + swapin + swapout, // swap in + nodein + nodeout // swap out + ); +} + + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + long pid; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + struct kinfo_proc kipp; + PyObject *py_path = NULL; + + int i, cnt; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; + if (kif->kf_fd == KF_FD_TYPE_CWD) { + py_path = PyUnicode_DecodeFSDefault(kif->kf_path); + if (!py_path) + goto error; + break; + } + } + /* + * For lower pids it seems we can't retrieve any information + * (lsof can't do that it either). Since this happens even + * as root we return an empty string instead of AccessDenied. + */ + if (py_path == NULL) + py_path = PyUnicode_DecodeFSDefault(""); + free(freep); + return py_path; + +error: + Py_XDECREF(py_path); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int cnt; + + struct kinfo_file *freep; + struct kinfo_proc kipp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kipp) == -1) + return NULL; + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} +#endif + + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + static int maxcpus; + int mib[2]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + // retrieve maxcpus value + size = sizeof(maxcpus); + if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { + Py_DECREF(py_retlist); + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('kern.smp.maxcpus')"); + } + long cpu_time[maxcpus][CPUSTATES]; + + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_NCPU)"); + goto error; + } + + // per-cpu info + size = sizeof(cpu_time); + if (sysctlbyname("kern.cp_times", &cpu_time, &size, NULL, 0) == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctlbyname('kern.smp.maxcpus')"); + goto error; + } + + for (i = 0; i < ncpu; i++) { + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[i][CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i; + struct statinfo stats; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + if (py_retdict == NULL) + return NULL; + if (devstat_checkversion(NULL) < 0) { + PyErr_Format(PyExc_RuntimeError, + "devstat_checkversion() syscall failed"); + goto error; + } + + stats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); + if (stats.dinfo == NULL) { + PyErr_NoMemory(); + goto error; + } + bzero(stats.dinfo, sizeof(struct devinfo)); + + if (devstat_getdevs(NULL, &stats) == -1) { + PyErr_Format(PyExc_RuntimeError, "devstat_getdevs() syscall failed"); + goto error; + } + + for (i = 0; i < stats.dinfo->numdevs; i++) { + py_disk_info = NULL; + struct devstat current; + char disk_name[128]; + current = stats.dinfo->devices[i]; + snprintf(disk_name, sizeof(disk_name), "%s%d", + current.device_name, + current.unit_number); + + py_disk_info = Py_BuildValue( + "(KKKKLLL)", + current.operations[DEVSTAT_READ], // no reads + current.operations[DEVSTAT_WRITE], // no writes + current.bytes[DEVSTAT_READ], // bytes read + current.bytes[DEVSTAT_WRITE], // bytes written + (long long) PSUTIL_BT2MSEC(current.duration[DEVSTAT_READ]), // r time + (long long) PSUTIL_BT2MSEC(current.duration[DEVSTAT_WRITE]), // w time + (long long) PSUTIL_BT2MSEC(current.busy_time) // busy time + ); // finished transactions + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + if (stats.dinfo->mem_ptr) + free(stats.dinfo->mem_ptr); + free(stats.dinfo); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats.dinfo != NULL) + free(stats.dinfo); + return NULL; +} + + +PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) { + // Return a list of tuples for every process memory maps. + //'procstat' cmdline utility has been used as an example. + long pid; + int ptrwidth; + int i, cnt; + char addr[1000]; + char perms[4]; + char *path; + struct kinfo_proc kp; + struct kinfo_vmentry *freep = NULL; + struct kinfo_vmentry *kve; + ptrwidth = 2 * sizeof(void *); + PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kp) == -1) + goto error; + + errno = 0; + freep = kinfo_getvmmap(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getvmmap()"); + goto error; + } + for (i = 0; i < cnt; i++) { + py_tuple = NULL; + kve = &freep[i]; + addr[0] = '\0'; + perms[0] = '\0'; + sprintf(addr, "%#*jx-%#*jx", ptrwidth, (uintmax_t)kve->kve_start, + ptrwidth, (uintmax_t)kve->kve_end); + psutil_remove_spaces(addr); + strlcat(perms, kve->kve_protection & KVME_PROT_READ ? "r" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_WRITE ? "w" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_EXEC ? "x" : "-", + sizeof(perms)); + + if (strlen(kve->kve_path) == 0) { + switch (kve->kve_type) { + case KVME_TYPE_NONE: + path = "[none]"; + break; + case KVME_TYPE_DEFAULT: + path = "[default]"; + break; + case KVME_TYPE_VNODE: + path = "[vnode]"; + break; + case KVME_TYPE_SWAP: + path = "[swap]"; + break; + case KVME_TYPE_DEVICE: + path = "[device]"; + break; + case KVME_TYPE_PHYS: + path = "[phys]"; + break; + case KVME_TYPE_DEAD: + path = "[dead]"; + break; + case KVME_TYPE_SG: + path = "[sg]"; + break; + case KVME_TYPE_UNKNOWN: + path = "[unknown]"; + break; + default: + path = "[?]"; + break; + } + } + else { + path = kve->kve_path; + } + + py_path = PyUnicode_DecodeFSDefault(path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue("ssOiiii", + addr, // "start-end" address + perms, // "rwx" permissions + py_path, // path + kve->kve_resident, // rss + kve->kve_private_resident, // private + kve->kve_ref_count, // ref count + kve->kve_shadow_count); // shadow count + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_path); + Py_DECREF(py_tuple); + } + free(freep); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_path); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + return NULL; +} + + +PyObject* +psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args) { + // Get process CPU affinity. + // Reference: + // http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + long pid; + int ret; + int i; + cpuset_t mask; + PyObject* py_retlist; + PyObject* py_cpu_num; + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + ret = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(mask), &mask); + if (ret != 0) + return PyErr_SetFromErrno(PyExc_OSError); + + py_retlist = PyList_New(0); + if (py_retlist == NULL) + return NULL; + + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, &mask)) { + py_cpu_num = Py_BuildValue("i", i); + if (py_cpu_num == NULL) + goto error; + if (PyList_Append(py_retlist, py_cpu_num)) + goto error; + } + } + + return py_retlist; + +error: + Py_XDECREF(py_cpu_num); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + // Set process CPU affinity. + // Reference: + // http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + long pid; + int i; + int seq_len; + int ret; + cpuset_t cpu_set; + PyObject *py_cpu_set; + PyObject *py_cpu_seq = NULL; + + if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set)) + return NULL; + + py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); + if (!py_cpu_seq) + return NULL; + seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); + + // calculate the mask + CPU_ZERO(&cpu_set); + for (i = 0; i < seq_len; i++) { + PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); +#if PY_MAJOR_VERSION >= 3 + long value = PyLong_AsLong(item); +#else + long value = PyInt_AsLong(item); +#endif + if (value == -1 || PyErr_Occurred()) + goto error; + CPU_SET(value, &cpu_set); + } + + // set affinity + ret = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(cpu_set), &cpu_set); + if (ret != 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + Py_DECREF(py_cpu_seq); + Py_RETURN_NONE; + +error: + if (py_cpu_seq != NULL) + Py_DECREF(py_cpu_seq); + return NULL; +} + + +PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + unsigned int v_soft; + unsigned int v_intr; + unsigned int v_syscall; + unsigned int v_trap; + unsigned int v_swtch; + size_t size = sizeof(v_soft); + + if (sysctlbyname("vm.stats.sys.v_soft", &v_soft, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.sys.v_soft')"); + } + if (sysctlbyname("vm.stats.sys.v_intr", &v_intr, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.sys.v_intr')"); + } + if (sysctlbyname("vm.stats.sys.v_syscall", &v_syscall, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.sys.v_syscall')"); + } + if (sysctlbyname("vm.stats.sys.v_trap", &v_trap, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.sys.v_trap')"); + } + if (sysctlbyname("vm.stats.sys.v_swtch", &v_swtch, &size, NULL, 0)) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('vm.stats.sys.v_swtch')"); + } + + return Py_BuildValue( + "IIIII", + v_swtch, // ctx switches + v_intr, // interrupts + v_soft, // software interrupts + v_syscall, // syscalls + v_trap // traps + ); +} + + +/* + * Return battery information. + */ +PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + int percent; + int minsleft; + int power_plugged; + size_t size = sizeof(percent); + + if (sysctlbyname("hw.acpi.battery.life", &percent, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.acpi.battery.time", &minsleft, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.acpi.acline", &power_plugged, &size, NULL, 0)) + goto error; + return Py_BuildValue("iii", percent, minsleft, power_plugged); + +error: + // see: https://github.com/giampaolo/psutil/issues/1074 + if (errno == ENOENT) + PyErr_SetString(PyExc_NotImplementedError, "no battery"); + else + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * Return temperature information for a given CPU core number. + */ +PyObject * +psutil_sensors_cpu_temperature(PyObject *self, PyObject *args) { + int current; + int tjmax; + int core; + char sensor[26]; + size_t size = sizeof(current); + + if (! PyArg_ParseTuple(args, "i", &core)) + return NULL; + sprintf(sensor, "dev.cpu.%d.temperature", core); + if (sysctlbyname(sensor, ¤t, &size, NULL, 0)) + goto error; + current = DECIKELVIN_2_CELCIUS(current); + + // Return -273 in case of faliure. + sprintf(sensor, "dev.cpu.%d.coretemp.tjmax", core); + if (sysctlbyname(sensor, &tjmax, &size, NULL, 0)) + tjmax = 0; + tjmax = DECIKELVIN_2_CELCIUS(tjmax); + + return Py_BuildValue("ii", current, tjmax); + +error: + if (errno == ENOENT) + PyErr_SetString(PyExc_NotImplementedError, "no temperature sensors"); + else + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * Return frequency information of a given CPU. + * As of Dec 2018 only CPU 0 appears to be supported and all other + * cores match the frequency of CPU 0. + */ +PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + int current; + int core; + char sensor[26]; + char available_freq_levels[1000]; + size_t size = sizeof(current); + + if (! PyArg_ParseTuple(args, "i", &core)) + return NULL; + // https://www.unix.com/man-page/FreeBSD/4/cpufreq/ + sprintf(sensor, "dev.cpu.%d.freq", core); + if (sysctlbyname(sensor, ¤t, &size, NULL, 0)) + goto error; + + size = sizeof(available_freq_levels); + // https://www.unix.com/man-page/FreeBSD/4/cpufreq/ + // In case of failure, an empty string is returned. + sprintf(sensor, "dev.cpu.%d.freq_levels", core); + sysctlbyname(sensor, &available_freq_levels, &size, NULL, 0); + + return Py_BuildValue("is", current, available_freq_levels); + +error: + if (errno == ENOENT) + PyErr_SetString(PyExc_NotImplementedError, "unable to read frequency"); + else + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} diff --git a/ddtrace/vendor/psutil/arch/freebsd/specific.h b/ddtrace/vendor/psutil/arch/freebsd/specific.h new file mode 100644 index 00000000000..875c8166465 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/specific.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +typedef struct kinfo_proc kinfo_proc; + +int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount); +int psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc); + +// +PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); +PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +PyObject* psutil_get_cmdline(long pid); +PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args); +PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args); +PyObject* psutil_proc_cwd(PyObject* self, PyObject* args); +PyObject* psutil_proc_exe(PyObject* self, PyObject* args); +PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); +PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args); +PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); +PyObject* psutil_proc_threads(PyObject* self, PyObject* args); +PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); +PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); +#if defined(PSUTIL_FREEBSD) +PyObject* psutil_sensors_battery(PyObject* self, PyObject* args); +PyObject* psutil_sensors_cpu_temperature(PyObject* self, PyObject* args); +PyObject* psutil_cpu_freq(PyObject* self, PyObject* args); +#endif diff --git a/ddtrace/vendor/psutil/arch/freebsd/sys_socks.c b/ddtrace/vendor/psutil/arch/freebsd/sys_socks.c new file mode 100644 index 00000000000..e0e2046be6d --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/sys_socks.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Retrieves system-wide open socket connections. This is based off of + * sockstat utility source code: + * https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c + */ + +#include +#include +#include +#include // for struct xsocket +#include +#include +#include +#include // for xinpcb struct +#include +#include +#include // for struct xtcpcb +#include // for inet_ntop() + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +static struct xfile *psutil_xfiles; +static int psutil_nxfiles; + + +int +psutil_populate_xfiles() { + size_t len; + + if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { + PyErr_NoMemory(); + return 0; + } + while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) { + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + return 0; + } + len *= 2; + if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) { + PyErr_NoMemory(); + return 0; + } + } + if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) { + PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch"); + return 0; + } + psutil_nxfiles = len / sizeof *psutil_xfiles; + return 1; +} + + +struct xfile * +psutil_get_file_from_sock(void *sock) { + struct xfile *xf; + int n; + + for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) { + if (xf->xf_data == sock) + return xf; + } + return NULL; +} + + +// Reference: +// https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c +int psutil_gather_inet(int proto, PyObject *py_retlist) { + struct xinpgen *xig, *exig; + struct xinpcb *xip; + struct xtcpcb *xtp; +#if __FreeBSD_version >= 1200026 + struct xinpcb *inp; +#else + struct inpcb *inp; +#endif + struct xsocket *so; + const char *varname = NULL; + size_t len, bufsize; + void *buf; + int retry; + int type; + + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + + switch (proto) { + case IPPROTO_TCP: + varname = "net.inet.tcp.pcblist"; + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + varname = "net.inet.udp.pcblist"; + type = SOCK_DGRAM; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) + continue; // XXX + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xig = (struct xinpgen *)buf; + exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); + if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xig->xig_gen != exig->xig_gen && retry--); + + for (;;) { + struct xfile *xf; + int lport, rport, status, family; + + xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); + if (xig >= exig) + break; + + switch (proto) { + case IPPROTO_TCP: + xtp = (struct xtcpcb *)xig; + if (xtp->xt_len != sizeof *xtp) { + PyErr_Format(PyExc_RuntimeError, + "struct xtcpcb size mismatch"); + goto error; + } + inp = &xtp->xt_inp; +#if __FreeBSD_version >= 1200026 + so = &inp->xi_socket; + status = xtp->t_state; +#else + so = &xtp->xt_socket; + status = xtp->xt_tp.t_state; +#endif + break; + case IPPROTO_UDP: + xip = (struct xinpcb *)xig; + if (xip->xi_len != sizeof *xip) { + PyErr_Format(PyExc_RuntimeError, + "struct xinpcb size mismatch"); + goto error; + } +#if __FreeBSD_version >= 1200026 + inp = xip; +#else + inp = &xip->xi_inp; +#endif + so = &xip->xi_socket; + status = PSUTIL_CONN_NONE; + break; + default: + PyErr_Format(PyExc_RuntimeError, "invalid proto"); + goto error; + } + + char lip[200], rip[200]; + + xf = psutil_get_file_from_sock(so->xso_so); + if (xf == NULL) + continue; + lport = ntohs(inp->inp_lport); + rport = ntohs(inp->inp_fport); + + if (inp->inp_vflag & INP_IPV4) { + family = AF_INET; + inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip)); + inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip)); + } + else if (inp->inp_vflag & INP_IPV6) { + family = AF_INET6; + inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip)); + inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip)); + } + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNii)", + xf->xf_fd, // fd + family, // family + type, // type + py_laddr, // laddr + py_raddr, // raddr + status, // status + xf->xf_pid); // pid + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + free(buf); + return 0; +} + + +int psutil_gather_unix(int proto, PyObject *py_retlist) { + struct xunpgen *xug, *exug; + struct xunpcb *xup; + const char *varname = NULL; + const char *protoname = NULL; + size_t len; + size_t bufsize; + void *buf; + int retry; + struct sockaddr_un *sun; + char path[PATH_MAX]; + + PyObject *py_tuple = NULL; + PyObject *py_lpath = NULL; + + switch (proto) { + case SOCK_STREAM: + varname = "net.local.stream.pcblist"; + protoname = "stream"; + break; + case SOCK_DGRAM: + varname = "net.local.dgram.pcblist"; + protoname = "dgram"; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xug = (struct xunpgen *)buf; + exug = (struct xunpgen *)(void *) + ((char *)buf + len - sizeof *exug); + if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xug->xug_gen != exug->xug_gen && retry--); + + for (;;) { + struct xfile *xf; + + xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); + if (xug >= exug) + break; + xup = (struct xunpcb *)xug; + if (xup->xu_len != sizeof *xup) + goto error; + + xf = psutil_get_file_from_sock(xup->xu_socket.xso_so); + if (xf == NULL) + continue; + + sun = (struct sockaddr_un *)&xup->xu_addr; + snprintf(path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + py_lpath = PyUnicode_DecodeFSDefault(path); + if (! py_lpath) + goto error; + + py_tuple = Py_BuildValue("(iiiOsii)", + xf->xf_fd, // fd + AF_UNIX, // family + proto, // type + py_lpath, // lpath + "", // rath + PSUTIL_CONN_NONE, // status + xf->xf_pid); // pid + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_lpath); + Py_DECREF(py_tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_lpath); + free(buf); + return 0; +} + + +PyObject* +psutil_net_connections(PyObject* self, PyObject* args) { + // Return system-wide open connections. + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (psutil_populate_xfiles() != 1) + goto error; + if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0) + goto error; + if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0) + goto error; + + free(psutil_xfiles); + return py_retlist; + +error: + Py_DECREF(py_retlist); + free(psutil_xfiles); + return NULL; +} diff --git a/ddtrace/vendor/psutil/arch/freebsd/sys_socks.h b/ddtrace/vendor/psutil/arch/freebsd/sys_socks.h new file mode 100644 index 00000000000..75247926556 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/freebsd/sys_socks.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +PyObject* psutil_net_connections(PyObject* self, PyObject* args); diff --git a/ddtrace/vendor/psutil/arch/netbsd/socks.c b/ddtrace/vendor/psutil/arch/netbsd/socks.c new file mode 100644 index 00000000000..f370f094667 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/netbsd/socks.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * Copyright (c) 2015, Ryo ONODERA. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +// address family filter +enum af_filter { + INET, + INET4, + INET6, + TCP, + TCP4, + TCP6, + UDP, + UDP4, + UDP6, + UNIX, + ALL, +}; + +// kinfo_file results +struct kif { + SLIST_ENTRY(kif) kifs; + struct kinfo_file *kif; +}; + +// kinfo_file results list +SLIST_HEAD(kifhead, kif) kihead = SLIST_HEAD_INITIALIZER(kihead); + + +// kinfo_pcb results +struct kpcb { + SLIST_ENTRY(kpcb) kpcbs; + struct kinfo_pcb *kpcb; +}; + +// kinfo_pcb results list +SLIST_HEAD(kpcbhead, kpcb) kpcbhead = SLIST_HEAD_INITIALIZER(kpcbhead); + +static void psutil_kiflist_init(void); +static void psutil_kiflist_clear(void); +static void psutil_kpcblist_init(void); +static void psutil_kpcblist_clear(void); +static int psutil_get_files(void); +static int psutil_get_sockets(const char *name); +static int psutil_get_info(int aff); + + +// Initialize kinfo_file results list. +static void +psutil_kiflist_init(void) { + SLIST_INIT(&kihead); + return; +} + + +// Clear kinfo_file results list. +static void +psutil_kiflist_clear(void) { + while (!SLIST_EMPTY(&kihead)) { + SLIST_REMOVE_HEAD(&kihead, kifs); + } + + return; +} + + +// Initialize kinof_pcb result list. +static void +psutil_kpcblist_init(void) { + SLIST_INIT(&kpcbhead); + return; +} + + +// Clear kinof_pcb result list. +static void +psutil_kpcblist_clear(void) { + while (!SLIST_EMPTY(&kpcbhead)) { + SLIST_REMOVE_HEAD(&kpcbhead, kpcbs); + } + + return; +} + + +// Get all open files including socket. +static int +psutil_get_files(void) { + size_t len; + size_t j; + int mib[6]; + char *buf; + off_t offset; + + mib[0] = CTL_KERN; + mib[1] = KERN_FILE2; + mib[2] = KERN_FILE_BYFILE; + mib[3] = 0; + mib[4] = sizeof(struct kinfo_file); + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + offset = len % sizeof(off_t); + mib[5] = len / sizeof(struct kinfo_file); + + if ((buf = malloc(len + offset)) == NULL) { + PyErr_NoMemory(); + return -1; + } + + if (sysctl(mib, 6, buf + offset, &len, NULL, 0) == -1) { + free(buf); + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + len /= sizeof(struct kinfo_file); + struct kinfo_file *ki = (struct kinfo_file *)(buf + offset); + + for (j = 0; j < len; j++) { + struct kif *kif = malloc(sizeof(struct kif)); + kif->kif = &ki[j]; + SLIST_INSERT_HEAD(&kihead, kif, kifs); + } + + /* + // debug + struct kif *k; + SLIST_FOREACH(k, &kihead, kifs) { + printf("%d\n", k->kif->ki_pid); + } + */ + + return 0; +} + + +// Get open sockets. +static int +psutil_get_sockets(const char *name) { + size_t namelen; + int mib[8]; + struct kinfo_pcb *pcb; + size_t len; + size_t j; + + memset(mib, 0, sizeof(mib)); + + if (sysctlnametomib(name, mib, &namelen) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + if (sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + if ((pcb = malloc(len)) == NULL) { + PyErr_NoMemory(); + return -1; + } + memset(pcb, 0, len); + + mib[6] = sizeof(*pcb); + mib[7] = len / sizeof(*pcb); + + if (sysctl(mib, __arraycount(mib), pcb, &len, NULL, 0) == -1) { + free(pcb); + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + len /= sizeof(struct kinfo_pcb); + struct kinfo_pcb *kp = (struct kinfo_pcb *)pcb; + + for (j = 0; j < len; j++) { + struct kpcb *kpcb = malloc(sizeof(struct kpcb)); + kpcb->kpcb = &kp[j]; + SLIST_INSERT_HEAD(&kpcbhead, kpcb, kpcbs); + } + + /* + // debug + struct kif *k; + struct kpcb *k; + SLIST_FOREACH(k, &kpcbhead, kpcbs) { + printf("ki_type: %d\n", k->kpcb->ki_type); + printf("ki_family: %d\n", k->kpcb->ki_family); + } + */ + + return 0; +} + + +// Collect open file and connections. +static int +psutil_get_info(int aff) { + switch (aff) { + case INET: + if (psutil_get_sockets("net.inet.tcp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet.udp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.tcp6.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.udp6.pcblist") != 0) + return -1; + break; + case INET4: + if (psutil_get_sockets("net.inet.tcp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet.udp.pcblist") != 0) + return -1; + break; + case INET6: + if (psutil_get_sockets("net.inet6.tcp6.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.udp6.pcblist") != 0) + return -1; + break; + case TCP: + if (psutil_get_sockets("net.inet.tcp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.tcp6.pcblist") != 0) + return -1; + break; + case TCP4: + if (psutil_get_sockets("net.inet.tcp.pcblist") != 0) + return -1; + break; + case TCP6: + if (psutil_get_sockets("net.inet6.tcp6.pcblist") != 0) + return -1; + break; + case UDP: + if (psutil_get_sockets("net.inet.udp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.udp6.pcblist") != 0) + return -1; + break; + case UDP4: + if (psutil_get_sockets("net.inet.udp.pcblist") != 0) + return -1; + break; + case UDP6: + if (psutil_get_sockets("net.inet6.udp6.pcblist") != 0) + return -1; + break; + case UNIX: + if (psutil_get_sockets("net.local.stream.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.local.seqpacket.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.local.dgram.pcblist") != 0) + return -1; + break; + case ALL: + if (psutil_get_sockets("net.inet.tcp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet.udp.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.tcp6.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.inet6.udp6.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.local.stream.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.local.seqpacket.pcblist") != 0) + return -1; + if (psutil_get_sockets("net.local.dgram.pcblist") != 0) + return -1; + break; + } + + return 0; +} + + +/* + * Return system-wide connections (unless a pid != -1 is passed). + */ +PyObject * +psutil_net_connections(PyObject *self, PyObject *args) { + char laddr[PATH_MAX]; + char raddr[PATH_MAX]; + int32_t lport; + int32_t rport; + int32_t status; + pid_t pid; + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + psutil_kiflist_init(); + psutil_kpcblist_init(); + if (psutil_get_files() != 0) + goto error; + if (psutil_get_info(ALL) != 0) + goto error; + + struct kif *k; + SLIST_FOREACH(k, &kihead, kifs) { + struct kpcb *kp; + if ((pid != -1) && (k->kif->ki_pid != (unsigned int)pid)) + continue; + SLIST_FOREACH(kp, &kpcbhead, kpcbs) { + if (k->kif->ki_fdata != kp->kpcb->ki_sockaddr) + continue; + + // IPv4 or IPv6 + if ((kp->kpcb->ki_family == AF_INET) || + (kp->kpcb->ki_family == AF_INET6)) { + + if (kp->kpcb->ki_family == AF_INET) { + // IPv4 + struct sockaddr_in *sin_src = + (struct sockaddr_in *)&kp->kpcb->ki_src; + struct sockaddr_in *sin_dst = + (struct sockaddr_in *)&kp->kpcb->ki_dst; + // source addr and port + inet_ntop(AF_INET, &sin_src->sin_addr, laddr, + sizeof(laddr)); + lport = ntohs(sin_src->sin_port); + // remote addr and port + inet_ntop(AF_INET, &sin_dst->sin_addr, raddr, + sizeof(raddr)); + rport = ntohs(sin_dst->sin_port); + } + else { + // IPv6 + struct sockaddr_in6 *sin6_src = + (struct sockaddr_in6 *)&kp->kpcb->ki_src; + struct sockaddr_in6 *sin6_dst = + (struct sockaddr_in6 *)&kp->kpcb->ki_dst; + // local addr and port + inet_ntop(AF_INET6, &sin6_src->sin6_addr, laddr, + sizeof(laddr)); + lport = ntohs(sin6_src->sin6_port); + // remote addr and port + inet_ntop(AF_INET6, &sin6_dst->sin6_addr, raddr, + sizeof(raddr)); + rport = ntohs(sin6_dst->sin6_port); + } + + // status + if (kp->kpcb->ki_type == SOCK_STREAM) + status = kp->kpcb->ki_tstate; + else + status = PSUTIL_CONN_NONE; + + // build addr tuple + py_laddr = Py_BuildValue("(si)", laddr, lport); + if (! py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", raddr, rport); + else + py_raddr = Py_BuildValue("()"); + if (! py_raddr) + goto error; + } + else if (kp->kpcb->ki_family == AF_UNIX) { + // UNIX sockets + struct sockaddr_un *sun_src = + (struct sockaddr_un *)&kp->kpcb->ki_src; + struct sockaddr_un *sun_dst = + (struct sockaddr_un *)&kp->kpcb->ki_dst; + strcpy(laddr, sun_src->sun_path); + strcpy(raddr, sun_dst->sun_path); + status = PSUTIL_CONN_NONE; + py_laddr = PyUnicode_DecodeFSDefault(laddr); + if (! py_laddr) + goto error; + py_raddr = PyUnicode_DecodeFSDefault(raddr); + if (! py_raddr) + goto error; + } + else { + continue; + } + + // append tuple to list + py_tuple = Py_BuildValue( + "(iiiOOii)", + k->kif->ki_fd, + kp->kpcb->ki_family, + kp->kpcb->ki_type, + py_laddr, + py_raddr, + status, + k->kif->ki_pid); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_laddr); + Py_DECREF(py_raddr); + Py_DECREF(py_tuple); + } + } + + psutil_kiflist_clear(); + psutil_kpcblist_clear(); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + return 0; +} diff --git a/ddtrace/vendor/psutil/arch/netbsd/socks.h b/ddtrace/vendor/psutil/arch/netbsd/socks.h new file mode 100644 index 00000000000..9e6a97c0a8c --- /dev/null +++ b/ddtrace/vendor/psutil/arch/netbsd/socks.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. + * Copyright (c) 2015, Ryo ONODERA. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +PyObject *psutil_proc_connections(PyObject *, PyObject *); +PyObject *psutil_net_connections(PyObject *, PyObject *); diff --git a/ddtrace/vendor/psutil/arch/netbsd/specific.c b/ddtrace/vendor/psutil/arch/netbsd/specific.c new file mode 100644 index 00000000000..25adffcd3bf --- /dev/null +++ b/ddtrace/vendor/psutil/arch/netbsd/specific.c @@ -0,0 +1,684 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Platform-specific module methods for NetBSD. + */ + +#if defined(PSUTIL_NETBSD) + #define _KMEMUSER +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for swap_mem +#include +#include +// connection stuff +#include // for NI_MAXHOST +#include +#include // for CPUSTATES & CP_* +#define _KERNEL // for DTYPE_* +#include +#undef _KERNEL +#include // struct diskstats +#include +#include + +#include "specific.h" +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +#define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) +#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + + +// ============================================================================ +// Utility functions +// ============================================================================ + + +int +psutil_kinfo_proc(pid_t pid, kinfo_proc *proc) { + // Fills a kinfo_proc struct based on process pid. + int ret; + int mib[6]; + size_t size = sizeof(kinfo_proc); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC2; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + mib[4] = size; + mib[5] = 1; + + ret = sysctl((int*)mib, 6, proc, &size, NULL, 0); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + // sysctl stores 0 in the size if we can't find the process information. + if (size == 0) { + NoSuchProcess(""); + return -1; + } + return 0; +} + + +struct kinfo_file * +kinfo_getfile(pid_t pid, int* cnt) { + // Mimic's FreeBSD kinfo_file call, taking a pid and a ptr to an + // int as arg and returns an array with cnt struct kinfo_file. + int mib[6]; + size_t len; + struct kinfo_file* kf; + mib[0] = CTL_KERN; + mib[1] = KERN_FILE2; + mib[2] = KERN_FILE_BYPID; + mib[3] = (int) pid; + mib[4] = sizeof(struct kinfo_file); + mib[5] = 0; + + // get the size of what would be returned + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if ((kf = malloc(len)) == NULL) { + PyErr_NoMemory(); + return NULL; + } + mib[5] = (int)(len / sizeof(struct kinfo_file)); + if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + *cnt = (int)(len / sizeof(struct kinfo_file)); + return kf; +} + +PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + long pid; + + char path[MAXPATHLEN]; + size_t pathlen = sizeof path; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + +#ifdef KERN_PROC_CWD + int name[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_CWD}; + if (sysctl(name, 4, path, &pathlen, NULL, 0) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } +#else + char *buf; + if (asprintf(&buf, "/proc/%d/cwd", (int)pid) < 0) { + PyErr_NoMemory(); + return NULL; + } + + ssize_t len = readlink(buf, path, sizeof(path) - 1); + free(buf); + if (len == -1) { + if (errno == ENOENT) + NoSuchProcess(""); + else + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + path[len] = '\0'; +#endif + + return PyUnicode_DecodeFSDefault(path); +} + + +// XXX: This is no longer used as per +// https://github.com/giampaolo/psutil/pull/557#issuecomment-171912820 +// Current implementation uses /proc instead. +// Left here just in case. +/* +PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { +#if __NetBSD_Version__ >= 799000000 + pid_t pid; + char pathname[MAXPATHLEN]; + int error; + int mib[4]; + int ret; + size_t size; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (pid == 0) { + // else returns ENOENT + return Py_BuildValue("s", ""); + } + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = pid; + mib[3] = KERN_PROC_PATHNAME; + + size = sizeof(pathname); + error = sysctl(mib, 4, NULL, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + error = sysctl(mib, 4, pathname, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (size == 0 || strlen(pathname) == 0) { + ret = psutil_pid_exists(pid); + if (ret == -1) + return NULL; + else if (ret == 0) + return NoSuchProcess(""); + else + strcpy(pathname, ""); + } + + return PyUnicode_DecodeFSDefault(pathname); +#else + return Py_BuildValue("s", ""); +#endif +} +*/ + +PyObject * +psutil_proc_num_threads(PyObject *self, PyObject *args) { + // Return number of threads used by process as a Python integer. + long pid; + kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.p_nlwps); +} + +PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + pid_t pid; + int mib[5]; + int i, nlwps; + ssize_t st; + size_t size; + struct kinfo_lwp *kl = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + mib[0] = CTL_KERN; + mib[1] = KERN_LWP; + mib[2] = pid; + mib[3] = sizeof(struct kinfo_lwp); + mib[4] = 0; + + st = sysctl(mib, 5, NULL, &size, NULL, 0); + if (st == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(""); + goto error; + } + + mib[4] = size / sizeof(size_t); + kl = malloc(size); + if (kl == NULL) { + PyErr_NoMemory(); + goto error; + } + + st = sysctl(mib, 5, kl, &size, NULL, 0); + if (st == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(""); + goto error; + } + + nlwps = (int)(size / sizeof(struct kinfo_lwp)); + for (i = 0; i < nlwps; i++) { + py_tuple = Py_BuildValue("idd", + (&kl[i])->l_lid, + PSUTIL_KPT2DOUBLE((&kl[i])->l_rtime), + PSUTIL_KPT2DOUBLE((&kl[i])->l_rtime)); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(kl); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (kl != NULL) + free(kl); + return NULL; +} + + +// ============================================================================ +// APIS +// ============================================================================ + +int +psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { + // Returns a list of all BSD processes on the system. This routine + // allocates the list and puts it in *procList and a count of the + // number of entries in *procCount. You are responsible for freeing + // this list (use "free" from System framework). + // On success, the function returns 0. + // On error, the function returns a BSD errno value. + kinfo_proc *result; + // Declaring name as const requires us to cast it when passing it to + // sysctl because the prototype doesn't include the const modifier. + char errbuf[_POSIX2_LINE_MAX]; + int cnt; + kvm_t *kd; + + assert( procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + + if (kd == NULL) { + PyErr_Format( + PyExc_RuntimeError, "kvm_openfiles() syscall failed: %s", errbuf); + return errno; + } + + result = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(kinfo_proc), &cnt); + if (result == NULL) { + PyErr_Format(PyExc_RuntimeError, "kvm_getproc2() syscall failed"); + kvm_close(kd); + return errno; + } + + *procCount = (size_t)cnt; + + size_t mlen = cnt * sizeof(kinfo_proc); + + if ((*procList = malloc(mlen)) == NULL) { + PyErr_NoMemory(); + kvm_close(kd); + return errno; + } + + memcpy(*procList, result, mlen); + assert(*procList != NULL); + kvm_close(kd); + + return 0; +} + + +char * +psutil_get_cmd_args(pid_t pid, size_t *argsize) { + int mib[4]; + int st; + size_t len; + char *procargs; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = pid; + mib[3] = KERN_PROC_ARGV; + len = 0; + + st = sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0); + if (st == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + procargs = (char *)malloc(len); + if (procargs == NULL) { + PyErr_NoMemory(); + return NULL; + } + st = sysctl(mib, __arraycount(mib), procargs, &len, NULL, 0); + if (st == -1) { + free(procargs); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + *argsize = len; + return procargs; +} + + +// Return the command line as a python list object. +// XXX - most of the times sysctl() returns a truncated string. +// Also /proc/pid/cmdline behaves the same so it looks like this +// is a kernel bug. +PyObject * +psutil_get_cmdline(pid_t pid) { + char *argstr = NULL; + size_t pos = 0; + size_t argsize = 0; + PyObject *py_arg = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (pid == 0) + return py_retlist; + + argstr = psutil_get_cmd_args(pid, &argsize); + if (argstr == NULL) + goto error; + + // args are returned as a flattened string with \0 separators between + // arguments add each string to the list then step forward to the next + // separator + if (argsize > 0) { + while (pos < argsize) { + py_arg = PyUnicode_DecodeFSDefault(&argstr[pos]); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + pos = pos + strlen(&argstr[pos]) + 1; + } + } + + free(argstr); + return py_retlist; + +error: + Py_XDECREF(py_arg); + Py_DECREF(py_retlist); + if (argstr != NULL) + free(argstr); + return NULL; +} + + +/* + * Virtual memory stats, taken from: + * https://github.com/satterly/zabbix-stats/blob/master/src/libs/zbxsysinfo/ + * netbsd/memory.c + */ +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + size_t size; + struct uvmexp_sysctl uv; + int mib[] = {CTL_VM, VM_UVMEXP2}; + long pagesize = getpagesize(); + + size = sizeof(uv); + if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKKKKKK", + (unsigned long long) uv.npages << uv.pageshift, // total + (unsigned long long) uv.free << uv.pageshift, // free + (unsigned long long) uv.active << uv.pageshift, // active + (unsigned long long) uv.inactive << uv.pageshift, // inactive + (unsigned long long) uv.wired << uv.pageshift, // wired + (unsigned long long) uv.filepages + uv.execpages * pagesize, // cached + // These are determined from /proc/meminfo in Python. + (unsigned long long) 0, // buffers + (unsigned long long) 0 // shared + ); +} + + +PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + uint64_t swap_total, swap_free; + struct swapent *swdev; + int nswap, i; + + nswap = swapctl(SWAP_NSWAP, 0, 0); + if (nswap == 0) { + // This means there's no swap partition. + return Py_BuildValue("(iiiii)", 0, 0, 0, 0, 0); + } + + swdev = calloc(nswap, sizeof(*swdev)); + if (swdev == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (swapctl(SWAP_STATS, swdev, nswap) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // Total things up. + swap_total = swap_free = 0; + for (i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + swap_total += swdev[i].se_nblks * DEV_BSIZE; + swap_free += (swdev[i].se_nblks - swdev[i].se_inuse) * DEV_BSIZE; + } + } + free(swdev); + + // Get swap in/out + unsigned int total; + size_t size = sizeof(total); + struct uvmexp_sysctl uv; + int mib[] = {CTL_VM, VM_UVMEXP2}; + long pagesize = getpagesize(); + size = sizeof(uv); + if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + return Py_BuildValue("(LLLll)", + swap_total, + (swap_total - swap_free), + swap_free, + (long) uv.pgswapin * pagesize, // swap in + (long) uv.pgswapout * pagesize); // swap out + +error: + free(swdev); + return NULL; +} + + +PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int cnt; + + struct kinfo_file *freep; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} + + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + // XXX: why static? + int mib[3]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_cputime = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + uint64_t cpu_time[CPUSTATES]; + + for (i = 0; i < ncpu; i++) { + // per-cpu info + mib[0] = CTL_KERN; + mib[1] = KERN_CP_TIME; + mib[2] = i; + size = sizeof(cpu_time); + if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) { + warn("failed to get kern.cptime2"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i, dk_ndrive, mib[3]; + size_t len; + struct io_sysctl *stats = NULL; + PyObject *py_disk_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + mib[0] = CTL_HW; + mib[1] = HW_IOSTATS; + mib[2] = sizeof(struct io_sysctl); + len = 0; + if (sysctl(mib, 3, NULL, &len, NULL, 0) < 0) { + warn("can't get HW_IOSTATS"); + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + dk_ndrive = (int)(len / sizeof(struct io_sysctl)); + + stats = malloc(len); + if (stats == NULL) { + PyErr_NoMemory(); + goto error; + } + if (sysctl(mib, 3, stats, &len, NULL, 0) < 0 ) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < dk_ndrive; i++) { + py_disk_info = Py_BuildValue( + "(KKKK)", + stats[i].rxfer, + stats[i].wxfer, + stats[i].rbytes, + stats[i].wbytes + ); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, stats[i].name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + free(stats); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats != NULL) + free(stats); + return NULL; +} + + +PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + size_t size; + struct uvmexp_sysctl uv; + int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2}; + + size = sizeof(uv); + if (sysctl(uvmexp_mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue( + "IIIIIII", + uv.swtch, // ctx switches + uv.intrs, // interrupts - XXX always 0, will be determined via /proc + uv.softs, // soft interrupts + uv.syscalls, // syscalls - XXX always 0 + uv.traps, // traps + uv.faults, // faults + uv.forks // forks + ); +} diff --git a/ddtrace/vendor/psutil/arch/netbsd/specific.h b/ddtrace/vendor/psutil/arch/netbsd/specific.h new file mode 100644 index 00000000000..391ed164a4c --- /dev/null +++ b/ddtrace/vendor/psutil/arch/netbsd/specific.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +typedef struct kinfo_proc2 kinfo_proc; + +int psutil_kinfo_proc(pid_t pid, kinfo_proc *proc); +struct kinfo_file * kinfo_getfile(pid_t pid, int* cnt); +int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount); +char *psutil_get_cmd_args(pid_t pid, size_t *argsize); + +// +PyObject *psutil_get_cmdline(pid_t pid); +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); +PyObject *psutil_proc_connections(PyObject *self, PyObject *args); +PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); +PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +PyObject* psutil_proc_exe(PyObject* self, PyObject* args); +PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); +PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); +PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); diff --git a/ddtrace/vendor/psutil/arch/openbsd/specific.c b/ddtrace/vendor/psutil/arch/openbsd/specific.c new file mode 100644 index 00000000000..33ebdeecbaf --- /dev/null +++ b/ddtrace/vendor/psutil/arch/openbsd/specific.c @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Platform-specific module methods for OpenBSD. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for VFS_* +#include // for swap_mem +#include // for vmtotal struct +#include +#include +// connection stuff +#include // for NI_MAXHOST +#include +#include // for CPUSTATES & CP_* +#define _KERNEL // for DTYPE_* +#include +#undef _KERNEL +#include // struct diskstats +#include // for inet_ntoa() +#include // for warn() & err() + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +#define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) +// #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + + +// ============================================================================ +// Utility functions +// ============================================================================ + +int +psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) { + // Fills a kinfo_proc struct based on process pid. + int ret; + int mib[6]; + size_t size = sizeof(struct kinfo_proc); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + mib[4] = size; + mib[5] = 1; + + ret = sysctl((int*)mib, 6, proc, &size, NULL, 0); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + // sysctl stores 0 in the size if we can't find the process information. + if (size == 0) { + NoSuchProcess(""); + return -1; + } + return 0; +} + + +struct kinfo_file * +kinfo_getfile(long pid, int* cnt) { + // Mimic's FreeBSD kinfo_file call, taking a pid and a ptr to an + // int as arg and returns an array with cnt struct kinfo_file. + int mib[6]; + size_t len; + struct kinfo_file* kf; + mib[0] = CTL_KERN; + mib[1] = KERN_FILE; + mib[2] = KERN_FILE_BYPID; + mib[3] = (int) pid; + mib[4] = sizeof(struct kinfo_file); + mib[5] = 0; + + /* get the size of what would be returned */ + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if ((kf = malloc(len)) == NULL) { + PyErr_NoMemory(); + return NULL; + } + mib[5] = (int)(len / sizeof(struct kinfo_file)); + if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) { + free(kf); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + *cnt = (int)(len / sizeof(struct kinfo_file)); + return kf; +} + + +// ============================================================================ +// APIS +// ============================================================================ + +int +psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) { + // Returns a list of all BSD processes on the system. This routine + // allocates the list and puts it in *procList and a count of the + // number of entries in *procCount. You are responsible for freeing + // this list (use "free" from System framework). + // On success, the function returns 0. + // On error, the function returns a BSD errno value. + struct kinfo_proc *result; + // Declaring name as const requires us to cast it when passing it to + // sysctl because the prototype doesn't include the const modifier. + char errbuf[_POSIX2_LINE_MAX]; + int cnt; + kvm_t *kd; + + assert(procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + + if (kd == NULL) { + return errno; + } + + result = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &cnt); + if (result == NULL) { + kvm_close(kd); + err(1, NULL); + return errno; + } + + *procCount = (size_t)cnt; + + size_t mlen = cnt * sizeof(struct kinfo_proc); + + if ((*procList = malloc(mlen)) == NULL) { + kvm_close(kd); + err(1, NULL); + return errno; + } + + memcpy(*procList, result, mlen); + assert(*procList != NULL); + kvm_close(kd); + + return 0; +} + + +char ** +_psutil_get_argv(long pid) { + static char **argv; + int argv_mib[] = {CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV}; + size_t argv_size = 128; + // Loop and reallocate until we have enough space to fit argv. + for (;; argv_size *= 2) { + if (argv_size >= 8192) { + PyErr_SetString(PyExc_RuntimeError, + "can't allocate enough space for KERN_PROC_ARGV"); + return NULL; + } + if ((argv = realloc(argv, argv_size)) == NULL) + continue; + if (sysctl(argv_mib, 4, argv, &argv_size, NULL, 0) == 0) + return argv; + if (errno == ENOMEM) + continue; + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } +} + + +// returns the command line as a python list object +PyObject * +psutil_get_cmdline(long pid) { + static char **argv; + char **p; + PyObject *py_arg = NULL; + PyObject *py_retlist = Py_BuildValue("[]"); + + if (!py_retlist) + return NULL; + if (pid < 0) + return py_retlist; + + if ((argv = _psutil_get_argv(pid)) == NULL) + goto error; + + for (p = argv; *p != NULL; p++) { + py_arg = PyUnicode_DecodeFSDefault(*p); + if (!py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + } + return py_retlist; + +error: + Py_XDECREF(py_arg); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + // OpenBSD reference: + // https://github.com/janmojzis/pstree/blob/master/proc_kvm.c + // Note: this requires root access, else it will fail trying + // to access /dev/kmem. + long pid; + kvm_t *kd = NULL; + int nentries, i; + char errbuf[4096]; + struct kinfo_proc *kp; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + kd = kvm_openfiles(0, 0, 0, O_RDONLY, errbuf); + if (! kd) { + if (strstr(errbuf, "Permission denied") != NULL) + AccessDenied(""); + else + PyErr_Format(PyExc_RuntimeError, "kvm_openfiles() syscall failed"); + goto error; + } + + kp = kvm_getprocs( + kd, KERN_PROC_PID | KERN_PROC_SHOW_THREADS | KERN_PROC_KTHREAD, pid, + sizeof(*kp), &nentries); + if (! kp) { + if (strstr(errbuf, "Permission denied") != NULL) + AccessDenied(""); + else + PyErr_Format(PyExc_RuntimeError, "kvm_getprocs() syscall failed"); + goto error; + } + + for (i = 0; i < nentries; i++) { + if (kp[i].p_tid < 0) + continue; + if (kp[i].p_pid == pid) { + py_tuple = Py_BuildValue( + "Idd", + kp[i].p_tid, + PSUTIL_KPT2DOUBLE(kp[i].p_uutime), + PSUTIL_KPT2DOUBLE(kp[i].p_ustime)); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } + + kvm_close(kd); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (kd != NULL) + kvm_close(kd); + return NULL; +} + + +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + int64_t total_physmem; + int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + int bcstats_mib[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT}; + int physmem_mib[] = {CTL_HW, HW_PHYSMEM64}; + int vmmeter_mib[] = {CTL_VM, VM_METER}; + size_t size; + struct uvmexp uvmexp; + struct bcachestats bcstats; + struct vmtotal vmdata; + long pagesize = getpagesize(); + + size = sizeof(total_physmem); + if (sysctl(physmem_mib, 2, &total_physmem, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + size = sizeof(uvmexp); + if (sysctl(uvmexp_mib, 2, &uvmexp, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + size = sizeof(bcstats); + if (sysctl(bcstats_mib, 3, &bcstats, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + size = sizeof(vmdata); + if (sysctl(vmmeter_mib, 2, &vmdata, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("KKKKKKKK", + // Note: many programs calculate total memory as + // "uvmexp.npages * pagesize" but this is incorrect and does not + // match "sysctl | grep hw.physmem". + (unsigned long long) total_physmem, + (unsigned long long) uvmexp.free * pagesize, + (unsigned long long) uvmexp.active * pagesize, + (unsigned long long) uvmexp.inactive * pagesize, + (unsigned long long) uvmexp.wired * pagesize, + // this is how "top" determines it + (unsigned long long) bcstats.numbufpages * pagesize, // cached + (unsigned long long) 0, // buffers + (unsigned long long) vmdata.t_vmshr + vmdata.t_rmshr // shared + ); +} + + +PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + uint64_t swap_total, swap_free; + struct swapent *swdev; + int nswap, i; + + if ((nswap = swapctl(SWAP_NSWAP, 0, 0)) == 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if ((swdev = calloc(nswap, sizeof(*swdev))) == NULL) { + PyErr_NoMemory(); + return NULL; + } + + if (swapctl(SWAP_STATS, swdev, nswap) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // Total things up. + swap_total = swap_free = 0; + for (i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + swap_free += (swdev[i].se_nblks - swdev[i].se_inuse); + swap_total += swdev[i].se_nblks; + } + } + + free(swdev); + return Py_BuildValue("(LLLII)", + swap_total * DEV_BSIZE, + (swap_total - swap_free) * DEV_BSIZE, + swap_free * DEV_BSIZE, + // swap in / swap out is not supported as the + // swapent struct does not provide any info + // about it. + 0, 0); + +error: + free(swdev); + return NULL; +} + + +PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int cnt; + + struct kinfo_file *freep; + struct kinfo_proc kipp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kipp) == -1) + return NULL; + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} + + +PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + // Reference: + // https://github.com/openbsd/src/blob/ + // 588f7f8c69786211f2d16865c552afb91b1c7cba/bin/ps/print.c#L191 + long pid; + struct kinfo_proc kp; + char path[MAXPATHLEN]; + size_t pathlen = sizeof path; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + + int name[] = { CTL_KERN, KERN_PROC_CWD, pid }; + if (sysctl(name, 3, path, &pathlen, NULL, 0) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return PyUnicode_DecodeFSDefault(path); +} + + +// see sys/kern/kern_sysctl.c lines 1100 and +// usr.bin/fstat/fstat.c print_inet_details() +static char * +psutil_convert_ipv4(int family, uint32_t addr[4]) { + struct in_addr a; + memcpy(&a, addr, sizeof(a)); + return inet_ntoa(a); +} + + +static char * +psutil_inet6_addrstr(struct in6_addr *p) +{ + struct sockaddr_in6 sin6; + static char hbuf[NI_MAXHOST]; + const int niflags = NI_NUMERICHOST; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr = *p; + if (IN6_IS_ADDR_LINKLOCAL(p) && + *(u_int16_t *)&sin6.sin6_addr.s6_addr[2] != 0) { + sin6.sin6_scope_id = + ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]); + sin6.sin6_addr.s6_addr[2] = sin6.sin6_addr.s6_addr[3] = 0; + } + + if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len, + hbuf, sizeof(hbuf), NULL, 0, niflags)) + return "invalid"; + + return hbuf; +} + + +/* + * List process connections. + * Note: there is no net_connections() on OpenBSD. The Python + * implementation will iterate over all processes and use this + * function. + * Note: local and remote paths cannot be determined for UNIX sockets. + */ +PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + long pid; + int i; + int cnt; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + PyObject *py_family = NULL; + PyObject *_type = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter)) + goto error; + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + errno = 0; + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_for_pid(pid, "kinfo_getfile()"); + goto error; + } + + for (i = 0; i < cnt; i++) { + int state; + int lport; + int rport; + char addrbuf[NI_MAXHOST + 2]; + int inseq; + struct in6_addr laddr6; + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + + kif = &freep[i]; + if (kif->f_type == DTYPE_SOCKET) { + // apply filters + py_family = PyLong_FromLong((long)kif->so_family); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + _type = PyLong_FromLong((long)kif->so_type); + inseq = PySequence_Contains(py_type_filter, _type); + Py_DECREF(_type); + if (inseq == 0) + continue; + + // IPv4 / IPv6 socket + if ((kif->so_family == AF_INET) || (kif->so_family == AF_INET6)) { + // fill status + if (kif->so_type == SOCK_STREAM) + state = kif->t_state; + else + state = PSUTIL_CONN_NONE; + + // ports + lport = ntohs(kif->inp_lport); + rport = ntohs(kif->inp_fport); + + // local address, IPv4 + if (kif->so_family == AF_INET) { + py_laddr = Py_BuildValue( + "(si)", + psutil_convert_ipv4(kif->so_family, kif->inp_laddru), + lport); + if (!py_laddr) + goto error; + } + else { + // local address, IPv6 + memcpy(&laddr6, kif->inp_laddru, sizeof(laddr6)); + snprintf(addrbuf, sizeof(addrbuf), "%s", + psutil_inet6_addrstr(&laddr6)); + py_laddr = Py_BuildValue("(si)", addrbuf, lport); + if (!py_laddr) + goto error; + } + + if (rport != 0) { + // remote address, IPv4 + if (kif->so_family == AF_INET) { + py_raddr = Py_BuildValue( + "(si)", + psutil_convert_ipv4( + kif->so_family, kif->inp_faddru), + rport); + } + else { + // remote address, IPv6 + memcpy(&laddr6, kif->inp_faddru, sizeof(laddr6)); + snprintf(addrbuf, sizeof(addrbuf), "%s", + psutil_inet6_addrstr(&laddr6)); + py_raddr = Py_BuildValue("(si)", addrbuf, rport); + if (!py_raddr) + goto error; + } + } + else { + py_raddr = Py_BuildValue("()"); + } + + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNi)", + kif->fd_fd, + kif->so_family, + kif->so_type, + py_laddr, + py_raddr, + state); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + // UNIX socket. + // XXX: local addr is supposed to be in "unp_path" but it + // always empty; also "fstat" command is not able to show + // UNIX socket paths. + else if (kif->so_family == AF_UNIX) { + py_tuple = Py_BuildValue( + "(iiissi)", + kif->fd_fd, + kif->so_family, + kif->so_type, + "", // laddr (kif->unp_path is empty) + "", // raddr + PSUTIL_CONN_NONE); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_INCREF(Py_None); + } + } + } + free(freep); + free(tcplist); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + if (tcplist != NULL) + free(tcplist); + return NULL; +} + + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + int mib[3]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + uint64_t cpu_time[CPUSTATES]; + + for (i = 0; i < ncpu; i++) { + // per-cpu info + mib[0] = CTL_KERN; + mib[1] = KERN_CPTIME2; + mib[2] = i; + size = sizeof(cpu_time); + if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) { + warn("failed to get kern.cptime2"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i, dk_ndrive, mib[3]; + size_t len; + struct diskstats *stats = NULL; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + if (py_retdict == NULL) + return NULL; + + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + len = 0; + if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0) { + warn("can't get hw.diskstats size"); + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + dk_ndrive = (int)(len / sizeof(struct diskstats)); + + stats = malloc(len); + if (stats == NULL) { + warn("can't malloc"); + PyErr_NoMemory(); + goto error; + } + if (sysctl(mib, 2, stats, &len, NULL, 0) < 0 ) { + warn("could not read hw.diskstats"); + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < dk_ndrive; i++) { + py_disk_info = Py_BuildValue( + "(KKKK)", + stats[i].ds_rxfer, // num reads + stats[i].ds_wxfer, // num writes + stats[i].ds_rbytes, // read bytes + stats[i].ds_wbytes // write bytes + ); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, stats[i].ds_name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + free(stats); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats != NULL) + free(stats); + return NULL; +} + + +PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + size_t size; + struct uvmexp uv; + int uvmexp_mib[] = {CTL_VM, VM_UVMEXP}; + + size = sizeof(uv); + if (sysctl(uvmexp_mib, 2, &uv, &size, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue( + "IIIIIII", + uv.swtch, // ctx switches + uv.intrs, // interrupts - XXX always 0, will be determined via /proc + uv.softs, // soft interrupts + uv.syscalls, // syscalls - XXX always 0 + uv.traps, // traps + uv.faults, // faults + uv.forks // forks + ); +} diff --git a/ddtrace/vendor/psutil/arch/openbsd/specific.h b/ddtrace/vendor/psutil/arch/openbsd/specific.h new file mode 100644 index 00000000000..4f870268d61 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/openbsd/specific.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil. + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +typedef struct kinfo_proc kinfo_proc; + +int psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc); +struct kinfo_file * kinfo_getfile(long pid, int* cnt); +int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount); +char **_psutil_get_argv(long pid); +PyObject * psutil_get_cmdline(long pid); + +// +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); +PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); +PyObject *psutil_proc_connections(PyObject *self, PyObject *args); +PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); +PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +PyObject* psutil_cpu_stats(PyObject* self, PyObject* args); diff --git a/ddtrace/vendor/psutil/arch/osx/process_info.c b/ddtrace/vendor/psutil/arch/osx/process_info.c new file mode 100644 index 00000000000..d21c048edb3 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/osx/process_info.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Helper functions related to fetching process information. + * Used by _psutil_osx module methods. + */ + + +#include +#include +#include +#include // for INT_MAX +#include +#include +#include +#include +#include +#include + +#include "process_info.h" +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + +/* + * Returns a list of all BSD processes on the system. This routine + * allocates the list and puts it in *procList and a count of the + * number of entries in *procCount. You are responsible for freeing + * this list (use "free" from System framework). + * On success, the function returns 0. + * On error, the function returns a BSD errno value. + */ +int +psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { + int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; + size_t size, size2; + void *ptr; + int err; + int lim = 8; // some limit + + assert( procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + *procCount = 0; + + /* + * We start by calling sysctl with ptr == NULL and size == 0. + * That will succeed, and set size to the appropriate length. + * We then allocate a buffer of at least that size and call + * sysctl with that buffer. If that succeeds, we're done. + * If that call fails with ENOMEM, we throw the buffer away + * and try again. + * Note that the loop calls sysctl with NULL again. This is + * is necessary because the ENOMEM failure case sets size to + * the amount of data returned, not the amount of data that + * could have been returned. + */ + while (lim-- > 0) { + size = 0; + if (sysctl((int *)mib3, 3, NULL, &size, NULL, 0) == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + return 1; + } + size2 = size + (size >> 3); // add some + if (size2 > size) { + ptr = malloc(size2); + if (ptr == NULL) + ptr = malloc(size); + else + size = size2; + } + else { + ptr = malloc(size); + } + if (ptr == NULL) { + PyErr_NoMemory(); + return 1; + } + + if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) { + err = errno; + free(ptr); + if (err != ENOMEM) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + return 1; + } + } + else { + *procList = (kinfo_proc *)ptr; + *procCount = size / sizeof(kinfo_proc); + if (procCount <= 0) { + PyErr_Format(PyExc_RuntimeError, "no PIDs found"); + return 1; + } + return 0; // success + } + } + + PyErr_Format(PyExc_RuntimeError, "couldn't collect PIDs list"); + return 1; +} + + +// Read the maximum argument size for processes +int +psutil_get_argmax() { + int argmax; + int mib[] = { CTL_KERN, KERN_ARGMAX }; + size_t size = sizeof(argmax); + + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) + return argmax; + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)"); + return 0; +} + + +// Return 1 if pid refers to a zombie process else 0. +int +psutil_is_zombie(long pid) { + struct kinfo_proc kp; + + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return 0; + return (kp.kp_proc.p_stat == SZOMB) ? 1 : 0; +} + + + +// return process args as a python list +PyObject * +psutil_get_cmdline(long pid) { + int mib[3]; + int nargs; + size_t len; + char *procargs = NULL; + char *arg_ptr; + char *arg_end; + char *curr_arg; + size_t argmax; + + PyObject *py_arg = NULL; + PyObject *py_retlist = NULL; + + // special case for PID 0 (kernel_task) where cmdline cannot be fetched + if (pid == 0) + return Py_BuildValue("[]"); + + // read argmax and allocate memory for argument space. + argmax = psutil_get_argmax(); + if (! argmax) + goto error; + + procargs = (char *)malloc(argmax); + if (NULL == procargs) { + PyErr_NoMemory(); + goto error; + } + + // read argument space + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = (pid_t)pid; + if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { + // In case of zombie process we'll get EINVAL. We translate it + // to NSP and _psosx.py will translate it to ZP. + if ((errno == EINVAL) && (psutil_pid_exists(pid))) + NoSuchProcess(""); + else + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + arg_end = &procargs[argmax]; + // copy the number of arguments to nargs + memcpy(&nargs, procargs, sizeof(nargs)); + + arg_ptr = procargs + sizeof(nargs); + len = strlen(arg_ptr); + arg_ptr += len + 1; + + if (arg_ptr == arg_end) { + free(procargs); + return Py_BuildValue("[]"); + } + + // skip ahead to the first argument + for (; arg_ptr < arg_end; arg_ptr++) { + if (*arg_ptr != '\0') + break; + } + + // iterate through arguments + curr_arg = arg_ptr; + py_retlist = Py_BuildValue("[]"); + if (!py_retlist) + goto error; + while (arg_ptr < arg_end && nargs > 0) { + if (*arg_ptr++ == '\0') { + py_arg = PyUnicode_DecodeFSDefault(curr_arg); + if (! py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + // iterate to next arg and decrement # of args + curr_arg = arg_ptr; + nargs--; + } + } + + free(procargs); + return py_retlist; + +error: + Py_XDECREF(py_arg); + Py_XDECREF(py_retlist); + if (procargs != NULL) + free(procargs); + return NULL; +} + + +// return process environment as a python string +PyObject * +psutil_get_environ(long pid) { + int mib[3]; + int nargs; + char *procargs = NULL; + char *procenv = NULL; + char *arg_ptr; + char *arg_end; + char *env_start; + size_t argmax; + PyObject *py_ret = NULL; + + // special case for PID 0 (kernel_task) where cmdline cannot be fetched + if (pid == 0) + goto empty; + + // read argmax and allocate memory for argument space. + argmax = psutil_get_argmax(); + if (! argmax) + goto error; + + procargs = (char *)malloc(argmax); + if (NULL == procargs) { + PyErr_NoMemory(); + goto error; + } + + // read argument space + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = (pid_t)pid; + if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { + // In case of zombie process we'll get EINVAL. We translate it + // to NSP and _psosx.py will translate it to ZP. + if ((errno == EINVAL) && (psutil_pid_exists(pid))) + NoSuchProcess(""); + else + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + arg_end = &procargs[argmax]; + // copy the number of arguments to nargs + memcpy(&nargs, procargs, sizeof(nargs)); + + // skip executable path + arg_ptr = procargs + sizeof(nargs); + arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr); + + if (arg_ptr == NULL || arg_ptr == arg_end) + goto empty; + + // skip ahead to the first argument + for (; arg_ptr < arg_end; arg_ptr++) { + if (*arg_ptr != '\0') + break; + } + + // iterate through arguments + while (arg_ptr < arg_end && nargs > 0) { + if (*arg_ptr++ == '\0') + nargs--; + } + + // build an environment variable block + env_start = arg_ptr; + + procenv = calloc(1, arg_end - arg_ptr); + if (procenv == NULL) { + PyErr_NoMemory(); + goto error; + } + + while (*arg_ptr != '\0' && arg_ptr < arg_end) { + char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); + + if (s == NULL) + break; + + memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr); + + arg_ptr = s + 1; + } + + py_ret = PyUnicode_DecodeFSDefaultAndSize( + procenv, arg_ptr - env_start + 1); + if (!py_ret) { + // XXX: don't want to free() this as per: + // https://github.com/giampaolo/psutil/issues/926 + // It sucks but not sure what else to do. + procargs = NULL; + goto error; + } + + free(procargs); + free(procenv); + + return py_ret; + +empty: + if (procargs != NULL) + free(procargs); + return Py_BuildValue("s", ""); + +error: + Py_XDECREF(py_ret); + if (procargs != NULL) + free(procargs); + if (procenv != NULL) + free(procargs); + return NULL; +} + + +int +psutil_get_kinfo_proc(long pid, struct kinfo_proc *kp) { + int mib[4]; + size_t len; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = (pid_t)pid; + + // fetch the info with sysctl() + len = sizeof(struct kinfo_proc); + + // now read the data from sysctl + if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) { + // raise an exception and throw errno as the error + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + // sysctl succeeds but len is zero, happens when process has gone away + if (len == 0) { + NoSuchProcess(""); + return -1; + } + return 0; +} + + +/* + * A wrapper around proc_pidinfo(). + * Returns 0 on failure (and Python exception gets already set). + */ +int +psutil_proc_pidinfo(long pid, int flavor, uint64_t arg, void *pti, int size) { + errno = 0; + int ret = proc_pidinfo((int)pid, flavor, arg, pti, size); + if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) { + psutil_raise_for_pid(pid, "proc_pidinfo()"); + return 0; + } + return ret; +} diff --git a/ddtrace/vendor/psutil/arch/osx/process_info.h b/ddtrace/vendor/psutil/arch/osx/process_info.h new file mode 100644 index 00000000000..bd7ffa89caf --- /dev/null +++ b/ddtrace/vendor/psutil/arch/osx/process_info.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include + +typedef struct kinfo_proc kinfo_proc; + +int psutil_get_argmax(void); +int psutil_is_zombie(long pid); +int psutil_get_kinfo_proc(long pid, struct kinfo_proc *kp); +int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount); +int psutil_proc_pidinfo( + long pid, int flavor, uint64_t arg, void *pti, int size); +PyObject* psutil_get_cmdline(long pid); +PyObject* psutil_get_environ(long pid); diff --git a/ddtrace/vendor/psutil/arch/solaris/environ.c b/ddtrace/vendor/psutil/arch/solaris/environ.c new file mode 100644 index 00000000000..1af4c129344 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/solaris/environ.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Oleksii Shevchuk. + * All rights reserved. Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * + * Functions specific for Process.environ(). + */ + +#define NEW_MIB_COMPLIANT 1 +#define _STRUCTURED_PROC 1 + +#include + +#if !defined(_LP64) && _FILE_OFFSET_BITS == 64 +# undef _FILE_OFFSET_BITS +# undef _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include + +#include "environ.h" + + +#define STRING_SEARCH_BUF_SIZE 512 + + +/* + * Open address space of specified process and return file descriptor. + * @param pid a pid of process. + * @param procfs_path a path to mounted procfs filesystem. + * @return file descriptor or -1 in case of error. + */ +static int +open_address_space(pid_t pid, const char *procfs_path) { + int fd; + char proc_path[PATH_MAX]; + + snprintf(proc_path, PATH_MAX, "%s/%i/as", procfs_path, pid); + fd = open(proc_path, O_RDONLY); + if (fd < 0) + PyErr_SetFromErrno(PyExc_OSError); + + return fd; +} + + +/* + * Read chunk of data by offset to specified buffer of the same size. + * @param fd a file descriptor. + * @param offset an required offset in file. + * @param buf a buffer where to store result. + * @param buf_size a size of buffer where data will be stored. + * @return amount of bytes stored to the buffer or -1 in case of + * error. + */ +static int +read_offt(int fd, off_t offset, char *buf, size_t buf_size) { + size_t to_read = buf_size; + size_t stored = 0; + int r; + + while (to_read) { + r = pread(fd, buf + stored, to_read, offset + stored); + if (r < 0) + goto error; + else if (r == 0) + break; + + to_read -= r; + stored += r; + } + + return stored; + + error: + PyErr_SetFromErrno(PyExc_OSError); + return -1; +} + + +/* + * Read null-terminated string from file descriptor starting from + * specified offset. + * @param fd a file descriptor of opened address space. + * @param offset an offset in specified file descriptor. + * @return allocated null-terminated string or NULL in case of error. +*/ +static char * +read_cstring_offt(int fd, off_t offset) { + int r; + int i = 0; + off_t end = offset; + size_t len; + char buf[STRING_SEARCH_BUF_SIZE]; + char *result = NULL; + + if (lseek(fd, offset, SEEK_SET) == (off_t)-1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // Search end of string + for (;;) { + r = read(fd, buf, sizeof(buf)); + if (r == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + else if (r == 0) { + break; + } + else { + for (i=0; i= 0 && count) + *count = env_count; + + if (env_count > 0) + result = read_cstrings_block( + as, info.pr_envp, ptr_size, env_count); + + close(as); + return result; +} + + +/* + * Free array of cstrings. + * @param array an array of cstrings returned by psutil_read_raw_env, + * psutil_read_raw_args or any other function. + * @param count a count of strings in the passed array + */ +void +psutil_free_cstrings_array(char **array, size_t count) { + int i; + + if (!array) + return; + for (i=0; i +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ifaddrs.h" + +#define MAX(x,y) ((x)>(y)?(x):(y)) +#define SIZE(p) MAX((p).ss_len,sizeof(p)) + + +static struct sockaddr * +sa_dup (struct sockaddr_storage *sa1) +{ + struct sockaddr *sa2; + size_t sz = sizeof(struct sockaddr_storage); + sa2 = (struct sockaddr *) calloc(1,sz); + memcpy(sa2,sa1,sz); + return(sa2); +} + + +void freeifaddrs (struct ifaddrs *ifp) +{ + if (NULL == ifp) return; + free(ifp->ifa_name); + free(ifp->ifa_addr); + free(ifp->ifa_netmask); + free(ifp->ifa_dstaddr); + freeifaddrs(ifp->ifa_next); + free(ifp); +} + + +int getifaddrs (struct ifaddrs **ifap) +{ + int sd = -1; + char *ccp, *ecp; + struct lifconf ifc; + struct lifreq *ifr; + struct lifnum lifn; + struct ifaddrs *cifa = NULL; /* current */ + struct ifaddrs *pifa = NULL; /* previous */ + const size_t IFREQSZ = sizeof(struct lifreq); + + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) + goto error; + + ifc.lifc_buf = NULL; + *ifap = NULL; + /* find how much memory to allocate for the SIOCGLIFCONF call */ + lifn.lifn_family = AF_UNSPEC; + lifn.lifn_flags = 0; + if (ioctl(sd, SIOCGLIFNUM, &lifn) < 0) + goto error; + + /* Sun and Apple code likes to pad the interface count here in case interfaces + * are coming up between calls */ + lifn.lifn_count += 4; + + ifc.lifc_family = AF_UNSPEC; + ifc.lifc_len = lifn.lifn_count * sizeof(struct lifreq); + ifc.lifc_buf = calloc(1, ifc.lifc_len); + if (ioctl(sd, SIOCGLIFCONF, &ifc) < 0) + goto error; + + ccp = (char *)ifc.lifc_req; + ecp = ccp + ifc.lifc_len; + + while (ccp < ecp) { + + ifr = (struct lifreq *) ccp; + cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs)); + cifa->ifa_next = NULL; + cifa->ifa_name = strdup(ifr->lifr_name); + + if (pifa == NULL) *ifap = cifa; /* first one */ + else pifa->ifa_next = cifa; + + if (ioctl(sd, SIOCGLIFADDR, ifr, IFREQSZ) < 0) + goto error; + cifa->ifa_addr = sa_dup(&ifr->lifr_addr); + + if (ioctl(sd, SIOCGLIFNETMASK, ifr, IFREQSZ) < 0) + goto error; + cifa->ifa_netmask = sa_dup(&ifr->lifr_addr); + + cifa->ifa_flags = 0; + cifa->ifa_dstaddr = NULL; + + if (0 == ioctl(sd, SIOCGLIFFLAGS, ifr)) /* optional */ + cifa->ifa_flags = ifr->lifr_flags; + + if (ioctl(sd, SIOCGLIFDSTADDR, ifr, IFREQSZ) < 0) { + if (0 == ioctl(sd, SIOCGLIFBRDADDR, ifr, IFREQSZ)) + cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr); + } + else cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr); + + pifa = cifa; + ccp += IFREQSZ; + } + free(ifc.lifc_buf); + close(sd); + return 0; +error: + if (ifc.lifc_buf != NULL) + free(ifc.lifc_buf); + if (sd != -1) + close(sd); + freeifaddrs(*ifap); + return (-1); +} diff --git a/ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.h b/ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.h new file mode 100644 index 00000000000..0953a9b99a0 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.h @@ -0,0 +1,26 @@ +/* Reference: https://lists.samba.org/archive/samba-technical/2009-February/063079.html */ + + +#ifndef __IFADDRS_H__ +#define __IFADDRS_H__ + +#include +#include + +#undef ifa_dstaddr +#undef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; +}; + +extern int getifaddrs(struct ifaddrs **); +extern void freeifaddrs(struct ifaddrs *); + +#endif diff --git a/ddtrace/vendor/psutil/arch/windows/global.c b/ddtrace/vendor/psutil/arch/windows/global.c new file mode 100644 index 00000000000..4d8526e31d6 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/global.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * This code is executed on import. It loads private/undocumented + * Windows APIs and sets Windows version constants so that they are + * available globally. + */ + +#include +#include +#include "ntextapi.h" +#include "global.h" + + +// Needed to make these globally visible. +int PSUTIL_WINVER; +SYSTEM_INFO PSUTIL_SYSTEM_INFO; + +#define NT_FACILITY_MASK 0xfff +#define NT_FACILITY_SHIFT 16 +#define NT_FACILITY(Status) \ + ((((ULONG)(Status)) >> NT_FACILITY_SHIFT) & NT_FACILITY_MASK) +#define NT_NTWIN32(status) (NT_FACILITY(Status) == FACILITY_WIN32) +#define WIN32_FROM_NTSTATUS(Status) (((ULONG)(Status)) & 0xffff) + + +// A wrapper around GetModuleHandle and GetProcAddress. +PVOID +psutil_GetProcAddress(LPCSTR libname, LPCSTR procname) { + HMODULE mod; + FARPROC addr; + + if ((mod = GetModuleHandleA(libname)) == NULL) { + PyErr_SetFromWindowsErrWithFilename(0, libname); + return NULL; + } + if ((addr = GetProcAddress(mod, procname)) == NULL) { + PyErr_SetFromWindowsErrWithFilename(0, procname); + return NULL; + } + return addr; +} + + +// A wrapper around LoadLibrary and GetProcAddress. +PVOID +psutil_GetProcAddressFromLib(LPCSTR libname, LPCSTR procname) { + HMODULE mod; + FARPROC addr; + + Py_BEGIN_ALLOW_THREADS + mod = LoadLibraryA(libname); + Py_END_ALLOW_THREADS + if (mod == NULL) { + PyErr_SetFromWindowsErrWithFilename(0, libname); + return NULL; + } + if ((addr = GetProcAddress(mod, procname)) == NULL) { + PyErr_SetFromWindowsErrWithFilename(0, procname); + FreeLibrary(mod); + return NULL; + } + // Causes crash. + // FreeLibrary(mod); + return addr; +} + + +/* + * Convert a NTSTATUS value to a Win32 error code and set the proper + * Python exception. + */ +PVOID +psutil_SetFromNTStatusErr(NTSTATUS Status, const char *syscall) { + ULONG err; + char fullmsg[1024]; + + if (NT_NTWIN32(Status)) + err = WIN32_FROM_NTSTATUS(Status); + else + err = psutil_RtlNtStatusToDosErrorNoTeb(Status); + // if (GetLastError() != 0) + // err = GetLastError(); + sprintf(fullmsg, "(originated from %s)", syscall); + return PyErr_SetFromWindowsErrWithFilename(err, fullmsg); +} + + +static int +psutil_loadlibs() { + /* + * Mandatory. + */ + psutil_NtQuerySystemInformation = psutil_GetProcAddressFromLib( + "ntdll.dll", "NtQuerySystemInformation"); + if (psutil_NtQuerySystemInformation == NULL) + return 1; + + psutil_NtQueryInformationProcess = psutil_GetProcAddress( + "ntdll.dll", "NtQueryInformationProcess"); + if (! psutil_NtQueryInformationProcess) + return 1; + + psutil_NtSetInformationProcess = psutil_GetProcAddress( + "ntdll.dll", "NtSetInformationProcess"); + if (! psutil_NtSetInformationProcess) + return 1; + + psutil_WinStationQueryInformationW = psutil_GetProcAddressFromLib( + "winsta.dll", "WinStationQueryInformationW"); + if (! psutil_WinStationQueryInformationW) + return 1; + + psutil_NtQueryObject = psutil_GetProcAddressFromLib( + "ntdll.dll", "NtQueryObject"); + if (! psutil_NtQueryObject) + return 1; + + psutil_rtlIpv4AddressToStringA = psutil_GetProcAddressFromLib( + "ntdll.dll", "RtlIpv4AddressToStringA"); + if (! psutil_rtlIpv4AddressToStringA) + return 1; + + // minimum requirement: Win XP SP3 + psutil_GetExtendedTcpTable = psutil_GetProcAddressFromLib( + "iphlpapi.dll", "GetExtendedTcpTable"); + if (! psutil_GetExtendedTcpTable) + return 1; + + // minimum requirement: Win XP SP3 + psutil_GetExtendedUdpTable = psutil_GetProcAddressFromLib( + "iphlpapi.dll", "GetExtendedUdpTable"); + if (! psutil_GetExtendedUdpTable) + return 1; + + psutil_RtlGetVersion = psutil_GetProcAddressFromLib( + "ntdll.dll", "RtlGetVersion"); + if (! psutil_RtlGetVersion) + return 1; + + psutil_NtSuspendProcess = psutil_GetProcAddressFromLib( + "ntdll", "NtSuspendProcess"); + if (! psutil_NtSuspendProcess) + return 1; + + psutil_NtResumeProcess = psutil_GetProcAddressFromLib( + "ntdll", "NtResumeProcess"); + if (! psutil_NtResumeProcess) + return 1; + + psutil_NtQueryVirtualMemory = psutil_GetProcAddressFromLib( + "ntdll", "NtQueryVirtualMemory"); + if (! psutil_NtQueryVirtualMemory) + return 1; + + psutil_RtlNtStatusToDosErrorNoTeb = psutil_GetProcAddressFromLib( + "ntdll", "RtlNtStatusToDosErrorNoTeb"); + if (! psutil_RtlNtStatusToDosErrorNoTeb) + return 1; + + /* + * Optional. + */ + // not available on Wine + psutil_rtlIpv6AddressToStringA = psutil_GetProcAddressFromLib( + "ntdll.dll", "RtlIpv6AddressToStringA"); + + // minimum requirement: Win Vista + psutil_GetTickCount64 = psutil_GetProcAddress( + "kernel32", "GetTickCount64"); + + // minimum requirement: Win 7 + psutil_GetActiveProcessorCount = psutil_GetProcAddress( + "kernel32", "GetActiveProcessorCount"); + + // minumum requirement: Win 7 + psutil_GetLogicalProcessorInformationEx = psutil_GetProcAddressFromLib( + "kernel32", "GetLogicalProcessorInformationEx"); + + PyErr_Clear(); + return 0; +} + + +static int +psutil_set_winver() { + RTL_OSVERSIONINFOEXW versionInfo; + ULONG maj; + ULONG min; + + versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + memset(&versionInfo, 0, sizeof(RTL_OSVERSIONINFOEXW)); + psutil_RtlGetVersion((PRTL_OSVERSIONINFOW)&versionInfo); + maj = versionInfo.dwMajorVersion; + min = versionInfo.dwMinorVersion; + if (maj == 5 && min == 1) + PSUTIL_WINVER = PSUTIL_WINDOWS_XP; + else if (maj == 5 && min == 2) + PSUTIL_WINVER = PSUTIL_WINDOWS_SERVER_2003; + else if (maj == 6 && min == 0) + PSUTIL_WINVER = PSUTIL_WINDOWS_VISTA; // or Server 2008 + else if (maj == 6 && min == 1) + PSUTIL_WINVER = PSUTIL_WINDOWS_7; + else if (maj == 6 && min == 2) + PSUTIL_WINVER = PSUTIL_WINDOWS_8; + else if (maj == 6 && min == 3) + PSUTIL_WINVER = PSUTIL_WINDOWS_8_1; + else if (maj == 10 && min == 0) + PSUTIL_WINVER = PSUTIL_WINDOWS_10; + else + PSUTIL_WINVER = PSUTIL_WINDOWS_NEW; + return 0; +} + + +static int +psutil_load_sysinfo() { + GetSystemInfo(&PSUTIL_SYSTEM_INFO); + return 0; +} + + +int +psutil_load_globals() { + if (psutil_loadlibs() != 0) + return 1; + if (psutil_set_winver() != 0) + return 1; + if (psutil_load_sysinfo() != 0) + return 1; + return 0; +} diff --git a/ddtrace/vendor/psutil/arch/windows/global.h b/ddtrace/vendor/psutil/arch/windows/global.h new file mode 100644 index 00000000000..10ae640595d --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/global.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + + * List of constants and objects that are globally available. + */ + +#include +#include "ntextapi.h" + +extern int PSUTIL_WINVER; +extern SYSTEM_INFO PSUTIL_SYSTEM_INFO; +#define PSUTIL_WINDOWS_XP 51 +#define PSUTIL_WINDOWS_SERVER_2003 52 +#define PSUTIL_WINDOWS_VISTA 60 +#define PSUTIL_WINDOWS_7 61 +#define PSUTIL_WINDOWS_8 62 +#define PSUTIL_WINDOWS_8_1 63 +#define PSUTIL_WINDOWS_10 100 +#define PSUTIL_WINDOWS_NEW MAXLONG + +int psutil_load_globals(); +PVOID psutil_GetProcAddress(LPCSTR libname, LPCSTR procname); +PVOID psutil_GetProcAddressFromLib(LPCSTR libname, LPCSTR procname); +PVOID psutil_SetFromNTStatusErr(NTSTATUS Status, const char *syscall); + +_NtQuerySystemInformation \ + psutil_NtQuerySystemInformation; + +_NtQueryInformationProcess \ + psutil_NtQueryInformationProcess; + +_NtSetInformationProcess + psutil_NtSetInformationProcess; + +_WinStationQueryInformationW \ + psutil_WinStationQueryInformationW; + +_RtlIpv4AddressToStringA \ + psutil_rtlIpv4AddressToStringA; + +_RtlIpv6AddressToStringA \ + psutil_rtlIpv6AddressToStringA; + +_GetExtendedTcpTable \ + psutil_GetExtendedTcpTable; + +_GetExtendedUdpTable \ + psutil_GetExtendedUdpTable; + +_GetActiveProcessorCount \ + psutil_GetActiveProcessorCount; + +_GetTickCount64 \ + psutil_GetTickCount64; + +_NtQueryObject \ + psutil_NtQueryObject; + +_GetLogicalProcessorInformationEx \ + psutil_GetLogicalProcessorInformationEx; + +_RtlGetVersion \ + psutil_RtlGetVersion; + +_NtSuspendProcess \ + psutil_NtSuspendProcess; + +_NtResumeProcess \ + psutil_NtResumeProcess; + +_NtQueryVirtualMemory \ + psutil_NtQueryVirtualMemory; + +_RtlNtStatusToDosErrorNoTeb \ + psutil_RtlNtStatusToDosErrorNoTeb; diff --git a/ddtrace/vendor/psutil/arch/windows/inet_ntop.c b/ddtrace/vendor/psutil/arch/windows/inet_ntop.c new file mode 100644 index 00000000000..3db507b9ba0 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/inet_ntop.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Jeff Tang. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include "inet_ntop.h" + +// From: https://memset.wordpress.com/2010/10/09/inet_ntop-for-win32/ +PCSTR WSAAPI +inet_ntop(INT family, PVOID pAddr, PSTR stringBuf, size_t strBufSize) { + DWORD dwAddressLength = 0; + struct sockaddr_storage srcaddr; + struct sockaddr_in *srcaddr4 = (struct sockaddr_in*) &srcaddr; + struct sockaddr_in6 *srcaddr6 = (struct sockaddr_in6*) &srcaddr; + + memset(&srcaddr, 0, sizeof(struct sockaddr_storage)); + srcaddr.ss_family = family; + + if (family == AF_INET) { + dwAddressLength = sizeof(struct sockaddr_in); + memcpy(&(srcaddr4->sin_addr), pAddr, sizeof(struct in_addr)); + } + else if (family == AF_INET6) { + dwAddressLength = sizeof(struct sockaddr_in6); + memcpy(&(srcaddr6->sin6_addr), pAddr, sizeof(struct in6_addr)); + } + else { + PyErr_SetString(PyExc_ValueError, "invalid family"); + return NULL; + } + + if (WSAAddressToStringA( + (LPSOCKADDR) &srcaddr, + dwAddressLength, + 0, + stringBuf, + (LPDWORD) &strBufSize) != 0) + { + PyErr_SetExcFromWindowsErr(PyExc_OSError, WSAGetLastError()); + return NULL; + } + return stringBuf; +} diff --git a/ddtrace/vendor/psutil/arch/windows/inet_ntop.h b/ddtrace/vendor/psutil/arch/windows/inet_ntop.h new file mode 100644 index 00000000000..2d86c26cf18 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/inet_ntop.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Jeff Tang. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// because of WSAAddressToStringA +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include + +PCSTR WSAAPI +inet_ntop( + __in INT Family, + __in const VOID * pAddr, + __out_ecount(StringBufSize) PSTR pStringBuf, + __in size_t StringBufSize +); diff --git a/ddtrace/vendor/psutil/arch/windows/ntextapi.h b/ddtrace/vendor/psutil/arch/windows/ntextapi.h new file mode 100644 index 00000000000..b6f23d99676 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/ntextapi.h @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#if !defined(__NTEXTAPI_H__) +#define __NTEXTAPI_H__ +#include +#include + +typedef LONG NTSTATUS; + +#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 +#define STATUS_BUFFER_TOO_SMALL 0xC0000023L +#define SystemExtendedHandleInformation 64 +#define MemoryWorkingSetInformation 0x1 +#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) + +/* + * ================================================================ + * Enums. + * ================================================================ + */ + +typedef enum _PROCESSINFOCLASS2 { + _ProcessBasicInformation, + ProcessQuotaLimits, + ProcessIoCounters, + ProcessVmCounters, + ProcessTimes, + ProcessBasePriority, + ProcessRaisePriority, + _ProcessDebugPort, + ProcessExceptionPort, + ProcessAccessToken, + ProcessLdtInformation, + ProcessLdtSize, + ProcessDefaultHardErrorMode, + ProcessIoPortHandlers, + ProcessPooledUsageAndLimits, + ProcessWorkingSetWatch, + ProcessUserModeIOPL, + ProcessEnableAlignmentFaultFixup, + ProcessPriorityClass, + ProcessWx86Information, + ProcessHandleCount, + ProcessAffinityMask, + ProcessPriorityBoost, + ProcessDeviceMap, + ProcessSessionInformation, + ProcessForegroundInformation, + _ProcessWow64Information, + /* added after XP+ */ + _ProcessImageFileName, + ProcessLUIDDeviceMapsEnabled, + _ProcessBreakOnTermination, + ProcessDebugObjectHandle, + ProcessDebugFlags, + ProcessHandleTracing, + ProcessIoPriority, + ProcessExecuteFlags, + ProcessResourceManagement, + ProcessCookie, + ProcessImageInformation, + MaxProcessInfoClass +} PROCESSINFOCLASS2; + +#define PROCESSINFOCLASS PROCESSINFOCLASS2 +#define ProcessBasicInformation _ProcessBasicInformation +#define ProcessWow64Information _ProcessWow64Information +#define ProcessDebugPort _ProcessDebugPort +#define ProcessImageFileName _ProcessImageFileName +#define ProcessBreakOnTermination _ProcessBreakOnTermination + +/* + * ================================================================ + * Structs. + * ================================================================ + */ + +typedef struct { + LARGE_INTEGER IdleTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER DpcTime; + LARGE_INTEGER InterruptTime; + ULONG InterruptCount; +} _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; + +typedef struct { + LARGE_INTEGER IdleProcessTime; + LARGE_INTEGER IoReadTransferCount; + LARGE_INTEGER IoWriteTransferCount; + LARGE_INTEGER IoOtherTransferCount; + ULONG IoReadOperationCount; + ULONG IoWriteOperationCount; + ULONG IoOtherOperationCount; + ULONG AvailablePages; + ULONG CommittedPages; + ULONG CommitLimit; + ULONG PeakCommitment; + ULONG PageFaultCount; + ULONG CopyOnWriteCount; + ULONG TransitionCount; + ULONG CacheTransitionCount; + ULONG DemandZeroCount; + ULONG PageReadCount; + ULONG PageReadIoCount; + ULONG CacheReadCount; + ULONG CacheIoCount; + ULONG DirtyPagesWriteCount; + ULONG DirtyWriteIoCount; + ULONG MappedPagesWriteCount; + ULONG MappedWriteIoCount; + ULONG PagedPoolPages; + ULONG NonPagedPoolPages; + ULONG PagedPoolAllocs; + ULONG PagedPoolFrees; + ULONG NonPagedPoolAllocs; + ULONG NonPagedPoolFrees; + ULONG FreeSystemPtes; + ULONG ResidentSystemCodePage; + ULONG TotalSystemDriverPages; + ULONG TotalSystemCodePages; + ULONG NonPagedPoolLookasideHits; + ULONG PagedPoolLookasideHits; + ULONG AvailablePagedPoolPages; + ULONG ResidentSystemCachePage; + ULONG ResidentPagedPoolPage; + ULONG ResidentSystemDriverPage; + ULONG CcFastReadNoWait; + ULONG CcFastReadWait; + ULONG CcFastReadResourceMiss; + ULONG CcFastReadNotPossible; + ULONG CcFastMdlReadNoWait; + ULONG CcFastMdlReadWait; + ULONG CcFastMdlReadResourceMiss; + ULONG CcFastMdlReadNotPossible; + ULONG CcMapDataNoWait; + ULONG CcMapDataWait; + ULONG CcMapDataNoWaitMiss; + ULONG CcMapDataWaitMiss; + ULONG CcPinMappedDataCount; + ULONG CcPinReadNoWait; + ULONG CcPinReadWait; + ULONG CcPinReadNoWaitMiss; + ULONG CcPinReadWaitMiss; + ULONG CcCopyReadNoWait; + ULONG CcCopyReadWait; + ULONG CcCopyReadNoWaitMiss; + ULONG CcCopyReadWaitMiss; + ULONG CcMdlReadNoWait; + ULONG CcMdlReadWait; + ULONG CcMdlReadNoWaitMiss; + ULONG CcMdlReadWaitMiss; + ULONG CcReadAheadIos; + ULONG CcLazyWriteIos; + ULONG CcLazyWritePages; + ULONG CcDataFlushes; + ULONG CcDataPages; + ULONG ContextSwitches; + ULONG FirstLevelTbFills; + ULONG SecondLevelTbFills; + ULONG SystemCalls; +} _SYSTEM_PERFORMANCE_INFORMATION; + +typedef struct { + ULONG ContextSwitches; + ULONG DpcCount; + ULONG DpcRate; + ULONG TimeIncrement; + ULONG DpcBypassCount; + ULONG ApcBypassCount; +} _SYSTEM_INTERRUPT_INFORMATION; + +typedef enum _KTHREAD_STATE { + Initialized, + Ready, + Running, + Standby, + Terminated, + Waiting, + Transition, + DeferredReady, + GateWait, + MaximumThreadState +} KTHREAD_STATE, *PKTHREAD_STATE; + +typedef enum _KWAIT_REASON { + Executive, + FreePage, + PageIn, + PoolAllocation, + DelayExecution, + Suspended, + UserRequest, + WrExecutive, + WrFreePage, + WrPageIn, + WrPoolAllocation, + WrDelayExecution, + WrSuspended, + WrUserRequest, + WrEventPair, + WrQueue, + WrLpcReceive, + WrLpcReply, + WrVirtualMemory, + WrPageOut, + WrRendezvous, + WrKeyedEvent, + WrTerminated, + WrProcessInSwap, + WrCpuRateControl, + WrCalloutStack, + WrKernel, + WrResource, + WrPushLock, + WrMutex, + WrQuantumEnd, + WrDispatchInt, + WrPreempted, + WrYieldExecution, + WrFastMutex, + WrGuardedMutex, + WrRundown, + WrAlertByThreadId, + WrDeferredPreempt, + MaximumWaitReason +} KWAIT_REASON, *PKWAIT_REASON; + +typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { + PVOID Object; + HANDLE UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _CLIENT_ID2 { + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID2, *PCLIENT_ID2; + +#define CLIENT_ID CLIENT_ID2 +#define PCLIENT_ID PCLIENT_ID2 + +typedef struct _SYSTEM_THREAD_INFORMATION2 { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + ULONG WaitTime; + PVOID StartAddress; + CLIENT_ID ClientId; + LONG Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + KWAIT_REASON WaitReason; +} SYSTEM_THREAD_INFORMATION2, *PSYSTEM_THREAD_INFORMATION2; + +#define SYSTEM_THREAD_INFORMATION SYSTEM_THREAD_INFORMATION2 +#define PSYSTEM_THREAD_INFORMATION PSYSTEM_THREAD_INFORMATION2 + +typedef struct _TEB *PTEB; + +typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION { + SYSTEM_THREAD_INFORMATION ThreadInfo; + PVOID StackBase; + PVOID StackLimit; + PVOID Win32StartAddress; + PTEB TebBase; + ULONG_PTR Reserved2; + ULONG_PTR Reserved3; + ULONG_PTR Reserved4; +} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION; + +typedef struct _SYSTEM_PROCESS_INFORMATION2 { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER SpareLi1; + LARGE_INTEGER SpareLi2; + LARGE_INTEGER SpareLi3; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + LONG BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR PageDirectoryBase; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + SYSTEM_THREAD_INFORMATION Threads[1]; +} SYSTEM_PROCESS_INFORMATION2, *PSYSTEM_PROCESS_INFORMATION2; + +#define SYSTEM_PROCESS_INFORMATION SYSTEM_PROCESS_INFORMATION2 +#define PSYSTEM_PROCESS_INFORMATION PSYSTEM_PROCESS_INFORMATION2 + +typedef struct _PROCESSOR_POWER_INFORMATION { + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; + +#ifndef __IPHLPAPI_H__ +typedef struct in6_addr { + union { + UCHAR Byte[16]; + USHORT Word[8]; + } u; +} IN6_ADDR, *PIN6_ADDR, FAR *LPIN6_ADDR; +#endif + +// http://msdn.microsoft.com/en-us/library/aa813741(VS.85).aspx +typedef struct { + BYTE Reserved1[16]; + PVOID Reserved2[5]; + UNICODE_STRING CurrentDirectoryPath; + PVOID CurrentDirectoryHandle; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + LPCWSTR env; +} RTL_USER_PROCESS_PARAMETERS_, *PRTL_USER_PROCESS_PARAMETERS_; + +typedef struct _WINSTATION_INFO { + BYTE Reserved1[72]; + ULONG SessionId; + BYTE Reserved2[4]; + FILETIME ConnectTime; + FILETIME DisconnectTime; + FILETIME LastInputTime; + FILETIME LoginTime; + BYTE Reserved3[1096]; + FILETIME CurrentTime; +} WINSTATION_INFO, *PWINSTATION_INFO; + +#if (_WIN32_WINNT < 0x0601) // Windows < 7 (Vista and XP) +typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX { + LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + DWORD Size; + _ANONYMOUS_UNION + union { + PROCESSOR_RELATIONSHIP Processor; + NUMA_NODE_RELATIONSHIP NumaNode; + CACHE_RELATIONSHIP Cache; + GROUP_RELATIONSHIP Group; + } DUMMYUNIONNAME; +} SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, *PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX; +#endif + +typedef struct _MEMORY_WORKING_SET_BLOCK { + ULONG_PTR Protection : 5; + ULONG_PTR ShareCount : 3; + ULONG_PTR Shared : 1; + ULONG_PTR Node : 3; +#ifdef _WIN64 + ULONG_PTR VirtualPage : 52; +#else + ULONG VirtualPage : 20; +#endif +} MEMORY_WORKING_SET_BLOCK, *PMEMORY_WORKING_SET_BLOCK; + +typedef struct _MEMORY_WORKING_SET_INFORMATION { + ULONG_PTR NumberOfEntries; + MEMORY_WORKING_SET_BLOCK WorkingSetInfo[1]; +} MEMORY_WORKING_SET_INFORMATION, *PMEMORY_WORKING_SET_INFORMATION; + +typedef struct _PSUTIL_PROCESS_WS_COUNTERS { + SIZE_T NumberOfPages; + SIZE_T NumberOfPrivatePages; + SIZE_T NumberOfSharedPages; + SIZE_T NumberOfShareablePages; +} PSUTIL_PROCESS_WS_COUNTERS, *PPSUTIL_PROCESS_WS_COUNTERS; + +/* + * ================================================================ + * Type defs for modules loaded at runtime. + * ================================================================ + */ + +typedef BOOL (WINAPI *_GetLogicalProcessorInformationEx)( + LOGICAL_PROCESSOR_RELATIONSHIP relationship, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Buffer, + PDWORD ReturnLength); + +typedef BOOLEAN (WINAPI * _WinStationQueryInformationW)( + HANDLE ServerHandle, + ULONG SessionId, + WINSTATIONINFOCLASS WinStationInformationClass, + PVOID pWinStationInformation, + ULONG WinStationInformationLength, + PULONG pReturnLength); + +typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)( + HANDLE ProcessHandle, + DWORD ProcessInformationClass, + PVOID ProcessInformation, + DWORD ProcessInformationLength, + PDWORD ReturnLength); + +typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( + ULONG SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength); + +typedef NTSTATUS (NTAPI *_NtSetInformationProcess)( + HANDLE ProcessHandle, + DWORD ProcessInformationClass, + PVOID ProcessInformation, + DWORD ProcessInformationLength); + +typedef PSTR (NTAPI * _RtlIpv4AddressToStringA)( + struct in_addr *Addr, + PSTR S); + +typedef PSTR (NTAPI * _RtlIpv6AddressToStringA)( + struct in6_addr *Addr, + PSTR P); + +typedef DWORD (WINAPI * _GetExtendedTcpTable)( + PVOID pTcpTable, + PDWORD pdwSize, + BOOL bOrder, + ULONG ulAf, + TCP_TABLE_CLASS TableClass, + ULONG Reserved); + +typedef DWORD (WINAPI * _GetExtendedUdpTable)( + PVOID pUdpTable, + PDWORD pdwSize, + BOOL bOrder, + ULONG ulAf, + UDP_TABLE_CLASS TableClass, + ULONG Reserved); + +typedef DWORD (CALLBACK *_GetActiveProcessorCount)( + WORD GroupNumber); + +typedef ULONGLONG (CALLBACK *_GetTickCount64)( + void); + +typedef NTSTATUS (NTAPI *_NtQueryObject)( + HANDLE Handle, + OBJECT_INFORMATION_CLASS ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength); + +typedef NTSTATUS (WINAPI *_RtlGetVersion) ( + PRTL_OSVERSIONINFOW lpVersionInformation +); + +typedef NTSTATUS (WINAPI *_NtResumeProcess) ( + HANDLE hProcess +); + +typedef NTSTATUS (WINAPI *_NtSuspendProcess) ( + HANDLE hProcess +); + +typedef NTSTATUS (NTAPI *_NtQueryVirtualMemory) ( + HANDLE ProcessHandle, + PVOID BaseAddress, + int MemoryInformationClass, + PVOID MemoryInformation, + SIZE_T MemoryInformationLength, + PSIZE_T ReturnLength +); + +typedef ULONG (WINAPI *_RtlNtStatusToDosErrorNoTeb) ( + NTSTATUS status +); + +#endif // __NTEXTAPI_H__ diff --git a/ddtrace/vendor/psutil/arch/windows/process_handles.c b/ddtrace/vendor/psutil/arch/windows/process_handles.c new file mode 100644 index 00000000000..5966669e280 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/process_handles.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + */ + +#include +#include +#include +#include "ntextapi.h" +#include "global.h" +#include "process_handles.h" +#include "process_info.h" +#include "../../_psutil_common.h" + +CRITICAL_SECTION g_cs; +BOOL g_initialized = FALSE; +NTSTATUS g_status; +HANDLE g_hFile = NULL; +HANDLE g_hEvtStart = NULL; +HANDLE g_hEvtFinish = NULL; +HANDLE g_hThread = NULL; +PUNICODE_STRING g_pNameBuffer = NULL; +ULONG g_dwSize = 0; +ULONG g_dwLength = 0; + + +#define ObjectNameInformation 1 +#define NTQO_TIMEOUT 100 + + +static VOID +psutil_get_open_files_init(BOOL threaded) { + if (g_initialized == TRUE) + return; + + // Create events for signalling work between threads + if (threaded == TRUE) { + g_hEvtStart = CreateEvent(NULL, FALSE, FALSE, NULL); + g_hEvtFinish = CreateEvent(NULL, FALSE, FALSE, NULL); + InitializeCriticalSection(&g_cs); + } + + g_initialized = TRUE; +} + + +static DWORD WINAPI +psutil_wait_thread(LPVOID lpvParam) { + // Loop infinitely waiting for work + while (TRUE) { + WaitForSingleObject(g_hEvtStart, INFINITE); + + // TODO: return code not checked + g_status = psutil_NtQueryObject( + g_hFile, + ObjectNameInformation, + g_pNameBuffer, + g_dwSize, + &g_dwLength); + SetEvent(g_hEvtFinish); + } +} + + +static DWORD +psutil_create_thread() { + DWORD dwWait = 0; + + if (g_hThread == NULL) + g_hThread = CreateThread( + NULL, + 0, + psutil_wait_thread, + NULL, + 0, + NULL); + if (g_hThread == NULL) + return GetLastError(); + + // Signal the worker thread to start + SetEvent(g_hEvtStart); + + // Wait for the worker thread to finish + dwWait = WaitForSingleObject(g_hEvtFinish, NTQO_TIMEOUT); + + // If the thread hangs, kill it and cleanup + if (dwWait == WAIT_TIMEOUT) { + SuspendThread(g_hThread); + TerminateThread(g_hThread, 1); + WaitForSingleObject(g_hThread, INFINITE); + CloseHandle(g_hThread); + + g_hThread = NULL; + } + + return dwWait; +} + + +static PyObject * +psutil_get_open_files_ntqueryobject(long dwPid, HANDLE hProcess) { + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + DWORD dwInfoSize = 0x10000; + DWORD dwRet = 0; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; + DWORD i = 0; + BOOLEAN error = FALSE; + DWORD dwWait = 0; + PyObject* py_retlist = NULL; + PyObject* py_path = NULL; + + if (g_initialized == FALSE) + psutil_get_open_files_init(TRUE); + + // Due to the use of global variables, ensure only 1 call + // to psutil_get_open_files() is running + EnterCriticalSection(&g_cs); + + if (g_hEvtStart == NULL || + g_hEvtFinish == NULL) + + { + PyErr_SetFromWindowsErr(0); + error = TRUE; + goto cleanup; + } + + // Py_BuildValue raises an exception if NULL is returned + py_retlist = PyList_New(0); + if (py_retlist == NULL) { + error = TRUE; + goto cleanup; + } + + do { + if (pHandleInfo != NULL) { + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + } + + // NtQuerySystemInformation won't give us the correct buffer size, + // so we guess by doubling the buffer size. + dwInfoSize *= 2; + pHandleInfo = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + dwInfoSize); + + if (pHandleInfo == NULL) { + PyErr_NoMemory(); + error = TRUE; + goto cleanup; + } + } while ((status = psutil_NtQuerySystemInformation( + SystemExtendedHandleInformation, + pHandleInfo, + dwInfoSize, + &dwRet)) == STATUS_INFO_LENGTH_MISMATCH); + + // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQuerySystemInformation(SystemExtendedHandleInformation)"); + error = TRUE; + goto cleanup; + } + + for (i = 0; i < pHandleInfo->NumberOfHandles; i++) { + hHandle = &pHandleInfo->Handles[i]; + + // Check if this hHandle belongs to the PID the user specified. + if (hHandle->UniqueProcessId != (ULONG_PTR)dwPid) + goto loop_cleanup; + + if (!DuplicateHandle(hProcess, + (HANDLE)hHandle->HandleValue, + GetCurrentProcess(), + &g_hFile, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + { + /* + printf("[%d] DuplicateHandle (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + // Guess buffer size is MAX_PATH + 1 + g_dwLength = (MAX_PATH+1) * sizeof(WCHAR); + + do { + // Release any previously allocated buffer + if (g_pNameBuffer != NULL) { + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + } + + // NtQueryObject puts the required buffer size in g_dwLength + // WinXP edge case puts g_dwLength == 0, just skip this handle + if (g_dwLength == 0) + goto loop_cleanup; + + g_dwSize = g_dwLength; + if (g_dwSize > 0) { + g_pNameBuffer = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + g_dwSize); + + if (g_pNameBuffer == NULL) + goto loop_cleanup; + } + + dwWait = psutil_create_thread(); + + // If the call does not return, skip this handle + if (dwWait != WAIT_OBJECT_0) + goto loop_cleanup; + + } while (g_status == STATUS_INFO_LENGTH_MISMATCH); + + // NtQueryObject stopped returning STATUS_INFO_LENGTH_MISMATCH + if (!NT_SUCCESS(g_status)) + goto loop_cleanup; + + // Convert to PyUnicode and append it to the return list + if (g_pNameBuffer->Length > 0) { + /* + printf("[%d] Filename (%#x) %#d bytes: %S\n", + dwPid, + hHandle->HandleValue, + g_pNameBuffer->Length, + g_pNameBuffer->Buffer); + */ + + py_path = PyUnicode_FromWideChar(g_pNameBuffer->Buffer, + g_pNameBuffer->Length/2); + if (py_path == NULL) { + /* + printf("[%d] PyUnicode_FromWideChar (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + + if (PyList_Append(py_retlist, py_path)) { + /* + printf("[%d] PyList_Append (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + } + +loop_cleanup: + Py_XDECREF(py_path); + py_path = NULL; + + if (g_pNameBuffer != NULL) + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + g_dwLength = 0; + + if (g_hFile != NULL) + CloseHandle(g_hFile); + g_hFile = NULL; +} + +cleanup: + if (g_pNameBuffer != NULL) + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + g_dwLength = 0; + + if (g_hFile != NULL) + CloseHandle(g_hFile); + g_hFile = NULL; + + if (pHandleInfo != NULL) + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + + if (error) { + Py_XDECREF(py_retlist); + py_retlist = NULL; + } + + LeaveCriticalSection(&g_cs); + + return py_retlist; +} + + +static PyObject * +psutil_get_open_files_getmappedfilename(long dwPid, HANDLE hProcess) { + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + DWORD dwInfoSize = 0x10000; + DWORD dwRet = 0; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; + HANDLE hFile = NULL; + HANDLE hMap = NULL; + DWORD i = 0; + BOOLEAN error = FALSE; + PyObject* py_retlist = NULL; + PyObject* py_path = NULL; + ULONG dwSize = 0; + LPVOID pMem = NULL; + wchar_t pszFilename[MAX_PATH+1]; + + if (g_initialized == FALSE) + psutil_get_open_files_init(FALSE); + + // Py_BuildValue raises an exception if NULL is returned + py_retlist = PyList_New(0); + if (py_retlist == NULL) { + error = TRUE; + goto cleanup; + } + + do { + if (pHandleInfo != NULL) { + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + } + + // NtQuerySystemInformation won't give us the correct buffer size, + // so we guess by doubling the buffer size. + dwInfoSize *= 2; + pHandleInfo = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + dwInfoSize); + + if (pHandleInfo == NULL) { + PyErr_NoMemory(); + error = TRUE; + goto cleanup; + } + } while ((status = psutil_NtQuerySystemInformation( + SystemExtendedHandleInformation, + pHandleInfo, + dwInfoSize, + &dwRet)) == STATUS_INFO_LENGTH_MISMATCH); + + // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQuerySystemInformation(SystemExtendedHandleInformation)"); + error = TRUE; + goto cleanup; + } + + for (i = 0; i < pHandleInfo->NumberOfHandles; i++) { + hHandle = &pHandleInfo->Handles[i]; + + // Check if this hHandle belongs to the PID the user specified. + if (hHandle->UniqueProcessId != (ULONG_PTR)dwPid) + goto loop_cleanup; + + if (!DuplicateHandle(hProcess, + (HANDLE)hHandle->HandleValue, + GetCurrentProcess(), + &hFile, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + { + /* + printf("[%d] DuplicateHandle (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hMap == NULL) { + /* + printf("[%d] CreateFileMapping (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + pMem = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 1); + + if (pMem == NULL) { + /* + printf("[%d] MapViewOfFile (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + dwSize = GetMappedFileName( + GetCurrentProcess(), pMem, (LPSTR)pszFilename, MAX_PATH); + if (dwSize == 0) { + /* + printf("[%d] GetMappedFileName (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + pszFilename[dwSize] = '\0'; + /* + printf("[%d] Filename (%#x) %#d bytes: %S\n", + dwPid, + hHandle->HandleValue, + dwSize, + pszFilename); + */ + + py_path = PyUnicode_FromWideChar(pszFilename, dwSize); + if (py_path == NULL) { + /* + printf("[%d] PyUnicode_FromStringAndSize (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + + if (PyList_Append(py_retlist, py_path)) { + /* + printf("[%d] PyList_Append (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + +loop_cleanup: + Py_XDECREF(py_path); + py_path = NULL; + + if (pMem != NULL) + UnmapViewOfFile(pMem); + pMem = NULL; + + if (hMap != NULL) + CloseHandle(hMap); + hMap = NULL; + + if (hFile != NULL) + CloseHandle(hFile); + hFile = NULL; + + dwSize = 0; + } + +cleanup: + if (pMem != NULL) + UnmapViewOfFile(pMem); + pMem = NULL; + + if (hMap != NULL) + CloseHandle(hMap); + hMap = NULL; + + if (hFile != NULL) + CloseHandle(hFile); + hFile = NULL; + + if (pHandleInfo != NULL) + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + + if (error) { + Py_XDECREF(py_retlist); + py_retlist = NULL; + } + + return py_retlist; +} + + +/* + * The public function. + */ +PyObject * +psutil_get_open_files(long dwPid, HANDLE hProcess) { + // Threaded version only works for Vista+ + if (PSUTIL_WINVER >= PSUTIL_WINDOWS_VISTA) + return psutil_get_open_files_ntqueryobject(dwPid, hProcess); + else + return psutil_get_open_files_getmappedfilename(dwPid, hProcess); +} diff --git a/ddtrace/vendor/psutil/arch/windows/process_handles.h b/ddtrace/vendor/psutil/arch/windows/process_handles.h new file mode 100644 index 00000000000..342ce8fd26a --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/process_handles.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include + +PyObject* psutil_get_open_files(long pid, HANDLE processHandle); diff --git a/ddtrace/vendor/psutil/arch/windows/process_info.c b/ddtrace/vendor/psutil/arch/windows/process_info.c new file mode 100644 index 00000000000..5ea5f765c0c --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/process_info.c @@ -0,0 +1,1027 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Helper functions related to fetching process information. Used by + * _psutil_windows module methods. + */ + +#include +#include +#include +#include + +#include "ntextapi.h" +#include "global.h" +#include "security.h" +#include "process_info.h" +#include "../../_psutil_common.h" + + +// ==================================================================== +// Helper structures to access the memory correctly. +// Some of these might also be defined in the winternl.h header file +// but unfortunately not in a usable way. +// ==================================================================== + +// https://msdn.microsoft.com/en-us/library/aa813706(v=vs.85).aspx +#ifdef _WIN64 +typedef struct { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[21]; + PVOID LoaderData; + PRTL_USER_PROCESS_PARAMETERS_ ProcessParameters; + /* More fields ... */ +} PEB_; +#else +typedef struct { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PVOID Ldr; + PRTL_USER_PROCESS_PARAMETERS_ ProcessParameters; + /* More fields ... */ +} PEB_; +#endif + +#ifdef _WIN64 +/* When we are a 64 bit process accessing a 32 bit (WoW64) process we need to + use the 32 bit structure layout. */ +typedef struct { + USHORT Length; + USHORT MaxLength; + DWORD Buffer; +} UNICODE_STRING32; + +typedef struct { + BYTE Reserved1[16]; + DWORD Reserved2[5]; + UNICODE_STRING32 CurrentDirectoryPath; + DWORD CurrentDirectoryHandle; + UNICODE_STRING32 DllPath; + UNICODE_STRING32 ImagePathName; + UNICODE_STRING32 CommandLine; + DWORD env; +} RTL_USER_PROCESS_PARAMETERS32; + +typedef struct { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + DWORD Reserved3[2]; + DWORD Ldr; + DWORD ProcessParameters; + /* More fields ... */ +} PEB32; +#else +/* When we are a 32 bit (WoW64) process accessing a 64 bit process we need to + use the 64 bit structure layout and a special function to read its memory. + */ +typedef NTSTATUS (NTAPI *_NtWow64ReadVirtualMemory64)( + HANDLE ProcessHandle, + PVOID64 BaseAddress, + PVOID Buffer, + ULONG64 Size, + PULONG64 NumberOfBytesRead); + +typedef struct { + PVOID Reserved1[2]; + PVOID64 PebBaseAddress; + PVOID Reserved2[4]; + PVOID UniqueProcessId[2]; + PVOID Reserved3[2]; +} PROCESS_BASIC_INFORMATION64; + +typedef struct { + USHORT Length; + USHORT MaxLength; + PVOID64 Buffer; +} UNICODE_STRING64; + +typedef struct { + BYTE Reserved1[16]; + PVOID64 Reserved2[5]; + UNICODE_STRING64 CurrentDirectoryPath; + PVOID64 CurrentDirectoryHandle; + UNICODE_STRING64 DllPath; + UNICODE_STRING64 ImagePathName; + UNICODE_STRING64 CommandLine; + PVOID64 env; +} RTL_USER_PROCESS_PARAMETERS64; + +typedef struct { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[21]; + PVOID64 LoaderData; + PVOID64 ProcessParameters; + /* More fields ... */ +} PEB64; +#endif + + +#define PSUTIL_FIRST_PROCESS(Processes) ( \ + (PSYSTEM_PROCESS_INFORMATION)(Processes)) +#define PSUTIL_NEXT_PROCESS(Process) ( \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ + (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : NULL) + + +// ==================================================================== +// Process and PIDs utiilties. +// ==================================================================== + + +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) + +/* + * Return 1 if PID exists, 0 if not, -1 on error. + */ +int +psutil_pid_in_pids(DWORD pid) { + DWORD *proclist = NULL; + DWORD numberOfReturnedPIDs; + DWORD i; + + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + return -1; + for (i = 0; i < numberOfReturnedPIDs; i++) { + if (proclist[i] == pid) { + free(proclist); + return 1; + } + } + free(proclist); + return 0; +} + + +/* + * Given a process HANDLE checks whether it's actually running. + * Returns: + * - 1: running + * - 0: not running + * - -1: WindowsError + * - -2: AssertionError + */ +int +psutil_is_phandle_running(HANDLE hProcess, DWORD pid) { + DWORD processExitCode = 0; + + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // Yeah, this is the actual error code in case of + // "no such process". + if (! psutil_assert_pid_not_exists( + pid, "iphr: OpenProcess() -> ERROR_INVALID_PARAMETER")) { + return -2; + } + return 0; + } + return -1; + } + + if (GetExitCodeProcess(hProcess, &processExitCode)) { + // XXX - maybe STILL_ACTIVE is not fully reliable as per: + // http://stackoverflow.com/questions/1591342/#comment47830782_1591379 + if (processExitCode == STILL_ACTIVE) { + if (! psutil_assert_pid_exists( + pid, "iphr: GetExitCodeProcess() -> STILL_ACTIVE")) { + return -2; + } + return 1; + } + else { + // We can't be sure so we look into pids. + if (psutil_pid_in_pids(pid) == 1) { + return 1; + } + else { + CloseHandle(hProcess); + return 0; + } + } + } + + CloseHandle(hProcess); + if (! psutil_assert_pid_not_exists( pid, "iphr: exit fun")) { + return -2; + } + return -1; +} + + +/* + * Given a process HANDLE checks whether it's actually running and if + * it does return it, else return NULL with the proper Python exception + * set. + */ +HANDLE +psutil_check_phandle(HANDLE hProcess, DWORD pid) { + int ret = psutil_is_phandle_running(hProcess, pid); + if (ret == 1) { + return hProcess; + } + else if (ret == 0) { + return NoSuchProcess(""); + } + else if (ret == -1) { + if (GetLastError() == ERROR_ACCESS_DENIED) + return PyErr_SetFromWindowsErr(0); + else + return PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); + } + else { + return NULL; + } +} + + +/* + * A wrapper around OpenProcess setting NSP exception if process + * no longer exists. + * "pid" is the process pid, "dwDesiredAccess" is the first argument + * exptected by OpenProcess. + * Return a process handle or NULL. + */ +HANDLE +psutil_handle_from_pid(DWORD pid, DWORD access) { + HANDLE hProcess; + + if (pid == 0) { + // otherwise we'd get NoSuchProcess + return AccessDenied(""); + } + // needed for GetExitCodeProcess + access |= PROCESS_QUERY_LIMITED_INFORMATION; + hProcess = OpenProcess(access, FALSE, pid); + return psutil_check_phandle(hProcess, pid); +} + + +DWORD * +psutil_get_pids(DWORD *numberOfReturnedPIDs) { + // Win32 SDK says the only way to know if our process array + // wasn't large enough is to check the returned size and make + // sure that it doesn't match the size of the array. + // If it does we allocate a larger array and try again + + // Stores the actual array + DWORD *procArray = NULL; + DWORD procArrayByteSz; + int procArraySz = 0; + + // Stores the byte size of the returned array from enumprocesses + DWORD enumReturnSz = 0; + + do { + procArraySz += 1024; + if (procArray != NULL) + free(procArray); + procArrayByteSz = procArraySz * sizeof(DWORD); + procArray = malloc(procArrayByteSz); + if (procArray == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { + free(procArray); + PyErr_SetFromWindowsErr(0); + return NULL; + } + } while (enumReturnSz == procArraySz * sizeof(DWORD)); + + // The number of elements is the returned size / size of each element + *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); + + return procArray; +} + + +int +psutil_assert_pid_exists(DWORD pid, char *err) { + if (PSUTIL_TESTING) { + if (psutil_pid_in_pids(pid) == 0) { + PyErr_SetString(PyExc_AssertionError, err); + return 0; + } + } + return 1; +} + + +int +psutil_assert_pid_not_exists(DWORD pid, char *err) { + if (PSUTIL_TESTING) { + if (psutil_pid_in_pids(pid) == 1) { + PyErr_SetString(PyExc_AssertionError, err); + return 0; + } + } + return 1; +} + + +/* +/* Check for PID existance by using OpenProcess() + GetExitCodeProcess. +/* Returns: + * 1: pid exists + * 0: it doesn't + * -1: error + */ +int +psutil_pid_is_running(DWORD pid) { + HANDLE hProcess; + DWORD exitCode; + DWORD err; + + // Special case for PID 0 System Idle Process + if (pid == 0) + return 1; + if (pid < 0) + return 0; + hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (NULL == hProcess) { + err = GetLastError(); + // Yeah, this is the actual error code in case of "no such process". + if (err == ERROR_INVALID_PARAMETER) { + if (! psutil_assert_pid_not_exists( + pid, "pir: OpenProcess() -> INVALID_PARAMETER")) { + return -1; + } + return 0; + } + // Access denied obviously means there's a process to deny access to. + else if (err == ERROR_ACCESS_DENIED) { + if (! psutil_assert_pid_exists( + pid, "pir: OpenProcess() ACCESS_DENIED")) { + return -1; + } + return 1; + } + // Be strict and raise an exception; the caller is supposed + // to take -1 into account. + else { + PyErr_SetFromOSErrnoWithSyscall("OpenProcess(PROCESS_VM_READ)"); + return -1; + } + } + + if (GetExitCodeProcess(hProcess, &exitCode)) { + CloseHandle(hProcess); + // XXX - maybe STILL_ACTIVE is not fully reliable as per: + // http://stackoverflow.com/questions/1591342/#comment47830782_1591379 + if (exitCode == STILL_ACTIVE) { + if (! psutil_assert_pid_exists( + pid, "pir: GetExitCodeProcess() -> STILL_ACTIVE")) { + return -1; + } + return 1; + } + // We can't be sure so we look into pids. + else { + return psutil_pid_in_pids(pid); + } + } + else { + err = GetLastError(); + CloseHandle(hProcess); + // Same as for OpenProcess, assume access denied means there's + // a process to deny access to. + if (err == ERROR_ACCESS_DENIED) { + if (! psutil_assert_pid_exists( + pid, "pir: GetExitCodeProcess() -> ERROR_ACCESS_DENIED")) { + return -1; + } + return 1; + } + else { + PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); + return -1; + } + } +} + + +/* Given a pointer into a process's memory, figure out how much data can be + * read from it. */ +static int +psutil_get_process_region_size(HANDLE hProcess, LPCVOID src, SIZE_T *psize) { + MEMORY_BASIC_INFORMATION info; + + if (!VirtualQueryEx(hProcess, src, &info, sizeof(info))) { + PyErr_SetFromOSErrnoWithSyscall("VirtualQueryEx"); + return -1; + } + + *psize = info.RegionSize - ((char*)src - (char*)info.BaseAddress); + return 0; +} + + +enum psutil_process_data_kind { + KIND_CMDLINE, + KIND_CWD, + KIND_ENVIRON, +}; + +/* Get data from the process with the given pid. The data is returned in the + pdata output member as a nul terminated string which must be freed on + success. + + On success 0 is returned. On error the output parameter is not touched, -1 + is returned, and an appropriate Python exception is set. */ +static int +psutil_get_process_data(long pid, + enum psutil_process_data_kind kind, + WCHAR **pdata, + SIZE_T *psize) { + /* This function is quite complex because there are several cases to be + considered: + + Two cases are really simple: we (i.e. the python interpreter) and the + target process are both 32 bit or both 64 bit. In that case the memory + layout of the structures matches up and all is well. + + When we are 64 bit and the target process is 32 bit we need to use + custom 32 bit versions of the structures. + + When we are 32 bit and the target process is 64 bit we need to use + custom 64 bit version of the structures. Also we need to use separate + Wow64 functions to get the information. + + A few helper structs are defined above so that the compiler can handle + calculating the correct offsets. + + Additional help also came from the following sources: + + https://github.com/kohsuke/winp and + http://wj32.org/wp/2009/01/24/howto-get-the-command-line-of-processes/ + http://stackoverflow.com/a/14012919 + http://www.drdobbs.com/embracing-64-bit-windows/184401966 + */ + _NtQueryInformationProcess NtQueryInformationProcess = NULL; +#ifndef _WIN64 + static _NtQueryInformationProcess NtWow64QueryInformationProcess64 = NULL; + static _NtWow64ReadVirtualMemory64 NtWow64ReadVirtualMemory64 = NULL; +#endif + HANDLE hProcess = NULL; + LPCVOID src; + SIZE_T size; + WCHAR *buffer = NULL; +#ifdef _WIN64 + LPVOID ppeb32 = NULL; +#else + PVOID64 src64; + BOOL weAreWow64; + BOOL theyAreWow64; +#endif + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + NTSTATUS status; + + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return -1; + +#ifdef _WIN64 + /* 64 bit case. Check if the target is a 32 bit process running in WoW64 + * mode. */ + status = psutil_NtQueryInformationProcess( + hProcess, + ProcessWow64Information, + &ppeb32, + sizeof(LPVOID), + NULL); + + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQueryInformationProcess(ProcessWow64Information)"); + goto error; + } + + if (ppeb32 != NULL) { + /* We are 64 bit. Target process is 32 bit running in WoW64 mode. */ + PEB32 peb32; + RTL_USER_PROCESS_PARAMETERS32 procParameters32; + + // read PEB + if (!ReadProcessMemory(hProcess, ppeb32, &peb32, sizeof(peb32), NULL)) { + // May fail with ERROR_PARTIAL_COPY, see: + // https://github.com/giampaolo/psutil/issues/875 + PyErr_SetFromWindowsErr(0); + goto error; + } + + // read process parameters + if (!ReadProcessMemory(hProcess, + UlongToPtr(peb32.ProcessParameters), + &procParameters32, + sizeof(procParameters32), + NULL)) + { + // May fail with ERROR_PARTIAL_COPY, see: + // https://github.com/giampaolo/psutil/issues/875 + PyErr_SetFromWindowsErr(0); + goto error; + } + + switch (kind) { + case KIND_CMDLINE: + src = UlongToPtr(procParameters32.CommandLine.Buffer), + size = procParameters32.CommandLine.Length; + break; + case KIND_CWD: + src = UlongToPtr(procParameters32.CurrentDirectoryPath.Buffer); + size = procParameters32.CurrentDirectoryPath.Length; + break; + case KIND_ENVIRON: + src = UlongToPtr(procParameters32.env); + break; + } + } else +#else + /* 32 bit case. Check if the target is also 32 bit. */ + if (!IsWow64Process(GetCurrentProcess(), &weAreWow64) || + !IsWow64Process(hProcess, &theyAreWow64)) { + PyErr_SetFromOSErrnoWithSyscall("IsWow64Process"); + goto error; + } + + if (weAreWow64 && !theyAreWow64) { + /* We are 32 bit running in WoW64 mode. Target process is 64 bit. */ + PROCESS_BASIC_INFORMATION64 pbi64; + PEB64 peb64; + RTL_USER_PROCESS_PARAMETERS64 procParameters64; + + if (NtWow64QueryInformationProcess64 == NULL) { + NtWow64QueryInformationProcess64 = \ + psutil_GetProcAddressFromLib( + "ntdll.dll", "NtWow64QueryInformationProcess64"); + if (NtWow64QueryInformationProcess64 == NULL) { + PyErr_Clear(); + AccessDenied("can't query 64-bit process in 32-bit-WoW mode"); + goto error; + } + } + if (NtWow64ReadVirtualMemory64 == NULL) { + NtWow64ReadVirtualMemory64 = \ + psutil_GetProcAddressFromLib( + "ntdll.dll", "NtWow64ReadVirtualMemory64"); + if (NtWow64ReadVirtualMemory64 == NULL) { + PyErr_Clear(); + AccessDenied("can't query 64-bit process in 32-bit-WoW mode"); + goto error; + } + } + + status = NtWow64QueryInformationProcess64( + hProcess, + ProcessBasicInformation, + &pbi64, + sizeof(pbi64), + NULL); + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, + "NtWow64QueryInformationProcess64(ProcessBasicInformation)" + ); + goto error; + } + + // read peb + status = NtWow64ReadVirtualMemory64( + hProcess, + pbi64.PebBaseAddress, + &peb64, + sizeof(peb64), + NULL); + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr(status, "NtWow64ReadVirtualMemory64"); + goto error; + } + + // read process parameters + status = NtWow64ReadVirtualMemory64( + hProcess, + peb64.ProcessParameters, + &procParameters64, + sizeof(procParameters64), + NULL); + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, + "NtWow64ReadVirtualMemory64(ProcessParameters)" + ); + goto error; + } + + switch (kind) { + case KIND_CMDLINE: + src64 = procParameters64.CommandLine.Buffer; + size = procParameters64.CommandLine.Length; + break; + case KIND_CWD: + src64 = procParameters64.CurrentDirectoryPath.Buffer, + size = procParameters64.CurrentDirectoryPath.Length; + break; + case KIND_ENVIRON: + src64 = procParameters64.env; + break; + } + } else +#endif + /* Target process is of the same bitness as us. */ + { + PROCESS_BASIC_INFORMATION pbi; + PEB_ peb; + RTL_USER_PROCESS_PARAMETERS_ procParameters; + + status = psutil_NtQueryInformationProcess( + hProcess, + ProcessBasicInformation, + &pbi, + sizeof(pbi), + NULL); + + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQueryInformationProcess(ProcessBasicInformation)"); + goto error; + } + + + // read peb + if (!ReadProcessMemory(hProcess, + pbi.PebBaseAddress, + &peb, + sizeof(peb), + NULL)) + { + // May fail with ERROR_PARTIAL_COPY, see: + // https://github.com/giampaolo/psutil/issues/875 + PyErr_SetFromWindowsErr(0); + goto error; + } + + // read process parameters + if (!ReadProcessMemory(hProcess, + peb.ProcessParameters, + &procParameters, + sizeof(procParameters), + NULL)) + { + // May fail with ERROR_PARTIAL_COPY, see: + // https://github.com/giampaolo/psutil/issues/875 + PyErr_SetFromWindowsErr(0); + goto error; + } + + switch (kind) { + case KIND_CMDLINE: + src = procParameters.CommandLine.Buffer; + size = procParameters.CommandLine.Length; + break; + case KIND_CWD: + src = procParameters.CurrentDirectoryPath.Buffer; + size = procParameters.CurrentDirectoryPath.Length; + break; + case KIND_ENVIRON: + src = procParameters.env; + break; + } + } + + if (kind == KIND_ENVIRON) { +#ifndef _WIN64 + if (weAreWow64 && !theyAreWow64) { + AccessDenied("can't query 64-bit process in 32-bit-WoW mode"); + goto error; + } + else +#endif + if (psutil_get_process_region_size(hProcess, src, &size) != 0) + goto error; + } + + buffer = calloc(size + 2, 1); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + +#ifndef _WIN64 + if (weAreWow64 && !theyAreWow64) { + status = NtWow64ReadVirtualMemory64( + hProcess, + src64, + buffer, + size, + NULL); + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr(status, "NtWow64ReadVirtualMemory64"); + goto error; + } + } else +#endif + if (!ReadProcessMemory(hProcess, src, buffer, size, NULL)) { + // May fail with ERROR_PARTIAL_COPY, see: + // https://github.com/giampaolo/psutil/issues/875 + PyErr_SetFromWindowsErr(0); + goto error; + } + + CloseHandle(hProcess); + + *pdata = buffer; + *psize = size; + + return 0; + +error: + if (hProcess != NULL) + CloseHandle(hProcess); + if (buffer != NULL) + free(buffer); + return -1; +} + + +/* + * Get process cmdline by using NtQueryInformationProcess. This is a + * method alternative to PEB which is less likely to result in + * AccessDenied. Requires Windows 8.1+. + */ +static int +psutil_cmdline_query_proc(long pid, WCHAR **pdata, SIZE_T *psize) { + HANDLE hProcess; + ULONG bufLen = 0; + NTSTATUS status; + char * buffer = NULL; + WCHAR * bufWchar = NULL; + PUNICODE_STRING tmp = NULL; + size_t size; + int ProcessCommandLineInformation = 60; + + if (PSUTIL_WINVER < PSUTIL_WINDOWS_8_1) { + PyErr_SetString( + PyExc_RuntimeError, "requires Windows 8.1+"); + goto error; + } + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + goto error; + + // get the right buf size + status = psutil_NtQueryInformationProcess( + hProcess, + ProcessCommandLineInformation, + NULL, + 0, + &bufLen); + + // 0xC0000225 == STATUS_NOT_FOUND, see: + // https://github.com/giampaolo/psutil/issues/1501 + if (status == 0xC0000225) { + AccessDenied("NtQueryInformationProcess(ProcessBasicInformation) -> " + "STATUS_NOT_FOUND translated into PermissionError"); + goto error; + } + + if (status != STATUS_BUFFER_OVERFLOW && \ + status != STATUS_BUFFER_TOO_SMALL && \ + status != STATUS_INFO_LENGTH_MISMATCH) { + psutil_SetFromNTStatusErr( + status, "NtQueryInformationProcess(ProcessBasicInformation)"); + goto error; + } + + // allocate memory + buffer = calloc(bufLen, 1); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + + // get the cmdline + status = psutil_NtQueryInformationProcess( + hProcess, + ProcessCommandLineInformation, + buffer, + bufLen, + &bufLen + ); + if (!NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQueryInformationProcess(ProcessCommandLineInformation)"); + goto error; + } + + // build the string + tmp = (PUNICODE_STRING)buffer; + size = wcslen(tmp->Buffer) + 1; + bufWchar = (WCHAR *)calloc(size, sizeof(WCHAR)); + if (bufWchar == NULL) { + PyErr_NoMemory(); + goto error; + } + wcscpy_s(bufWchar, size, tmp->Buffer); + *pdata = bufWchar; + *psize = size * sizeof(WCHAR); + free(buffer); + CloseHandle(hProcess); + return 0; + +error: + if (buffer != NULL) + free(buffer); + if (hProcess != NULL) + CloseHandle(hProcess); + return -1; +} + + +/* + * Return a Python list representing the arguments for the process + * with given pid or NULL on error. + */ +PyObject * +psutil_get_cmdline(long pid, int use_peb) { + PyObject *ret = NULL; + WCHAR *data = NULL; + SIZE_T size; + PyObject *py_retlist = NULL; + PyObject *py_unicode = NULL; + LPWSTR *szArglist = NULL; + int nArgs, i; + int func_ret; + + /* + Reading the PEB to get the cmdline seem to be the best method if + somebody has tampered with the parameters after creating the process. + For instance, create a process as suspended, patch the command line + in its PEB and unfreeze it. It requires more privileges than + NtQueryInformationProcess though (the fallback): + - https://github.com/giampaolo/psutil/pull/1398 + - https://blog.xpnsec.com/how-to-argue-like-cobalt-strike/ + */ + if (use_peb == 1) + func_ret = psutil_get_process_data(pid, KIND_CMDLINE, &data, &size); + else + func_ret = psutil_cmdline_query_proc(pid, &data, &size); + if (func_ret != 0) + goto out; + + // attempt to parse the command line using Win32 API + szArglist = CommandLineToArgvW(data, &nArgs); + if (szArglist == NULL) { + PyErr_SetFromOSErrnoWithSyscall("CommandLineToArgvW"); + goto out; + } + + // arglist parsed as array of UNICODE_STRING, so convert each to + // Python string object and add to arg list + py_retlist = PyList_New(nArgs); + if (py_retlist == NULL) + goto out; + for (i = 0; i < nArgs; i++) { + py_unicode = PyUnicode_FromWideChar(szArglist[i], + wcslen(szArglist[i])); + if (py_unicode == NULL) + goto out; + PyList_SET_ITEM(py_retlist, i, py_unicode); + py_unicode = NULL; + } + ret = py_retlist; + py_retlist = NULL; + +out: + if (szArglist != NULL) + LocalFree(szArglist); + if (data != NULL) + free(data); + Py_XDECREF(py_unicode); + Py_XDECREF(py_retlist); + return ret; +} + + +PyObject * +psutil_get_cwd(long pid) { + PyObject *ret = NULL; + WCHAR *data = NULL; + SIZE_T size; + + if (psutil_get_process_data(pid, KIND_CWD, &data, &size) != 0) + goto out; + + // convert wchar array to a Python unicode string + ret = PyUnicode_FromWideChar(data, wcslen(data)); + +out: + if (data != NULL) + free(data); + + return ret; +} + + +/* + * returns a Python string containing the environment variable data for the + * process with given pid or NULL on error. + */ +PyObject * +psutil_get_environ(long pid) { + PyObject *ret = NULL; + WCHAR *data = NULL; + SIZE_T size; + + if (psutil_get_process_data(pid, KIND_ENVIRON, &data, &size) != 0) + goto out; + + // convert wchar array to a Python unicode string + ret = PyUnicode_FromWideChar(data, size / 2); + +out: + if (data != NULL) + free(data); + return ret; +} + + +/* + * Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure + * fills the structure with various process information by using + * NtQuerySystemInformation. + * We use this as a fallback when faster functions fail with access + * denied. This is slower because it iterates over all processes. + * On success return 1, else 0 with Python exception already set. + */ +int +psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, + PVOID *retBuffer) { + static ULONG initialBufferSize = 0x4000; + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + PSYSTEM_PROCESS_INFORMATION process; + + bufferSize = initialBufferSize; + buffer = malloc(bufferSize); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + + while (TRUE) { + status = psutil_NtQuerySystemInformation( + SystemProcessInformation, + buffer, + bufferSize, + &bufferSize); + if (status == STATUS_BUFFER_TOO_SMALL || + status == STATUS_INFO_LENGTH_MISMATCH) + { + free(buffer); + buffer = malloc(bufferSize); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + } + else { + break; + } + } + + if (! NT_SUCCESS(status)) { + psutil_SetFromNTStatusErr( + status, "NtQuerySystemInformation(SystemProcessInformation)"); + goto error; + } + + if (bufferSize <= 0x20000) + initialBufferSize = bufferSize; + + process = PSUTIL_FIRST_PROCESS(buffer); + do { + if (process->UniqueProcessId == (HANDLE)pid) { + *retProcess = process; + *retBuffer = buffer; + return 1; + } + } while ( (process = PSUTIL_NEXT_PROCESS(process)) ); + + NoSuchProcess(""); + goto error; + +error: + if (buffer != NULL) + free(buffer); + return 0; +} diff --git a/ddtrace/vendor/psutil/arch/windows/process_info.h b/ddtrace/vendor/psutil/arch/windows/process_info.h new file mode 100644 index 00000000000..4278c4df9e0 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/process_info.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#if !defined(__PROCESS_INFO_H) +#define __PROCESS_INFO_H + +#include +#include +#include "security.h" +#include "ntextapi.h" + +#define HANDLE_TO_PYNUM(handle) PyLong_FromUnsignedLong((unsigned long) handle) +#define PYNUM_TO_HANDLE(obj) ((HANDLE)PyLong_AsUnsignedLong(obj)) + +DWORD* psutil_get_pids(DWORD *numberOfReturnedPIDs); +HANDLE psutil_handle_from_pid(DWORD pid, DWORD dwDesiredAccess); +int psutil_pid_is_running(DWORD pid); +int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, + PVOID *retBuffer); + +int psutil_assert_pid_exists(DWORD pid, char *err); +int psutil_assert_pid_not_exists(DWORD pid, char *err); + +PyObject* psutil_get_cmdline(long pid, int use_peb); +PyObject* psutil_get_cwd(long pid); +PyObject* psutil_get_environ(long pid); + +#endif diff --git a/ddtrace/vendor/psutil/arch/windows/security.c b/ddtrace/vendor/psutil/arch/windows/security.c new file mode 100644 index 00000000000..4e2c7435b22 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/security.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Security related functions for Windows platform (Set privileges such as + * SE DEBUG). + */ + +#include +#include +#include "../../_psutil_common.h" + + +static BOOL +psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { + TOKEN_PRIVILEGES tp; + LUID luid; + TOKEN_PRIVILEGES tpPrevious; + DWORD cbPrevious = sizeof(TOKEN_PRIVILEGES); + + if (! LookupPrivilegeValue(NULL, Privilege, &luid)) { + PyErr_SetFromOSErrnoWithSyscall("LookupPrivilegeValue"); + return 1; + } + + // first pass. get current privilege setting + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = 0; + + if (! AdjustTokenPrivileges( + hToken, + FALSE, + &tp, + sizeof(TOKEN_PRIVILEGES), + &tpPrevious, + &cbPrevious)) + { + PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); + return 1; + } + + // Second pass. Set privilege based on previous setting. + tpPrevious.PrivilegeCount = 1; + tpPrevious.Privileges[0].Luid = luid; + + if (bEnablePrivilege) + tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED); + else + tpPrevious.Privileges[0].Attributes ^= + (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes); + + if (! AdjustTokenPrivileges( + hToken, + FALSE, + &tpPrevious, + cbPrevious, + NULL, + NULL)) + { + PyErr_SetFromOSErrnoWithSyscall("AdjustTokenPrivileges"); + return 1; + } + + return 0; +} + + +static HANDLE +psutil_get_thisproc_token() { + HANDLE hToken = NULL; + HANDLE me = GetCurrentProcess(); + + if (! OpenProcessToken( + me, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + if (GetLastError() == ERROR_NO_TOKEN) + { + if (! ImpersonateSelf(SecurityImpersonation)) { + PyErr_SetFromOSErrnoWithSyscall("ImpersonateSelf"); + return NULL; + } + if (! OpenProcessToken( + me, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + return NULL; + } + } + else { + PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + return NULL; + } + } + + return hToken; +} + + +static void +psutil_print_err() { + char *msg = "psutil module couldn't set SE DEBUG mode for this process; " \ + "please file an issue against psutil bug tracker"; + psutil_debug(msg); + if (GetLastError() != ERROR_ACCESS_DENIED) + PyErr_WarnEx(PyExc_RuntimeWarning, msg, 1); + PyErr_Clear(); +} + + +/* + * Set this process in SE DEBUG mode so that we have more chances of + * querying processes owned by other users, including many owned by + * Administrator and Local System. + * https://docs.microsoft.com/windows-hardware/drivers/debugger/debug-privilege + * This is executed on module import and we don't crash on error. + */ +int +psutil_set_se_debug() { + HANDLE hToken; + int err = 1; + + if ((hToken = psutil_get_thisproc_token()) == NULL) { + // "return 1;" to get an exception + psutil_print_err(); + return 0; + } + + if (psutil_set_privilege(hToken, SE_DEBUG_NAME, TRUE) != 0) { + // "return 1;" to get an exception + psutil_print_err(); + } + + RevertToSelf(); + CloseHandle(hToken); + return 0; +} diff --git a/ddtrace/vendor/psutil/arch/windows/security.h b/ddtrace/vendor/psutil/arch/windows/security.h new file mode 100644 index 00000000000..8d4ddb00d41 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/security.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Security related functions for Windows platform (Set privileges such as + * SeDebug), as well as security helper functions. + */ + +#include + +int psutil_set_se_debug(); + diff --git a/ddtrace/vendor/psutil/arch/windows/services.c b/ddtrace/vendor/psutil/arch/windows/services.c new file mode 100644 index 00000000000..92458494b4a --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/services.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include + +#include "services.h" +#include "../../_psutil_common.h" + +// ================================================================== +// utils +// ================================================================== + +SC_HANDLE +psutil_get_service_handler(char *service_name, DWORD scm_access, DWORD access) +{ + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; + SC_HANDLE sc = NULL; + SC_HANDLE hService = NULL; + + sc = OpenSCManager(NULL, NULL, scm_access); + if (sc == NULL) { + PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); + return NULL; + } + hService = OpenService(sc, service_name, access); + if (hService == NULL) { + PyErr_SetFromOSErrnoWithSyscall("OpenService"); + CloseServiceHandle(sc); + return NULL; + } + CloseServiceHandle(sc); + return hService; +} + + +// XXX - expose these as constants? +static const char * +get_startup_string(DWORD startup) { + switch (startup) { + case SERVICE_AUTO_START: + return "automatic"; + case SERVICE_DEMAND_START: + return "manual"; + case SERVICE_DISABLED: + return "disabled"; +/* + // drivers only (since we use EnumServicesStatusEx() with + // SERVICE_WIN32) + case SERVICE_BOOT_START: + return "boot-start"; + case SERVICE_SYSTEM_START: + return "system-start"; +*/ + default: + return "unknown"; + } +} + + +// XXX - expose these as constants? +static const char * +get_state_string(DWORD state) { + switch (state) { + case SERVICE_RUNNING: + return "running"; + case SERVICE_PAUSED: + return "paused"; + case SERVICE_START_PENDING: + return "start_pending"; + case SERVICE_PAUSE_PENDING: + return "pause_pending"; + case SERVICE_CONTINUE_PENDING: + return "continue_pending"; + case SERVICE_STOP_PENDING: + return "stop_pending"; + case SERVICE_STOPPED: + return "stopped"; + default: + return "unknown"; + } +} + + +// ================================================================== +// APIs +// ================================================================== + +/* + * Enumerate all services. + */ +PyObject * +psutil_winservice_enumerate(PyObject *self, PyObject *args) { + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; + BOOL ok; + SC_HANDLE sc = NULL; + DWORD bytesNeeded = 0; + DWORD srvCount; + DWORD resumeHandle = 0; + DWORD dwBytes = 0; + DWORD i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_name = NULL; + PyObject *py_display_name = NULL; + + if (py_retlist == NULL) + return NULL; + + sc = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); + if (sc == NULL) { + PyErr_SetFromOSErrnoWithSyscall("OpenSCManager"); + return NULL; + } + + for (;;) { + ok = EnumServicesStatusExW( + sc, + SC_ENUM_PROCESS_INFO, + SERVICE_WIN32, // XXX - extend this to include drivers etc.? + SERVICE_STATE_ALL, + (LPBYTE)lpService, + dwBytes, + &bytesNeeded, + &srvCount, + &resumeHandle, + NULL); + if (ok || (GetLastError() != ERROR_MORE_DATA)) + break; + if (lpService) + free(lpService); + dwBytes = bytesNeeded; + lpService = (ENUM_SERVICE_STATUS_PROCESSW*)malloc(dwBytes); + } + + for (i = 0; i < srvCount; i++) { + // Get unicode name / display name. + py_name = NULL; + py_name = PyUnicode_FromWideChar( + lpService[i].lpServiceName, wcslen(lpService[i].lpServiceName)); + if (py_name == NULL) + goto error; + + py_display_name = NULL; + py_display_name = PyUnicode_FromWideChar( + lpService[i].lpDisplayName, wcslen(lpService[i].lpDisplayName)); + if (py_display_name == NULL) + goto error; + + // Construct the result. + py_tuple = Py_BuildValue("(OO)", py_name, py_display_name); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_display_name); + Py_DECREF(py_name); + Py_DECREF(py_tuple); + } + + // Free resources. + CloseServiceHandle(sc); + free(lpService); + return py_retlist; + +error: + Py_DECREF(py_name); + Py_XDECREF(py_display_name); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (sc != NULL) + CloseServiceHandle(sc); + if (lpService != NULL) + free(lpService); + return NULL; +} + + +/* + * Get service config information. Returns: + * - display_name + * - binpath + * - username + * - startup_type + */ +PyObject * +psutil_winservice_query_config(PyObject *self, PyObject *args) { + char *service_name; + SC_HANDLE hService = NULL; + BOOL ok; + DWORD bytesNeeded = 0; + DWORD resumeHandle = 0; + DWORD dwBytes = 0; + QUERY_SERVICE_CONFIGW *qsc = NULL; + PyObject *py_tuple = NULL; + PyObject *py_unicode_display_name = NULL; + PyObject *py_unicode_binpath = NULL; + PyObject *py_unicode_username = NULL; + + if (!PyArg_ParseTuple(args, "s", &service_name)) + return NULL; + hService = psutil_get_service_handler( + service_name, SC_MANAGER_ENUMERATE_SERVICE, SERVICE_QUERY_CONFIG); + if (hService == NULL) + goto error; + + // First call to QueryServiceConfigW() is necessary to get the + // right size. + bytesNeeded = 0; + QueryServiceConfigW(hService, NULL, 0, &bytesNeeded); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); + goto error; + } + qsc = (QUERY_SERVICE_CONFIGW *)malloc(bytesNeeded); + ok = QueryServiceConfigW(hService, qsc, bytesNeeded, &bytesNeeded); + if (ok == 0) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfigW"); + goto error; + } + + // Get unicode display name. + py_unicode_display_name = PyUnicode_FromWideChar( + qsc->lpDisplayName, wcslen(qsc->lpDisplayName)); + if (py_unicode_display_name == NULL) + goto error; + + // Get unicode bin path. + py_unicode_binpath = PyUnicode_FromWideChar( + qsc->lpBinaryPathName, wcslen(qsc->lpBinaryPathName)); + if (py_unicode_binpath == NULL) + goto error; + + // Get unicode username. + py_unicode_username = PyUnicode_FromWideChar( + qsc->lpServiceStartName, wcslen(qsc->lpServiceStartName)); + if (py_unicode_username == NULL) + goto error; + + // Construct result tuple. + py_tuple = Py_BuildValue( + "(OOOs)", + py_unicode_display_name, + py_unicode_binpath, + py_unicode_username, + get_startup_string(qsc->dwStartType) // startup + ); + if (py_tuple == NULL) + goto error; + + // Free resources. + Py_DECREF(py_unicode_display_name); + Py_DECREF(py_unicode_binpath); + Py_DECREF(py_unicode_username); + free(qsc); + CloseServiceHandle(hService); + return py_tuple; + +error: + Py_XDECREF(py_unicode_display_name); + Py_XDECREF(py_unicode_binpath); + Py_XDECREF(py_unicode_username); + Py_XDECREF(py_tuple); + if (hService != NULL) + CloseServiceHandle(hService); + if (qsc != NULL) + free(qsc); + return NULL; +} + + +/* + * Get service status information. Returns: + * - status + * - pid + */ +PyObject * +psutil_winservice_query_status(PyObject *self, PyObject *args) { + char *service_name; + SC_HANDLE hService = NULL; + BOOL ok; + DWORD bytesNeeded = 0; + DWORD resumeHandle = 0; + DWORD dwBytes = 0; + SERVICE_STATUS_PROCESS *ssp = NULL; + PyObject *py_tuple = NULL; + + if (!PyArg_ParseTuple(args, "s", &service_name)) + return NULL; + hService = psutil_get_service_handler( + service_name, SC_MANAGER_ENUMERATE_SERVICE, SERVICE_QUERY_STATUS); + if (hService == NULL) + goto error; + + // First call to QueryServiceStatusEx() is necessary to get the + // right size. + QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, NULL, 0, + &bytesNeeded); + if (GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + // Also services.msc fails in the same manner, so we return an + // empty string. + CloseServiceHandle(hService); + return Py_BuildValue("s", ""); + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); + goto error; + } + ssp = (SERVICE_STATUS_PROCESS *)HeapAlloc( + GetProcessHeap(), 0, bytesNeeded); + if (ssp == NULL) { + PyErr_NoMemory(); + goto error; + } + + // Actual call. + ok = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)ssp, + bytesNeeded, &bytesNeeded); + if (ok == 0) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceStatusEx"); + goto error; + } + + py_tuple = Py_BuildValue( + "(sk)", + get_state_string(ssp->dwCurrentState), + ssp->dwProcessId + ); + if (py_tuple == NULL) + goto error; + + CloseServiceHandle(hService); + HeapFree(GetProcessHeap(), 0, ssp); + return py_tuple; + +error: + Py_XDECREF(py_tuple); + if (hService != NULL) + CloseServiceHandle(hService); + if (ssp != NULL) + HeapFree(GetProcessHeap(), 0, ssp); + return NULL; +} + + +/* + * Get service description. + */ +PyObject * +psutil_winservice_query_descr(PyObject *self, PyObject *args) { + ENUM_SERVICE_STATUS_PROCESSW *lpService = NULL; + BOOL ok; + DWORD bytesNeeded = 0; + DWORD resumeHandle = 0; + DWORD dwBytes = 0; + SC_HANDLE hService = NULL; + SERVICE_DESCRIPTIONW *scd = NULL; + char *service_name; + PyObject *py_retstr = NULL; + + if (!PyArg_ParseTuple(args, "s", &service_name)) + return NULL; + hService = psutil_get_service_handler( + service_name, SC_MANAGER_ENUMERATE_SERVICE, SERVICE_QUERY_CONFIG); + if (hService == NULL) + goto error; + + // This first call to QueryServiceConfig2W() is necessary in order + // to get the right size. + bytesNeeded = 0; + QueryServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, + &bytesNeeded); + if (GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + // Also services.msc fails in the same manner, so we return an + // empty string. + CloseServiceHandle(hService); + return Py_BuildValue("s", ""); + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); + goto error; + } + + scd = (SERVICE_DESCRIPTIONW *)malloc(bytesNeeded); + ok = QueryServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, + (LPBYTE)scd, bytesNeeded, &bytesNeeded); + if (ok == 0) { + PyErr_SetFromOSErrnoWithSyscall("QueryServiceConfig2W"); + goto error; + } + + if (scd->lpDescription == NULL) { + py_retstr = Py_BuildValue("s", ""); + } + else { + py_retstr = PyUnicode_FromWideChar( + scd->lpDescription, wcslen(scd->lpDescription)); + } + if (!py_retstr) + goto error; + + free(scd); + CloseServiceHandle(hService); + return py_retstr; + +error: + if (hService != NULL) + CloseServiceHandle(hService); + if (lpService != NULL) + free(lpService); + return NULL; +} + + +/* + * Start service. + * XXX - note: this is exposed but not used. + */ +PyObject * +psutil_winservice_start(PyObject *self, PyObject *args) { + char *service_name; + BOOL ok; + SC_HANDLE hService = NULL; + + if (!PyArg_ParseTuple(args, "s", &service_name)) + return NULL; + hService = psutil_get_service_handler( + service_name, SC_MANAGER_ALL_ACCESS, SERVICE_START); + if (hService == NULL) { + goto error; + } + ok = StartService(hService, 0, NULL); + if (ok == 0) { + PyErr_SetFromOSErrnoWithSyscall("StartService"); + goto error; + } + + Py_RETURN_NONE; + +error: + if (hService != NULL) + CloseServiceHandle(hService); + return NULL; +} + + +/* + * Stop service. + * XXX - note: this is exposed but not used. + */ +PyObject * +psutil_winservice_stop(PyObject *self, PyObject *args) { + char *service_name; + BOOL ok; + SC_HANDLE hService = NULL; + SERVICE_STATUS ssp; + + if (!PyArg_ParseTuple(args, "s", &service_name)) + return NULL; + hService = psutil_get_service_handler( + service_name, SC_MANAGER_ALL_ACCESS, SERVICE_STOP); + if (hService == NULL) + goto error; + + // Note: this can hang for 30 secs. + Py_BEGIN_ALLOW_THREADS + ok = ControlService(hService, SERVICE_CONTROL_STOP, &ssp); + Py_END_ALLOW_THREADS + if (ok == 0) { + PyErr_SetFromOSErrnoWithSyscall("ControlService"); + goto error; + } + + CloseServiceHandle(hService); + Py_RETURN_NONE; + +error: + if (hService != NULL) + CloseServiceHandle(hService); + return NULL; +} diff --git a/ddtrace/vendor/psutil/arch/windows/services.h b/ddtrace/vendor/psutil/arch/windows/services.h new file mode 100644 index 00000000000..286ed232c90 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/services.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include + +SC_HANDLE psutil_get_service_handle( +char service_name, DWORD scm_access, DWORD access); +PyObject *psutil_winservice_enumerate(PyObject *self, PyObject *args); +PyObject *psutil_winservice_query_config(PyObject *self, PyObject *args); +PyObject *psutil_winservice_query_status(PyObject *self, PyObject *args); +PyObject *psutil_winservice_query_descr(PyObject *self, PyObject *args); +PyObject *psutil_winservice_start(PyObject *self, PyObject *args); +PyObject *psutil_winservice_stop(PyObject *self, PyObject *args); diff --git a/ddtrace/vendor/psutil/arch/windows/wmi.c b/ddtrace/vendor/psutil/arch/windows/wmi.c new file mode 100644 index 00000000000..f43d790c035 --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/wmi.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Functions related to the Windows Management Instrumentation API. + */ + +#include +#include +#include + +#include "../../_psutil_common.h" + + +// We use an exponentially weighted moving average, just like Unix systems do +// https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation +// +// These constants serve as the damping factor and are calculated with +// 1 / exp(sampling interval in seconds / window size in seconds) +// +// This formula comes from linux's include/linux/sched/loadavg.h +// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 +#define LOADAVG_FACTOR_1F 0.9200444146293232478931553241 +#define LOADAVG_FACTOR_5F 0.9834714538216174894737477501 +#define LOADAVG_FACTOR_15F 0.9944598480048967508795473394 +// The time interval in seconds between taking load counts, same as Linux +#define SAMPLING_INTERVAL 5 + +double load_avg_1m = 0; +double load_avg_5m = 0; +double load_avg_15m = 0; + + +VOID CALLBACK LoadAvgCallback(PVOID hCounter) { + PDH_FMT_COUNTERVALUE displayValue; + double currentLoad; + PDH_STATUS err; + + err = PdhGetFormattedCounterValue( + (PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &displayValue); + // Skip updating the load if we can't get the value successfully + if (err != ERROR_SUCCESS) { + return; + } + currentLoad = displayValue.doubleValue; + + load_avg_1m = load_avg_1m * LOADAVG_FACTOR_1F + currentLoad * \ + (1.0 - LOADAVG_FACTOR_1F); + load_avg_5m = load_avg_5m * LOADAVG_FACTOR_5F + currentLoad * \ + (1.0 - LOADAVG_FACTOR_5F); + load_avg_15m = load_avg_15m * LOADAVG_FACTOR_15F + currentLoad * \ + (1.0 - LOADAVG_FACTOR_15F); +} + + +PyObject * +psutil_init_loadavg_counter(PyObject *self, PyObject *args) { + WCHAR *szCounterPath = L"\\System\\Processor Queue Length"; + PDH_STATUS s; + BOOL ret; + HQUERY hQuery; + HCOUNTER hCounter; + HANDLE event; + HANDLE waitHandle; + + if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) + goto error; + + s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter); + if (s != ERROR_SUCCESS) + goto error; + + event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent"); + if (event == NULL) { + PyErr_SetFromWindowsErr(GetLastError()); + return NULL; + } + + s = PdhCollectQueryDataEx(hQuery, SAMPLING_INTERVAL, event); + if (s != ERROR_SUCCESS) + goto error; + + ret = RegisterWaitForSingleObject( + &waitHandle, + event, + (WAITORTIMERCALLBACK)LoadAvgCallback, + (PVOID) + hCounter, + INFINITE, + WT_EXECUTEDEFAULT); + + if (ret == 0) { + PyErr_SetFromWindowsErr(GetLastError()); + return NULL; + } + + Py_RETURN_NONE; + +error: + PyErr_SetExcFromWindowsErr(PyExc_OSError, 0); + return NULL; +} + + +/* + * Gets the emulated 1 minute, 5 minute and 15 minute load averages + * (processor queue length) for the system. + * `init_loadavg_counter` must be called before this function to engage the + * mechanism that records load values. + */ +PyObject * +psutil_get_loadavg(PyObject *self, PyObject *args) { + return Py_BuildValue("(ddd)", load_avg_1m, load_avg_5m, load_avg_15m); +} diff --git a/ddtrace/vendor/psutil/arch/windows/wmi.h b/ddtrace/vendor/psutil/arch/windows/wmi.h new file mode 100644 index 00000000000..0210f2d699e --- /dev/null +++ b/ddtrace/vendor/psutil/arch/windows/wmi.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + + +#include + +PyObject* psutil_init_loadavg_counter(); +PyObject* psutil_get_loadavg(); diff --git a/ddtrace/vendor/psutil/setup.py b/ddtrace/vendor/psutil/setup.py new file mode 100644 index 00000000000..21de235487c --- /dev/null +++ b/ddtrace/vendor/psutil/setup.py @@ -0,0 +1,227 @@ +__all__ = ["get_extensions"] + +import contextlib +import io +import os +import platform +from setuptools import Extension +import shutil +import sys +import tempfile + +POSIX = os.name == "posix" +WINDOWS = os.name == "nt" +LINUX = sys.platform.startswith("linux") +MACOS = sys.platform.startswith("darwin") +OSX = MACOS # deprecated alias +FREEBSD = sys.platform.startswith("freebsd") +OPENBSD = sys.platform.startswith("openbsd") +NETBSD = sys.platform.startswith("netbsd") +BSD = FREEBSD or OPENBSD or NETBSD +SUNOS = sys.platform.startswith(("sunos", "solaris")) +AIX = sys.platform.startswith("aix") + + +@contextlib.contextmanager +def silenced_output(stream_name): + class DummyFile(io.BytesIO): + # see: https://github.com/giampaolo/psutil/issues/678 + errors = "ignore" + + def write(self, s): + pass + + orig = getattr(sys, stream_name) + try: + setattr(sys, stream_name, DummyFile()) + yield + finally: + setattr(sys, stream_name, orig) + + +def get_extensions(): + macros = [("PSUTIL_VERSION", 567)] + if POSIX: + macros.append(("PSUTIL_POSIX", 1)) + if BSD: + macros.append(("PSUTIL_BSD", 1)) + + sources = ["ddtrace/vendor/psutil/_psutil_common.c"] + if POSIX: + sources.append("ddtrace/vendor/psutil/_psutil_posix.c") + + if WINDOWS: + + def get_winver(): + win_maj, win_min = sys.getwindowsversion()[0:2] + return "0x0%s" % ((win_maj * 100) + win_min) + + if sys.getwindowsversion()[0] < 6: + msg = "this Windows version is too old (< Windows Vista); " + msg += "psutil 3.4.2 is the latest version which supports Windows " + msg += "2000, XP and 2003 server" + raise RuntimeError(msg) + + macros.append(("PSUTIL_WINDOWS", 1)) + macros.extend( + [ + # be nice to mingw, see: + # http://www.mingw.org/wiki/Use_more_recent_defined_functions + ("_WIN32_WINNT", get_winver()), + ("_AVAIL_WINVER_", get_winver()), + ("_CRT_SECURE_NO_WARNINGS", None), + # see: https://github.com/giampaolo/psutil/issues/348 + ("PSAPI_VERSION", 1), + ] + ) + + sources += [ + "ddtrace/vendor/psutil/_psutil_windows.c", + "ddtrace/vendor/psutil/arch/windows/process_info.c", + "ddtrace/vendor/psutil/arch/windows/process_handles.c", + "ddtrace/vendor/psutil/arch/windows/security.c", + "ddtrace/vendor/psutil/arch/windows/inet_ntop.c", + "ddtrace/vendor/psutil/arch/windows/services.c", + "ddtrace/vendor/psutil/arch/windows/global.c", + "ddtrace/vendor/psutil/arch/windows/wmi.c", + ] + ext = Extension( + "ddtrace.vendor.psutil._psutil_windows", + sources=sources, + define_macros=macros, + libraries=[ + "psapi", + "kernel32", + "advapi32", + "shell32", + "netapi32", + "wtsapi32", + "ws2_32", + "PowrProf", + "pdh", + ], + # extra_compile_args=["/Z7"], + # extra_link_args=["/DEBUG"] + ) + + elif MACOS: + macros.append(("PSUTIL_OSX", 1)) + sources += [ + "ddtrace/vendor/psutil/_psutil_osx.c", + "ddtrace/vendor/psutil/arch/osx/process_info.c", + ] + ext = Extension( + "ddtrace.vendor.psutil._psutil_osx", + sources=sources, + define_macros=macros, + extra_link_args=["-framework", "CoreFoundation", "-framework", "IOKit"], + ) + + elif FREEBSD: + macros.append(("PSUTIL_FREEBSD", 1)) + sources += [ + "ddtrace/vendor/psutil/_psutil_bsd.c", + "ddtrace/vendor/psutil/arch/freebsd/specific.c", + "ddtrace/vendor/psutil/arch/freebsd/sys_socks.c", + "ddtrace/vendor/psutil/arch/freebsd/proc_socks.c", + ] + ext = Extension( + "ddtrace.vendor.psutil._psutil_bsd", sources=sources, define_macros=macros, libraries=["devstat"], + ) + + elif OPENBSD: + macros.append(("PSUTIL_OPENBSD", 1)) + ext = Extension( + "ddtrace.vendor.psutil._psutil_bsd", + sources=sources + ["ddtrace/vendor/psutil/_psutil_bsd.c", "ddtrace/vendor/psutil/arch/openbsd/specific.c"], + define_macros=macros, + libraries=["kvm"], + ) + + elif NETBSD: + macros.append(("PSUTIL_NETBSD", 1)) + sources += [ + "ddtrace/vendor/psutil/_psutil_bsd.c", + "ddtrace/vendor/psutil/arch/netbsd/specific.c", + "ddtrace/vendor/psutil/arch/netbsd/socks.c", + ] + ext = Extension("ddtrace.vendor.psutil._psutil_bsd", sources=sources, define_macros=macros, libraries=["kvm"],) + + elif LINUX: + + def get_ethtool_macro(): + # see: https://github.com/giampaolo/ddtrace/vendor/psutil/issues/659 + from distutils.unixccompiler import UnixCCompiler + from distutils.errors import CompileError + + with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="wt") as f: + f.write("#include ") + + output_dir = tempfile.mkdtemp() + try: + compiler = UnixCCompiler() + # https://github.com/giampaolo/ddtrace/vendor/psutil/pull/1568 + if os.getenv("CC"): + compiler.set_executable("compiler_so", os.getenv("CC")) + with silenced_output("stderr"): + with silenced_output("stdout"): + compiler.compile([f.name], output_dir=output_dir) + except CompileError: + return ("PSUTIL_ETHTOOL_MISSING_TYPES", 1) + else: + return None + finally: + os.remove(f.name) + shutil.rmtree(output_dir) + + macros.append(("PSUTIL_LINUX", 1)) + ETHTOOL_MACRO = get_ethtool_macro() + if ETHTOOL_MACRO is not None: + macros.append(ETHTOOL_MACRO) + ext = Extension( + "ddtrace.vendor.psutil._psutil_linux", + sources=sources + ["ddtrace/vendor/psutil/_psutil_linux.c"], + define_macros=macros, + ) + + elif SUNOS: + macros.append(("PSUTIL_SUNOS", 1)) + sources += [ + "ddtrace/vendor/psutil/_psutil_sunos.c", + "ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.c", + "ddtrace/vendor/psutil/arch/solaris/environ.c", + ] + ext = Extension( + "ddtrace.vendor.psutil._psutil_sunos", + sources=sources, + define_macros=macros, + libraries=["kstat", "nsl", "socket"], + ) + + elif AIX: + macros.append(("PSUTIL_AIX", 1)) + sources += [ + "ddtrace/vendor/psutil/_psutil_aix.c", + "ddtrace/vendor/psutil/arch/aix/net_connections.c", + "ddtrace/vendor/psutil/arch/aix/common.c", + "ddtrace/vendor/psutil/arch/aix/ifaddrs.c", + ] + ext = Extension( + "ddtrace.vendor.psutil._psutil_aix", sources=sources, libraries=["perfstat"], define_macros=macros, + ) + else: + raise RuntimeError("platform %s is not supported" % sys.platform) + + if POSIX: + posix_extension = Extension("ddtrace.vendor.psutil._psutil_posix", define_macros=macros, sources=sources) + if SUNOS: + posix_extension.libraries.append("socket") + if platform.release() == "5.10": + posix_extension.sources.append("ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.c") + posix_extension.define_macros.append(("PSUTIL_SUNOS10", 1)) + elif AIX: + posix_extension.sources.append("ddtrace/vendor/psutil/arch/aix/ifaddrs.c") + + return [ext, posix_extension] + else: + return [ext] diff --git a/ddtrace/vendor/wrapt/LICENSE b/ddtrace/vendor/wrapt/LICENSE new file mode 100644 index 00000000000..fa5e1558c14 --- /dev/null +++ b/ddtrace/vendor/wrapt/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013-2022, Graham Dumpleton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ddtrace/vendor/wrapt/__init__.py b/ddtrace/vendor/wrapt/__init__.py new file mode 100644 index 00000000000..ee6539b774a --- /dev/null +++ b/ddtrace/vendor/wrapt/__init__.py @@ -0,0 +1,27 @@ +__version_info__ = ('1', '14', '1') +__version__ = '.'.join(__version_info__) + +from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, WeakFunctionProxy, PartialCallableObjectProxy, + resolve_path, apply_patch, wrap_object, wrap_object_attribute, + function_wrapper, wrap_function_wrapper, patch_function_wrapper, + transient_function_wrapper) + +from .decorators import (adapter_factory, AdapterFactory, decorator, + synchronized) + +from .importer import (register_post_import_hook, when_imported, + notify_module_loaded, discover_post_import_hooks) + +# Import of inspect.getcallargs() included for backward compatibility. An +# implementation of this was previously bundled and made available here for +# Python <2.7. Avoid using this in future. + +from inspect import getcallargs + +# Variant of inspect.formatargspec() included here for forward compatibility. +# This is being done because Python 3.11 dropped inspect.formatargspec() but +# code for handling signature changing decorators relied on it. Exposing the +# bundled implementation here in case any user of wrapt was also needing it. + +from .arguments import formatargspec diff --git a/ddtrace/vendor/wrapt/_wrappers.c b/ddtrace/vendor/wrapt/_wrappers.c new file mode 100644 index 00000000000..67c5d5e1afd --- /dev/null +++ b/ddtrace/vendor/wrapt/_wrappers.c @@ -0,0 +1,3199 @@ +/* ------------------------------------------------------------------------- */ + +#include "Python.h" + +#include "structmember.h" + +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, +#endif + +/* ------------------------------------------------------------------------- */ + +typedef struct { + PyObject_HEAD + + PyObject *dict; + PyObject *wrapped; + PyObject *weakreflist; +} WraptObjectProxyObject; + +PyTypeObject WraptObjectProxy_Type; +PyTypeObject WraptCallableObjectProxy_Type; + +typedef struct { + WraptObjectProxyObject object_proxy; + + PyObject *args; + PyObject *kwargs; +} WraptPartialCallableObjectProxyObject; + +PyTypeObject WraptPartialCallableObjectProxy_Type; + +typedef struct { + WraptObjectProxyObject object_proxy; + + PyObject *instance; + PyObject *wrapper; + PyObject *enabled; + PyObject *binding; + PyObject *parent; +} WraptFunctionWrapperObject; + +PyTypeObject WraptFunctionWrapperBase_Type; +PyTypeObject WraptBoundFunctionWrapper_Type; +PyTypeObject WraptFunctionWrapper_Type; + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_new(PyTypeObject *type, + PyObject *args, PyObject *kwds) +{ + WraptObjectProxyObject *self; + + self = (WraptObjectProxyObject *)type->tp_alloc(type, 0); + + if (!self) + return NULL; + + self->dict = PyDict_New(); + self->wrapped = NULL; + self->weakreflist = NULL; + + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_raw_init(WraptObjectProxyObject *self, + PyObject *wrapped) +{ + static PyObject *module_str = NULL; + static PyObject *doc_str = NULL; + + PyObject *object = NULL; + + Py_INCREF(wrapped); + Py_XDECREF(self->wrapped); + self->wrapped = wrapped; + + if (!module_str) { +#if PY_MAJOR_VERSION >= 3 + module_str = PyUnicode_InternFromString("__module__"); +#else + module_str = PyString_InternFromString("__module__"); +#endif + } + + if (!doc_str) { +#if PY_MAJOR_VERSION >= 3 + doc_str = PyUnicode_InternFromString("__doc__"); +#else + doc_str = PyString_InternFromString("__doc__"); +#endif + } + + object = PyObject_GetAttr(wrapped, module_str); + + if (object) { + if (PyDict_SetItem(self->dict, module_str, object) == -1) { + Py_DECREF(object); + return -1; + } + Py_DECREF(object); + } + else + PyErr_Clear(); + + object = PyObject_GetAttr(wrapped, doc_str); + + if (object) { + if (PyDict_SetItem(self->dict, doc_str, object) == -1) { + Py_DECREF(object); + return -1; + } + Py_DECREF(object); + } + else + PyErr_Clear(); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_init(WraptObjectProxyObject *self, + PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + + static char *kwlist[] = { "wrapped", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:ObjectProxy", + kwlist, &wrapped)) { + return -1; + } + + return WraptObjectProxy_raw_init(self, wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_traverse(WraptObjectProxyObject *self, + visitproc visit, void *arg) +{ + Py_VISIT(self->dict); + Py_VISIT(self->wrapped); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_clear(WraptObjectProxyObject *self) +{ + Py_CLEAR(self->dict); + Py_CLEAR(self->wrapped); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static void WraptObjectProxy_dealloc(WraptObjectProxyObject *self) +{ + PyObject_GC_UnTrack(self); + + if (self->weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *)self); + + WraptObjectProxy_clear(self); + + Py_TYPE(self)->tp_free(self); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_repr(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + +#if PY_MAJOR_VERSION >= 3 + return PyUnicode_FromFormat("<%s at %p for %s at %p>", + Py_TYPE(self)->tp_name, self, + Py_TYPE(self->wrapped)->tp_name, self->wrapped); +#else + return PyString_FromFormat("<%s at %p for %s at %p>", + Py_TYPE(self)->tp_name, self, + Py_TYPE(self->wrapped)->tp_name, self->wrapped); +#endif +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 3) +typedef long Py_hash_t; +#endif + +static Py_hash_t WraptObjectProxy_hash(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_Hash(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_str(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_Str(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_add(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Add(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_subtract(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + + return PyNumber_Subtract(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_multiply(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Multiply(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_divide(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Divide(o1, o2); +} +#endif + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_remainder(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Remainder(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_divmod(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Divmod(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_power(PyObject *o1, PyObject *o2, + PyObject *modulo) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Power(o1, o2, modulo); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_negative(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Negative(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_positive(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Positive(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_absolute(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Absolute(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_bool(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_IsTrue(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_invert(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Invert(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_lshift(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Lshift(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_rshift(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Rshift(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_and(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_And(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_xor(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Xor(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_or(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Or(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_int(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Int(self->wrapped); +} +#endif + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_long(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Long(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_float(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Float(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_oct(WraptObjectProxyObject *self) +{ + PyNumberMethods *nb; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if ((nb = self->wrapped->ob_type->tp_as_number) == NULL || + nb->nb_oct == NULL) { + PyErr_SetString(PyExc_TypeError, + "oct() argument can't be converted to oct"); + return NULL; + } + + return (*nb->nb_oct)(self->wrapped); +} +#endif + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_hex(WraptObjectProxyObject *self) +{ + PyNumberMethods *nb; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if ((nb = self->wrapped->ob_type->tp_as_number) == NULL || + nb->nb_hex == NULL) { + PyErr_SetString(PyExc_TypeError, + "hex() argument can't be converted to hex"); + return NULL; + } + + return (*nb->nb_hex)(self->wrapped); +} +#endif + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_add(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceAdd(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_subtract( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceSubtract(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_multiply( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceMultiply(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_inplace_divide( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceDivide(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} +#endif + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_remainder( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceRemainder(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_power(WraptObjectProxyObject *self, + PyObject *other, PyObject *modulo) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlacePower(self->wrapped, other, modulo); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_lshift(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceLshift(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_rshift(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceRshift(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_and(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceAnd(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_xor(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceXor(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_or(WraptObjectProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceOr(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_floor_divide(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_FloorDivide(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_true_divide(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; + } + + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_TrueDivide(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_floor_divide( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceFloorDivide(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_inplace_true_divide( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; + + object = PyNumber_InPlaceTrueDivide(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_index(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyNumber_Index(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static Py_ssize_t WraptObjectProxy_length(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_Length(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_contains(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PySequence_Contains(self->wrapped, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_getitem(WraptObjectProxyObject *self, + PyObject *key) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetItem(self->wrapped, key); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_setitem(WraptObjectProxyObject *self, + PyObject *key, PyObject* value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + if (value == NULL) + return PyObject_DelItem(self->wrapped, key); + else + return PyObject_SetItem(self->wrapped, key, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_dir( + WraptObjectProxyObject *self, PyObject *args) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_Dir(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_enter( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + method = PyObject_GetAttrString(self->wrapped, "__enter__"); + + if (!method) + return NULL; + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_exit( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + method = PyObject_GetAttrString(self->wrapped, "__exit__"); + + if (!method) + return NULL; + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_copy( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __copy__()"); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_deepcopy( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __deepcopy__()"); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_reduce( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __reduce_ex__()"); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_reduce_ex( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __reduce_ex__()"); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_bytes( + WraptObjectProxyObject *self, PyObject *args) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_Bytes(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_reversed( + WraptObjectProxyObject *self, PyObject *args) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_CallFunctionObjArgs((PyObject *)&PyReversed_Type, + self->wrapped, NULL); +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION >= 3 +static PyObject *WraptObjectProxy_round( + WraptObjectProxyObject *self, PyObject *args) +{ + PyObject *module = NULL; + PyObject *dict = NULL; + PyObject *round = NULL; + + PyObject *result = NULL; + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + module = PyImport_ImportModule("builtins"); + + if (!module) + return NULL; + + dict = PyModule_GetDict(module); + round = PyDict_GetItemString(dict, "round"); + + if (!round) { + Py_DECREF(module); + return NULL; + } + + Py_INCREF(round); + Py_DECREF(module); + + result = PyObject_CallFunctionObjArgs(round, self->wrapped, NULL); + + Py_DECREF(round); + + return result; +} +#endif + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_complex( + WraptObjectProxyObject *self, PyObject *args) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_CallFunctionObjArgs((PyObject *)&PyComplex_Type, + self->wrapped, NULL); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_mro_entries( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return Py_BuildValue("(O)", self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_name( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__name__"); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_name(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_SetAttrString(self->wrapped, "__name__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_qualname( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__qualname__"); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_qualname(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_SetAttrString(self->wrapped, "__qualname__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_module( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__module__"); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_module(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + if (PyObject_SetAttrString(self->wrapped, "__module__", value) == -1) + return -1; + + return PyDict_SetItemString(self->dict, "__module__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_doc( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__doc__"); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_doc(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + if (PyObject_SetAttrString(self->wrapped, "__doc__", value) == -1) + return -1; + + return PyDict_SetItemString(self->dict, "__doc__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_class( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__class__"); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_annotations( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttrString(self->wrapped, "__annotations__"); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_annotations(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_SetAttrString(self->wrapped, "__annotations__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_get_wrapped( + WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + Py_INCREF(self->wrapped); + return self->wrapped; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_set_wrapped(WraptObjectProxyObject *self, + PyObject *value) +{ + if (!value) { + PyErr_SetString(PyExc_TypeError, "__wrapped__ must be an object"); + return -1; + } + + Py_INCREF(value); + Py_XDECREF(self->wrapped); + + self->wrapped = value; + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_getattro( + WraptObjectProxyObject *self, PyObject *name) +{ + PyObject *object = NULL; + PyObject *result = NULL; + + static PyObject *getattr_str = NULL; + + object = PyObject_GenericGetAttr((PyObject *)self, name); + + if (object) + return object; + + PyErr_Clear(); + + if (!getattr_str) { +#if PY_MAJOR_VERSION >= 3 + getattr_str = PyUnicode_InternFromString("__getattr__"); +#else + getattr_str = PyString_InternFromString("__getattr__"); +#endif + } + + object = PyObject_GenericGetAttr((PyObject *)self, getattr_str); + + if (!object) + return NULL; + + result = PyObject_CallFunctionObjArgs(object, name, NULL); + + Py_DECREF(object); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_getattr( + WraptObjectProxyObject *self, PyObject *args) +{ + PyObject *name = NULL; + +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTuple(args, "U:__getattr__", &name)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "S:__getattr__", &name)) + return NULL; +#endif + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetAttr(self->wrapped, name); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptObjectProxy_setattro( + WraptObjectProxyObject *self, PyObject *name, PyObject *value) +{ + static PyObject *self_str = NULL; + static PyObject *wrapped_str = NULL; + static PyObject *startswith_str = NULL; + + PyObject *match = NULL; + + if (!startswith_str) { +#if PY_MAJOR_VERSION >= 3 + startswith_str = PyUnicode_InternFromString("startswith"); +#else + startswith_str = PyString_InternFromString("startswith"); +#endif + } + + if (!self_str) { +#if PY_MAJOR_VERSION >= 3 + self_str = PyUnicode_InternFromString("_self_"); +#else + self_str = PyString_InternFromString("_self_"); +#endif + } + + match = PyObject_CallMethodObjArgs(name, startswith_str, self_str, NULL); + + if (match == Py_True) { + Py_DECREF(match); + + return PyObject_GenericSetAttr((PyObject *)self, name, value); + } + else if (!match) + PyErr_Clear(); + + Py_XDECREF(match); + + if (!wrapped_str) { +#if PY_MAJOR_VERSION >= 3 + wrapped_str = PyUnicode_InternFromString("__wrapped__"); +#else + wrapped_str = PyString_InternFromString("__wrapped__"); +#endif + } + + if (PyObject_HasAttr((PyObject *)Py_TYPE(self), name)) + return PyObject_GenericSetAttr((PyObject *)self, name, value); + + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return -1; + } + + return PyObject_SetAttr(self->wrapped, name, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_richcompare(WraptObjectProxyObject *self, + PyObject *other, int opcode) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_RichCompare(self->wrapped, other, opcode); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptObjectProxy_iter(WraptObjectProxyObject *self) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_GetIter(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyNumberMethods WraptObjectProxy_as_number = { + (binaryfunc)WraptObjectProxy_add, /*nb_add*/ + (binaryfunc)WraptObjectProxy_subtract, /*nb_subtract*/ + (binaryfunc)WraptObjectProxy_multiply, /*nb_multiply*/ +#if PY_MAJOR_VERSION < 3 + (binaryfunc)WraptObjectProxy_divide, /*nb_divide*/ +#endif + (binaryfunc)WraptObjectProxy_remainder, /*nb_remainder*/ + (binaryfunc)WraptObjectProxy_divmod, /*nb_divmod*/ + (ternaryfunc)WraptObjectProxy_power, /*nb_power*/ + (unaryfunc)WraptObjectProxy_negative, /*nb_negative*/ + (unaryfunc)WraptObjectProxy_positive, /*nb_positive*/ + (unaryfunc)WraptObjectProxy_absolute, /*nb_absolute*/ + (inquiry)WraptObjectProxy_bool, /*nb_nonzero/nb_bool*/ + (unaryfunc)WraptObjectProxy_invert, /*nb_invert*/ + (binaryfunc)WraptObjectProxy_lshift, /*nb_lshift*/ + (binaryfunc)WraptObjectProxy_rshift, /*nb_rshift*/ + (binaryfunc)WraptObjectProxy_and, /*nb_and*/ + (binaryfunc)WraptObjectProxy_xor, /*nb_xor*/ + (binaryfunc)WraptObjectProxy_or, /*nb_or*/ +#if PY_MAJOR_VERSION < 3 + 0, /*nb_coerce*/ +#endif +#if PY_MAJOR_VERSION < 3 + (unaryfunc)WraptObjectProxy_int, /*nb_int*/ + (unaryfunc)WraptObjectProxy_long, /*nb_long*/ +#else + (unaryfunc)WraptObjectProxy_long, /*nb_int*/ + 0, /*nb_long/nb_reserved*/ +#endif + (unaryfunc)WraptObjectProxy_float, /*nb_float*/ +#if PY_MAJOR_VERSION < 3 + (unaryfunc)WraptObjectProxy_oct, /*nb_oct*/ + (unaryfunc)WraptObjectProxy_hex, /*nb_hex*/ +#endif + (binaryfunc)WraptObjectProxy_inplace_add, /*nb_inplace_add*/ + (binaryfunc)WraptObjectProxy_inplace_subtract, /*nb_inplace_subtract*/ + (binaryfunc)WraptObjectProxy_inplace_multiply, /*nb_inplace_multiply*/ +#if PY_MAJOR_VERSION < 3 + (binaryfunc)WraptObjectProxy_inplace_divide, /*nb_inplace_divide*/ +#endif + (binaryfunc)WraptObjectProxy_inplace_remainder, /*nb_inplace_remainder*/ + (ternaryfunc)WraptObjectProxy_inplace_power, /*nb_inplace_power*/ + (binaryfunc)WraptObjectProxy_inplace_lshift, /*nb_inplace_lshift*/ + (binaryfunc)WraptObjectProxy_inplace_rshift, /*nb_inplace_rshift*/ + (binaryfunc)WraptObjectProxy_inplace_and, /*nb_inplace_and*/ + (binaryfunc)WraptObjectProxy_inplace_xor, /*nb_inplace_xor*/ + (binaryfunc)WraptObjectProxy_inplace_or, /*nb_inplace_or*/ + (binaryfunc)WraptObjectProxy_floor_divide, /*nb_floor_divide*/ + (binaryfunc)WraptObjectProxy_true_divide, /*nb_true_divide*/ + (binaryfunc)WraptObjectProxy_inplace_floor_divide, /*nb_inplace_floor_divide*/ + (binaryfunc)WraptObjectProxy_inplace_true_divide, /*nb_inplace_true_divide*/ + (unaryfunc)WraptObjectProxy_index, /*nb_index*/ +}; + +static PySequenceMethods WraptObjectProxy_as_sequence = { + (lenfunc)WraptObjectProxy_length, /*sq_length*/ + 0, /*sq_concat*/ + 0, /*sq_repeat*/ + 0, /*sq_item*/ + 0, /*sq_slice*/ + 0, /*sq_ass_item*/ + 0, /*sq_ass_slice*/ + (objobjproc)WraptObjectProxy_contains, /* sq_contains */ +}; + +static PyMappingMethods WraptObjectProxy_as_mapping = { + (lenfunc)WraptObjectProxy_length, /*mp_length*/ + (binaryfunc)WraptObjectProxy_getitem, /*mp_subscript*/ + (objobjargproc)WraptObjectProxy_setitem, /*mp_ass_subscript*/ +}; + +static PyMethodDef WraptObjectProxy_methods[] = { + { "__dir__", (PyCFunction)WraptObjectProxy_dir, METH_NOARGS, 0 }, + { "__enter__", (PyCFunction)WraptObjectProxy_enter, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__exit__", (PyCFunction)WraptObjectProxy_exit, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__copy__", (PyCFunction)WraptObjectProxy_copy, + METH_NOARGS, 0 }, + { "__deepcopy__", (PyCFunction)WraptObjectProxy_deepcopy, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__reduce__", (PyCFunction)WraptObjectProxy_reduce, + METH_NOARGS, 0 }, + { "__reduce_ex__", (PyCFunction)WraptObjectProxy_reduce_ex, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__getattr__", (PyCFunction)WraptObjectProxy_getattr, + METH_VARARGS , 0 }, + { "__bytes__", (PyCFunction)WraptObjectProxy_bytes, METH_NOARGS, 0 }, + { "__reversed__", (PyCFunction)WraptObjectProxy_reversed, METH_NOARGS, 0 }, +#if PY_MAJOR_VERSION >= 3 + { "__round__", (PyCFunction)WraptObjectProxy_round, METH_NOARGS, 0 }, +#endif + { "__complex__", (PyCFunction)WraptObjectProxy_complex, METH_NOARGS, 0 }, +#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) + { "__mro_entries__", (PyCFunction)WraptObjectProxy_mro_entries, + METH_VARARGS | METH_KEYWORDS, 0 }, +#endif + { NULL, NULL }, +}; + +static PyGetSetDef WraptObjectProxy_getset[] = { + { "__name__", (getter)WraptObjectProxy_get_name, + (setter)WraptObjectProxy_set_name, 0 }, + { "__qualname__", (getter)WraptObjectProxy_get_qualname, + (setter)WraptObjectProxy_set_qualname, 0 }, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { "__class__", (getter)WraptObjectProxy_get_class, + NULL, 0 }, + { "__annotations__", (getter)WraptObjectProxy_get_annotations, + (setter)WraptObjectProxy_set_annotations, 0 }, + { "__wrapped__", (getter)WraptObjectProxy_get_wrapped, + (setter)WraptObjectProxy_set_wrapped, 0 }, + { NULL }, +}; + +PyTypeObject WraptObjectProxy_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "ObjectProxy", /*tp_name*/ + sizeof(WraptObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)WraptObjectProxy_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (unaryfunc)WraptObjectProxy_repr, /*tp_repr*/ + &WraptObjectProxy_as_number, /*tp_as_number*/ + &WraptObjectProxy_as_sequence, /*tp_as_sequence*/ + &WraptObjectProxy_as_mapping, /*tp_as_mapping*/ + (hashfunc)WraptObjectProxy_hash, /*tp_hash*/ + 0, /*tp_call*/ + (unaryfunc)WraptObjectProxy_str, /*tp_str*/ + (getattrofunc)WraptObjectProxy_getattro, /*tp_getattro*/ + (setattrofunc)WraptObjectProxy_setattro, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptObjectProxy_traverse, /*tp_traverse*/ + (inquiry)WraptObjectProxy_clear, /*tp_clear*/ + (richcmpfunc)WraptObjectProxy_richcompare, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + (getiterfunc)WraptObjectProxy_iter, /*tp_iter*/ + 0, /*tp_iternext*/ + WraptObjectProxy_methods, /*tp_methods*/ + 0, /*tp_members*/ + WraptObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + offsetof(WraptObjectProxyObject, dict), /*tp_dictoffset*/ + (initproc)WraptObjectProxy_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + WraptObjectProxy_new, /*tp_new*/ + PyObject_GC_Del, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptCallableObjectProxy_call( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return PyObject_Call(self->wrapped, args, kwds); +} + +/* ------------------------------------------------------------------------- */; + +static PyGetSetDef WraptCallableObjectProxy_getset[] = { + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, +}; + +PyTypeObject WraptCallableObjectProxy_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "CallableObjectProxy", /*tp_name*/ + sizeof(WraptObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptCallableObjectProxy_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptCallableObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptObjectProxy_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptPartialCallableObjectProxy_new(PyTypeObject *type, + PyObject *args, PyObject *kwds) +{ + WraptPartialCallableObjectProxyObject *self; + + self = (WraptPartialCallableObjectProxyObject *)WraptObjectProxy_new(type, + args, kwds); + + if (!self) + return NULL; + + self->args = NULL; + self->kwargs = NULL; + + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptPartialCallableObjectProxy_raw_init( + WraptPartialCallableObjectProxyObject *self, + PyObject *wrapped, PyObject *args, PyObject *kwargs) +{ + int result = 0; + + result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, + wrapped); + + if (result == 0) { + Py_INCREF(args); + Py_XDECREF(self->args); + self->args = args; + + Py_XINCREF(kwargs); + Py_XDECREF(self->kwargs); + self->kwargs = kwargs; + } + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptPartialCallableObjectProxy_init( + WraptPartialCallableObjectProxyObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *wrapped = NULL; + PyObject *fnargs = NULL; + + int result = 0; + + if (!PyObject_Length(args)) { + PyErr_SetString(PyExc_TypeError, + "__init__ of partial needs an argument"); + return -1; + } + + if (PyObject_Length(args) < 1) { + PyErr_SetString(PyExc_TypeError, + "partial type takes at least one argument"); + return -1; + } + + wrapped = PyTuple_GetItem(args, 0); + + if (!PyCallable_Check(wrapped)) { + PyErr_SetString(PyExc_TypeError, + "the first argument must be callable"); + return -1; + } + + fnargs = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); + + if (!fnargs) + return -1; + + result = WraptPartialCallableObjectProxy_raw_init(self, wrapped, + fnargs, kwds); + + Py_DECREF(fnargs); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptPartialCallableObjectProxy_traverse( + WraptPartialCallableObjectProxyObject *self, + visitproc visit, void *arg) +{ + WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); + + Py_VISIT(self->args); + Py_VISIT(self->kwargs); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptPartialCallableObjectProxy_clear( + WraptPartialCallableObjectProxyObject *self) +{ + WraptObjectProxy_clear((WraptObjectProxyObject *)self); + + Py_CLEAR(self->args); + Py_CLEAR(self->kwargs); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static void WraptPartialCallableObjectProxy_dealloc( + WraptPartialCallableObjectProxyObject *self) +{ + PyObject_GC_UnTrack(self); + + WraptPartialCallableObjectProxy_clear(self); + + WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptPartialCallableObjectProxy_call( + WraptPartialCallableObjectProxyObject *self, PyObject *args, + PyObject *kwds) +{ + PyObject *fnargs = NULL; + PyObject *fnkwargs = NULL; + + PyObject *result = NULL; + + long i; + long offset; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + fnargs = PyTuple_New(PyTuple_Size(self->args)+PyTuple_Size(args)); + + for (i=0; iargs); i++) { + PyObject *item; + item = PyTuple_GetItem(self->args, i); + Py_INCREF(item); + PyTuple_SetItem(fnargs, i, item); + } + + offset = PyTuple_Size(self->args); + + for (i=0; ikwargs && PyDict_Update(fnkwargs, self->kwargs) == -1) { + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); + return NULL; + } + + if (kwds && PyDict_Update(fnkwargs, kwds) == -1) { + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); + return NULL; + } + + result = PyObject_Call(self->object_proxy.wrapped, + fnargs, fnkwargs); + + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); + + return result; +} + +/* ------------------------------------------------------------------------- */; + +static PyGetSetDef WraptPartialCallableObjectProxy_getset[] = { + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, +}; + +PyTypeObject WraptPartialCallableObjectProxy_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "PartialCallableObjectProxy", /*tp_name*/ + sizeof(WraptPartialCallableObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)WraptPartialCallableObjectProxy_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptPartialCallableObjectProxy_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptPartialCallableObjectProxy_traverse, /*tp_traverse*/ + (inquiry)WraptPartialCallableObjectProxy_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptPartialCallableObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptPartialCallableObjectProxy_init, /*tp_init*/ + 0, /*tp_alloc*/ + WraptPartialCallableObjectProxy_new, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_new(PyTypeObject *type, + PyObject *args, PyObject *kwds) +{ + WraptFunctionWrapperObject *self; + + self = (WraptFunctionWrapperObject *)WraptObjectProxy_new(type, + args, kwds); + + if (!self) + return NULL; + + self->instance = NULL; + self->wrapper = NULL; + self->enabled = NULL; + self->binding = NULL; + self->parent = NULL; + + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptFunctionWrapperBase_raw_init(WraptFunctionWrapperObject *self, + PyObject *wrapped, PyObject *instance, PyObject *wrapper, + PyObject *enabled, PyObject *binding, PyObject *parent) +{ + int result = 0; + + result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, + wrapped); + + if (result == 0) { + Py_INCREF(instance); + Py_XDECREF(self->instance); + self->instance = instance; + + Py_INCREF(wrapper); + Py_XDECREF(self->wrapper); + self->wrapper = wrapper; + + Py_INCREF(enabled); + Py_XDECREF(self->enabled); + self->enabled = enabled; + + Py_INCREF(binding); + Py_XDECREF(self->binding); + self->binding = binding; + + Py_INCREF(parent); + Py_XDECREF(self->parent); + self->parent = parent; + } + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptFunctionWrapperBase_init(WraptFunctionWrapperObject *self, + PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + PyObject *instance = NULL; + PyObject *wrapper = NULL; + PyObject *enabled = Py_None; + PyObject *binding = NULL; + PyObject *parent = Py_None; + + static PyObject *function_str = NULL; + + static char *kwlist[] = { "wrapped", "instance", "wrapper", + "enabled", "binding", "parent", NULL }; + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "OOO|OOO:FunctionWrapperBase", kwlist, &wrapped, &instance, + &wrapper, &enabled, &binding, &parent)) { + return -1; + } + + if (!binding) + binding = function_str; + + return WraptFunctionWrapperBase_raw_init(self, wrapped, instance, wrapper, + enabled, binding, parent); +} + +/* ------------------------------------------------------------------------- */ + +static int WraptFunctionWrapperBase_traverse(WraptFunctionWrapperObject *self, + visitproc visit, void *arg) +{ + WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); + + Py_VISIT(self->instance); + Py_VISIT(self->wrapper); + Py_VISIT(self->enabled); + Py_VISIT(self->binding); + Py_VISIT(self->parent); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int WraptFunctionWrapperBase_clear(WraptFunctionWrapperObject *self) +{ + WraptObjectProxy_clear((WraptObjectProxyObject *)self); + + Py_CLEAR(self->instance); + Py_CLEAR(self->wrapper); + Py_CLEAR(self->enabled); + Py_CLEAR(self->binding); + Py_CLEAR(self->parent); + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static void WraptFunctionWrapperBase_dealloc(WraptFunctionWrapperObject *self) +{ + PyObject_GC_UnTrack(self); + + WraptFunctionWrapperBase_clear(self); + + WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_call( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *param_kwds = NULL; + + PyObject *result = NULL; + + static PyObject *function_str = NULL; + static PyObject *classmethod_str = NULL; + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); + classmethod_str = PyUnicode_InternFromString("classmethod"); +#else + function_str = PyString_InternFromString("function"); + classmethod_str = PyString_InternFromString("classmethod"); +#endif + } + + if (self->enabled != Py_None) { + if (PyCallable_Check(self->enabled)) { + PyObject *object = NULL; + + object = PyObject_CallFunctionObjArgs(self->enabled, NULL); + + if (!object) + return NULL; + + if (PyObject_Not(object)) { + Py_DECREF(object); + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + + Py_DECREF(object); + } + else if (PyObject_Not(self->enabled)) { + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + if ((self->instance == Py_None) && (self->binding == function_str || + PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1 || self->binding == classmethod_str || + PyObject_RichCompareBool(self->binding, classmethod_str, + Py_EQ) == 1)) { + + PyObject *instance = NULL; + + instance = PyObject_GetAttrString(self->object_proxy.wrapped, + "__self__"); + + if (instance) { + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, instance, args, kwds, NULL); + + Py_XDECREF(param_kwds); + + Py_DECREF(instance); + + return result; + } + else + PyErr_Clear(); + } + + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, self->instance, args, kwds, NULL); + + Py_XDECREF(param_kwds); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_descr_get( + WraptFunctionWrapperObject *self, PyObject *obj, PyObject *type) +{ + PyObject *bound_type = NULL; + PyObject *descriptor = NULL; + PyObject *result = NULL; + + static PyObject *bound_type_str = NULL; + static PyObject *function_str = NULL; + + if (!bound_type_str) { +#if PY_MAJOR_VERSION >= 3 + bound_type_str = PyUnicode_InternFromString( + "__bound_function_wrapper__"); +#else + bound_type_str = PyString_InternFromString( + "__bound_function_wrapper__"); +#endif + } + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + if (self->parent == Py_None) { +#if PY_MAJOR_VERSION < 3 + if (PyObject_IsInstance(self->object_proxy.wrapped, + (PyObject *)&PyClass_Type) || PyObject_IsInstance( + self->object_proxy.wrapped, (PyObject *)&PyType_Type)) { + Py_INCREF(self); + return (PyObject *)self; + } +#else + if (PyObject_IsInstance(self->object_proxy.wrapped, + (PyObject *)&PyType_Type)) { + Py_INCREF(self); + return (PyObject *)self; + } +#endif + + if (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get == NULL) { + PyErr_Format(PyExc_AttributeError, + "'%s' object has no attribute '__get__'", + Py_TYPE(self->object_proxy.wrapped)->tp_name); + return NULL; + } + + descriptor = (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get)( + self->object_proxy.wrapped, obj, type); + + if (!descriptor) + return NULL; + + if (Py_TYPE(self) != &WraptFunctionWrapper_Type) { + bound_type = PyObject_GenericGetAttr((PyObject *)self, + bound_type_str); + + if (!bound_type) + PyErr_Clear(); + } + + if (obj == NULL) + obj = Py_None; + + result = PyObject_CallFunctionObjArgs(bound_type ? bound_type : + (PyObject *)&WraptBoundFunctionWrapper_Type, descriptor, + obj, self->wrapper, self->enabled, self->binding, + self, NULL); + + Py_XDECREF(bound_type); + Py_DECREF(descriptor); + + return result; + } + + if (self->instance == Py_None && (self->binding == function_str || + PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1)) { + + PyObject *wrapped = NULL; + + static PyObject *wrapped_str = NULL; + + if (!wrapped_str) { +#if PY_MAJOR_VERSION >= 3 + wrapped_str = PyUnicode_InternFromString("__wrapped__"); +#else + wrapped_str = PyString_InternFromString("__wrapped__"); +#endif + } + + wrapped = PyObject_GetAttr(self->parent, wrapped_str); + + if (!wrapped) + return NULL; + + if (Py_TYPE(wrapped)->tp_descr_get == NULL) { + PyErr_Format(PyExc_AttributeError, + "'%s' object has no attribute '__get__'", + Py_TYPE(wrapped)->tp_name); + Py_DECREF(wrapped); + return NULL; + } + + descriptor = (Py_TYPE(wrapped)->tp_descr_get)(wrapped, obj, type); + + Py_DECREF(wrapped); + + if (!descriptor) + return NULL; + + if (Py_TYPE(self->parent) != &WraptFunctionWrapper_Type) { + bound_type = PyObject_GenericGetAttr((PyObject *)self->parent, + bound_type_str); + + if (!bound_type) + PyErr_Clear(); + } + + if (obj == NULL) + obj = Py_None; + + result = PyObject_CallFunctionObjArgs(bound_type ? bound_type : + (PyObject *)&WraptBoundFunctionWrapper_Type, descriptor, + obj, self->wrapper, self->enabled, self->binding, + self->parent, NULL); + + Py_XDECREF(bound_type); + Py_DECREF(descriptor); + + return result; + } + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_set_name( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + method = PyObject_GetAttrString(self->object_proxy.wrapped, + "__set_name__"); + + if (!method) { + PyErr_Clear(); + Py_INCREF(Py_None); + return Py_None; + } + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_instancecheck( + WraptFunctionWrapperObject *self, PyObject *instance) +{ + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + check = PyObject_IsInstance(instance, self->object_proxy.wrapped); + + if (check < 0) { + return NULL; + } + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_subclasscheck( + WraptFunctionWrapperObject *self, PyObject *args) +{ + PyObject *subclass = NULL; + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &subclass)) + return NULL; + + object = PyObject_GetAttrString(subclass, "__wrapped__"); + + if (!object) + PyErr_Clear(); + + check = PyObject_IsSubclass(object ? object: subclass, + self->object_proxy.wrapped); + + Py_XDECREF(object); + + if (check == -1) + return NULL; + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_get_self_instance( + WraptFunctionWrapperObject *self, void *closure) +{ + if (!self->instance) { + Py_INCREF(Py_None); + return Py_None; + } + + Py_INCREF(self->instance); + return self->instance; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_get_self_wrapper( + WraptFunctionWrapperObject *self, void *closure) +{ + if (!self->wrapper) { + Py_INCREF(Py_None); + return Py_None; + } + + Py_INCREF(self->wrapper); + return self->wrapper; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_get_self_enabled( + WraptFunctionWrapperObject *self, void *closure) +{ + if (!self->enabled) { + Py_INCREF(Py_None); + return Py_None; + } + + Py_INCREF(self->enabled); + return self->enabled; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_get_self_binding( + WraptFunctionWrapperObject *self, void *closure) +{ + if (!self->binding) { + Py_INCREF(Py_None); + return Py_None; + } + + Py_INCREF(self->binding); + return self->binding; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_get_self_parent( + WraptFunctionWrapperObject *self, void *closure) +{ + if (!self->parent) { + Py_INCREF(Py_None); + return Py_None; + } + + Py_INCREF(self->parent); + return self->parent; +} + +/* ------------------------------------------------------------------------- */; + +static PyMethodDef WraptFunctionWrapperBase_methods[] = { + { "__set_name__", (PyCFunction)WraptFunctionWrapperBase_set_name, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__instancecheck__", (PyCFunction)WraptFunctionWrapperBase_instancecheck, + METH_O, 0}, + { "__subclasscheck__", (PyCFunction)WraptFunctionWrapperBase_subclasscheck, + METH_VARARGS, 0 }, + { NULL, NULL }, +}; + +/* ------------------------------------------------------------------------- */; + +static PyGetSetDef WraptFunctionWrapperBase_getset[] = { + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { "_self_instance", (getter)WraptFunctionWrapperBase_get_self_instance, + NULL, 0 }, + { "_self_wrapper", (getter)WraptFunctionWrapperBase_get_self_wrapper, + NULL, 0 }, + { "_self_enabled", (getter)WraptFunctionWrapperBase_get_self_enabled, + NULL, 0 }, + { "_self_binding", (getter)WraptFunctionWrapperBase_get_self_binding, + NULL, 0 }, + { "_self_parent", (getter)WraptFunctionWrapperBase_get_self_parent, + NULL, 0 }, + { NULL }, +}; + +PyTypeObject WraptFunctionWrapperBase_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_FunctionWrapperBase", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)WraptFunctionWrapperBase_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptFunctionWrapperBase_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptFunctionWrapperBase_traverse, /*tp_traverse*/ + (inquiry)WraptFunctionWrapperBase_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + WraptFunctionWrapperBase_methods, /*tp_methods*/ + 0, /*tp_members*/ + WraptFunctionWrapperBase_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + (descrgetfunc)WraptFunctionWrapperBase_descr_get, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptFunctionWrapperBase_init, /*tp_init*/ + 0, /*tp_alloc*/ + WraptFunctionWrapperBase_new, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptBoundFunctionWrapper_call( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *param_args = NULL; + PyObject *param_kwds = NULL; + + PyObject *wrapped = NULL; + PyObject *instance = NULL; + + PyObject *result = NULL; + + static PyObject *function_str = NULL; + + if (self->enabled != Py_None) { + if (PyCallable_Check(self->enabled)) { + PyObject *object = NULL; + + object = PyObject_CallFunctionObjArgs(self->enabled, NULL); + + if (!object) + return NULL; + + if (PyObject_Not(object)) { + Py_DECREF(object); + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + + Py_DECREF(object); + } + else if (PyObject_Not(self->enabled)) { + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + } + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + /* + * We need to do things different depending on whether we are likely + * wrapping an instance method vs a static method or class method. + */ + + if (self->binding == function_str || PyObject_RichCompareBool( + self->binding, function_str, Py_EQ) == 1) { + + if (self->instance == Py_None) { + /* + * This situation can occur where someone is calling the + * instancemethod via the class type and passing the + * instance as the first argument. We need to shift the args + * before making the call to the wrapper and effectively + * bind the instance to the wrapped function using a partial + * so the wrapper doesn't see anything as being different. + */ + + if (PyTuple_Size(args) == 0) { + PyErr_SetString(PyExc_TypeError, + "missing 1 required positional argument"); + return NULL; + } + + instance = PyTuple_GetItem(args, 0); + + if (!instance) + return NULL; + + wrapped = PyObject_CallFunctionObjArgs( + (PyObject *)&WraptPartialCallableObjectProxy_Type, + self->object_proxy.wrapped, instance, NULL); + + if (!wrapped) + return NULL; + + param_args = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); + + if (!param_args) { + Py_DECREF(wrapped); + return NULL; + } + + args = param_args; + } + else + instance = self->instance; + + if (!wrapped) { + Py_INCREF(self->object_proxy.wrapped); + wrapped = self->object_proxy.wrapped; + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + result = PyObject_CallFunctionObjArgs(self->wrapper, wrapped, + instance, args, kwds, NULL); + + Py_XDECREF(param_args); + Py_XDECREF(param_kwds); + Py_DECREF(wrapped); + + return result; + } + else { + /* + * As in this case we would be dealing with a classmethod or + * staticmethod, then _self_instance will only tell us whether + * when calling the classmethod or staticmethod they did it via + * an instance of the class it is bound to and not the case + * where done by the class type itself. We thus ignore + * _self_instance and use the __self__ attribute of the bound + * function instead. For a classmethod, this means instance will + * be the class type and for a staticmethod it will be None. + * This is probably the more useful thing we can pass through + * even though we loose knowledge of whether they were called on + * the instance vs the class type, as it reflects what they have + * available in the decoratored function. + */ + + instance = PyObject_GetAttrString(self->object_proxy.wrapped, + "__self__"); + + if (!instance) { + PyErr_Clear(); + Py_INCREF(Py_None); + instance = Py_None; + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, instance, args, kwds, NULL); + + Py_XDECREF(param_kwds); + + Py_DECREF(instance); + + return result; + } +} + +/* ------------------------------------------------------------------------- */ + +static PyGetSetDef WraptBoundFunctionWrapper_getset[] = { + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, +}; + +PyTypeObject WraptBoundFunctionWrapper_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "BoundFunctionWrapper", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptBoundFunctionWrapper_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptBoundFunctionWrapper_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static int WraptFunctionWrapper_init(WraptFunctionWrapperObject *self, + PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + PyObject *wrapper = NULL; + PyObject *enabled = Py_None; + PyObject *binding = NULL; + PyObject *instance = NULL; + + static PyObject *classmethod_str = NULL; + static PyObject *staticmethod_str = NULL; + static PyObject *function_str = NULL; + + int result = 0; + + static char *kwlist[] = { "wrapped", "wrapper", "enabled", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O:FunctionWrapper", + kwlist, &wrapped, &wrapper, &enabled)) { + return -1; + } + + if (!classmethod_str) { +#if PY_MAJOR_VERSION >= 3 + classmethod_str = PyUnicode_InternFromString("classmethod"); +#else + classmethod_str = PyString_InternFromString("classmethod"); +#endif + } + + if (!staticmethod_str) { +#if PY_MAJOR_VERSION >= 3 + staticmethod_str = PyUnicode_InternFromString("staticmethod"); +#else + staticmethod_str = PyString_InternFromString("staticmethod"); +#endif + } + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + if (PyObject_IsInstance(wrapped, (PyObject *)&PyClassMethod_Type)) { + binding = classmethod_str; + } + else if (PyObject_IsInstance(wrapped, (PyObject *)&PyStaticMethod_Type)) { + binding = staticmethod_str; + } + else if ((instance = PyObject_GetAttrString(wrapped, "__self__")) != 0) { +#if PY_MAJOR_VERSION < 3 + if (PyObject_IsInstance(instance, (PyObject *)&PyClass_Type) || + PyObject_IsInstance(instance, (PyObject *)&PyType_Type)) { + binding = classmethod_str; + } +#else + if (PyObject_IsInstance(instance, (PyObject *)&PyType_Type)) { + binding = classmethod_str; + } +#endif + else + binding = function_str; + + Py_DECREF(instance); + } + else { + PyErr_Clear(); + + binding = function_str; + } + + result = WraptFunctionWrapperBase_raw_init(self, wrapped, Py_None, + wrapper, enabled, binding, Py_None); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyGetSetDef WraptFunctionWrapper_getset[] = { + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, +}; + +PyTypeObject WraptFunctionWrapper_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "FunctionWrapper", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptFunctionWrapper_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptFunctionWrapper_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "_wrappers", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + NULL, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + +static PyObject * +moduleinit(void) +{ + PyObject *module; + +#if PY_MAJOR_VERSION >= 3 + module = PyModule_Create(&moduledef); +#else + module = Py_InitModule3("_wrappers", NULL, NULL); +#endif + + if (module == NULL) + return NULL; + + if (PyType_Ready(&WraptObjectProxy_Type) < 0) + return NULL; + + /* Ensure that inheritance relationships specified. */ + + WraptCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; + WraptPartialCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; + WraptFunctionWrapperBase_Type.tp_base = &WraptObjectProxy_Type; + WraptBoundFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; + WraptFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; + + if (PyType_Ready(&WraptCallableObjectProxy_Type) < 0) + return NULL; + if (PyType_Ready(&WraptPartialCallableObjectProxy_Type) < 0) + return NULL; + if (PyType_Ready(&WraptFunctionWrapperBase_Type) < 0) + return NULL; + if (PyType_Ready(&WraptBoundFunctionWrapper_Type) < 0) + return NULL; + if (PyType_Ready(&WraptFunctionWrapper_Type) < 0) + return NULL; + + Py_INCREF(&WraptObjectProxy_Type); + PyModule_AddObject(module, "ObjectProxy", + (PyObject *)&WraptObjectProxy_Type); + Py_INCREF(&WraptCallableObjectProxy_Type); + PyModule_AddObject(module, "CallableObjectProxy", + (PyObject *)&WraptCallableObjectProxy_Type); + PyModule_AddObject(module, "PartialCallableObjectProxy", + (PyObject *)&WraptPartialCallableObjectProxy_Type); + Py_INCREF(&WraptFunctionWrapper_Type); + PyModule_AddObject(module, "FunctionWrapper", + (PyObject *)&WraptFunctionWrapper_Type); + + Py_INCREF(&WraptFunctionWrapperBase_Type); + PyModule_AddObject(module, "_FunctionWrapperBase", + (PyObject *)&WraptFunctionWrapperBase_Type); + Py_INCREF(&WraptBoundFunctionWrapper_Type); + PyModule_AddObject(module, "BoundFunctionWrapper", + (PyObject *)&WraptBoundFunctionWrapper_Type); + + return module; +} + +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC init_wrappers(void) +{ + moduleinit(); +} +#else +PyMODINIT_FUNC PyInit__wrappers(void) +{ + return moduleinit(); +} +#endif + +/* ------------------------------------------------------------------------- */ diff --git a/ddtrace/vendor/wrapt/arguments.py b/ddtrace/vendor/wrapt/arguments.py new file mode 100644 index 00000000000..032bc059e00 --- /dev/null +++ b/ddtrace/vendor/wrapt/arguments.py @@ -0,0 +1,38 @@ +# The inspect.formatargspec() function was dropped in Python 3.11 but we need +# need it for when constructing signature changing decorators based on result of +# inspect.getargspec() or inspect.getfullargspec(). The code here implements +# inspect.formatargspec() base on Parameter and Signature from inspect module, +# which were added in Python 3.6. Thanks to Cyril Jouve for the implementation. + +try: + from inspect import Parameter, Signature +except ImportError: + from inspect import formatargspec +else: + def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): + if kwonlydefaults is None: + kwonlydefaults = {} + ndefaults = len(defaults) if defaults else 0 + parameters = [ + Parameter( + arg, + Parameter.POSITIONAL_OR_KEYWORD, + default=defaults[i] if i >= 0 else Parameter.empty, + annotation=annotations.get(arg, Parameter.empty), + ) for i, arg in enumerate(args, ndefaults - len(args)) + ] + if varargs: + parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL)) + parameters.extend( + Parameter( + kwonlyarg, + Parameter.KEYWORD_ONLY, + default=kwonlydefaults.get(kwonlyarg, Parameter.empty), + annotation=annotations.get(kwonlyarg, Parameter.empty), + ) for kwonlyarg in kwonlyargs + ) + if varkw: + parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD)) + return_annotation = annotations.get('return', Signature.empty) + return str(Signature(parameters, return_annotation=return_annotation)) \ No newline at end of file diff --git a/ddtrace/vendor/wrapt/decorators.py b/ddtrace/vendor/wrapt/decorators.py new file mode 100644 index 00000000000..c3f2547295a --- /dev/null +++ b/ddtrace/vendor/wrapt/decorators.py @@ -0,0 +1,541 @@ +"""This module implements decorators for implementing other decorators +as well as some commonly used decorators. + +""" + +import sys + +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = basestring, + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + +else: + string_types = str, + + import builtins + + exec_ = getattr(builtins, "exec") + del builtins + +from functools import partial +from inspect import isclass +from threading import Lock, RLock + +from .arguments import formatargspec + +try: + from inspect import signature +except ImportError: + pass + +from .wrappers import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy, + CallableObjectProxy) + +# Adapter wrapper for the wrapped function which will overlay certain +# properties from the adapter function onto the wrapped function so that +# functions such as inspect.getargspec(), inspect.getfullargspec(), +# inspect.signature() and inspect.getsource() return the correct results +# one would expect. + +class _AdapterFunctionCode(CallableObjectProxy): + + def __init__(self, wrapped_code, adapter_code): + super(_AdapterFunctionCode, self).__init__(wrapped_code) + self._self_adapter_code = adapter_code + + @property + def co_argcount(self): + return self._self_adapter_code.co_argcount + + @property + def co_code(self): + return self._self_adapter_code.co_code + + @property + def co_flags(self): + return self._self_adapter_code.co_flags + + @property + def co_kwonlyargcount(self): + return self._self_adapter_code.co_kwonlyargcount + + @property + def co_varnames(self): + return self._self_adapter_code.co_varnames + +class _AdapterFunctionSurrogate(CallableObjectProxy): + + def __init__(self, wrapped, adapter): + super(_AdapterFunctionSurrogate, self).__init__(wrapped) + self._self_adapter = adapter + + @property + def __code__(self): + return _AdapterFunctionCode(self.__wrapped__.__code__, + self._self_adapter.__code__) + + @property + def __defaults__(self): + return self._self_adapter.__defaults__ + + @property + def __kwdefaults__(self): + return self._self_adapter.__kwdefaults__ + + @property + def __signature__(self): + if 'signature' not in globals(): + return self._self_adapter.__signature__ + else: + return signature(self._self_adapter) + + if PY2: + func_code = __code__ + func_defaults = __defaults__ + +class _BoundAdapterWrapper(BoundFunctionWrapper): + + @property + def __func__(self): + return _AdapterFunctionSurrogate(self.__wrapped__.__func__, + self._self_parent._self_adapter) + + @property + def __signature__(self): + if 'signature' not in globals(): + return self.__wrapped__.__signature__ + else: + return signature(self._self_parent._self_adapter) + + if PY2: + im_func = __func__ + +class AdapterWrapper(FunctionWrapper): + + __bound_function_wrapper__ = _BoundAdapterWrapper + + def __init__(self, *args, **kwargs): + adapter = kwargs.pop('adapter') + super(AdapterWrapper, self).__init__(*args, **kwargs) + self._self_surrogate = _AdapterFunctionSurrogate( + self.__wrapped__, adapter) + self._self_adapter = adapter + + @property + def __code__(self): + return self._self_surrogate.__code__ + + @property + def __defaults__(self): + return self._self_surrogate.__defaults__ + + @property + def __kwdefaults__(self): + return self._self_surrogate.__kwdefaults__ + + if PY2: + func_code = __code__ + func_defaults = __defaults__ + + @property + def __signature__(self): + return self._self_surrogate.__signature__ + +class AdapterFactory(object): + def __call__(self, wrapped): + raise NotImplementedError() + +class DelegatedAdapterFactory(AdapterFactory): + def __init__(self, factory): + super(DelegatedAdapterFactory, self).__init__() + self.factory = factory + def __call__(self, wrapped): + return self.factory(wrapped) + +adapter_factory = DelegatedAdapterFactory + +# Decorator for creating other decorators. This decorator and the +# wrappers which they use are designed to properly preserve any name +# attributes, function signatures etc, in addition to the wrappers +# themselves acting like a transparent proxy for the original wrapped +# function so the wrapper is effectively indistinguishable from the +# original wrapped function. + +def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper): + # The decorator should be supplied with a single positional argument + # which is the wrapper function to be used to implement the + # decorator. This may be preceded by a step whereby the keyword + # arguments are supplied to customise the behaviour of the + # decorator. The 'adapter' argument is used to optionally denote a + # separate function which is notionally used by an adapter + # decorator. In that case parts of the function '__code__' and + # '__defaults__' attributes are used from the adapter function + # rather than those of the wrapped function. This allows for the + # argument specification from inspect.getfullargspec() and similar + # functions to be overridden with a prototype for a different + # function than what was wrapped. The 'enabled' argument provides a + # way to enable/disable the use of the decorator. If the type of + # 'enabled' is a boolean, then it is evaluated immediately and the + # wrapper not even applied if it is False. If not a boolean, it will + # be evaluated when the wrapper is called for an unbound wrapper, + # and when binding occurs for a bound wrapper. When being evaluated, + # if 'enabled' is callable it will be called to obtain the value to + # be checked. If False, the wrapper will not be called and instead + # the original wrapped function will be called directly instead. + # The 'proxy' argument provides a way of passing a custom version of + # the FunctionWrapper class used in decorating the function. + + if wrapper is not None: + # Helper function for creating wrapper of the appropriate + # time when we need it down below. + + def _build(wrapped, wrapper, enabled=None, adapter=None): + if adapter: + if isinstance(adapter, AdapterFactory): + adapter = adapter(wrapped) + + if not callable(adapter): + ns = {} + + # Check if the signature argument specification has + # annotations. If it does then we need to remember + # it but also drop it when attempting to manufacture + # a standin adapter function. This is necessary else + # it will try and look up any types referenced in + # the annotations in the empty namespace we use, + # which will fail. + + annotations = {} + + if not isinstance(adapter, string_types): + if len(adapter) == 7: + annotations = adapter[-1] + adapter = adapter[:-1] + adapter = formatargspec(*adapter) + + exec_('def adapter{}: pass'.format(adapter), ns, ns) + adapter = ns['adapter'] + + # Override the annotations for the manufactured + # adapter function so they match the original + # adapter signature argument specification. + + if annotations: + adapter.__annotations__ = annotations + + return AdapterWrapper(wrapped=wrapped, wrapper=wrapper, + enabled=enabled, adapter=adapter) + + return proxy(wrapped=wrapped, wrapper=wrapper, enabled=enabled) + + # The wrapper has been provided so return the final decorator. + # The decorator is itself one of our function wrappers so we + # can determine when it is applied to functions, instance methods + # or class methods. This allows us to bind the instance or class + # method so the appropriate self or cls attribute is supplied + # when it is finally called. + + def _wrapper(wrapped, instance, args, kwargs): + # We first check for the case where the decorator was applied + # to a class type. + # + # @decorator + # class mydecoratorclass(object): + # def __init__(self, arg=None): + # self.arg = arg + # def __call__(self, wrapped, instance, args, kwargs): + # return wrapped(*args, **kwargs) + # + # @mydecoratorclass(arg=1) + # def function(): + # pass + # + # In this case an instance of the class is to be used as the + # decorator wrapper function. If args was empty at this point, + # then it means that there were optional keyword arguments + # supplied to be used when creating an instance of the class + # to be used as the wrapper function. + + if instance is None and isclass(wrapped) and not args: + # We still need to be passed the target function to be + # wrapped as yet, so we need to return a further function + # to be able to capture it. + + def _capture(target_wrapped): + # Now have the target function to be wrapped and need + # to create an instance of the class which is to act + # as the decorator wrapper function. Before we do that, + # we need to first check that use of the decorator + # hadn't been disabled by a simple boolean. If it was, + # the target function to be wrapped is returned instead. + + _enabled = enabled + if type(_enabled) is bool: + if not _enabled: + return target_wrapped + _enabled = None + + # Now create an instance of the class which is to act + # as the decorator wrapper function. Any arguments had + # to be supplied as keyword only arguments so that is + # all we pass when creating it. + + target_wrapper = wrapped(**kwargs) + + # Finally build the wrapper itself and return it. + + return _build(target_wrapped, target_wrapper, + _enabled, adapter) + + return _capture + + # We should always have the target function to be wrapped at + # this point as the first (and only) value in args. + + target_wrapped = args[0] + + # Need to now check that use of the decorator hadn't been + # disabled by a simple boolean. If it was, then target + # function to be wrapped is returned instead. + + _enabled = enabled + if type(_enabled) is bool: + if not _enabled: + return target_wrapped + _enabled = None + + # We now need to build the wrapper, but there are a couple of + # different cases we need to consider. + + if instance is None: + if isclass(wrapped): + # In this case the decorator was applied to a class + # type but optional keyword arguments were not supplied + # for initialising an instance of the class to be used + # as the decorator wrapper function. + # + # @decorator + # class mydecoratorclass(object): + # def __init__(self, arg=None): + # self.arg = arg + # def __call__(self, wrapped, instance, + # args, kwargs): + # return wrapped(*args, **kwargs) + # + # @mydecoratorclass + # def function(): + # pass + # + # We still need to create an instance of the class to + # be used as the decorator wrapper function, but no + # arguments are pass. + + target_wrapper = wrapped() + + else: + # In this case the decorator was applied to a normal + # function, or possibly a static method of a class. + # + # @decorator + # def mydecoratorfuntion(wrapped, instance, + # args, kwargs): + # return wrapped(*args, **kwargs) + # + # @mydecoratorfunction + # def function(): + # pass + # + # That normal function becomes the decorator wrapper + # function. + + target_wrapper = wrapper + + else: + if isclass(instance): + # In this case the decorator was applied to a class + # method. + # + # class myclass(object): + # @decorator + # @classmethod + # def decoratorclassmethod(cls, wrapped, + # instance, args, kwargs): + # return wrapped(*args, **kwargs) + # + # instance = myclass() + # + # @instance.decoratorclassmethod + # def function(): + # pass + # + # This one is a bit strange because binding was actually + # performed on the wrapper created by our decorator + # factory. We need to apply that binding to the decorator + # wrapper function that the decorator factory + # was applied to. + + target_wrapper = wrapper.__get__(None, instance) + + else: + # In this case the decorator was applied to an instance + # method. + # + # class myclass(object): + # @decorator + # def decoratorclassmethod(self, wrapped, + # instance, args, kwargs): + # return wrapped(*args, **kwargs) + # + # instance = myclass() + # + # @instance.decoratorclassmethod + # def function(): + # pass + # + # This one is a bit strange because binding was actually + # performed on the wrapper created by our decorator + # factory. We need to apply that binding to the decorator + # wrapper function that the decorator factory + # was applied to. + + target_wrapper = wrapper.__get__(instance, type(instance)) + + # Finally build the wrapper itself and return it. + + return _build(target_wrapped, target_wrapper, _enabled, adapter) + + # We first return our magic function wrapper here so we can + # determine in what context the decorator factory was used. In + # other words, it is itself a universal decorator. The decorator + # function is used as the adapter so that linters see a signature + # corresponding to the decorator and not the wrapper it is being + # applied to. + + return _build(wrapper, _wrapper, adapter=decorator) + + else: + # The wrapper still has not been provided, so we are just + # collecting the optional keyword arguments. Return the + # decorator again wrapped in a partial using the collected + # arguments. + + return partial(decorator, enabled=enabled, adapter=adapter, + proxy=proxy) + +# Decorator for implementing thread synchronization. It can be used as a +# decorator, in which case the synchronization context is determined by +# what type of function is wrapped, or it can also be used as a context +# manager, where the user needs to supply the correct synchronization +# context. It is also possible to supply an object which appears to be a +# synchronization primitive of some sort, by virtue of having release() +# and acquire() methods. In that case that will be used directly as the +# synchronization primitive without creating a separate lock against the +# derived or supplied context. + +def synchronized(wrapped): + # Determine if being passed an object which is a synchronization + # primitive. We can't check by type for Lock, RLock, Semaphore etc, + # as the means of creating them isn't the type. Therefore use the + # existence of acquire() and release() methods. This is more + # extensible anyway as it allows custom synchronization mechanisms. + + if hasattr(wrapped, 'acquire') and hasattr(wrapped, 'release'): + # We remember what the original lock is and then return a new + # decorator which accesses and locks it. When returning the new + # decorator we wrap it with an object proxy so we can override + # the context manager methods in case it is being used to wrap + # synchronized statements with a 'with' statement. + + lock = wrapped + + @decorator + def _synchronized(wrapped, instance, args, kwargs): + # Execute the wrapped function while the original supplied + # lock is held. + + with lock: + return wrapped(*args, **kwargs) + + class _PartialDecorator(CallableObjectProxy): + + def __enter__(self): + lock.acquire() + return lock + + def __exit__(self, *args): + lock.release() + + return _PartialDecorator(wrapped=_synchronized) + + # Following only apply when the lock is being created automatically + # based on the context of what was supplied. In this case we supply + # a final decorator, but need to use FunctionWrapper directly as we + # want to derive from it to add context manager methods in case it is + # being used to wrap synchronized statements with a 'with' statement. + + def _synchronized_lock(context): + # Attempt to retrieve the lock for the specific context. + + lock = vars(context).get('_synchronized_lock', None) + + if lock is None: + # There is no existing lock defined for the context we + # are dealing with so we need to create one. This needs + # to be done in a way to guarantee there is only one + # created, even if multiple threads try and create it at + # the same time. We can't always use the setdefault() + # method on the __dict__ for the context. This is the + # case where the context is a class, as __dict__ is + # actually a dictproxy. What we therefore do is use a + # meta lock on this wrapper itself, to control the + # creation and assignment of the lock attribute against + # the context. + + with synchronized._synchronized_meta_lock: + # We need to check again for whether the lock we want + # exists in case two threads were trying to create it + # at the same time and were competing to create the + # meta lock. + + lock = vars(context).get('_synchronized_lock', None) + + if lock is None: + lock = RLock() + setattr(context, '_synchronized_lock', lock) + + return lock + + def _synchronized_wrapper(wrapped, instance, args, kwargs): + # Execute the wrapped function while the lock for the + # desired context is held. If instance is None then the + # wrapped function is used as the context. + + with _synchronized_lock(instance if instance is not None else wrapped): + return wrapped(*args, **kwargs) + + class _FinalDecorator(FunctionWrapper): + + def __enter__(self): + self._self_lock = _synchronized_lock(self.__wrapped__) + self._self_lock.acquire() + return self._self_lock + + def __exit__(self, *args): + self._self_lock.release() + + return _FinalDecorator(wrapped=wrapped, wrapper=_synchronized_wrapper) + +synchronized._synchronized_meta_lock = Lock() diff --git a/ddtrace/vendor/wrapt/importer.py b/ddtrace/vendor/wrapt/importer.py new file mode 100644 index 00000000000..5c4d4cc6630 --- /dev/null +++ b/ddtrace/vendor/wrapt/importer.py @@ -0,0 +1,286 @@ +"""This module implements a post import hook mechanism styled after what is +described in PEP-369. Note that it doesn't cope with modules being reloaded. + +""" + +import sys +import threading + +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = basestring, + find_spec = None +else: + string_types = str, + from importlib.util import find_spec + +from .decorators import synchronized + +# The dictionary registering any post import hooks to be triggered once +# the target module has been imported. Once a module has been imported +# and the hooks fired, the list of hooks recorded against the target +# module will be truncated but the list left in the dictionary. This +# acts as a flag to indicate that the module had already been imported. + +_post_import_hooks = {} +_post_import_hooks_init = False +_post_import_hooks_lock = threading.RLock() + +# Register a new post import hook for the target module name. This +# differs from the PEP-369 implementation in that it also allows the +# hook function to be specified as a string consisting of the name of +# the callback in the form 'module:function'. This will result in a +# proxy callback being registered which will defer loading of the +# specified module containing the callback function until required. + +def _create_import_hook_from_string(name): + def import_hook(module): + module_name, function = name.split(':') + attrs = function.split('.') + __import__(module_name) + callback = sys.modules[module_name] + for attr in attrs: + callback = getattr(callback, attr) + return callback(module) + return import_hook + +@synchronized(_post_import_hooks_lock) +def register_post_import_hook(hook, name): + # Create a deferred import hook if hook is a string name rather than + # a callable function. + + if isinstance(hook, string_types): + hook = _create_import_hook_from_string(hook) + + # Automatically install the import hook finder if it has not already + # been installed. + + global _post_import_hooks_init + + if not _post_import_hooks_init: + _post_import_hooks_init = True + sys.meta_path.insert(0, ImportHookFinder()) + + # Determine if any prior registration of a post import hook for + # the target modules has occurred and act appropriately. + + hooks = _post_import_hooks.get(name, None) + + if hooks is None: + # No prior registration of post import hooks for the target + # module. We need to check whether the module has already been + # imported. If it has we fire the hook immediately and add an + # empty list to the registry to indicate that the module has + # already been imported and hooks have fired. Otherwise add + # the post import hook to the registry. + + module = sys.modules.get(name, None) + + if module is not None: + _post_import_hooks[name] = [] + hook(module) + + else: + _post_import_hooks[name] = [hook] + + elif hooks == []: + # A prior registration of port import hooks for the target + # module was done and the hooks already fired. Fire the hook + # immediately. + + module = sys.modules[name] + hook(module) + + else: + # A prior registration of port import hooks for the target + # module was done but the module has not yet been imported. + + _post_import_hooks[name].append(hook) + +# Register post import hooks defined as package entry points. + +def _create_import_hook_from_entrypoint(entrypoint): + def import_hook(module): + __import__(entrypoint.module_name) + callback = sys.modules[entrypoint.module_name] + for attr in entrypoint.attrs: + callback = getattr(callback, attr) + return callback(module) + return import_hook + +def discover_post_import_hooks(group): + try: + import pkg_resources + except ImportError: + return + + for entrypoint in pkg_resources.iter_entry_points(group=group): + callback = _create_import_hook_from_entrypoint(entrypoint) + register_post_import_hook(callback, entrypoint.name) + +# Indicate that a module has been loaded. Any post import hooks which +# were registered against the target module will be invoked. If an +# exception is raised in any of the post import hooks, that will cause +# the import of the target module to fail. + +@synchronized(_post_import_hooks_lock) +def notify_module_loaded(module): + name = getattr(module, '__name__', None) + hooks = _post_import_hooks.get(name, None) + + if hooks: + _post_import_hooks[name] = [] + + for hook in hooks: + hook(module) + +# A custom module import finder. This intercepts attempts to import +# modules and watches out for attempts to import target modules of +# interest. When a module of interest is imported, then any post import +# hooks which are registered will be invoked. + +class _ImportHookLoader: + + def load_module(self, fullname): + module = sys.modules[fullname] + notify_module_loaded(module) + + return module + +class _ImportHookChainedLoader: + + def __init__(self, loader): + self.loader = loader + + if hasattr(loader, "load_module"): + self.load_module = self._load_module + if hasattr(loader, "create_module"): + self.create_module = self._create_module + if hasattr(loader, "exec_module"): + self.exec_module = self._exec_module + + def _load_module(self, fullname): + module = self.loader.load_module(fullname) + notify_module_loaded(module) + + return module + + # Python 3.4 introduced create_module() and exec_module() instead of + # load_module() alone. Splitting the two steps. + + def _create_module(self, spec): + return self.loader.create_module(spec) + + def _exec_module(self, module): + self.loader.exec_module(module) + notify_module_loaded(module) + +class ImportHookFinder: + + def __init__(self): + self.in_progress = {} + + @synchronized(_post_import_hooks_lock) + def find_module(self, fullname, path=None): + # If the module being imported is not one we have registered + # post import hooks for, we can return immediately. We will + # take no further part in the importing of this module. + + if not fullname in _post_import_hooks: + return None + + # When we are interested in a specific module, we will call back + # into the import system a second time to defer to the import + # finder that is supposed to handle the importing of the module. + # We set an in progress flag for the target module so that on + # the second time through we don't trigger another call back + # into the import system and cause a infinite loop. + + if fullname in self.in_progress: + return None + + self.in_progress[fullname] = True + + # Now call back into the import system again. + + try: + if not find_spec: + # For Python 2 we don't have much choice but to + # call back in to __import__(). This will + # actually cause the module to be imported. If no + # module could be found then ImportError will be + # raised. Otherwise we return a loader which + # returns the already loaded module and invokes + # the post import hooks. + + __import__(fullname) + + return _ImportHookLoader() + + else: + # For Python 3 we need to use find_spec().loader + # from the importlib.util module. It doesn't actually + # import the target module and only finds the + # loader. If a loader is found, we need to return + # our own loader which will then in turn call the + # real loader to import the module and invoke the + # post import hooks. + + loader = getattr(find_spec(fullname), "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + return _ImportHookChainedLoader(loader) + + finally: + del self.in_progress[fullname] + + def find_spec(self, fullname, path=None, target=None): + # Since Python 3.4, you are meant to implement find_spec() method + # instead of find_module() and since Python 3.10 you get deprecation + # warnings if you don't define find_spec(). + + # If the module being imported is not one we have registered + # post import hooks for, we can return immediately. We will + # take no further part in the importing of this module. + + if not fullname in _post_import_hooks: + return None + + # When we are interested in a specific module, we will call back + # into the import system a second time to defer to the import + # finder that is supposed to handle the importing of the module. + # We set an in progress flag for the target module so that on + # the second time through we don't trigger another call back + # into the import system and cause a infinite loop. + + if fullname in self.in_progress: + return None + + self.in_progress[fullname] = True + + # Now call back into the import system again. + + try: + # This should only be Python 3 so find_spec() should always + # exist so don't need to check. + + spec = find_spec(fullname) + loader = getattr(spec, "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + spec.loader = _ImportHookChainedLoader(loader) + + return spec + + finally: + del self.in_progress[fullname] + +# Decorator for marking that a function should be called as a post +# import hook when the target module is imported. + +def when_imported(name): + def register(hook): + register_post_import_hook(hook, name) + return hook + return register diff --git a/ddtrace/vendor/wrapt/setup.py b/ddtrace/vendor/wrapt/setup.py new file mode 100644 index 00000000000..dae324bd230 --- /dev/null +++ b/ddtrace/vendor/wrapt/setup.py @@ -0,0 +1,7 @@ +__all__ = ["get_extensions"] + +from setuptools import Extension + + +def get_extensions(): + return [Extension("ddtrace.vendor.wrapt._wrappers", sources=["ddtrace/vendor/wrapt/_wrappers.c"],)] diff --git a/ddtrace/vendor/wrapt/wrappers.py b/ddtrace/vendor/wrapt/wrappers.py new file mode 100644 index 00000000000..e2825c644c2 --- /dev/null +++ b/ddtrace/vendor/wrapt/wrappers.py @@ -0,0 +1,1001 @@ +import os +import sys +import functools +import operator +import weakref +import inspect + +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = (basestring,) +else: + string_types = (str,) + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + return meta("NewBase", bases, {}) + + +class _ObjectProxyMethods(object): + + # We use properties to override the values of __module__ and + # __doc__. If we add these in ObjectProxy, the derived class + # __dict__ will still be setup to have string variants of these + # attributes and the rules of descriptors means that they appear to + # take precedence over the properties in the base class. To avoid + # that, we copy the properties into the derived class type itself + # via a meta class. In that way the properties will always take + # precedence. + + @property + def __module__(self): + return self.__wrapped__.__module__ + + @__module__.setter + def __module__(self, value): + self.__wrapped__.__module__ = value + + @property + def __doc__(self): + return self.__wrapped__.__doc__ + + @__doc__.setter + def __doc__(self, value): + self.__wrapped__.__doc__ = value + + # We similar use a property for __dict__. We need __dict__ to be + # explicit to ensure that vars() works as expected. + + @property + def __dict__(self): + return self.__wrapped__.__dict__ + + # Need to also propagate the special __weakref__ attribute for case + # where decorating classes which will define this. If do not define + # it and use a function like inspect.getmembers() on a decorator + # class it will fail. This can't be in the derived classes. + + @property + def __weakref__(self): + return self.__wrapped__.__weakref__ + + +class _ObjectProxyMetaType(type): + def __new__(cls, name, bases, dictionary): + # Copy our special properties into the class so that they + # always take precedence over attributes of the same name added + # during construction of a derived class. This is to save + # duplicating the implementation for them in all derived classes. + + dictionary.update(vars(_ObjectProxyMethods)) + + return type.__new__(cls, name, bases, dictionary) + + +class ObjectProxy(with_metaclass(_ObjectProxyMetaType)): + + __slots__ = "__wrapped__" + + def __init__(self, wrapped): + object.__setattr__(self, "__wrapped__", wrapped) + + # Python 3.2+ has the __qualname__ attribute, but it does not + # allow it to be overridden using a property and it must instead + # be an actual string object instead. + + try: + object.__setattr__(self, "__qualname__", wrapped.__qualname__) + except AttributeError: + pass + + # Python 3.10 onwards also does not allow itself to be overridden + # using a property and it must instead be set explicitly. + + try: + object.__setattr__(self, "__annotations__", wrapped.__annotations__) + except AttributeError: + pass + + @property + def __name__(self): + return self.__wrapped__.__name__ + + @__name__.setter + def __name__(self, value): + self.__wrapped__.__name__ = value + + @property + def __class__(self): + return self.__wrapped__.__class__ + + @__class__.setter + def __class__(self, value): + self.__wrapped__.__class__ = value + + def __dir__(self): + return dir(self.__wrapped__) + + def __str__(self): + return str(self.__wrapped__) + + if not PY2: + + def __bytes__(self): + return bytes(self.__wrapped__) + + def __repr__(self): + return "<{} at 0x{:x} for {} at 0x{:x}>".format( + type(self).__name__, id(self), type(self.__wrapped__).__name__, id(self.__wrapped__) + ) + + def __reversed__(self): + return reversed(self.__wrapped__) + + if not PY2: + + def __round__(self): + return round(self.__wrapped__) + + if sys.hexversion >= 0x03070000: + + def __mro_entries__(self, bases): + return (self.__wrapped__,) + + def __lt__(self, other): + return self.__wrapped__ < other + + def __le__(self, other): + return self.__wrapped__ <= other + + def __eq__(self, other): + return self.__wrapped__ == other + + def __ne__(self, other): + return self.__wrapped__ != other + + def __gt__(self, other): + return self.__wrapped__ > other + + def __ge__(self, other): + return self.__wrapped__ >= other + + def __hash__(self): + return hash(self.__wrapped__) + + def __nonzero__(self): + return bool(self.__wrapped__) + + def __bool__(self): + return bool(self.__wrapped__) + + def __setattr__(self, name, value): + if name.startswith("_self_"): + object.__setattr__(self, name, value) + + elif name == "__wrapped__": + object.__setattr__(self, name, value) + try: + object.__delattr__(self, "__qualname__") + except AttributeError: + pass + try: + object.__setattr__(self, "__qualname__", value.__qualname__) + except AttributeError: + pass + try: + object.__delattr__(self, "__annotations__") + except AttributeError: + pass + try: + object.__setattr__(self, "__annotations__", value.__annotations__) + except AttributeError: + pass + + elif name == "__qualname__": + setattr(self.__wrapped__, name, value) + object.__setattr__(self, name, value) + + elif name == "__annotations__": + setattr(self.__wrapped__, name, value) + object.__setattr__(self, name, value) + + elif hasattr(type(self), name): + object.__setattr__(self, name, value) + + else: + setattr(self.__wrapped__, name, value) + + def __getattr__(self, name): + # If we are being to lookup '__wrapped__' then the + # '__init__()' method cannot have been called. + + if name == "__wrapped__": + raise ValueError("wrapper has not been initialised") + + return getattr(self.__wrapped__, name) + + def __delattr__(self, name): + if name.startswith("_self_"): + object.__delattr__(self, name) + + elif name == "__wrapped__": + raise TypeError("__wrapped__ must be an object") + + elif name == "__qualname__": + object.__delattr__(self, name) + delattr(self.__wrapped__, name) + + elif hasattr(type(self), name): + object.__delattr__(self, name) + + else: + delattr(self.__wrapped__, name) + + def __add__(self, other): + return self.__wrapped__ + other + + def __sub__(self, other): + return self.__wrapped__ - other + + def __mul__(self, other): + return self.__wrapped__ * other + + def __div__(self, other): + return operator.div(self.__wrapped__, other) + + def __truediv__(self, other): + return operator.truediv(self.__wrapped__, other) + + def __floordiv__(self, other): + return self.__wrapped__ // other + + def __mod__(self, other): + return self.__wrapped__ % other + + def __divmod__(self, other): + return divmod(self.__wrapped__, other) + + def __pow__(self, other, *args): + return pow(self.__wrapped__, other, *args) + + def __lshift__(self, other): + return self.__wrapped__ << other + + def __rshift__(self, other): + return self.__wrapped__ >> other + + def __and__(self, other): + return self.__wrapped__ & other + + def __xor__(self, other): + return self.__wrapped__ ^ other + + def __or__(self, other): + return self.__wrapped__ | other + + def __radd__(self, other): + return other + self.__wrapped__ + + def __rsub__(self, other): + return other - self.__wrapped__ + + def __rmul__(self, other): + return other * self.__wrapped__ + + def __rdiv__(self, other): + return operator.div(other, self.__wrapped__) + + def __rtruediv__(self, other): + return operator.truediv(other, self.__wrapped__) + + def __rfloordiv__(self, other): + return other // self.__wrapped__ + + def __rmod__(self, other): + return other % self.__wrapped__ + + def __rdivmod__(self, other): + return divmod(other, self.__wrapped__) + + def __rpow__(self, other, *args): + return pow(other, self.__wrapped__, *args) + + def __rlshift__(self, other): + return other << self.__wrapped__ + + def __rrshift__(self, other): + return other >> self.__wrapped__ + + def __rand__(self, other): + return other & self.__wrapped__ + + def __rxor__(self, other): + return other ^ self.__wrapped__ + + def __ror__(self, other): + return other | self.__wrapped__ + + def __iadd__(self, other): + self.__wrapped__ += other + return self + + def __isub__(self, other): + self.__wrapped__ -= other + return self + + def __imul__(self, other): + self.__wrapped__ *= other + return self + + def __idiv__(self, other): + self.__wrapped__ = operator.idiv(self.__wrapped__, other) + return self + + def __itruediv__(self, other): + self.__wrapped__ = operator.itruediv(self.__wrapped__, other) + return self + + def __ifloordiv__(self, other): + self.__wrapped__ //= other + return self + + def __imod__(self, other): + self.__wrapped__ %= other + return self + + def __ipow__(self, other): + self.__wrapped__ **= other + return self + + def __ilshift__(self, other): + self.__wrapped__ <<= other + return self + + def __irshift__(self, other): + self.__wrapped__ >>= other + return self + + def __iand__(self, other): + self.__wrapped__ &= other + return self + + def __ixor__(self, other): + self.__wrapped__ ^= other + return self + + def __ior__(self, other): + self.__wrapped__ |= other + return self + + def __neg__(self): + return -self.__wrapped__ + + def __pos__(self): + return +self.__wrapped__ + + def __abs__(self): + return abs(self.__wrapped__) + + def __invert__(self): + return ~self.__wrapped__ + + def __int__(self): + return int(self.__wrapped__) + + def __long__(self): + return long(self.__wrapped__) + + def __float__(self): + return float(self.__wrapped__) + + def __complex__(self): + return complex(self.__wrapped__) + + def __oct__(self): + return oct(self.__wrapped__) + + def __hex__(self): + return hex(self.__wrapped__) + + def __index__(self): + return operator.index(self.__wrapped__) + + def __len__(self): + return len(self.__wrapped__) + + def __contains__(self, value): + return value in self.__wrapped__ + + def __getitem__(self, key): + return self.__wrapped__[key] + + def __setitem__(self, key, value): + self.__wrapped__[key] = value + + def __delitem__(self, key): + del self.__wrapped__[key] + + def __getslice__(self, i, j): + return self.__wrapped__[i:j] + + def __setslice__(self, i, j, value): + self.__wrapped__[i:j] = value + + def __delslice__(self, i, j): + del self.__wrapped__[i:j] + + def __enter__(self): + return self.__wrapped__.__enter__() + + def __exit__(self, *args, **kwargs): + return self.__wrapped__.__exit__(*args, **kwargs) + + def __iter__(self): + return iter(self.__wrapped__) + + def __copy__(self): + raise NotImplementedError("object proxy must define __copy__()") + + def __deepcopy__(self, memo): + raise NotImplementedError("object proxy must define __deepcopy__()") + + def __reduce__(self): + raise NotImplementedError("object proxy must define __reduce_ex__()") + + def __reduce_ex__(self, protocol): + raise NotImplementedError("object proxy must define __reduce_ex__()") + + +class CallableObjectProxy(ObjectProxy): + def __call__(self, *args, **kwargs): + return self.__wrapped__(*args, **kwargs) + + +class PartialCallableObjectProxy(ObjectProxy): + def __init__(self, *args, **kwargs): + if len(args) < 1: + raise TypeError("partial type takes at least one argument") + + wrapped, args = args[0], args[1:] + + if not callable(wrapped): + raise TypeError("the first argument must be callable") + + super(PartialCallableObjectProxy, self).__init__(wrapped) + + self._self_args = args + self._self_kwargs = kwargs + + def __call__(self, *args, **kwargs): + _args = self._self_args + args + + _kwargs = dict(self._self_kwargs) + _kwargs.update(kwargs) + + return self.__wrapped__(*_args, **_kwargs) + + +class _FunctionWrapperBase(ObjectProxy): + + __slots__ = ("_self_instance", "_self_wrapper", "_self_enabled", "_self_binding", "_self_parent") + + def __init__(self, wrapped, instance, wrapper, enabled=None, binding="function", parent=None): + + super(_FunctionWrapperBase, self).__init__(wrapped) + + object.__setattr__(self, "_self_instance", instance) + object.__setattr__(self, "_self_wrapper", wrapper) + object.__setattr__(self, "_self_enabled", enabled) + object.__setattr__(self, "_self_binding", binding) + object.__setattr__(self, "_self_parent", parent) + + def __get__(self, instance, owner): + # This method is actually doing double duty for both unbound and + # bound derived wrapper classes. It should possibly be broken up + # and the distinct functionality moved into the derived classes. + # Can't do that straight away due to some legacy code which is + # relying on it being here in this base class. + # + # The distinguishing attribute which determines whether we are + # being called in an unbound or bound wrapper is the parent + # attribute. If binding has never occurred, then the parent will + # be None. + # + # First therefore, is if we are called in an unbound wrapper. In + # this case we perform the binding. + # + # We have one special case to worry about here. This is where we + # are decorating a nested class. In this case the wrapped class + # would not have a __get__() method to call. In that case we + # simply return self. + # + # Note that we otherwise still do binding even if instance is + # None and accessing an unbound instance method from a class. + # This is because we need to be able to later detect that + # specific case as we will need to extract the instance from the + # first argument of those passed in. + + if self._self_parent is None: + if not inspect.isclass(self.__wrapped__): + descriptor = self.__wrapped__.__get__(instance, owner) + + return self.__bound_function_wrapper__( + descriptor, instance, self._self_wrapper, self._self_enabled, self._self_binding, self + ) + + return self + + # Now we have the case of binding occurring a second time on what + # was already a bound function. In this case we would usually + # return ourselves again. This mirrors what Python does. + # + # The special case this time is where we were originally bound + # with an instance of None and we were likely an instance + # method. In that case we rebind against the original wrapped + # function from the parent again. + + if self._self_instance is None and self._self_binding == "function": + descriptor = self._self_parent.__wrapped__.__get__(instance, owner) + + return self._self_parent.__bound_function_wrapper__( + descriptor, instance, self._self_wrapper, self._self_enabled, self._self_binding, self._self_parent + ) + + return self + + def __call__(self, *args, **kwargs): + # If enabled has been specified, then evaluate it at this point + # and if the wrapper is not to be executed, then simply return + # the bound function rather than a bound wrapper for the bound + # function. When evaluating enabled, if it is callable we call + # it, otherwise we evaluate it as a boolean. + + if self._self_enabled is not None: + if callable(self._self_enabled): + if not self._self_enabled(): + return self.__wrapped__(*args, **kwargs) + elif not self._self_enabled: + return self.__wrapped__(*args, **kwargs) + + # This can occur where initial function wrapper was applied to + # a function that was already bound to an instance. In that case + # we want to extract the instance from the function and use it. + + if self._self_binding in ("function", "classmethod"): + if self._self_instance is None: + instance = getattr(self.__wrapped__, "__self__", None) + if instance is not None: + return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + + # This is generally invoked when the wrapped function is being + # called as a normal function and is not bound to a class as an + # instance method. This is also invoked in the case where the + # wrapped function was a method, but this wrapper was in turn + # wrapped using the staticmethod decorator. + + return self._self_wrapper(self.__wrapped__, self._self_instance, args, kwargs) + + def __set_name__(self, owner, name): + # This is a special method use to supply information to + # descriptors about what the name of variable in a class + # definition is. Not wanting to add this to ObjectProxy as not + # sure of broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(self.__wrapped__, "__set_name__"): + self.__wrapped__.__set_name__(owner, name) + + def __instancecheck__(self, instance): + # This is a special method used by isinstance() to make checks + # instance of the `__wrapped__`. + return isinstance(instance, self.__wrapped__) + + def __subclasscheck__(self, subclass): + # This is a special method used by issubclass() to make checks + # about inheritance of classes. We need to upwrap any object + # proxy. Not wanting to add this to ObjectProxy as not sure of + # broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(subclass, "__wrapped__"): + return issubclass(subclass.__wrapped__, self.__wrapped__) + else: + return issubclass(subclass, self.__wrapped__) + + +class BoundFunctionWrapper(_FunctionWrapperBase): + def __call__(self, *args, **kwargs): + # If enabled has been specified, then evaluate it at this point + # and if the wrapper is not to be executed, then simply return + # the bound function rather than a bound wrapper for the bound + # function. When evaluating enabled, if it is callable we call + # it, otherwise we evaluate it as a boolean. + + if self._self_enabled is not None: + if callable(self._self_enabled): + if not self._self_enabled(): + return self.__wrapped__(*args, **kwargs) + elif not self._self_enabled: + return self.__wrapped__(*args, **kwargs) + + # We need to do things different depending on whether we are + # likely wrapping an instance method vs a static method or class + # method. + + if self._self_binding == "function": + if self._self_instance is None: + # This situation can occur where someone is calling the + # instancemethod via the class type and passing the instance + # as the first argument. We need to shift the args before + # making the call to the wrapper and effectively bind the + # instance to the wrapped function using a partial so the + # wrapper doesn't see anything as being different. + + if not args: + raise TypeError("missing 1 required positional argument") + + instance, args = args[0], args[1:] + wrapped = PartialCallableObjectProxy(self.__wrapped__, instance) + return self._self_wrapper(wrapped, instance, args, kwargs) + + return self._self_wrapper(self.__wrapped__, self._self_instance, args, kwargs) + + else: + # As in this case we would be dealing with a classmethod or + # staticmethod, then _self_instance will only tell us whether + # when calling the classmethod or staticmethod they did it via an + # instance of the class it is bound to and not the case where + # done by the class type itself. We thus ignore _self_instance + # and use the __self__ attribute of the bound function instead. + # For a classmethod, this means instance will be the class type + # and for a staticmethod it will be None. This is probably the + # more useful thing we can pass through even though we loose + # knowledge of whether they were called on the instance vs the + # class type, as it reflects what they have available in the + # decoratored function. + + instance = getattr(self.__wrapped__, "__self__", None) + + return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + + +class FunctionWrapper(_FunctionWrapperBase): + + __bound_function_wrapper__ = BoundFunctionWrapper + + def __init__(self, wrapped, wrapper, enabled=None): + # What it is we are wrapping here could be anything. We need to + # try and detect specific cases though. In particular, we need + # to detect when we are given something that is a method of a + # class. Further, we need to know when it is likely an instance + # method, as opposed to a class or static method. This can + # become problematic though as there isn't strictly a fool proof + # method of knowing. + # + # The situations we could encounter when wrapping a method are: + # + # 1. The wrapper is being applied as part of a decorator which + # is a part of the class definition. In this case what we are + # given is the raw unbound function, classmethod or staticmethod + # wrapper objects. + # + # The problem here is that we will not know we are being applied + # in the context of the class being set up. This becomes + # important later for the case of an instance method, because in + # that case we just see it as a raw function and can't + # distinguish it from wrapping a normal function outside of + # a class context. + # + # 2. The wrapper is being applied when performing monkey + # patching of the class type afterwards and the method to be + # wrapped was retrieved direct from the __dict__ of the class + # type. This is effectively the same as (1) above. + # + # 3. The wrapper is being applied when performing monkey + # patching of the class type afterwards and the method to be + # wrapped was retrieved from the class type. In this case + # binding will have been performed where the instance against + # which the method is bound will be None at that point. + # + # This case is a problem because we can no longer tell if the + # method was a static method, plus if using Python3, we cannot + # tell if it was an instance method as the concept of an + # unnbound method no longer exists. + # + # 4. The wrapper is being applied when performing monkey + # patching of an instance of a class. In this case binding will + # have been performed where the instance was not None. + # + # This case is a problem because we can no longer tell if the + # method was a static method. + # + # Overall, the best we can do is look at the original type of the + # object which was wrapped prior to any binding being done and + # see if it is an instance of classmethod or staticmethod. In + # the case where other decorators are between us and them, if + # they do not propagate the __class__ attribute so that the + # isinstance() checks works, then likely this will do the wrong + # thing where classmethod and staticmethod are used. + # + # Since it is likely to be very rare that anyone even puts + # decorators around classmethod and staticmethod, likelihood of + # that being an issue is very small, so we accept it and suggest + # that those other decorators be fixed. It is also only an issue + # if a decorator wants to actually do things with the arguments. + # + # As to not being able to identify static methods properly, we + # just hope that that isn't something people are going to want + # to wrap, or if they do suggest they do it the correct way by + # ensuring that it is decorated in the class definition itself, + # or patch it in the __dict__ of the class type. + # + # So to get the best outcome we can, whenever we aren't sure what + # it is, we label it as a 'function'. If it was already bound and + # that is rebound later, we assume that it will be an instance + # method and try an cope with the possibility that the 'self' + # argument it being passed as an explicit argument and shuffle + # the arguments around to extract 'self' for use as the instance. + + if isinstance(wrapped, classmethod): + binding = "classmethod" + + elif isinstance(wrapped, staticmethod): + binding = "staticmethod" + + elif hasattr(wrapped, "__self__"): + if inspect.isclass(wrapped.__self__): + binding = "classmethod" + else: + binding = "function" + + else: + binding = "function" + + super(FunctionWrapper, self).__init__(wrapped, None, wrapper, enabled, binding) + + +try: + if not os.environ.get("WRAPT_DISABLE_EXTENSIONS"): + from ._wrappers import ( + ObjectProxy, + CallableObjectProxy, + PartialCallableObjectProxy, + FunctionWrapper, + BoundFunctionWrapper, + _FunctionWrapperBase, + ) +except ImportError: + pass + +# Helper functions for applying wrappers to existing functions. + + +def resolve_path(module, name): + if isinstance(module, string_types): + __import__(module) + module = sys.modules[module] + + parent = module + + path = name.split(".") + attribute = path[0] + + # We can't just always use getattr() because in doing + # that on a class it will cause binding to occur which + # will complicate things later and cause some things not + # to work. For the case of a class we therefore access + # the __dict__ directly. To cope though with the wrong + # class being given to us, or a method being moved into + # a base class, we need to walk the class hierarchy to + # work out exactly which __dict__ the method was defined + # in, as accessing it from __dict__ will fail if it was + # not actually on the class given. Fallback to using + # getattr() if we can't find it. If it truly doesn't + # exist, then that will fail. + + def lookup_attribute(parent, attribute): + if inspect.isclass(parent): + for cls in inspect.getmro(parent): + if attribute in vars(cls): + return vars(cls)[attribute] + else: + return getattr(parent, attribute) + else: + return getattr(parent, attribute) + + original = lookup_attribute(parent, attribute) + + for attribute in path[1:]: + parent = original + original = lookup_attribute(parent, attribute) + + return (parent, attribute, original) + + +def apply_patch(parent, attribute, replacement): + setattr(parent, attribute, replacement) + + +def wrap_object(module, name, factory, args=(), kwargs={}): + (parent, attribute, original) = resolve_path(module, name) + wrapper = factory(original, *args, **kwargs) + apply_patch(parent, attribute, wrapper) + return wrapper + + +# Function for applying a proxy object to an attribute of a class +# instance. The wrapper works by defining an attribute of the same name +# on the class which is a descriptor and which intercepts access to the +# instance attribute. Note that this cannot be used on attributes which +# are themselves defined by a property object. + + +class AttributeWrapper(object): + def __init__(self, attribute, factory, args, kwargs): + self.attribute = attribute + self.factory = factory + self.args = args + self.kwargs = kwargs + + def __get__(self, instance, owner): + value = instance.__dict__[self.attribute] + return self.factory(value, *self.args, **self.kwargs) + + def __set__(self, instance, value): + instance.__dict__[self.attribute] = value + + def __delete__(self, instance): + del instance.__dict__[self.attribute] + + +def wrap_object_attribute(module, name, factory, args=(), kwargs={}): + path, attribute = name.rsplit(".", 1) + parent = resolve_path(module, path)[2] + wrapper = AttributeWrapper(attribute, factory, args, kwargs) + apply_patch(parent, attribute, wrapper) + return wrapper + + +# Functions for creating a simple decorator using a FunctionWrapper, +# plus short cut functions for applying wrappers to functions. These are +# for use when doing monkey patching. For a more featured way of +# creating decorators see the decorator decorator instead. + + +def function_wrapper(wrapper): + def _wrapper(wrapped, instance, args, kwargs): + target_wrapped = args[0] + if instance is None: + target_wrapper = wrapper + elif inspect.isclass(instance): + target_wrapper = wrapper.__get__(None, instance) + else: + target_wrapper = wrapper.__get__(instance, type(instance)) + return FunctionWrapper(target_wrapped, target_wrapper) + + return FunctionWrapper(wrapper, _wrapper) + + +def wrap_function_wrapper(module, name, wrapper): + return wrap_object(module, name, FunctionWrapper, (wrapper,)) + + +def patch_function_wrapper(module, name): + def _wrapper(wrapper): + return wrap_object(module, name, FunctionWrapper, (wrapper,)) + + return _wrapper + + +def transient_function_wrapper(module, name): + def _decorator(wrapper): + def _wrapper(wrapped, instance, args, kwargs): + target_wrapped = args[0] + if instance is None: + target_wrapper = wrapper + elif inspect.isclass(instance): + target_wrapper = wrapper.__get__(None, instance) + else: + target_wrapper = wrapper.__get__(instance, type(instance)) + + def _execute(wrapped, instance, args, kwargs): + (parent, attribute, original) = resolve_path(module, name) + replacement = FunctionWrapper(original, target_wrapper) + setattr(parent, attribute, replacement) + try: + return wrapped(*args, **kwargs) + finally: + setattr(parent, attribute, original) + + return FunctionWrapper(target_wrapped, _execute) + + return FunctionWrapper(wrapper, _wrapper) + + return _decorator + + +# A weak function proxy. This will work on instance methods, class +# methods, static methods and regular functions. Special treatment is +# needed for the method types because the bound method is effectively a +# transient object and applying a weak reference to one will immediately +# result in it being destroyed and the weakref callback called. The weak +# reference is therefore applied to the instance the method is bound to +# and the original function. The function is then rebound at the point +# of a call via the weak function proxy. + + +def _weak_function_proxy_callback(ref, proxy, callback): + if proxy._self_expired: + return + + proxy._self_expired = True + + # This could raise an exception. We let it propagate back and let + # the weakref.proxy() deal with it, at which point it generally + # prints out a short error message direct to stderr and keeps going. + + if callback is not None: + callback(proxy) + + +class WeakFunctionProxy(ObjectProxy): + + __slots__ = ("_self_expired", "_self_instance") + + def __init__(self, wrapped, callback=None): + # We need to determine if the wrapped function is actually a + # bound method. In the case of a bound method, we need to keep a + # reference to the original unbound function and the instance. + # This is necessary because if we hold a reference to the bound + # function, it will be the only reference and given it is a + # temporary object, it will almost immediately expire and + # the weakref callback triggered. So what is done is that we + # hold a reference to the instance and unbound function and + # when called bind the function to the instance once again and + # then call it. Note that we avoid using a nested function for + # the callback here so as not to cause any odd reference cycles. + + _callback = callback and functools.partial(_weak_function_proxy_callback, proxy=self, callback=callback) + + self._self_expired = False + + if isinstance(wrapped, _FunctionWrapperBase): + self._self_instance = weakref.ref(wrapped._self_instance, _callback) + + if wrapped._self_parent is not None: + super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped._self_parent, _callback)) + + else: + super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback)) + + return + + try: + self._self_instance = weakref.ref(wrapped.__self__, _callback) + + super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped.__func__, _callback)) + + except AttributeError: + self._self_instance = None + + super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback)) + + def __call__(self, *args, **kwargs): + # We perform a boolean check here on the instance and wrapped + # function as that will trigger the reference error prior to + # calling if the reference had expired. + + instance = self._self_instance and self._self_instance() + function = self.__wrapped__ and self.__wrapped__ + + # If the wrapped function was originally a bound function, for + # which we retained a reference to the instance and the unbound + # function we need to rebind the function and then call it. If + # not just called the wrapped function. + + if instance is None: + return self.__wrapped__(*args, **kwargs) + + return function.__get__(instance, type(instance))(*args, **kwargs) diff --git a/docs/contributing-integrations.rst b/docs/contributing-integrations.rst index 76761b329c1..29989e565f2 100644 --- a/docs/contributing-integrations.rst +++ b/docs/contributing-integrations.rst @@ -25,7 +25,7 @@ What tools does an integration rely on? Integrations rely primarily on Wrapt, the Pin API, and the Core API. -The `Wrapt `_ library is the main +The `Wrapt `_ library, vendored in ``ddtrace.vendor.wrapt``, is the main piece of code that ``ddtrace`` uses to hook into the runtime execution of third-party libraries. The essential task of writing an integration is determining the functions in the third-party library that would serve as useful entrypoints and wrapping them with ``wrap_function_wrapper``. There are exceptions, but this is diff --git a/pyproject.toml b/pyproject.toml index 203a69661fc..d401b3d3bb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,12 +30,10 @@ dependencies = [ "envier", "importlib_metadata; python_version<'3.8'", "opentelemetry-api>=1", - "psutil~=5.8", "protobuf>=3", "setuptools; python_version>='3.12'", "six>=1.12.0", "typing_extensions", - "wrapt~=1.15", "xmltodict>=0.12", ] diff --git a/releasenotes/notes/revendor-psutil-1d24b2b3a27cf4a5.yaml b/releasenotes/notes/revendor-psutil-1d24b2b3a27cf4a5.yaml new file mode 100644 index 00000000000..ab5344ad31c --- /dev/null +++ b/releasenotes/notes/revendor-psutil-1d24b2b3a27cf4a5.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + The ``wrapt`` and ``psutil`` packages are vendored to help users avoid building these packages if wheels were not available for a given platform. This reverses a change released in v2.0.0. diff --git a/setup.cfg b/setup.cfg index 61c9ea70de3..0a148b4ce59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ universal=1 [codespell] -skip = *.json,*.h,*.cpp,*.c,.riot,.tox,.mypy_cache,.git,ddtrace/vendor/*,tests/contrib/openai/cassettes/*,tests/contrib/langchain/cassettes/*,ddtrace/appsec/_iast/_taint_tracking/_vendor/* +skip = *.json,*.h,*.cpp,*.c,.riot,.tox,.mypy_cache,.git,*ddtrace/vendor,tests/contrib/openai/cassettes/*,tests/contrib/langchain/cassettes/*,ddtrace/appsec/_iast/_taint_tracking/_vendor/* exclude-file = .codespellignorelines ignore-words-list = asend,dne,fo,medias,ment,nin,ot,setttings,statics,ba,spawnve,doas diff --git a/setup.py b/setup.py index 054b5e1266f..93a460f0504 100644 --- a/setup.py +++ b/setup.py @@ -555,9 +555,7 @@ def get_ddup_ext(): "xmltodict>=0.12", "envier", "opentelemetry-api>=1", - "psutil>=5.8.0", "setuptools; python_version>='3.12'", - "wrapt>=1.15.0", ] + bytecode, extras_require={ @@ -657,5 +655,7 @@ def get_ddup_ext(): force=True, annotate=os.getenv("_DD_CYTHON_ANNOTATE") == "1", ) + + get_exts_for("wrapt") + + get_exts_for("psutil") + get_ddup_ext(), ) diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index 77aa5c71b10..9fc49b57fe3 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -4,9 +4,8 @@ import subprocess import sys -import psutil - from ddtrace.internal.utils.retry import RetryError +from ddtrace.vendor import psutil from tests.webclient import Client diff --git a/tests/appsec/iast/test_iast_mem_check.py b/tests/appsec/iast/test_iast_mem_check.py index 34647335577..f111e8ad553 100644 --- a/tests/appsec/iast/test_iast_mem_check.py +++ b/tests/appsec/iast/test_iast_mem_check.py @@ -30,8 +30,6 @@ ], ) def test_propagation_memory_check(origin1, origin2, iast_span_defaults): - import psutil - from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import active_map_addreses_size from ddtrace.appsec._iast._taint_tracking import create_context @@ -39,6 +37,7 @@ def test_propagation_memory_check(origin1, origin2, iast_span_defaults): from ddtrace.appsec._iast._taint_tracking import num_objects_tainted from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.vendor import psutil from tests.appsec.iast.fixtures.propagation_path import propagation_memory_check expected_result = propagation_memory_check(origin1, origin2) diff --git a/tests/contrib/aioredis/test_aioredis.py b/tests/contrib/aioredis/test_aioredis.py index 87ab9be5660..216be33ef07 100644 --- a/tests/contrib/aioredis/test_aioredis.py +++ b/tests/contrib/aioredis/test_aioredis.py @@ -3,13 +3,13 @@ import aioredis import pytest -from wrapt import ObjectProxy from ddtrace import Pin from ddtrace import tracer from ddtrace.contrib.aioredis.patch import aioredis_version from ddtrace.contrib.aioredis.patch import patch from ddtrace.contrib.aioredis.patch import unpatch +from ddtrace.vendor.wrapt import ObjectProxy from tests.utils import override_config from ..config import REDIS_CONFIG diff --git a/tests/contrib/aredis/test_aredis.py b/tests/contrib/aredis/test_aredis.py index 9c21d30f649..6317e32ea34 100644 --- a/tests/contrib/aredis/test_aredis.py +++ b/tests/contrib/aredis/test_aredis.py @@ -3,12 +3,12 @@ import aredis import pytest -from wrapt import ObjectProxy from ddtrace import Pin from ddtrace.contrib.aredis.patch import patch from ddtrace.contrib.aredis.patch import unpatch from ddtrace.internal.schema.span_attribute_schema import _DEFAULT_SPAN_SERVICE_NAMES +from ddtrace.vendor.wrapt import ObjectProxy from tests.opentracer.utils import init_tracer from tests.utils import override_config diff --git a/tests/contrib/asyncio/test_patch.py b/tests/contrib/asyncio/test_patch.py index 3ea32621164..b2acfc4a041 100644 --- a/tests/contrib/asyncio/test_patch.py +++ b/tests/contrib/asyncio/test_patch.py @@ -2,10 +2,10 @@ import sys import pytest -import wrapt from ddtrace.contrib.asyncio.patch import patch from ddtrace.contrib.asyncio.patch import unpatch +from ddtrace.vendor import wrapt from .utils import AsyncioTestCase diff --git a/tests/contrib/asynctest/test_asynctest.py b/tests/contrib/asynctest/test_asynctest.py index 046cf430b45..b919dcfbfe4 100644 --- a/tests/contrib/asynctest/test_asynctest.py +++ b/tests/contrib/asynctest/test_asynctest.py @@ -42,7 +42,7 @@ def pytest_configure(config): "attribute 'coroutine'", ) def test_asynctest_not_raise_attribute_error_exception(self): - """Test AttributeError exception in `wrapt/wrappers.py` when try to import asynctest package. + """Test AttributeError exception in `ddtrace/vendor/wrapt/wrappers.py` when try to import asynctest package. Issue: https://github.com/DataDog/dd-trace-py/issues/4484 """ py_file = self.testdir.makepyfile( diff --git a/tests/contrib/consul/test.py b/tests/contrib/consul/test.py index a1af811b92d..98c5cc781d1 100644 --- a/tests/contrib/consul/test.py +++ b/tests/contrib/consul/test.py @@ -1,5 +1,4 @@ import consul -from wrapt import BoundFunctionWrapper from ddtrace import Pin from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -7,6 +6,7 @@ from ddtrace.contrib.consul.patch import unpatch from ddtrace.ext import consul as consulx from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME +from ddtrace.vendor.wrapt import BoundFunctionWrapper from tests.utils import TracerTestCase from tests.utils import assert_is_measured diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index d3ce3fc9acd..5368bb8929d 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -15,7 +15,6 @@ import mock import pytest from six import ensure_text -import wrapt from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -38,6 +37,7 @@ from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID +from ddtrace.vendor import wrapt from tests.opentracer.utils import init_tracer from tests.utils import assert_dict_issuperset from tests.utils import override_config diff --git a/tests/contrib/flask/__init__.py b/tests/contrib/flask/__init__.py index bbec059ec0f..0fc1971dea7 100644 --- a/tests/contrib/flask/__init__.py +++ b/tests/contrib/flask/__init__.py @@ -1,10 +1,10 @@ import flask from flask.testing import FlaskClient -import wrapt from ddtrace import Pin from ddtrace.contrib.flask import patch from ddtrace.contrib.flask import unpatch +from ddtrace.vendor import wrapt from tests.utils import TracerTestCase diff --git a/tests/contrib/flask/test_idempotency.py b/tests/contrib/flask/test_idempotency.py index 5c87f060277..5782ffb5749 100644 --- a/tests/contrib/flask/test_idempotency.py +++ b/tests/contrib/flask/test_idempotency.py @@ -2,12 +2,12 @@ import flask import mock -import wrapt from ddtrace.contrib.flask import patch from ddtrace.contrib.flask import unpatch from ddtrace.contrib.flask.patch import _u from ddtrace.contrib.flask.patch import _w +from ddtrace.vendor import wrapt class FlaskIdempotencyTestCase(unittest.TestCase): diff --git a/tests/contrib/flask_autopatch/test_flask_autopatch.py b/tests/contrib/flask_autopatch/test_flask_autopatch.py index 3da871f27f4..89a8752ec17 100644 --- a/tests/contrib/flask_autopatch/test_flask_autopatch.py +++ b/tests/contrib/flask_autopatch/test_flask_autopatch.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import flask -import wrapt from ddtrace import Pin from ddtrace.contrib.flask.patch import flask_version from ddtrace.ext import http +from ddtrace.vendor import wrapt from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code diff --git a/tests/contrib/httplib/test_httplib.py b/tests/contrib/httplib/test_httplib.py index 2fb373dc444..73943165371 100644 --- a/tests/contrib/httplib/test_httplib.py +++ b/tests/contrib/httplib/test_httplib.py @@ -3,7 +3,6 @@ import sys import pytest -import wrapt from ddtrace import config from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY @@ -17,6 +16,7 @@ from ddtrace.internal.constants import _HTTPLIB_NO_TRACE_REQUEST from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.pin import Pin +from ddtrace.vendor import wrapt from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_span_http_status_code diff --git a/tests/contrib/httplib/test_httplib_distributed.py b/tests/contrib/httplib/test_httplib_distributed.py index 4c72113cb4e..8c7675de3c9 100644 --- a/tests/contrib/httplib/test_httplib_distributed.py +++ b/tests/contrib/httplib/test_httplib_distributed.py @@ -1,12 +1,11 @@ # Standard library import contextlib -import wrapt - # Project from ddtrace import config from ddtrace.internal.compat import httplib from ddtrace.pin import Pin +from ddtrace.vendor import wrapt from tests.utils import TracerTestCase from .test_httplib import SOCKET diff --git a/tests/contrib/httpx/test_httpx.py b/tests/contrib/httpx/test_httpx.py index 0857cae3f58..010e6fb7a3c 100644 --- a/tests/contrib/httpx/test_httpx.py +++ b/tests/contrib/httpx/test_httpx.py @@ -1,7 +1,6 @@ import httpx import pytest import six -from wrapt import ObjectProxy from ddtrace import config from ddtrace.contrib.httpx.patch import HTTPX_VERSION @@ -9,6 +8,7 @@ from ddtrace.contrib.httpx.patch import unpatch from ddtrace.pin import Pin from ddtrace.settings.http import HttpConfig +from ddtrace.vendor.wrapt import ObjectProxy from tests.utils import override_config from tests.utils import override_http_config diff --git a/tests/contrib/httpx/test_httpx_pre_0_11.py b/tests/contrib/httpx/test_httpx_pre_0_11.py index 7bd00e4fe95..3be81d81194 100644 --- a/tests/contrib/httpx/test_httpx_pre_0_11.py +++ b/tests/contrib/httpx/test_httpx_pre_0_11.py @@ -1,7 +1,6 @@ import httpx import pytest import six -from wrapt import ObjectProxy from ddtrace import config from ddtrace.contrib.httpx.patch import HTTPX_VERSION @@ -9,6 +8,7 @@ from ddtrace.contrib.httpx.patch import unpatch from ddtrace.pin import Pin from ddtrace.settings.http import HttpConfig +from ddtrace.vendor.wrapt import ObjectProxy from tests.utils import override_config from tests.utils import override_http_config diff --git a/tests/contrib/logging/test_logging.py b/tests/contrib/logging/test_logging.py index cda9da527f8..93522a9e075 100644 --- a/tests/contrib/logging/test_logging.py +++ b/tests/contrib/logging/test_logging.py @@ -2,7 +2,6 @@ import pytest import six -import wrapt import ddtrace from ddtrace.constants import ENV_KEY @@ -13,6 +12,7 @@ from ddtrace.contrib.logging.constants import RECORD_ATTR_TRACE_ID from ddtrace.internal.compat import StringIO from ddtrace.internal.constants import MAX_UINT_64BITS +from ddtrace.vendor import wrapt from tests.utils import TracerTestCase diff --git a/tests/contrib/patch.py b/tests/contrib/patch.py index 5ef41339fdc..b90c1854698 100644 --- a/tests/contrib/patch.py +++ b/tests/contrib/patch.py @@ -7,9 +7,8 @@ from textwrap import dedent import unittest -import wrapt - from ddtrace.internal.compat import httplib +from ddtrace.vendor import wrapt from ddtrace.version import get_version from tests.subprocesstest import SubprocessTestCase from tests.subprocesstest import run_in_subprocess @@ -730,7 +729,7 @@ def test_ddtrace_run_patch_on_import(self): from ddtrace.internal.module import ModuleWatchdog - from wrapt import wrap_function_wrapper as wrap + from ddtrace.vendor.wrapt import wrap_function_wrapper as wrap patched = False diff --git a/tests/contrib/psycopg/test_psycopg_snapshot.py b/tests/contrib/psycopg/test_psycopg_snapshot.py index 86571b48abc..2023f97afdc 100644 --- a/tests/contrib/psycopg/test_psycopg_snapshot.py +++ b/tests/contrib/psycopg/test_psycopg_snapshot.py @@ -3,10 +3,10 @@ import psycopg import pytest -import wrapt from ddtrace.contrib.psycopg.patch import patch from ddtrace.contrib.psycopg.patch import unpatch +from ddtrace.vendor import wrapt from tests.contrib.config import POSTGRES_CONFIG from tests.utils import override_config diff --git a/tests/contrib/psycopg2/test_psycopg_snapshot.py b/tests/contrib/psycopg2/test_psycopg_snapshot.py index c2e63464c2a..27e98d944d6 100644 --- a/tests/contrib/psycopg2/test_psycopg_snapshot.py +++ b/tests/contrib/psycopg2/test_psycopg_snapshot.py @@ -2,10 +2,10 @@ import psycopg2 import pytest -import wrapt from ddtrace.contrib.psycopg.patch import patch from ddtrace.contrib.psycopg.patch import unpatch +from ddtrace.vendor import wrapt from tests.contrib.config import POSTGRES_CONFIG from tests.utils import override_config diff --git a/tests/contrib/pymemcache/autopatch/test.py b/tests/contrib/pymemcache/autopatch/test.py index 2c4a4ed20ef..c494dda549e 100644 --- a/tests/contrib/pymemcache/autopatch/test.py +++ b/tests/contrib/pymemcache/autopatch/test.py @@ -2,7 +2,8 @@ import pymemcache from pymemcache.client import hash as pymemcache_hash -import wrapt + +from ddtrace.vendor import wrapt class AutoPatchTestCase(unittest.TestCase): diff --git a/tests/contrib/pymemcache/test_client.py b/tests/contrib/pymemcache/test_client.py index 66f4637e15a..71ef3ff7349 100644 --- a/tests/contrib/pymemcache/test_client.py +++ b/tests/contrib/pymemcache/test_client.py @@ -7,7 +7,6 @@ from pymemcache.exceptions import MemcacheUnknownCommandError from pymemcache.exceptions import MemcacheUnknownError import pytest -import wrapt # project from ddtrace import Pin @@ -15,6 +14,7 @@ from ddtrace.contrib.pymemcache.patch import patch from ddtrace.contrib.pymemcache.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME +from ddtrace.vendor import wrapt from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import override_config diff --git a/tests/contrib/redis/test_redis_asyncio.py b/tests/contrib/redis/test_redis_asyncio.py index 3ef7924764e..a364187ae96 100644 --- a/tests/contrib/redis/test_redis_asyncio.py +++ b/tests/contrib/redis/test_redis_asyncio.py @@ -4,12 +4,12 @@ import pytest import redis import redis.asyncio -from wrapt import ObjectProxy from ddtrace import Pin from ddtrace import tracer from ddtrace.contrib.redis.patch import patch from ddtrace.contrib.redis.patch import unpatch +from ddtrace.vendor.wrapt import ObjectProxy from tests.utils import override_config from ..config import REDIS_CONFIG diff --git a/tests/contrib/tornado/test_executor_decorator.py b/tests/contrib/tornado/test_executor_decorator.py index 3e0eb1d952e..94970b239e9 100644 --- a/tests/contrib/tornado/test_executor_decorator.py +++ b/tests/contrib/tornado/test_executor_decorator.py @@ -188,7 +188,7 @@ def test_futures_double_instrumentation(self): patch(futures=True) from concurrent.futures import ThreadPoolExecutor - from wrapt import BoundFunctionWrapper + from ddtrace.vendor.wrapt import BoundFunctionWrapper fn_wrapper = getattr(ThreadPoolExecutor.submit, "__wrapped__", None) assert not isinstance(fn_wrapper, BoundFunctionWrapper) diff --git a/tests/contrib/vertica/test_vertica.py b/tests/contrib/vertica/test_vertica.py index 605050cdd60..6d102ebf7ad 100644 --- a/tests/contrib/vertica/test_vertica.py +++ b/tests/contrib/vertica/test_vertica.py @@ -1,5 +1,4 @@ import pytest -import wrapt import ddtrace from ddtrace import Pin @@ -12,6 +11,7 @@ from ddtrace.contrib.vertica.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.settings.config import _deepmerge +from ddtrace.vendor import wrapt from tests.contrib.config import VERTICA_CONFIG from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer diff --git a/tests/contrib/yaaredis/test_yaaredis.py b/tests/contrib/yaaredis/test_yaaredis.py index a1195161de8..88b897cbb05 100644 --- a/tests/contrib/yaaredis/test_yaaredis.py +++ b/tests/contrib/yaaredis/test_yaaredis.py @@ -3,12 +3,12 @@ import uuid import pytest -from wrapt import ObjectProxy import yaaredis from ddtrace import Pin from ddtrace.contrib.yaaredis.patch import patch from ddtrace.contrib.yaaredis.patch import unpatch +from ddtrace.vendor.wrapt import ObjectProxy from tests.opentracer.utils import init_tracer from tests.utils import override_config diff --git a/tests/debugging/function/test_discovery.py b/tests/debugging/function/test_discovery.py index 00c94c2fcda..d01fbfa589e 100644 --- a/tests/debugging/function/test_discovery.py +++ b/tests/debugging/function/test_discovery.py @@ -112,7 +112,7 @@ def test_function_mangled(stuff_discovery): def test_discovery_after_external_wrapping(stuff): - import wrapt as wrapt + import ddtrace.vendor.wrapt as wrapt def wrapper(wrapped, inst, args, kwargs): pass diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index 193dfd5fb80..9f0b9dc03fd 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -550,7 +550,7 @@ def test_debugger_multiple_function_probes_on_same_lazy_module(): # DEV: The following tests are to ensure compatibility with the tracer -import wrapt as wrapt # noqa +import ddtrace.vendor.wrapt as wrapt # noqa def wrapper(wrapped, instance, args, kwargs): diff --git a/tests/tracer/runtime/test_metric_collectors.py b/tests/tracer/runtime/test_metric_collectors.py index 5536ec9f2b6..56ee763666e 100644 --- a/tests/tracer/runtime/test_metric_collectors.py +++ b/tests/tracer/runtime/test_metric_collectors.py @@ -36,7 +36,7 @@ def test_static_metrics(self): import threading import time - import psutil + from ddtrace.vendor import psutil # Something to bump CPU utilization def busy_wait(duration_ms): diff --git a/tests/utils.py b/tests/utils.py index d82fa7b26e1..e9c8219c6b6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,7 +11,6 @@ import attr import pkg_resources import pytest -import wrapt import ddtrace from ddtrace import Span @@ -30,6 +29,7 @@ from ddtrace.internal.schema import SCHEMA_VERSION from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.internal.writer import AgentWriter +from ddtrace.vendor import wrapt from tests.subprocesstest import SubprocessTestCase