mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
新增 crate: - aether-runtime: 服务运行时基础设施(并发门控、分布式并发、指标、队列、优雅关闭、tracing) - aether-cache: 通用 TTL 缓存与命名空间抽象 - aether-data: 数据访问层(PostgreSQL/Redis 后端、repository 模式) - aether-http: HTTP 客户端封装(重试、配置) - aether-testkit: 集成测试工具集(gateway/executor/hub/proxy fixture、等待、负载测试) gateway 扩展: - 引入 audit 模块(shadow 执行审计、决策链路追踪、请求审计 bundle) - 引入 cache 模块(AuthContext 缓存、direct-plan bypass 缓存) - 引入 data 模块(auth/candidates/config/usage/video_tasks 数据访问) - 集成 ConcurrencyGate/DistributedConcurrencyGate 请求门控 - 新增本地 auth 拒绝、过载响应构建器 - 补充 control/auth_cache/video/concurrency 集成测试 aether-proxy 扩展: - AppState 集成 stream_gate / distributed_stream_gate 并发门控 - 新增 ProxyAdmissionError 及准入拒绝流程 - stream_handler 补充门控饱和/不可用场景测试 - 配置与注册客户端逻辑完善 aether-hub 扩展: - main.rs 引入运行时初始化、指标端点、健康检查 - local_relay 重构为 lib.rs 暴露公共接口
137 lines
4.0 KiB
Rust
137 lines
4.0 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct PrometheusSample {
|
|
pub name: String,
|
|
pub labels: BTreeMap<String, String>,
|
|
pub value: String,
|
|
}
|
|
|
|
pub async fn fetch_prometheus_samples(url: &str) -> Result<Vec<PrometheusSample>, String> {
|
|
let client = reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(5))
|
|
.build()
|
|
.map_err(|err| format!("failed to build metrics http client: {err}"))?;
|
|
let response = client
|
|
.get(url)
|
|
.send()
|
|
.await
|
|
.map_err(|err| format!("failed to fetch metrics from {url}: {err}"))?;
|
|
let status = response.status();
|
|
let body = response
|
|
.text()
|
|
.await
|
|
.map_err(|err| format!("failed to read metrics body from {url}: {err}"))?;
|
|
if !status.is_success() {
|
|
return Err(format!("metrics endpoint {url} returned {status}: {body}"));
|
|
}
|
|
Ok(parse_prometheus_samples(&body))
|
|
}
|
|
|
|
pub fn parse_prometheus_samples(text: &str) -> Vec<PrometheusSample> {
|
|
text.lines()
|
|
.filter_map(parse_prometheus_line)
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
pub fn find_metric_value_u64(
|
|
samples: &[PrometheusSample],
|
|
metric_name: &str,
|
|
labels: &[(&str, &str)],
|
|
) -> Option<u64> {
|
|
samples
|
|
.iter()
|
|
.find(|sample| {
|
|
metric_name_matches(&sample.name, metric_name) && labels_match(sample, labels)
|
|
})
|
|
.and_then(|sample| sample.value.parse::<u64>().ok())
|
|
}
|
|
|
|
fn metric_name_matches(actual: &str, expected: &str) -> bool {
|
|
actual == expected
|
|
|| actual
|
|
.rsplit_once('_')
|
|
.map(|(_, suffix)| suffix == expected)
|
|
.unwrap_or(false)
|
|
|| actual.ends_with(&format!("_{expected}"))
|
|
}
|
|
|
|
fn labels_match(sample: &PrometheusSample, labels: &[(&str, &str)]) -> bool {
|
|
labels
|
|
.iter()
|
|
.all(|(key, value)| sample.labels.get(*key).map(|current| current.as_str()) == Some(*value))
|
|
}
|
|
|
|
fn parse_prometheus_line(line: &str) -> Option<PrometheusSample> {
|
|
let trimmed = line.trim();
|
|
if trimmed.is_empty() || trimmed.starts_with('#') {
|
|
return None;
|
|
}
|
|
let (metric, value) = trimmed.rsplit_once(' ')?;
|
|
let (name, labels) = if let Some((name, raw_labels)) = metric
|
|
.split_once('{')
|
|
.and_then(|(name, rest)| rest.strip_suffix('}').map(|labels| (name, labels)))
|
|
{
|
|
(name.to_string(), parse_labels(raw_labels))
|
|
} else {
|
|
(metric.to_string(), BTreeMap::new())
|
|
};
|
|
Some(PrometheusSample {
|
|
name,
|
|
labels,
|
|
value: value.to_string(),
|
|
})
|
|
}
|
|
|
|
fn parse_labels(raw: &str) -> BTreeMap<String, String> {
|
|
let mut labels = BTreeMap::new();
|
|
for pair in raw.split(',').filter(|pair| !pair.is_empty()) {
|
|
if let Some((key, value)) = pair.split_once('=') {
|
|
labels.insert(
|
|
key.trim().to_string(),
|
|
value
|
|
.trim()
|
|
.trim_matches('"')
|
|
.replace("\\\"", "\"")
|
|
.replace("\\n", "\n")
|
|
.replace("\\\\", "\\"),
|
|
);
|
|
}
|
|
}
|
|
labels
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{find_metric_value_u64, parse_prometheus_samples};
|
|
|
|
#[test]
|
|
fn parses_prometheus_samples_with_labels() {
|
|
let samples = parse_prometheus_samples(
|
|
r#"
|
|
# HELP aether_gateway_concurrency_in_flight Current number of in-flight operations.
|
|
# TYPE aether_gateway_concurrency_in_flight gauge
|
|
aether_gateway_concurrency_in_flight{gate="gateway_requests"} 7
|
|
aether_gateway_concurrency_rejected_total{gate="gateway_requests"} 12
|
|
"#,
|
|
);
|
|
|
|
assert_eq!(
|
|
find_metric_value_u64(
|
|
&samples,
|
|
"concurrency_in_flight",
|
|
&[("gate", "gateway_requests")]
|
|
),
|
|
Some(7)
|
|
);
|
|
assert_eq!(
|
|
find_metric_value_u64(
|
|
&samples,
|
|
"concurrency_rejected_total",
|
|
&[("gate", "gateway_requests")]
|
|
),
|
|
Some(12)
|
|
);
|
|
}
|
|
}
|