From de89b22b4828c0ca6ab19002d9f6ffc182f48283 Mon Sep 17 00:00:00 2001 From: Allen Kim Date: Thu, 23 Nov 2023 05:00:09 +0900 Subject: [PATCH] Feature/process resource detector (#3472) --- CHANGELOG.md | 3 + .../opentelemetry/sdk/resources/__init__.py | 44 +++++++++---- .../tests/resources/test_resources.py | 61 +++++++++++++++++-- 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc24ba9dc..4f895dec6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3251](https://github.com/open-telemetry/opentelemetry-python/pull/3251)) - Prometheus exporter support for auto instrumentation ([#3413](https://github.com/open-telemetry/opentelemetry-python/pull/3413)) +- Implement Process Resource detector + ([#3472](https://github.com/open-telemetry/opentelemetry-python/pull/3472)) + ## Version 1.20.0/0.41b0 (2023-09-04) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 42fcf125bb..f92fdb964e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -58,6 +58,7 @@ import abc import concurrent.futures import logging +import os import sys import typing from json import dumps @@ -74,11 +75,15 @@ from opentelemetry.util._importlib_metadata import entry_points, version from opentelemetry.util.types import AttributeValue +try: + import psutil +except ImportError: + psutil = None + LabelValue = AttributeValue Attributes = typing.Dict[str, LabelValue] logger = logging.getLogger(__name__) - CLOUD_PROVIDER = ResourceAttributes.CLOUD_PROVIDER CLOUD_ACCOUNT_ID = ResourceAttributes.CLOUD_ACCOUNT_ID CLOUD_REGION = ResourceAttributes.CLOUD_REGION @@ -117,6 +122,7 @@ OS_TYPE = ResourceAttributes.OS_TYPE OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION PROCESS_PID = ResourceAttributes.PROCESS_PID +PROCESS_PARENT_PID = ResourceAttributes.PROCESS_PARENT_PID PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME PROCESS_EXECUTABLE_PATH = ResourceAttributes.PROCESS_EXECUTABLE_PATH PROCESS_COMMAND = ResourceAttributes.PROCESS_COMMAND @@ -135,7 +141,6 @@ TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE - _OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk") @@ -180,7 +185,6 @@ def create( otel_experimental_resource_detectors.append("otel") for resource_detector in otel_experimental_resource_detectors: - resource_detectors.append( next( iter( @@ -337,14 +341,32 @@ def detect(self) -> "Resource": else sys.version_info, ) ) - - return Resource( - { - PROCESS_RUNTIME_DESCRIPTION: sys.version, - PROCESS_RUNTIME_NAME: sys.implementation.name, - PROCESS_RUNTIME_VERSION: _runtime_version, - } - ) + _process_pid = os.getpid() + _process_executable_name = sys.executable + _process_executable_path = os.path.dirname(_process_executable_name) + _process_command = sys.argv[0] + _process_command_line = " ".join(sys.argv) + _process_command_args = sys.argv[1:] + resource_info = { + PROCESS_RUNTIME_DESCRIPTION: sys.version, + PROCESS_RUNTIME_NAME: sys.implementation.name, + PROCESS_RUNTIME_VERSION: _runtime_version, + PROCESS_PID: _process_pid, + PROCESS_EXECUTABLE_NAME: _process_executable_name, + PROCESS_EXECUTABLE_PATH: _process_executable_path, + PROCESS_COMMAND: _process_command, + PROCESS_COMMAND_LINE: _process_command_line, + PROCESS_COMMAND_ARGS: _process_command_args, + } + if hasattr(os, "getppid"): + # pypy3 does not have getppid() + resource_info[PROCESS_PARENT_PID] = os.getppid() + + if psutil is not None: + process = psutil.Process() + resource_info[PROCESS_OWNER] = process.username() + + return Resource(resource_info) def get_aggregated_resources( diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 1fc81c7cae..53ecf30cab 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -11,9 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -# pylint: disable=protected-access - +import os +import sys import unittest import uuid from logging import ERROR, WARNING @@ -30,7 +29,14 @@ _OPENTELEMETRY_SDK_VERSION, OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME, + PROCESS_COMMAND, + PROCESS_COMMAND_ARGS, + PROCESS_COMMAND_LINE, PROCESS_EXECUTABLE_NAME, + PROCESS_EXECUTABLE_PATH, + PROCESS_OWNER, + PROCESS_PARENT_PID, + PROCESS_PID, PROCESS_RUNTIME_DESCRIPTION, PROCESS_RUNTIME_NAME, PROCESS_RUNTIME_VERSION, @@ -45,6 +51,11 @@ get_aggregated_resources, ) +try: + import psutil +except ImportError: + psutil = None + class TestResources(unittest.TestCase): def setUp(self) -> None: @@ -524,6 +535,10 @@ def test_service_name_env_precedence(self): Resource({"service.name": "from-service-name"}), ) + @patch( + "sys.argv", + ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"], + ) def test_process_detector(self): initial_resource = Resource({"foo": "bar"}) aggregated_resource = get_aggregated_resources( @@ -543,8 +558,42 @@ def test_process_detector(self): aggregated_resource.attributes.keys(), ) - def test_resource_detector_entry_points_default(self): + self.assertEqual( + aggregated_resource.attributes[PROCESS_PID], os.getpid() + ) + if hasattr(os, "getppid"): + self.assertEqual( + aggregated_resource.attributes[PROCESS_PARENT_PID], + os.getppid(), + ) + + if psutil is not None: + self.assertEqual( + aggregated_resource.attributes[PROCESS_OWNER], + psutil.Process().username(), + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_EXECUTABLE_NAME], + sys.executable, + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_EXECUTABLE_PATH], + os.path.dirname(sys.executable), + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND], sys.argv[0] + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND_LINE], + " ".join(sys.argv), + ) + self.assertEqual( + aggregated_resource.attributes[PROCESS_COMMAND_ARGS], + tuple(sys.argv[1:]), + ) + + def test_resource_detector_entry_points_default(self): resource = Resource({}).create() self.assertEqual( @@ -644,7 +693,9 @@ def test_resource_detector_entry_points_otel(self): resource.attributes["telemetry.sdk.name"], "opentelemetry" ) self.assertEqual( - resource.attributes["service.name"], "unknown_service" + resource.attributes["service.name"], + "unknown_service:" + + resource.attributes["process.executable.name"], ) self.assertEqual(resource.attributes["a"], "b") self.assertEqual(resource.attributes["c"], "d")