ソースを参照

增加对比测试工具

Gogs 7 時間 前
コミット
0998518695
5 ファイル変更715 行追加514 行削除
  1. 146 42
      cli.py
  2. 1 0
      config.json
  3. 39 1
      relay_server.py
  4. 519 471
      scripts/benchmark_local.py
  5. 10 0
      socks_edge.py

+ 146 - 42
cli.py

@@ -21,12 +21,98 @@ TCP_WIN_RE = re.compile(
 UDP_WIN_RE = re.compile(
     r"udp flow=(?P<flow>\d+) winner=(?P<winner>\S+) target=(?P<host>[^:]+):(?P<port>\d+)"
 )
+UDP_SOCKS_SUMMARY_RE = re.compile(
+    r"udp summary bind=(?P<bind>\S+) flows=(?P<flows>\d+) winners=(?P<winners>\d+) "
+    r"winner_breakdown=direct=(?P<direct>\d+),relay=(?P<relay>\d+) "
+    r"sample=(?P<sample>.*?) candidates=(?P<candidates>\[.*?\]) "
+    r"sent=(?P<sent>\d+) recv=(?P<recv>\d+) dup=(?P<dup>\d+) "
+    r"direct_paths=(?P<direct_paths>\d+) relay_paths=(?P<relay_paths>\d+) relay_errors=(?P<relay_errors>.*)"
+)
 
 
 def normalize_winner(name: str) -> str:
     return "direct" if name.startswith("direct") else name
 
 
+def parse_summary_log(log_path: Path) -> dict[str, object]:
+    tcp_total = 0
+    tcp_direct = 0
+    tcp_relay = 0
+    tcp_winners: dict[str, int] = defaultdict(int)
+    tcp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
+
+    udp_total = 0
+    udp_direct = 0
+    udp_relay = 0
+    udp_winners: dict[str, int] = defaultdict(int)
+    udp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
+    udp_snapshot: dict[str, object] | None = None
+
+    for line in log_path.read_text(errors="replace").splitlines():
+        tcp_match = TCP_WIN_RE.search(line)
+        if tcp_match:
+            tcp_total += 1
+            winner = normalize_winner(tcp_match.group("winner"))
+            host = tcp_match.group("host")
+            port = tcp_match.group("port")
+            key = f"{host}:{port}"
+            tcp_winners[winner] += 1
+            tcp_targets[key][winner] += 1
+            if winner == "direct":
+                tcp_direct += 1
+            else:
+                tcp_relay += 1
+            continue
+
+        udp_match = UDP_WIN_RE.search(line)
+        if udp_match:
+            udp_total += 1
+            winner = normalize_winner(udp_match.group("winner"))
+            host = udp_match.group("host")
+            port = udp_match.group("port")
+            key = f"{host}:{port}"
+            udp_winners[winner] += 1
+            udp_targets[key][winner] += 1
+            if winner == "direct":
+                udp_direct += 1
+            else:
+                udp_relay += 1
+            continue
+
+        udp_snapshot_match = UDP_SOCKS_SUMMARY_RE.search(line)
+        if udp_snapshot_match:
+            udp_snapshot = {
+                "bind": udp_snapshot_match.group("bind"),
+                "flows": int(udp_snapshot_match.group("flows")),
+                "winners": int(udp_snapshot_match.group("winners")),
+                "direct": int(udp_snapshot_match.group("direct")),
+                "relay": int(udp_snapshot_match.group("relay")),
+                "sample": udp_snapshot_match.group("sample"),
+                "candidates": udp_snapshot_match.group("candidates"),
+                "sent": int(udp_snapshot_match.group("sent")),
+                "recv": int(udp_snapshot_match.group("recv")),
+                "dup": int(udp_snapshot_match.group("dup")),
+                "direct_paths": int(udp_snapshot_match.group("direct_paths")),
+                "relay_paths": int(udp_snapshot_match.group("relay_paths")),
+                "relay_errors": udp_snapshot_match.group("relay_errors"),
+            }
+
+    return {
+        "path": log_path,
+        "tcp_total": tcp_total,
+        "tcp_direct": tcp_direct,
+        "tcp_relay": tcp_relay,
+        "tcp_winners": tcp_winners,
+        "tcp_targets": tcp_targets,
+        "udp_total": udp_total,
+        "udp_direct": udp_direct,
+        "udp_relay": udp_relay,
+        "udp_winners": udp_winners,
+        "udp_targets": udp_targets,
+        "udp_snapshot": udp_snapshot,
+    }
+
+
 def build_parser() -> argparse.ArgumentParser:
     parser = argparse.ArgumentParser(prog="mynetspeeder")
     parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
@@ -98,48 +184,34 @@ def handle_summary(args: argparse.Namespace) -> int:
     log_path = Path(args.log_file)
     if not log_path.exists():
         raise SystemExit(f"log file not found: {log_path}")
-
-    tcp_total = 0
-    tcp_direct = 0
-    tcp_relay = 0
-    tcp_winners: dict[str, int] = defaultdict(int)
-    tcp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
-
-    udp_total = 0
-    udp_direct = 0
-    udp_relay = 0
-    udp_winners: dict[str, int] = defaultdict(int)
-    udp_targets: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
-
-    for line in log_path.read_text(errors="replace").splitlines():
-        tcp_match = TCP_WIN_RE.search(line)
-        if tcp_match:
-            tcp_total += 1
-            winner = normalize_winner(tcp_match.group("winner"))
-            host = tcp_match.group("host")
-            port = tcp_match.group("port")
-            key = f"{host}:{port}"
-            tcp_winners[winner] += 1
-            tcp_targets[key][winner] += 1
-            if winner == "direct":
-                tcp_direct += 1
-            else:
-                tcp_relay += 1
-            continue
-
-        udp_match = UDP_WIN_RE.search(line)
-        if udp_match:
-            udp_total += 1
-            winner = normalize_winner(udp_match.group("winner"))
-            host = udp_match.group("host")
-            port = udp_match.group("port")
-            key = f"{host}:{port}"
-            udp_winners[winner] += 1
-            udp_targets[key][winner] += 1
-            if winner == "direct":
-                udp_direct += 1
-            else:
-                udp_relay += 1
+    parsed = parse_summary_log(log_path)
+    tcp_total = parsed["tcp_total"]
+    tcp_direct = parsed["tcp_direct"]
+    tcp_relay = parsed["tcp_relay"]
+    tcp_winners = parsed["tcp_winners"]
+    tcp_targets = parsed["tcp_targets"]
+    udp_total = parsed["udp_total"]
+    udp_direct = parsed["udp_direct"]
+    udp_relay = parsed["udp_relay"]
+    udp_winners = parsed["udp_winners"]
+    udp_targets = parsed["udp_targets"]
+    udp_snapshot = parsed["udp_snapshot"]
+
+    if udp_total == 0:
+        sibling_logs = [log_path.with_name("mynetspeeder-socks.log"), log_path.with_name("mynetspeeder-relay.log")]
+        for sibling in sibling_logs:
+            if not sibling.exists():
+                continue
+            sibling_parsed = parse_summary_log(sibling)
+            if sibling_parsed["udp_total"] == 0 and sibling_parsed["udp_snapshot"] is None:
+                continue
+            udp_total = sibling_parsed["udp_total"]
+            udp_direct = sibling_parsed["udp_direct"]
+            udp_relay = sibling_parsed["udp_relay"]
+            udp_winners = sibling_parsed["udp_winners"]
+            udp_targets = sibling_parsed["udp_targets"]
+            udp_snapshot = sibling_parsed["udp_snapshot"]
+            break
 
     tcp_ordered_targets = sorted(
         tcp_targets.items(),
@@ -179,9 +251,34 @@ def handle_summary(args: argparse.Namespace) -> int:
                 }
                 for target, counts in udp_ordered_targets
             ],
