v0.25.5
This commit is contained in:
1 parent
1dea7f55c8
commit
c819f0a335
151 files changed
+7610
-4264
No files matched your search
+382
-319
@@ -1,370 +1,433 @@
|
||||
fn render_core(app: &App, snapshot: &crate::config::Snapshot, out: &mut String, f: &mut Families) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
f.add(
|
||||
"rust_proxy_admin_requests_total",
|
||||
"counter",
|
||||
"Admin HTTP requests, including denied requests and static assets.",
|
||||
"",
|
||||
app.metrics.admin_requests.load(Ordering::Relaxed),
|
||||
);
|
||||
for (index, counter) in app.metrics.admin_status.iter().enumerate() {
|
||||
f.add(
|
||||
"rust_proxy_admin_responses_total",
|
||||
"rust_proxy_admin_requests_total",
|
||||
"counter",
|
||||
"Admin response headers by status class.",
|
||||
&format!("class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
out.push_str("# HELP rust_proxy_admin_header_duration_seconds Time to control-plane response headers; not SSE lifetime.\n# TYPE rust_proxy_admin_header_duration_seconds summary\n");
|
||||
let _ = writeln!(out, "rust_proxy_admin_header_duration_seconds_count {}\nrust_proxy_admin_header_duration_seconds_sum {:.6}",
|
||||
app.metrics.admin_response_headers.load(Ordering::Relaxed), app.metrics.admin_header_duration_us.load(Ordering::Relaxed) as f64/1e6);
|
||||
f.add(
|
||||
"rust_proxy_build_info",
|
||||
"gauge",
|
||||
"Build version.",
|
||||
&labels(&[("version", env!("CARGO_PKG_VERSION"))]),
|
||||
1,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_uptime_seconds",
|
||||
"gauge",
|
||||
"Process monotonic uptime.",
|
||||
"",
|
||||
app.started.elapsed().as_secs_f64(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhosts_configured",
|
||||
"gauge",
|
||||
"Configured vhosts including disabled.",
|
||||
"",
|
||||
config.vhosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_hosts_configured",
|
||||
"gauge",
|
||||
"Configured redirect hosts including disabled.",
|
||||
"",
|
||||
config.redirects.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_hosts_configured",
|
||||
"gauge",
|
||||
"Configured local-response/404 hosts including disabled.",
|
||||
"",
|
||||
config.not_found_hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_stream_hosts_configured",
|
||||
"gauge",
|
||||
"Configured Layer-4 stream hosts including disabled.",
|
||||
"",
|
||||
config.streams.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_acl_profiles_configured",
|
||||
"gauge",
|
||||
"Configured reusable ACL profiles.",
|
||||
"",
|
||||
config.acl_profiles.len(),
|
||||
);
|
||||
if settings.include_system {
|
||||
let geo = app.geoip.view();
|
||||
f.add(
|
||||
"rust_proxy_geoip_loaded",
|
||||
"gauge",
|
||||
"Whether the bounded country GeoIP database is loaded.",
|
||||
"Admin HTTP requests, including denied requests and static assets.",
|
||||
"",
|
||||
u8::from(geo.loaded),
|
||||
app.metrics.admin_requests.load(Ordering::Relaxed),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_geoip_ipv4_ranges",
|
||||
"gauge",
|
||||
"Loaded IPv4 country ranges.",
|
||||
"",
|
||||
geo.ipv4_ranges,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_geoip_ipv6_ranges",
|
||||
"gauge",
|
||||
"Loaded IPv6 country ranges.",
|
||||
"",
|
||||
geo.ipv6_ranges,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_certificates_configured",
|
||||
"gauge",
|
||||
"Certificate inventory size, not validity.",
|
||||
"",
|
||||
config.tls.certificates.len(),
|
||||
);
|
||||
let renewals = app.acme.renewal_view();
|
||||
for certificate in &config.tls.certificates {
|
||||
let l = labels(&[("certificate", &certificate.id)]);
|
||||
let issuer = certificate.issuer.as_ref();
|
||||
f.add(
|
||||
"rust_proxy_certificate_auto_renew_enabled",
|
||||
"gauge",
|
||||
"Desired automatic renewal switch for a configured certificate.",
|
||||
&l,
|
||||
u8::from(issuer.is_some_and(|issuer| issuer.auto_renew)),
|
||||
);
|
||||
if let Some(issuer) = issuer {
|
||||
for (index, counter) in app.metrics.admin_status.iter().enumerate() {
|
||||
f.add(
|
||||
"rust_proxy_certificate_issuer_info",
|
||||
"gauge",
|
||||
"Bounded configured certificate issuer metadata.",
|
||||
&labels(&[
|
||||
("certificate", &certificate.id),
|
||||
("provider", &issuer.provider),
|
||||
("environment", &issuer.environment),
|
||||
]),
|
||||
1,
|
||||
"rust_proxy_admin_responses_total",
|
||||
"counter",
|
||||
"Admin response headers by status class.",
|
||||
&format!("class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
if let Some(renewal) = renewals.get(&certificate.id) {
|
||||
for (name, value) in [
|
||||
("last_checked_timestamp_seconds", Some(renewal.last_checked)),
|
||||
("next_check_timestamp_seconds", Some(renewal.next_check)),
|
||||
("last_renewed_timestamp_seconds", renewal.last_renewed),
|
||||
("not_after_timestamp_seconds", renewal.not_after),
|
||||
] {
|
||||
if let Some(value) = value.filter(|value| *value > 0) {
|
||||
f.add(
|
||||
&format!("rust_proxy_certificate_renewal_{name}"),
|
||||
"gauge",
|
||||
"Certificate renewal observation timestamp.",
|
||||
&l,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
out.push_str("# HELP rust_proxy_admin_header_duration_seconds Time to control-plane response headers; not SSE lifetime.\n# TYPE rust_proxy_admin_header_duration_seconds summary\n");
|
||||
let _ = writeln!(out, "rust_proxy_admin_header_duration_seconds_count {}\nrust_proxy_admin_header_duration_seconds_sum {:.6}",
|
||||
app.metrics.admin_response_headers.load(Ordering::Relaxed), app.metrics.admin_header_duration_us.load(Ordering::Relaxed) as f64/1e6);
|
||||
f.add(
|
||||
"rust_proxy_build_info",
|
||||
"gauge",
|
||||
"Build version.",
|
||||
&labels(&[("version", env!("CARGO_PKG_VERSION"))]),
|
||||
1,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_uptime_seconds",
|
||||
"gauge",
|
||||
"Process monotonic uptime.",
|
||||
"",
|
||||
app.started.elapsed().as_secs_f64(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhosts_configured",
|
||||
"gauge",
|
||||
"Configured vhosts including disabled.",
|
||||
"",
|
||||
config.vhosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_hosts_configured",
|
||||
"gauge",
|
||||
"Configured redirect hosts including disabled.",
|
||||
"",
|
||||
config.redirects.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_hosts_configured",
|
||||
"gauge",
|
||||
"Configured local-response/404 hosts including disabled.",
|
||||
"",
|
||||
config.not_found_hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_stream_hosts_configured",
|
||||
"gauge",
|
||||
"Configured Layer-4 stream hosts including disabled.",
|
||||
"",
|
||||
config.streams.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_acl_profiles_configured",
|
||||
"gauge",
|
||||
"Configured reusable ACL profiles.",
|
||||
"",
|
||||
config.acl_profiles.len(),
|
||||
);
|
||||
if settings.include_system {
|
||||
let geo = app.geoip.view();
|
||||
f.add(
|
||||
"rust_proxy_certificate_renewal_failures",
|
||||
"rust_proxy_geoip_loaded",
|
||||
"gauge",
|
||||
"Consecutive observed automatic renewal failures.",
|
||||
&l,
|
||||
renewal.failures,
|
||||
"Whether the bounded country GeoIP database is loaded.",
|
||||
"",
|
||||
u8::from(geo.loaded),
|
||||
);
|
||||
if !renewal.status.is_empty() {
|
||||
f.add(
|
||||
"rust_proxy_geoip_ipv4_ranges",
|
||||
"gauge",
|
||||
"Loaded IPv4 country ranges.",
|
||||
"",
|
||||
geo.ipv4_ranges,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_geoip_ipv6_ranges",
|
||||
"gauge",
|
||||
"Loaded IPv6 country ranges.",
|
||||
"",
|
||||
geo.ipv6_ranges,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_certificates_configured",
|
||||
"gauge",
|
||||
"Certificate inventory size, not validity.",
|
||||
"",
|
||||
config.tls.certificates.len(),
|
||||
);
|
||||
let renewals = app.acme.renewal_view();
|
||||
for certificate in &config.tls.certificates {
|
||||
let l = labels(&[("certificate", &certificate.id)]);
|
||||
let issuer = certificate.issuer.as_ref();
|
||||
f.add(
|
||||
"rust_proxy_certificate_auto_renew_enabled",
|
||||
"gauge",
|
||||
"Desired automatic renewal switch for a configured certificate.",
|
||||
&l,
|
||||
u8::from(issuer.is_some_and(|issuer| issuer.auto_renew)),
|
||||
);
|
||||
if let Some(issuer) = issuer {
|
||||
f.add(
|
||||
"rust_proxy_certificate_renewal_status_info",
|
||||
"rust_proxy_certificate_issuer_info",
|
||||
"gauge",
|
||||
"Latest bounded certificate renewal status.",
|
||||
"Bounded configured certificate issuer metadata.",
|
||||
&labels(&[
|
||||
("certificate", &certificate.id),
|
||||
("status", &renewal.status),
|
||||
("provider", &issuer.provider),
|
||||
("environment", &issuer.environment),
|
||||
]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
if let Some(renewal) = renewals.get(&certificate.id) {
|
||||
for (name, value) in [
|
||||
("last_checked_timestamp_seconds", Some(renewal.last_checked)),
|
||||
("next_check_timestamp_seconds", Some(renewal.next_check)),
|
||||
("last_renewed_timestamp_seconds", renewal.last_renewed),
|
||||
("not_after_timestamp_seconds", renewal.not_after),
|
||||
] {
|
||||
if let Some(value) = value.filter(|value| *value > 0) {
|
||||
f.add(
|
||||
&format!("rust_proxy_certificate_renewal_{name}"),
|
||||
"gauge",
|
||||
"Certificate renewal observation timestamp.",
|
||||
&l,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_certificate_renewal_failures",
|
||||
"gauge",
|
||||
"Consecutive observed automatic renewal failures.",
|
||||
&l,
|
||||
renewal.failures,
|
||||
);
|
||||
if !renewal.status.is_empty() {
|
||||
f.add(
|
||||
"rust_proxy_certificate_renewal_status_info",
|
||||
"gauge",
|
||||
"Latest bounded certificate renewal status.",
|
||||
&labels(&[
|
||||
("certificate", &certificate.id),
|
||||
("status", &renewal.status),
|
||||
]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_listeners_active",
|
||||
"gauge",
|
||||
"Bound frontend endpoints.",
|
||||
"",
|
||||
app.active_listeners.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_restart_required",
|
||||
"gauge",
|
||||
"Listener/exporter intent differs from startup state.",
|
||||
"",
|
||||
u8::from(
|
||||
snapshot.listeners != app.active_listeners
|
||||
|| settings.restart_differs(&app.active_prometheus),
|
||||
),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_request_capacity",
|
||||
"gauge",
|
||||
"Configured maximum concurrent admitted exchanges.",
|
||||
"",
|
||||
app.max_inflight,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_request_permits_available",
|
||||
"gauge",
|
||||
"Currently available exchange permits.",
|
||||
"",
|
||||
app.inflight.available_permits(),
|
||||
);
|
||||
if let Some(job) = app.acme.view() {
|
||||
f.add(
|
||||
"rust_proxy_acme_job_state",
|
||||
"rust_proxy_listeners_active",
|
||||
"gauge",
|
||||
"Latest ACME job state; no domains or secrets.",
|
||||
&labels(&[("provider", &job.provider), ("state", job.status)]),
|
||||
1,
|
||||
"Bound frontend endpoints.",
|
||||
"",
|
||||
app.active_listeners.len(),
|
||||
);
|
||||
}
|
||||
for (index, listener) in app.active_listeners.iter().enumerate() {
|
||||
let index = index.to_string();
|
||||
let address = listener.address.to_string();
|
||||
let l = labels(&[("listener", &index), ("address", &address)]);
|
||||
for (protocol, enabled) in [
|
||||
("http1", true),
|
||||
("http2", listener.tls && listener.http2),
|
||||
("http3", listener.http3.is_some()),
|
||||
("tls", listener.tls),
|
||||
] {
|
||||
f.add(
|
||||
"rust_proxy_restart_required",
|
||||
"gauge",
|
||||
"Listener/exporter intent differs from startup state.",
|
||||
"",
|
||||
u8::from(
|
||||
snapshot.listeners != app.active_listeners
|
||||
|| settings.restart_differs(&app.active_prometheus),
|
||||
),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_request_capacity",
|
||||
"gauge",
|
||||
"Configured maximum concurrent admitted exchanges.",
|
||||
"",
|
||||
app.max_inflight,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_request_permits_available",
|
||||
"gauge",
|
||||
"Currently available exchange permits.",
|
||||
"",
|
||||
app.inflight.available_permits(),
|
||||
);
|
||||
if let Some(job) = app.acme.view() {
|
||||
f.add(
|
||||
"rust_proxy_listener_protocol_enabled",
|
||||
"rust_proxy_acme_job_state",
|
||||
"gauge",
|
||||
"Protocols active per listener.",
|
||||
&format!("{l},protocol=\"{protocol}\""),
|
||||
u8::from(enabled),
|
||||
"Latest ACME job state; no domains or secrets.",
|
||||
&labels(&[("provider", &job.provider), ("state", job.status)]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (index, listener) in app.active_listeners.iter().enumerate() {
|
||||
let index = index.to_string();
|
||||
let address = listener.address.to_string();
|
||||
let l = labels(&[("listener", &index), ("address", &address)]);
|
||||
for (protocol, enabled) in [
|
||||
("http1", true),
|
||||
("http2", listener.tls && listener.http2),
|
||||
("http3", listener.http3.is_some()),
|
||||
("tls", listener.tls),
|
||||
] {
|
||||
f.add(
|
||||
"rust_proxy_listener_protocol_enabled",
|
||||
"gauge",
|
||||
"Protocols active per listener.",
|
||||
&format!("{l},protocol=\"{protocol}\""),
|
||||
u8::from(enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_vhosts(snapshot: &crate::config::Snapshot, f: &mut Families) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_vhosts {
|
||||
for redirect in &config.redirects {
|
||||
let l = labels(&[("redirect", &redirect.id)]);
|
||||
if settings.include_vhosts {
|
||||
f.add(
|
||||
"rust_proxy_redirect_host_enabled",
|
||||
"rust_proxy_web_services_configured",
|
||||
"gauge",
|
||||
"Desired redirect-host enable flag.",
|
||||
&l,
|
||||
u8::from(redirect.enabled),
|
||||
"Configured built-in Web Server services.",
|
||||
"",
|
||||
config.web_services.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_host_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a redirect host.",
|
||||
&l,
|
||||
redirect.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_host_rules",
|
||||
"gauge",
|
||||
"Configured redirect rules.",
|
||||
&l,
|
||||
redirect.rules.len(),
|
||||
);
|
||||
}
|
||||
for host in &config.not_found_hosts {
|
||||
let l = labels(&[("host", &host.id)]);
|
||||
f.add(
|
||||
"rust_proxy_not_found_host_enabled",
|
||||
"gauge",
|
||||
"Desired local-response/404-host enable flag.",
|
||||
&l,
|
||||
u8::from(host.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_host_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a local-response/404 host.",
|
||||
&l,
|
||||
host.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_host_status",
|
||||
"gauge",
|
||||
"Configured local-response HTTP status.",
|
||||
&l,
|
||||
host.page.status.unwrap_or(404),
|
||||
);
|
||||
}
|
||||
for host in &config.vhosts {
|
||||
let l = labels(&[("vhost", &host.id)]);
|
||||
f.add(
|
||||
"rust_proxy_vhost_enabled",
|
||||
"gauge",
|
||||
"Desired vhost enable flag.",
|
||||
&l,
|
||||
u8::from(host.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a vhost.",
|
||||
&l,
|
||||
host.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_paths",
|
||||
"gauge",
|
||||
"Configured path rules for a vhost.",
|
||||
&l,
|
||||
host.routes.len(),
|
||||
);
|
||||
if let Ok(cache) = host.cache.resolve(&config.cache) {
|
||||
for service in &config.web_services {
|
||||
let php = if service.php.enabled { "true" } else { "false" };
|
||||
let directory_listing = if service.directory_listing {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
};
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_enabled",
|
||||
"rust_proxy_web_service_info",
|
||||
"gauge",
|
||||
"Desired effective cache switch; request privacy rules still apply.",
|
||||
&l,
|
||||
u8::from(cache.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_max_object_bytes",
|
||||
"gauge",
|
||||
"Effective maximum cache object size.",
|
||||
&l,
|
||||
cache.max_object_bytes,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_max_ttl_seconds",
|
||||
"gauge",
|
||||
"Effective maximum cache TTL.",
|
||||
&l,
|
||||
cache.max_ttl_seconds,
|
||||
);
|
||||
}
|
||||
for domain in &host.frontend.hosts {
|
||||
f.add(
|
||||
"rust_proxy_vhost_domain_info",
|
||||
"gauge",
|
||||
"Configured name ownership.",
|
||||
&labels(&[("vhost", &host.id), ("domain", domain)]),
|
||||
"Bounded built-in Web Server service configuration metadata.",
|
||||
&labels(&[
|
||||
("service", &service.id),
|
||||
("php", php),
|
||||
("directory_listing", directory_listing),
|
||||
]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
let m = &snapshot.vhost_metrics[&host.id];
|
||||
for (name, counter) in [
|
||||
("requests_total", &m.requests),
|
||||
("completed_total", &m.completed),
|
||||
("errors_total", &m.errors),
|
||||
("rejected_total", &m.rejected),
|
||||
("request_bytes_total", &m.bytes_in),
|
||||
("response_bytes_total", &m.bytes_out),
|
||||
] {
|
||||
for redirect in &config.redirects {
|
||||
let l = labels(&[("redirect", &redirect.id)]);
|
||||
f.add(
|
||||
&format!("rust_proxy_vhost_{name}"),
|
||||
"counter",
|
||||
"Vhost exchange counter; reset on process restart or vhost removal.",
|
||||
"rust_proxy_redirect_host_enabled",
|
||||
"gauge",
|
||||
"Desired redirect-host enable flag.",
|
||||
&l,
|
||||
counter.load(Ordering::Relaxed),
|
||||
u8::from(redirect.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_host_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a redirect host.",
|
||||
&l,
|
||||
redirect.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_redirect_host_rules",
|
||||
"gauge",
|
||||
"Configured redirect rules.",
|
||||
&l,
|
||||
redirect.rules.len(),
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_vhost_active",
|
||||
"gauge",
|
||||
"Admitted unfinished exchanges.",
|
||||
&l,
|
||||
m.active.load(Ordering::Relaxed),
|
||||
);
|
||||
for (index, counter) in m.status.iter().enumerate() {
|
||||
for host in &config.not_found_hosts {
|
||||
let l = labels(&[("host", &host.id)]);
|
||||
f.add(
|
||||
"rust_proxy_vhost_responses_total",
|
||||
"counter",
|
||||
"Completed exchanges by status class.",
|
||||
&format!("{l},class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
"rust_proxy_not_found_host_enabled",
|
||||
"gauge",
|
||||
"Desired local-response/404-host enable flag.",
|
||||
&l,
|
||||
u8::from(host.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_host_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a local-response/404 host.",
|
||||
&l,
|
||||
host.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_not_found_host_status",
|
||||
"gauge",
|
||||
"Configured local-response HTTP status.",
|
||||
&l,
|
||||
host.page.status.unwrap_or(404),
|
||||
);
|
||||
}
|
||||
for host in &config.vhosts {
|
||||
let l = labels(&[("vhost", &host.id)]);
|
||||
let (source, web_service) = match &host.source {
|
||||
crate::vhost::Source::ReverseProxy => ("reverse_proxy", ""),
|
||||
crate::vhost::Source::WebServer { web_service_id } => {
|
||||
("web_server", web_service_id.as_str())
|
||||
}
|
||||
};
|
||||
f.add(
|
||||
"rust_proxy_vhost_source_info",
|
||||
"gauge",
|
||||
"Configured main serving source for a vhost.",
|
||||
&format!(
|
||||
"{l},source=\"{source}\",web_service=\"{}\"",
|
||||
escaped(web_service)
|
||||
),
|
||||
1,
|
||||
);
|
||||
for route in host.routes.iter().filter(|route| route.enabled) {
|
||||
let (route_source, route_service) = if route.response.is_some() {
|
||||
("local_response", "")
|
||||
} else {
|
||||
match &route.source {
|
||||
crate::vhost::Source::ReverseProxy => ("reverse_proxy", ""),
|
||||
crate::vhost::Source::WebServer { web_service_id } => {
|
||||
("web_server", web_service_id.as_str())
|
||||
}
|
||||
}
|
||||
};
|
||||
f.add(
|
||||
"rust_proxy_vhost_route_source_info",
|
||||
"gauge",
|
||||
"Configured serving source for an enabled vhost route.",
|
||||
&format!(
|
||||
"{l},route=\"{}\",source=\"{route_source}\",web_service=\"{}\"",
|
||||
escaped(&route.id),
|
||||
escaped(route_service)
|
||||
),
|
||||
1,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_vhost_enabled",
|
||||
"gauge",
|
||||
"Desired vhost enable flag.",
|
||||
&l,
|
||||
u8::from(host.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a vhost.",
|
||||
&l,
|
||||
host.frontend.hosts.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_paths",
|
||||
"gauge",
|
||||
"Configured path rules for a vhost.",
|
||||
&l,
|
||||
host.routes.len(),
|
||||
);
|
||||
if let Ok(cache) = host.cache.resolve(&config.cache) {
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_enabled",
|
||||
"gauge",
|
||||
"Desired effective cache switch; request privacy rules still apply.",
|
||||
&l,
|
||||
u8::from(cache.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_max_object_bytes",
|
||||
"gauge",
|
||||
"Effective maximum cache object size.",
|
||||
&l,
|
||||
cache.max_object_bytes,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_vhost_cache_max_ttl_seconds",
|
||||
"gauge",
|
||||
"Effective maximum cache TTL.",
|
||||
&l,
|
||||
cache.max_ttl_seconds,
|
||||
);
|
||||
}
|
||||
for domain in &host.frontend.hosts {
|
||||
f.add(
|
||||
"rust_proxy_vhost_domain_info",
|
||||
"gauge",
|
||||
"Configured name ownership.",
|
||||
&labels(&[("vhost", &host.id), ("domain", domain)]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
let m = &snapshot.vhost_metrics[&host.id];
|
||||
for (name, counter) in [
|
||||
("requests_total", &m.requests),
|
||||
("completed_total", &m.completed),
|
||||
("errors_total", &m.errors),
|
||||
("rejected_total", &m.rejected),
|
||||
("request_bytes_total", &m.bytes_in),
|
||||
("response_bytes_total", &m.bytes_out),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_vhost_{name}"),
|
||||
"counter",
|
||||
"Vhost exchange counter; reset on process restart or vhost removal.",
|
||||
&l,
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_vhost_active",
|
||||
"gauge",
|
||||
"Admitted unfinished exchanges.",
|
||||
&l,
|
||||
m.active.load(Ordering::Relaxed),
|
||||
);
|
||||
for (index, counter) in m.status.iter().enumerate() {
|
||||
f.add(
|
||||
"rust_proxy_vhost_responses_total",
|
||||
"counter",
|
||||
"Completed exchanges by status class.",
|
||||
&format!("{l},class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+192
-189
@@ -1,226 +1,228 @@
|
||||
fn render_misc(app: &App, snapshot: &crate::config::Snapshot, f: &mut Families) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_streams {
|
||||
for host in &config.streams {
|
||||
let protocol = match host.protocol {
|
||||
crate::stream::Protocol::Tcp => "tcp",
|
||||
crate::stream::Protocol::Udp => "udp",
|
||||
};
|
||||
if settings.include_streams {
|
||||
for host in &config.streams {
|
||||
let protocol = match host.protocol {
|
||||
crate::stream::Protocol::Tcp => "tcp",
|
||||
crate::stream::Protocol::Udp => "udp",
|
||||
};
|
||||
f.add(
|
||||
"rust_proxy_stream_enabled",
|
||||
"gauge",
|
||||
"Desired Layer-4 stream enable flag.",
|
||||
&labels(&[("stream", &host.id), ("protocol", protocol)]),
|
||||
u8::from(host.enabled),
|
||||
);
|
||||
}
|
||||
for host in &app.active_streams {
|
||||
let Some(metrics) = app.stream_metrics.get(&host.id) else {
|
||||
continue;
|
||||
};
|
||||
let protocol = match host.protocol {
|
||||
crate::stream::Protocol::Tcp => "tcp",
|
||||
crate::stream::Protocol::Udp => "udp",
|
||||
};
|
||||
let l = labels(&[("stream", &host.id), ("protocol", protocol)]);
|
||||
let view = metrics.view();
|
||||
for (name, kind, value) in [
|
||||
("sessions_total", "counter", view.sessions_total),
|
||||
(
|
||||
"sessions_completed_total",
|
||||
"counter",
|
||||
view.sessions_completed,
|
||||
),
|
||||
("active", "gauge", view.active),
|
||||
("rejected_total", "counter", view.rejected),
|
||||
("errors_total", "counter", view.errors),
|
||||
("bytes_in_total", "counter", view.bytes_in),
|
||||
("bytes_out_total", "counter", view.bytes_out),
|
||||
("packets_in_total", "counter", view.packets_in),
|
||||
("packets_out_total", "counter", view.packets_out),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_stream_{name}"),
|
||||
kind,
|
||||
"Layer-4 stream observation; reset on process restart.",
|
||||
&l,
|
||||
value,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_stream_max_connections",
|
||||
"gauge",
|
||||
"Configured stream connection/session capacity.",
|
||||
&l,
|
||||
host.max_connections,
|
||||
);
|
||||
}
|
||||
}
|
||||
if settings.include_logging {
|
||||
let stats = app.logs.stats();
|
||||
f.add(
|
||||
"rust_proxy_stream_enabled",
|
||||
"rust_proxy_log_sink_queue_capacity",
|
||||
"gauge",
|
||||
"Desired Layer-4 stream enable flag.",
|
||||
&labels(&[("stream", &host.id), ("protocol", protocol)]),
|
||||
u8::from(host.enabled),
|
||||
"Structured log sink queue capacity.",
|
||||
"",
|
||||
stats.queue_capacity,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_queue_dropped_total",
|
||||
"counter",
|
||||
"Structured log entries dropped because the sink queue was full.",
|
||||
"",
|
||||
stats.queue_dropped,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_sent_total",
|
||||
"counter",
|
||||
"Structured log entries delivered to configured sinks.",
|
||||
"",
|
||||
stats.sent,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_errors_total",
|
||||
"counter",
|
||||
"Structured log sink delivery errors.",
|
||||
"",
|
||||
stats.errors,
|
||||
);
|
||||
}
|
||||
for host in &app.active_streams {
|
||||
let Some(metrics) = app.stream_metrics.get(&host.id) else {
|
||||
continue;
|
||||
};
|
||||
let protocol = match host.protocol {
|
||||
crate::stream::Protocol::Tcp => "tcp",
|
||||
crate::stream::Protocol::Udp => "udp",
|
||||
};
|
||||
let l = labels(&[("stream", &host.id), ("protocol", protocol)]);
|
||||
let view = metrics.view();
|
||||
for (name, kind, value) in [
|
||||
("sessions_total", "counter", view.sessions_total),
|
||||
(
|
||||
"sessions_completed_total",
|
||||
"counter",
|
||||
view.sessions_completed,
|
||||
),
|
||||
("active", "gauge", view.active),
|
||||
("rejected_total", "counter", view.rejected),
|
||||
("errors_total", "counter", view.errors),
|
||||
("bytes_in_total", "counter", view.bytes_in),
|
||||
("bytes_out_total", "counter", view.bytes_out),
|
||||
("packets_in_total", "counter", view.packets_in),
|
||||
("packets_out_total", "counter", view.packets_out),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_stream_{name}"),
|
||||
kind,
|
||||
"Layer-4 stream observation; reset on process restart.",
|
||||
&l,
|
||||
value,
|
||||
);
|
||||
}
|
||||
if settings.include_cache_domains {
|
||||
// Bounded resident inspector. Counter families are gauges deliberately:
|
||||
// these per-domain values reset on config publication, unlike globals.
|
||||
let dashboard = app.cache.dashboard(None, 0, 1);
|
||||
f.add(
|
||||
"rust_proxy_stream_max_connections",
|
||||
"rust_proxy_cache_statistics_revision",
|
||||
"gauge",
|
||||
"Configured stream connection/session capacity.",
|
||||
&l,
|
||||
host.max_connections,
|
||||
"Revision that owns domain statistics.",
|
||||
"",
|
||||
dashboard.statistics_revision,
|
||||
);
|
||||
}
|
||||
}
|
||||
if settings.include_logging {
|
||||
let stats = app.logs.stats();
|
||||
f.add(
|
||||
"rust_proxy_log_sink_queue_capacity",
|
||||
"gauge",
|
||||
"Structured log sink queue capacity.",
|
||||
"",
|
||||
stats.queue_capacity,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_queue_dropped_total",
|
||||
"counter",
|
||||
"Structured log entries dropped because the sink queue was full.",
|
||||
"",
|
||||
stats.queue_dropped,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_sent_total",
|
||||
"counter",
|
||||
"Structured log entries delivered to configured sinks.",
|
||||
"",
|
||||
stats.sent,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_log_sink_errors_total",
|
||||
"counter",
|
||||
"Structured log sink delivery errors.",
|
||||
"",
|
||||
stats.errors,
|
||||
);
|
||||
}
|
||||
if settings.include_cache_domains {
|
||||
// Bounded resident inspector. Counter families are gauges deliberately:
|
||||
// these per-domain values reset on config publication, unlike globals.
|
||||
let dashboard = app.cache.dashboard(None, 0, 1);
|
||||
f.add(
|
||||
"rust_proxy_cache_statistics_revision",
|
||||
"gauge",
|
||||
"Revision that owns domain statistics.",
|
||||
"",
|
||||
dashboard.statistics_revision,
|
||||
);
|
||||
for domain in dashboard.domains {
|
||||
// Only configured names, never arbitrary Host input or unknown scope.
|
||||
if !config
|
||||
.vhosts
|
||||
.iter()
|
||||
.any(|h| h.id == domain.vhost && h.frontend.hosts.contains(&domain.domain))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let l = labels(&[("vhost", &domain.vhost), ("domain", &domain.domain)]);
|
||||
for (name, n) in [
|
||||
("objects", domain.entries as u64),
|
||||
("hot_objects", domain.hot_entries as u64),
|
||||
("resident_bytes", domain.resident_bytes as u64),
|
||||
("cold_objects", domain.cold_entries as u64),
|
||||
("cold_bytes", domain.cold_bytes),
|
||||
("hits", domain.counters.hits),
|
||||
("misses", domain.counters.misses),
|
||||
("bypasses", domain.counters.bypasses),
|
||||
("stored", domain.counters.stored),
|
||||
("saved_body_bytes", domain.counters.saved_body_bytes),
|
||||
] {
|
||||
for domain in dashboard.domains {
|
||||
// Only configured names, never arbitrary Host input or unknown scope.
|
||||
if !config
|
||||
.vhosts
|
||||
.iter()
|
||||
.any(|h| h.id == domain.vhost && h.frontend.hosts.contains(&domain.domain))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let l = labels(&[("vhost", &domain.vhost), ("domain", &domain.domain)]);
|
||||
for (name, n) in [
|
||||
("objects", domain.entries as u64),
|
||||
("hot_objects", domain.hot_entries as u64),
|
||||
("resident_bytes", domain.resident_bytes as u64),
|
||||
("cold_objects", domain.cold_entries as u64),
|
||||
("cold_bytes", domain.cold_bytes),
|
||||
("hits", domain.counters.hits),
|
||||
("misses", domain.counters.misses),
|
||||
("bypasses", domain.counters.bypasses),
|
||||
("stored", domain.counters.stored),
|
||||
("saved_body_bytes", domain.counters.saved_body_bytes),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_cache_domain_{name}"),
|
||||
"gauge",
|
||||
"Domain cache observation since current configuration revision.",
|
||||
&l,
|
||||
n,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
&format!("rust_proxy_cache_domain_{name}"),
|
||||
"rust_proxy_cache_domain_hit_ratio",
|
||||
"gauge",
|
||||
"Domain cache observation since current configuration revision.",
|
||||
"Domain HIT ratio 0..1.",
|
||||
&l,
|
||||
n,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_cache_domain_hit_ratio",
|
||||
"gauge",
|
||||
"Domain HIT ratio 0..1.",
|
||||
&l,
|
||||
domain.hit_ratio,
|
||||
);
|
||||
for (reason, count) in domain.counters.reasons {
|
||||
f.add(
|
||||
"rust_proxy_cache_domain_decisions",
|
||||
"gauge",
|
||||
"Decision reasons since config publication.",
|
||||
&format!("{l},reason=\"{}\"", escaped(&reason)),
|
||||
count,
|
||||
domain.hit_ratio,
|
||||
);
|
||||
for (reason, count) in domain.counters.reasons {
|
||||
f.add(
|
||||
"rust_proxy_cache_domain_decisions",
|
||||
"gauge",
|
||||
"Decision reasons since config publication.",
|
||||
&format!("{l},reason=\"{}\"", escaped(&reason)),
|
||||
count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn render_histograms(snapshot: &crate::config::Snapshot, docker_targets: &DockerTargets<'_>, out: &mut String) {
|
||||
fn render_histograms(
|
||||
snapshot: &crate::config::Snapshot,
|
||||
docker_targets: &DockerTargets<'_>,
|
||||
out: &mut String,
|
||||
) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_vhosts {
|
||||
out.push_str("# HELP rust_proxy_vhost_exchange_duration_seconds Full exchange lifetime including streaming and cancellation.\n# TYPE rust_proxy_vhost_exchange_duration_seconds histogram\n");
|
||||
for (id, m) in &snapshot.vhost_metrics {
|
||||
if docker_targets.contains_key(id) {
|
||||
continue;
|
||||
}
|
||||
let l = labels(&[("vhost", id)]);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_vhost_exchange_duration_seconds_sum{{{l}}} {:.6}",
|
||||
m.duration_us.load(Ordering::Relaxed) as f64 / 1e6
|
||||
);
|
||||
// Read the cumulative last bucket once for +Inf and count.
|
||||
let count =
|
||||
m.buckets[crate::metrics::DURATION_BUCKETS_US.len()].load(Ordering::Relaxed);
|
||||
let mut previous = 0;
|
||||
for (i, threshold) in crate::metrics::DURATION_BUCKETS_US.iter().enumerate() {
|
||||
let value = m.buckets[i]
|
||||
.load(Ordering::Relaxed)
|
||||
.max(previous)
|
||||
.min(count);
|
||||
previous = value;
|
||||
if settings.include_vhosts {
|
||||
out.push_str("# HELP rust_proxy_vhost_exchange_duration_seconds Full exchange lifetime including streaming and cancellation.\n# TYPE rust_proxy_vhost_exchange_duration_seconds histogram\n");
|
||||
for (id, m) in &snapshot.vhost_metrics {
|
||||
if docker_targets.contains_key(id) {
|
||||
continue;
|
||||
}
|
||||
let l = labels(&[("vhost", id)]);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_vhost_exchange_duration_seconds_bucket{{{l},le=\"{}\"}} {value}",
|
||||
*threshold as f64 / 1e6
|
||||
"rust_proxy_vhost_exchange_duration_seconds_sum{{{l}}} {:.6}",
|
||||
m.duration_us.load(Ordering::Relaxed) as f64 / 1e6
|
||||
);
|
||||
// Read the cumulative last bucket once for +Inf and count.
|
||||
let count =
|
||||
m.buckets[crate::metrics::DURATION_BUCKETS_US.len()].load(Ordering::Relaxed);
|
||||
let mut previous = 0;
|
||||
for (i, threshold) in crate::metrics::DURATION_BUCKETS_US.iter().enumerate() {
|
||||
let value = m.buckets[i]
|
||||
.load(Ordering::Relaxed)
|
||||
.max(previous)
|
||||
.min(count);
|
||||
previous = value;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_vhost_exchange_duration_seconds_bucket{{{l},le=\"{}\"}} {value}",
|
||||
*threshold as f64 / 1e6
|
||||
);
|
||||
}
|
||||
let _ = writeln!(out, "rust_proxy_vhost_exchange_duration_seconds_bucket{{{l},le=\"+Inf\"}} {count}\nrust_proxy_vhost_exchange_duration_seconds_count{{{l}}} {count}");
|
||||
}
|
||||
let _ = writeln!(out, "rust_proxy_vhost_exchange_duration_seconds_bucket{{{l},le=\"+Inf\"}} {count}\nrust_proxy_vhost_exchange_duration_seconds_count{{{l}}} {count}");
|
||||
}
|
||||
}
|
||||
if settings.include_docker_vhosts {
|
||||
out.push_str("# HELP rust_proxy_docker_vhost_exchange_duration_seconds Full Docker-vhost exchange lifetime including streaming and cancellation.\n# TYPE rust_proxy_docker_vhost_exchange_duration_seconds histogram\n");
|
||||
for (id, m) in &snapshot.vhost_metrics {
|
||||
let Some((agent, assignment)) = docker_targets.get(id) else {
|
||||
continue;
|
||||
};
|
||||
let l = labels(&[
|
||||
("agent", &agent.id),
|
||||
("vhost", id),
|
||||
("service", &assignment.service_key),
|
||||
]);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_docker_vhost_exchange_duration_seconds_sum{{{l}}} {:.6}",
|
||||
m.duration_us.load(Ordering::Relaxed) as f64 / 1e6
|
||||
);
|
||||
let count =
|
||||
m.buckets[crate::metrics::DURATION_BUCKETS_US.len()].load(Ordering::Relaxed);
|
||||
let mut previous = 0;
|
||||
for (i, threshold) in crate::metrics::DURATION_BUCKETS_US.iter().enumerate() {
|
||||
let value = m.buckets[i]
|
||||
.load(Ordering::Relaxed)
|
||||
.max(previous)
|
||||
.min(count);
|
||||
previous = value;
|
||||
if settings.include_docker_vhosts {
|
||||
out.push_str("# HELP rust_proxy_docker_vhost_exchange_duration_seconds Full Docker-vhost exchange lifetime including streaming and cancellation.\n# TYPE rust_proxy_docker_vhost_exchange_duration_seconds histogram\n");
|
||||
for (id, m) in &snapshot.vhost_metrics {
|
||||
let Some((agent, assignment)) = docker_targets.get(id) else {
|
||||
continue;
|
||||
};
|
||||
let l = labels(&[
|
||||
("agent", &agent.id),
|
||||
("vhost", id),
|
||||
("service", &assignment.service_key),
|
||||
]);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_docker_vhost_exchange_duration_seconds_sum{{{l}}} {:.6}",
|
||||
m.duration_us.load(Ordering::Relaxed) as f64 / 1e6
|
||||
);
|
||||
let count =
|
||||
m.buckets[crate::metrics::DURATION_BUCKETS_US.len()].load(Ordering::Relaxed);
|
||||
let mut previous = 0;
|
||||
for (i, threshold) in crate::metrics::DURATION_BUCKETS_US.iter().enumerate() {
|
||||
let value = m.buckets[i]
|
||||
.load(Ordering::Relaxed)
|
||||
.max(previous)
|
||||
.min(count);
|
||||
previous = value;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"rust_proxy_docker_vhost_exchange_duration_seconds_bucket{{{l},le=\"{}\"}} {value}",
|
||||
*threshold as f64 / 1e6
|
||||
);
|
||||
}
|
||||
let _ = writeln!(out, "rust_proxy_docker_vhost_exchange_duration_seconds_bucket{{{l},le=\"+Inf\"}} {count}\nrust_proxy_docker_vhost_exchange_duration_seconds_count{{{l}}} {count}");
|
||||
}
|
||||
let _ = writeln!(out, "rust_proxy_docker_vhost_exchange_duration_seconds_bucket{{{l},le=\"+Inf\"}} {count}\nrust_proxy_docker_vhost_exchange_duration_seconds_count{{{l}}} {count}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn render(app: &App) -> String {
|
||||
let snapshot = app.config.load_full();
|
||||
let config = &snapshot.envelope.config;
|
||||
@@ -236,6 +238,7 @@ pub fn render(app: &App) -> String {
|
||||
let mut families = Families::default();
|
||||
render_core(app, &snapshot, &mut out, &mut families);
|
||||
render_vhosts(&snapshot, &mut families);
|
||||
render_traffic(app, &snapshot, &mut families);
|
||||
render_backends(&snapshot, &docker_targets, &mut families);
|
||||
render_docker(&snapshot, &docker_targets, &mut families);
|
||||
render_misc(app, &snapshot, &mut families);
|
||||
|
||||
@@ -29,6 +29,7 @@ pub struct Settings {
|
||||
pub allow_trusted_network_without_token: bool,
|
||||
pub include_system: bool,
|
||||
pub include_vhosts: bool,
|
||||
pub include_traffic: bool,
|
||||
pub include_backends: bool,
|
||||
pub include_docker_vhosts: bool,
|
||||
pub include_cache_domains: bool,
|
||||
@@ -46,6 +47,7 @@ impl Default for Settings {
|
||||
allow_trusted_network_without_token: false,
|
||||
include_system: true,
|
||||
include_vhosts: true,
|
||||
include_traffic: true,
|
||||
include_backends: true,
|
||||
include_docker_vhosts: true,
|
||||
include_cache_domains: true,
|
||||
@@ -252,7 +254,8 @@ impl Families {
|
||||
}
|
||||
}
|
||||
|
||||
type DockerTargets<'a> = BTreeMap<String, (&'a crate::docker::Agent, &'a crate::docker::Assignment)>;
|
||||
type DockerTargets<'a> =
|
||||
BTreeMap<String, (&'a crate::docker::Agent, &'a crate::docker::Assignment)>;
|
||||
|
||||
fn docker_targets(config: &crate::config::Config) -> DockerTargets<'_> {
|
||||
let mut targets = BTreeMap::new();
|
||||
|
||||
@@ -4,6 +4,7 @@ mod tests {
|
||||
fn secure_defaults_and_paths() {
|
||||
let mut settings = Settings::default();
|
||||
assert!(!settings.enabled);
|
||||
assert!(settings.include_traffic);
|
||||
settings.validate().unwrap();
|
||||
settings.enabled = true;
|
||||
settings.listen = "0.0.0.0:9191".parse().unwrap();
|
||||
@@ -27,6 +28,21 @@ mod tests {
|
||||
assert!(settings.validate().is_err());
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn traffic_labels_are_bounded() {
|
||||
assert!(safe_country("PL"));
|
||||
assert!(!safe_country("pl"));
|
||||
assert!(!safe_country("POL"));
|
||||
assert!(safe_traffic_state(
|
||||
"web_server",
|
||||
&["web_server", "reverse_proxy"]
|
||||
));
|
||||
assert!(!safe_traffic_state(
|
||||
"attacker-value",
|
||||
&["web_server", "reverse_proxy"]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaping_and_grouping() {
|
||||
assert_eq!(escaped("a\"b\\c\nd"), "a\\\"b\\\\c\\nd");
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
fn safe_country(value: &str) -> bool {
|
||||
value.len() == 2 && value.bytes().all(|byte| byte.is_ascii_uppercase())
|
||||
}
|
||||
|
||||
fn safe_traffic_state(value: &str, allowed: &[&str]) -> bool {
|
||||
allowed.contains(&value)
|
||||
}
|
||||
|
||||
fn render_traffic(app: &App, snapshot: &crate::config::Snapshot, f: &mut Families) {
|
||||
let config = &snapshot.envelope.config;
|
||||
if !config.prometheus.include_traffic {
|
||||
return;
|
||||
}
|
||||
|
||||
let overall = app.traffic.view(None);
|
||||
f.add(
|
||||
"rust_proxy_traffic_user_agent_capture_enabled",
|
||||
"gauge",
|
||||
"Whether Traffic/logging User-Agent capture is enabled.",
|
||||
"",
|
||||
u8::from(config.logging.capture_user_agent),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_traffic_country_capture_enabled",
|
||||
"gauge",
|
||||
"Whether Traffic/logging country capture is enabled.",
|
||||
"",
|
||||
u8::from(config.logging.capture_country),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_traffic_queue_capacity",
|
||||
"gauge",
|
||||
"Bounded asynchronous Traffic analytics queue capacity.",
|
||||
"",
|
||||
overall.queue_capacity,
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_traffic_queue_dropped_total",
|
||||
"counter",
|
||||
"Traffic analytics samples dropped because the bounded queue was full.",
|
||||
"",
|
||||
overall.queue_dropped,
|
||||
);
|
||||
|
||||
let summaries = app.traffic.summaries();
|
||||
for host in &config.vhosts {
|
||||
let Some(data) = summaries.get(&host.id) else {
|
||||
continue;
|
||||
};
|
||||
let base = labels(&[("vhost", &host.id)]);
|
||||
for (name, help, value) in [
|
||||
(
|
||||
"rust_proxy_traffic_requests_total",
|
||||
"Completed requests retained by bounded Traffic analytics.",
|
||||
data.requests,
|
||||
),
|
||||
(
|
||||
"rust_proxy_traffic_errors_total",
|
||||
"Completed 5xx requests retained by bounded Traffic analytics.",
|
||||
data.errors,
|
||||
),
|
||||
(
|
||||
"rust_proxy_traffic_request_bytes_total",
|
||||
"Request body bytes retained by bounded Traffic analytics.",
|
||||
data.request_bytes,
|
||||
),
|
||||
(
|
||||
"rust_proxy_traffic_response_bytes_total",
|
||||
"Response body bytes retained by bounded Traffic analytics.",
|
||||
data.response_bytes,
|
||||
),
|
||||
(
|
||||
"rust_proxy_traffic_duration_milliseconds_total",
|
||||
"Accumulated request duration retained by bounded Traffic analytics.",
|
||||
data.duration_ms,
|
||||
),
|
||||
] {
|
||||
f.add(name, "counter", help, &base, value);
|
||||
}
|
||||
|
||||
for entry in &data.countries {
|
||||
if safe_country(&entry.value) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_country_requests",
|
||||
"gauge",
|
||||
"Approximate bounded top-country request counts since Traffic analytics reset.",
|
||||
&format!("{base},country=\"{}\"", escaped(&entry.value)),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.user_agent_families {
|
||||
if safe_traffic_state(
|
||||
&entry.value,
|
||||
&[
|
||||
"bot", "curl", "wget", "python", "go", "java", "postman", "edge", "opera",
|
||||
"chrome", "firefox", "safari", "other",
|
||||
],
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_user_agent_family_requests_total",
|
||||
"counter",
|
||||
"Requests grouped into a fixed User-Agent family set; raw User-Agent values are never Prometheus labels.",
|
||||
&format!("{base},family=\"{}\"", entry.value),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.method_families {
|
||||
if safe_traffic_state(
|
||||
&entry.value,
|
||||
&[
|
||||
"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "CONNECT", "TRACE",
|
||||
"OTHER",
|
||||
],
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_method_requests_total",
|
||||
"counter",
|
||||
"Requests grouped into a fixed HTTP method set.",
|
||||
&format!("{base},method=\"{}\"", entry.value),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.sources {
|
||||
if safe_traffic_state(
|
||||
&entry.value,
|
||||
&[
|
||||
"reverse_proxy",
|
||||
"web_server",
|
||||
"local_response",
|
||||
"unresolved",
|
||||
],
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_source_requests_total",
|
||||
"counter",
|
||||
"Requests grouped by bounded serving source, including the built-in Web Server.",
|
||||
&format!("{base},source=\"{}\"", entry.value),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.statuses {
|
||||
if safe_traffic_state(&entry.value, &["1xx", "2xx", "3xx", "4xx", "5xx"]) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_status_class_requests_total",
|
||||
"counter",
|
||||
"Requests grouped by HTTP response status class.",
|
||||
&format!("{base},class=\"{}\"", entry.value),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.cache {
|
||||
if safe_traffic_state(&entry.value, &["HIT", "MISS", "BYPASS", "-"]) {
|
||||
f.add(
|
||||
"rust_proxy_traffic_cache_requests_total",
|
||||
"counter",
|
||||
"Requests grouped by bounded cache outcome.",
|
||||
&format!("{base},cache=\"{}\"", escaped(&entry.value)),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
for entry in &data.routes {
|
||||
let configured = entry.value == "(main)"
|
||||
|| host
|
||||
.routes
|
||||
.iter()
|
||||
.any(|route| route.enabled && route.id == entry.value);
|
||||
if configured {
|
||||
f.add(
|
||||
"rust_proxy_traffic_route_requests",
|
||||
"gauge",
|
||||
"Approximate bounded top-route request counts for currently configured route identifiers.",
|
||||
&format!("{base},route=\"{}\"", escaped(&entry.value)),
|
||||
entry.count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+356
-286
@@ -1,253 +1,15 @@
|
||||
fn render_backends(snapshot: &crate::config::Snapshot, docker_targets: &DockerTargets<'_>, f: &mut Families) {
|
||||
fn render_backends(
|
||||
snapshot: &crate::config::Snapshot,
|
||||
docker_targets: &DockerTargets<'_>,
|
||||
f: &mut Families,
|
||||
) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_backends {
|
||||
for pool in &snapshot.pools {
|
||||
if docker_targets.contains_key(&pool.id) {
|
||||
continue;
|
||||
}
|
||||
let view = pool.view();
|
||||
if let (Some(algorithm), Some(health_mode), Some(sticky_mode)) = (
|
||||
view["algorithm"].as_str(),
|
||||
view["health_mode"].as_str(),
|
||||
view["sticky_mode"].as_str(),
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_backend_pool_info",
|
||||
"gauge",
|
||||
"Bounded backend-pool selection and health policy.",
|
||||
&labels(&[
|
||||
("vhost", &pool.id),
|
||||
("algorithm", algorithm),
|
||||
("health_mode", health_mode),
|
||||
("sticky_mode", sticky_mode),
|
||||
]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_backend_pool_ready",
|
||||
"gauge",
|
||||
"At least one healthy eligible pool member.",
|
||||
&labels(&[("vhost", &pool.id)]),
|
||||
u8::from(pool.ready()),
|
||||
);
|
||||
if let Some(members) = view["members"].as_array() {
|
||||
for member in members {
|
||||
let l = labels(&[
|
||||
("vhost", &pool.id),
|
||||
("backend", member["id"].as_str().unwrap_or("unknown")),
|
||||
]);
|
||||
let status = member["status"].as_str().unwrap_or("unknown");
|
||||
f.add(
|
||||
"rust_proxy_backend_status_info",
|
||||
"gauge",
|
||||
"Latest bounded backend selection/health state.",
|
||||
&format!("{l},status=\"{}\"", escaped(status)),
|
||||
1,
|
||||
);
|
||||
for (name, flag) in [
|
||||
(
|
||||
"up",
|
||||
status != "disabled" && member["healthy"].as_bool().unwrap_or(false),
|
||||
),
|
||||
("draining", status == "draining"),
|
||||
("backup", member["backup"].as_bool().unwrap_or(false)),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_backend_{name}"),
|
||||
"gauge",
|
||||
"Backend health or selection state.",
|
||||
&l,
|
||||
u8::from(flag),
|
||||
);
|
||||
}
|
||||
for (field, metric, kind) in [
|
||||
("active", "active_exchanges", "gauge"),
|
||||
("requests", "requests_total", "counter"),
|
||||
("errors", "errors_total", "counter"),
|
||||
("bytes_in", "bytes_in_total", "counter"),
|
||||
("bytes_out", "bytes_out_total", "counter"),
|
||||
("weight", "weight", "gauge"),
|
||||
("failures", "consecutive_failures", "gauge"),
|
||||
("passive_failures", "passive_failures", "gauge"),
|
||||
("successes", "consecutive_successes", "gauge"),
|
||||
("passive_successes", "passive_successes", "gauge"),
|
||||
("transitions", "health_transitions_total", "counter"),
|
||||
("active_health_failures_total", "active_health_failures_total", "counter"),
|
||||
("passive_health_failures_total", "passive_health_failures_total", "counter"),
|
||||
("http_5xx_total", "http_5xx_total", "counter"),
|
||||
("http_5xx_total", "passive_http_5xx_total", "counter"),
|
||||
("timeouts_total", "timeouts_total", "counter"),
|
||||
("timeouts_total", "passive_timeouts_total", "counter"),
|
||||
("connect_errors_total", "connect_errors_total", "counter"),
|
||||
("connect_errors_total", "passive_connect_errors_total", "counter"),
|
||||
("io_errors_total", "io_errors_total", "counter"),
|
||||
("io_errors_total", "passive_io_errors_total", "counter"),
|
||||
("slow_responses_total", "slow_responses_total", "counter"),
|
||||
("slow_responses_total", "passive_slow_total", "counter"),
|
||||
("capacity_rejections_total", "capacity_rejections_total", "counter"),
|
||||
("queue_timeouts_total", "queue_timeouts_total", "counter"),
|
||||
("dns_errors_total", "dns_errors_total", "counter"),
|
||||
("tls_errors_total", "tls_errors_total", "counter"),
|
||||
("response_header_timeouts_total", "response_header_timeouts_total", "counter"),
|
||||
("read_timeouts_total", "read_timeouts_total", "counter"),
|
||||
("send_timeouts_total", "send_timeouts_total", "counter"),
|
||||
("max_concurrent_requests", "max_concurrent_requests", "gauge"),
|
||||
("queue_timeout_ms", "queue_timeout_milliseconds", "gauge"),
|
||||
("last_status", "last_http_status", "gauge"),
|
||||
] {
|
||||
if let Some(n) = member[field].as_u64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_backend_{metric}"),
|
||||
kind,
|
||||
"Backend observation.",
|
||||
&l,
|
||||
n,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (field, metric, divisor) in [
|
||||
("latency_ms", "latency_seconds", 1000.0),
|
||||
("ewma_latency_ms", "ewma_latency_seconds", 1000.0),
|
||||
("last_probe_ms", "last_probe_timestamp_seconds", 1000.0),
|
||||
("last_failure_at_ms", "last_failure_timestamp_seconds", 1000.0),
|
||||
("last_ejected_ms", "last_ejected_timestamp_seconds", 1000.0),
|
||||
("last_recovered_ms", "last_recovered_timestamp_seconds", 1000.0),
|
||||
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
|
||||
] {
|
||||
if let Some(n) = member[field].as_f64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_backend_{metric}"),
|
||||
"gauge",
|
||||
"Backend moving observation.",
|
||||
&l,
|
||||
n / divisor,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(result) = member["last_result"]
|
||||
.as_str()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
f.add(
|
||||
"rust_proxy_backend_last_probe_result_info",
|
||||
"gauge",
|
||||
"Latest bounded backend health-probe result.",
|
||||
&format!("{l},result=\"{}\"", escaped(result)),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn render_docker(snapshot: &crate::config::Snapshot, docker_targets: &DockerTargets<'_>, f: &mut Families) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_docker_vhosts {
|
||||
f.add(
|
||||
"rust_proxy_docker_agents_configured",
|
||||
"gauge",
|
||||
"Configured Docker discovery agents including disabled agents.",
|
||||
"",
|
||||
config.docker_agents.agents.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_vhosts_configured",
|
||||
"gauge",
|
||||
"Configured Docker vhost assignments including disabled assignments.",
|
||||
"",
|
||||
docker_targets.len(),
|
||||
);
|
||||
for agent in &config.docker_agents.agents {
|
||||
let agent_labels = labels(&[("agent", &agent.id)]);
|
||||
f.add(
|
||||
"rust_proxy_docker_agent_enabled",
|
||||
"gauge",
|
||||
"Desired Docker agent enable flag.",
|
||||
&agent_labels,
|
||||
u8::from(agent.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_agent_assignments",
|
||||
"gauge",
|
||||
"Configured Docker vhost assignments for an agent.",
|
||||
&agent_labels,
|
||||
agent.assignments.len(),
|
||||
);
|
||||
for assignment in &agent.assignments {
|
||||
let vhost = crate::docker::generated_id(&agent.id, &assignment.service_key);
|
||||
let base_labels = labels(&[
|
||||
("agent", &agent.id),
|
||||
("vhost", &vhost),
|
||||
("service", &assignment.service_key),
|
||||
]);
|
||||
let domains = crate::docker::assignment_hosts(assignment, &agent.base_domain)
|
||||
.unwrap_or_default();
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_enabled",
|
||||
"gauge",
|
||||
"Desired Docker vhost assignment enable flag.",
|
||||
&base_labels,
|
||||
u8::from(agent.enabled && assignment.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a Docker vhost assignment.",
|
||||
&base_labels,
|
||||
domains.len(),
|
||||
);
|
||||
for domain in domains {
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_domain_info",
|
||||
"gauge",
|
||||
"Configured Docker vhost name ownership.",
|
||||
&format!("{base_labels},domain=\"{}\"", escaped(&domain)),
|
||||
1,
|
||||
);
|
||||
}
|
||||
if let Some(m) = snapshot.vhost_metrics.get(&vhost) {
|
||||
for (name, counter) in [
|
||||
("requests_total", &m.requests),
|
||||
("completed_total", &m.completed),
|
||||
("errors_total", &m.errors),
|
||||
("rejected_total", &m.rejected),
|
||||
("request_bytes_total", &m.bytes_in),
|
||||
("response_bytes_total", &m.bytes_out),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_vhost_{name}"),
|
||||
"counter",
|
||||
"Docker vhost exchange counter; reset on process restart or assignment removal.",
|
||||
&base_labels,
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_active",
|
||||
"gauge",
|
||||
"Admitted unfinished Docker-vhost exchanges.",
|
||||
&base_labels,
|
||||
m.active.load(Ordering::Relaxed),
|
||||
);
|
||||
for (index, counter) in m.status.iter().enumerate() {
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_responses_total",
|
||||
"counter",
|
||||
"Completed Docker-vhost exchanges by status class.",
|
||||
&format!("{base_labels},class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
}
|
||||
let Some(pool) = snapshot.pools.iter().find(|pool| pool.id == vhost) else {
|
||||
if settings.include_backends {
|
||||
for pool in &snapshot.pools {
|
||||
if docker_targets.contains_key(&pool.id) {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
let view = pool.view();
|
||||
if let (Some(algorithm), Some(health_mode), Some(sticky_mode)) = (
|
||||
view["algorithm"].as_str(),
|
||||
@@ -255,52 +17,52 @@ if settings.include_docker_vhosts {
|
||||
view["sticky_mode"].as_str(),
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_pool_info",
|
||||
"rust_proxy_backend_pool_info",
|
||||
"gauge",
|
||||
"Bounded Docker backend-pool selection and health policy.",
|
||||
&format!(
|
||||
"{base_labels},algorithm=\"{}\",health_mode=\"{}\",sticky_mode=\"{}\"",
|
||||
escaped(algorithm),
|
||||
escaped(health_mode),
|
||||
escaped(sticky_mode)
|
||||
),
|
||||
"Bounded backend-pool selection and health policy.",
|
||||
&labels(&[
|
||||
("vhost", &pool.id),
|
||||
("algorithm", algorithm),
|
||||
("health_mode", health_mode),
|
||||
("sticky_mode", sticky_mode),
|
||||
]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_pool_ready",
|
||||
"rust_proxy_backend_pool_ready",
|
||||
"gauge",
|
||||
"At least one healthy eligible Docker backend member.",
|
||||
&base_labels,
|
||||
"At least one healthy eligible pool member.",
|
||||
&labels(&[("vhost", &pool.id)]),
|
||||
u8::from(pool.ready()),
|
||||
);
|
||||
if let Some(members) = view["members"].as_array() {
|
||||
for member in members {
|
||||
let backend = member["id"].as_str().unwrap_or("unknown");
|
||||
let member_labels =
|
||||
format!("{base_labels},backend=\"{}\"", escaped(backend));
|
||||
let l = labels(&[
|
||||
("vhost", &pool.id),
|
||||
("backend", member["id"].as_str().unwrap_or("unknown")),
|
||||
]);
|
||||
let status = member["status"].as_str().unwrap_or("unknown");
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_status_info",
|
||||
"rust_proxy_backend_status_info",
|
||||
"gauge",
|
||||
"Latest bounded Docker backend selection/health state.",
|
||||
&format!("{member_labels},status=\"{}\"", escaped(status)),
|
||||
"Latest bounded backend selection/health state.",
|
||||
&format!("{l},status=\"{}\"", escaped(status)),
|
||||
1,
|
||||
);
|
||||
for (name, flag) in [
|
||||
(
|
||||
"up",
|
||||
status != "disabled"
|
||||
&& member["healthy"].as_bool().unwrap_or(false),
|
||||
status != "disabled" && member["healthy"].as_bool().unwrap_or(false),
|
||||
),
|
||||
("draining", status == "draining"),
|
||||
("backup", member["backup"].as_bool().unwrap_or(false)),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{name}"),
|
||||
&format!("rust_proxy_backend_{name}"),
|
||||
"gauge",
|
||||
"Docker backend health or selection state.",
|
||||
&member_labels,
|
||||
"Backend health or selection state.",
|
||||
&l,
|
||||
u8::from(flag),
|
||||
);
|
||||
}
|
||||
@@ -316,35 +78,59 @@ if settings.include_docker_vhosts {
|
||||
("successes", "consecutive_successes", "gauge"),
|
||||
("passive_successes", "passive_successes", "gauge"),
|
||||
("transitions", "health_transitions_total", "counter"),
|
||||
("active_health_failures_total", "active_health_failures_total", "counter"),
|
||||
("passive_health_failures_total", "passive_health_failures_total", "counter"),
|
||||
(
|
||||
"active_health_failures_total",
|
||||
"active_health_failures_total",
|
||||
"counter",
|
||||
),
|
||||
(
|
||||
"passive_health_failures_total",
|
||||
"passive_health_failures_total",
|
||||
"counter",
|
||||
),
|
||||
("http_5xx_total", "http_5xx_total", "counter"),
|
||||
("http_5xx_total", "passive_http_5xx_total", "counter"),
|
||||
("timeouts_total", "timeouts_total", "counter"),
|
||||
("timeouts_total", "passive_timeouts_total", "counter"),
|
||||
("connect_errors_total", "connect_errors_total", "counter"),
|
||||
("connect_errors_total", "passive_connect_errors_total", "counter"),
|
||||
(
|
||||
"connect_errors_total",
|
||||
"passive_connect_errors_total",
|
||||
"counter",
|
||||
),
|
||||
("io_errors_total", "io_errors_total", "counter"),
|
||||
("io_errors_total", "passive_io_errors_total", "counter"),
|
||||
("slow_responses_total", "slow_responses_total", "counter"),
|
||||
("slow_responses_total", "passive_slow_total", "counter"),
|
||||
("capacity_rejections_total", "capacity_rejections_total", "counter"),
|
||||
(
|
||||
"capacity_rejections_total",
|
||||
"capacity_rejections_total",
|
||||
"counter",
|
||||
),
|
||||
("queue_timeouts_total", "queue_timeouts_total", "counter"),
|
||||
("dns_errors_total", "dns_errors_total", "counter"),
|
||||
("tls_errors_total", "tls_errors_total", "counter"),
|
||||
("response_header_timeouts_total", "response_header_timeouts_total", "counter"),
|
||||
(
|
||||
"response_header_timeouts_total",
|
||||
"response_header_timeouts_total",
|
||||
"counter",
|
||||
),
|
||||
("read_timeouts_total", "read_timeouts_total", "counter"),
|
||||
("send_timeouts_total", "send_timeouts_total", "counter"),
|
||||
("max_concurrent_requests", "max_concurrent_requests", "gauge"),
|
||||
(
|
||||
"max_concurrent_requests",
|
||||
"max_concurrent_requests",
|
||||
"gauge",
|
||||
),
|
||||
("queue_timeout_ms", "queue_timeout_milliseconds", "gauge"),
|
||||
("last_status", "last_http_status", "gauge"),
|
||||
] {
|
||||
if let Some(n) = member[field].as_u64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{metric}"),
|
||||
&format!("rust_proxy_backend_{metric}"),
|
||||
kind,
|
||||
"Docker backend observation.",
|
||||
&member_labels,
|
||||
"Backend observation.",
|
||||
&l,
|
||||
n,
|
||||
);
|
||||
}
|
||||
@@ -353,17 +139,25 @@ if settings.include_docker_vhosts {
|
||||
("latency_ms", "latency_seconds", 1000.0),
|
||||
("ewma_latency_ms", "ewma_latency_seconds", 1000.0),
|
||||
("last_probe_ms", "last_probe_timestamp_seconds", 1000.0),
|
||||
("last_failure_at_ms", "last_failure_timestamp_seconds", 1000.0),
|
||||
(
|
||||
"last_failure_at_ms",
|
||||
"last_failure_timestamp_seconds",
|
||||
1000.0,
|
||||
),
|
||||
("last_ejected_ms", "last_ejected_timestamp_seconds", 1000.0),
|
||||
("last_recovered_ms", "last_recovered_timestamp_seconds", 1000.0),
|
||||
(
|
||||
"last_recovered_ms",
|
||||
"last_recovered_timestamp_seconds",
|
||||
1000.0,
|
||||
),
|
||||
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
|
||||
] {
|
||||
if let Some(n) = member[field].as_f64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{metric}"),
|
||||
&format!("rust_proxy_backend_{metric}"),
|
||||
"gauge",
|
||||
"Docker backend moving observation.",
|
||||
&member_labels,
|
||||
"Backend moving observation.",
|
||||
&l,
|
||||
n / divisor,
|
||||
);
|
||||
}
|
||||
@@ -373,10 +167,10 @@ if settings.include_docker_vhosts {
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_last_probe_result_info",
|
||||
"rust_proxy_backend_last_probe_result_info",
|
||||
"gauge",
|
||||
"Latest bounded Docker backend health-probe result.",
|
||||
&format!("{member_labels},result=\"{}\"", escaped(result)),
|
||||
"Latest bounded backend health-probe result.",
|
||||
&format!("{l},result=\"{}\"", escaped(result)),
|
||||
1,
|
||||
);
|
||||
}
|
||||
@@ -386,4 +180,280 @@ if settings.include_docker_vhosts {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_docker(
|
||||
snapshot: &crate::config::Snapshot,
|
||||
docker_targets: &DockerTargets<'_>,
|
||||
f: &mut Families,
|
||||
) {
|
||||
let config = &snapshot.envelope.config;
|
||||
let settings = &config.prometheus;
|
||||
if settings.include_docker_vhosts {
|
||||
f.add(
|
||||
"rust_proxy_docker_agents_configured",
|
||||
"gauge",
|
||||
"Configured Docker discovery agents including disabled agents.",
|
||||
"",
|
||||
config.docker_agents.agents.len(),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_vhosts_configured",
|
||||
"gauge",
|
||||
"Configured Docker vhost assignments including disabled assignments.",
|
||||
"",
|
||||
docker_targets.len(),
|
||||
);
|
||||
for agent in &config.docker_agents.agents {
|
||||
let agent_labels = labels(&[("agent", &agent.id)]);
|
||||
f.add(
|
||||
"rust_proxy_docker_agent_enabled",
|
||||
"gauge",
|
||||
"Desired Docker agent enable flag.",
|
||||
&agent_labels,
|
||||
u8::from(agent.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_agent_assignments",
|
||||
"gauge",
|
||||
"Configured Docker vhost assignments for an agent.",
|
||||
&agent_labels,
|
||||
agent.assignments.len(),
|
||||
);
|
||||
for assignment in &agent.assignments {
|
||||
let vhost = crate::docker::generated_id(&agent.id, &assignment.service_key);
|
||||
let base_labels = labels(&[
|
||||
("agent", &agent.id),
|
||||
("vhost", &vhost),
|
||||
("service", &assignment.service_key),
|
||||
]);
|
||||
let domains = crate::docker::assignment_hosts(assignment, &agent.base_domain)
|
||||
.unwrap_or_default();
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_enabled",
|
||||
"gauge",
|
||||
"Desired Docker vhost assignment enable flag.",
|
||||
&base_labels,
|
||||
u8::from(agent.enabled && assignment.enabled),
|
||||
);
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_domains",
|
||||
"gauge",
|
||||
"Configured DNS names for a Docker vhost assignment.",
|
||||
&base_labels,
|
||||
domains.len(),
|
||||
);
|
||||
for domain in domains {
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_domain_info",
|
||||
"gauge",
|
||||
"Configured Docker vhost name ownership.",
|
||||
&format!("{base_labels},domain=\"{}\"", escaped(&domain)),
|
||||
1,
|
||||
);
|
||||
}
|
||||
if let Some(m) = snapshot.vhost_metrics.get(&vhost) {
|
||||
for (name, counter) in [
|
||||
("requests_total", &m.requests),
|
||||
("completed_total", &m.completed),
|
||||
("errors_total", &m.errors),
|
||||
("rejected_total", &m.rejected),
|
||||
("request_bytes_total", &m.bytes_in),
|
||||
("response_bytes_total", &m.bytes_out),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_vhost_{name}"),
|
||||
"counter",
|
||||
"Docker vhost exchange counter; reset on process restart or assignment removal.",
|
||||
&base_labels,
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_active",
|
||||
"gauge",
|
||||
"Admitted unfinished Docker-vhost exchanges.",
|
||||
&base_labels,
|
||||
m.active.load(Ordering::Relaxed),
|
||||
);
|
||||
for (index, counter) in m.status.iter().enumerate() {
|
||||
f.add(
|
||||
"rust_proxy_docker_vhost_responses_total",
|
||||
"counter",
|
||||
"Completed Docker-vhost exchanges by status class.",
|
||||
&format!("{base_labels},class=\"{}xx\"", index + 1),
|
||||
counter.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
}
|
||||
let Some(pool) = snapshot.pools.iter().find(|pool| pool.id == vhost) else {
|
||||
continue;
|
||||
};
|
||||
let view = pool.view();
|
||||
if let (Some(algorithm), Some(health_mode), Some(sticky_mode)) = (
|
||||
view["algorithm"].as_str(),
|
||||
view["health_mode"].as_str(),
|
||||
view["sticky_mode"].as_str(),
|
||||
) {
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_pool_info",
|
||||
"gauge",
|
||||
"Bounded Docker backend-pool selection and health policy.",
|
||||
&format!(
|
||||
"{base_labels},algorithm=\"{}\",health_mode=\"{}\",sticky_mode=\"{}\"",
|
||||
escaped(algorithm),
|
||||
escaped(health_mode),
|
||||
escaped(sticky_mode)
|
||||
),
|
||||
1,
|
||||
);
|
||||
}
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_pool_ready",
|
||||
"gauge",
|
||||
"At least one healthy eligible Docker backend member.",
|
||||
&base_labels,
|
||||
u8::from(pool.ready()),
|
||||
);
|
||||
if let Some(members) = view["members"].as_array() {
|
||||
for member in members {
|
||||
let backend = member["id"].as_str().unwrap_or("unknown");
|
||||
let member_labels =
|
||||
format!("{base_labels},backend=\"{}\"", escaped(backend));
|
||||
let status = member["status"].as_str().unwrap_or("unknown");
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_status_info",
|
||||
"gauge",
|
||||
"Latest bounded Docker backend selection/health state.",
|
||||
&format!("{member_labels},status=\"{}\"", escaped(status)),
|
||||
1,
|
||||
);
|
||||
for (name, flag) in [
|
||||
(
|
||||
"up",
|
||||
status != "disabled"
|
||||
&& member["healthy"].as_bool().unwrap_or(false),
|
||||
),
|
||||
("draining", status == "draining"),
|
||||
("backup", member["backup"].as_bool().unwrap_or(false)),
|
||||
] {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{name}"),
|
||||
"gauge",
|
||||
"Docker backend health or selection state.",
|
||||
&member_labels,
|
||||
u8::from(flag),
|
||||
);
|
||||
}
|
||||
for (field, metric, kind) in [
|
||||
("active", "active_exchanges", "gauge"),
|
||||
("requests", "requests_total", "counter"),
|
||||
("errors", "errors_total", "counter"),
|
||||
("bytes_in", "bytes_in_total", "counter"),
|
||||
("bytes_out", "bytes_out_total", "counter"),
|
||||
("weight", "weight", "gauge"),
|
||||
("failures", "consecutive_failures", "gauge"),
|
||||
("passive_failures", "passive_failures", "gauge"),
|
||||
("successes", "consecutive_successes", "gauge"),
|
||||
("passive_successes", "passive_successes", "gauge"),
|
||||
("transitions", "health_transitions_total", "counter"),
|
||||
(
|
||||
"active_health_failures_total",
|
||||
"active_health_failures_total",
|
||||
"counter",
|
||||
),
|
||||
(
|
||||
"passive_health_failures_total",
|
||||
"passive_health_failures_total",
|
||||
"counter",
|
||||
),
|
||||
("http_5xx_total", "http_5xx_total", "counter"),
|
||||
("http_5xx_total", "passive_http_5xx_total", "counter"),
|
||||
("timeouts_total", "timeouts_total", "counter"),
|
||||
("timeouts_total", "passive_timeouts_total", "counter"),
|
||||
("connect_errors_total", "connect_errors_total", "counter"),
|
||||
(
|
||||
"connect_errors_total",
|
||||
"passive_connect_errors_total",
|
||||
"counter",
|
||||
),
|
||||
("io_errors_total", "io_errors_total", "counter"),
|
||||
("io_errors_total", "passive_io_errors_total", "counter"),
|
||||
("slow_responses_total", "slow_responses_total", "counter"),
|
||||
("slow_responses_total", "passive_slow_total", "counter"),
|
||||
(
|
||||
"capacity_rejections_total",
|
||||
"capacity_rejections_total",
|
||||
"counter",
|
||||
),
|
||||
("queue_timeouts_total", "queue_timeouts_total", "counter"),
|
||||
("dns_errors_total", "dns_errors_total", "counter"),
|
||||
("tls_errors_total", "tls_errors_total", "counter"),
|
||||
(
|
||||
"response_header_timeouts_total",
|
||||
"response_header_timeouts_total",
|
||||
"counter",
|
||||
),
|
||||
("read_timeouts_total", "read_timeouts_total", "counter"),
|
||||
("send_timeouts_total", "send_timeouts_total", "counter"),
|
||||
(
|
||||
"max_concurrent_requests",
|
||||
"max_concurrent_requests",
|
||||
"gauge",
|
||||
),
|
||||
("queue_timeout_ms", "queue_timeout_milliseconds", "gauge"),
|
||||
("last_status", "last_http_status", "gauge"),
|
||||
] {
|
||||
if let Some(n) = member[field].as_u64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{metric}"),
|
||||
kind,
|
||||
"Docker backend observation.",
|
||||
&member_labels,
|
||||
n,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (field, metric, divisor) in [
|
||||
("latency_ms", "latency_seconds", 1000.0),
|
||||
("ewma_latency_ms", "ewma_latency_seconds", 1000.0),
|
||||
("last_probe_ms", "last_probe_timestamp_seconds", 1000.0),
|
||||
(
|
||||
"last_failure_at_ms",
|
||||
"last_failure_timestamp_seconds",
|
||||
1000.0,
|
||||
),
|
||||
("last_ejected_ms", "last_ejected_timestamp_seconds", 1000.0),
|
||||
(
|
||||
"last_recovered_ms",
|
||||
"last_recovered_timestamp_seconds",
|
||||
1000.0,
|
||||
),
|
||||
("traffic_bytes_per_second", "traffic_bytes_per_second", 1.0),
|
||||
] {
|
||||
if let Some(n) = member[field].as_f64() {
|
||||
f.add(
|
||||
&format!("rust_proxy_docker_backend_{metric}"),
|
||||
"gauge",
|
||||
"Docker backend moving observation.",
|
||||
&member_labels,
|
||||
n / divisor,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(result) = member["last_result"]
|
||||
.as_str()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
f.add(
|
||||
"rust_proxy_docker_backend_last_probe_result_info",
|
||||
"gauge",
|
||||
"Latest bounded Docker backend health-probe result.",
|
||||
&format!("{member_labels},result=\"{}\"", escaped(result)),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user