diff --git a/envoyfilter/src/lib.rs b/envoyfilter/src/lib.rs index 69131654..5bc97cc1 100644 --- a/envoyfilter/src/lib.rs +++ b/envoyfilter/src/lib.rs @@ -1,14 +1,24 @@ 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 { 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")), + } }) }); }} @@ -16,6 +26,7 @@ proxy_wasm::main! {{ struct HttpHeader { context_id: u32, header_content: String, + metrics: WasmMetrics, } // HttpContext is the trait that allows the Rust code to interact with HTTP objects. @@ -23,6 +34,14 @@ 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); @@ -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 {} @@ -105,6 +132,7 @@ impl RootContext for HttpHeaderRoot { Some(Box::new(HttpHeader { context_id, header_content: self.header_content.clone(), + metrics: self.metrics, })) } diff --git a/envoyfilter/src/stats.rs b/envoyfilter/src/stats.rs new file mode 100644 index 00000000..f08c39c9 --- /dev/null +++ b/envoyfilter/src/stats.rs @@ -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 {}