Skip to content

Commit a8a25ce

Browse files
committed
track ddm state durations
1 parent de065a8 commit a8a25ce

File tree

9 files changed

+300
-64
lines changed

9 files changed

+300
-64
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ddm-admin-client/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ progenitor::generate_api!(
1414
}),
1515
post_hook = (|log: &slog::Logger, result: &Result<_, _>| {
1616
slog::trace!(log, "client response"; "result" => ?result);
17-
})
17+
}),
18+
replace = { Duration = std::time::Duration }
1819
);
1920

2021
impl Copy for types::Ipv4Prefix {}

ddm/src/admin.rs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use crate::db::{Db, PeerInfo, TunnelRoute};
5+
use crate::db::{Db, PeerInfo, PeerStatus, RouterKind, TunnelRoute};
66
use crate::exchange::PathVector;
77
use crate::sm::{AdminEvent, Event, PrefixSet, SmContext};
88
use dropshot::endpoint;
@@ -27,6 +27,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
2727
use std::sync::mpsc::Sender;
2828
use std::sync::Arc;
2929
use std::sync::Mutex;
30+
use std::time::{Duration, Instant};
3031
use tokio::spawn;
3132
use tokio::task::JoinHandle;
3233
use uuid::Uuid;
@@ -103,12 +104,71 @@ pub fn handler(
103104
Ok(())
104105
}
105106

107+
/// Status of a DDM peer with state expressed as durations.
108+
#[derive(
109+
Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema,
110+
)]
111+
#[serde(tag = "type", content = "value")]
112+
pub enum ApiPeerStatus {
113+
NoContact,
114+
Init(Duration),
115+
Solicit(Duration),
116+
Exchange(Duration),
117+
Expired(Duration),
118+
}
119+
120+
// Translate internal peer status which is based on instants, to API
121+
// representation which is based on durations.
122+
impl From<PeerStatus> for ApiPeerStatus {
123+
fn from(value: PeerStatus) -> Self {
124+
match value {
125+
PeerStatus::NoContact => Self::NoContact,
126+
PeerStatus::Init(t) => Self::Init(Instant::now().duration_since(t)),
127+
PeerStatus::Solicit(t) => {
128+
Self::Solicit(Instant::now().duration_since(t))
129+
}
130+
PeerStatus::Exchange(t) => {
131+
Self::Exchange(Instant::now().duration_since(t))
132+
}
133+
PeerStatus::Expired(t) => {
134+
Self::Expired(Instant::now().duration_since(t))
135+
}
136+
}
137+
}
138+
}
139+
140+
/// Information about a DDM peer.
141+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
142+
pub struct ApiPeerInfo {
143+
pub status: ApiPeerStatus,
144+
pub addr: Ipv6Addr,
145+
pub host: String,
146+
pub kind: RouterKind,
147+
}
148+
149+
impl From<PeerInfo> for ApiPeerInfo {
150+
fn from(value: PeerInfo) -> Self {
151+
Self {
152+
status: value.status.into(),
153+
addr: value.addr,
154+
host: value.host,
155+
kind: value.kind,
156+
}
157+
}
158+
}
159+
106160
#[endpoint { method = GET, path = "/peers" }]
107161
async fn get_peers(
108162
ctx: RequestContext<Arc<Mutex<HandlerContext>>>,
109-
) -> Result<HttpResponseOk<HashMap<u32, PeerInfo>>, HttpError> {
163+
) -> Result<HttpResponseOk<HashMap<u32, ApiPeerInfo>>, HttpError> {
110164
let ctx = ctx.context().lock().unwrap();
111-
Ok(HttpResponseOk(ctx.db.peers()))
165+
let peers = ctx
166+
.db
167+
.peers()
168+
.into_iter()
169+
.map(|(k, v)| (k, v.into()))
170+
.collect();
171+
Ok(HttpResponseOk(peers))
112172
}
113173

114174
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]

ddm/src/db.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use mg_common::net::{IpPrefix, Ipv6Prefix, TunnelOrigin};
66
use schemars::{JsonSchema, JsonSchema_repr};
77
use serde::{Deserialize, Serialize};
88
use serde_repr::{Deserialize_repr, Serialize_repr};
9-
use slog::{error, Logger};
9+
use slog::{debug, error, Logger};
1010
use std::collections::{HashMap, HashSet};
1111
use std::net::Ipv6Addr;
1212
use std::sync::{Arc, Mutex};
13+
use std::time::Instant;
1314

