1use std::collections::HashMap;
2use std::ffi::{c_char, c_int, c_void, CStr, CString, NulError};
3use std::marker::PhantomData;
4use std::path::Path;
5use std::ptr;
6use std::time::Duration;
7
8use varnish_sys::ffi;
9use varnish_sys::vcl::{VclError, VclResult};
10
11#[derive(Debug)]
33pub struct MetricsReader<'a> {
34 vsm: *mut ffi::vsm,
35 vsc: *mut ffi::vsc,
36 internal: Box<MetricsReaderImpl<'a>>,
37}
38
39#[derive(Debug, Default)]
40struct MetricsReaderImpl<'a> {
41 points: HashMap<usize, Metric<'a>>,
42 added: Vec<usize>,
43 deleted: Vec<usize>,
44}
45
46pub struct MetricsReaderBuilder<'a> {
48 vsm: *mut ffi::vsm,
49 vsc: *mut ffi::vsc,
50 phantom: PhantomData<&'a ()>,
51}
52
53impl<'a> MetricsReaderBuilder<'a> {
54 #[expect(clippy::new_without_default)] pub fn new() -> Self {
57 unsafe {
58 let vsm = ffi::VSM_New();
59 assert!(!vsm.is_null());
60 let vsc = ffi::VSC_New();
61 assert!(!vsc.is_null());
62 ffi::VSC_Arg(vsc, 'r' as c_char, ptr::null());
64 Self {
65 vsm,
66 vsc,
67 phantom: PhantomData,
68 }
69 }
70 }
71
72 pub fn work_dir(self, dir: &Path) -> Result<Self, NulError> {
77 let c_dir = CString::new(dir.to_str().expect("work_dir path must be valid UTF-8"))?;
78 let ret = unsafe { ffi::VSM_Arg(self.vsm, 'n' as c_char, c_dir.as_ptr()) };
79 assert_eq!(ret, 1);
80 Ok(self)
81 }
82
83 #[must_use]
89 pub fn patience(self, t: Option<Duration>) -> Self {
90 let arg = CString::new(match t {
91 None => "off".to_string(),
92 Some(t) => t.as_secs_f64().to_string(),
93 })
94 .expect("string constructed from f64 cannot contain null bytes");
95
96 unsafe {
99 let ret = ffi::VSM_Arg(self.vsm, 't' as c_char, arg.as_ptr());
102 assert_eq!(ret, 1);
103 }
104
105 self
106 }
107
108 fn vsc_arg(self, o: char, s: &str) -> Result<Self, NulError> {
109 let c_s = CString::new(s)?;
110 unsafe {
111 let ret = ffi::VSC_Arg(self.vsc, o as c_char, c_s.as_ptr());
112 assert_eq!(ret, 1);
113 }
114 Ok(self)
115 }
116
117 pub fn include(self, s: &str) -> Result<Self, NulError> {
121 self.vsc_arg('I', s)
122 }
123
124 pub fn exclude(self, s: &str) -> Result<Self, NulError> {
128 self.vsc_arg('X', s)
129 }
130
131 pub fn require(self, s: &str) -> Result<Self, NulError> {
134 self.vsc_arg('R', s)
135 }
136
137 pub fn build(mut self) -> VclResult<MetricsReader<'a>> {
139 let ret = unsafe { ffi::VSM_Attach(self.vsm, 0) };
140 if ret != 0 {
141 let err = vsm_error(self.vsm);
142 unsafe {
143 ffi::VSM_ResetError(self.vsm);
144 }
145 Err(err)
146 } else {
147 let mut internal = Box::new(MetricsReaderImpl::default());
148 unsafe {
149 ffi::VSC_State(
150 self.vsc,
151 Some(add_point),
152 Some(del_point),
153 ptr::from_mut::<MetricsReaderImpl>(&mut *internal).cast::<c_void>(),
154 );
155 }
156 let vsm = self.vsm;
157 let vsc = self.vsc;
158 self.vsm = ptr::null_mut();
160 self.vsc = ptr::null_mut();
161 Ok(MetricsReader { vsm, vsc, internal })
162 }
163 }
164}
165
166fn vsm_error(p: *const ffi::vsm) -> VclError {
167 unsafe {
168 VclError::new(
169 CStr::from_ptr(ffi::VSM_Error(p))
170 .to_str()
171 .expect("VSM error message must be valid UTF-8")
172 .to_string(),
173 )
174 }
175}
176
177impl Drop for MetricsReaderBuilder<'_> {
178 fn drop(&mut self) {
179 assert!(
180 (self.vsc.is_null() && self.vsm.is_null())
181 || (!self.vsc.is_null() && !self.vsm.is_null())
182 );
183 if !self.vsc.is_null() {
184 unsafe {
185 ffi::VSC_Destroy(&raw mut self.vsc, self.vsm);
186 }
187 }
188 }
189}
190
191impl Drop for MetricsReader<'_> {
192 fn drop(&mut self) {
193 unsafe {
194 ffi::VSC_Destroy(&raw mut self.vsc, self.vsm);
195 }
196 }
197}
198
199#[derive(Debug, Copy, Clone, Eq, PartialEq)]
201pub enum Semantics {
202 Counter,
204 Gauge,
206 Bitmap,
208 Unknown,
210}
211
212impl From<c_int> for Semantics {
213 fn from(value: c_int) -> Self {
214 match char::from_u32(value as u32) {
215 Some('c') => Semantics::Counter,
216 Some('g') => Semantics::Gauge,
217 Some('b') => Semantics::Bitmap,
218 _ => Semantics::Unknown,
219 }
220 }
221}
222
223impl From<Semantics> for char {
224 fn from(value: Semantics) -> char {
225 match value {
226 Semantics::Counter => 'c',
227 Semantics::Gauge => 'g',
228 Semantics::Bitmap => 'b',
229 Semantics::Unknown => '?',
230 }
231 }
232}
233
234#[derive(Debug, Copy, Clone, Eq, PartialEq)]
236pub enum MetricFormat {
237 Integer,
239 Bytes,
241 Bitmap,
243 Duration,
245 Unknown,
248}
249
250impl From<c_int> for MetricFormat {
251 fn from(value: c_int) -> Self {
252 match char::from_u32(value as u32) {
253 Some('i') => MetricFormat::Integer,
254 Some('B') => MetricFormat::Bytes,
255 Some('b') => MetricFormat::Bitmap,
256 Some('d') => MetricFormat::Duration,
257 _ => MetricFormat::Unknown,
258 }
259 }
260}
261
262impl From<MetricFormat> for char {
263 fn from(value: MetricFormat) -> char {
264 match value {
265 MetricFormat::Integer => 'i',
266 MetricFormat::Bytes => 'B',
267 MetricFormat::Bitmap => 'b',
268 MetricFormat::Duration => 'd',
269 MetricFormat::Unknown => '?',
270 }
271 }
272}
273
274unsafe extern "C" fn add_point(ptr: *mut c_void, point: *const ffi::VSC_point) -> *mut c_void {
275 let internal = ptr
277 .cast::<MetricsReaderImpl>()
278 .as_mut()
279 .expect("VSC add_point callback priv pointer must not be null");
280 let k = point as usize;
281 let point = point.as_ref().expect("VSC point pointer must not be null");
282
283 let stat = Metric {
284 value: point.ptr,
285 name: CStr::from_ptr(point.name)
286 .to_str()
287 .expect("VSC point name must be valid UTF-8"),
288 short_desc: CStr::from_ptr(point.sdesc)
289 .to_str()
290 .expect("VSC point short description must be valid UTF-8"),
291 long_desc: CStr::from_ptr(point.ldesc)
292 .to_str()
293 .expect("VSC point long description must be valid UTF-8"),
294 semantics: point.semantics.into(),
295 format: point.format.into(),
296 };
297 assert_eq!(internal.points.insert(k, stat), None);
299 internal.added.push(k);
300 ptr::null_mut()
301}
302
303unsafe extern "C" fn del_point(ptr: *mut c_void, point: *const ffi::VSC_point) {
304 let internal = ptr
305 .cast::<MetricsReaderImpl>()
306 .as_mut()
307 .expect("VSC del_point callback priv pointer must not be null");
308 let k = point as usize;
309 assert!(internal.points.contains_key(&k));
310 internal.deleted.push(k);
311 assert!(internal.points.remove(&k).is_some());
312}
313
314#[derive(Debug, Copy, Clone, Eq, PartialEq)]
318pub struct Metric<'a> {
319 value: *const u64,
320 pub name: &'a str,
321 pub short_desc: &'a str,
322 pub long_desc: &'a str,
323 pub semantics: Semantics,
324 pub format: MetricFormat,
325}
326
327impl Metric<'_> {
328 pub fn get_raw_value(&self) -> u64 {
330 unsafe { *self.value }
334 }
335
336 pub fn get_clamped_value(&self) -> u64 {
342 let v = unsafe { *self.value };
346 if i64::try_from(v).is_ok() {
347 v
348 } else {
349 0
350 }
351 }
352}
353
354impl MetricsReader<'_> {
355 pub fn stats(&self) -> &HashMap<usize, Metric<'_>> {
363 &self.internal.points
364 }
365
366 pub fn update(&mut self) -> (Vec<usize>, Vec<usize>) {
375 unsafe {
376 ffi::VSC_Iter(self.vsc, self.vsm, None, ptr::null_mut());
377 }
378 let added = std::mem::take(&mut self.internal.added);
379 let deleted = std::mem::take(&mut self.internal.deleted);
380 (added, deleted)
381 }
382}