From e48c8d3d81121cb2f8e04654a6cf2497d8820fee Mon Sep 17 00:00:00 2001 From: Kevin Leimkuhler Date: Fri, 24 Jul 2020 09:19:40 -0700 Subject: [PATCH] Add proxy_build_info metric (#600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Closes linkerd/linkerd2#3424 ## What A new `proxy_build_info` metric is introduced that contains the following fields: - `git_branch`: The branch that the proxy was built on - `git_version`: The version of the proxy. This is the output of `git describe` and finds the most recent tag that is reachable from the commit. - `rust_version`: The rust version used to build the proxy This metric is a gauge that always has a value of `1`. The reasoning is described [here](https://www.robustperception.io/exposing-the-software-version-to-prometheus). The fields reflect those of the `prometheus_build_info` discussed in the link above. ```bash ❯ linkerd metrics -n linkerd pods/linkerd-tap-5f6565cfc5-56q9r .. # HELP proxy_build_info Proxy build info # TYPE proxy_build_info gauge proxy_build_info{git_branch="kleimkuhler/build-info",git_version="release/v2.104.1-11-gea34a589",rust_version="rustc 1.44.1 (c7087fe00 2020-06-17)",} 1 ``` ## How The `.git/` directory is now copied into the docker container when building so that the `build.rs` file being introduced can reference the git history to find the most recent tag and populate the `GIT_BRANCH` and `GIT_VERSION` environment variables at compile time. ### Labels I considered a `git_release` and `git_revision` tag instead of the single `git_version`. `git_release` would be the most recent tag reachable from the commit, and `git_revision` would be the full commit SHA. I chose the output of `git describe` instead because it includes both the tag and short SHA as well as how many additional commits have occurred on top of the tag. I think this makes it a little easier as a user to see that the proxy build version is the `kleimkuhler/build-info` branch, which is `1` commit from `release/v2.104.1` with the SHA starting with `1ee7452f`: ```bash ❯ git describe release/v2.104.1-1-g1ee7452f ``` Signed-off-by: Kevin Leimkuhler --- .dockerignore | 1 - linkerd/app/core/build.rs | 30 +++++++++ linkerd/app/core/src/telemetry/build_info.rs | 68 ++++++++++++++++++++ linkerd/app/core/src/telemetry/mod.rs | 1 + linkerd/app/src/metrics.rs | 5 +- 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 linkerd/app/core/build.rs create mode 100644 linkerd/app/core/src/telemetry/build_info.rs diff --git a/.dockerignore b/.dockerignore index 27febbf365..1376a5c411 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,4 @@ .travis.yml -.git **/.idea **/cmake-* **/CMakeLists.txt diff --git a/linkerd/app/core/build.rs b/linkerd/app/core/build.rs new file mode 100644 index 0000000000..c26885c31e --- /dev/null +++ b/linkerd/app/core/build.rs @@ -0,0 +1,30 @@ +use std::process::Command; +use std::string::String; + +const UNAVAILABLE: &str = "unavailable"; + +fn set_env(name: &str, cmd: &mut Command) { + let value = match cmd.output() { + Ok(output) => String::from_utf8(output.stdout).unwrap(), + Err(err) => { + println!("cargo:warning={}", err); + UNAVAILABLE.to_string() + } + }; + println!("cargo:rustc-env={}={}", name, value); +} + +fn main() { + set_env( + "GIT_BRANCH", + Command::new("git").args(&["rev-parse", "--abbrev-ref", "HEAD"]), + ); + set_env( + "GIT_VERSION", + Command::new("git").args(&["describe", "--always", "HEAD"]), + ); + set_env("RUST_VERSION", Command::new("rustc").arg("--version")); + + let profile = std::env::var("PROFILE").unwrap(); + println!("cargo:rustc-env=PROFILE={}", profile); +} diff --git a/linkerd/app/core/src/telemetry/build_info.rs b/linkerd/app/core/src/telemetry/build_info.rs new file mode 100644 index 0000000000..d731c4c659 --- /dev/null +++ b/linkerd/app/core/src/telemetry/build_info.rs @@ -0,0 +1,68 @@ +use linkerd2_metrics::{metrics, FmtLabels, FmtMetric, FmtMetrics, Gauge}; +use std::env; +use std::fmt; +use std::string::String; +use std::sync::Arc; + +const GIT_BRANCH: &'static str = env!("GIT_BRANCH"); +const GIT_VERSION: &'static str = env!("GIT_VERSION"); +const PROFILE: &'static str = env!("PROFILE"); +const RUST_VERSION: &'static str = env!("RUST_VERSION"); + +metrics! { + proxy_build_info: Gauge { + "Proxy build info" + } +} + +#[derive(Clone, Debug, Default)] +pub struct Report { + name: String, + // `value` remains constant over the lifetime of the proxy so that + // build information in `labels` remains accurate + value: Arc, + labels: Arc, +} + +#[derive(Clone, Debug, Default)] +struct BuildInfoLabels { + git_branch: String, + git_version: String, + profile: String, + rust_version: String, +} + +impl Report { + pub fn new() -> Self { + let labels = Arc::new(BuildInfoLabels { + git_branch: GIT_BRANCH.to_string(), + git_version: GIT_VERSION.to_string(), + profile: PROFILE.to_string(), + rust_version: RUST_VERSION.to_string(), + }); + Self { + name: "proxy_build_info".to_string(), + value: Arc::new(1.into()), + labels, + } + } +} + +impl FmtMetrics for Report { + fn fmt_metrics(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + proxy_build_info.fmt_help(f)?; + self.value + .fmt_metric_labeled(f, self.name.as_str(), self.labels.as_ref())?; + Ok(()) + } +} + +impl FmtLabels for BuildInfoLabels { + fn fmt_labels(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "git_branch=\"{}\",", self.git_branch)?; + write!(f, "git_version=\"{}\",", self.git_version)?; + write!(f, "profile=\"{}\",", self.profile)?; + write!(f, "rust_version=\"{}\",", self.rust_version)?; + Ok(()) + } +} diff --git a/linkerd/app/core/src/telemetry/mod.rs b/linkerd/app/core/src/telemetry/mod.rs index 80fe812c8b..ebb00845af 100644 --- a/linkerd/app/core/src/telemetry/mod.rs +++ b/linkerd/app/core/src/telemetry/mod.rs @@ -1 +1,2 @@ +pub mod build_info; pub mod process; diff --git a/linkerd/app/src/metrics.rs b/linkerd/app/src/metrics.rs index 0b7caa95e0..bcf9c6909c 100644 --- a/linkerd/app/src/metrics.rs +++ b/linkerd/app/src/metrics.rs @@ -18,6 +18,8 @@ impl Metrics { pub fn new(retain_idle: Duration) -> (Self, impl FmtMetrics + Clone + Send + 'static) { let process = telemetry::process::Report::new(SystemTime::now()); + let build_info = telemetry::build_info::Report::new(); + let (control, control_report) = { let m = metrics::Requests::::default(); let r = m.clone().into_report(retain_idle).with_prefix("control"); @@ -98,7 +100,8 @@ impl Metrics { .and_then(transport_report) .and_then(opencensus_report) .and_then(stack) - .and_then(process); + .and_then(process) + .and_then(build_info); (metrics, report) }