Skip to content

Commit 4cb4918

Browse files
pvitalGSVarsha
andcommitted
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. Co-authored-by: Varsha GS <[email protected]> Signed-off-by: Paulo Vital <[email protected]>
1 parent f52b206 commit 4cb4918

File tree

3 files changed

+153
-6
lines changed

3 files changed

+153
-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: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
"""
14+
Cross-platform resource usage information, mirroring fields found in the Unix rusage struct.
15+
16+
Attributes:
17+
ru_utime (float): User CPU time used (seconds).
18+
ru_stime (float): System CPU time used (seconds).
19+
ru_maxrss (int): Maximum resident set size used (bytes).
20+
ru_ixrss (int): Integral shared memory size (bytes).
21+
ru_idrss (int): Integral unshared data size (bytes).
22+
ru_isrss (int): Integral unshared stack size (bytes).
23+
ru_minflt (int): Number of page reclaims (soft page faults).
24+
ru_majflt (int): Number of page faults requiring I/O (hard page faults).
25+
ru_nswap (int): Number of times a process was swapped out.
26+
ru_inblock (int): Number of file system input blocks.
27+
ru_oublock (int): Number of file system output blocks.
28+
ru_msgsnd (int): Number of messages sent.
29+
ru_msgrcv (int): Number of messages received.
30+
ru_nsignals (int): Number of signals received.
31+
ru_nvcsw (int): Number of voluntary context switches.
32+
ru_nivcsw (int): Number of involuntary context switches.
33+
"""
34+
35+
ru_utime: float = 0.0
36+
ru_stime: float = 0.0
37+
ru_maxrss: int = 0
38+
ru_ixrss: int = 0
39+
ru_idrss: int = 0
40+
ru_isrss: int = 0
41+
ru_minflt: int = 0
42+
ru_majflt: int = 0
43+
ru_nswap: int = 0
44+
ru_inblock: int = 0
45+
ru_oublock: int = 0
46+
ru_msgsnd: int = 0
47+
ru_msgrcv: int = 0
48+
ru_nsignals: int = 0
49+
ru_nvcsw: int = 0
50+
ru_nivcsw: int = 0
51+
52+
53+
def get_resource_usage() -> ResourceUsage:
54+
"""Get resource usage in a cross-platform way"""
55+
if is_windows():
56+
return _get_windows_resource_usage()
57+
else:
58+
return _get_unix_resource_usage()
59+
60+
61+
def _get_unix_resource_usage() -> ResourceUsage:
62+
"""Get resource usage on Unix systems"""
63+
import resource
64+
65+
rusage = resource.getrusage(resource.RUSAGE_SELF)
66+
67+
return ResourceUsage(
68+
ru_utime=rusage.ru_utime,
69+
ru_stime=rusage.ru_stime,
70+
ru_maxrss=rusage.ru_maxrss,
71+
ru_ixrss=rusage.ru_ixrss,
72+
ru_idrss=rusage.ru_idrss,
73+
ru_isrss=rusage.ru_isrss,
74+
ru_minflt=rusage.ru_minflt,
75+
ru_majflt=rusage.ru_majflt,
76+
ru_nswap=rusage.ru_nswap,
77+
ru_inblock=rusage.ru_inblock,
78+
ru_oublock=rusage.ru_oublock,
79+
ru_msgsnd=rusage.ru_msgsnd,
80+
ru_msgrcv=rusage.ru_msgrcv,
81+
ru_nsignals=rusage.ru_nsignals,
82+
ru_nvcsw=rusage.ru_nvcsw,
83+
ru_nivcsw=rusage.ru_nivcsw,
84+
)
85+
86+
87+
def _get_windows_resource_usage() -> ResourceUsage:
88+
"""Get resource usage on Windows systems"""
89+
# On Windows, we can use psutil to get some of the metrics
90+
# For metrics that aren't available, we return 0
91+
try:
92+
import psutil
93+
94+
process = psutil.Process(os.getpid())
95+
96+
# Get CPU times
97+
cpu_times = process.cpu_times()
98+
99+
# Get memory info
100+
memory_info = process.memory_info()
101+
102+
# Get IO counters
103+
io_counters = process.io_counters() if hasattr(process, "io_counters") else None
104+
105+
# Get context switch counts if available
106+
ctx_switches = (
107+
process.num_ctx_switches() if hasattr(process, "num_ctx_switches") else None
108+
)
109+
110+
return ResourceUsage(
111+
ru_utime=cpu_times.user if hasattr(cpu_times, "user") else 0.0,
112+
ru_stime=cpu_times.system if hasattr(cpu_times, "system") else 0.0,
113+
ru_maxrss=memory_info.rss // 1024
114+
if hasattr(memory_info, "rss")
115+
else 0, # Convert to KB to match Unix
116+
ru_ixrss=0, # Not available on Windows
117+
ru_idrss=0, # Not available on Windows
118+
ru_isrss=0, # Not available on Windows
119+
ru_minflt=0, # Not directly available on Windows
120+
ru_majflt=0, # Not directly available on Windows
121+
ru_nswap=0, # Not available on Windows
122+
ru_inblock=io_counters.read_count
123+
if io_counters and hasattr(io_counters, "read_count")
124+
else 0,
125+
ru_oublock=io_counters.write_count
126+
if io_counters and hasattr(io_counters, "write_count")
127+
else 0,
128+
ru_msgsnd=0, # Not available on Windows
129+
ru_msgrcv=0, # Not available on Windows
130+
ru_nsignals=0, # Not available on Windows
131+
ru_nvcsw=ctx_switches.voluntary
132+
if ctx_switches and hasattr(ctx_switches, "voluntary")
133+
else 0,
134+
ru_nivcsw=ctx_switches.involuntary
135+
if ctx_switches and hasattr(ctx_switches, "involuntary")
136+
else 0,
137+
)
138+
except ImportError:
139+
# If psutil is not available, return zeros
140+
logger.debug(
141+
"get_windows_resource_usage: psutil is not available, returning zeros"
142+
)
143+
return ResourceUsage()
144+
145+
146+
# 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)