+            "snapshot": udp_snapshot,
         },
     }
 
+    if udp_total == 0 and udp_snapshot is not None:
+        result["udp"]["total"] = udp_snapshot["winners"]
+        result["udp"]["direct"] = udp_snapshot["direct"]
+        result["udp"]["relay"] = udp_snapshot["relay"]
+    if not result["udp"]["winners"] and udp_snapshot is not None:
+        snapshot_winners: dict[str, int] = {}
+        if int(udp_snapshot["direct"]) > 0:
+            snapshot_winners["direct"] = int(udp_snapshot["direct"])
+        if int(udp_snapshot["relay"]) > 0:
+            snapshot_winners["relay"] = int(udp_snapshot["relay"])
+        result["udp"]["winners"] = snapshot_winners
+    if not result["udp"]["targets"] and udp_snapshot is not None:
+        snapshot_target_wins: dict[str, int] = {}
+        if int(udp_snapshot["direct"]) > 0:
+            snapshot_target_wins["direct"] = int(udp_snapshot["direct"])
+        if int(udp_snapshot["relay"]) > 0:
+            snapshot_target_wins["relay"] = int(udp_snapshot["relay"])
+        result["udp"]["targets"] = [
+            {
+                "target": f"snapshot@{udp_snapshot['bind']}",
+                "wins": snapshot_target_wins,
+            }
+        ]
+
     if args.json:
         print(json.dumps(result, ensure_ascii=False, indent=2))
         return 0
