1pub use self::cdtime::CdTime;
2pub use self::logger::{collectd_log, log_err, CollectdLogger, CollectdLoggerBuilder, LogLevel};
3pub use self::oconfig::{ConfigItem, ConfigValue};
4use crate::bindings::{
5 data_set_t, meta_data_add_boolean, meta_data_add_double, meta_data_add_signed_int,
6 meta_data_add_string, meta_data_add_unsigned_int, meta_data_create, meta_data_destroy,
7 meta_data_get_boolean, meta_data_get_double, meta_data_get_signed_int, meta_data_get_string,
8 meta_data_get_unsigned_int, meta_data_t, meta_data_toc, meta_data_type, plugin_dispatch_values,
9 uc_get_rate, value_list_t, value_t, ARR_LENGTH, DS_TYPE_ABSOLUTE, DS_TYPE_COUNTER,
10 DS_TYPE_DERIVE, DS_TYPE_GAUGE, MD_TYPE_BOOLEAN, MD_TYPE_DOUBLE, MD_TYPE_SIGNED_INT,
11 MD_TYPE_STRING, MD_TYPE_UNSIGNED_INT,
12};
13use crate::errors::{ArrayError, CacheRateError, ReceiveError, SubmitError};
14use chrono::prelude::*;
15use chrono::Duration;
16use memchr::memchr;
17use std::borrow::Cow;
18use std::collections::HashMap;
19use std::ffi::{CStr, CString};
20use std::fmt;
21use std::os::raw::{c_char, c_void};
22use std::ptr;
23use std::slice;
24use std::str::Utf8Error;
25
26mod cdtime;
27mod logger;
28mod oconfig;
29
30#[derive(Debug, Clone, PartialEq)]
33pub enum MetaValue {
34 String(String),
35 SignedInt(i64),
36 UnsignedInt(u64),
37 Double(f64),
38 Boolean(bool),
39}
40
41#[derive(Debug, PartialEq, Eq, Clone, Copy)]
42#[repr(u32)]
43#[allow(dead_code)]
44enum ValueType {
45 Counter = DS_TYPE_COUNTER,
46 Gauge = DS_TYPE_GAUGE,
47 Derive = DS_TYPE_DERIVE,
48 Absolute = DS_TYPE_ABSOLUTE,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
53pub enum Value {
54 Counter(u64),
62
63 Gauge(f64),
66
67 Derive(i64),
73
74 Absolute(u64),
78}
79
80impl Value {
81 pub fn is_nan(&self) -> bool {
90 if let Value::Gauge(x) = *self {
91 x.is_nan()
92 } else {
93 false
94 }
95 }
96}
97
98impl fmt::Display for Value {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match *self {
101 Value::Counter(x) | Value::Absolute(x) => write!(f, "{}", x),
102 Value::Gauge(x) => write!(f, "{}", x),
103 Value::Derive(x) => write!(f, "{}", x),
104 }
105 }
106}
107
108impl From<Value> for value_t {
109 fn from(x: Value) -> Self {
110 match x {
111 Value::Counter(x) => value_t { counter: x },
112 Value::Gauge(x) => value_t { gauge: x },
113 Value::Derive(x) => value_t { derive: x },
114 Value::Absolute(x) => value_t { absolute: x },
115 }
116 }
117}
118
119#[derive(Debug, PartialEq, Clone, Copy)]
121pub struct ValueReport<'a> {
122 pub name: &'a str,
124
125 pub value: Value,
127
128 pub min: f64,
130
131 pub max: f64,
133}
134
135#[derive(Debug, PartialEq, Clone)]
137pub struct ValueList<'a> {
138 pub values: Vec<ValueReport<'a>>,
139
140 pub plugin: &'a str,
143
144 pub plugin_instance: Option<&'a str>,
147
148 pub type_: &'a str,
151
152 pub type_instance: Option<&'a str>,
156
157 pub host: &'a str,
159
160 pub time: DateTime<Utc>,
162
163 pub interval: Duration,
165
166 pub meta: HashMap<String, MetaValue>,
168
169 original_list: *const value_list_t,
171 original_set: *const data_set_t,
172}
173
174impl<'a> ValueList<'a> {
175 pub fn rates(&self) -> Result<Cow<'_, Vec<ValueReport<'a>>>, CacheRateError> {
182 let all_gauges = self
185 .values
186 .iter()
187 .all(|x| matches!(x.value, Value::Gauge(_)));
188
189 if all_gauges {
190 return Ok(Cow::Borrowed(&self.values));
191 }
192
193 let ptr = unsafe { uc_get_rate(self.original_set, self.original_list) };
194 if !ptr.is_null() {
195 let nv = unsafe { slice::from_raw_parts(ptr, self.values.len()) }
196 .iter()
197 .zip(self.values.iter())
198 .map(|(rate, report)| match report.value {
199 Value::Gauge(_) => *report,
200 _ => ValueReport {
201 value: Value::Gauge(*rate),
202 ..*report
203 },
204 })
205 .collect();
206 Ok(Cow::Owned(nv))
207 } else {
208 Err(CacheRateError)
209 }
210 }
211
212 pub fn from<'b>(
213 set: &'b data_set_t,
214 list: &'b value_list_t,
215 ) -> Result<ValueList<'b>, ReceiveError> {
216 let plugin = receive_array(&list.plugin, "", "plugin name")?;
217 let ds_len = length(set.ds_num);
218 let list_len = length(list.values_len);
219
220 let values = if !list.values.is_null() && !set.ds.is_null() {
221 unsafe { slice::from_raw_parts(list.values, list_len) }
222 .iter()
223 .zip(unsafe { slice::from_raw_parts(set.ds, ds_len) })
224 .map(|(val, source)| unsafe {
225 let v = match ::std::mem::transmute::<i32, ValueType>(source.type_) {
226 ValueType::Gauge => Value::Gauge(val.gauge),
227 ValueType::Counter => Value::Counter(val.counter),
228 ValueType::Derive => Value::Derive(val.derive),
229 ValueType::Absolute => Value::Absolute(val.absolute),
230 };
231
232 let name = receive_array(&source.name, plugin, "data source name")?;
233 Ok(ValueReport {
234 name,
235 value: v,
236 min: source.min,
237 max: source.max,
238 })
239 })
240 .collect::<Result<Vec<_>, _>>()?
241 } else {
242 Vec::new()
243 };
244
245 assert!(list.time > 0);
246 assert!(list.interval > 0);
247
248 let plugin_instance =
249 receive_array(&list.plugin_instance, plugin, "plugin_instance").map(empty_to_none)?;
250
251 let type_ = receive_array(&list.type_, plugin, "type")?;
252
253 let type_instance =
254 receive_array(&list.type_instance, plugin, "type_instance").map(empty_to_none)?;
255
256 let host = receive_array(&list.host, plugin, "host")?;
257
258 let meta = from_meta_data(plugin, list.meta)?;
259
260 Ok(ValueList {
261 values,
262 plugin_instance,
263 plugin,
264 type_,
265 type_instance,
266 host,
267 time: CdTime::from(list.time).into(),
268 interval: CdTime::from(list.interval).into(),
269 meta,
270 original_list: list,
271 original_set: set,
272 })
273 }
274}
275
276#[derive(Debug, PartialEq, Clone)]
277struct SubmitValueList<'a> {
278 values: &'a [Value],
279 plugin_instance: Option<&'a str>,
280 plugin: &'a str,
281 type_: &'a str,
282 type_instance: Option<&'a str>,
283 host: Option<&'a str>,
284 time: Option<DateTime<Utc>>,
285 interval: Option<Duration>,
286 meta: HashMap<&'a str, MetaValue>,
287}
288
289#[derive(Debug, PartialEq, Clone)]
291pub struct ValueListBuilder<'a> {
292 list: SubmitValueList<'a>,
293}
294
295impl<'a> ValueListBuilder<'a> {
296 pub fn new<T: Into<&'a str>, U: Into<&'a str>>(plugin: T, type_: U) -> ValueListBuilder<'a> {
299 ValueListBuilder {
300 list: SubmitValueList {
301 values: &[],
302 plugin_instance: None,
303 plugin: plugin.into(),
304 type_: type_.into(),
305 type_instance: None,
306 host: None,
307 time: None,
308 interval: None,
309 meta: HashMap::new(),
310 },
311 }
312 }
313
314 pub fn values(mut self, values: &'a [Value]) -> ValueListBuilder<'a> {
316 self.list.values = values;
317 self
318 }
319
320 pub fn plugin_instance<T: Into<&'a str>>(mut self, plugin_instance: T) -> ValueListBuilder<'a> {
323 self.list.plugin_instance = Some(plugin_instance.into());
324 self
325 }
326
327 pub fn type_instance<T: Into<&'a str>>(mut self, type_instance: T) -> ValueListBuilder<'a> {
331 self.list.type_instance = Some(type_instance.into());
332 self
333 }
334
335 pub fn host<T: Into<&'a str>>(mut self, host: T) -> ValueListBuilder<'a> {
338 self.list.host = Some(host.into());
339 self
340 }
341
342 pub fn time(mut self, dt: DateTime<Utc>) -> ValueListBuilder<'a> {
346 self.list.time = Some(dt);
347 self
348 }
349
350 pub fn interval(mut self, interval: Duration) -> ValueListBuilder<'a> {
353 self.list.interval = Some(interval);
354 self
355 }
356
357 pub fn metadata(mut self, key: &'a str, value: MetaValue) -> ValueListBuilder<'a> {
362 self.list.meta.insert(key, value);
363 self
364 }
365
366 pub fn submit(self) -> Result<(), SubmitError> {
368 let mut v: Vec<value_t> = self.list.values.iter().map(|&x| x.into()).collect();
369 let plugin_instance = self
370 .list
371 .plugin_instance
372 .map(|x| submit_array_res(x, "plugin_instance"))
373 .unwrap_or_else(|| Ok([0 as c_char; ARR_LENGTH]))?;
374
375 let type_instance = self
376 .list
377 .type_instance
378 .map(|x| submit_array_res(x, "type_instance"))
379 .unwrap_or_else(|| Ok([0 as c_char; ARR_LENGTH]))?;
380
381 let host = self
382 .list
383 .host
384 .map(|x| submit_array_res(x, "host"))
385 .transpose()?;
386
387 let host = host.unwrap_or([0 as c_char; ARR_LENGTH]);
395 let len = v.len();
396
397 let plugin = submit_array_res(self.list.plugin, "plugin")?;
398
399 let type_ = submit_array_res(self.list.type_, "type")?;
400
401 let meta = to_meta_data(&self.list.meta)?;
402
403 let list = value_list_t {
404 values: v.as_mut_ptr(),
405 values_len: len,
406 plugin_instance,
407 plugin,
408 type_,
409 type_instance,
410 host,
411 time: self.list.time.map(CdTime::from).unwrap_or(CdTime(0)).into(),
412 interval: self
413 .list
414 .interval
415 .map(CdTime::from)
416 .unwrap_or(CdTime(0))
417 .into(),
418 meta,
419 };
420
421 match unsafe { plugin_dispatch_values(&list) } {
422 0 => Ok(()),
423 i => Err(SubmitError::Dispatch(i)),
424 }
425 }
426}
427
428fn to_meta_data<'a, 'b: 'a, T>(meta_hm: T) -> Result<*mut meta_data_t, SubmitError>
429where
430 T: IntoIterator<Item = (&'a &'b str, &'a MetaValue)>,
431{
432 let meta = unsafe { meta_data_create() };
433 let conversion_result = to_meta_data_with_meta(meta_hm, meta);
434 match conversion_result {
435 Ok(()) => Ok(meta),
436 Err(error) => {
437 unsafe {
438 meta_data_destroy(meta);
439 }
440 Err(error)
441 }
442 }
443}
444
445fn to_meta_data_with_meta<'a, 'b: 'a, T>(
446 meta_hm: T,
447 meta: *mut meta_data_t,
448) -> Result<(), SubmitError>
449where
450 T: IntoIterator<Item = (&'a &'b str, &'a MetaValue)>,
451{
452 for (key, value) in meta_hm.into_iter() {
453 let c_key = CString::new(*key).map_err(|e| SubmitError::Field {
454 name: "meta key",
455 err: ArrayError::NullPresent(e.nul_position(), key.to_string()),
456 })?;
457 match value {
458 MetaValue::String(str) => {
459 let c_value = CString::new(str.as_str()).map_err(|e| SubmitError::Field {
460 name: "meta value",
461 err: ArrayError::NullPresent(e.nul_position(), str.to_string()),
462 })?;
463 unsafe {
464 meta_data_add_string(meta, c_key.as_ptr(), c_value.as_ptr());
465 }
466 }
467 MetaValue::SignedInt(i) => unsafe {
468 meta_data_add_signed_int(meta, c_key.as_ptr(), *i);
469 },
470 MetaValue::UnsignedInt(u) => unsafe {
471 meta_data_add_unsigned_int(meta, c_key.as_ptr(), *u);
472 },
473 MetaValue::Double(d) => unsafe {
474 meta_data_add_double(meta, c_key.as_ptr(), *d);
475 },
476 MetaValue::Boolean(b) => unsafe {
477 meta_data_add_boolean(meta, c_key.as_ptr(), *b);
478 },
479 }
480 }
481 Ok(())
482}
483
484fn from_meta_data(
485 plugin: &str,
486 meta: *mut meta_data_t,
487) -> Result<HashMap<String, MetaValue>, ReceiveError> {
488 if meta.is_null() {
489 return Ok(HashMap::new());
490 }
491
492 let mut c_toc: *mut *mut c_char = ptr::null_mut();
493 let count_or_err = unsafe { meta_data_toc(meta, &mut c_toc as *mut *mut *mut c_char) };
494 if count_or_err < 0 {
495 return Err(ReceiveError::Metadata {
496 plugin: plugin.to_string(),
497 field: "toc".to_string(),
498 msg: "invalid parameters to meta_data_toc",
499 });
500 }
501 let count = count_or_err as usize;
502 if count == 0 || c_toc.is_null() {
503 return Ok(HashMap::new());
504 }
505
506 let toc = unsafe { slice::from_raw_parts(c_toc, count) };
507 let conversion_result = from_meta_data_with_toc(plugin, meta, toc);
508
509 for c_key_ptr in toc {
510 unsafe {
511 libc::free(*c_key_ptr as *mut c_void);
512 }
513 }
514 unsafe {
515 libc::free(c_toc as *mut c_void);
516 }
517
518 conversion_result
519}
520
521fn from_meta_data_with_toc(
522 plugin: &str,
523 meta: *mut meta_data_t,
524 toc: &[*mut c_char],
525) -> Result<HashMap<String, MetaValue>, ReceiveError> {
526 let mut meta_hm = HashMap::with_capacity(toc.len());
527 for c_key_ptr in toc {
528 let (c_key, key, value_type) = unsafe {
529 let c_key: &CStr = CStr::from_ptr(*c_key_ptr);
530 let key: String = c_key
531 .to_str()
532 .map_err(|e| ReceiveError::Utf8 {
533 plugin: plugin.to_string(),
534 field: "metadata key",
535 err: e,
536 })?
537 .to_string();
538 let value_type: u32 = meta_data_type(meta, c_key.as_ptr()) as u32;
539 (c_key, key, value_type)
540 };
541 match value_type {
542 MD_TYPE_BOOLEAN => {
543 let mut c_value = false;
544 unsafe {
545 meta_data_get_boolean(meta, c_key.as_ptr(), &mut c_value as *mut bool);
546 }
547 meta_hm.insert(key, MetaValue::Boolean(c_value));
548 }
549 MD_TYPE_DOUBLE => {
550 let mut c_value = 0.0;
551 unsafe {
552 meta_data_get_double(meta, c_key.as_ptr(), &mut c_value as *mut f64);
553 }
554 meta_hm.insert(key, MetaValue::Double(c_value));
555 }
556 MD_TYPE_SIGNED_INT => {
557 let mut c_value = 0i64;
558 unsafe {
559 meta_data_get_signed_int(meta, c_key.as_ptr(), &mut c_value as *mut i64);
560 }
561 meta_hm.insert(key, MetaValue::SignedInt(c_value));
562 }
563 MD_TYPE_STRING => {
564 let value: String = unsafe {
565 let mut c_value: *mut c_char = ptr::null_mut();
566 meta_data_get_string(meta, c_key.as_ptr(), &mut c_value as *mut *mut c_char);
567 CStr::from_ptr(c_value)
568 .to_str()
569 .map_err(|e| ReceiveError::Utf8 {
570 plugin: plugin.to_string(),
571 field: "metadata value",
572 err: e,
573 })?
574 .to_string()
575 };
576 meta_hm.insert(key, MetaValue::String(value));
577 }
578 MD_TYPE_UNSIGNED_INT => {
579 let mut c_value = 0u64;
580 unsafe {
581 meta_data_get_unsigned_int(meta, c_key.as_ptr(), &mut c_value as *mut u64);
582 }
583 meta_hm.insert(key, MetaValue::UnsignedInt(c_value));
584 }
585 _ => {
586 return Err(ReceiveError::Metadata {
587 plugin: plugin.to_string(),
588 field: key,
589 msg: "unknown metadata type",
590 });
591 }
592 }
593 }
594 Ok(meta_hm)
595}
596
597fn submit_array_res(s: &str, name: &'static str) -> Result<[c_char; ARR_LENGTH], SubmitError> {
598 to_array_res(s).map_err(|e| SubmitError::Field { name, err: e })
599}
600
601fn to_array_res(s: &str) -> Result<[c_char; ARR_LENGTH], ArrayError> {
605 if s.len() >= ARR_LENGTH {
607 return Err(ArrayError::TooLong(s.len()));
608 }
609
610 let bytes = s.as_bytes();
611
612 if let Some(ind) = memchr(0, bytes) {
616 return Err(ArrayError::NullPresent(ind, s.to_string()));
617 }
618
619 let mut arr = [0; ARR_LENGTH];
620 arr[0..bytes.len()].copy_from_slice(bytes);
621 Ok(unsafe { ::std::mem::transmute::<[u8; ARR_LENGTH], [c_char; ARR_LENGTH]>(arr) })
622}
623
624fn receive_array<'a>(
625 s: &'a [c_char; ARR_LENGTH],
626 plugin: &str,
627 field: &'static str,
628) -> Result<&'a str, ReceiveError> {
629 from_array(s).map_err(|e| ReceiveError::Utf8 {
630 plugin: String::from(plugin),
631 field,
632 err: e,
633 })
634}
635
636pub fn from_array(s: &[c_char; ARR_LENGTH]) -> Result<&str, Utf8Error> {
638 unsafe {
639 let a = s as *const [c_char; ARR_LENGTH] as *const c_char;
640 CStr::from_ptr(a).to_str()
641 }
642}
643
644pub fn empty_to_none(s: &str) -> Option<&str> {
646 if s.is_empty() {
647 None
648 } else {
649 Some(s)
650 }
651}
652
653pub fn length(len: usize) -> usize {
654 len
655}
656
657pub fn get_default_interval() -> u64 {
658 0
659}
660
661#[cfg(test)]
662mod tests {
663 use self::cdtime::nanos_to_collectd;
664 use super::*;
665 use crate::bindings::data_source_t;
666 use std::os::raw::c_char;
667
668 #[test]
669 fn test_empty_to_none() {
670 assert_eq!(None, empty_to_none(""));
671
672 let s = "hi";
673 assert_eq!(Some("hi"), empty_to_none(s));
674 }
675
676 #[test]
677 fn test_from_array() {
678 let mut name: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
679 name[0] = b'h' as c_char;
680 name[1] = b'i' as c_char;
681 assert_eq!(Ok("hi"), from_array(&name));
682 }
683
684 #[test]
685 fn test_to_array() {
686 let actual = to_array_res("Hi");
687 assert!(actual.is_ok());
688 assert_eq!(&actual.unwrap()[..2], &[b'H' as c_char, b'i' as c_char]);
689 }
690
691 #[test]
692 fn test_to_array_res_nul() {
693 let actual = to_array_res("hi\0");
694 assert!(actual.is_err());
695 }
696
697 #[test]
698 fn test_to_array_res_too_long() {
699 let actual = to_array_res(
700 "Hello check this out, I am a long string and there is no signs of stopping; well, maybe one day I will stop when I get too longggggggggggggggggggggggggggggggggggg",
701 );
702 assert!(actual.is_err());
703 }
704
705 #[test]
706 fn test_submit() {
707 let values = vec![Value::Gauge(15.0), Value::Gauge(10.0), Value::Gauge(12.0)];
708 let result = ValueListBuilder::new("my-plugin", "load")
709 .values(&values)
710 .submit();
711 assert_eq!(result.unwrap(), ());
712 }
713
714 #[test]
715 fn test_recv_value_list_conversion() {
716 let empty: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
717 let mut metric: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
718 metric[0] = b'h' as c_char;
719 metric[1] = b'o' as c_char;
720
721 let mut name: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
722 name[0] = b'h' as c_char;
723 name[1] = b'i' as c_char;
724
725 let val = data_source_t {
726 name,
727 type_: DS_TYPE_GAUGE as i32,
728 min: 10.0,
729 max: 11.0,
730 };
731
732 let mut v = vec![val];
733
734 let conv = data_set_t {
735 type_: metric,
736 ds_num: 1,
737 ds: v.as_mut_ptr(),
738 };
739
740 let mut vs = vec![value_t { gauge: 3.0 }];
741
742 let list_t = value_list_t {
743 values: vs.as_mut_ptr(),
744 values_len: 1,
745 time: nanos_to_collectd(1_000_000_000),
746 interval: nanos_to_collectd(1_000_000_000),
747 host: metric,
748 plugin: name,
749 plugin_instance: metric,
750 type_: metric,
751 type_instance: empty,
752 meta: ptr::null_mut(),
753 };
754
755 let actual = ValueList::from(&conv, &list_t).unwrap();
756 assert_eq!(
757 actual,
758 ValueList {
759 values: vec![ValueReport {
760 name: "hi",
761 value: Value::Gauge(3.0),
762 min: 10.0,
763 max: 11.0,
764 }],
765 plugin_instance: Some("ho"),
766 plugin: "hi",
767 type_: "ho",
768 type_instance: None,
769 host: "ho",
770 time: Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1).unwrap(),
771 interval: Duration::seconds(1),
772 original_list: &list_t,
773 original_set: &conv,
774 meta: HashMap::new(),
775 }
776 );
777 }
778}