Synced from monorepo
Synced from monorepo Changes: - Workspace server: surface preview-proxy metrics through the hub metric pump - Shell: reclaim a session’s retained state in one entry - Shell: reclaim a session’s resident state in one entry - Pager: withhold key event types from Alacritty builds that double keys - Tools: cancel a session’s subagents when it closes - Pager: keep the whole plan in scrollback and separate reasoning from output in minimal mode - Pager: probe terminal version over DA2 and include it with feedback - SuperGrok Plus: identity, CLI, and analytics tier surfaces - Shell: inherit the session process scope into subagents - Pager: build @-file-search matcher lazily on first use - Tools: fix description and output contradictions in tool definitions - Workspace: degrade @-file-search instead of aborting on thread exhaustion - Tools: reap a session’s LSP servers when it closes - Tools: fix contradictions and defects in tool descriptions, schemas, and harness pools - MCP: reap stdio MCP children on session close - Shell: reuse spawn-time skill discovery for session telemetry - Tools: stop leaking shell-wrapper positional params into sourced scripts (fixes activate_conda under persistent/static shell) - Shell: self-heal corrupt session-search SQLite cache - Workspace: cap workspace-server tokio workers on many-core hosts - Shell: reap a session’s child processes when it closes - Crash handler: capture SIGABRT so panic-aborts leave crash reports - CLI chat proxy: team-scoped Grok Code managed-config admin routes - MCP: add CLI enable/disable for MCP servers - Shell: cap tokio worker threads for startup thread demand - Workspace: harden git_commit and add git_sync_base operation - Circuit breaker: add feature-gated gRPC retry policy Source-Revision: 2a818575225183d8ca915f5632a09b8067b5156a
This commit is contained in:
parent
02d9359435
commit
5da6962e4a
192 changed files with 10337 additions and 3421 deletions
|
|
@ -6,7 +6,7 @@ edition.workspace = true
|
|||
description = "SDK for the xAI Computer Hub: connection pool, transparent reconnect, tool harness, and tool-server runtime."
|
||||
|
||||
[features]
|
||||
metrics = ["dep:prometheus"]
|
||||
metrics = ["dep:prometheus", "dep:prometheus-parse"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "sync", "time", "macros"] }
|
||||
|
|
@ -25,6 +25,7 @@ tracing-subscriber = { workspace = true }
|
|||
url = { workspace = true }
|
||||
http = { workspace = true }
|
||||
prometheus = { workspace = true, optional = true }
|
||||
prometheus-parse = { version = "0.2.5", optional = true }
|
||||
reqwest = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -125,6 +125,138 @@ fn convert_families(families: &[MetricFamily]) -> Vec<Metric> {
|
|||
out
|
||||
}
|
||||
|
||||
/// Text-format exposition → OTLP, same shapes as [`convert_families`].
|
||||
fn convert_text_exposition(text: &str, prefix: &str) -> Vec<Metric> {
|
||||
let Ok(scrape) =
|
||||
prometheus_parse::Scrape::parse(text.lines().map(|l| std::io::Result::Ok(l.to_owned())))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let now = now_unix_nanos();
|
||||
let cumulative = AggregationTemporality::Cumulative as i32;
|
||||
|
||||
let histogram_names: std::collections::HashSet<&str> = scrape
|
||||
.samples
|
||||
.iter()
|
||||
.filter(|s| matches!(s.value, prometheus_parse::Value::Histogram(_)))
|
||||
.map(|s| s.metric.as_str())
|
||||
.collect();
|
||||
let histogram_sums: std::collections::HashMap<(String, String), f64> = scrape
|
||||
.samples
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
let base = s.metric.strip_suffix("_sum")?;
|
||||
if !histogram_names.contains(base) {
|
||||
return None;
|
||||
}
|
||||
let prometheus_parse::Value::Untyped(v) = s.value else {
|
||||
return None;
|
||||
};
|
||||
Some(((base.to_owned(), s.labels.to_string()), v))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let parsed_labels_to_kv = |labels: &prometheus_parse::Labels| -> Vec<KeyValue> {
|
||||
let mut kv: Vec<KeyValue> = labels
|
||||
.iter()
|
||||
.map(|(k, v)| string_kv(k, v.clone()))
|
||||
.collect();
|
||||
kv.sort_by(|a, b| a.key.cmp(&b.key));
|
||||
kv
|
||||
};
|
||||
|
||||
let mut out = Vec::new();
|
||||
for sample in &scrape.samples {
|
||||
if !sample.metric.starts_with(prefix) {
|
||||
continue;
|
||||
}
|
||||
let data = match &sample.value {
|
||||
prometheus_parse::Value::Counter(v) => metric::Data::Sum(Sum {
|
||||
data_points: vec![NumberDataPoint {
|
||||
attributes: parsed_labels_to_kv(&sample.labels),
|
||||
time_unix_nano: now,
|
||||
value: Some(number_data_point::Value::AsDouble(*v)),
|
||||
..Default::default()
|
||||
}],
|
||||
aggregation_temporality: cumulative,
|
||||
is_monotonic: true,
|
||||
}),
|
||||
prometheus_parse::Value::Gauge(v) => metric::Data::Gauge(Gauge {
|
||||
data_points: vec![NumberDataPoint {
|
||||
attributes: parsed_labels_to_kv(&sample.labels),
|
||||
time_unix_nano: now,
|
||||
value: Some(number_data_point::Value::AsDouble(*v)),
|
||||
..Default::default()
|
||||
}],
|
||||
}),
|
||||
prometheus_parse::Value::Histogram(counts) => {
|
||||
let mut counts = counts.clone();
|
||||
counts.sort_by(|a, b| a.less_than.total_cmp(&b.less_than));
|
||||
let mut bucket_counts = Vec::with_capacity(counts.len());
|
||||
let mut explicit_bounds = Vec::with_capacity(counts.len().saturating_sub(1));
|
||||
let mut prev = 0f64;
|
||||
let mut total = 0u64;
|
||||
for bucket in &counts {
|
||||
bucket_counts.push((bucket.count - prev).max(0.0) as u64);
|
||||
if bucket.less_than.is_finite() {
|
||||
explicit_bounds.push(bucket.less_than);
|
||||
} else {
|
||||
total = bucket.count.max(0.0) as u64;
|
||||
}
|
||||
prev = bucket.count;
|
||||
}
|
||||
let sum = histogram_sums
|
||||
.get(&(sample.metric.clone(), sample.labels.to_string()))
|
||||
.copied();
|
||||
metric::Data::Histogram(Histogram {
|
||||
data_points: vec![HistogramDataPoint {
|
||||
attributes: parsed_labels_to_kv(&sample.labels),
|
||||
time_unix_nano: now,
|
||||
count: total,
|
||||
sum,
|
||||
bucket_counts,
|
||||
explicit_bounds,
|
||||
..Default::default()
|
||||
}],
|
||||
aggregation_temporality: cumulative,
|
||||
})
|
||||
}
|
||||
// Untyped = histogram `_sum`/`_count` twins, joined above.
|
||||
prometheus_parse::Value::Summary(_) | prometheus_parse::Value::Untyped(_) => continue,
|
||||
};
|
||||
out.push(Metric {
|
||||
name: sample.metric.clone(),
|
||||
data: Some(data),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Donates metrics from outside the default Prometheus registry.
|
||||
pub struct DonatedMetricsSink {
|
||||
exporter: Arc<MetricExporter>,
|
||||
}
|
||||
|
||||
impl DonatedMetricsSink {
|
||||
/// Returns the number of OTLP metrics queued (malformed input → 0).
|
||||
pub fn export_text_exposition(&self, text: &str, prefix: &str) -> usize {
|
||||
let metrics = convert_text_exposition(text, prefix);
|
||||
let exported = metrics.len();
|
||||
if exported > 0 {
|
||||
self.exporter.export(metrics);
|
||||
}
|
||||
exported
|
||||
}
|
||||
}
|
||||
|
||||
/// `Some` while the donation pump is active; re-fetch per use (cheap).
|
||||
pub fn active_metrics_sink() -> Option<DonatedMetricsSink> {
|
||||
ACTIVE_METRIC_EXPORTER
|
||||
.load_full()
|
||||
.map(|exporter| DonatedMetricsSink { exporter })
|
||||
}
|
||||
|
||||
/// Encodes batches of OTLP metrics onto the pump channel. Chunks at
|
||||
/// [`MAX_METRICS_PER_DONATION`], drops payloads over
|
||||
/// [`MAX_DONATION_BYTES`], and never blocks.
|
||||
|
|
@ -299,6 +431,92 @@ mod tests {
|
|||
.collect()
|
||||
}
|
||||
|
||||
const PROXY_TEXT: &str = "\
|
||||
# HELP preview_proxy_requests_total Proxied preview requests.\n\
|
||||
# TYPE preview_proxy_requests_total counter\n\
|
||||
preview_proxy_requests_total{visibility=\"public\",outcome=\"ok\"} 41\n\
|
||||
preview_proxy_requests_total{visibility=\"private\",outcome=\"upstream_error\"} 2\n\
|
||||
# HELP preview_proxy_active_ws_connections Live proxied WebSocket connections.\n\
|
||||
# TYPE preview_proxy_active_ws_connections gauge\n\
|
||||
preview_proxy_active_ws_connections 3\n\
|
||||
# HELP preview_proxy_request_duration_seconds Preview request latency.\n\
|
||||
# TYPE preview_proxy_request_duration_seconds histogram\n\
|
||||
preview_proxy_request_duration_seconds_bucket{le=\"0.5\"} 4\n\
|
||||
preview_proxy_request_duration_seconds_bucket{le=\"1\"} 6\n\
|
||||
preview_proxy_request_duration_seconds_bucket{le=\"+Inf\"} 7\n\
|
||||
preview_proxy_request_duration_seconds_sum 5.25\n\
|
||||
preview_proxy_request_duration_seconds_count 7\n\
|
||||
# TYPE other_family_total counter\n\
|
||||
other_family_total 9\n";
|
||||
|
||||
#[test]
|
||||
fn text_exposition_converts_and_filters_by_prefix() {
|
||||
let metrics = convert_text_exposition(PROXY_TEXT, "preview_proxy_");
|
||||
let by_name: std::collections::HashMap<&str, &Metric> =
|
||||
metrics.iter().map(|m| (m.name.as_str(), m)).collect();
|
||||
|
||||
assert!(!by_name.contains_key("other_family_total"));
|
||||
assert!(!by_name.contains_key("preview_proxy_request_duration_seconds_sum"));
|
||||
assert!(!by_name.contains_key("preview_proxy_request_duration_seconds_count"));
|
||||
|
||||
let counters: Vec<&Metric> = metrics
|
||||
.iter()
|
||||
.filter(|m| m.name == "preview_proxy_requests_total")
|
||||
.collect();
|
||||
assert_eq!(2, counters.len());
|
||||
let ok_point = counters
|
||||
.iter()
|
||||
.find_map(|m| match m.data.as_ref() {
|
||||
Some(metric::Data::Sum(sum)) => {
|
||||
assert!(sum.is_monotonic);
|
||||
sum.data_points.iter().find(|p| {
|
||||
label_map(&p.attributes).get("outcome").map(String::as_str) == Some("ok")
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("ok counter point");
|
||||
assert_eq!(
|
||||
Some(number_data_point::Value::AsDouble(41.0)),
|
||||
ok_point.value
|
||||
);
|
||||
assert_eq!(
|
||||
Some("public"),
|
||||
label_map(&ok_point.attributes)
|
||||
.get("visibility")
|
||||
.map(String::as_str)
|
||||
);
|
||||
|
||||
let Some(metric::Data::Gauge(gauge)) =
|
||||
by_name["preview_proxy_active_ws_connections"].data.as_ref()
|
||||
else {
|
||||
panic!("gauge expected");
|
||||
};
|
||||
assert_eq!(
|
||||
Some(number_data_point::Value::AsDouble(3.0)),
|
||||
gauge.data_points[0].value
|
||||
);
|
||||
|
||||
let Some(metric::Data::Histogram(hist)) = by_name["preview_proxy_request_duration_seconds"]
|
||||
.data
|
||||
.as_ref()
|
||||
else {
|
||||
panic!("histogram expected");
|
||||
};
|
||||
let point = &hist.data_points[0];
|
||||
assert_eq!(vec![0.5, 1.0], point.explicit_bounds);
|
||||
assert_eq!(vec![4, 2, 1], point.bucket_counts);
|
||||
assert_eq!(7, point.count);
|
||||
assert_eq!(Some(5.25), point.sum);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_exposition_tolerates_garbage_and_empty_input() {
|
||||
assert!(convert_text_exposition("", "preview_proxy_").is_empty());
|
||||
assert!(convert_text_exposition("not a metric line at all", "preview_proxy_").is_empty());
|
||||
assert!(convert_text_exposition(PROXY_TEXT, "no_such_prefix_").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_counter_gauge_histogram_with_labels() {
|
||||
let registry = Registry::new();
|
||||
|
|
|
|||
Loading…
Reference in a new issue