Skip to content

Commit 3c4b118

Browse files
committed
feat(collector): add Windows platform support for resource metrics
This change adds cross-platform resource usage monitoring with Windows support: - Create new resource_usage.py module with platform-specific implementations - Add psutil dependency for Windows systems in pyproject.toml - Refactor runtime.py to use the new cross-platform resource usage functions - Implement ResourceUsage class that provides consistent interface across platforms The implementation gracefully handles platform differences, ensuring consistent metrics collection on both Unix and Windows environments. Signed-off-by: Paulo Vital <[email protected]>
1 parent f52b206 commit 3c4b118

File tree

3 files changed

+133
-6
lines changed

3 files changed

+133
-6
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ dependencies = [
5353
"opentelemetry-semantic-conventions>=0.48b0",
5454
"typing_extensions>=4.12.2",
5555
"pyyaml>=6.0.2",
56-
"setuptools>=69.0.0; python_version >= \"3.12\"",
56+
"setuptools>=69.0.0; python_version >= \"3.12\"",
57+
"psutil>=5.9.0; sys_platform == \"win32\"",
5758
]
5859

5960
[project.entry-points."instana"]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
"""Cross-platform resource usage information"""
4+
5+
import os
6+
from typing import NamedTuple
7+
8+
from instana.log import logger
9+
from instana.util.runtime import is_windows
10+
11+
12+
class ResourceUsage(NamedTuple):
13+
"""Cross-platform resource usage information"""
14+
15+
ru_utime: float = 0.0
16+
ru_stime: float = 0.0
17+
ru_maxrss: int = 0
18+
ru_ixrss: int = 0
19+
ru_idrss: int = 0
20+
ru_isrss: int = 0
21+
ru_minflt: int = 0
22+
ru_majflt: int = 0
23+
ru_nswap: int = 0
24+
ru_inblock: int = 0
25+
ru_oublock: int = 0
26+
ru_msgsnd: int = 0
27+
ru_msgrcv: int = 0
28+
ru_nsignals: int = 0
29+
ru_nvcsw: int = 0
30+
ru_nivcsw: int = 0
31+
32+
33+
def get_resource_usage() -> ResourceUsage:
34+
"""Get resource usage in a cross-platform way"""
35+
if is_windows():
36+
return _get_windows_resource_usage()
37+
else:
38+
return _get_unix_resource_usage()
39+
40+
41+
def _get_unix_resource_usage() -> ResourceUsage:
42+
"""Get resource usage on Unix systems"""
43+
import resource
44+
45+
rusage = resource.getrusage(resource.RUSAGE_SELF)
46+
47+
return ResourceUsage(
48+
ru_utime=rusage.ru_utime,
49+
ru_stime=rusage.ru_stime,
50+
ru_maxrss=rusage.ru_maxrss,
51+
ru_ixrss=rusage.ru_ixrss,
52+
ru_idrss=rusage.ru_idrss,
53+
ru_isrss=rusage.ru_isrss,
54+
ru_minflt=rusage.ru_minflt,
55+
ru_majflt=rusage.ru_majflt,
56+
ru_nswap=rusage.ru_nswap,
57+
ru_inblock=rusage.ru_inblock,
58+
ru_oublock=rusage.ru_oublock,
59+
ru_msgsnd=rusage.ru_msgsnd,
60+
ru_msgrcv=rusage.ru_msgrcv,
61+
ru_nsignals=rusage.ru_nsignals,
62+
ru_nvcsw=rusage.ru_nvcsw,
63+
ru_nivcsw=rusage.ru_nivcsw,
64+
)
65+
66+
67+
def _get_windows_resource_usage() -> ResourceUsage:
68+
"""Get resource usage on Windows systems"""
69+
# On Windows, we can use psutil to get some of the metrics
70+
# For metrics that aren't available, we return 0
71+
try:
72+
import psutil
73+
74+
process = psutil.Process(os.getpid())
75+
76+
# Get CPU times
77+
cpu_times = process.cpu_times()
78+
79+
# Get memory info
80+
memory_info = process.memory_info()
81+
82+
# Get IO counters
83+
io_counters = process.io_counters() if hasattr(process, "io_counters") else None
84+
85+
# Get context switch counts if available
86+
ctx_switches = (
87+
process.num_ctx_switches() if hasattr(process, "num_ctx_switches") else None
88+
)
89+
90+
return ResourceUsage(
91+
ru_utime=cpu_times.user if hasattr(cpu_times, "user") else 0.0,
92+
ru_stime=cpu_times.system if hasattr(cpu_times, "system") else 0.0,
93+
ru_maxrss=memory_info.rss // 1024
94+
if hasattr(memory_info, "rss")
95+
else 0, # Convert to KB to match Unix
96+
ru_ixrss=0, # Not available on Windows
97+
ru_idrss=0, # Not available on Windows
98+
ru_isrss=0, # Not available on Windows
99+
ru_minflt=0, # Not directly available on Windows
100+
ru_majflt=0, # Not directly available on Windows
101+
ru_nswap=0, # Not available on Windows
102+
ru_inblock=io_counters.read_count
103+
if io_counters and hasattr(io_counters, "read_count")
104+
else 0,
105+
ru_oublock=io_counters.write_count
106+
if io_counters and hasattr(io_counters, "write_count")
107+
else 0,
108+
ru_msgsnd=0, # Not available on Windows
109+
ru_msgrcv=0, # Not available on Windows
110+
ru_nsignals=0, # Not available on Windows
111+
ru_nvcsw=ctx_switches.voluntary
112+
if ctx_switches and hasattr(ctx_switches, "voluntary")
113+
else 0,
114+
ru_nivcsw=ctx_switches.involuntary
115+
if ctx_switches and hasattr(ctx_switches, "involuntary")
116+
else 0,
117+
)
118+
except ImportError:
119+
# If psutil is not available, return zeros
120+
logger.debug(
121+
"get_windows_resource_usage: psutil is not available, returning zeros"
122+
)
123+
return ResourceUsage()
124+
125+
126+
# Made with Bob

