Add health endpoint for agent monitoring
Added /health HTTP endpoint on port 8080 that returns:
- Agent status and version
- Uptime in seconds
- Pending metrics count
- Last error (if any)
Implementation:
- Uses lightweight tiny_http server in background thread
- Non-blocking health checks
- Returns JSON for easy integration with monitoring tools
- Ready for Kubernetes liveness/readiness probes
Example response:
{
"status": "healthy",
"version": "0.1.0",
"uptime_seconds": 3600,
"config_last_fetch": "2026-01-14T23:00:00Z",
"metrics_pending": 0,
"last_error": null
}
This commit is contained in:
parent
824f4388eb
commit
f7ac5f48e8
4 changed files with 167 additions and 0 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -85,6 +85,12 @@ version = "1.0.100"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
|
|
@ -119,6 +125,12 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chunked_transfer"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.54"
|
||||
|
|
@ -309,6 +321,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
|
|
@ -852,6 +870,18 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny_http"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"chunked_transfer",
|
||||
"httpdate",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
|
|
@ -896,6 +926,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"snmp",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"ureq",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ log = { version = "0.4", features = ["std"] }
|
|||
clap = { version = "4.0", features = ["derive", "env"] }
|
||||
prost = "0.13"
|
||||
prost-types = "0.13"
|
||||
tiny_http = "0.12"
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
|
|
|
|||
130
src/health.rs
Normal file
130
src/health.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
use crate::buffer::Storage;
|
||||
use crate::metrics::Timestamp;
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use tiny_http::{Response, Server};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealthStatus {
|
||||
pub status: String,
|
||||
pub version: String,
|
||||
pub uptime_seconds: u64,
|
||||
pub config_last_fetch: Option<String>,
|
||||
pub metrics_pending: usize,
|
||||
pub last_error: Option<String>,
|
||||
}
|
||||
|
||||
pub struct HealthServer {
|
||||
start_time: Timestamp,
|
||||
storage: Storage,
|
||||
last_error: Arc<Mutex<Option<String>>>,
|
||||
config_last_fetch: Arc<Mutex<Option<Timestamp>>>,
|
||||
}
|
||||
|
||||
impl HealthServer {
|
||||
pub fn new(storage: Storage) -> Self {
|
||||
Self {
|
||||
start_time: Timestamp::now(),
|
||||
storage,
|
||||
last_error: Arc::new(Mutex::new(None)),
|
||||
config_last_fetch: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config_fetch_time(&self) {
|
||||
if let Ok(mut last_fetch) = self.config_last_fetch.lock() {
|
||||
*last_fetch = Some(Timestamp::now());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_error(&self, error: String) {
|
||||
if let Ok(mut last_error) = self.last_error.lock() {
|
||||
*last_error = Some(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, port: u16) {
|
||||
thread::spawn(move || {
|
||||
let addr = format!("0.0.0.0:{}", port);
|
||||
info!("Starting health endpoint on {}", addr);
|
||||
|
||||
let server = match Server::http(&addr) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Failed to start health server: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for request in server.incoming_requests() {
|
||||
let path = request.url();
|
||||
|
||||
if path == "/health" {
|
||||
let status = self.get_health_status();
|
||||
let json = match serde_json::to_string(&status) {
|
||||
Ok(j) => j,
|
||||
Err(e) => {
|
||||
error!("Failed to serialize health status: {}", e);
|
||||
let _ = request.respond(
|
||||
Response::from_string("Internal error").with_status_code(500),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let response = Response::from_string(json)
|
||||
.with_header(
|
||||
tiny_http::Header::from_bytes(
|
||||
&b"Content-Type"[..],
|
||||
&b"application/json"[..],
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.with_status_code(200);
|
||||
|
||||
let _ = request.respond(response);
|
||||
} else {
|
||||
let _ =
|
||||
request.respond(Response::from_string("Not found").with_status_code(404));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_health_status(&self) -> HealthStatus {
|
||||
let uptime = self.start_time.elapsed_secs() as u64;
|
||||
|
||||
// Get pending metrics count
|
||||
let metrics_pending = self
|
||||
.storage
|
||||
.get_pending_metrics(1000)
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Get last config fetch time
|
||||
let config_last_fetch = self
|
||||
.config_last_fetch
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|guard| *guard)
|
||||
.map(|ts| ts.to_rfc3339());
|
||||
|
||||
// Get last error
|
||||
let last_error = self
|
||||
.last_error
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|guard| (*guard).clone());
|
||||
|
||||
HealthStatus {
|
||||
status: "healthy".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
uptime_seconds: uptime,
|
||||
config_last_fetch,
|
||||
metrics_pending,
|
||||
last_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
mod api_client;
|
||||
mod buffer;
|
||||
mod config;
|
||||
mod health;
|
||||
mod metrics;
|
||||
mod poller;
|
||||
mod proto;
|
||||
|
|
@ -100,6 +101,10 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Start health endpoint server
|
||||
let health_server = health::HealthServer::new(storage.clone());
|
||||
health_server.start(8080);
|
||||
|
||||
let snmp_client = snmp::SnmpClient::new();
|
||||
|
||||
// Create and run scheduler
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue