Skip to content

Commit 58936f5

Browse files
committed
Merge branch 'main' into aiohttp-server-instrumentation
2 parents fbbe270 + 3478831 commit 58936f5

File tree

9 files changed

+150
-23
lines changed

9 files changed

+150
-23
lines changed

CHANGELOG.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- `opentelemetry-instrumentation-aiohttp-server` Add instrumentor and auto instrumentation support for aiohttp-server
1111
([#1800](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1800))
1212

13+
### Added
14+
- `opentelemetry-instrumentation-system-metrics` Add support for collecting process metrics
15+
([#1948](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1948))
16+
17+
### Fixed
18+
19+
- Fix version of Flask dependency `werkzeug`
20+
([#1980](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1980))
21+
- `opentelemetry-resource-detector-azure` Using new Cloud Resource ID attribute.
22+
([#1976](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1976))
23+
1324
## Version 1.20.0/0.41b0 (2023-09-01)
1425

1526
### Fixed
@@ -355,7 +366,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
355366
- `opentelemetry-instrumentation-sqlalchemy` Added span for the connection phase ([#1133](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1133))
356367
- Add metric instrumentation in asgi
357368
([#1197](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1197))
358-
- Add metric instumentation for flask
369+
- Add metric instrumentation for flask
359370
([#1186](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1186))
360371
- Add a test for asgi using NoOpTracerProvider
361372
([#1367](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1367))

instrumentation/opentelemetry-instrumentation-flask/pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies = [
3636
[project.optional-dependencies]
3737
instruments = [
3838
"flask >= 1.0, < 3.0",
39+
"werkzeug < 3.0.0"
3940
]
4041
test = [
4142
"opentelemetry-instrumentation-flask[instruments]",

instrumentation/opentelemetry-instrumentation-sklearn/src/opentelemetry/instrumentation/sklearn/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
).instrument()
3636
3737
38-
Model intrumentation example:
38+
Model instrumentation example:
3939
4040
.. code-block:: python
4141
@@ -291,7 +291,7 @@ class descendent) is being instrumented with opentelemetry. Within a
291291
SklearnInstrumentor(packages=packages).instrument()
292292
293293
294-
Model intrumentation example:
294+
Model instrumentation example:
295295
296296
.. code-block:: python
297297

instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/__init__.py

+65
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
"system.thread_count": None
3737
"process.runtime.memory": ["rss", "vms"],
3838
"process.runtime.cpu.time": ["user", "system"],
39+
"process.runtime.gc_count": None,
40+
"process.runtime.thread_count": None,
41+
"process.runtime.cpu.utilization": None,
42+
"process.runtime.context_switches": ["involuntary", "voluntary"],
3943
}
4044
4145
Usage
@@ -63,6 +67,7 @@
6367
"system.network.io": ["transmit", "receive"],
6468
"process.runtime.memory": ["rss", "vms"],
6569
"process.runtime.cpu.time": ["user", "system"],
70+
"process.runtime.context_switches": ["involuntary", "voluntary"],
6671
}
6772
SystemMetricsInstrumentor(config=configuration).instrument()
6873
@@ -105,6 +110,9 @@
105110
"process.runtime.memory": ["rss", "vms"],
106111
"process.runtime.cpu.time": ["user", "system"],
107112
"process.runtime.gc_count": None,
113+
"process.runtime.thread_count": None,
114+
"process.runtime.cpu.utilization": None,
115+
"process.runtime.context_switches": ["involuntary", "voluntary"],
108116
}
109117

110118

@@ -150,6 +158,9 @@ def __init__(
150158
self._runtime_memory_labels = self._labels.copy()
151159
self._runtime_cpu_time_labels = self._labels.copy()
152160
self._runtime_gc_count_labels = self._labels.copy()
161+
self._runtime_thread_count_labels = self._labels.copy()
162+
self._runtime_cpu_utilization_labels = self._labels.copy()
163+
self._runtime_context_switches_labels = self._labels.copy()
153164

154165
def instrumentation_dependencies(self) -> Collection[str]:
155166
return _instruments
@@ -347,6 +358,29 @@ def _instrument(self, **kwargs):
347358
unit="bytes",
348359
)
349360

361+
if "process.runtime.thread_count" in self._config:
362+
self._meter.create_observable_up_down_counter(
363+
name=f"process.runtime.{self._python_implementation}.thread_count",
364+
callbacks=[self._get_runtime_thread_count],
365+
description="Runtime active threads count",
366+
)
367+
368+
if "process.runtime.cpu.utilization" in self._config:
369+
self._meter.create_observable_gauge(
370+
name=f"process.runtime.{self._python_implementation}.cpu.utilization",
371+
callbacks=[self._get_runtime_cpu_utilization],
372+
description="Runtime CPU utilization",
373+
unit="1",
374+
)
375+
376+
if "process.runtime.context_switches" in self._config:
377+
self._meter.create_observable_counter(
378+
name=f"process.runtime.{self._python_implementation}.context_switches",
379+
callbacks=[self._get_runtime_context_switches],
380+
description="Runtime context switches",
381+
unit="switches",
382+
)
383+
350384
def _uninstrument(self, **__):
351385
pass
352386

@@ -646,3 +680,34 @@ def _get_runtime_gc_count(
646680
for index, count in enumerate(gc.get_count()):
647681
self._runtime_gc_count_labels["count"] = str(index)
648682
yield Observation(count, self._runtime_gc_count_labels.copy())
683+
684+
def _get_runtime_thread_count(
685+
self, options: CallbackOptions
686+
) -> Iterable[Observation]:
687+
"""Observer callback for runtime active thread count"""
688+
yield Observation(
689+
self._proc.num_threads(), self._runtime_thread_count_labels.copy()
690+
)
691+
692+
def _get_runtime_cpu_utilization(
693+
self, options: CallbackOptions
694+
) -> Iterable[Observation]:
695+
"""Observer callback for runtime CPU utilization"""
696+
proc_cpu_percent = self._proc.cpu_percent()
697+
yield Observation(
698+
proc_cpu_percent,
699+
self._runtime_cpu_utilization_labels.copy(),
700+
)
701+
702+
def _get_runtime_context_switches(
703+
self, options: CallbackOptions
704+
) -> Iterable[Observation]:
705+
"""Observer callback for runtime context switches"""
706+
ctx_switches = self._proc.num_ctx_switches()
707+
for metric in self._config["process.runtime.context_switches"]:
708+
if hasattr(ctx_switches, metric):
709+
self._runtime_context_switches_labels["type"] = metric
710+
yield Observation(
711+
getattr(ctx_switches, metric),
712+
self._runtime_context_switches_labels.copy(),
713+
)

instrumentation/opentelemetry-instrumentation-system-metrics/tests/test_system_metrics.py

+52-8
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
from platform import python_implementation
1919
from unittest import mock
2020

21-
from opentelemetry.instrumentation.system_metrics import (
22-
SystemMetricsInstrumentor,
23-
)
2421
from opentelemetry.sdk.metrics import MeterProvider
2522
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
2623
from opentelemetry.test.test_base import TestBase
2724

25+
from opentelemetry.instrumentation.system_metrics import (
26+
SystemMetricsInstrumentor,
27+
)
28+
2829

2930
def _mock_netconnection():
3031
NetConnection = namedtuple(
@@ -96,7 +97,7 @@ def test_system_metrics_instrument(self):
9697
for scope_metrics in resource_metrics.scope_metrics:
9798
for metric in scope_metrics.metrics:
9899
metric_names.append(metric.name)
99-
self.assertEqual(len(metric_names), 18)
100+
self.assertEqual(len(metric_names), 21)
100101

101102
observer_names = [
102103
"system.cpu.time",
@@ -117,6 +118,9 @@ def test_system_metrics_instrument(self):
117118
f"process.runtime.{self.implementation}.memory",
118119
f"process.runtime.{self.implementation}.cpu_time",
119120
f"process.runtime.{self.implementation}.gc_count",
121+
f"process.runtime.{self.implementation}.thread_count",
122+
f"process.runtime.{self.implementation}.context_switches",
123+
f"process.runtime.{self.implementation}.cpu.utilization",
120124
]
121125

122126
for observer in metric_names:
@@ -128,6 +132,9 @@ def test_runtime_metrics_instrument(self):
128132
"process.runtime.memory": ["rss", "vms"],
129133
"process.runtime.cpu.time": ["user", "system"],
130134
"process.runtime.gc_count": None,
135+
"process.runtime.thread_count": None,
136+
"process.runtime.cpu.utilization": None,
137+
"process.runtime.context_switches": ["involuntary", "voluntary"],
131138
}
132139

133140
reader = InMemoryMetricReader()
@@ -140,12 +147,15 @@ def test_runtime_metrics_instrument(self):
140147
for scope_metrics in resource_metrics.scope_metrics:
141148
for metric in scope_metrics.metrics:
142149
metric_names.append(metric.name)
143-
self.assertEqual(len(metric_names), 3)
150+
self.assertEqual(len(metric_names), 6)
144151

145152
observer_names = [
146153
f"process.runtime.{self.implementation}.memory",
147154
f"process.runtime.{self.implementation}.cpu_time",
148155
f"process.runtime.{self.implementation}.gc_count",
156+
f"process.runtime.{self.implementation}.thread_count",
157+
f"process.runtime.{self.implementation}.context_switches",
158+
f"process.runtime.{self.implementation}.cpu.utilization",
149159
]
150160

151161
for observer in metric_names:
@@ -161,9 +171,9 @@ def _assert_metrics(self, observer_name, reader, expected):
161171
for data_point in metric.data.data_points:
162172
for expect in expected:
163173
if (
164-
dict(data_point.attributes)
165-
== expect.attributes
166-
and metric.name == observer_name
174+
dict(data_point.attributes)
175+
== expect.attributes
176+
and metric.name == observer_name
167177
):
168178
self.assertEqual(
169179
data_point.value,
@@ -782,3 +792,37 @@ def test_runtime_get_count(self, mock_gc_get_count):
782792
self._test_metrics(
783793
f"process.runtime.{self.implementation}.gc_count", expected
784794
)
795+
796+
@mock.patch("psutil.Process.num_ctx_switches")
797+
def test_runtime_context_switches(self, mock_process_num_ctx_switches):
798+
PCtxSwitches = namedtuple("PCtxSwitches", ["voluntary", "involuntary"])
799+
800+
mock_process_num_ctx_switches.configure_mock(
801+
**{"return_value": PCtxSwitches(voluntary=1, involuntary=2)}
802+
)
803+
804+
expected = [
805+
_SystemMetricsResult({"type": "voluntary"}, 1),
806+
_SystemMetricsResult({"type": "involuntary"}, 2),
807+
]
808+
self._test_metrics(
809+
f"process.runtime.{self.implementation}.context_switches", expected
810+
)
811+
812+
@mock.patch("psutil.Process.num_threads")
813+
def test_runtime_thread_num(self, mock_process_thread_num):
814+
mock_process_thread_num.configure_mock(**{"return_value": 42})
815+
816+
expected = [_SystemMetricsResult({}, 42)]
817+
self._test_metrics(
818+
f"process.runtime.{self.implementation}.thread_count", expected
819+
)
820+
821+
@mock.patch("psutil.Process.cpu_percent")
822+
def test_runtime_cpu_percent(self, mock_process_cpu_percent):
823+
mock_process_cpu_percent.configure_mock(**{"return_value": 42})
824+
825+
expected = [_SystemMetricsResult({}, 42)]
826+
self._test_metrics(
827+
f"process.runtime.{self.implementation}.cpu.utilization", expected
828+
)

opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@
8888
"library": "flask >= 1.0, < 3.0",
8989
"instrumentation": "opentelemetry-instrumentation-flask==0.42b0.dev",
9090
},
91+
"werkzeug": {
92+
"library": "werkzeug < 3.0.0",
93+
"instrumentation": "opentelemetry-instrumentation-flask==0.42b0.dev",
94+
},
9195
"grpcio": {
9296
"library": "grpcio ~= 1.27",
9397
"instrumentation": "opentelemetry-instrumentation-grpc==0.42b0.dev",

opentelemetry-instrumentation/src/opentelemetry/instrumentation/propagators.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ def set(self, carrier, key, value): # pylint: disable=no-self-use
5959

6060

6161
class FuncSetter(Setter):
62-
"""FuncSetter coverts a function into a valid Setter. Any function that can
63-
set values in a carrier can be converted into a Setter by using FuncSetter.
64-
This is useful when injecting trace context into non-dict objects such
65-
HTTP Response objects for different framework.
62+
"""FuncSetter converts a function into a valid Setter. Any function that
63+
can set values in a carrier can be converted into a Setter by using
64+
FuncSetter. This is useful when injecting trace context into non-dict
65+
objects such HTTP Response objects for different framework.
6666
67-
For example, it can be used to create a setter for Falcon response object as:
67+
For example, it can be used to create a setter for Falcon response object
68+
as:
6869
6970
setter = FuncSetter(falcon.api.Response.append_header)
7071

resource/opentelemetry-resource-detector-azure/src/opentelemetry/resource/detector/azure/app_service.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
from opentelemetry.semconv.resource import ResourceAttributes, CloudPlatformValues, CloudProviderValues
1919

2020
_AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE = "azure.app.service.stamp"
21-
# TODO: Remove once this resource attribute is no longer missing from SDK
22-
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE = "cloud.resource_id"
2321
_REGION_NAME = "REGION_NAME"
2422
_WEBSITE_HOME_STAMPNAME = "WEBSITE_HOME_STAMPNAME"
2523
_WEBSITE_HOSTNAME = "WEBSITE_HOSTNAME"
@@ -49,7 +47,7 @@ def detect(self) -> Resource:
4947

5048
azure_resource_uri = _get_azure_resource_uri(website_site_name)
5149
if azure_resource_uri:
52-
attributes[_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE] = azure_resource_uri
50+
attributes[ResourceAttributes.CLOUD_RESOURCE_ID] = azure_resource_uri
5351
for (key, env_var) in _APP_SERVICE_ATTRIBUTE_ENV_VARS.items():
5452
value = environ.get(env_var)
5553
if value:

resource/opentelemetry-resource-detector-azure/src/opentelemetry/resource/detector/azure/vm.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
from urllib.error import URLError
2020

2121
from opentelemetry.sdk.resources import ResourceDetector, Resource
22-
from opentelemetry.semconv.resource import ResourceAttributes, CloudPlatformValues, CloudProviderValues
22+
from opentelemetry.semconv.resource import (
23+
ResourceAttributes,
24+
CloudPlatformValues,
25+
CloudProviderValues,
26+
)
2327

2428

2529
# TODO: Remove when cloud resource id is no longer missing in Resource Attributes
26-
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE = "cloud.resource_id"
2730
_AZURE_VM_METADATA_ENDPOINT = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json"
2831
_AZURE_VM_SCALE_SET_NAME_ATTRIBUTE = "azure.vm.scaleset.name"
2932
_AZURE_VM_SKU_ATTRIBUTE = "azure.vm.sku"
@@ -35,7 +38,7 @@
3538
ResourceAttributes.CLOUD_PLATFORM,
3639
ResourceAttributes.CLOUD_PROVIDER,
3740
ResourceAttributes.CLOUD_REGION,
38-
_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE,
41+
ResourceAttributes.CLOUD_RESOURCE_ID,
3942
ResourceAttributes.HOST_ID,
4043
ResourceAttributes.HOST_NAME,
4144
ResourceAttributes.HOST_TYPE,
@@ -81,7 +84,7 @@ def get_attribute_from_metadata(self, metadata_json, attribute_key):
8184
ams_value = CloudProviderValues.AZURE.value
8285
elif attribute_key == ResourceAttributes.CLOUD_REGION:
8386
ams_value = metadata_json["location"]
84-
elif attribute_key == _CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE:
87+
elif attribute_key == ResourceAttributes.CLOUD_RESOURCE_ID:
8588
ams_value = metadata_json["resourceId"]
8689
elif attribute_key == ResourceAttributes.HOST_ID or \
8790
attribute_key == ResourceAttributes.SERVICE_INSTANCE_ID:

0 commit comments

Comments
 (0)