Skip to content

Commit ee3e96e

Browse files
committed
feat: Add adaptive poll rate interval logic
Signed-off-by: Cagri Yonca <cagri@ibm.com>
1 parent 6407963 commit ee3e96e

4 files changed

Lines changed: 178 additions & 0 deletions

File tree

src/instana/collector/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(self, agent: Type["BaseAgent"]) -> None:
5858
self.background_report_lock = threading.RLock()
5959

6060
# Reporting interval for the background thread(s)
61+
# Default is 1 but can be changed by the agent options
6162
self.report_interval = 1
6263

6364
# Flag to indicate if start/shutdown state

src/instana/collector/host.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ def start(self) -> None:
3535
)
3636
return
3737

38+
# Update report_interval from agent options poll_rate
39+
if hasattr(self.agent, "options") and hasattr(self.agent.options, "poll_rate"):
40+
self.report_interval = self.agent.options.poll_rate
41+
logger.info(
42+
f"Collector started with poll_rate: {self.report_interval} second(s)"
43+
)
44+
3845
super(HostCollector, self).start()
3946

4047
def prepare_and_report_data(self) -> None:

src/instana/options.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,15 @@ class StandardOptions(BaseOptions):
351351

352352
AGENT_DEFAULT_HOST = "localhost"
353353
AGENT_DEFAULT_PORT = 42699
354+
DEFAULT_POLL_RATE = 1
355+
MAX_POLL_RATE = 5
354356

355357
def __init__(self, **kwds: Dict[str, Any]) -> None:
356358
super(StandardOptions, self).__init__()
357359

358360
self.agent_host = os.environ.get("INSTANA_AGENT_HOST", self.AGENT_DEFAULT_HOST)
359361
self.agent_port = os.environ.get("INSTANA_AGENT_PORT", self.AGENT_DEFAULT_PORT)
362+
self.poll_rate = self.DEFAULT_POLL_RATE
360363

361364
if not isinstance(self.agent_port, int):
362365
self.agent_port = int(self.agent_port)
@@ -516,6 +519,32 @@ def set_from(self, res_data: Dict[str, Any]) -> None:
516519
logger.debug(f"options.set_from: Wrong data type - {type(res_data)}")
517520
return
518521

522+
# Extract poll_rate from plugin.python.poll_rate
523+
if "plugin" in res_data and isinstance(res_data["plugin"], dict):
524+
if "python" in res_data["plugin"] and isinstance(
525+
res_data["plugin"]["python"], dict
526+
):
527+
poll_rate_value = res_data["plugin"]["python"].get("poll_rate")
528+
if poll_rate_value is not None:
529+
try:
530+
poll_rate = int(poll_rate_value)
531+
# Validate: only 1 or 5 are allowed
532+
if poll_rate in (self.DEFAULT_POLL_RATE, self.MAX_POLL_RATE):
533+
self.poll_rate = poll_rate
534+
logger.debug(
535+
f"Poll rate set to {self.poll_rate} seconds from agent configuration"
536+
)
537+
else:
538+
logger.debug(
539+
f"Invalid poll_rate value {poll_rate}, defaulting to {self.DEFAULT_POLL_RATE}"
540+
)
541+
self.poll_rate = self.DEFAULT_POLL_RATE
542+
except (ValueError, TypeError):
543+
logger.debug(
544+
f"Invalid poll_rate type, defaulting to {self.DEFAULT_POLL_RATE}"
545+
)
546+
self.poll_rate = self.DEFAULT_POLL_RATE
547+
519548
if "secrets" in res_data:
520549
self.set_secrets(res_data["secrets"])
521550

