Skip to content

Commit

Permalink
sdk: Implement basic os resource detector (open-telemetry#3992)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zirak authored Aug 1, 2024
1 parent d981cf1 commit e16dc48
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 11 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Drop Final annotation from Enum in semantic conventions
([#4085](https://github.com/open-telemetry/opentelemetry-python/pull/4085))
- Update log export example to not use root logger ([#4090](https://github.com/open-telemetry/opentelemetry-python/pull/4090))
- sdk: Add OS resource detector
([#3992](https://github.com/open-telemetry/opentelemetry-python/pull/3992))

## Version 1.26.0/0.47b0 (2024-07-25)

Expand Down Expand Up @@ -1633,4 +1635,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3778](https://github.com/open-telemetry/opentelemetry-python/pull/3778))
- Fix license field in pyproject.toml files
([#3803](https://github.com/open-telemetry/opentelemetry-python/pull/3803))

1 change: 1 addition & 0 deletions opentelemetry-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ console = "opentelemetry.sdk.trace.export:ConsoleSpanExporter"
[project.entry-points.opentelemetry_resource_detector]
otel = "opentelemetry.sdk.resources:OTELResourceDetector"
process = "opentelemetry.sdk.resources:ProcessResourceDetector"
os = "opentelemetry.sdk.resources:OsResourceDetector"

[project.urls]
Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk"
Expand Down
107 changes: 97 additions & 10 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import concurrent.futures
import logging
import os
import platform
import sys
import typing
from json import dumps
Expand Down Expand Up @@ -125,8 +126,9 @@
KUBERNETES_JOB_NAME = ResourceAttributes.K8S_JOB_NAME
KUBERNETES_CRON_JOB_UID = ResourceAttributes.K8S_CRONJOB_UID
KUBERNETES_CRON_JOB_NAME = ResourceAttributes.K8S_CRONJOB_NAME
OS_TYPE = ResourceAttributes.OS_TYPE
OS_DESCRIPTION = ResourceAttributes.OS_DESCRIPTION
OS_TYPE = ResourceAttributes.OS_TYPE
OS_VERSION = ResourceAttributes.OS_VERSION
PROCESS_PID = ResourceAttributes.PROCESS_PID
PROCESS_PARENT_PID = ResourceAttributes.PROCESS_PARENT_PID
PROCESS_EXECUTABLE_NAME = ResourceAttributes.PROCESS_EXECUTABLE_NAME
Expand Down Expand Up @@ -182,16 +184,17 @@ def create(
if not attributes:
attributes = {}

resource_detectors: List[ResourceDetector] = []

resource = _DEFAULT_RESOURCE

otel_experimental_resource_detectors = environ.get(
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "otel"
).split(",")
otel_experimental_resource_detectors = {"otel"}.union(
{
otel_experimental_resource_detector.strip()
for otel_experimental_resource_detector in environ.get(
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, ""
).split(",")
if otel_experimental_resource_detector
}
)

if "otel" not in otel_experimental_resource_detectors:
otel_experimental_resource_detectors.append("otel")
resource_detectors: List[ResourceDetector] = []

resource_detector: str
for resource_detector in otel_experimental_resource_detectors:
Expand Down Expand Up @@ -384,6 +387,90 @@ def detect(self) -> "Resource":
return Resource(resource_info) # type: ignore


class OsResourceDetector(ResourceDetector):
"""Detect os resources based on `Operating System conventions <https://opentelemetry.io/docs/specs/semconv/resource/os/>`_."""

def detect(self) -> "Resource":
"""Returns a resource with with ``os.type`` and ``os.version``.
Python's platform library
~~~~~~~~~~~~~~~~~~~~~~~~~
To grab this information, Python's ``platform`` does not return what a
user might expect it to. Below is a breakdown of its return values in
different operating systems.
.. code-block:: python
:caption: Linux
>>> platform.system()
'Linux'
>>> platform.release()
'6.5.0-35-generic'
>>> platform.version()
'#35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2'
.. code-block:: python
:caption: MacOS
>>> platform.system()
'Darwin'
>>> platform.release()
'23.0.0'
>>> platform.version()
'Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:57 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T8112'
.. code-block:: python
:caption: Windows
>>> platform.system()
'Windows'
>>> platform.release()
'2022Server'
>>> platform.version()
'10.0.20348'
.. code-block:: python
:caption: FreeBSD
>>> platform.system()
'FreeBSD'
>>> platform.release()
'14.1-RELEASE'
>>> platform.version()
'FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC'
.. code-block:: python
:caption: Solaris
>>> platform.system()
'SunOS'
>>> platform.release()
'5.11'
>>> platform.version()
'11.4.0.15.0'
"""

os_type = platform.system().lower()
os_version = platform.release()

# See docstring
if os_type == "windows":
os_version = platform.version()
# Align SunOS with conventions
elif os_type == "sunos":
os_type = "solaris"
os_version = platform.version()

return Resource(
{
OS_TYPE: os_type,
OS_VERSION: os_version,
}
)


def get_aggregated_resources(
detectors: typing.List["ResourceDetector"],
initial_resource: typing.Optional[Resource] = None,
Expand Down
54 changes: 54 additions & 0 deletions opentelemetry-sdk/tests/resources/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
_DEFAULT_RESOURCE,
_EMPTY_RESOURCE,
_OPENTELEMETRY_SDK_VERSION,
OS_TYPE,
OS_VERSION,
OTEL_RESOURCE_ATTRIBUTES,
OTEL_SERVICE_NAME,
PROCESS_COMMAND,
Expand All @@ -45,6 +47,7 @@
TELEMETRY_SDK_LANGUAGE,
TELEMETRY_SDK_NAME,
TELEMETRY_SDK_VERSION,
OsResourceDetector,
OTELResourceDetector,
ProcessResourceDetector,
Resource,
Expand Down Expand Up @@ -673,6 +676,24 @@ def test_resource_detector_entry_points_non_default(self):
self.assertEqual(resource.attributes["a"], "b")
self.assertEqual(resource.schema_url, "")

@patch.dict(
environ, {OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: ""}, clear=True
)
def test_resource_detector_entry_points_empty(self):
resource = Resource({}).create()
self.assertEqual(
resource.attributes["telemetry.sdk.language"], "python"
)

@patch.dict(
environ, {OTEL_EXPERIMENTAL_RESOURCE_DETECTORS: "os"}, clear=True
)
def test_resource_detector_entry_points_os(self):
resource = Resource({}).create()

self.assertIn(OS_TYPE, resource.attributes)
self.assertIn(OS_VERSION, resource.attributes)

def test_resource_detector_entry_points_otel(self):
"""
Test that OTELResourceDetector-resource-generated attributes are
Expand Down Expand Up @@ -723,3 +744,36 @@ def test_resource_detector_entry_points_otel(self):
)
self.assertIn(PROCESS_RUNTIME_VERSION, resource.attributes.keys())
self.assertEqual(resource.schema_url, "")

@patch("platform.system", lambda: "Linux")
@patch("platform.release", lambda: "666.5.0-35-generic")
def test_os_detector_linux(self):
resource = get_aggregated_resources(
[OsResourceDetector()],
Resource({}),
)

self.assertEqual(resource.attributes[OS_TYPE], "linux")
self.assertEqual(resource.attributes[OS_VERSION], "666.5.0-35-generic")

@patch("platform.system", lambda: "Windows")
@patch("platform.version", lambda: "10.0.666")
def test_os_detector_windows(self):
resource = get_aggregated_resources(
[OsResourceDetector()],
Resource({}),
)

self.assertEqual(resource.attributes[OS_TYPE], "windows")
self.assertEqual(resource.attributes[OS_VERSION], "10.0.666")

@patch("platform.system", lambda: "SunOS")
@patch("platform.version", lambda: "666.4.0.15.0")
def test_os_detector_solaris(self):
resource = get_aggregated_resources(
[OsResourceDetector()],
Resource({}),
)

self.assertEqual(resource.attributes[OS_TYPE], "solaris")
self.assertEqual(resource.attributes[OS_VERSION], "666.4.0.15.0")

0 comments on commit e16dc48

Please sign in to comment.