by-framework 0.2.2.dev14__py3-none-any.whl → 0.2.2.dev16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- by_framework/metrics/catalog.py +54 -0
- by_framework/worker/runner.py +25 -2
- {by_framework-0.2.2.dev14.dist-info → by_framework-0.2.2.dev16.dist-info}/METADATA +1 -1
- {by_framework-0.2.2.dev14.dist-info → by_framework-0.2.2.dev16.dist-info}/RECORD +7 -7
- {by_framework-0.2.2.dev14.dist-info → by_framework-0.2.2.dev16.dist-info}/WHEEL +0 -0
- {by_framework-0.2.2.dev14.dist-info → by_framework-0.2.2.dev16.dist-info}/entry_points.txt +0 -0
- {by_framework-0.2.2.dev14.dist-info → by_framework-0.2.2.dev16.dist-info}/licenses/LICENSE +0 -0
by_framework/metrics/catalog.py
CHANGED
|
@@ -367,6 +367,60 @@ _CATALOG: dict[str, MetricDefinition] = {
|
|
|
367
367
|
"Instrumentation reliability signal pinpointing exporter failures."
|
|
368
368
|
),
|
|
369
369
|
),
|
|
370
|
+
# Self-monitoring for the background collector in metrics/collector.py.
|
|
371
|
+
# Exactly one process holds its Redis lock and writes history points, so
|
|
372
|
+
# these answer "is anyone still collecting?" — a gap here means the
|
|
373
|
+
# dashboards go stale silently rather than erroring.
|
|
374
|
+
"by_framework_metrics_collector_cycles_total": MetricDefinition(
|
|
375
|
+
name="by_framework_metrics_collector_cycles_total",
|
|
376
|
+
kind=MetricKind.COUNTER,
|
|
377
|
+
unit=MetricUnit.NONE,
|
|
378
|
+
labels=("result",),
|
|
379
|
+
description=(
|
|
380
|
+
"Metrics collector cycles by result "
|
|
381
|
+
"(success, snapshot_failed, lock_skipped, disabled)."
|
|
382
|
+
),
|
|
383
|
+
interpretation=(
|
|
384
|
+
"Collector health signal: only the lock holder reports success, so "
|
|
385
|
+
"lock_skipped dominating across a fleet is expected."
|
|
386
|
+
),
|
|
387
|
+
),
|
|
388
|
+
"by_framework_metrics_collector_snapshot_duration_ms": MetricDefinition(
|
|
389
|
+
name="by_framework_metrics_collector_snapshot_duration_ms",
|
|
390
|
+
kind=MetricKind.HISTOGRAM,
|
|
391
|
+
unit=MetricUnit.MILLISECONDS,
|
|
392
|
+
description=(
|
|
393
|
+
"Duration of one collector snapshot build plus history write."
|
|
394
|
+
),
|
|
395
|
+
interpretation=(
|
|
396
|
+
"Latency signal for collection itself; approaching the collection "
|
|
397
|
+
"interval means cycles are about to overlap."
|
|
398
|
+
),
|
|
399
|
+
),
|
|
400
|
+
"by_framework_metrics_collector_lock_held": MetricDefinition(
|
|
401
|
+
name="by_framework_metrics_collector_lock_held",
|
|
402
|
+
kind=MetricKind.GAUGE,
|
|
403
|
+
unit=MetricUnit.NONE,
|
|
404
|
+
description=(
|
|
405
|
+
"Whether this process currently holds the metrics collector lock."
|
|
406
|
+
),
|
|
407
|
+
interpretation=(
|
|
408
|
+
"Saturation signal: should sum to 1 across the fleet — 0 means "
|
|
409
|
+
"nobody is collecting, above 1 means the lock split."
|
|
410
|
+
),
|
|
411
|
+
),
|
|
412
|
+
"by_framework_metrics_collector_last_success_timestamp_ms": MetricDefinition(
|
|
413
|
+
name="by_framework_metrics_collector_last_success_timestamp_ms",
|
|
414
|
+
kind=MetricKind.GAUGE,
|
|
415
|
+
unit=MetricUnit.MILLISECONDS,
|
|
416
|
+
description=(
|
|
417
|
+
"Unix timestamp in milliseconds of the last successful collection."
|
|
418
|
+
),
|
|
419
|
+
interpretation=(
|
|
420
|
+
"Freshness signal: alert on now minus this value exceeding a few "
|
|
421
|
+
"collection intervals."
|
|
422
|
+
),
|
|
423
|
+
),
|
|
370
424
|
}
|
|
371
425
|
|
|
372
426
|
|
by_framework/worker/runner.py
CHANGED
|
@@ -83,6 +83,7 @@ class WorkerRunner:
|
|
|
83
83
|
self.worker = worker
|
|
84
84
|
self.group_name = group_name or self._auto_group_name()
|
|
85
85
|
self.consumer_name = worker.worker_id
|
|
86
|
+
self.max_concurrency = max_concurrency
|
|
86
87
|
self.semaphore = asyncio.Semaphore(max_concurrency)
|
|
87
88
|
self.fetch_count = fetch_count
|
|
88
89
|
# OTel for worker.execute is emitted via a live wrapping span (see
|
|
@@ -121,6 +122,14 @@ class WorkerRunner:
|
|
|
121
122
|
float(self.worker.heartbeat_lease_ttl_seconds) * 2.0,
|
|
122
123
|
stream_block_seconds * 3.0,
|
|
123
124
|
)
|
|
125
|
+
# Poll interval used by _acquire_slot() while waiting for a saturated
|
|
126
|
+
# semaphore. Derived from _consumer_health_timeout_seconds (not a
|
|
127
|
+
# hardcoded constant) so it stays well under the health-check
|
|
128
|
+
# deadline under any configuration, with a floor to avoid busy-
|
|
129
|
+
# polling if the timeout is configured very small.
|
|
130
|
+
self._slot_wait_poll_seconds = max(
|
|
131
|
+
self._consumer_health_timeout_seconds / 4.0, 1.0
|
|
132
|
+
)
|
|
124
133
|
# Set as the first step of _shutdown() - see
|
|
125
134
|
# docs/architecture/worker-readiness-endpoint.md Decision 6.
|
|
126
135
|
self._draining: bool = False
|
|
@@ -386,9 +395,23 @@ class WorkerRunner:
|
|
|
386
395
|
finally:
|
|
387
396
|
await self.redis.xack(stream_name, self.group_name, msg_id)
|
|
388
397
|
|
|
398
|
+
async def _acquire_slot(self) -> None:
|
|
399
|
+
"""Wait for a concurrency slot, marking the consumer tick on every
|
|
400
|
+
poll so a saturated-but-alive loop isn't judged stalled.
|
|
401
|
+
|
|
402
|
+
Polling on `len(self._running_tasks)` instead of wrapping
|
|
403
|
+
`semaphore.acquire()` in `asyncio.wait_for()` avoids relying on
|
|
404
|
+
Semaphore cancellation semantics for a lock this runner is the only
|
|
405
|
+
waiter on — see design.md's "why not asyncio.wait_for" section.
|
|
406
|
+
"""
|
|
407
|
+
while len(self._running_tasks) >= self.max_concurrency:
|
|
408
|
+
self._mark_consumer_tick()
|
|
409
|
+
await asyncio.sleep(self._slot_wait_poll_seconds)
|
|
410
|
+
await self.semaphore.acquire()
|
|
411
|
+
|
|
389
412
|
async def _run_once(self) -> bool:
|
|
390
413
|
"""Fetch and start processing messages."""
|
|
391
|
-
await self.
|
|
414
|
+
await self._acquire_slot()
|
|
392
415
|
|
|
393
416
|
try:
|
|
394
417
|
messages = await self.fetch_messages(
|
|
@@ -401,7 +424,7 @@ class WorkerRunner:
|
|
|
401
424
|
|
|
402
425
|
for i, (stream_name, msg_id, data_dict) in enumerate(messages):
|
|
403
426
|
if i > 0:
|
|
404
|
-
await self.
|
|
427
|
+
await self._acquire_slot()
|
|
405
428
|
|
|
406
429
|
task = asyncio.create_task(
|
|
407
430
|
self._process_and_release(stream_name, msg_id, data_dict)
|
|
@@ -69,7 +69,7 @@ by_framework/errors/http.py,sha256=xtm4EKwbE1Z0n4yqWTjPOVbDd9fIPfmT8W4ou5aOLCQ,7
|
|
|
69
69
|
by_framework/errors/protocol.py,sha256=MWr15TFIHs8Ql67mgvU9mebGdE04yPxmg4AOEGbF9E4,1269
|
|
70
70
|
by_framework/errors/registry.py,sha256=LmcqSXiW8FSOghpLPHTllkbrQCnVcMYTLzO2cNU3wIw,842
|
|
71
71
|
by_framework/metrics/__init__.py,sha256=8AQpVnOwAmHoQsDhCfkNXSKUOX_ZCBF_QajBXmsNhgc,11353
|
|
72
|
-
by_framework/metrics/catalog.py,sha256=
|
|
72
|
+
by_framework/metrics/catalog.py,sha256=1yWxVCvvrbkT0Rdmi0x-rtRwtXFzQg6Dmuj75izN2X0,19684
|
|
73
73
|
by_framework/metrics/collector.py,sha256=uIO2TJfX4zQTBi5gWbMfgwtdMjrhmhzjzW36BcqxBKE,8533
|
|
74
74
|
by_framework/metrics/read_client.py,sha256=kcgHJlJh47eFPhlbH8QOyD-K6SpKbZHEX624XD6Mvxo,18511
|
|
75
75
|
by_framework/metrics/snapshot.py,sha256=eI4JVV3uma7fT6nvp2bliO2ZUBJhJT6bzTvQDIeC-wk,101784
|
|
@@ -94,12 +94,12 @@ by_framework/worker/context.py,sha256=5As3GcnVrwIE1nCJDDYLt_Ke8-8nzh44CzQBvyncQs
|
|
|
94
94
|
by_framework/worker/health_server.py,sha256=4Jw3ltAD7Ow4dPHhwTUU_4JjKz-iDCNG7RJzD2VzPlM,6090
|
|
95
95
|
by_framework/worker/heartbeat.py,sha256=trHDS8FUALsA6jyzDPa087hpr8_2vY8ZUFn0ld1kI2o,15465
|
|
96
96
|
by_framework/worker/processor.py,sha256=UgC4_p-bg2KOHVTEILeMXF44rT9VLrRP5vompX0EeiQ,16243
|
|
97
|
-
by_framework/worker/runner.py,sha256=
|
|
97
|
+
by_framework/worker/runner.py,sha256=nkJDpziUQaqqVEXc8OhoF4DKGiw7fthpvXudDjxYdm0,53093
|
|
98
98
|
by_framework/worker/worker.py,sha256=IaPFLWCFo5U5Xo7PIQHtTt4JmtxZA1O_3nAuGl_g4N0,49527
|
|
99
99
|
by_framework/worker/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
100
|
by_framework/worker/sandbox/hook_sandbox.py,sha256=XXSdH5YGX814LvV03vkq-zn5DVIBPYYt2NhRv1lT1Bk,2329
|
|
101
|
-
by_framework-0.2.2.
|
|
102
|
-
by_framework-0.2.2.
|
|
103
|
-
by_framework-0.2.2.
|
|
104
|
-
by_framework-0.2.2.
|
|
105
|
-
by_framework-0.2.2.
|
|
101
|
+
by_framework-0.2.2.dev16.dist-info/METADATA,sha256=3Sbgc5Aw5L6vYzfRQpcfyMK9B-qYs9V-TMvAJGHwrAM,28018
|
|
102
|
+
by_framework-0.2.2.dev16.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
103
|
+
by_framework-0.2.2.dev16.dist-info/entry_points.txt,sha256=5t6kgMjkR0UgJTpOsbuQDo0fOf7zeXjQW9DhkxfAGFc,56
|
|
104
|
+
by_framework-0.2.2.dev16.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
105
|
+
by_framework-0.2.2.dev16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|