mirror of
https://github.com/Horizontal42/netsleuth.git
synced 2026-11-10 19:12:03 +03:00
Remove sent variable from PingResult which can be derived from len(samples)
Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
parent
8c99ac78b7
commit
1b7dccf1b2
10 changed files with 109 additions and 37 deletions
|
|
@ -49,13 +49,13 @@ def latency_findings(pings: list[PingResult], t: Thresholds) -> list[Finding]:
|
||||||
id=f"latency.unreachable.{p.label}",
|
id=f"latency.unreachable.{p.label}",
|
||||||
severity="crit",
|
severity="crit",
|
||||||
title=f"{p.host} did not answer",
|
title=f"{p.host} did not answer",
|
||||||
detail=f"{p.sent} probes sent via {p.method}, none returned.",
|
detail=f"{len(p.samples)} probes sent via {p.method}, none returned.",
|
||||||
metric="received",
|
metric="received",
|
||||||
value=0,
|
value=0,
|
||||||
threshold=1,
|
threshold=1,
|
||||||
advice="If every reference host is unreachable the link is down or filtering the probe method.",
|
advice="If every reference host is unreachable the link is down or filtering the probe method.",
|
||||||
title_ru=f"{p.host} не отвечает",
|
title_ru=f"{p.host} не отвечает",
|
||||||
detail_ru=f"{p.sent} проб отправлено через {p.method}, ни одна не вернулась.",
|
detail_ru=f"{len(p.samples)} проб отправлено через {p.method}, ни одна не вернулась.",
|
||||||
advice_ru="Если все эталонные хосты недоступны, значит канал не работает или фильтрует метод пробы.",
|
advice_ru="Если все эталонные хосты недоступны, значит канал не работает или фильтрует метод пробы.",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -103,13 +103,13 @@ def latency_findings(pings: list[PingResult], t: Thresholds) -> list[Finding]:
|
||||||
id=f"latency.loss.{p.label}",
|
id=f"latency.loss.{p.label}",
|
||||||
severity=severity,
|
severity=severity,
|
||||||
title=f"Loss to {p.host}",
|
title=f"Loss to {p.host}",
|
||||||
detail=f"{p.loss_pct}% {kind} over {p.sent} probes via {p.method}.",
|
detail=f"{p.loss_pct}% {kind} over {len(p.samples)} probes via {p.method}.",
|
||||||
metric="loss_pct",
|
metric="loss_pct",
|
||||||
value=p.loss_pct,
|
value=p.loss_pct,
|
||||||
threshold=t.loss_pct.warn,
|
threshold=t.loss_pct.warn,
|
||||||
advice="Sustained loss on every host points at the local link or the first upstream hop.",
|
advice="Sustained loss on every host points at the local link or the first upstream hop.",
|
||||||
title_ru=f"Потери до {p.host}",
|
title_ru=f"Потери до {p.host}",
|
||||||
detail_ru=f"{p.loss_pct}% {kind_ru} за {p.sent} проб через {p.method}.",
|
detail_ru=f"{p.loss_pct}% {kind_ru} за {len(p.samples)} проб через {p.method}.",
|
||||||
advice_ru="Устойчивые потери на всех хостах указывают на локальный канал или первый аплинк.",
|
advice_ru="Устойчивые потери на всех хостах указывают на локальный канал или первый аплинк.",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -121,8 +121,16 @@ def _first_hop_findings(hop: TraceHop, local: LocalNet, t: Thresholds) -> list[F
|
||||||
return []
|
return []
|
||||||
is_gateway = bool(local.default_gateway_v4) and hop.ip == local.default_gateway_v4
|
is_gateway = bool(local.default_gateway_v4) and hop.ip == local.default_gateway_v4
|
||||||
findings: list[Finding] = []
|
findings: list[Finding] = []
|
||||||
where = f"your LAN/Wi-Fi link to {hop.ip}" if is_gateway else f"the first hop ({hop.ip})"
|
where = (
|
||||||
where_ru = f"вашу локальную сеть/Wi-Fi до {hop.ip}" if is_gateway else f"первый хоп ({hop.ip})"
|
f"your LAN/Wi-Fi link to {hop.ip}"
|
||||||
|
if is_gateway
|
||||||
|
else f"the first hop ({hop.ip})"
|
||||||
|
)
|
||||||
|
where_ru = (
|
||||||
|
f"вашу локальную сеть/Wi-Fi до {hop.ip}"
|
||||||
|
if is_gateway
|
||||||
|
else f"первый хоп ({hop.ip})"
|
||||||
|
)
|
||||||
if hop.loss_pct >= 20.0 or hop.loss_pct > t.loss_pct.warn:
|
if hop.loss_pct >= 20.0 or hop.loss_pct > t.loss_pct.warn:
|
||||||
severity = "crit" if hop.loss_pct >= 20.0 else "warn"
|
severity = "crit" if hop.loss_pct >= 20.0 else "warn"
|
||||||
findings.append(
|
findings.append(
|
||||||
|
|
@ -228,7 +236,9 @@ def dual_family_findings(pings: list[PingResult]) -> list[Finding]:
|
||||||
return findings
|
return findings
|
||||||
|
|
||||||
|
|
||||||
def path_findings(trace: TraceResult, local: LocalNet | None = None, t: Thresholds | None = None) -> list[Finding]:
|
def path_findings(
|
||||||
|
trace: TraceResult, local: LocalNet | None = None, t: Thresholds | None = None
|
||||||
|
) -> list[Finding]:
|
||||||
if not trace.hops:
|
if not trace.hops:
|
||||||
return [
|
return [
|
||||||
Finding(
|
Finding(
|
||||||
|
|
@ -250,7 +260,11 @@ def path_findings(trace: TraceResult, local: LocalNet | None = None, t: Threshol
|
||||||
# in a row stayed silent; only loss that persists all the way to the end is real.
|
# in a row stayed silent; only loss that persists all the way to the end is real.
|
||||||
loss_persists_to_the_end = trace.hops[-1].loss_pct >= 20.0
|
loss_persists_to_the_end = trace.hops[-1].loss_pct >= 20.0
|
||||||
for hop, following in zip(trace.hops, trace.hops[1:]):
|
for hop, following in zip(trace.hops, trace.hops[1:]):
|
||||||
if loss_persists_to_the_end and hop.loss_pct >= 20.0 and following.loss_pct >= 20.0:
|
if (
|
||||||
|
loss_persists_to_the_end
|
||||||
|
and hop.loss_pct >= 20.0
|
||||||
|
and following.loss_pct >= 20.0
|
||||||
|
):
|
||||||
findings.append(
|
findings.append(
|
||||||
Finding(
|
Finding(
|
||||||
id="path.loss_jump",
|
id="path.loss_jump",
|
||||||
|
|
@ -323,7 +337,9 @@ def cgnat_findings(local: LocalNet, traces: list[TraceResult]) -> list[Finding]:
|
||||||
for hop in trace.hops:
|
for hop in trace.hops:
|
||||||
if is_cgnat(hop.ip):
|
if is_cgnat(hop.ip):
|
||||||
detected = True
|
detected = True
|
||||||
evidence = f"traceroute hop {hop.ttl} ({hop.ip}) is in 100.64.0.0/10"
|
evidence = (
|
||||||
|
f"traceroute hop {hop.ttl} ({hop.ip}) is in 100.64.0.0/10"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
if detected:
|
if detected:
|
||||||
break
|
break
|
||||||
|
|
@ -340,7 +356,8 @@ def cgnat_findings(local: LocalNet, traces: list[TraceResult]) -> list[Finding]:
|
||||||
advice="No inbound port forwarding is possible; P2P and game/server hosting may not work. "
|
advice="No inbound port forwarding is possible; P2P and game/server hosting may not work. "
|
||||||
"Ask your ISP for a public IPv4 address, or use IPv6 where the destination supports it.",
|
"Ask your ISP for a public IPv4 address, or use IPv6 where the destination supports it.",
|
||||||
title_ru="За carrier-grade NAT (CGNAT)",
|
title_ru="За carrier-grade NAT (CGNAT)",
|
||||||
detail_ru=evidence or "Обнаружен адрес из диапазона 100.64.0.0/10 (RFC 6598).",
|
detail_ru=evidence
|
||||||
|
or "Обнаружен адрес из диапазона 100.64.0.0/10 (RFC 6598).",
|
||||||
advice_ru="Проброс входящих портов невозможен; P2P и хостинг игр/серверов могут не работать. "
|
advice_ru="Проброс входящих портов невозможен; P2P и хостинг игр/серверов могут не работать. "
|
||||||
"Запросите у провайдера публичный IPv4-адрес либо используйте IPv6, если целевой сервис его поддерживает.",
|
"Запросите у провайдера публичный IPv4-адрес либо используйте IPv6, если целевой сервис его поддерживает.",
|
||||||
)
|
)
|
||||||
|
|
@ -533,7 +550,11 @@ def path_asn_findings(trace: TraceResult, client_country: str | None) -> list[Fi
|
||||||
final_country = trace.hops[-1].country
|
final_country = trace.hops[-1].country
|
||||||
run: list[TraceHop] = []
|
run: list[TraceHop] = []
|
||||||
for hop in trace.hops:
|
for hop in trace.hops:
|
||||||
detour = bool(hop.country) and hop.country != client_country and hop.country != final_country
|
detour = (
|
||||||
|
bool(hop.country)
|
||||||
|
and hop.country != client_country
|
||||||
|
and hop.country != final_country
|
||||||
|
)
|
||||||
if not detour:
|
if not detour:
|
||||||
run = []
|
run = []
|
||||||
continue
|
continue
|
||||||
|
|
@ -584,7 +605,13 @@ _TZ_COUNTRY_PREFIX = {
|
||||||
|
|
||||||
|
|
||||||
def _signal(name: str, observed: bool, note: str = "") -> Signal:
|
def _signal(name: str, observed: bool, note: str = "") -> Signal:
|
||||||
return Signal(name=name, observed=observed, weight=SIGNAL_WEIGHTS[name], direction="vpn", note=note)
|
return Signal(
|
||||||
|
name=name,
|
||||||
|
observed=observed,
|
||||||
|
weight=SIGNAL_WEIGHTS[name],
|
||||||
|
direction="vpn",
|
||||||
|
note=note,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def gather_vpn_signals(ctx: VpnContext) -> list[Signal]:
|
def gather_vpn_signals(ctx: VpnContext) -> list[Signal]:
|
||||||
|
|
@ -598,7 +625,11 @@ def gather_vpn_signals(ctx: VpnContext) -> list[Signal]:
|
||||||
tz_country = _TZ_COUNTRY_PREFIX.get(ctx.os_timezone or "")
|
tz_country = _TZ_COUNTRY_PREFIX.get(ctx.os_timezone or "")
|
||||||
return [
|
return [
|
||||||
_signal("tunnel_iface", is_tunnel_iface(iface), iface),
|
_signal("tunnel_iface", is_tunnel_iface(iface), iface),
|
||||||
_signal("cf_warp", bool(ctx.cf and (ctx.cf.warp or "").lower() == "on"), (ctx.cf.warp if ctx.cf else "") or ""),
|
_signal(
|
||||||
|
"cf_warp",
|
||||||
|
bool(ctx.cf and (ctx.cf.warp or "").lower() == "on"),
|
||||||
|
(ctx.cf.warp if ctx.cf else "") or "",
|
||||||
|
),
|
||||||
_signal("provider_proxy", bool(ctx.provider_flags.get("proxy"))),
|
_signal("provider_proxy", bool(ctx.provider_flags.get("proxy"))),
|
||||||
_signal("provider_hosting", bool(ctx.provider_flags.get("hosting"))),
|
_signal("provider_hosting", bool(ctx.provider_flags.get("hosting"))),
|
||||||
_signal("provider_mobile", bool(ctx.provider_flags.get("mobile"))),
|
_signal("provider_mobile", bool(ctx.provider_flags.get("mobile"))),
|
||||||
|
|
@ -613,10 +644,18 @@ def gather_vpn_signals(ctx: VpnContext) -> list[Signal]:
|
||||||
bool(ctx.local.default_gateway_v4 and is_tunnel_iface(iface)),
|
bool(ctx.local.default_gateway_v4 and is_tunnel_iface(iface)),
|
||||||
ctx.local.default_gateway_v4 or "",
|
ctx.local.default_gateway_v4 or "",
|
||||||
),
|
),
|
||||||
_signal("pdb_info_type_nsp", (ctx.pdb_info_type or "").upper() in ("NSP", "CONTENT", "ENTERPRISE"), ctx.pdb_info_type or ""),
|
_signal(
|
||||||
|
"pdb_info_type_nsp",
|
||||||
|
(ctx.pdb_info_type or "").upper() in ("NSP", "CONTENT", "ENTERPRISE"),
|
||||||
|
ctx.pdb_info_type or "",
|
||||||
|
),
|
||||||
_signal(
|
_signal(
|
||||||
"timezone_mismatch",
|
"timezone_mismatch",
|
||||||
bool(tz_country and ctx.geo.country_code and tz_country != ctx.geo.country_code),
|
bool(
|
||||||
|
tz_country
|
||||||
|
and ctx.geo.country_code
|
||||||
|
and tz_country != ctx.geo.country_code
|
||||||
|
),
|
||||||
f"{ctx.os_timezone} vs {ctx.geo.country_code}" if tz_country else "",
|
f"{ctx.os_timezone} vs {ctx.geo.country_code}" if tz_country else "",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -670,14 +709,28 @@ _CONSEQUENCES_RU = {
|
||||||
"F": "Одна закачка делает звонок непригодным; это раздутие очереди, а не медленный канал.",
|
"F": "Одна закачка делает звонок непригодным; это раздутие очереди, а не медленный канал.",
|
||||||
"?": "Не измерено — уровень speedtest-каскада, который это измеряет, не запускался.",
|
"?": "Не измерено — уровень speedtest-каскада, который это измеряет, не запускался.",
|
||||||
}
|
}
|
||||||
_GRADE_SEVERITY = {"A": "ok", "B": "ok", "C": "warn", "D": "crit", "E": "crit", "F": "crit", "?": "info"}
|
_GRADE_SEVERITY = {
|
||||||
|
"A": "ok",
|
||||||
|
"B": "ok",
|
||||||
|
"C": "warn",
|
||||||
|
"D": "crit",
|
||||||
|
"E": "crit",
|
||||||
|
"F": "crit",
|
||||||
|
"?": "info",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def grade_bufferbloat(delta_ms: float | None, bands: BufferbloatBands) -> str:
|
def grade_bufferbloat(delta_ms: float | None, bands: BufferbloatBands) -> str:
|
||||||
if delta_ms is None:
|
if delta_ms is None:
|
||||||
return "?"
|
return "?"
|
||||||
delta = max(0.0, delta_ms)
|
delta = max(0.0, delta_ms)
|
||||||
for grade, ceiling in (("A", bands.a), ("B", bands.b), ("C", bands.c), ("D", bands.d), ("E", bands.e)):
|
for grade, ceiling in (
|
||||||
|
("A", bands.a),
|
||||||
|
("B", bands.b),
|
||||||
|
("C", bands.c),
|
||||||
|
("D", bands.d),
|
||||||
|
("E", bands.e),
|
||||||
|
):
|
||||||
if delta <= ceiling:
|
if delta <= ceiling:
|
||||||
return grade
|
return grade
|
||||||
return "F"
|
return "F"
|
||||||
|
|
@ -688,7 +741,9 @@ def bufferbloat_consequence(grade: str, lang: str = "en") -> str:
|
||||||
return table.get(grade, table["?"])
|
return table.get(grade, table["?"])
|
||||||
|
|
||||||
|
|
||||||
def speed_findings(speed: SpeedResult, bands: BufferbloatBands, client_country: str | None = None) -> list[Finding]:
|
def speed_findings(
|
||||||
|
speed: SpeedResult, bands: BufferbloatBands, client_country: str | None = None
|
||||||
|
) -> list[Finding]:
|
||||||
if speed.method == "none":
|
if speed.method == "none":
|
||||||
tried = ", ".join(a.tier for a in speed.tier_attempts) or "none"
|
tried = ", ".join(a.tier for a in speed.tier_attempts) or "none"
|
||||||
return [
|
return [
|
||||||
|
|
@ -729,7 +784,11 @@ def speed_findings(speed: SpeedResult, bands: BufferbloatBands, client_country:
|
||||||
+ " Включение SQM/fq_codel на роутере — стандартное решение.",
|
+ " Включение SQM/fq_codel на роутере — стандартное решение.",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if client_country and speed.server_country and client_country.casefold() != speed.server_country.casefold():
|
if (
|
||||||
|
client_country
|
||||||
|
and speed.server_country
|
||||||
|
and client_country.casefold() != speed.server_country.casefold()
|
||||||
|
):
|
||||||
findings.append(
|
findings.append(
|
||||||
Finding(
|
Finding(
|
||||||
id="speed.server_other_country",
|
id="speed.server_other_country",
|
||||||
|
|
@ -749,7 +808,9 @@ def speed_findings(speed: SpeedResult, bands: BufferbloatBands, client_country:
|
||||||
return findings
|
return findings
|
||||||
|
|
||||||
|
|
||||||
def _cpu_bound_ratio(tls_handshake_ms: float | None, tcp_rtt_ms: float | None) -> float | None:
|
def _cpu_bound_ratio(
|
||||||
|
tls_handshake_ms: float | None, tcp_rtt_ms: float | None
|
||||||
|
) -> float | None:
|
||||||
if tls_handshake_ms is None or not tcp_rtt_ms:
|
if tls_handshake_ms is None or not tcp_rtt_ms:
|
||||||
return None
|
return None
|
||||||
return tls_handshake_ms / tcp_rtt_ms
|
return tls_handshake_ms / tcp_rtt_ms
|
||||||
|
|
@ -775,7 +836,11 @@ def _check_tls_error(r: TlsResult) -> Finding | None:
|
||||||
def _check_tls_performance(r: TlsResult, t: Thresholds) -> list[Finding]:
|
def _check_tls_performance(r: TlsResult, t: Thresholds) -> list[Finding]:
|
||||||
findings: list[Finding] = []
|
findings: list[Finding] = []
|
||||||
ratio = _cpu_bound_ratio(r.tls_handshake_ms, r.tcp_rtt_ms)
|
ratio = _cpu_bound_ratio(r.tls_handshake_ms, r.tcp_rtt_ms)
|
||||||
if ratio is not None and ratio > t.tls_cpu_bound_ratio and (r.tls_handshake_ms or 0.0) >= 20.0:
|
if (
|
||||||
|
ratio is not None
|
||||||
|
and ratio > t.tls_cpu_bound_ratio
|
||||||
|
and (r.tls_handshake_ms or 0.0) >= 20.0
|
||||||
|
):
|
||||||
findings.append(
|
findings.append(
|
||||||
Finding(
|
Finding(
|
||||||
id=f"tls.server_cpu_bound.{r.label}",
|
id=f"tls.server_cpu_bound.{r.label}",
|
||||||
|
|
@ -937,9 +1002,21 @@ def prefix_findings(bench: PrefixBenchmark, t: Thresholds) -> list[Finding]:
|
||||||
|
|
||||||
|
|
||||||
_DPI_TEXT = {
|
_DPI_TEXT = {
|
||||||
"reset_injection": ("crit", "Possible active DPI interference on {t}", "Возможное активное вмешательство DPI на {t}"),
|
"reset_injection": (
|
||||||
"partial_filtering": ("warn", "Selective port filtering detected on {t}", "Обнаружена выборочная фильтрация портов на {t}"),
|
"crit",
|
||||||
"unreachable": ("info", "{t} did not respond on any probed port", "{t} не ответил ни на одном проверенном порту"),
|
"Possible active DPI interference on {t}",
|
||||||
|
"Возможное активное вмешательство DPI на {t}",
|
||||||
|
),
|
||||||
|
"partial_filtering": (
|
||||||
|
"warn",
|
||||||
|
"Selective port filtering detected on {t}",
|
||||||
|
"Обнаружена выборочная фильтрация портов на {t}",
|
||||||
|
),
|
||||||
|
"unreachable": (
|
||||||
|
"info",
|
||||||
|
"{t} did not respond on any probed port",
|
||||||
|
"{t} не ответил ни на одном проверенном порту",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,6 @@ class PingResult:
|
||||||
host: str
|
host: str
|
||||||
resolved_ip: str | None = None
|
resolved_ip: str | None = None
|
||||||
method: str = "none"
|
method: str = "none"
|
||||||
sent: int = 0
|
|
||||||
received: int = 0
|
received: int = 0
|
||||||
loss_pct: float = 0.0
|
loss_pct: float = 0.0
|
||||||
min_ms: float | None = None
|
min_ms: float | None = None
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ def summarize_ping(
|
||||||
host=host,
|
host=host,
|
||||||
resolved_ip=resolved_ip,
|
resolved_ip=resolved_ip,
|
||||||
method=method,
|
method=method,
|
||||||
sent=s.sent,
|
|
||||||
received=s.received,
|
received=s.received,
|
||||||
loss_pct=s.loss_pct,
|
loss_pct=s.loss_pct,
|
||||||
min_ms=s.min_ms,
|
min_ms=s.min_ms,
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ def modules() -> dict[str, ModuleResult]:
|
||||||
"latency": ModuleResult(
|
"latency": ModuleResult(
|
||||||
name="latency",
|
name="latency",
|
||||||
status="partial",
|
status="partial",
|
||||||
data=[PingResult(label="cloudflare-dns", host="1.1.1.1", method="icmp_win", sent=5, received=5)],
|
data=[PingResult(label="cloudflare-dns", host="1.1.1.1", method="icmp_win", received=5, samples=[12.0]*5)],
|
||||||
errors=[ProbeError(source="quad9-dns", kind="timeout", message="2s", retryable=True)],
|
errors=[ProbeError(source="quad9-dns", kind="timeout", message="2s", retryable=True)],
|
||||||
warnings=["quad9 did not answer"],
|
warnings=["quad9 did not answer"],
|
||||||
duration_ms=1500,
|
duration_ms=1500,
|
||||||
|
|
@ -420,7 +420,6 @@ def full_modules() -> dict[str, ModuleResult]:
|
||||||
label="cloudflare-dns",
|
label="cloudflare-dns",
|
||||||
host="1.1.1.1",
|
host="1.1.1.1",
|
||||||
method="icmp_win",
|
method="icmp_win",
|
||||||
sent=5,
|
|
||||||
received=5,
|
received=5,
|
||||||
avg_ms=12.4,
|
avg_ms=12.4,
|
||||||
min_ms=11.0,
|
min_ms=11.0,
|
||||||
|
|
@ -428,7 +427,7 @@ def full_modules() -> dict[str, ModuleResult]:
|
||||||
jitter_ms=1.9,
|
jitter_ms=1.9,
|
||||||
samples=[11.0, 12.0, None, 15.1, 12.4],
|
samples=[11.0, 12.0, None, 15.1, 12.4],
|
||||||
),
|
),
|
||||||
PingResult(label="github", host="github.com", method="tcp", sent=5, received=4, loss_pct=20.0, avg_ms=42.0),
|
PingResult(label="github", host="github.com", method="tcp", received=4, loss_pct=20.0, avg_ms=42.0, samples=[42.0, 42.0, 42.0, 42.0, None]),
|
||||||
],
|
],
|
||||||
duration_ms=2400,
|
duration_ms=2400,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ def ping(**kw) -> PingResult:
|
||||||
host="1.1.1.1",
|
host="1.1.1.1",
|
||||||
resolved_ip="1.1.1.1",
|
resolved_ip="1.1.1.1",
|
||||||
method="icmp_dgram",
|
method="icmp_dgram",
|
||||||
sent=20,
|
|
||||||
received=20,
|
received=20,
|
||||||
loss_pct=0.0,
|
loss_pct=0.0,
|
||||||
min_ms=10.0,
|
min_ms=10.0,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def test_summarize_ping_calculates_correct_stats():
|
||||||
assert result.host == "host.com"
|
assert result.host == "host.com"
|
||||||
assert result.resolved_ip == "1.1.1.1"
|
assert result.resolved_ip == "1.1.1.1"
|
||||||
assert result.method == "tcp"
|
assert result.method == "tcp"
|
||||||
assert result.sent == 4
|
assert len(result.samples) == 4
|
||||||
assert result.received == 4
|
assert result.received == 4
|
||||||
assert result.loss_pct == 0.0
|
assert result.loss_pct == 0.0
|
||||||
assert result.min_ms == 10.0
|
assert result.min_ms == 10.0
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,6 @@ def test_ping_result_round_trips_through_json():
|
||||||
host="1.1.1.1",
|
host="1.1.1.1",
|
||||||
resolved_ip="1.1.1.1",
|
resolved_ip="1.1.1.1",
|
||||||
method="icmp_win",
|
method="icmp_win",
|
||||||
sent=20,
|
|
||||||
received=20,
|
received=20,
|
||||||
loss_pct=0.0,
|
loss_pct=0.0,
|
||||||
min_ms=11.0,
|
min_ms=11.0,
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,10 @@ async def test_benchmark_prefixes_probes_selected_hosts_with_mocked_ping_host(mo
|
||||||
host=host,
|
host=host,
|
||||||
resolved_ip=host,
|
resolved_ip=host,
|
||||||
method="tcp",
|
method="tcp",
|
||||||
sent=count,
|
|
||||||
received=count,
|
received=count,
|
||||||
loss_pct=0.0,
|
loss_pct=0.0,
|
||||||
avg_ms=10.0,
|
avg_ms=10.0,
|
||||||
|
samples=[10.0] * count,
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setattr("netsleuth.probes.prefix_benchmark.ping_host", fake_ping_host)
|
monkeypatch.setattr("netsleuth.probes.prefix_benchmark.ping_host", fake_ping_host)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ def test_summarize_builds_a_complete_ping_result():
|
||||||
assert result.host == "1.1.1.1"
|
assert result.host == "1.1.1.1"
|
||||||
assert result.resolved_ip == "1.1.1.1"
|
assert result.resolved_ip == "1.1.1.1"
|
||||||
assert result.method == "icmp_dgram"
|
assert result.method == "icmp_dgram"
|
||||||
assert result.sent == 4
|
assert len(result.samples) == 4
|
||||||
assert result.received == 4
|
assert result.received == 4
|
||||||
assert result.loss_pct == 0.0
|
assert result.loss_pct == 0.0
|
||||||
assert result.min_ms == 10.0
|
assert result.min_ms == 10.0
|
||||||
|
|
@ -31,7 +31,7 @@ def test_summarize_builds_a_complete_ping_result():
|
||||||
def test_summarize_keeps_timeout_positions_in_the_sample_list():
|
def test_summarize_keeps_timeout_positions_in_the_sample_list():
|
||||||
result = summarize_ping("h", "example.test", None, "tcp", [None, 20.0, None, 24.0])
|
result = summarize_ping("h", "example.test", None, "tcp", [None, 20.0, None, 24.0])
|
||||||
assert result.samples == [None, 20.0, None, 24.0]
|
assert result.samples == [None, 20.0, None, 24.0]
|
||||||
assert result.sent == 4
|
assert len(result.samples) == 4
|
||||||
assert result.received == 2
|
assert result.received == 2
|
||||||
assert result.loss_pct == 50.0
|
assert result.loss_pct == 50.0
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ def test_summarize_of_a_single_sample_reports_zero_jitter():
|
||||||
|
|
||||||
def test_summarize_of_zero_probes_is_a_well_formed_empty_result():
|
def test_summarize_of_zero_probes_is_a_well_formed_empty_result():
|
||||||
result = summarize_ping("h", "1.1.1.1", None, "none", [])
|
result = summarize_ping("h", "1.1.1.1", None, "none", [])
|
||||||
assert result.sent == 0
|
assert len(result.samples) == 0
|
||||||
assert result.received == 0
|
assert result.received == 0
|
||||||
assert result.loss_pct == 0.0
|
assert result.loss_pct == 0.0
|
||||||
assert result.samples == []
|
assert result.samples == []
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ def ping(label: str, avg: float | None, loss: float = 0.0) -> PingResult:
|
||||||
host="1.1.1.1",
|
host="1.1.1.1",
|
||||||
resolved_ip="1.1.1.1",
|
resolved_ip="1.1.1.1",
|
||||||
method="icmp_win",
|
method="icmp_win",
|
||||||
sent=5,
|
|
||||||
received=5 if avg is not None else 0,
|
received=5 if avg is not None else 0,
|
||||||
loss_pct=loss,
|
loss_pct=loss,
|
||||||
avg_ms=avg,
|
avg_ms=avg,
|
||||||
jitter_ms=1.5 if avg is not None else None,
|
jitter_ms=1.5 if avg is not None else None,
|
||||||
|
samples=[avg] * 5 if avg is not None else [None] * 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue