use std::collections::HashMap;
use std::ffi::{c_char, c_int, c_void, CStr, CString, NulError};
use std::marker::PhantomData;
use std::path::Path;
use std::ptr;
use std::time::Duration;
use varnish_sys::ffi;
use varnish_sys::vcl::{VclError, VclResult};
#[derive(Debug)]
pub struct MetricsReader<'a> {
vsm: *mut ffi::vsm,
vsc: *mut ffi::vsc,
internal: Box<MetricsReaderImpl<'a>>,
}
#[derive(Debug, Default)]
struct MetricsReaderImpl<'a> {
points: HashMap<usize, Metric<'a>>,
added: Vec<usize>,
deleted: Vec<usize>,
}
pub struct MetricsReaderBuilder<'a> {
vsm: *mut ffi::vsm,
vsc: *mut ffi::vsc,
phantom: PhantomData<&'a ()>,
}
impl<'a> MetricsReaderBuilder<'a> {
#[expect(clippy::new_without_default)] pub fn new() -> Self {
unsafe {
let vsm = ffi::VSM_New();
assert!(!vsm.is_null());
let vsc = ffi::VSC_New();
assert!(!vsc.is_null());
ffi::VSC_Arg(vsc, 'r' as c_char, ptr::null());
Self {
vsm,
vsc,
phantom: PhantomData,
}
}
}
pub fn work_dir(self, dir: &Path) -> Result<Self, NulError> {
let c_dir = CString::new(dir.to_str().unwrap())?;
let ret = unsafe { ffi::VSM_Arg(self.vsm, 'n' as c_char, c_dir.as_ptr()) };
assert_eq!(ret, 1);
Ok(self)
}
#[must_use]
pub fn patience(self, t: Option<Duration>) -> Self {
let arg = CString::new(match t {
None => "off".to_string(),
Some(t) => t.as_secs_f64().to_string(),
})
.unwrap();
unsafe {
let ret = ffi::VSM_Arg(self.vsm, 't' as c_char, arg.as_ptr());
assert_eq!(ret, 1);
}
self
}
fn vsc_arg(self, o: char, s: &str) -> Result<Self, NulError> {
let c_s = CString::new(s)?;
unsafe {
let ret = ffi::VSC_Arg(self.vsc, o as c_char, c_s.as_ptr());
assert_eq!(ret, 1);
}
Ok(self)
}
pub fn include(self, s: &str) -> Result<Self, NulError> {
self.vsc_arg('I', s)
}
pub fn exclude(self, s: &str) -> Result<Self, NulError> {
self.vsc_arg('X', s)
}
pub fn require(self, s: &str) -> Result<Self, NulError> {
self.vsc_arg('R', s)
}
pub fn build(mut self) -> VclResult<MetricsReader<'a>> {
let ret = unsafe { ffi::VSM_Attach(self.vsm, 0) };
if ret != 0 {
let err = vsm_error(self.vsm);
unsafe {
ffi::VSM_ResetError(self.vsm);
}
Err(err)
} else {
let mut internal = Box::new(MetricsReaderImpl::default());
unsafe {
ffi::VSC_State(
self.vsc,
Some(add_point),
Some(del_point),
ptr::from_mut::<MetricsReaderImpl>(&mut *internal).cast::<c_void>(),
);
}
let vsm = self.vsm;
let vsc = self.vsc;
self.vsm = ptr::null_mut();
self.vsc = ptr::null_mut();
Ok(MetricsReader { vsm, vsc, internal })
}
}
}
fn vsm_error(p: *const ffi::vsm) -> VclError {
unsafe {
VclError::new(
CStr::from_ptr(ffi::VSM_Error(p))
.to_str()
.unwrap()
.to_string(),
)
}
}
impl Drop for MetricsReaderBuilder<'_> {
fn drop(&mut self) {
assert!(
(self.vsc.is_null() && self.vsm.is_null())
|| (!self.vsc.is_null() && !self.vsm.is_null())
);
if !self.vsc.is_null() {
unsafe {
ffi::VSC_Destroy(&raw mut self.vsc, self.vsm);
}
}
}
}
impl Drop for MetricsReader<'_> {
fn drop(&mut self) {
unsafe {
ffi::VSC_Destroy(&raw mut self.vsc, self.vsm);
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Semantics {
Counter,
Gauge,
Bitmap,
Unknown,
}
impl From<c_int> for Semantics {
fn from(value: c_int) -> Self {
let c = char::from_u32(value as u32).unwrap();
match c {
'c' => Semantics::Counter,
'g' => Semantics::Gauge,
'b' => Semantics::Bitmap,
_ => Semantics::Unknown,
}
}
}
impl From<Semantics> for char {
fn from(value: Semantics) -> char {
match value {
Semantics::Counter => 'c',
Semantics::Gauge => 'g',
Semantics::Bitmap => 'b',
Semantics::Unknown => '?',
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MetricFormat {
Integer,
Bytes,
Bitmap,
Duration,
Unknown,
}
impl From<c_int> for MetricFormat {
fn from(value: c_int) -> Self {
match char::from_u32(value as u32).unwrap() {
'i' => MetricFormat::Integer,
'B' => MetricFormat::Bytes,
'b' => MetricFormat::Bitmap,
'd' => MetricFormat::Duration,
_ => MetricFormat::Unknown,
}
}
}
impl From<MetricFormat> for char {
fn from(value: MetricFormat) -> char {
match value {
MetricFormat::Integer => 'i',
MetricFormat::Bytes => 'B',
MetricFormat::Bitmap => 'b',
MetricFormat::Duration => 'd',
MetricFormat::Unknown => '?',
}
}
}
unsafe extern "C" fn add_point(ptr: *mut c_void, point: *const ffi::VSC_point) -> *mut c_void {
let internal = ptr.cast::<MetricsReaderImpl>().as_mut().unwrap();
let k = point as usize;
let point = point.as_ref().unwrap();
let stat = Metric {
value: point.ptr,
name: CStr::from_ptr(point.name).to_str().unwrap(),
short_desc: CStr::from_ptr(point.sdesc).to_str().unwrap(),
long_desc: CStr::from_ptr(point.ldesc).to_str().unwrap(),
semantics: point.semantics.into(),
format: point.format.into(),
};
assert_eq!(internal.points.insert(k, stat), None);
internal.added.push(k);
ptr::null_mut()
}
unsafe extern "C" fn del_point(ptr: *mut c_void, point: *const ffi::VSC_point) {
let internal = ptr.cast::<MetricsReaderImpl>().as_mut().unwrap();
let k = point as usize;
assert!(internal.points.contains_key(&k));
internal.deleted.push(k);
assert!(internal.points.remove(&k).is_some());
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Metric<'a> {
value: *const u64,
pub name: &'a str,
pub short_desc: &'a str,
pub long_desc: &'a str,
pub semantics: Semantics,
pub format: MetricFormat,
}
impl Metric<'_> {
pub fn get_raw_value(&self) -> u64 {
unsafe { *self.value }
}
pub fn get_clamped_value(&self) -> u64 {
let v = unsafe { *self.value };
if i64::try_from(v).is_ok() {
v
} else {
0
}
}
}
impl MetricsReader<'_> {
pub fn stats(&self) -> &HashMap<usize, Metric<'_>> {
&self.internal.points
}
pub fn update(&mut self) -> (Vec<usize>, Vec<usize>) {
unsafe {
ffi::VSC_Iter(self.vsc, self.vsm, None, ptr::null_mut());
}
let added = std::mem::take(&mut self.internal.added);
let deleted = std::mem::take(&mut self.internal.deleted);
(added, deleted)
}
}