1415
/// The handle used to open a persistent key-value tree for originated
1516
/// prefixes.
@@ -227,10 +228,34 @@ impl Db {
227228

228229
/// Set peer info at the given index. Returns true if peer information was
229230
/// changed.
230-
pub fn set_peer(&self, index: u32, info: PeerInfo) -> bool {
231-
match self.data.lock().unwrap().peers.insert(index, info.clone()) {
232-
Some(previous) => previous == info,
233-
None => true,
231+
pub fn set_peer_info(
232+
&self,
233+
index: u32,
234+
addr: Ipv6Addr,
235+
host: String,
236+
kind: RouterKind,
237+
) -> bool {
238+
let mut data = self.data.lock().unwrap();
239+
if let Some(peer) = data.peers.get_mut(&index) {
240+
if peer.addr == addr && peer.host == host && peer.kind == kind {
241+
false
242+
} else {
243+
peer.addr = addr;
244+
peer.host = host;
245+
peer.kind = kind;
246+
true
247+
}
248+
} else {
249+
data.peers.insert(
250+
index,
251+
PeerInfo {
252+
addr,
253+
host,
254+
kind,
255+
status: PeerStatus::Init(Instant::now()),
256+
},
257+
);
258+
true
234259
}
235260
}
236261

@@ -267,6 +292,19 @@ impl Db {
267292
self.data.lock().unwrap().peers.remove(&index);
268293
}
269294

295+
pub fn peer_status_transition(&self, index: u32, status: PeerStatus) {
296+
if let Some(info) = self.data.lock().unwrap().peers.get_mut(&index) {
297+
info.status = status;
298+
} else {
299+
// This is expected to happen during initialization as we don't
300+
// add a peer to the db until an advertisement is received.
301+
debug!(
302+
self.log,
303+
"status update: peer with index {} does not exist", index
304+
);
305+
}
306+
}
307+
270308
pub fn routes_by_vector(
271309
&self,
272310
dst: Ipv6Prefix,
@@ -283,16 +321,16 @@ impl Db {
283321
}
284322
}
285323

286-
#[derive(
287-
Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema,
288-
)]
324+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
289325
pub enum PeerStatus {
290326
NoContact,
291-
Active,
292-
Expired,
327+
Init(Instant),
328+
Solicit(Instant),
329+
Exchange(Instant),
330+
Expired(Instant),
293331
}
294332

295-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
333+
#[derive(Clone, Debug, PartialEq)]
296334
pub struct PeerInfo {
297335
pub status: PeerStatus,
298336
pub addr: Ipv6Addr,

ddm/src/discovery.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
//! and 1 for a transit routers. The fourth byte is a hostname length followed
8686
//! directly by a hostname of up to 255 bytes in length.
8787
88-
use crate::db::{Db, PeerInfo, PeerStatus, RouterKind};
88+
use crate::db::{Db, RouterKind};
8989
use crate::sm::{Config, Event, NeighborEvent, SessionStats};
9090
use crate::util::u8_slice_assume_init_ref;
9191
use crate::{dbg, err, inf, trc, wrn};
@@ -504,15 +504,9 @@ fn handle_advertisement(
504504
}
505505
};
506506
drop(guard);
507-
let updated = ctx.db.set_peer(
508-
ctx.config.if_index,
509-
PeerInfo {
510-
status: PeerStatus::Active,
511-
addr: *sender,
512-
host: hostname,
513-
kind,
514-
},
515-
);
507+
let updated =
508+
ctx.db
509+
.set_peer_info(ctx.config.if_index, *sender, hostname, kind);
516510
if updated {
517511
stats.peer_address.lock().unwrap().replace(*sender);
518512
emit_nbr_update(ctx, sender, version);

ddm/src/sm.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use crate::db::{Db, RouterKind};
5+
use crate::db::{Db, PeerStatus, RouterKind};
66
use crate::discovery::Version;
77
use crate::exchange::{PathVector, TunnelUpdate, UnderlayUpdate, Update};
88
use crate::{dbg, discovery, err, exchange, inf, wrn};
@@ -16,7 +16,7 @@ use std::sync::mpsc::{Receiver, Sender};
1616
use std::sync::{Arc, Mutex};
1717
use std::thread::sleep;
1818
use std::thread::spawn;
19-
use std::time::Duration;
19+
use std::time::{Duration, Instant};
2020
use thiserror::Error;
2121

2222
#[derive(Debug)]
@@ -228,6 +228,10 @@ impl State for Init {
228228
&mut self,
229229
event: Receiver<Event>,
230230
) -> (Box<dyn State>, Receiver<Event>) {
231+
self.ctx.db.peer_status_transition(
232+
self.ctx.config.if_index,
233+
PeerStatus::Init(Instant::now()),
234+
);
231235
loop {
232236
let info = match get_ipaddr_info(&self.ctx.config.aobj_name) {
233237
Ok(info) => info,
@@ -303,6 +307,10 @@ impl State for Solicit {
303307
&mut self,
304308
event: Receiver<Event>,
305309
) -> (Box<dyn State>, Receiver<Event>) {
310+
self.ctx.db.peer_status_transition(
311+
self.ctx.config.if_index,
312+
PeerStatus::Solicit(Instant::now()),
313+
);
306314
loop {
307315
let e = match event.recv() {
308316
Ok(e) => e,
@@ -529,6 +537,10 @@ impl State for Exchange {
529537
&mut self,
530538
event: Receiver<Event>,
531539
) -> (Box<dyn State>, Receiver<Event>) {
540+
self.ctx.db.peer_status_transition(
541+
self.ctx.config.if_index,
542+
PeerStatus::Exchange(Instant::now()),
543+
);
532544
let exchange_thread = loop {
533545
match exchange::handler(
534546
self.ctx.clone(),
@@ -759,7 +771,6 @@ impl State for Exchange {
759771
);
760772
}
761773
}
762-
// TODO tunnel
763774
Event::Peer(PeerEvent::Push(update)) => {
764775
inf!(
765776
self.log,

ddmadm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ tabwriter.workspace = true
1717
colored.workspace = true
1818
anyhow.workspace = true
1919
anstyle.workspace = true
20+
humantime.workspace = true

ddmadm/src/main.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use anyhow::Result;
66
use clap::Parser;
77
use colored::*;
8+
use ddm_admin_client::types::ApiPeerStatus;
89
use ddm_admin_client::{types, Client};
10+
use humantime::Duration;
911
use mg_common::cli::oxide_cli_style;
1012
use mg_common::net::{IpPrefix, Ipv4Prefix, Ipv6Prefix};
1113
use slog::{Drain, Logger};
@@ -120,7 +122,7 @@ async fn run() -> Result<()> {
120122
for (index, info) in &msg.into_inner() {
121123
writeln!(
122124
&mut tw,
123-
"{}\t{}\t{}\t{}\t{:?}",
125+
"{}\t{}\t{}\t{}\t{}",
124126
index,
125127
info.host,
126128
info.addr,
@@ -129,7 +131,34 @@ async fn run() -> Result<()> {
129131
1 => "Transit",
130132
_ => "?",
131133
},
132-
info.status,
134+
match info.status {
135+
ApiPeerStatus::NoContact => "no contact".into(),
136+
ApiPeerStatus::Init(t) => format!(
137+
"Init {}",
138+
// Don't care about precision beyond milliseconds
139+
Duration::from(std::time::Duration::from_millis(
140+
t.as_millis() as u64
141+
))
142+
),
143+
ApiPeerStatus::Solicit(t) => format!(
144+
"Solicit {}",
145+
Duration::from(std::time::Duration::from_millis(
146+
t.as_millis() as u64
147+
))
148+
),
149+
ApiPeerStatus::Exchange(t) => format!(
150+
"Exchange {}",
151+
Duration::from(std::time::Duration::from_millis(
152+
t.as_millis() as u64
153+
))
154+
),
155+
ApiPeerStatus::Expired(t) => format!(
156+
"Expired {}",
157+
Duration::from(std::time::Duration::from_millis(
158+
t.as_millis() as u64
159+
))
160+
),
161+
}
133162
)?;
134163
}
135164
tw.flush()?;

0 commit comments

Comments
 (0)