Skip to content

Add stats API #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions envoyfilter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
use log::info;
use stats::IncrementingMetric;
use stats::Metric;
use stats::RecordingMetric;
use std::time::Duration;

use proxy_wasm::traits::*;
use proxy_wasm::types::*;

mod stats;

proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Trace);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> {
Box::new(HttpHeaderRoot {
header_content: String::new(),
metrics: WasmMetrics {
counter: stats::Counter::new(String::from("wasm_counter")),
gauge: stats::Gauge::new(String::from("wasm_gauge")),
histogram: stats::Histogram::new(String::from("wasm_histogram")),
}
})
});
}}

struct HttpHeader {
context_id: u32,
header_content: String,
metrics: WasmMetrics,
}

// HttpContext is the trait that allows the Rust code to interact with HTTP objects.
impl HttpContext for HttpHeader {
// Envoy's HTTP model is event driven. The WASM ABI has given implementors events to hook onto
// the lifecycle of the http request and response.
fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
// Metrics
self.metrics.counter.increment(10);
info!("counter -> {}", self.metrics.counter.value());
self.metrics.gauge.record(20);
info!("gauge -> {}", self.metrics.gauge.value());
self.metrics.histogram.record(30);
info!("histogram -> {}", self.metrics.histogram.value());

// Example of reading the HTTP headers on the incoming request
for (name, value) in &self.get_http_request_headers() {
info!("#{} -> {}: {}", self.context_id, name, value);
Expand Down Expand Up @@ -86,8 +105,16 @@ impl Context for HttpHeader {
}
}

#[derive(Copy, Clone)]
struct WasmMetrics {
counter: stats::Counter,
gauge: stats::Gauge,
histogram: stats::Histogram,
}

struct HttpHeaderRoot {
header_content: String,
metrics: WasmMetrics,
}

impl Context for HttpHeaderRoot {}
Expand All @@ -105,6 +132,7 @@ impl RootContext for HttpHeaderRoot {
Some(Box::new(HttpHeader {
context_id,
header_content: self.header_content.clone(),
metrics: self.metrics,
}))
}

Expand Down
98 changes: 98 additions & 0 deletions envoyfilter/src/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

use proxy_wasm::hostcalls;
use proxy_wasm::types::*;

pub trait Metric {
fn id(&self) -> u32;

fn value(&self) -> u64 {
match hostcalls::get_metric(self.id()) {
Ok(value) => value,
Err(Status::NotFound) => panic!("metric not found: {}", self.id()),
Err(err) => panic!("unexpected status: {:?}", err),
}
}
}

pub trait IncrementingMetric: Metric {
fn increment(&self, offset: i64) {
match hostcalls::increment_metric(self.id(), offset) {
Ok(_) => return,
Err(Status::NotFound) => panic!("metric not found: {}", self.id()),
Err(err) => panic!("unexpected status: {:?}", err),
}
}
}

pub trait RecordingMetric: Metric {
fn record(&self, value: u64) {
match hostcalls::record_metric(self.id(), value) {
Ok(_) => return,
Err(Status::NotFound) => panic!("metric not found: {}", self.id()),
Err(err) => panic!("unexpected status: {:?}", err),
}
}
}

#[derive(Copy, Clone)]
pub struct Counter {
id: u32,
}

impl Counter {
pub fn new(name: String) -> Counter {
let returned_id = hostcalls::define_metric(MetricType::Counter, &name)
.expect("failed to define counter '{}', name");
Counter { id: returned_id }
}
}

impl Metric for Counter {
fn id(&self) -> u32 {
self.id
}
}

impl IncrementingMetric for Counter {}

#[derive(Copy, Clone)]
pub struct Gauge {
id: u32,
}

impl Gauge {
pub fn new(name: String) -> Gauge {
let returned_id = hostcalls::define_metric(MetricType::Gauge, &name)
.expect("failed to define gauge '{}', name");
Gauge { id: returned_id }
}
}

impl Metric for Gauge {
fn id(&self) -> u32 {
self.id
}
}

impl RecordingMetric for Gauge {}

#[derive(Copy, Clone)]
pub struct Histogram {
id: u32,
}

impl Histogram {
pub fn new(name: String) -> Histogram {
let returned_id = hostcalls::define_metric(MetricType::Histogram, &name)
.expect("failed to define histogram '{}', name");
Histogram { id: returned_id }
}
}

impl Metric for Histogram {
fn id(&self) -> u32 {
self.id
}
}

impl RecordingMetric for Histogram {}