Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@
import os
import sys
import threading
import warnings
from contextlib import contextmanager
from copy import deepcopy
from platform import python_implementation
from typing import Any, Collection, Iterable

Expand Down Expand Up @@ -149,7 +152,7 @@
"process.runtime.context_switches": ["involuntary", "voluntary"],
}

if sys.platform == "darwin":
if psutil.MACOS:
# see https://github.com/giampaolo/psutil/issues/1219
_DEFAULT_CONFIG.pop("system.network.connections")

Expand All @@ -161,14 +164,49 @@ def __init__(
config: dict[str, list[str] | None] | None = None,
):
super().__init__()
if config is None:
self._config = _DEFAULT_CONFIG
else:
self._config = config

self._config = deepcopy(_DEFAULT_CONFIG if config is None else config)
self._labels = {} if labels is None else labels
self._meter = None
self._python_implementation = python_implementation().lower()

# If 'system.network.connections' is found in the config at this time,
# then the user chose to explicitly add it themselves.
# We therefore remove the metric and issue a warning.
if psutil.MACOS and "system.network.connections" in self._config:
_logger.warning(
"'psutil.net_connections' can not reliably be computed on macOS! "
"'system.network.connections' will be excluded from metrics."
)
self._config.pop("system.network.connections")

# Filter 'sin' and 'sout' from 'system.swap' metrics
# if '/proc/vmstat' is not available and issue a warning.
# See: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3740.
if psutil.LINUX and (
"system.swap.usage" in self._config
or "system.swap.utilization" in self._config
):
vmstat = os.path.join(psutil.PROCFS_PATH, "vmstat")
if not os.path.exists(vmstat):
_logger.warning(
"Could not find '%s'! The 'sin' and 'sout' states"
"will not be included in 'system.swap' metrics.",
vmstat,
)
if usage := self._config.get("system.swap.usage"):
self._config["system.swap.usage"] = [
state
for state in usage
if state not in ("sin", "sout")
]
if utilization := self._config.get("system.swap.utilization"):
self._config["system.swap.utilization"] = [
state
for state in utilization
if state not in ("sin", "sout")
]

self._proc = psutil.Process(os.getpid())

self._system_cpu_time_labels = self._labels.copy()
Expand Down Expand Up @@ -628,7 +666,8 @@ def _get_system_swap_usage(
self, options: CallbackOptions
) -> Iterable[Observation]:
"""Observer callback for swap usage"""
system_swap = psutil.swap_memory()
with self._suppress_psutil_swap_warnings():
system_swap = psutil.swap_memory()

for metric in self._config["system.swap.usage"]:
self._system_swap_usage_labels["state"] = metric
Expand All @@ -642,7 +681,8 @@ def _get_system_swap_utilization(
self, options: CallbackOptions
) -> Iterable[Observation]:
"""Observer callback for swap utilization"""
system_swap = psutil.swap_memory()
with self._suppress_psutil_swap_warnings():
system_swap = psutil.swap_memory()

for metric in self._config["system.swap.utilization"]:
if hasattr(system_swap, metric):
Expand Down Expand Up @@ -1001,3 +1041,17 @@ def _get_runtime_context_switches(
getattr(ctx_switches, metric),
self._runtime_context_switches_labels.copy(),
)

@staticmethod
@contextmanager
def _suppress_psutil_swap_warnings():
with warnings.catch_warnings():
warnings.filterwarnings(
action="ignore",
category=RuntimeWarning,
# language=regexp
message=r"^'sin' and 'sout' swap memory stats couldn't be determined and were set to 0",
# language=regexp
module=r"^psutil$",
)
yield
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import sys
from collections import namedtuple
from platform import python_implementation
from unittest import mock, skipIf
from tempfile import TemporaryDirectory
from unittest import mock, skipIf, skipUnless

from opentelemetry.instrumentation.system_metrics import (
_DEFAULT_CONFIG,
Expand Down Expand Up @@ -415,6 +416,38 @@ def test_system_swap_utilization(self, mock_swap_memory):
]
self._test_metrics("system.swap.utilization", expected)

@skipUnless(sys.platform == "linux", "Linux only")
def test_system_swap_states_removed_when_vmstat_missing(self):
with (
self.assertLogs(level="WARNING") as logwatcher,
TemporaryDirectory() as tmpdir,
mock.patch(
target="psutil.PROCFS_PATH",
new=tmpdir,
create=True,
),
):
runtime_config = {
"system.swap.usage": ["free", "sin", "sout", "used"],
"system.swap.utilization": ["free", "sin", "sout", "used"],
}

runtime_metrics = SystemMetricsInstrumentor(config=runtime_config)
runtime_metrics.instrument()

self.assertEqual(
first=len(logwatcher.records),
second=1,
)
self.assertListEqual(
list1=runtime_metrics._config["system.swap.usage"],
list2=["free", "used"],
)
self.assertListEqual(
list1=runtime_metrics._config["system.swap.utilization"],
list2=["free", "used"],
)

@mock.patch("psutil.disk_io_counters")
def test_system_disk_io(self, mock_disk_io_counters):
DiskIO = namedtuple(
Expand Down