Skip to content

Commit a443d4e

Browse files
authored
Track max buffer usage in memory channel statistics (#3474)
* Track max buffer usage in memory channel statistics Add a max_buffer_used field to MemoryChannelStatistics that reports the largest number of items the channel's buffer has held at once since it was created. This lets users pick a sensible max_buffer_size based on data from a real run instead of guessing. Closes #1723 * Rename max_buffer_used to peak_buffer_used per review feedback max_buffer_used read as a configured limit rather than a runtime metric, easily confused with max_buffer_size. peak_buffer_used pairs more clearly with the existing current_buffer_used field.
1 parent 38b34b4 commit a443d4e

3 files changed

Lines changed: 24 additions & 0 deletions

File tree

newsfragments/1723.feature.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
`MemoryChannelStatistics` now has a ``peak_buffer_used`` field, which reports
2+
the largest number of items that have ever been in the channel's buffer at
3+
once. This makes it easier to pick a sensible ``max_buffer_size`` based on
4+
data from a real run, instead of guessing.

src/trio/_channel.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ class open_memory_channel(tuple["MemorySendChannel[T]", "MemoryReceiveChannel[T]
9696
channel (summing over all clones).
9797
* ``tasks_waiting_receive``: The number of tasks blocked in ``receive`` on
9898
this channel (summing over all clones).
99+
* ``peak_buffer_used``: The largest number of items that have been in the
100+
buffer at once since the channel was created.
99101
"""
100102

101103
def __new__( # type: ignore[misc] # "must return a subtype"
@@ -142,6 +144,10 @@ class MemoryChannelStatistics:
142144
tasks_waiting_receive: int
143145
"""The number of tasks currently blocked waiting to receive."""
144146

147+
peak_buffer_used: int
148+
"""The largest number of items that have been in the buffer at once
149+
since the channel was created."""
150+
145151

146152
@attrs.define
147153
class MemoryChannelState(Generic[T]):
@@ -154,6 +160,8 @@ class MemoryChannelState(Generic[T]):
154160
send_tasks: OrderedDict[Task, T] = attrs.Factory(OrderedDict)
155161
# {task: None}
156162
receive_tasks: OrderedDict[Task, None] = attrs.Factory(OrderedDict)
163+
# The largest len(self.data) has ever been
164+
peak_buffer_used: int = 0
157165

158166
def statistics(self) -> MemoryChannelStatistics:
159167
return MemoryChannelStatistics(
@@ -163,6 +171,7 @@ def statistics(self) -> MemoryChannelStatistics:
163171
open_receive_channels=self.open_receive_channels,
164172
tasks_waiting_send=len(self.send_tasks),
165173
tasks_waiting_receive=len(self.receive_tasks),
174+
peak_buffer_used=self.peak_buffer_used,
166175
)
167176

168177

@@ -211,6 +220,10 @@ def send_nowait(self, value: SendType) -> None:
211220
trio.lowlevel.reschedule(task, Value(value))
212221
elif len(self._state.data) < self._state.max_buffer_size:
213222
self._state.data.append(value)
223+
self._state.peak_buffer_used = max(
224+
self._state.peak_buffer_used,
225+
len(self._state.data),
226+
)
214227
else:
215228
raise trio.WouldBlock
216229

src/trio/_tests/test_channel.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,11 @@ async def test_statistics() -> None:
320320
assert stats.open_receive_channels == 1
321321
assert stats.tasks_waiting_send == 0
322322
assert stats.tasks_waiting_receive == 0
323+
assert stats.peak_buffer_used == 0
323324

324325
s.send_nowait(None)
325326
assert s.statistics().current_buffer_used == 1
327+
assert s.statistics().peak_buffer_used == 1
326328

327329
s2 = s.clone()
328330
assert s.statistics().open_send_channels == 2
@@ -337,6 +339,7 @@ async def test_statistics() -> None:
337339
async with trio.open_nursery() as nursery:
338340
s2.send_nowait(None) # fill up the buffer
339341
assert s.statistics().current_buffer_used == 2
342+
assert s.statistics().peak_buffer_used == 2
340343
nursery.start_soon(s2.send, None)
341344
nursery.start_soon(s2.send, None)
342345
await wait_all_tasks_blocked()
@@ -351,6 +354,10 @@ async def test_statistics() -> None:
351354
except trio.WouldBlock:
352355
pass
353356

357+
# draining the buffer doesn't reset the high-water mark
358+
assert s.statistics().current_buffer_used == 0
359+
assert s.statistics().peak_buffer_used == 2
360+
354361
async with trio.open_nursery() as nursery:
355362
nursery.start_soon(r.receive)
356363
await wait_all_tasks_blocked()

0 commit comments

Comments
 (0)