Skip to content

Commit

Permalink
Provide Static Metric Macros (tikv#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
breezewish authored and azdlowry committed Sep 10, 2018
1 parent 2807b5f commit 91ac05f
Show file tree
Hide file tree
Showing 13 changed files with 913 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ optional = true
[dev-dependencies]
getopts = "0.2"
hyper = {version = "0.9", default-features = false}

[workspace]
members = ["static-metric"]
28 changes: 28 additions & 0 deletions static-metric/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "prometheus-static-metric"
version = "0.1.0"
license = "Apache-2.0"
authors = ["me@breeswish.org"]
description = "Static metric helper utilities for rust-prometheus."
repository = "https://github.com/pingcap/rust-prometheus"
homepage = "https://github.com/pingcap/rust-prometheus"
documentation = "https://docs.rs/prometheus-static-metric"
include = [
"LICENSE",
"Cargo.toml",
"src/**/*.rs",
]

[lib]
proc-macro = true

[dependencies]
syn = { version = "0.12", features = ["full", "extra-traits"] }
quote = "0.4"
lazy_static = "1.0"

[dev-dependencies]
prometheus = { path = ".." }

[features]
default = []
25 changes: 25 additions & 0 deletions static-metric/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ENABLE_FEATURES ?= default

all: format build test examples

build:
cargo build --features="${ENABLE_FEATURES}"

test:
cargo test --features="${ENABLE_FEATURES}" -- --nocapture

format:
@cargo fmt --all -- --write-mode diff >/dev/null || cargo fmt --all

bench: format
RUSTFLAGS="--cfg bench" cargo bench --features="${ENABLE_FEATURES}" -- --nocapture

clean:
cargo clean

examples:
cargo build --example simple
cargo build --example with_lazy_static
cargo build --example advanced

.PHONY: all
87 changes: 87 additions & 0 deletions static-metric/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# prometheus-static-metric

[![docs.rs](https://docs.rs/prometheus-static-metric/badge.svg)](https://docs.rs/prometheus-static-metric)
[![crates.io](http://meritbadge.herokuapp.com/prometheus-static-metric)](https://crates.io/crates/prometheus-static-metric)

Utility macro to build static metrics for the [rust-prometheus](https://github.com/pingcap/rust-prometheus) library.

## Why?

`MetricVec` (i.e. `CounterVec`, `GaugeVec` or `HistogramVec`) is slow. However if every possible values for labels are
known, each metric in the `MetricVec` can be cached to avoid the runtime cost.

For example, the following code can be slow when it is invoked multiple times:

```rust
some_counter_vec.with_label_values(&["label_1_foo", "label_2_bar"]).inc();
```

It is because we are retriving a specific `Counter` according to values each time and to ensure thread-safety there is
a lock inside which makes things worse.

We can optimize it by caching the counter by label values:

```rust
// init before hand
let foo_bar_counter = some_counter.with_label_values(&["label_1_foo", "label_2_bar"]);

foo_bar_counter.inc();
```

So far everything seems good. We achieve the same performance as `Counter` for `CounterVec`. But what if there are many
labels and each of them has many values? We need to hand-craft a lot of code in this way.

That's what this crate solves. This crate provides a macro that helps you do the optimization above without really
introducing a lot of templating code.

## Getting Started

+ Add to `Cargo.toml`:

```toml
[dependencies]
prometheus-static-metric = "0.1"
```

+ Add to `lib.rs`:

```rust
#![feature(proc_macro)]

extern crate prometheus_static_metric;
```

## Example

Use the `make_static_metric!` to define all possible values for each label. Your definition will be expanded to a real
`struct` for easy access while keeping high-performance.

```rust
use prometheus_static_metric::make_static_metric;

make_static_metric! {
pub struct MyStaticCounterVec: Counter {
"method" => {
post,
get,
put,
delete,
},
"product" => {
foo,
bar,
},
}
}

fn main() {
let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap();
let static_counter_vec = MyStaticCounterVec::from(&vec);

static_counter_vec.post.foo.inc();
static_counter_vec.delete.bar.inc_by(4.0);
assert_eq!(static_counter_vec.post.bar.get(), 0.0);
assert_eq!(vec.with_label_values(&["post", "foo"]).get(), 1.0);
assert_eq!(vec.with_label_values(&["delete", "bar"]).get(), 4.0);
}
```
129 changes: 129 additions & 0 deletions static-metric/benches/benches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

#![feature(test)]
#![feature(proc_macro)]

extern crate prometheus;
extern crate prometheus_static_metric;
extern crate test;

use prometheus::{IntCounter, IntCounterVec, Opts};
use prometheus_static_metric::make_static_metric;
use test::Bencher;

#[bench]
/// Single `IntCounter` performance.
fn bench_single_counter(b: &mut Bencher) {
let counter = IntCounter::new("foo", "bar").unwrap();
b.iter(|| counter.inc());
}

#[bench]
/// `IntCounterVec` performance.
fn bench_counter_vec(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap();
b.iter(|| counter_vec.with_label_values(&["foo", "bar"]).inc());
}

#[bench]
/// Manually implemented static metrics performance, metrics are placed outside a struct.
fn bench_static_metrics_handwrite_1(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap();
let counter = counter_vec.with_label_values(&["foo", "bar"]);
b.iter(|| counter.inc());
}

#[bench]
/// Manually implemented static metrics performance, metrics are placed nested inside a struct.
fn bench_static_metrics_handwrite_2(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap();
struct StaticCounter1 {
foo: StaticCounter1Field2,
}
struct StaticCounter1Field2 {
bar: IntCounter,
}
let static_counter = StaticCounter1 {
foo: StaticCounter1Field2 {
bar: counter_vec.with_label_values(&["foo", "bar"]),
},
};
b.iter(|| static_counter.foo.bar.inc());
}

make_static_metric! {
struct StaticCounter2: IntCounter {
"d1" => {
foo,
},
"d2" => {
bar,
},
}
}

#[bench]
/// macro implemented static metrics performance.
fn bench_static_metrics_macro(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap();
let static_counter = StaticCounter2::from(&counter_vec);
b.iter(|| static_counter.foo.bar.inc());
}

#[bench]
/// macro implemented static metrics performance, with dynamic lookup.
fn bench_static_metrics_macro_with_lookup(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap();
let static_counter = StaticCounter2::from(&counter_vec);
b.iter(|| static_counter.get("foo").get("bar").inc());
}

make_static_metric! {
struct StaticCounter3: IntCounter {
"d1" => { val1 },
"d2" => { val2 },
"d3" => { val3 },
"d4" => { val4 },
"d5" => { val5 },
"d6" => { val6 },
"d7" => { val7 },
"d8" => { val8 },
"d9" => { val9 },
"d10" => { val10 },
}
}

#[bench]
/// macro implemented static metrics performance, with a deep nesting level.
fn bench_static_metrics_macro_deep(b: &mut Bencher) {
let counter_vec = IntCounterVec::new(
Opts::new("foo", "bar"),
&["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10"],
).unwrap();
let static_counter = StaticCounter3::from(&counter_vec);
b.iter(|| {
static_counter
.val1
.val2
.val3
.val4
.val5
.val6
.val7
.val8
.val9
.val10
.inc()
});
}
85 changes: 85 additions & 0 deletions static-metric/examples/advanced.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

#![feature(proc_macro)]

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate prometheus;
extern crate prometheus_static_metric;

use prometheus::IntCounterVec;
use prometheus_static_metric::make_static_metric;

make_static_metric! {
pub struct HttpRequestStatistics: IntCounter {
"method" => {
post,
get,
put,
delete,
},
"version" => {
http1: "HTTP/1",
http2: "HTTP/2",
},
"product" => {
foo,
bar,
},
}
}

lazy_static! {
pub static ref HTTP_COUNTER_VEC: IntCounterVec =
register_int_counter_vec!(
"http_requests",
"Total number of HTTP requests.",
&["product", "method", "version"] // it doesn't matter for the label order
).unwrap();

pub static ref HTTP_COUNTER: HttpRequestStatistics = HttpRequestStatistics
::from(&HTTP_COUNTER_VEC);
}

/// This example demonstrates the usage of:
/// 1. using alternative metric types (i.e. IntCounter)
/// 2. specifying different label order compared to the definition
/// 3. using non-identifiers as values
fn main() {
HTTP_COUNTER.post.http1.foo.inc_by(4);
assert_eq!(
HTTP_COUNTER_VEC
.with_label_values(&["foo", "post", "HTTP/1"])
.get(),
4
);

// Note: You cannot specify values other than the definition in `get()` because
// it is purely static.
HTTP_COUNTER
.get("delete")
.unwrap()
.get("HTTP/1")
.unwrap()
.foo
.inc_by(7);
assert_eq!(
HTTP_COUNTER_VEC
.with_label_values(&["foo", "delete", "HTTP/1"])
.get(),
7
);
}
Loading

0 comments on commit 91ac05f

Please sign in to comment.