1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//! `hindsight serve` — start the web dashboard server (+ optional embedded OTLP listener)
use crate::error::Result;
/// Check whether something is already listening on `port`.
fn port_in_use(port: u16) -> bool {
use std::net::TcpStream;
use std::time::Duration;
TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_millis(200),
)
.is_ok()
}
pub fn run(port: u16, open: bool, otel_port: u16) -> Result<()> {
// If an OTLP daemon is already running on the target port, let it be.
// Both the daemon and `serve` write to the same SQLite database, so
// there is no need to kill the daemon — it will continue collecting
// OTLP data while `serve` provides the web dashboard. The `serve`
// server already has /v1/metrics and /v1/logs on its own port (the
// dashboard port), so OTLP is still reachable on both addresses.
//
// Only start the embedded OTLP listener if nothing is on the port.
let effective_otel_port = if otel_port > 0 && port_in_use(otel_port) {
eprintln!(
"OTLP daemon already running on port {otel_port} — reusing it (no restart needed)."
);
0 // skip starting the OTLP listener inside serve
} else {
otel_port
};
let addr: std::net::SocketAddr =
format!("0.0.0.0:{port}")
.parse()
.map_err(|e: std::net::AddrParseError| {
crate::error::HindsightError::Config(e.to_string())
})?;
if open {
// Best-effort browser open — ignore errors
let url = format!("http://localhost:{port}");
#[cfg(target_os = "macos")]
let _ = std::process::Command::new("open").arg(&url).spawn();
#[cfg(target_os = "linux")]
let _ = std::process::Command::new("xdg-open").arg(&url).spawn();
#[cfg(target_os = "windows")]
let _ = std::process::Command::new("cmd")
.args(["/c", "start", &url])
.spawn();
}
// Use a multi-thread tokio runtime; block_on keeps main() synchronous.
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|e| crate::error::HindsightError::Config(e.to_string()))?
.block_on(async { crate::server::serve(addr, effective_otel_port).await })
.map_err(|e| crate::error::HindsightError::Config(e.to_string()))?;
Ok(())
}