tests/test_options.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,147 @@ def test_set_from_bool(
10031003
assert self.standart_options.span_filters == {"exclude": INTERNAL_SPAN_FILTERS}
10041004
assert not self.standart_options.extra_http_headers
10051005

1006+
def test_default_poll_rate(self) -> None:
1007+
"""Test that default poll_rate is 1 second"""
1008+
self.standart_options = StandardOptions()
1009+
assert self.standart_options.poll_rate == 1
1010+
1011+
def test_set_from_with_valid_poll_rate_1(
1012+
self,
1013+
caplog: pytest.LogCaptureFixture,
1014+
) -> None:
1015+
"""Test setting poll_rate to 1 from announce response"""
1016+
caplog.set_level(logging.DEBUG, logger="instana")
1017+
caplog.clear()
1018+
1019+
self.standart_options = StandardOptions()
1020+
test_res_data = {"plugin": {"python": {"poll_rate": 1}}}
1021+
self.standart_options.set_from(test_res_data)
1022+
1023+
assert self.standart_options.poll_rate == 1
1024+
assert "Poll rate set to 1 seconds from agent configuration" in caplog.messages
1025+
1026+
def test_set_from_with_valid_poll_rate_5(
1027+
self,
1028+
caplog: pytest.LogCaptureFixture,
1029+
) -> None:
1030+
"""Test setting poll_rate to 5 from announce response"""
1031+
caplog.set_level(logging.DEBUG, logger="instana")
1032+
caplog.clear()
1033+
1034+
self.standart_options = StandardOptions()
1035+
test_res_data = {"plugin": {"python": {"poll_rate": 5}}}
1036+
self.standart_options.set_from(test_res_data)
1037+
1038+
assert self.standart_options.poll_rate == 5
1039+
assert "Poll rate set to 5 seconds from agent configuration" in caplog.messages
1040+
1041+
def test_set_from_with_invalid_poll_rate_defaults_to_1(
1042+
self,
1043+
caplog: pytest.LogCaptureFixture,
1044+
) -> None:
1045+
"""Test that invalid poll_rate values default to 1"""
1046+
caplog.set_level(logging.DEBUG, logger="instana")
1047+
1048+
# Test with poll_rate = 10
1049+
caplog.clear()
1050+
self.standart_options = StandardOptions()
1051+
test_res_data = {"plugin": {"python": {"poll_rate": 10}}}
1052+
self.standart_options.set_from(test_res_data)
1053+
assert self.standart_options.poll_rate == 1
1054+
assert "Invalid poll_rate value 10, defaulting to 1" in caplog.messages
1055+
1056+
# Test with poll_rate = 0
1057+
caplog.clear()
1058+
self.standart_options = StandardOptions()
1059+
test_res_data = {"plugin": {"python": {"poll_rate": 0}}}
1060+
self.standart_options.set_from(test_res_data)
1061+
assert self.standart_options.poll_rate == 1
1062+
assert "Invalid poll_rate value 0, defaulting to 1" in caplog.messages
1063+
1064+
# Test with poll_rate = -5
1065+
caplog.clear()
1066+
self.standart_options = StandardOptions()
1067+
test_res_data = {"plugin": {"python": {"poll_rate": -5}}}
1068+
self.standart_options.set_from(test_res_data)
1069+
assert self.standart_options.poll_rate == 1
1070+
assert "Invalid poll_rate value -5, defaulting to 1" in caplog.messages
1071+
1072+
# Test with poll_rate = 3
1073+
caplog.clear()
1074+
self.standart_options = StandardOptions()
1075+
test_res_data = {"plugin": {"python": {"poll_rate": 3}}}
1076+
self.standart_options.set_from(test_res_data)
1077+
assert self.standart_options.poll_rate == 1
1078+
assert "Invalid poll_rate value 3, defaulting to 1" in caplog.messages
1079+
1080+
def test_set_from_with_invalid_poll_rate_type(
1081+
self,
1082+
caplog: pytest.LogCaptureFixture,
1083+
) -> None:
1084+
"""Test that non-integer poll_rate values default to 1"""
1085+
caplog.set_level(logging.DEBUG, logger="instana")
1086+
1087+
# Test with string
1088+
caplog.clear()
1089+
self.standart_options = StandardOptions()
1090+
test_res_data = {"plugin": {"python": {"poll_rate": "invalid"}}}
1091+
self.standart_options.set_from(test_res_data)
1092+
assert self.standart_options.poll_rate == 1
1093+
assert "Invalid poll_rate type, defaulting to 1" in caplog.messages
1094+
1095+
# Test with None
1096+
caplog.clear()
1097+
self.standart_options = StandardOptions()
1098+
test_res_data = {"plugin": {"python": {"poll_rate": None}}}
1099+
self.standart_options.set_from(test_res_data)
1100+
assert self.standart_options.poll_rate == 1
1101+
1102+
def test_set_from_without_poll_rate(self) -> None:
1103+
"""Test that poll_rate remains default when not in response"""
1104+
self.standart_options = StandardOptions()
1105+
test_res_data = {
1106+
"secrets": {"matcher": "sample-match", "list": ["sample", "list"]}
1107+
}
1108+
self.standart_options.set_from(test_res_data)
1109+
assert self.standart_options.poll_rate == 1
1110+
1111+
def test_set_from_with_poll_rate_and_other_config(
1112+
self,
1113+
caplog: pytest.LogCaptureFixture,
1114+
) -> None:
1115+
"""Test that poll_rate works alongside other configuration"""
1116+
caplog.set_level(logging.DEBUG, logger="instana")
1117+
caplog.clear()
1118+
1119+
self.standart_options = StandardOptions()
1120+
test_res_data = {
1121+
"plugin": {"python": {"poll_rate": 5}},
1122+
"secrets": {"matcher": "sample-match", "list": ["sample", "list"]},
1123+
"tracing": {
1124+
"filter": {
1125+
"exclude": [
1126+
{
1127+
"name": "service1",
1128+
"attributes": [
1129+
{
1130+
"key": "service",
1131+
"values": ["service1"],
1132+
"match_type": "strict",
1133+
}
1134+
],
1135+
}
1136+
]
1137+
}
1138+
},
1139+
}
1140+
self.standart_options.set_from(test_res_data)
1141+
1142+
assert self.standart_options.poll_rate == 5
1143+
assert self.standart_options.secrets_matcher == "sample-match"
1144+
assert self.standart_options.secrets_list == ["sample", "list"]
1145+
assert "Poll rate set to 5 seconds from agent configuration" in caplog.messages
1146+
10061147

10071148
class TestServerlessOptions:
10081149
@pytest.fixture(autouse=True)

0 commit comments

Comments
 (0)