Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .in-toto/tag.47c5a022.link

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions consul/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ files:

- name: acl_token
description: ACL token to use for authentication.
secret: true
value:
type: string

Expand Down
6 changes: 6 additions & 0 deletions datadog_checks_base/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

<!-- towncrier release notes start -->

## 37.17.1 / 2025-08-26

***Fixed***:

* Regularly check for cancel event between DBMAsyncJob check intervals ([#21150](https://github.com/DataDog/integrations-core/pull/21150))

## 37.17.0 / 2025-08-07

***Added***:
Expand Down
2 changes: 1 addition & 1 deletion datadog_checks_base/datadog_checks/base/__about__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) Datadog, Inc. 2018-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
__version__ = "37.17.0"
__version__ = "37.17.1"
30 changes: 22 additions & 8 deletions datadog_checks_base/datadog_checks/base/utils/db/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,33 @@ class ConstantRateLimiter:
Basic rate limiter that sleeps long enough to ensure the rate limit is not exceeded. Not thread safe.
"""

def __init__(self, rate_limit_s):
def __init__(self, rate_limit_s, max_sleep_chunk_s=5):
"""
:param rate_limit_s: rate limit in seconds
:param max_sleep_chunk_s: maximum size of each sleep chunk while waiting for the next period
"""
self.rate_limit_s = max(rate_limit_s, 0)
self.period_s = 1.0 / self.rate_limit_s if self.rate_limit_s > 0 else 0
self.last_event = 0
self.max_sleep_chunk_s = max(0, max_sleep_chunk_s)

def update_last_time_and_sleep(self):
def update_last_time_and_sleep(self, cancel_event: Optional[threading.Event] = None):
"""
Sleeps long enough to enforce the rate limit
"""
elapsed_s = time.time() - self.last_event
sleep_amount = max(self.period_s - elapsed_s, 0)
time.sleep(sleep_amount)
if self.period_s <= 0:
self.update_last_time()
return

deadline = self.last_event + self.period_s
while True:
now = time.time()
remaining = deadline - now
if remaining <= 0:
break
if cancel_event is not None and cancel_event.is_set():
break
time.sleep(min(remaining, self.max_sleep_chunk_s if self.max_sleep_chunk_s > 0 else remaining))
self.update_last_time()

def shall_execute(self):
Expand Down Expand Up @@ -275,6 +287,7 @@ def __init__(
min_collection_interval=15,
dbms="TODO",
rate_limit=1,
max_sleep_chunk_s=1,
run_sync=False,
enabled=True,
expected_db_exceptions=(),
Expand All @@ -295,7 +308,8 @@ def __init__(
self._last_check_run = 0
self._shutdown_callback = shutdown_callback
self._dbms = dbms
self._rate_limiter = ConstantRateLimiter(rate_limit)
self._rate_limiter = ConstantRateLimiter(rate_limit, max_sleep_chunk_s=max_sleep_chunk_s)
self._max_sleep_chunk_s = max_sleep_chunk_s
self._run_sync = run_sync
self._enabled = enabled
self._expected_db_exceptions = expected_db_exceptions
Expand Down Expand Up @@ -387,7 +401,7 @@ def _job_loop(self):

def _set_rate_limit(self, rate_limit):
if self._rate_limiter.rate_limit_s != rate_limit:
self._rate_limiter = ConstantRateLimiter(rate_limit)
self._rate_limiter = ConstantRateLimiter(rate_limit, max_sleep_chunk_s=self._max_sleep_chunk_s)

def _run_sync_job_rate_limited(self):
if self._rate_limiter.shall_execute():
Expand All @@ -401,7 +415,7 @@ def _run_job_rate_limited(self):
raise
finally:
if not self._cancel_event.is_set():
self._rate_limiter.update_last_time_and_sleep()
self._rate_limiter.update_last_time_and_sleep(cancel_event=self._cancel_event)
else:
self._rate_limiter.update_last_time()

Expand Down
24 changes: 23 additions & 1 deletion datadog_checks_base/tests/base/utils/db/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,14 @@ def _mock_obfuscate_sql(query, options=None):

class JobForTesting(DBMAsyncJob):
def __init__(
self, check, run_sync=False, enabled=True, rate_limit=10, min_collection_interval=15, job_execution_time=0
self,
check,
run_sync=False,
enabled=True,
rate_limit=10,
min_collection_interval=15,
job_execution_time=0,
max_sleep_chunk_s=5,
):
super(JobForTesting, self).__init__(
check,
Expand All @@ -253,6 +260,7 @@ def __init__(
config_host="test-host",
dbms="test-dbms",
rate_limit=rate_limit,
max_sleep_chunk_s=max_sleep_chunk_s,
job_name="test-job",
shutdown_callback=self.test_shutdown,
)
Expand Down Expand Up @@ -307,6 +315,20 @@ def test_dbm_async_job_cancel(aggregator):
aggregator.assert_metric("dbm.async_job_test.shutdown")


def test_dbm_async_job_cancel_returns_early_on_long_sleep():
# Configure a very low rate so the sleep interval would be ~60s without cancellation
job = JobForTesting(AgentCheck(), rate_limit=1 / 60.0, max_sleep_chunk_s=0.1)
job.run_job_loop([])
# Allow the thread to start and enter the sleep window
time.sleep(0.2)
start = time.time()
job.cancel()
# Should finish well before the full ~10s timeout and 60s rate-limiter interval
job._job_loop_future.result(timeout=10)
elapsed = time.time() - start
assert elapsed < 10, "Job did not cancel before the full sleep interval"


def test_dbm_async_job_run_sync(aggregator):
job = JobForTesting(AgentCheck(), run_sync=True)
job.run_job_loop([])
Expand Down
4 changes: 2 additions & 2 deletions ibm_db2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The [ibm_db][4] client library is required. To install it, ensure you have a wor
##### Unix

```text
sudo -Hu dd-agent /opt/datadog-agent/embedded/bin/pip install ibm_db==3.2.3
sudo -Hu dd-agent /opt/datadog-agent/embedded/bin/pip install ibm_db==3.2.6
```

Note: If you are on an Agent running Python 2, use `ibm_db==3.0.1` instead of `ibm_db=3.1.0`.
Expand Down Expand Up @@ -47,7 +47,7 @@ For Agent versions >= 7.0 and < 7.58:
For Agent versions >= 7.58:

```text
"C:\Program Files\Datadog\Datadog Agent\embedded3\python.exe" -m pip install ibm_db==3.2.3
"C:\Program Files\Datadog\Datadog Agent\embedded3\python.exe" -m pip install ibm_db==3.2.6
```

On Linux there may be need for XML functionality. If you encounter errors during
Expand Down
2 changes: 1 addition & 1 deletion ibm_db2/hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ IBM_DB_INSTALLER_URL = "https://ddintegrations.blob.core.windows.net/ibm-db2/"
[envs.default]
dependencies = [
"ibm_db==3.0.1; python_version < '3.0'",
"ibm_db==3.2.3; python_version > '3.0'",
"ibm_db==3.2.6; python_version > '3.0'",
]

[envs.bench]
2 changes: 1 addition & 1 deletion ibm_db2/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
'start_commands': [
'apt-get update',
'apt-get install -y build-essential libxslt-dev',
'pip install ibm_db==3.2.3',
'pip install ibm_db==3.2.6',
],
}
13 changes: 13 additions & 0 deletions mongo/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def test_integration_mongos(instance_integration_cluster, aggregator, check, dd_
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.operation.time',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -200,6 +201,7 @@ def test_integration_replicaset_primary_in_shard(instance_integration, aggregato
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -299,6 +301,7 @@ def test_integration_replicaset_secondary_in_shard(instance_integration, aggrega
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -355,6 +358,7 @@ def test_integration_replicaset_arbiter_in_shard(instance_integration, aggregato
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -419,6 +423,7 @@ def test_integration_configsvr_primary(instance_integration, aggregator, check,
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -516,6 +521,7 @@ def test_integration_configsvr_secondary(instance_integration, aggregator, check
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -583,6 +589,7 @@ def test_integration_replicaset_primary(instance_integration, aggregator, check,
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -688,6 +695,7 @@ def test_integration_replicaset_primary_config(instance_integration, aggregator,
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -797,6 +805,7 @@ def test_integration_replicaset_secondary(
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -852,6 +861,7 @@ def test_integration_replicaset_arbiter(instance_integration, aggregator, check,
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -908,6 +918,7 @@ def test_standalone(instance_integration, aggregator, check, dd_run_check):
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -974,6 +985,7 @@ def test_db_names_with_nonexistent_database(check, instance_integration, aggrega
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down Expand Up @@ -1009,6 +1021,7 @@ def test_db_names_missing_existent_database(check, instance_integration, aggrega
'dd.custom.mongo.count',
'dd.custom.mongo.query_a.amount',
'dd.custom.mongo.query_a.el',
'dd.mongo.async_job.cancel',
],
check_submission_type=True,
)
Expand Down
8 changes: 6 additions & 2 deletions mongo/tests/test_integration_shard.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ def test_mongo_arbiter(aggregator, check, instance_arbiter, dd_run_check):
if metric_name in METRIC_VAL_CHECKS:
metric = aggregator.metrics(metric_name)[0]
assert METRIC_VAL_CHECKS[metric_name](metric.value)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)

expected_metrics = {
'mongodb.replset.health': 1.0,
Expand Down Expand Up @@ -77,4 +79,6 @@ def test_mongo_replset(instance_shard, aggregator, check, dd_run_check):
'mongodb.replset.optime_lag',
tags=replset_common_tags + ['replset_state:secondary', 'member:shard01b:27019', 'replset_me:shard01a:27018'],
)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)
16 changes: 12 additions & 4 deletions mongo/tests/test_integration_standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def test_mongo_authdb(aggregator, check, instance_authdb, dd_run_check):
if metric_name in METRIC_VAL_CHECKS:
metric = aggregator.metrics(metric_name)[0]
assert METRIC_VAL_CHECKS[metric_name](metric.value)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)


@pytest.mark.parametrize(
Expand All @@ -63,7 +65,9 @@ def test_mongo_db_test(aggregator, check, instance_user, dd_run_check):
if metric_name in METRIC_VAL_CHECKS:
metric = aggregator.metrics(metric_name)[0]
assert METRIC_VAL_CHECKS[metric_name](metric.value)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)


def test_mongo_old_config(aggregator, check, instance, dd_run_check):
Expand All @@ -77,7 +81,9 @@ def test_mongo_old_config(aggregator, check, instance, dd_run_check):
if metric_name in METRIC_VAL_CHECKS_OLD:
metric = aggregator.metrics(metric_name)[0]
assert METRIC_VAL_CHECKS_OLD[metric_name](metric.value)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)


def test_mongo_dbstats_tag(aggregator, check, instance_dbstats_tag_dbname, dd_run_check):
Expand All @@ -91,7 +97,9 @@ def test_mongo_dbstats_tag(aggregator, check, instance_dbstats_tag_dbname, dd_ru
if metric_name in METRIC_VAL_CHECKS:
metric = aggregator.metrics(metric_name)[0]
assert METRIC_VAL_CHECKS[metric_name](metric.value)
aggregator.assert_metrics_using_metadata(get_metadata_metrics(), check_submission_type=True)
aggregator.assert_metrics_using_metadata(
get_metadata_metrics(), check_submission_type=True, exclude=['dd.mongo.async_job.cancel']
)

expected_metrics = {
'mongodb.stats.avgobjsize': None,
Expand Down
2 changes: 1 addition & 1 deletion requirements-agent-release.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ datadog-celery==2.0.1
datadog-ceph==4.1.0; sys_platform != 'win32'
datadog-cert-manager==6.0.1
datadog-checkpoint-quantum-firewall==1.0.0
datadog-checks-base==37.17.0
datadog-checks-base==37.17.1
datadog-checks-dependency-provider==3.0.0
datadog-checks-downloader==8.0.0
datadog-cilium==6.0.1
Expand Down
Loading
Loading