Skip to content

Commit b814548

Browse files
blink1073NoahStappCopilot
authored
PYTHON-5846 Consolidate CMAP, heartbeat, and SDAM telemetry into _telemetry.py (#2907)
Co-authored-by: Noah Stapp <noah@noahstapp.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 4b45e4b commit b814548

13 files changed

Lines changed: 781 additions & 1084 deletions

File tree

pymongo/_telemetry.py

Lines changed: 561 additions & 2 deletions
Large diffs are not rendered by default.

pymongo/asynchronous/mongo_client.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from bson.codec_options import DEFAULT_CODEC_OPTIONS, CodecOptions, TypeRegistry
5858
from bson.timestamp import Timestamp
5959
from pymongo import _csot, common, helpers_shared, periodic_executor
60+
from pymongo._telemetry import log_command_retry
6061
from pymongo.asynchronous import client_session, database, uri_parser
6162
from pymongo.asynchronous.change_stream import AsyncChangeStream, AsyncClusterChangeStream
6263
from pymongo.asynchronous.client_bulk import _AsyncClientBulk
@@ -90,8 +91,6 @@
9091
)
9192
from pymongo.logger import (
9293
_CLIENT_LOGGER,
93-
_COMMAND_LOGGER,
94-
_debug_log,
9594
_log_client_error,
9695
_log_or_warn,
9796
)
@@ -2980,6 +2979,15 @@ async def _get_server(self) -> Server:
29802979
operation_id=self._operation_id,
29812980
)
29822981

