sync: print probe endpoint versions
This commit is contained in:
parent
b948994811
commit
7a8150b2db
@ -174,5 +174,6 @@ Context: the mirrored browser probe finally reproduced the real failure class on
|
||||
- Replaying the 0.16.21 artifact after 0.16.22 analyzer hardening changes the verdict from false `catastrophic_failure` to `gross_failure`: p95 `273.8 ms`, median `-188.4 ms`, 7 paired coded pulses. The raw activity-start delta (`-3620.7 ms`) is still printed, but it is ignored for verdict/calibration because it disagrees with coded pairs by `3432.3 ms`; unpaired video/audio onsets are printed for triage.
|
||||
- 0.16.22 live mirrored run still failed with p95 `433.7 ms`, median `-359.4 ms`, and 5 paired coded pulses. Client telemetry showed camera uplink `latest_age_ms` repeatedly around `300-350 ms`, matching the measured skew; patch 0.16.23 to make video queues latest-only instead of draining stale-but-under-budget backlog.
|
||||
- 0.16.23 local validation passed for fresh-queue behavior, uplink/probe freshness contracts, sync analyzer tests, client/server binary checks, and whitespace checks.
|
||||
- 0.16.23 live mirrored run improved to p95 `215.2 ms`, median `+142.2 ms`, 13 paired coded pulses, and raw activity alignment within `6.6 ms` of coded pairs. Patch 0.16.24 makes the probe print local client and remote server versions before capture so every run records what was actually tested.
|
||||
- [ ] Re-run the mirrored browser probe after the pre-start false-positive fix.
|
||||
- [ ] Run Google Meet manual validation.
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use anyhow::{Context, Result, bail};
|
||||
#[cfg(not(coverage))]
|
||||
use lesavka_common::lesavka::Empty;
|
||||
use lesavka_common::lesavka::{
|
||||
CapturePowerCommand, SetCapturePowerRequest, relay_client::RelayClient,
|
||||
CapturePowerCommand, HandshakeSet, SetCapturePowerRequest, relay_client::RelayClient,
|
||||
};
|
||||
#[cfg(not(coverage))]
|
||||
use lesavka_common::lesavka::{Empty, handshake_client::HandshakeClient};
|
||||
#[cfg(not(coverage))]
|
||||
use tonic::Request;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
@ -13,6 +13,7 @@ use lesavka_client::relay_transport;
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum CommandKind {
|
||||
Status,
|
||||
Version,
|
||||
Auto,
|
||||
On,
|
||||
Off,
|
||||
@ -27,6 +28,7 @@ impl CommandKind {
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
"status" | "get" => Some(Self::Status),
|
||||
"version" | "versions" => Some(Self::Version),
|
||||
"auto" => Some(Self::Auto),
|
||||
"on" | "force-on" => Some(Self::On),
|
||||
"off" | "force-off" => Some(Self::Off),
|
||||
@ -52,7 +54,7 @@ enum ParseOutcome {
|
||||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
|
||||
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|version|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
|
||||
}
|
||||
|
||||
fn parse_args_outcome_from<I, S>(args: I) -> Result<ParseOutcome>
|
||||
@ -111,6 +113,7 @@ fn parse_args() -> Result<ParseOutcome> {
|
||||
fn capture_power_request(command: CommandKind) -> Option<SetCapturePowerRequest> {
|
||||
let (enabled, command) = match command {
|
||||
CommandKind::Status
|
||||
| CommandKind::Version
|
||||
| CommandKind::RecoverUsb
|
||||
| CommandKind::RecoverUac
|
||||
| CommandKind::RecoverUvc
|
||||
@ -136,6 +139,21 @@ async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
|
||||
Ok(RelayClient::new(channel))
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
async fn get_server_capabilities(server_addr: &str) -> Result<HandshakeSet> {
|
||||
let channel = relay_transport::endpoint(server_addr)?
|
||||
.tcp_nodelay(true)
|
||||
.connect()
|
||||
.await
|
||||
.with_context(|| format!("connecting to handshake at {server_addr}"))?;
|
||||
let mut client = HandshakeClient::new(channel);
|
||||
Ok(client
|
||||
.get_capabilities(Request::new(Empty {}))
|
||||
.await
|
||||
.context("querying server capabilities")?
|
||||
.into_inner())
|
||||
}
|
||||
|
||||
#[cfg(coverage)]
|
||||
async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
|
||||
let channel = relay_transport::endpoint(server_addr)?
|
||||
@ -154,6 +172,20 @@ fn print_state(state: lesavka_common::lesavka::CapturePowerState) {
|
||||
println!("detail={}", state.detail);
|
||||
}
|
||||
|
||||
fn print_versions(server_addr: &str, caps: &HandshakeSet) {
|
||||
let server_version = if caps.server_version.is_empty() {
|
||||
"unknown"
|
||||
} else {
|
||||
caps.server_version.as_str()
|
||||
};
|
||||
println!("client_version={}", lesavka_client::VERSION);
|
||||
println!("client_full_version={}", lesavka_client::FULL_VERSION);
|
||||
println!("server_addr={server_addr}");
|
||||
println!("server_version={server_version}");
|
||||
println!("server_camera_output={}", caps.camera_output);
|
||||
println!("server_camera_codec={}", caps.camera_codec);
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
@ -164,6 +196,13 @@ async fn main() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if config.command == CommandKind::Version {
|
||||
let caps = get_server_capabilities(config.server.as_str()).await?;
|
||||
print_versions(config.server.as_str(), &caps);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut client = connect(config.server.as_str()).await?;
|
||||
|
||||
if let Some(request) = capture_power_request(config.command) {
|
||||
@ -172,6 +211,7 @@ async fn main() -> Result<()> {
|
||||
CommandKind::On => "forcing capture power on",
|
||||
CommandKind::Off => "forcing capture power off",
|
||||
CommandKind::Status
|
||||
| CommandKind::Version
|
||||
| CommandKind::RecoverUsb
|
||||
| CommandKind::RecoverUac
|
||||
| CommandKind::RecoverUvc
|
||||
@ -228,7 +268,9 @@ async fn main() -> Result<()> {
|
||||
println!("ok={}", reply.ok);
|
||||
return Ok(());
|
||||
}
|
||||
CommandKind::Auto | CommandKind::On | CommandKind::Off => unreachable!(),
|
||||
CommandKind::Version | CommandKind::Auto | CommandKind::On | CommandKind::Off => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
print_state(reply);
|
||||
@ -253,6 +295,8 @@ mod tests {
|
||||
fn command_aliases_parse_to_stable_actions() {
|
||||
assert_eq!(CommandKind::parse("status"), Some(CommandKind::Status));
|
||||
assert_eq!(CommandKind::parse("get"), Some(CommandKind::Status));
|
||||
assert_eq!(CommandKind::parse("version"), Some(CommandKind::Version));
|
||||
assert_eq!(CommandKind::parse("versions"), Some(CommandKind::Version));
|
||||
assert_eq!(CommandKind::parse("force-on"), Some(CommandKind::On));
|
||||
assert_eq!(CommandKind::parse("force-off"), Some(CommandKind::Off));
|
||||
assert_eq!(
|
||||
@ -342,6 +386,7 @@ mod tests {
|
||||
assert_eq!(off.command, CapturePowerCommand::ForceOff as i32);
|
||||
|
||||
assert!(capture_power_request(CommandKind::Status).is_none());
|
||||
assert!(capture_power_request(CommandKind::Version).is_none());
|
||||
assert!(capture_power_request(CommandKind::RecoverUsb).is_none());
|
||||
assert!(capture_power_request(CommandKind::RecoverUac).is_none());
|
||||
assert!(capture_power_request(CommandKind::RecoverUvc).is_none());
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -190,6 +190,37 @@ systemctl is-active lesavka-server lesavka-uvc lesavka-core >/dev/null
|
||||
REMOTE_PREFLIGHT
|
||||
}
|
||||
|
||||
print_lesavka_versions() {
|
||||
echo "==> Lesavka versions under test"
|
||||
if [[ ! -x "${REPO_ROOT}/target/debug/lesavka-relayctl" ]]; then
|
||||
(cd "${REPO_ROOT}" && cargo build -p lesavka_client --bin lesavka-relayctl >/dev/null)
|
||||
fi
|
||||
local version_output
|
||||
if ! version_output="$(
|
||||
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
|
||||
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
|
||||
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
|
||||
version 2>&1
|
||||
)"; then
|
||||
echo "failed to query Lesavka versions through ${RESOLVED_LESAVKA_SERVER_ADDR}" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -q "^client_version=" <<<"${version_output}"; then
|
||||
echo "Lesavka version query did not report client_version=; refusing to run an unattributed probe" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -q "^server_version=" <<<"${version_output}"; then
|
||||
echo "Lesavka version query did not report server_version=; refusing to run an unattributed probe" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
[[ -n "${line}" ]] && echo " ↪ ${line}"
|
||||
done <<<"${version_output}"
|
||||
}
|
||||
|
||||
if [[ "${LOCAL_AUDIO_SANITY}" != "0" ]]; then
|
||||
echo "==> verifying local speaker-to-mic sanity before upstream sync run"
|
||||
"${SCRIPT_DIR}/run_local_audio_sanity.sh"
|
||||
@ -199,7 +230,7 @@ if [[ "${PROBE_PREBUILD}" != "0" ]]; then
|
||||
echo "==> prebuilding sync probe/analyzer before opening the capture window"
|
||||
(
|
||||
cd "${REPO_ROOT}"
|
||||
cargo build -p lesavka_client --bin lesavka-sync-probe --bin lesavka-sync-analyze
|
||||
cargo build -p lesavka_client --bin lesavka-sync-probe --bin lesavka-sync-analyze --bin lesavka-relayctl
|
||||
)
|
||||
fi
|
||||
|
||||
@ -218,6 +249,7 @@ if [[ -n "${SERVER_TUNNEL_PID}" ]]; then
|
||||
echo " ↪ tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${SERVER_TUNNEL_REMOTE_PORT}"
|
||||
fi
|
||||
|
||||
print_lesavka_versions
|
||||
preflight_server_path
|
||||
|
||||
echo "==> starting Tethys capture on ${TETHYS_HOST}"
|
||||
|
||||
@ -154,6 +154,37 @@ start_server_tunnel_if_needed() {
|
||||
echo " ↪ tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${LESAVKA_SERVER_PORT}"
|
||||
}
|
||||
|
||||
print_lesavka_versions() {
|
||||
echo "==> Lesavka versions under test"
|
||||
if [[ ! -x "${REPO_ROOT}/target/debug/lesavka-relayctl" ]]; then
|
||||
(cd "${REPO_ROOT}" && cargo build -p lesavka_client --bin lesavka-relayctl >/dev/null)
|
||||
fi
|
||||
local version_output
|
||||
if ! version_output="$(
|
||||
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
|
||||
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
|
||||
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
|
||||
version 2>&1
|
||||
)"; then
|
||||
echo "failed to query Lesavka versions through ${RESOLVED_LESAVKA_SERVER_ADDR}" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -q "^client_version=" <<<"${version_output}"; then
|
||||
echo "Lesavka version query did not report client_version=; refusing to run an unattributed probe" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! grep -q "^server_version=" <<<"${version_output}"; then
|
||||
echo "Lesavka version query did not report server_version=; refusing to run an unattributed probe" >&2
|
||||
echo "${version_output}" >&2
|
||||
return 1
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
[[ -n "${line}" ]] && echo " ↪ ${line}"
|
||||
done <<<"${version_output}"
|
||||
}
|
||||
|
||||
start_local_stimulus() {
|
||||
echo "==> starting local A/V stimulus server"
|
||||
python3 "${REPO_ROOT}/scripts/manual/local_av_stimulus.py" \
|
||||
@ -230,10 +261,11 @@ run_browser_capture_with_real_driver() {
|
||||
echo "==> prebuilding real client and analyzer"
|
||||
(
|
||||
cd "${REPO_ROOT}"
|
||||
cargo build -p lesavka_client --bin lesavka-client --bin lesavka-sync-analyze >/dev/null
|
||||
cargo build -p lesavka_client --bin lesavka-client --bin lesavka-sync-analyze --bin lesavka-relayctl >/dev/null
|
||||
)
|
||||
|
||||
start_server_tunnel_if_needed
|
||||
print_lesavka_versions
|
||||
start_local_stimulus
|
||||
start_real_lesavka_client
|
||||
run_browser_capture_with_real_driver
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.16.23"
|
||||
version = "0.16.24"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -47,6 +47,10 @@ fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() {
|
||||
"Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.",
|
||||
"artifact_dir: ${LOCAL_REPORT_DIR}",
|
||||
"events_csv: ${LOCAL_EVENTS_CSV}",
|
||||
"==> Lesavka versions under test",
|
||||
"lesavka-relayctl",
|
||||
"--bin lesavka-relayctl",
|
||||
"server_version=",
|
||||
] {
|
||||
assert!(
|
||||
SYNC_SCRIPT.contains(expected),
|
||||
@ -92,6 +96,10 @@ fn mirrored_sync_script_uses_real_client_capture_path() {
|
||||
"run_upstream_browser_av_sync.sh",
|
||||
"wait_for_stimulus_page_ready 15",
|
||||
"Point the real webcam at the stimulus window",
|
||||
"==> Lesavka versions under test",
|
||||
"lesavka-relayctl",
|
||||
"--bin lesavka-relayctl",
|
||||
"server_version=",
|
||||
] {
|
||||
assert!(
|
||||
MIRRORED_SYNC_SCRIPT.contains(expected),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user