Skip to content

Commit ed4c1fb

Browse files
committed
test(collector): add tests for cross-platform resource metrics
Signed-off-by: Paulo Vital <[email protected]>
1 parent 52db86b commit ed4c1fb

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-0
lines changed

tests/agent/test_host.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from instana.singletons import get_agent
2121
from instana.span.span import InstanaSpan
2222
from instana.span_context import SpanContext
23+
from instana.util.runtime import is_windows
2324

2425

2526
class TestHostAgent:
@@ -279,6 +280,10 @@ def test_agent_connection_attempt_fails_with_404(
279280
assert not result
280281
assert msg in caplog.messages[0]
281282

283+
@pytest.mark.skipif(
284+
is_windows(),
285+
reason='Avoiding "psutil.NoSuchProcess: process PID not found (pid=12345)"',
286+
)
282287
def test_init(self) -> None:
283288
with patch(
284289
"instana.agent.base.BaseAgent.update_log_level"

tests/collector/helpers/test_collector_runtime.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77

88
from instana.agent.host import HostAgent
9+
from instana.collector.helpers.resource_usage import ResourceUsage
910
from instana.collector.helpers.runtime import RuntimeHelper
1011
from instana.collector.host import HostCollector
1112

@@ -66,3 +67,122 @@ def test_collect_gc_metrics(self) -> None:
6667

6768
self.helper._collect_gc_metrics(plugin_data[0], True)
6869
assert len(self.helper.previous["data"]["metrics"]["gc"]) == 6
70+
71+
def test_collect_runtime_metrics(self) -> None:
72+
"""Test that _collect_runtime_metrics properly collects metrics"""
73+
plugin_data = self.helper.collect_metrics()
74+
75+
# Call the method directly
76+
self.helper._collect_runtime_metrics(plugin_data[0], True)
77+
78+
# Verify metrics were collected
79+
assert "metrics" in plugin_data[0]["data"]
80+
metrics = plugin_data[0]["data"]["metrics"]
81+
82+
# Check that resource usage metrics are present
83+
assert "ru_utime" in metrics
84+
assert "ru_stime" in metrics
85+
assert "ru_maxrss" in metrics
86+
assert "ru_minflt" in metrics
87+
assert "ru_majflt" in metrics
88+
89+
# Check that thread metrics are present
90+
assert "daemon_threads" in metrics
91+
assert "alive_threads" in metrics
92+
assert "dummy_threads" in metrics
93+
94+
def test_runtime_helper_initialization_with_resource_usage(self, mocker):
95+
"""Test that RuntimeHelper initializes with resource_usage"""
96+
mock_resource = ResourceUsage(
97+
ru_utime=1.0,
98+
ru_stime=2.0,
99+
ru_maxrss=3,
100+
)
101+
mocker.patch(
102+
"instana.collector.helpers.runtime.get_resource_usage",
103+
return_value=mock_resource,
104+
)
105+
106+
helper = RuntimeHelper(collector=HostCollector(HostAgent()))
107+
108+
assert helper.previous_rusage == mock_resource
109+
assert helper.previous_rusage.ru_utime == 1.0
110+
assert helper.previous_rusage.ru_stime == 2.0
111+
assert helper.previous_rusage.ru_maxrss == 3
112+
113+
def test_collect_runtime_metrics_with_resource_usage(self, mocker):
114+
"""Test that _collect_runtime_metrics uses resource_usage correctly"""
115+
# Setup initial state
116+
initial_resource = ResourceUsage(
117+
ru_utime=1.0,
118+
ru_stime=2.0,
119+
ru_maxrss=3000,
120+
ru_minflt=100,
121+
ru_majflt=10,
122+
ru_nswap=5,
123+
ru_inblock=200,
124+
ru_oublock=300,
125+
ru_msgsnd=10,
126+
ru_msgrcv=20,
127+
ru_nsignals=1,
128+
ru_nvcsw=1000,
129+
ru_nivcsw=500,
130+
)
131+
self.helper.previous_rusage = initial_resource
132+
133+
# Setup new resource usage values with increments
134+
new_resource = ResourceUsage(
135+
ru_utime=1.5, # +0.5
136+
ru_stime=3.0, # +1.0
137+
ru_maxrss=4000, # +1000
138+
ru_minflt=150, # +50
139+
ru_majflt=15, # +5
140+
ru_nswap=7, # +2
141+
ru_inblock=250, # +50
142+
ru_oublock=350, # +50
143+
ru_msgsnd=15, # +5
144+
ru_msgrcv=25, # +5
145+
ru_nsignals=3, # +2
146+
ru_nvcsw=1200, # +200
147+
ru_nivcsw=600, # +100
148+
)
149+
mocker.patch(
150+
"instana.collector.helpers.runtime.get_resource_usage",
151+
return_value=new_resource,
152+
)
153+
154+
# Call the method
155+
plugin_data = {"data": {"metrics": {}}}
156+
self.helper._collect_runtime_metrics(plugin_data, True)
157+
158+
# Verify metrics were collected with correct deltas
159+
metrics = plugin_data["data"]["metrics"]
160+
assert metrics["ru_utime"] == 0.5 # Difference between new and old
161+
assert metrics["ru_stime"] == 1.0
162+
assert metrics["ru_maxrss"] == 4000 # This is absolute, not a delta
163+
assert metrics["ru_minflt"] == 50
164+
assert metrics["ru_majflt"] == 5
165+
assert metrics["ru_nswap"] == 2
166+
assert metrics["ru_inblock"] == 50
167+
assert metrics["ru_oublock"] == 50
168+
assert metrics["ru_msgsnd"] == 5
169+
assert metrics["ru_msgrcv"] == 5
170+
assert metrics["ru_nsignals"] == 2
171+
assert metrics["ru_nvcsw"] == 200
172+
assert metrics["ru_nivcsw"] == 100
173+
174+
# Verify the previous_rusage was updated
175+
assert self.helper.previous_rusage == new_resource
176+
177+
@patch("os.environ")
178+
def test_collect_runtime_metrics_disabled(self, mock_environ):
179+
"""Test that _collect_runtime_metrics respects INSTANA_DISABLE_METRICS_COLLECTION"""
180+
# Setup environment variable
181+
mock_environ.get.return_value = True
182+
183+
# Call the method
184+
plugin_data = {"data": {"metrics": {}}}
185+
self.helper._collect_runtime_metrics(plugin_data, True)
186+
187+
# Verify no metrics were collected
188+
assert plugin_data["data"]["metrics"] == {}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
4+
import pytest
5+
6+
from instana.collector.helpers.resource_usage import (
7+
ResourceUsage,
8+
_get_unix_resource_usage,
9+
_get_windows_resource_usage,
10+
get_resource_usage,
11+
)
12+
from instana.util.runtime import is_windows
13+
14+
15+
class TestResourceUsage:
16+
def test_resource_usage_namedtuple_defaults(self):
17+
"""Test that ResourceUsage has proper default values"""
18+
usage = ResourceUsage()
19+
assert usage.ru_utime == 0.0
20+
assert usage.ru_stime == 0.0
21+
assert usage.ru_maxrss == 0
22+
assert usage.ru_ixrss == 0
23+
assert usage.ru_idrss == 0
24+
assert usage.ru_isrss == 0
25+
assert usage.ru_minflt == 0
26+
assert usage.ru_majflt == 0
27+
assert usage.ru_nswap == 0
28+
assert usage.ru_inblock == 0
29+
assert usage.ru_oublock == 0
30+
assert usage.ru_msgsnd == 0
31+
assert usage.ru_msgrcv == 0
32+
assert usage.ru_nsignals == 0
33+
assert usage.ru_nvcsw == 0
34+
assert usage.ru_nivcsw == 0
35+
36+
def test_resource_usage_namedtuple_custom_values(self):
37+
"""Test that ResourceUsage can be initialized with custom values"""
38+
usage = ResourceUsage(
39+
ru_utime=1.0,
40+
ru_stime=2.0,
41+
ru_maxrss=3,
42+
ru_ixrss=4,
43+
ru_idrss=5,
44+
ru_isrss=6,
45+
ru_minflt=7,
46+
ru_majflt=8,
47+
ru_nswap=9,
48+
ru_inblock=10,
49+
ru_oublock=11,
50+
ru_msgsnd=12,
51+
ru_msgrcv=13,
52+
ru_nsignals=14,
53+
ru_nvcsw=15,
54+
ru_nivcsw=16,
55+
)
56+
assert usage.ru_utime == 1.0
57+
assert usage.ru_stime == 2.0
58+
assert usage.ru_maxrss == 3
59+
assert usage.ru_ixrss == 4
60+
assert usage.ru_idrss == 5
61+
assert usage.ru_isrss == 6
62+
assert usage.ru_minflt == 7
63+
assert usage.ru_majflt == 8
64+
assert usage.ru_nswap == 9
65+
assert usage.ru_inblock == 10
66+
assert usage.ru_oublock == 11
67+
assert usage.ru_msgsnd == 12
68+
assert usage.ru_msgrcv == 13
69+
assert usage.ru_nsignals == 14
70+
assert usage.ru_nvcsw == 15
71+
assert usage.ru_nivcsw == 16
72+
73+
@pytest.mark.skipif(
74+
is_windows(),
75+
reason="Avoiding Unix resource usage collection on Windows systems.",
76+
)
77+
def test_get_resource_usage_unix(self):
78+
"""Test that get_resource_usage calls _get_unix_resource_usage on Unix-like systems."""
79+
usage = get_resource_usage()
80+
81+
assert usage.ru_utime >= 0.0
82+
assert usage.ru_stime >= 0.0
83+
assert usage.ru_maxrss >= 0
84+
assert usage.ru_ixrss >= 0
85+
assert usage.ru_idrss >= 0
86+
assert usage.ru_isrss >= 0
87+
assert usage.ru_minflt >= 0
88+
assert usage.ru_majflt >= 0
89+
assert usage.ru_nswap >= 0
90+
assert usage.ru_inblock >= 0
91+
assert usage.ru_oublock >= 0
92+
assert usage.ru_msgsnd >= 0
93+
assert usage.ru_msgrcv >= 0
94+
assert usage.ru_nsignals >= 0
95+
assert usage.ru_nvcsw >= 0
96+
assert usage.ru_nivcsw >= 0
97+
98+
@pytest.mark.skipif(
99+
not is_windows(),
100+
reason="Avoiding Windows resource usage collection on Unix-like systems.",
101+
)
102+
def test_get_resource_usage_windows(self):
103+
"""Test that get_resource_usage calls _get_windows_resource_usage on Windows systems"""
104+
usage = get_resource_usage()
105+
106+
assert usage.ru_utime >= 0.0
107+
assert usage.ru_stime >= 0.0
108+
assert usage.ru_maxrss >= 0
109+
assert usage.ru_ixrss == 0
110+
assert usage.ru_idrss == 0
111+
assert usage.ru_isrss == 0
112+
assert usage.ru_minflt == 0
113+
assert usage.ru_majflt == 0
114+
assert usage.ru_nswap == 0
115+
assert usage.ru_inblock >= 0
116+
assert usage.ru_oublock >= 0
117+
assert usage.ru_msgsnd == 0
118+
assert usage.ru_msgrcv == 0
119+
assert usage.ru_nsignals == 0
120+
assert usage.ru_nvcsw >= 0
121+
assert usage.ru_nivcsw >= 0
122+
123+
@pytest.mark.skipif(
124+
is_windows(),
125+
reason="Avoiding Unix resource usage collection on Windows. systems",
126+
)
127+
def test_get_unix_resource_usage(self):
128+
"""Test _get_unix_resource_usage function"""
129+
usage = _get_unix_resource_usage()
130+
131+
assert usage.ru_utime >= 0.0
132+
assert usage.ru_stime >= 0.0
133+
assert usage.ru_maxrss >= 0
134+
assert usage.ru_ixrss >= 0
135+
assert usage.ru_idrss >= 0
136+
assert usage.ru_isrss >= 0
137+
assert usage.ru_minflt >= 0
138+
assert usage.ru_majflt >= 0
139+
assert usage.ru_nswap >= 0
140+
assert usage.ru_inblock >= 0
141+
assert usage.ru_oublock >= 0
142+
assert usage.ru_msgsnd >= 0
143+
assert usage.ru_msgrcv >= 0
144+
assert usage.ru_nsignals >= 0
145+
assert usage.ru_nvcsw >= 0
146+
assert usage.ru_nivcsw >= 0
147+
148+
@pytest.mark.skipif(
149+
not is_windows(),
150+
reason="Avoiding Windows resource usage collection on Unix-like systems.",
151+
)
152+
def test_get_windows_resource_usage_with_psutil(self):
153+
"""Test _get_windows_resource_usage function with psutil available"""
154+
usage = _get_windows_resource_usage()
155+
156+
assert usage.ru_utime >= 0.0
157+
assert usage.ru_stime >= 0.0
158+
assert usage.ru_maxrss >= 0
159+
assert usage.ru_ixrss == 0
160+
assert usage.ru_idrss == 0
161+
assert usage.ru_isrss == 0
162+
assert usage.ru_minflt == 0
163+
assert usage.ru_majflt == 0
164+
assert usage.ru_nswap == 0
165+
assert usage.ru_inblock >= 0
166+
assert usage.ru_oublock >= 0
167+
assert usage.ru_msgsnd == 0
168+
assert usage.ru_msgrcv == 0
169+
assert usage.ru_nsignals == 0
170+
assert usage.ru_nvcsw >= 0
171+
assert usage.ru_nivcsw >= 0
172+
173+
@pytest.mark.skipif(
174+
not is_windows(),
175+
reason="Avoiding Windows resource usage collection on Unix-like systems.",
176+
)
177+
def test_get_windows_resource_usage_without_psutil(self, mocker):
178+
"""Test _get_windows_resource_usage function when psutil is not available"""
179+
180+
mocker.patch("psutil.Process", side_effect=ImportError)
181+
result = _get_windows_resource_usage()
182+
183+
# Should return default ResourceUsage with all zeros
184+
assert result.ru_utime == 0.0
185+
assert result.ru_stime == 0.0
186+
assert result.ru_maxrss == 0
187+
assert result.ru_ixrss == 0
188+
assert result.ru_idrss == 0
189+
assert result.ru_isrss == 0
190+
assert result.ru_minflt == 0
191+
assert result.ru_majflt == 0
192+
assert result.ru_nswap == 0
193+
assert result.ru_inblock == 0
194+
assert result.ru_oublock == 0
195+
assert result.ru_msgsnd == 0
196+
assert result.ru_msgrcv == 0
197+
assert result.ru_nsignals == 0
198+
assert result.ru_nvcsw == 0
199+
assert result.ru_nivcsw == 0
200+
201+
202+
# Made with Bob

0 commit comments

Comments
 (0)