src/instana/collector/helpers/runtime.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
import importlib.metadata
88
import os
99
import platform
10-
import resource
1110
import sys
1211
import threading
1312
from types import ModuleType
14-
from typing import Any, Dict, List, Union, Callable
13+
from typing import Any, Callable, Dict, List, Union
1514

15+
from instana.collector.base import BaseCollector
1616
from instana.collector.helpers.base import BaseHelper
17+
from instana.collector.helpers.resource_usage import get_resource_usage
1718
from instana.log import logger
1819
from instana.util import DictionaryOfStan
1920
from instana.util.runtime import determine_service_name
2021
from instana.version import VERSION
21-
from instana.collector.base import BaseCollector
2222

2323
PATH_OF_DEPRECATED_INSTALLATION_VIA_HOST_AGENT = "/tmp/.instana/python"
2424

@@ -42,7 +42,7 @@ def __init__(
4242
) -> None:
4343
super(RuntimeHelper, self).__init__(collector)
4444
self.previous = DictionaryOfStan()
45-
self.previous_rusage = resource.getrusage(resource.RUSAGE_SELF)
45+
self.previous_rusage = get_resource_usage()
4646

4747
if gc.isenabled():
4848
self.previous_gc_count = gc.get_count()
@@ -83,7 +83,7 @@ def _collect_runtime_metrics(
8383

8484
""" Collect up and return the runtime metrics """
8585
try:
86-
rusage = resource.getrusage(resource.RUSAGE_SELF)
86+
rusage = get_resource_usage()
8787
if gc.isenabled():
8888
self._collect_gc_metrics(plugin_data, with_snapshot)
8989

0 commit comments

Comments
 (0)