diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 0cbcece..1ab0a0d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -49,7 +49,9 @@ jobs: run: cargo clippy --all-targets -- -D warnings - name: finally, get picky about formatting - run: cargo fmt --check + run: | + cargo fmt --check || \ + (echo "Fix formatting errors by running \`just lint\` locally."; exit 1) cargo-deny: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index f65cddb..f8e37cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1446,6 +1446,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199523ba70af2b447640715e8c4bd2b5360313a71d2d69361ae4dd1dc31487dd" +dependencies = [ + "libc", + "windows", +] + [[package]] name = "getrandom" version = "0.2.9" @@ -2839,6 +2849,7 @@ dependencies = [ "engine", "env_logger", "futures", + "gethostname", "http", "hyper", "log", @@ -2858,6 +2869,7 @@ dependencies = [ "urlencoding", "utils", "uuid", + "vergen", ] [[package]] @@ -3148,6 +3160,7 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ + "itoa", "serde", "time-core", "time-macros", @@ -3496,6 +3509,18 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vergen" +version = "8.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e03272e388fb78fc79481a493424f78d77be1d55f21bcd314b5a6716e195afe" +dependencies = [ + "anyhow", + "rustc_version", + "rustversion", + "time", +] + [[package]] name = "version_check" version = "0.9.4" @@ -4097,6 +4122,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 2aab786..b39c7b6 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -3,6 +3,7 @@ name = "serval-agent" version = "0.1.0" edition = "2021" license = "BSD-2-Clause-Patent" +build = "build.rs" [dependencies] anyhow = { workspace = true } @@ -16,6 +17,7 @@ dotenvy = "0.15.6" engine = { path = "../engine" } env_logger = { workspace = true } futures = "0.3.28" +gethostname = "0.4.2" http = "0.2.8" hyper = "0.14.23" log = "0.4.17" @@ -35,3 +37,6 @@ toml = { workspace = true } urlencoding = "2.1.2" utils = { path = "../utils" } uuid = { workspace = true } + +[build-dependencies] +vergen = { version = "8.1.3", features = ["build", "cargo", "git", "gitcl", "rustc"] } diff --git a/agent/build.rs b/agent/build.rs new file mode 100644 index 0000000..98f08d3 --- /dev/null +++ b/agent/build.rs @@ -0,0 +1,14 @@ +use std::error::Error; + +use vergen::EmitBuilder; + +fn main() -> Result<(), Box> { + // Emit the instructions + EmitBuilder::builder() + .all_build() + .all_cargo() + .all_git() + .all_rustc() + .emit()?; + Ok(()) +} diff --git a/agent/src/api/mod.rs b/agent/src/api/mod.rs index 4ef5ef7..0f4b43a 100644 --- a/agent/src/api/mod.rs +++ b/agent/src/api/mod.rs @@ -1,11 +1,16 @@ +use std::collections::HashMap; +use std::time::Duration; + use anyhow::Result; use axum::extract::State; use axum::http::{Request, StatusCode}; use axum::middleware::Next; use axum::response::{IntoResponse, Response}; +use axum::Json; use http::header::HeaderValue; +use utils::mesh::PeerMetadata; -use crate::structures::AppState; +use crate::structures::{AgentInfo, AppState, MESH}; pub mod v1; // Follow this pattern for additional major versions. E.g., @@ -52,3 +57,15 @@ pub async fn monitor_status(_state: State) -> impl IntoResponse { metrics::increment_counter!("monitor:status"); StatusCode::NOT_IMPLEMENTED } + +/// Provide agent status information. +pub async fn agent_info(state: State) -> Json { + metrics::increment_counter!("agent:info"); + Json(AgentInfo::new(&state)) +} + +/// Provide agent peer information. +pub async fn agent_peers(_state: State) -> Json> { + metrics::increment_counter!("agent:peers"); + Json(MESH.get().unwrap().peer_latencies().await) +} diff --git a/agent/src/main.rs b/agent/src/main.rs index cf213e2..688280d 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -236,7 +236,9 @@ fn init_router(state: &Arc) -> Router { let mut router: Router, Body> = Router::new() .route("/monitor/ping", get(ping)) - .route("/monitor/status", get(monitor_status)); + .route("/monitor/status", get(monitor_status)) + .route("/agent/info", get(agent_info)) + .route("/agent/peers", get(agent_peers)); router = v1::mesh::mount(router); // NOTE: We have two of these now. If we develop a third, generalize this pattern. diff --git a/agent/src/structures/mod.rs b/agent/src/structures/mod.rs index 9e5ccb2..7667784 100644 --- a/agent/src/structures/mod.rs +++ b/agent/src/structures/mod.rs @@ -1,10 +1,13 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; +use std::time::Instant; use anyhow::Result; use engine::extensions::{load_extensions, ServalExtension}; +use gethostname::gethostname; use once_cell::sync::OnceCell; +use serde::Serialize; use utils::errors::ServalError; use utils::mesh::ServalMesh; use uuid::Uuid; @@ -21,6 +24,7 @@ pub struct RunnerState { pub should_run_jobs: bool, pub should_run_scheduler: bool, pub has_storage: bool, + pub start_timestamp: Instant, } impl RunnerState { @@ -53,8 +57,61 @@ impl RunnerState { should_run_jobs, should_run_scheduler, has_storage, + start_timestamp: Instant::now(), }) } } pub type AppState = Arc; + +/// Agent metadata. +#[derive(Serialize)] +struct BuildInfo { + build_timestamp: String, + build_date: String, + git_branch: String, + git_timestamp: String, + git_date: String, + git_hash: String, + git_describe: String, + rustc_host_triple: String, + rustc_version: String, + cargo_target_triple: String, +} + +impl BuildInfo { + fn new() -> BuildInfo { + BuildInfo { + build_timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")), + build_date: String::from(env!("VERGEN_BUILD_DATE")), + git_branch: String::from(env!("VERGEN_GIT_BRANCH")), + git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")), + git_date: String::from(env!("VERGEN_GIT_COMMIT_DATE")), + git_hash: String::from(env!("VERGEN_GIT_SHA")), + git_describe: String::from(env!("VERGEN_GIT_DESCRIBE")), + rustc_host_triple: String::from(env!("VERGEN_RUSTC_HOST_TRIPLE")), + rustc_version: String::from(env!("VERGEN_RUSTC_SEMVER")), + cargo_target_triple: String::from(env!("VERGEN_CARGO_TARGET_TRIPLE")), + } + } +} + +/// Agent metadata. +#[derive(Serialize)] +pub struct AgentInfo { + hostname: String, + instance_id: Uuid, + uptime: f64, + build_info: BuildInfo, +} + +impl AgentInfo { + pub fn new(state: &AppState) -> AgentInfo { + AgentInfo { + hostname: gethostname().into_string().expect("Failed to get hostname"), + instance_id: state.instance_id, + uptime: state.start_timestamp.elapsed().as_secs_f64(), + build_info: BuildInfo::new(), + } + } +}