Skip to content

Commit 8400d90

Browse files
authored
Add build_info metric & use it in generated queries (#69)
* Add build_info metric & use it in generated queries * Include vergen as dev dependency to fix doctest
1 parent 49cce6b commit 8400d90

File tree

12 files changed

+137
-17
lines changed

12 files changed

+137
-17
lines changed

autometrics-macros/src/lib.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod parse;
1010
const COUNTER_NAME_PROMETHEUS: &str = "function_calls_count";
1111
const HISTOGRAM_BUCKET_NAME_PROMETHEUS: &str = "function_calls_duration_bucket";
1212
const GAUGE_NAME_PROMETHEUS: &str = "function_calls_concurrent";
13+
const ADD_BUILD_INFO_LABELS: &str = "* on (instance, job) group_left(version, commit) build_info";
1314

1415
const DEFAULT_PROMETHEUS_URL: &str = "http://localhost:9090";
1516

@@ -126,7 +127,11 @@ fn instrument_function(args: &AutometricsArgs, item: ItemFn) -> Result<TokenStre
126127

127128
#vis #sig {
128129
let __autometrics_tracker = {
129-
use autometrics::__private::{AutometricsTracker, TrackMetrics};
130+
use autometrics::__private::{AutometricsTracker, BuildInfoLabels, TrackMetrics};
131+
AutometricsTracker::set_build_info(&BuildInfoLabels::new(
132+
option_env!("AUTOMETRICS_VERSION").or(option_env!("CARGO_PKG_VERSION")).unwrap_or_default(),
133+
option_env!("AUTOMETRICS_COMMIT").or(option_env!("VERGEN_GIT_SHA")).unwrap_or_default()
134+
));
130135
AutometricsTracker::start(#gauge_labels)
131136
};
132137

@@ -260,18 +265,19 @@ fn make_prometheus_url(url: &str, query: &str, comment: &str) -> String {
260265
}
261266

262267
fn request_rate_query(counter_name: &str, label_key: &str, label_value: &str) -> String {
263-
format!("sum by (function, module) (rate({counter_name}{{{label_key}=\"{label_value}\"}}[5m]))")
268+
format!("sum by (function, module, commit, version) (rate({counter_name}{{{label_key}=\"{label_value}\"}}[5m]) {ADD_BUILD_INFO_LABELS})")
264269
}
265270

266271
fn error_ratio_query(counter_name: &str, label_key: &str, label_value: &str) -> String {
267272
let request_rate = request_rate_query(counter_name, label_key, label_value);
268-
format!("sum by (function, module) (rate({counter_name}{{{label_key}=\"{label_value}\",result=\"error\"}}[5m])) /
273+
format!("sum by (function, module, commit, version) (rate({counter_name}{{{label_key}=\"{label_value}\",result=\"error\"}}[5m]) {ADD_BUILD_INFO_LABELS})
274+
/
269275
{request_rate}", )
270276
}
271277

272278
fn latency_query(bucket_name: &str, label_key: &str, label_value: &str) -> String {
273279
let latency = format!(
274-
"sum by (le, function, module) (rate({bucket_name}{{{label_key}=\"{label_value}\"}}[5m]))"
280+
"sum by (le, function, module, commit, version) (rate({bucket_name}{{{label_key}=\"{label_value}\"}}[5m]) {ADD_BUILD_INFO_LABELS})"
275281
);
276282
format!(
277283
"histogram_quantile(0.99, {latency}) or
@@ -280,5 +286,5 @@ histogram_quantile(0.95, {latency})"
280286
}
281287

282288
fn concurrent_calls_query(gauge_name: &str, label_key: &str, label_value: &str) -> String {
283-
format!("sum by (function, module) {gauge_name}{{{label_key}=\"{label_value}\"}}")
289+
format!("sum by (function, module, commit, version) ({gauge_name}{{{label_key}=\"{label_value}\"}} {ADD_BUILD_INFO_LABELS})")
284290
}

autometrics/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const_format = { version = "0.2", features = ["rust_1_51"], optional = true }
4848
[dev-dependencies]
4949
regex = "1.7"
5050
http = "0.2"
51+
vergen = { version = "8.1", features = ["git", "gitcl"] }
5152

5253
[package.metadata.docs.rs]
5354
all-features = true

autometrics/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Here is a demo of jumping from function docs to live Prometheus charts:
2929
-[`#[autometrics]`](https://docs.rs/autometrics/latest/autometrics/attr.autometrics.html) macro instruments any function or `impl` block to track the most useful metrics
3030
- 💡 Writes Prometheus queries so you can understand the data generated without knowing PromQL
3131
- 🔗 Injects links to live Prometheus charts directly into each function's doc comments
32+
- [🔍 Identify commits](#identifying-commits-that-introduced-problems) that introduced errors or increased latency
3233
- [🚨 Define alerts](#alerts--slos) using SLO best practices directly in your source code
3334
- [📊 Grafana dashboards](#dashboards) work out of the box to visualize the performance of instrumented functions & SLOs
3435
- [⚙️ Configurable](#metrics-libraries) metric collection library (`opentelemetry`, `prometheus`, or `metrics`)
@@ -96,6 +97,33 @@ Autometrics uses existing metrics libraries (see [below](#metrics-libraries)) to
9697

9798
If you are already using one of these to collect metrics, simply configure autometrics to use the same library and the metrics it produces will be exported alongside yours. You do not need to use the Prometheus exporter functions this library provides and you do not need a separate endpoint for autometrics' metrics.
9899

100+
## Identifying commits that introduced problems
101+
102+
Autometrics makes it easy to identify if a specific version or commit introduced errors or increased latencies.
103+
104+
It uses a separate metric (`build_info`) to track the version and, optionally, git commit of your service. It then writes queries that group metrics by the `version` and `commit` labels so you can spot correlations between those and potential issues.
105+
106+
The `version` is collected from the `CARGO_PKG_VERSION` environment variable, which `cargo` sets by default. You can override this by setting the compile-time environment variable `AUTOMETRICS_VERSION`. This follows the method outlined in [Exposing the software version to Prometheus](https://www.robustperception.io/exposing-the-software-version-to-prometheus/).
107+
108+
To set the `commit`, you can either set the compile-time environment variable `AUTOMETRICS_COMMIT`, or have it set automatically using the [vergen](https://crates.io/crates/vergen) crate:
109+
110+
```toml
111+
# Cargo.toml
112+
113+
[build-dependencies]
114+
vergen = { version = "8.1", features = ["git", "gitoxide"] }
115+
```
116+
117+
```rust
118+
// build.rs
119+
fn main() {
120+
vergen::EmitBuilder::builder()
121+
.git_sha(true)
122+
.emit()
123+
.expect("Unable to generate build info");
124+
}
125+
```
126+
99127
## Dashboards
100128

101129
Autometrics provides [Grafana dashboards](https://github.com/autometrics-dev/autometrics-shared#dashboards) that will work for any project instrumented with the library.

autometrics/src/constants.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
pub const COUNTER_NAME: &str = "function.calls.count";
33
pub const HISTOGRAM_NAME: &str = "function.calls.duration";
44
pub const GAUGE_NAME: &str = "function.calls.concurrent";
5+
pub const BUILD_INFO_NAME: &str = "build_info";
56

67
// Descriptions
78
pub const COUNTER_DESCRIPTION: &str = "Autometrics counter for tracking function calls";
89
pub const HISTOGRAM_DESCRIPTION: &str = "Autometrics histogram for tracking function call duration";
910
pub const GAUGE_DESCRIPTION: &str = "Autometrics gauge for tracking concurrent function calls";
11+
pub const BUILD_INFO_DESCRIPTION: &str =
12+
"Autometrics info metric for tracking software version and build details";
1013

1114
// Labels
1215
pub const FUNCTION_KEY: &'static str = "function";
@@ -18,3 +21,5 @@ pub const ERROR_KEY: &'static str = "error";
1821
pub const OBJECTIVE_NAME: &'static str = "objective.name";
1922
pub const OBJECTIVE_PERCENTILE: &'static str = "objective.percentile";
2023
pub const OBJECTIVE_LATENCY_THRESHOLD: &'static str = "objective.latency_threshold";
24+
pub const VERSION_KEY: &'static str = "version";
25+
pub const COMMIT_KEY: &'static str = "commit";

autometrics/src/labels.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ use std::ops::Deref;
44
pub(crate) type Label = (&'static str, &'static str);
55
type ResultAndReturnTypeLabels = (&'static str, Option<&'static str>);
66

7+
/// These are the labels used for the `build_info` metric.
8+
pub struct BuildInfoLabels {
9+
pub(crate) version: &'static str,
10+
pub(crate) commit: &'static str,
11+
}
12+
13+
impl BuildInfoLabels {
14+
pub fn new(version: &'static str, commit: &'static str) -> Self {
15+
Self { version, commit }
16+
}
17+
18+
pub fn to_vec(&self) -> Vec<Label> {
19+
vec![(COMMIT_KEY, self.commit), (VERSION_KEY, self.version)]
20+
}
21+
}
22+
723
/// These are the labels used for the `function.calls.count` metric.
824
pub struct CounterLabels {
925
pub(crate) function: &'static str,

autometrics/src/tracker/metrics.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
use crate::constants::*;
2-
use crate::labels::{CounterLabels, GaugeLabels, HistogramLabels};
2+
use crate::labels::{BuildInfoLabels, CounterLabels, GaugeLabels, HistogramLabels};
33
use crate::tracker::TrackMetrics;
44
use metrics::{
55
describe_counter, describe_gauge, describe_histogram, register_counter, register_gauge,
66
register_histogram, Gauge,
77
};
88
use std::{sync::Once, time::Instant};
99

10-
static ONCE: Once = Once::new();
10+
const DESCRIBE_METRICS: Once = Once::new();
11+
const SET_BUILD_INFO: Once = Once::new();
1112

1213
fn describe_metrics() {
13-
ONCE.call_once(|| {
14+
DESCRIBE_METRICS.call_once(|| {
1415
describe_counter!(COUNTER_NAME, COUNTER_DESCRIPTION);
1516
describe_histogram!(HISTOGRAM_NAME, HISTOGRAM_DESCRIPTION);
1617
describe_gauge!(GAUGE_NAME, GAUGE_DESCRIPTION);
18+
describe_gauge!(BUILD_INFO_NAME, BUILD_INFO_DESCRIPTION);
1719
});
1820
}
1921

@@ -48,4 +50,10 @@ impl TrackMetrics for MetricsTracker {
4850
gauge.decrement(1.0);
4951
}
5052
}
53+
54+
fn set_build_info(build_info_labels: &BuildInfoLabels) {
55+
SET_BUILD_INFO.call_once(|| {
56+
register_gauge!(BUILD_INFO_NAME, &build_info_labels.to_vec()).set(1.0);
57+
});
58+
}
5159
}

autometrics/src/tracker/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::labels::{CounterLabels, GaugeLabels, HistogramLabels};
1+
use crate::labels::{BuildInfoLabels, CounterLabels, GaugeLabels, HistogramLabels};
22

33
#[cfg(feature = "metrics")]
44
mod metrics;
@@ -21,6 +21,7 @@ pub use self::metrics::MetricsTracker as AutometricsTracker;
2121
pub use self::prometheus::PrometheusTracker as AutometricsTracker;
2222

2323
pub trait TrackMetrics {
24+
fn set_build_info(build_info_labels: &BuildInfoLabels);
2425
fn start(gauge_labels: Option<&GaugeLabels>) -> Self;
2526
fn finish(self, counter_labels: &CounterLabels, histogram_labels: &HistogramLabels);
2627
}

autometrics/src/tracker/opentelemetry.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use crate::labels::{CounterLabels, GaugeLabels, HistogramLabels, Label};
1+
use crate::labels::{BuildInfoLabels, CounterLabels, GaugeLabels, HistogramLabels, Label};
22
use crate::{constants::*, tracker::TrackMetrics};
33
use opentelemetry_api::{global, metrics::UpDownCounter, Context, KeyValue};
4-
use std::time::Instant;
4+
use std::{sync::Once, time::Instant};
5+
6+
const SET_BUILD_INFO: Once = Once::new();
57

68
/// Tracks the number of function calls, concurrent calls, and latency
79
pub struct OpenTelemetryTracker {
@@ -58,6 +60,17 @@ impl TrackMetrics for OpenTelemetryTracker {
5860
concurrency_tracker.add(&self.context, -1, &gauge_labels);
5961
}
6062
}
63+
64+
fn set_build_info(build_info_labels: &BuildInfoLabels) {
65+
SET_BUILD_INFO.call_once(|| {
66+
let build_info_labels = to_key_values(build_info_labels.to_vec());
67+
let build_info = global::meter("")
68+
.f64_up_down_counter(BUILD_INFO_NAME)
69+
.with_description(BUILD_INFO_DESCRIPTION)
70+
.init();
71+
build_info.add(&Context::current(), 1.0, &build_info_labels);
72+
});
73+
}
6174
}
6275

6376
fn to_key_values(labels: impl IntoIterator<Item = Label>) -> Vec<KeyValue> {

autometrics/src/tracker/prometheus.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
use crate::labels::{CounterLabels, GaugeLabels, HistogramLabels};
1+
use crate::labels::{BuildInfoLabels, CounterLabels, GaugeLabels, HistogramLabels};
22
use crate::{constants::*, tracker::TrackMetrics, HISTOGRAM_BUCKETS};
33
use const_format::{formatcp, str_replace};
44
use once_cell::sync::Lazy;
5-
use prometheus::histogram_opts;
5+
use prometheus::core::{AtomicI64, GenericGauge};
66
use prometheus::{
7-
core::{AtomicI64, GenericGauge},
8-
register_histogram_vec, register_int_counter_vec, register_int_gauge_vec, HistogramVec,
9-
IntCounterVec, IntGaugeVec,
7+
histogram_opts, register_histogram_vec, register_int_counter_vec, register_int_gauge_vec,
8+
HistogramVec, IntCounterVec, IntGaugeVec,
109
};
11-
use std::time::Instant;
10+
use std::{sync::Once, time::Instant};
1211

1312
const COUNTER_NAME_PROMETHEUS: &str = str_replace!(COUNTER_NAME, ".", "_");
1413
const HISTOGRAM_NAME_PROMETHEUS: &str = str_replace!(HISTOGRAM_NAME, ".", "_");
@@ -17,6 +16,8 @@ const OBJECTIVE_NAME_PROMETHEUS: &str = str_replace!(OBJECTIVE_NAME, ".", "_");
1716
const OBJECTIVE_PERCENTILE_PROMETHEUS: &str = str_replace!(OBJECTIVE_PERCENTILE, ".", "_");
1817
const OBJECTIVE_LATENCY_PROMETHEUS: &str = str_replace!(OBJECTIVE_LATENCY_THRESHOLD, ".", "_");
1918

19+
const SET_BUILD_INFO: Once = Once::new();
20+
2021
static COUNTER: Lazy<IntCounterVec> = Lazy::new(|| {
2122
register_int_counter_vec!(
2223
COUNTER_NAME_PROMETHEUS,
@@ -66,6 +67,14 @@ static GAUGE: Lazy<IntGaugeVec> = Lazy::new(|| {
6667
)
6768
.expect("Failed to register function_calls_concurrent gauge")
6869
});
70+
static BUILD_INFO: Lazy<IntGaugeVec> = Lazy::new(|| {
71+
register_int_gauge_vec!(
72+
BUILD_INFO_NAME,
73+
BUILD_INFO_DESCRIPTION,
74+
&[COMMIT_KEY, VERSION_KEY]
75+
)
76+
.expect("Failed to register build_info counter")
77+
});
6978

7079
pub struct PrometheusTracker {
7180
start: Instant,
@@ -149,4 +158,12 @@ impl TrackMetrics for PrometheusTracker {
149158
gauge.dec();
150159
}
151160
}
161+
162+
fn set_build_info(build_info_labels: &BuildInfoLabels) {
163+
SET_BUILD_INFO.call_once(|| {
164+
BUILD_INFO
165+
.with_label_values(&[build_info_labels.commit, build_info_labels.version])
166+
.set(1);
167+
});
168+
}
152169
}

autometrics/tests/integration_test.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,19 @@ fn caller_label() {
153153
.unwrap();
154154
assert!(call_count.is_match(&metrics));
155155
}
156+
157+
#[cfg(feature = "prometheus-exporter")]
158+
#[test]
159+
fn build_info() {
160+
let _ = autometrics::global_metrics_exporter();
161+
162+
#[autometrics]
163+
fn function_just_to_initialize_build_info() {}
164+
165+
function_just_to_initialize_build_info();
166+
function_just_to_initialize_build_info();
167+
168+
let metrics = autometrics::encode_global_metrics().unwrap();
169+
let build_info: Regex = Regex::new(r#"build_info\{commit="",version="\S+"\} 1"#).unwrap();
170+
assert!(build_info.is_match(&metrics));
171+
}

examples/full-api/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ serde = { version = "1", features = ["derive"] }
1414
strum = { version = "0.24", features = ["derive"] }
1515
thiserror = "1"
1616
tokio = { version = "1", features = ["full"] }
17+
18+
[build-dependencies]
19+
vergen = { version = "8.1", features = ["git", "gitoxide"] }

examples/full-api/build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
vergen::EmitBuilder::builder()
3+
.git_sha(true) // short commit hash
4+
.emit()
5+
.expect("Unable to generate build info");
6+
}

0 commit comments

Comments
 (0)