|
| 1 | +"""Connection-window aggregation and signal semantics shared across scan levels.""" |
| 2 | + |
| 3 | +RESPONSIVE_CONNECTION_OUTCOMES = frozenset(("connected", "refused", "reset")) |
| 4 | +MIN_QUALIFIED_WINDOW_ATTEMPTS = 5 |
| 5 | +BLOCKING_BASELINE_RATE = 0.8 |
| 6 | +BLOCKING_RESPONSE_RATE = 0.2 |
| 7 | +THROTTLING_DROP_RATIO = 0.7 |
| 8 | + |
| 9 | + |
| 10 | +def detect_connection_signals(windows: list | None) -> dict: |
| 11 | + """Derive blocking/throttling signals from sufficiently sampled windows.""" |
| 12 | + qualified = [] |
| 13 | + for window in windows or []: |
| 14 | + attempts = window.get("attempts") |
| 15 | + if not isinstance(attempts, (int, float)) or attempts < MIN_QUALIFIED_WINDOW_ATTEMPTS: |
| 16 | + continue |
| 17 | + responsive_count = window.get("responsive_count") |
| 18 | + response_rate = window.get("response_rate") |
| 19 | + if response_rate is None and responsive_count is not None: |
| 20 | + response_rate = responsive_count / attempts |
| 21 | + if responsive_count is None and response_rate is not None: |
| 22 | + responsive_count = response_rate * attempts |
| 23 | + if response_rate is None or responsive_count is None: |
| 24 | + continue |
| 25 | + qualified.append({ |
| 26 | + "attempts": attempts, |
| 27 | + "responsive_count": responsive_count, |
| 28 | + "response_rate": response_rate, |
| 29 | + }) |
| 30 | + |
| 31 | + blocking = any( |
| 32 | + previous["response_rate"] >= BLOCKING_BASELINE_RATE |
| 33 | + and current["response_rate"] <= BLOCKING_RESPONSE_RATE |
| 34 | + for previous, current in zip(qualified, qualified[1:]) |
| 35 | + ) |
| 36 | + |
| 37 | + throttling = False |
| 38 | + if len(qualified) >= 4: |
| 39 | + first_attempts = sum(window["attempts"] for window in qualified[:2]) |
| 40 | + last_attempts = sum(window["attempts"] for window in qualified[-2:]) |
| 41 | + first_responsive = sum(window["responsive_count"] for window in qualified[:2]) |
| 42 | + last_responsive = sum(window["responsive_count"] for window in qualified[-2:]) |
| 43 | + baseline_rate = first_responsive / first_attempts |
| 44 | + later_rate = last_responsive / last_attempts |
| 45 | + throttling = ( |
| 46 | + later_rate > BLOCKING_RESPONSE_RATE |
| 47 | + and later_rate < baseline_rate * THROTTLING_DROP_RATIO |
| 48 | + ) |
| 49 | + |
| 50 | + return { |
| 51 | + "rate_limiting_detected": throttling, |
| 52 | + "blocking_detected": blocking, |
| 53 | + } |
| 54 | + |
| 55 | + |
| 56 | +def merge_connection_windows(metrics_list: list) -> list | None: |
| 57 | + """Merge aligned count-bearing windows, excluding unverifiable legacy samples.""" |
| 58 | + grouped = {} |
| 59 | + legacy_fallback = None |
| 60 | + for metrics in metrics_list: |
| 61 | + windows = metrics.get("success_rate_over_time") or [] |
| 62 | + if legacy_fallback is None or len(windows) > len(legacy_fallback): |
| 63 | + legacy_fallback = windows |
| 64 | + for window in windows: |
| 65 | + attempts = window.get("attempts") |
| 66 | + responsive_count = window.get("responsive_count") |
| 67 | + if attempts is None or attempts <= 0: |
| 68 | + continue |
| 69 | + if responsive_count is None: |
| 70 | + response_rate = window.get("response_rate") |
| 71 | + if response_rate is None: |
| 72 | + continue |
| 73 | + responsive_count = round(response_rate * attempts) |
| 74 | + key = (window.get("window_start", 0), window.get("window_end", 0)) |
| 75 | + bucket = grouped.setdefault(key, { |
| 76 | + "attempts": 0, |
| 77 | + "responsive_count": 0, |
| 78 | + "connected_weight": 0.0, |
| 79 | + }) |
| 80 | + bucket["attempts"] += attempts |
| 81 | + bucket["responsive_count"] += responsive_count |
| 82 | + bucket["connected_weight"] += window.get("success_rate", 0) * attempts |
| 83 | + |
| 84 | + if not grouped: |
| 85 | + return legacy_fallback or None |
| 86 | + |
| 87 | + merged = [] |
| 88 | + for (window_start, window_end), counts in sorted(grouped.items()): |
| 89 | + attempts = counts["attempts"] |
| 90 | + merged.append({ |
| 91 | + "window_start": window_start, |
| 92 | + "window_end": window_end, |
| 93 | + "success_rate": round(counts["connected_weight"] / attempts, 3), |
| 94 | + "attempts": attempts, |
| 95 | + "responsive_count": counts["responsive_count"], |
| 96 | + "response_rate": round(counts["responsive_count"] / attempts, 3), |
| 97 | + }) |
| 98 | + return merged |
0 commit comments