@@ -190,6 +287,13 @@ def handle_summary(args: argparse.Namespace) -> int:
     for protocol in ("tcp", "udp"):
         section = result[protocol]
         print(f"{protocol}: total={section['total']} direct={section['direct']} relay={section['relay']}")
+        if protocol == "udp" and section.get("snapshot"):
+            snapshot = section["snapshot"]
+            print(
+                f"  snapshot: bind={snapshot['bind']} flows={snapshot['flows']} winners={snapshot['winners']} "
+                f"direct_paths={snapshot['direct_paths']} relay_paths={snapshot['relay_paths']} dup={snapshot['dup']} "
+                f"candidates={snapshot['candidates']}"
+            )
         print("winners:")
         for name, count in section["winners"].items():
             print(f"  {name}: {count}")

+ 1 - 0
config.json

@@ -24,6 +24,7 @@
   "socks_host": "127.0.0.1",
   "socks_port": 19180,
   "relays": [
+    {"name": "hk1", "host": "23.95.134.159", "port": 9009, "token": "130", "weight": 100},
     {"name": "hk2", "host": "23.238.9.140", "port": 9009, "token": "130", "weight": 100}
   ]
 }

+ 39 - 1
relay_server.py

@@ -26,6 +26,11 @@ class UdpSession:
     host: str = ""
     port: int = 0
     family: int = 0
+    created_at: float = 0.0
+    last_seen_at: float = 0.0
+    packets_sent: int = 0
+    packets_received: int = 0
+    first_seen_logged: bool = False
 
 
 class RelayUdpProtocol(asyncio.DatagramProtocol):
@@ -37,6 +42,18 @@ class RelayUdpProtocol(asyncio.DatagramProtocol):
     def datagram_received(self, data: bytes, _addr) -> None:
         if self.channel.closed:
             return
+        session = self.channel.udp_sessions.get((self.session_id, self.stream_id))
+        if session is not None:
+            session.packets_received += 1
+            session.last_seen_at = time.monotonic()
+            if not session.first_seen_logged:
+                session.first_seen_logged = True
+                lived = session.last_seen_at - session.created_at if session.created_at else 0.0
+                print(
+                    f"[relay] udp session first_packet peer={self.channel.writer.get_extra_info('peername')} "
+                    f"target={session.host}:{session.port} session={session.session_id} stream={session.stream_id} "
+                    f"family={session.family} lived={lived:.1f}s recv={session.packets_received} sent={session.packets_sent}"
+                )
         asyncio.create_task(self.channel.safe_send(Frame(UDP_RECV, self.session_id, self.stream_id, 0, 0, data)))
 
 