2982+
def _log_retry(self, is_write: bool) -> None:
2983+
log_command_retry(
2984+
self._client._topology_id,
2985+
self._operation,
2986+
self._operation_id,
2987+
self._attempt_number,
2988+
is_write,
2989+
)
2990+
29832991
async def _write(self) -> T:
29842992
"""Wrapper method for write-type retryable client executions
29852993
@@ -3003,13 +3011,7 @@ async def _write(self) -> T:
30033011
self._check_last_error()
30043012
self._retryable = False
30053013
if self._retrying:
3006-
_debug_log(
3007-
_COMMAND_LOGGER,
3008-
message=f"Retrying write attempt number {self._attempt_number}",
3009-
clientId=self._client._topology_id,
3010-
commandName=self._operation,
3011-
operationId=self._operation_id,
3012-
)
3014+
self._log_retry(is_write=True)
30133015
return await self._func(self._session, conn, self._retryable) # type: ignore
30143016
except PyMongoError as exc:
30153017
if not self._retryable:
@@ -3032,13 +3034,7 @@ async def _read(self) -> T:
30323034
if self._retrying and not self._retryable and not self._always_retryable:
30333035
self._check_last_error()
30343036
if self._retrying:
3035-
_debug_log(
3036-
_COMMAND_LOGGER,
3037-
message=f"Retrying read attempt number {self._attempt_number}",
3038-
clientId=self._client._topology_settings._topology_id,
3039-
commandName=self._operation,
3040-
operationId=self._operation_id,
3041-
)
3037+
self._log_retry(is_write=False)
30423038
return await self._func(self._session, self._server, conn, read_pref) # type: ignore
30433039

30443040

pymongo/asynchronous/monitor.py

Lines changed: 15 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818

1919
import asyncio
2020
import atexit
21-
import logging
2221
import time
2322
import weakref
2423
from typing import TYPE_CHECKING, Any, Optional
2524

2625
from pymongo import common, periodic_executor
2726
from pymongo._csot import MovingMinimum
27+
from pymongo._telemetry import _HeartbeatTelemetry, _monotonic_duration, log_srv_monitor_failure
2828
from pymongo.asynchronous.srv_resolver import _SrvResolver
2929
from pymongo.errors import NetworkTimeout, _OperationCancelled
3030
from pymongo.hello import Hello
3131
from pymongo.lock import _async_create_lock
32-
from pymongo.logger import _SDAM_LOGGER, _debug_log, _SDAMStatusMessage
3332
from pymongo.periodic_executor import _shutdown_executors
3433
from pymongo.pool_options import _is_faas
3534
from pymongo.read_preferences import MovingAverage
@@ -54,15 +53,6 @@ def _sanitize(error: Exception) -> None:
5453
error.__cause__ = None
5554

5655

57-
def _monotonic_duration(start: float) -> float:
58-
"""Return the duration since the given start time.
59-
60-
Accounts for buggy platforms where time.monotonic() is not monotonic.
61-
See PYTHON-4600.
62-
"""
63-
return max(0.0, time.monotonic() - start)
64-
65-
6656
class MonitorBase:
6757
def __init__(self, topology: Topology, name: str, interval: int, min_interval: float):
6858
"""Base class to do periodic work on a background thread.
@@ -151,9 +141,10 @@ def __init__(
151141
self._pool = pool
152142
self._settings = topology_settings
153143
self._listeners = self._settings._pool_options._event_listeners
154-
self._publish = self._listeners is not None and self._listeners.enabled_for_server_heartbeat
155144
self._cancel_context: Optional[_CancellationContext] = None
156145
self._conn_id: Optional[int] = None
146+
self._current_hb: Optional[_HeartbeatTelemetry] = None
147+
self._awaited: bool = False
157148
self._rtt_monitor = _RttMonitor(
158149
topology,
159150
topology_settings,
@@ -257,32 +248,17 @@ async def _check_server(self) -> ServerDescription:
257248
Returns a ServerDescription.
258249
"""
259250
self._conn_id = None
260-
start = time.monotonic()
251+
self._current_hb = None
252+
self._awaited = False
261253
try:
262254
return await self._check_once()
263255
except ReferenceError:
264256
raise
265257
except Exception as error:
266258
_sanitize(error)
267-
sd = self._server_description
268-
address = sd.address
269-
duration = _monotonic_duration(start)
270-
awaited = bool(self._stream and sd.is_server_type_known and sd.topology_version)
271-
if self._publish:
272-
assert self._listeners is not None
273-
self._listeners.publish_server_heartbeat_failed(address, duration, error, awaited)
274-
if _SDAM_LOGGER.isEnabledFor(logging.DEBUG):
275-
_debug_log(
276-
_SDAM_LOGGER,
277-
message=_SDAMStatusMessage.HEARTBEAT_FAIL,
278-
topologyId=self._topology._topology_id,
279-
serverHost=address[0],
280-
serverPort=address[1],
281-
awaited=awaited,
282-
durationMS=duration * 1000,
283-
failure=error,
284-
driverConnectionId=self._conn_id,
285-
)
259+
address = self._server_description.address
260+
if self._current_hb is not None:
261+
self._current_hb.failed(error, self._conn_id, self._awaited)
286262
await self._reset_connection()
287263
if isinstance(error, _OperationCancelled):
288264
raise
@@ -300,28 +276,17 @@ async def _check_once(self) -> ServerDescription:
300276

301277
# XXX: "awaited" could be incorrectly set to True in the rare case
302278
# the pool checkout closes and recreates a connection.
303-
awaited = bool(
279+
self._awaited = bool(
304280
self._pool.conns and self._stream and sd.is_server_type_known and sd.topology_version
305281
)
306-
if self._publish:
307-
assert self._listeners is not None
308-
self._listeners.publish_server_heartbeat_started(address, awaited)
282+
hb = _HeartbeatTelemetry(self._topology._topology_id, address, self._listeners)
283+
self._current_hb = hb
284+
hb.started(self._awaited)
309285

310286
if self._cancel_context and self._cancel_context.cancelled:
311287
await self._reset_connection()
312288
async with self._pool.checkout() as conn:
313-
if _SDAM_LOGGER.isEnabledFor(logging.DEBUG):
314-
_debug_log(
315-
_SDAM_LOGGER,
316-
message=_SDAMStatusMessage.HEARTBEAT_START,
317-
topologyId=self._topology._topology_id,
318-
driverConnectionId=conn.id,
319-
serverConnectionId=conn.server_connection_id,
320-
serverHost=address[0],
321-
serverPort=address[1],
322-
awaited=awaited,
323-
)
324-
289+
hb.emit_started_log(conn.id, conn.server_connection_id, self._awaited)
325290
self._cancel_context = conn.cancel_context
326291
# Record the connection id so we can later attach it to the failed log message.
327292
self._conn_id = conn.id
@@ -331,24 +296,7 @@ async def _check_once(self) -> ServerDescription:
331296

332297
avg_rtt, min_rtt = await self._rtt_monitor.get()
333298
sd = ServerDescription(address, response, avg_rtt, min_round_trip_time=min_rtt)
334-
if self._publish:
335-
assert self._listeners is not None
336-
self._listeners.publish_server_heartbeat_succeeded(
337-
address, round_trip_time, response, response.awaitable
338-
)
339-
if _SDAM_LOGGER.isEnabledFor(logging.DEBUG):
340-
_debug_log(
341-
_SDAM_LOGGER,
342-
message=_SDAMStatusMessage.HEARTBEAT_SUCCESS,
343-
topologyId=self._topology._topology_id,
344-
driverConnectionId=conn.id,
345-
serverConnectionId=conn.server_connection_id,
346-
serverHost=address[0],
347-
serverPort=address[1],
348-
awaited=awaited,
349-
durationMS=round_trip_time * 1000,
350-
reply=response.document,
351-
)
299+
hb.succeeded(round_trip_time, response, conn.id, conn.server_connection_id)
352300
return sd
353301

354302
async def _check_with_socket(self, conn: AsyncConnection) -> tuple[Hello, float]: # type: ignore[type-arg]
@@ -429,7 +377,7 @@ async def _get_seedlist(self) -> Optional[list[tuple[str, Any]]]:
429377
# - SRV records must be rescanned every heartbeatFrequencyMS
430378
# - Topology must be left unchanged
431379
self.request_check()
432-
_debug_log(_SDAM_LOGGER, message="SRV monitor check failed", failure=repr(exc))
380+
log_srv_monitor_failure(exc)
433381
return None
434382
else:
435383
self._executor.update_interval(max(ttl, common.MIN_SRV_RESCAN_INTERVAL))

0 commit comments

Comments
 (0)