WEB Carrier Counters + Status

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-31 21:39:09 +03:00
parent 66f2b8889f
commit 718ce0847e
51 changed files with 2252 additions and 453 deletions
+4 -2
View File
@@ -51,7 +51,8 @@ pub(super) async fn patch_config(
let active_config = shared.active_runtime.load_full().config();
let mut prepared =
prepare_patch_to_path(&shared.config_path, &patch_json, expected_revision).await?;
let resolved = resolve_reload_config(&active_config, &prepared.desired_config);
let resolved = resolve_reload_config(&active_config, &prepared.desired_config)
.map_err(ApiFailure::bad_request)?;
prepared.response.runtime_reload_required = resolved.runtime_changed;
prepared.response.process_restart_required = !resolved.deferred_process_fields.is_empty();
prepared.response.deferred_process_fields = resolved.deferred_process_fields;
@@ -205,7 +206,8 @@ async fn prepare_patch_to_path(
let revision = compute_snapshot_revision(&candidate);
let new_cfg = candidate.config;
let class = classify_config_changes(&old_cfg, &new_cfg);
let deferred_process_fields = deferred_process_fields(&old_cfg, &new_cfg);
let deferred_process_fields =
deferred_process_fields(&old_cfg, &new_cfg).map_err(ApiFailure::bad_request)?;
Ok(PreparedConfigPatch {
owner_path,
+6 -1
View File
@@ -19,7 +19,9 @@ use crate::web::manager::{ControlError, OperatorLifecycleError, SessionDetail, W
mod request;
// Ingress, capacity, and decoy telemetry remain separate availability planes.
mod observability;
use observability::{WebCapacityStatus, WebDecoyUpstreamStatus, WebIngressStatus};
use observability::{
WebCapacityStatus, WebCarrierNegotiationStatus, WebDecoyUpstreamStatus, WebIngressStatus,
};
use request::{
CloseRequest, DrainRequest, RuntimeInstanceRequest, parse_session_query, parse_session_ref,
valid_runtime_instance,
@@ -269,6 +271,7 @@ struct WebStatusData {
ingress: WebIngressStatus,
capacity: WebCapacityStatus,
decoy_upstream: WebDecoyUpstreamStatus,
carrier_negotiation: WebCarrierNegotiationStatus,
#[serde(skip_serializing_if = "Option::is_none")]
operator_lifecycle: Option<crate::web::manager::OperatorLifecycleStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -302,6 +305,7 @@ impl WebStatusData {
let ingress = WebIngressStatus::new(&publication, runtime.is_some());
let capacity = WebCapacityStatus::new(&publication, runtime, config);
let decoy_upstream = WebDecoyUpstreamStatus::new(&publication);
let carrier_negotiation = WebCarrierNegotiationStatus::new(&publication);
Self {
lifecycle: publication.lifecycle.as_str(),
lifecycle_epoch: publication.epoch,
@@ -317,6 +321,7 @@ impl WebStatusData {
ingress,
capacity,
decoy_upstream,
carrier_negotiation,
operator_lifecycle,
runtime: runtime.map(WebProcessRuntime::try_status),
}
+40
View File
@@ -4,6 +4,9 @@ use crate::config::{ProxyConfig, WebHttpConnectionCapacityAction};
use crate::web::control::{WebRuntimeLifecycle, WebRuntimePublication};
use crate::web::manager::{WebCapacityResourceStatus, WebCapacitySnapshot, WebProcessRuntime};
use crate::web::telemetry::{WebOutcomeCounter, WebRejectionCounter};
use crate::web::telemetry::{
WebCarrierFailureCounter, WebCarrierLearningCounter, WebCarrierSelectionCounter,
};
/// Private WEB ingress state owned by this Telemt process.
#[derive(Serialize)]
@@ -117,6 +120,25 @@ impl WebDecoyUpstreamStatus {
}
}
/// Fixed-cardinality process-lifetime carrier negotiation counters.
#[derive(Serialize)]
pub(super) struct WebCarrierNegotiationStatus {
selections: Vec<WebCarrierSelectionCounter>,
reported_failures: Vec<WebCarrierFailureCounter>,
learning_outcomes: Vec<WebCarrierLearningCounter>,
}
impl WebCarrierNegotiationStatus {
/// Builds counters from publication ownership even when runtime state is unavailable.
pub(super) fn new(publication: &WebRuntimePublication) -> Self {
Self {
selections: publication.telemetry.carrier_selection_counters(),
reported_failures: publication.telemetry.carrier_failure_counters(),
learning_outcomes: publication.telemetry.carrier_learning_counters(),
}
}
}
#[cfg(test)]
mod tests {
use crate::config::ProxyConfig;
@@ -143,6 +165,8 @@ mod tests {
serde_json::to_value(super::WebCapacityStatus::new(&publication, None, &config))
.unwrap();
let decoy = serde_json::to_value(super::WebDecoyUpstreamStatus::new(&publication)).unwrap();
let carrier =
serde_json::to_value(super::WebCarrierNegotiationStatus::new(&publication)).unwrap();
assert_eq!(
capacity["rejections"].as_array().unwrap().len(),
@@ -160,5 +184,21 @@ mod tests {
crate::web::telemetry::WebDecoyUpstreamOutcome::ALL.len()
);
assert_eq!(capacity["partial"][0], "runtime");
assert_eq!(
carrier["selections"].as_array().unwrap().len(),
crate::config::WebCarrier::ALL.len()
* crate::web::telemetry::WebCarrierSelectionDisposition::ALL.len()
);
assert_eq!(
carrier["reported_failures"].as_array().unwrap().len(),
crate::config::WebCarrier::ALL.len()
* crate::web::telemetry::WebCarrierFailurePhase::ALL.len()
* crate::web::manager::CarrierFailure::ALL.len()
);
assert_eq!(
carrier["learning_outcomes"].as_array().unwrap().len(),
crate::config::WebCarrier::ALL.len()
* crate::web::telemetry::WebCarrierLearningOutcome::ALL.len()
);
}
}