feat: distinguish UPSTREAM vs FORWARD in logs and stats #103
@@ -217,6 +217,7 @@ body {
|
|||||||
min-width: 2px;
|
min-width: 2px;
|
||||||
}
|
}
|
||||||
.path-bar-fill.forward { background: var(--amber); }
|
.path-bar-fill.forward { background: var(--amber); }
|
||||||
|
.path-bar-fill.upstream { background: var(--amber-dim); }
|
||||||
.path-bar-fill.recursive { background: var(--cyan); }
|
.path-bar-fill.recursive { background: var(--cyan); }
|
||||||
.path-bar-fill.cached { background: var(--teal); }
|
.path-bar-fill.cached { background: var(--teal); }
|
||||||
.path-bar-fill.local { background: var(--violet); }
|
.path-bar-fill.local { background: var(--violet); }
|
||||||
@@ -285,6 +286,7 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.path-tag.FORWARD { background: rgba(192, 98, 58, 0.12); color: var(--amber-dim); }
|
.path-tag.FORWARD { background: rgba(192, 98, 58, 0.12); color: var(--amber-dim); }
|
||||||
|
.path-tag.UPSTREAM { background: rgba(160, 120, 72, 0.12); color: var(--amber-dim); }
|
||||||
.path-tag.RECURSIVE { background: rgba(74, 124, 138, 0.12); color: var(--cyan); }
|
.path-tag.RECURSIVE { background: rgba(74, 124, 138, 0.12); color: var(--cyan); }
|
||||||
.path-tag.CACHED { background: rgba(107, 124, 78, 0.12); color: var(--teal-dim); }
|
.path-tag.CACHED { background: rgba(107, 124, 78, 0.12); color: var(--teal-dim); }
|
||||||
.path-tag.LOCAL { background: rgba(100, 116, 139, 0.12); color: var(--violet-dim); }
|
.path-tag.LOCAL { background: rgba(100, 116, 139, 0.12); color: var(--violet-dim); }
|
||||||
@@ -655,6 +657,7 @@ body {
|
|||||||
<option value="RECURSIVE">recursive</option>
|
<option value="RECURSIVE">recursive</option>
|
||||||
<option value="COALESCED">coalesced</option>
|
<option value="COALESCED">coalesced</option>
|
||||||
<option value="FORWARD">forward</option>
|
<option value="FORWARD">forward</option>
|
||||||
|
<option value="UPSTREAM">upstream</option>
|
||||||
<option value="CACHED">cached</option>
|
<option value="CACHED">cached</option>
|
||||||
<option value="BLOCKED">blocked</option>
|
<option value="BLOCKED">blocked</option>
|
||||||
<option value="OVERRIDE">override</option>
|
<option value="OVERRIDE">override</option>
|
||||||
@@ -936,7 +939,9 @@ function renderMemory(mem, stats) {
|
|||||||
|
|
||||||
function renderBarChart(containerId, defs, data, total) {
|
function renderBarChart(containerId, defs, data, total) {
|
||||||
total = total || 1;
|
total = total || 1;
|
||||||
document.getElementById(containerId).innerHTML = defs.map(d => {
|
document.getElementById(containerId).innerHTML = defs
|
||||||
|
.filter(d => (data[d.key] || 0) > 0)
|
||||||
|
.map(d => {
|
||||||
const count = data[d.key] || 0;
|
const count = data[d.key] || 0;
|
||||||
const pct = ((count / total) * 100).toFixed(1);
|
const pct = ((count / total) * 100).toFixed(1);
|
||||||
return `
|
return `
|
||||||
@@ -957,6 +962,7 @@ function encryptionPct(transport) {
|
|||||||
|
|
||||||
const PATH_DEFS = [
|
const PATH_DEFS = [
|
||||||
{ key: 'forwarded', label: 'Forward', cls: 'forward' },
|
{ key: 'forwarded', label: 'Forward', cls: 'forward' },
|
||||||
|
{ key: 'upstream', label: 'Upstream', cls: 'upstream' },
|
||||||
{ key: 'recursive', label: 'Recursive', cls: 'recursive' },
|
{ key: 'recursive', label: 'Recursive', cls: 'recursive' },
|
||||||
{ key: 'cached', label: 'Cached', cls: 'cached' },
|
{ key: 'cached', label: 'Cached', cls: 'cached' },
|
||||||
{ key: 'local', label: 'Local', cls: 'local' },
|
{ key: 'local', label: 'Local', cls: 'local' },
|
||||||
@@ -1209,7 +1215,7 @@ async function refresh() {
|
|||||||
prevTime = now;
|
prevTime = now;
|
||||||
|
|
||||||
// Cache hit rate
|
// Cache hit rate
|
||||||
const answered = q.cached + q.forwarded + q.recursive + q.coalesced + q.local + q.overridden;
|
const answered = q.cached + q.forwarded + q.upstream + q.recursive + q.coalesced + q.local + q.overridden;
|
||||||
const hitRate = answered > 0 ? ((q.cached / answered) * 100).toFixed(1) : '0.0';
|
const hitRate = answered > 0 ? ((q.cached / answered) * 100).toFixed(1) : '0.0';
|
||||||
document.getElementById('cacheRate').textContent = hitRate + '%';
|
document.getElementById('cacheRate').textContent = hitRate + '%';
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ struct LanStatsResponse {
|
|||||||
struct QueriesStats {
|
struct QueriesStats {
|
||||||
total: u64,
|
total: u64,
|
||||||
forwarded: u64,
|
forwarded: u64,
|
||||||
|
upstream: u64,
|
||||||
recursive: u64,
|
recursive: u64,
|
||||||
coalesced: u64,
|
coalesced: u64,
|
||||||
cached: u64,
|
cached: u64,
|
||||||
@@ -548,6 +549,7 @@ async fn stats(State(ctx): State<Arc<ServerCtx>>) -> Json<StatsResponse> {
|
|||||||
queries: QueriesStats {
|
queries: QueriesStats {
|
||||||
total: snap.total,
|
total: snap.total,
|
||||||
forwarded: snap.forwarded,
|
forwarded: snap.forwarded,
|
||||||
|
upstream: snap.upstream,
|
||||||
recursive: snap.recursive,
|
recursive: snap.recursive,
|
||||||
coalesced: snap.coalesced,
|
coalesced: snap.coalesced,
|
||||||
cached: snap.cached,
|
cached: snap.cached,
|
||||||
|
|||||||
27
src/ctx.rs
27
src/ctx.rs
@@ -246,7 +246,7 @@ pub async fn resolve_query(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(resp_wire) => match cache_and_parse(ctx, &qname, qtype, &resp_wire) {
|
Ok(resp_wire) => match cache_and_parse(ctx, &qname, qtype, &resp_wire) {
|
||||||
Ok(resp) => (resp, QueryPath::Forwarded, DnssecStatus::Indeterminate),
|
Ok(resp) => (resp, QueryPath::Upstream, DnssecStatus::Indeterminate),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{} | {:?} {} | PARSE ERROR | {}", src_addr, qtype, qname, e);
|
error!("{} | {:?} {} | PARSE ERROR | {}", src_addr, qtype, qname, e);
|
||||||
(
|
(
|
||||||
@@ -1253,4 +1253,29 @@ mod tests {
|
|||||||
other => panic!("expected A record, got {:?}", other),
|
other => panic!("expected A record, got {:?}", other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pipeline_default_pool_reports_upstream_path() {
|
||||||
|
let mut upstream_resp = DnsPacket::new();
|
||||||
|
upstream_resp.header.response = true;
|
||||||
|
upstream_resp.header.rescode = ResultCode::NOERROR;
|
||||||
|
upstream_resp.answers.push(DnsRecord::A {
|
||||||
|
domain: "example.com".to_string(),
|
||||||
|
addr: Ipv4Addr::new(93, 184, 216, 34),
|
||||||
|
ttl: 300,
|
||||||
|
});
|
||||||
|
let upstream_addr = crate::testutil::mock_upstream(upstream_resp).await;
|
||||||
|
|
||||||
|
let ctx = crate::testutil::test_ctx().await;
|
||||||
|
ctx.upstream_pool
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_primary(vec![Upstream::Udp(upstream_addr)]);
|
||||||
|
let ctx = Arc::new(ctx);
|
||||||
|
|
||||||
|
let (resp, path) = resolve_in_test(&ctx, "example.com", QueryType::A).await;
|
||||||
|
assert_eq!(path, QueryPath::Upstream);
|
||||||
|
assert_eq!(resp.header.rescode, ResultCode::NOERROR);
|
||||||
|
assert_eq!(resp.answers.len(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/stats.rs
14
src/stats.rs
@@ -90,6 +90,7 @@ fn linux_rss() -> usize {
|
|||||||
pub struct ServerStats {
|
pub struct ServerStats {
|
||||||
queries_total: u64,
|
queries_total: u64,
|
||||||
queries_forwarded: u64,
|
queries_forwarded: u64,
|
||||||
|
queries_upstream: u64,
|
||||||
queries_recursive: u64,
|
queries_recursive: u64,
|
||||||
queries_coalesced: u64,
|
queries_coalesced: u64,
|
||||||
queries_cached: u64,
|
queries_cached: u64,
|
||||||
@@ -127,7 +128,10 @@ impl Transport {
|
|||||||
pub enum QueryPath {
|
pub enum QueryPath {
|
||||||
Local,
|
Local,
|
||||||
Cached,
|
Cached,
|
||||||
|
/// Matched a `[[forwarding]]` suffix rule.
|
||||||
Forwarded,
|
Forwarded,
|
||||||
|
/// Resolved via the default `[upstream]` pool (no suffix match).
|
||||||
|
Upstream,
|
||||||
Recursive,
|
Recursive,
|
||||||
Coalesced,
|
Coalesced,
|
||||||
Blocked,
|
Blocked,
|
||||||
@@ -141,6 +145,7 @@ impl QueryPath {
|
|||||||
QueryPath::Local => "LOCAL",
|
QueryPath::Local => "LOCAL",
|
||||||
QueryPath::Cached => "CACHED",
|
QueryPath::Cached => "CACHED",
|
||||||
QueryPath::Forwarded => "FORWARD",
|
QueryPath::Forwarded => "FORWARD",
|
||||||
|
QueryPath::Upstream => "UPSTREAM",
|
||||||
QueryPath::Recursive => "RECURSIVE",
|
QueryPath::Recursive => "RECURSIVE",
|
||||||
QueryPath::Coalesced => "COALESCED",
|
QueryPath::Coalesced => "COALESCED",
|
||||||
QueryPath::Blocked => "BLOCKED",
|
QueryPath::Blocked => "BLOCKED",
|
||||||
@@ -156,6 +161,8 @@ impl QueryPath {
|
|||||||
Some(QueryPath::Cached)
|
Some(QueryPath::Cached)
|
||||||
} else if s.eq_ignore_ascii_case("FORWARD") {
|
} else if s.eq_ignore_ascii_case("FORWARD") {
|
||||||
Some(QueryPath::Forwarded)
|
Some(QueryPath::Forwarded)
|
||||||
|
} else if s.eq_ignore_ascii_case("UPSTREAM") {
|
||||||
|
Some(QueryPath::Upstream)
|
||||||
} else if s.eq_ignore_ascii_case("RECURSIVE") {
|
} else if s.eq_ignore_ascii_case("RECURSIVE") {
|
||||||
Some(QueryPath::Recursive)
|
Some(QueryPath::Recursive)
|
||||||
} else if s.eq_ignore_ascii_case("COALESCED") {
|
} else if s.eq_ignore_ascii_case("COALESCED") {
|
||||||
@@ -183,6 +190,7 @@ impl ServerStats {
|
|||||||
ServerStats {
|
ServerStats {
|
||||||
queries_total: 0,
|
queries_total: 0,
|
||||||
queries_forwarded: 0,
|
queries_forwarded: 0,
|
||||||
|
queries_upstream: 0,
|
||||||
queries_recursive: 0,
|
queries_recursive: 0,
|
||||||
queries_coalesced: 0,
|
queries_coalesced: 0,
|
||||||
queries_cached: 0,
|
queries_cached: 0,
|
||||||
@@ -204,6 +212,7 @@ impl ServerStats {
|
|||||||
QueryPath::Local => self.queries_local += 1,
|
QueryPath::Local => self.queries_local += 1,
|
||||||
QueryPath::Cached => self.queries_cached += 1,
|
QueryPath::Cached => self.queries_cached += 1,
|
||||||
QueryPath::Forwarded => self.queries_forwarded += 1,
|
QueryPath::Forwarded => self.queries_forwarded += 1,
|
||||||
|
QueryPath::Upstream => self.queries_upstream += 1,
|
||||||
QueryPath::Recursive => self.queries_recursive += 1,
|
QueryPath::Recursive => self.queries_recursive += 1,
|
||||||
QueryPath::Coalesced => self.queries_coalesced += 1,
|
QueryPath::Coalesced => self.queries_coalesced += 1,
|
||||||
QueryPath::Blocked => self.queries_blocked += 1,
|
QueryPath::Blocked => self.queries_blocked += 1,
|
||||||
@@ -232,6 +241,7 @@ impl ServerStats {
|
|||||||
uptime_secs: self.uptime_secs(),
|
uptime_secs: self.uptime_secs(),
|
||||||
total: self.queries_total,
|
total: self.queries_total,
|
||||||
forwarded: self.queries_forwarded,
|
forwarded: self.queries_forwarded,
|
||||||
|
upstream: self.queries_upstream,
|
||||||
recursive: self.queries_recursive,
|
recursive: self.queries_recursive,
|
||||||
coalesced: self.queries_coalesced,
|
coalesced: self.queries_coalesced,
|
||||||
cached: self.queries_cached,
|
cached: self.queries_cached,
|
||||||
@@ -253,10 +263,11 @@ impl ServerStats {
|
|||||||
let secs = uptime.as_secs() % 60;
|
let secs = uptime.as_secs() % 60;
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"STATS | uptime {}h{}m{}s | total {} | fwd {} | recursive {} | coalesced {} | cached {} | local {} | override {} | blocked {} | errors {}",
|
"STATS | uptime {}h{}m{}s | total {} | fwd {} | upstream {} | recursive {} | coalesced {} | cached {} | local {} | override {} | blocked {} | errors {}",
|
||||||
hours, mins, secs,
|
hours, mins, secs,
|
||||||
self.queries_total,
|
self.queries_total,
|
||||||
self.queries_forwarded,
|
self.queries_forwarded,
|
||||||
|
self.queries_upstream,
|
||||||
self.queries_recursive,
|
self.queries_recursive,
|
||||||
self.queries_coalesced,
|
self.queries_coalesced,
|
||||||
self.queries_cached,
|
self.queries_cached,
|
||||||
@@ -272,6 +283,7 @@ pub struct StatsSnapshot {
|
|||||||
pub uptime_secs: u64,
|
pub uptime_secs: u64,
|
||||||
pub total: u64,
|
pub total: u64,
|
||||||
pub forwarded: u64,
|
pub forwarded: u64,
|
||||||
|
pub upstream: u64,
|
||||||
pub recursive: u64,
|
pub recursive: u64,
|
||||||
pub coalesced: u64,
|
pub coalesced: u64,
|
||||||
pub cached: u64,
|
pub cached: u64,
|
||||||
|
|||||||
Reference in New Issue
Block a user