@@ -153,13 +170,28 @@ class RelayChannel:
                         remote_addr=(meta["host"], int(meta["port"])),
                         family=family or 0,
                     )
-                    session = UdpSession(frame.session_id, frame.stream_id, transport, protocol, meta["host"], int(meta["port"]), family)
+                    session = UdpSession(
+                        frame.session_id,
+                        frame.stream_id,
+                        transport,
+                        protocol,
+                        meta["host"],
+                        int(meta["port"]),
+                        family,
+                        created_at=time.monotonic(),
+                    )
                     self.udp_sessions[key] = session
+                    print(
+                        f"[relay] udp session open peer={peer} target={session.host}:{session.port} "
+                        f"session={session.session_id} stream={session.stream_id} family={session.family}"
+                    )
                 except Exception as exc:
                     await self.safe_send(Frame(TCP_STATUS, frame.session_id, frame.stream_id, 0, STATUS_ERR, str(exc).encode()))
                     return
             if session.transport is not None:
                 with contextlib.suppress(Exception):
+                    session.packets_sent += 1
+                    session.last_seen_at = time.monotonic()
                     session.transport.sendto(payload)
             return
 
@@ -201,6 +233,12 @@ class RelayChannel:
             await self._close_tcp(key)
         for session in self.udp_sessions.values():
             if session.transport:
+                lived = time.monotonic() - session.created_at if session.created_at else 0.0
+                print(
+                    f"[relay] udp session closed target={session.host}:{session.port} "
+                    f"session={session.session_id} stream={session.stream_id} family={session.family} "
+                    f"lived={lived:.1f}s sent={session.packets_sent} recv={session.packets_received}"
+                )
                 session.transport.close()
         self.udp_sessions.clear()
         self.writer.close()

ファイルの差分が大きいため隠しています
+ 519 - 471
scripts/benchmark_local.py


+ 10 - 0
socks_edge.py

@@ -326,6 +326,14 @@ class UdpAssociateServer(asyncio.DatagramProtocol):
         duplicates = sum(flow.duplicate_responses for flow in self.client_flows.values())
         direct_paths = sum(len(flow.direct_sockets) for flow in self.client_flows.values())
         relay_candidates = sum(len(flow.link_streams) for flow in self.client_flows.values())
+        candidate_names: list[str] = []
+        seen_candidates: set[str] = set()
+        for flow in sorted(self.client_flows.values(), key=lambda item: item.flow_id):
+            for name in flow.candidate_names:
+                if name in seen_candidates:
+                    continue
+                seen_candidates.add(name)
+                candidate_names.append(name)
         direct_wins = sum(1 for flow in self.client_flows.values() if flow.winner_name and flow.winner_name.startswith("direct"))
         relay_wins = winners - direct_wins
         sample_flows = [
@@ -343,6 +351,7 @@ class UdpAssociateServer(asyncio.DatagramProtocol):
             print(
                 f"[edge] udp summary bind={self.client_addr[0]}:{self.client_addr[1]} flows={active_flows} winners={winners} "
                 f"winner_breakdown=direct={direct_wins},relay={relay_wins} sample={winner_detail} "
+                f"candidates={candidate_names or ['none']} "
                 f"sent={packets_sent} recv={packets_received} dup={duplicates} "
                 f"direct_paths={direct_paths} relay_paths={relay_candidates} relay_errors={relay_error_detail}"
             )
@@ -350,6 +359,7 @@ class UdpAssociateServer(asyncio.DatagramProtocol):
             print(
                 f"[edge] udp summary bind=unbound flows={active_flows} winners={winners} "
                 f"winner_breakdown=direct={direct_wins},relay={relay_wins} sample={winner_detail} "
+                f"candidates={candidate_names or ['none']} "
                 f"sent={packets_sent} recv={packets_received} dup={duplicates} "
                 f"direct_paths={direct_paths} relay_paths={relay_candidates} relay_errors={relay_error_detail}"
             )

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません