diff --git a/Cargo.lock b/Cargo.lock index 1b0cf57b4..c089b328e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1200,6 +1200,8 @@ dependencies = [ "libipld 0.16.0", "libp2p", "multiaddr", + "multibase 0.9.1", + "multihash 0.18.1", "names", "prometheus-client", "recon", diff --git a/Cargo.toml b/Cargo.toml index 375e671d5..63f4bd682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ minicbor = { version = "0.19.1", features = ["alloc", "std"] } mockall = "0.11.4" multiaddr = "0.18" multibase = "0.9" -multihash = "0.18" +multihash = { version = "0.18", features = ["identity"] } names = { version = "0.14.0", default-features = false } nix = "0.26" num_enum = "0.5.7" @@ -199,6 +199,3 @@ authors = [ ] license = "Apache-2.0/MIT" repository = "https://github.com/3box/rust-ceramic" - -#[patch.crates-io] -#libp2p-metrics = { path = "../rust-libp2p/misc/metrics" } diff --git a/one/Cargo.toml b/one/Cargo.toml index 84f856fb7..cd224ed19 100644 --- a/one/Cargo.toml +++ b/one/Cargo.toml @@ -28,6 +28,8 @@ iroh-rpc-types.workspace = true libipld.workspace = true libp2p.workspace = true multiaddr.workspace = true +multibase.workspace = true +multihash.workspace = true names.workspace = true prometheus-client.workspace = true recon.workspace = true diff --git a/one/src/lib.rs b/one/src/lib.rs index 34a264ab4..9b2d5a69c 100644 --- a/one/src/lib.rs +++ b/one/src/lib.rs @@ -7,7 +7,7 @@ mod network; mod pubsub; mod sql; -use std::{path::PathBuf, str::FromStr, sync::Arc, time::Duration}; +use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; use anyhow::{anyhow, Result}; use ceramic_core::{EventId, Interest, PeerId}; @@ -20,11 +20,13 @@ use futures_util::future; use iroh_metrics::{config::Config as MetricsConfig, MetricsHandle}; use libipld::json::DagJsonCodec; use libp2p::metrics::Recorder; +use multibase::Base; +use multihash::{Code, Hasher, Multihash, MultihashDigest}; use recon::{FullInterests, Recon, ReconInterestProvider, SQLiteStore, Server, Sha256a}; use signal_hook::consts::signal::*; use signal_hook_tokio::Signals; use swagger::{auth::MakeAllowAllAuthenticator, EmptyContext}; -use tokio::{sync::oneshot, task, time::timeout}; +use tokio::{io::AsyncReadExt, sync::oneshot, task, time::timeout}; use tracing::{debug, info, warn}; use crate::{ @@ -196,7 +198,7 @@ impl Daemon { async fn build(opts: DaemonOpts) -> Result { let network = opts.network.to_network(&opts.local_network_id)?; - let info = Info::default(); + let info = Info::new().await?; let mut metrics_config = MetricsConfig { collect: opts.metrics, @@ -227,6 +229,7 @@ impl Daemon { version = info.version, build = info.build, instance_id = info.instance_id, + exe_hash = info.exe_hash, ); debug!(?opts, "using daemon options"); @@ -525,11 +528,14 @@ pub struct Info { pub build: String, /// Unique name generated for this invocation of the process. pub instance_id: String, + /// Multibase encoded multihash of the current running executable. + pub exe_hash: String, } -impl Default for Info { - fn default() -> Self { - Self { +impl Info { + async fn new() -> Result { + let exe_hash = multibase::encode(Base::Base64Url, current_exe_hash().await?.to_bytes()); + Ok(Self { service_name: env!("CARGO_PKG_NAME").to_string(), build: git_version::git_version!( prefix = "git:", @@ -539,10 +545,9 @@ impl Default for Info { .to_string(), version: env!("CARGO_PKG_VERSION").to_string(), instance_id: names::Generator::default().next().unwrap(), - } + exe_hash, + }) } -} -impl Info { fn apply_to_metrics_config(&self, cfg: &mut MetricsConfig) { cfg.service_name = self.service_name.clone(); cfg.version = self.version.clone(); @@ -550,3 +555,29 @@ impl Info { cfg.instance_id = self.instance_id.clone(); } } + +async fn current_exe_hash() -> Result { + if cfg!(debug_assertions) { + // Debug builds can be 1GB+, so do we not want to spend the time to hash them. + // Return a fake hash. + let mut hash = multihash::Identity256::default(); + // Spells debugg when base64 url encoded with some leading padding. + hash.update(&[00, 117, 230, 238, 130]); + Ok(Code::Identity.wrap(hash.finalize())?) + } else { + let exe_path = env::current_exe()?; + let mut hasher = multihash::Sha2_256::default(); + let mut f = tokio::fs::File::open(exe_path).await?; + let mut buffer = vec![0; 4096]; + + loop { + let bytes_read = f.read(&mut buffer[..]).await?; + if bytes_read == 0 { + break; + } + hasher.update(&buffer[..bytes_read]); + } + let hash = hasher.finalize(); + Ok(Code::Sha2_256.wrap(hash)?) + } +} diff --git a/one/src/metrics.rs b/one/src/metrics.rs index 32e4a5e7b..6144874b1 100644 --- a/one/src/metrics.rs +++ b/one/src/metrics.rs @@ -51,6 +51,7 @@ struct InfoLabels { version: String, build: String, instance_id: String, + exe_hash: String, } impl From for InfoLabels { @@ -60,6 +61,7 @@ impl From for InfoLabels { version: info.version, build: info.build, instance_id: info.instance_id, + exe_hash: info.exe_hash, } } } @@ -163,8 +165,11 @@ impl Recorder for Metrics { async fn handle(_req: Request) -> Result, Infallible> { let data = iroh_metrics::MetricsHandle::encode(); let mut resp = Response::new(Body::from(data)); - resp.headers_mut() - .insert("Content-Type", HeaderValue::from_static("text/plain")); + resp.headers_mut().insert( + "Content-Type", + // Use OpenMetrics content type so prometheus knows to parse it accordingly + HeaderValue::from_static("application/openmetrics-text"), + ); Ok(resp) }