Skip to content

Commit de89b22

Browse files
authored
Feature/process resource detector (open-telemetry#3472)
1 parent f399195 commit de89b22

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-16
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
([#3251](https://github.com/open-telemetry/opentelemetry-python/pull/3251))
3333
- Prometheus exporter support for auto instrumentation
3434
([#3413](https://github.com/open-telemetry/opentelemetry-python/pull/3413))
35+
- Implement Process Resource detector
36+
([#3472](https://github.com/open-telemetry/opentelemetry-python/pull/3472))
37+
3538

3639
## Version 1.20.0/0.41b0 (2023-09-04)
3740

opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py

+33-11
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import abc
5959
import concurrent.futures
6060
import logging
61+
import os
6162
import sys
6263
import typing
6364
from json import dumps
@@ -74,11 +75,15 @@
7475
from opentelemetry.util._importlib_metadata import entry_points, version
7576
from opentelemetry.util.types import AttributeValue
7677

78+
try:
79+
import psutil
80+
except ImportError:
81+
psutil = None
82+
7783
LabelValue = AttributeValue
7884
Attributes = typing.Dict[str, LabelValue]
7985
logger = logging.getLogger(__name__)
8086

81-
8287
CLOUD_PROVIDER = ResourceAttributes.CLOUD_PROVIDER
8388
CLOUD_ACCOUNT_ID = ResourceAttributes.CLOUD_ACCOUNT_ID
8489
CLOUD_REGION = ResourceAttributes.CLOUD_REGION
@@ -117,6 +122,7 @@
117122
OS_TYPE = ResourceAttributes.OS_TYPE
118123
OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION
119124
PROCESS_PID = ResourceAttributes.PROCESS_PID
125+
PROCESS_PARENT_PID = ResourceAttributes.PROCESS_PARENT_PID
120126
PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME
121127
PROCESS_EXECUTABLE_PATH = ResourceAttributes.PROCESS_EXECUTABLE_PATH
122128
PROCESS_COMMAND = ResourceAttributes.PROCESS_COMMAND
@@ -135,7 +141,6 @@
135141
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
136142
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE
137143

138-
139144
_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")
140145

141146

@@ -180,7 +185,6 @@ def create(
180185
otel_experimental_resource_detectors.append("otel")
181186

182187
for resource_detector in otel_experimental_resource_detectors:
183-
184188
resource_detectors.append(
185189
next(
186190
iter(
@@ -337,14 +341,32 @@ def detect(self) -> "Resource":
337341
else sys.version_info,
338342
)
339343
)
340-
341-
return Resource(
342-
{
343-
PROCESS_RUNTIME_DESCRIPTION: sys.version,
344-
PROCESS_RUNTIME_NAME: sys.implementation.name,
345-
PROCESS_RUNTIME_VERSION: _runtime_version,
346-
}
347-
)
344+
_process_pid = os.getpid()
345+
_process_executable_name = sys.executable
346+
_process_executable_path = os.path.dirname(_process_executable_name)
347+
_process_command = sys.argv[0]
348+
_process_command_line = " ".join(sys.argv)
349+
_process_command_args = sys.argv[1:]
350+
resource_info = {
351+
PROCESS_RUNTIME_DESCRIPTION: sys.version,
352+
PROCESS_RUNTIME_NAME: sys.implementation.name,
353+
PROCESS_RUNTIME_VERSION: _runtime_version,
354+
PROCESS_PID: _process_pid,
355+
PROCESS_EXECUTABLE_NAME: _process_executable_name,
356+
PROCESS_EXECUTABLE_PATH: _process_executable_path,
357+
PROCESS_COMMAND: _process_command,
358+
PROCESS_COMMAND_LINE: _process_command_line,
359+
PROCESS_COMMAND_ARGS: _process_command_args,
360+
}
361+
if hasattr(os, "getppid"):
362+
# pypy3 does not have getppid()
363+
resource_info[PROCESS_PARENT_PID] = os.getppid()
364+
365+
if psutil is not None:
366+
process = psutil.Process()
367+
resource_info[PROCESS_OWNER] = process.username()
368+
369+
return Resource(resource_info)
348370

349371

350372
def get_aggregated_resources(

opentelemetry-sdk/tests/resources/test_resources.py

+56-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
15-
# pylint: disable=protected-access
16-
14+
import os
15+
import sys
1716
import unittest
1817
import uuid
1918
from logging import ERROR, WARNING
@@ -30,7 +29,14 @@
3029
_OPENTELEMETRY_SDK_VERSION,
3130
OTEL_RESOURCE_ATTRIBUTES,
3231
OTEL_SERVICE_NAME,
32+
PROCESS_COMMAND,
33+
PROCESS_COMMAND_ARGS,
34+
PROCESS_COMMAND_LINE,
3335
PROCESS_EXECUTABLE_NAME,
36+
PROCESS_EXECUTABLE_PATH,
37+
PROCESS_OWNER,
38+
PROCESS_PARENT_PID,
39+
PROCESS_PID,
3440
PROCESS_RUNTIME_DESCRIPTION,
3541
PROCESS_RUNTIME_NAME,
3642
PROCESS_RUNTIME_VERSION,
@@ -45,6 +51,11 @@
4551
get_aggregated_resources,
4652
)
4753

54+
try:
55+
import psutil
56+
except ImportError:
57+
psutil = None
58+
4859

4960
class TestResources(unittest.TestCase):
5061
def setUp(self) -> None:
@@ -524,6 +535,10 @@ def test_service_name_env_precedence(self):
524535
Resource({"service.name": "from-service-name"}),
525536
)
526537

538+
@patch(
539+
"sys.argv",
540+
["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"],
541+
)
527542
def test_process_detector(self):
528543
initial_resource = Resource({"foo": "bar"})
529544
aggregated_resource = get_aggregated_resources(
@@ -543,8 +558,42 @@ def test_process_detector(self):
543558
aggregated_resource.attributes.keys(),
544559
)
545560

546-
def test_resource_detector_entry_points_default(self):
561+
self.assertEqual(
562+
aggregated_resource.attributes[PROCESS_PID], os.getpid()
563+
)
564+
if hasattr(os, "getppid"):
565+
self.assertEqual(
566+
aggregated_resource.attributes[PROCESS_PARENT_PID],
567+
os.getppid(),
568+
)
569+
570+
if psutil is not None:
571+
self.assertEqual(
572+
aggregated_resource.attributes[PROCESS_OWNER],
573+
psutil.Process().username(),
574+
)
547575

576+
self.assertEqual(
577+
aggregated_resource.attributes[PROCESS_EXECUTABLE_NAME],
578+
sys.executable,
579+
)
580+
self.assertEqual(
581+
aggregated_resource.attributes[PROCESS_EXECUTABLE_PATH],
582+
os.path.dirname(sys.executable),
583+
)
584+
self.assertEqual(
585+
aggregated_resource.attributes[PROCESS_COMMAND], sys.argv[0]
586+
)
587+
self.assertEqual(
588+
aggregated_resource.attributes[PROCESS_COMMAND_LINE],
589+
" ".join(sys.argv),
590+
)
591+
self.assertEqual(
592+
aggregated_resource.attributes[PROCESS_COMMAND_ARGS],
593+
tuple(sys.argv[1:]),
594+
)
595+
596+
def test_resource_detector_entry_points_default(self):
548597
resource = Resource({}).create()
549598

550599
self.assertEqual(
@@ -644,7 +693,9 @@ def test_resource_detector_entry_points_otel(self):
644693
resource.attributes["telemetry.sdk.name"], "opentelemetry"
645694
)
646695
self.assertEqual(
647-
resource.attributes["service.name"], "unknown_service"
696+
resource.attributes["service.name"],
697+
"unknown_service:"
698+
+ resource.attributes["process.executable.name"],
648699
)
649700
self.assertEqual(resource.attributes["a"], "b")
650701
self.assertEqual(resource.attributes["c"], "d")

0 commit comments

Comments
 (0)