feat(client): send stats snapshots to relay

Client counters were only local diagnostics even though the control protocol and
relay now understand Stats messages. Add a client-core sender that opens a
peer-to-relay unidirectional stream with the current TunnelStats snapshot.

The Windows client reports stats whenever it prints its diagnostics snapshot.
Failures are logged instead of stopping the frame pump because stats reporting
is diagnostic and should not be the reason a live tunnel goes down.

The client-core integration test now has the server decode the stats stream
before shutdown so the send path is covered without a timing race.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core \
  connects_to_relay_control_stream_as_client -- --nocapture
- cargo test -p lanparty-client-core
- cargo test -p lanparty-client-win
- cargo clippy -p lanparty-client-core --all-targets -- -D warnings
- cargo clippy -p lanparty-client-win --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
This commit is contained in:
2026-05-21 20:53:37 +02:00
parent dffb490afe
commit 60c41471fb
3 changed files with 64 additions and 14 deletions
+21 -12
View File
@@ -145,11 +145,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
verify_relay_route_is_pinned(session.config().relay_addr().ip(), relay_route_pin)
.context("relay route changed after TAP activation")?;
print_verified_relay_route(&relay_route);
print_client_diagnostics(&client_diagnostics_snapshot(
print_and_report_client_diagnostics(
session,
true,
tap_diagnostics.clone(),
));
&client_diagnostics_snapshot(session, true, tap_diagnostics.clone()),
)
.await;
println!(
"bridging TAP frames; relay route is pinned and TAP route policy is scoped; press Ctrl-C to stop"
);
@@ -184,11 +184,10 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
.context("relay route changed while bridging")?;
}
_ = diagnostics_check.tick() => {
print_client_diagnostics(&client_diagnostics_snapshot(
print_and_report_client_diagnostics(
session,
true,
tap_diagnostics.clone(),
));
&client_diagnostics_snapshot(session, true, tap_diagnostics.clone()),
).await;
}
}
}
@@ -197,11 +196,11 @@ async fn run_client(session: &ClientSession, relay_route_pin: &PinnedRelayRoute)
#[cfg(not(windows))]
async fn run_client(session: &ClientSession) -> Result<()> {
open_tap_adapter(session);
print_client_diagnostics(&client_diagnostics_snapshot(
print_and_report_client_diagnostics(
session,
false,
TapDiagnostics::new(false, None, None, None),
));
&client_diagnostics_snapshot(session, false, TapDiagnostics::new(false, None, None, None)),
)
.await;
println!("TAP frame pump and route pinning are not wired yet; press Ctrl-C to stop");
let control_events = run_control_event_log(session);
@@ -386,6 +385,16 @@ fn print_client_diagnostics(diagnostics: &ClientDiagnostics) {
println!("{}", format_client_diagnostics(diagnostics));
}
async fn print_and_report_client_diagnostics(
session: &ClientSession,
diagnostics: &ClientDiagnostics,
) {
print_client_diagnostics(diagnostics);
if let Err(error) = session.send_stats_snapshot().await {
eprintln!("failed to send client stats to relay: {error:#}");
}
}
fn format_client_diagnostics(diagnostics: &ClientDiagnostics) -> String {
let stats = diagnostics.stats();
format!(