pub use self::cdtime::CdTime;
pub use self::logger::{collectd_log, log_err, CollectdLogger, CollectdLoggerBuilder, LogLevel};
pub use self::oconfig::{ConfigItem, ConfigValue};
use crate::bindings::{
data_set_t, meta_data_add_boolean, meta_data_add_double, meta_data_add_signed_int,
meta_data_add_string, meta_data_add_unsigned_int, meta_data_create, meta_data_destroy,
meta_data_get_boolean, meta_data_get_double, meta_data_get_signed_int, meta_data_get_string,
meta_data_get_unsigned_int, meta_data_t, meta_data_toc, meta_data_type, plugin_dispatch_values,
uc_get_rate, value_list_t, value_t, ARR_LENGTH, DS_TYPE_ABSOLUTE, DS_TYPE_COUNTER,
DS_TYPE_DERIVE, DS_TYPE_GAUGE, MD_TYPE_BOOLEAN, MD_TYPE_DOUBLE, MD_TYPE_SIGNED_INT,
MD_TYPE_STRING, MD_TYPE_UNSIGNED_INT,
};
use crate::errors::{ArrayError, CacheRateError, ReceiveError, SubmitError};
use chrono::prelude::*;
use chrono::Duration;
use memchr::memchr;
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::slice;
use std::str::Utf8Error;
mod cdtime;
mod logger;
mod oconfig;
#[derive(Debug, Clone, PartialEq)]
pub enum MetaValue {
String(String),
SignedInt(i64),
UnsignedInt(u64),
Double(f64),
Boolean(bool),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[allow(dead_code)]
enum ValueType {
Counter = DS_TYPE_COUNTER,
Gauge = DS_TYPE_GAUGE,
Derive = DS_TYPE_DERIVE,
Absolute = DS_TYPE_ABSOLUTE,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Value {
Counter(u64),
Gauge(f64),
Derive(i64),
Absolute(u64),
}
impl Value {
pub fn is_nan(&self) -> bool {
if let Value::Gauge(x) = *self {
x.is_nan()
} else {
false
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Value::Counter(x) | Value::Absolute(x) => write!(f, "{}", x),
Value::Gauge(x) => write!(f, "{}", x),
Value::Derive(x) => write!(f, "{}", x),
}
}
}
impl From<Value> for value_t {
fn from(x: Value) -> Self {
match x {
Value::Counter(x) => value_t { counter: x },
Value::Gauge(x) => value_t { gauge: x },
Value::Derive(x) => value_t { derive: x },
Value::Absolute(x) => value_t { absolute: x },
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct ValueReport<'a> {
pub name: &'a str,
pub value: Value,
pub min: f64,
pub max: f64,
}
#[derive(Debug, PartialEq, Clone)]
pub struct ValueList<'a> {
pub values: Vec<ValueReport<'a>>,
pub plugin: &'a str,
pub plugin_instance: Option<&'a str>,
pub type_: &'a str,
pub type_instance: Option<&'a str>,
pub host: &'a str,
pub time: DateTime<Utc>,
pub interval: Duration,
pub meta: HashMap<String, MetaValue>,
original_list: *const value_list_t,
original_set: *const data_set_t,
}
impl<'a> ValueList<'a> {
pub fn rates(&self) -> Result<Cow<'_, Vec<ValueReport<'a>>>, CacheRateError> {
let all_gauges = self
.values
.iter()
.all(|x| matches!(x.value, Value::Gauge(_)));
if all_gauges {
return Ok(Cow::Borrowed(&self.values));
}
let ptr = unsafe { uc_get_rate(self.original_set, self.original_list) };
if !ptr.is_null() {
let nv = unsafe { slice::from_raw_parts(ptr, self.values.len()) }
.iter()
.zip(self.values.iter())
.map(|(rate, report)| match report.value {
Value::Gauge(_) => *report,
_ => ValueReport {
value: Value::Gauge(*rate),
..*report
},
})
.collect();
Ok(Cow::Owned(nv))
} else {
Err(CacheRateError)
}
}
pub fn from<'b>(
set: &'b data_set_t,
list: &'b value_list_t,
) -> Result<ValueList<'b>, ReceiveError> {
let plugin = receive_array(&list.plugin, "", "plugin name")?;
let ds_len = length(set.ds_num);
let list_len = length(list.values_len);
let values = if !list.values.is_null() && !set.ds.is_null() {
unsafe { slice::from_raw_parts(list.values, list_len) }
.iter()
.zip(unsafe { slice::from_raw_parts(set.ds, ds_len) })
.map(|(val, source)| unsafe {
let v = match ::std::mem::transmute::<i32, ValueType>(source.type_) {
ValueType::Gauge => Value::Gauge(val.gauge),
ValueType::Counter => Value::Counter(val.counter),
ValueType::Derive => Value::Derive(val.derive),
ValueType::Absolute => Value::Absolute(val.absolute),
};
let name = receive_array(&source.name, plugin, "data source name")?;
Ok(ValueReport {
name,
value: v,
min: source.min,
max: source.max,
})
})
.collect::<Result<Vec<_>, _>>()?
} else {
Vec::new()
};
assert!(list.time > 0);
assert!(list.interval > 0);
let plugin_instance =
receive_array(&list.plugin_instance, plugin, "plugin_instance").map(empty_to_none)?;
let type_ = receive_array(&list.type_, plugin, "type")?;
let type_instance =
receive_array(&list.type_instance, plugin, "type_instance").map(empty_to_none)?;
let host = receive_array(&list.host, plugin, "host")?;
let meta = from_meta_data(plugin, list.meta)?;
Ok(ValueList {
values,
plugin_instance,
plugin,
type_,
type_instance,
host,
time: CdTime::from(list.time).into(),
interval: CdTime::from(list.interval).into(),
meta,
original_list: list,
original_set: set,
})
}
}
#[derive(Debug, PartialEq, Clone)]
struct SubmitValueList<'a> {
values: &'a [Value],
plugin_instance: Option<&'a str>,
plugin: &'a str,
type_: &'a str,
type_instance: Option<&'a str>,
host: Option<&'a str>,
time: Option<DateTime<Utc>>,
interval: Option<Duration>,
meta: HashMap<&'a str, MetaValue>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct ValueListBuilder<'a> {
list: SubmitValueList<'a>,
}
impl<'a> ValueListBuilder<'a> {
pub fn new<T: Into<&'a str>, U: Into<&'a str>>(plugin: T, type_: U) -> ValueListBuilder<'a> {
ValueListBuilder {
list: SubmitValueList {
values: &[],
plugin_instance: None,
plugin: plugin.into(),
type_: type_.into(),
type_instance: None,
host: None,
time: None,
interval: None,
meta: HashMap::new(),
},
}
}
pub fn values(mut self, values: &'a [Value]) -> ValueListBuilder<'a> {
self.list.values = values;
self
}
pub fn plugin_instance<T: Into<&'a str>>(mut self, plugin_instance: T) -> ValueListBuilder<'a> {
self.list.plugin_instance = Some(plugin_instance.into());
self
}
pub fn type_instance<T: Into<&'a str>>(mut self, type_instance: T) -> ValueListBuilder<'a> {
self.list.type_instance = Some(type_instance.into());
self
}
pub fn host<T: Into<&'a str>>(mut self, host: T) -> ValueListBuilder<'a> {
self.list.host = Some(host.into());
self
}
pub fn time(mut self, dt: DateTime<Utc>) -> ValueListBuilder<'a> {
self.list.time = Some(dt);
self
}
pub fn interval(mut self, interval: Duration) -> ValueListBuilder<'a> {
self.list.interval = Some(interval);
self
}
pub fn metadata(mut self, key: &'a str, value: MetaValue) -> ValueListBuilder<'a> {
self.list.meta.insert(key, value);
self
}
pub fn submit(self) -> Result<(), SubmitError> {
let mut v: Vec<value_t> = self.list.values.iter().map(|&x| x.into()).collect();
let plugin_instance = self
.list
.plugin_instance
.map(|x| submit_array_res(x, "plugin_instance"))
.unwrap_or_else(|| Ok([0 as c_char; ARR_LENGTH]))?;
let type_instance = self
.list
.type_instance
.map(|x| submit_array_res(x, "type_instance"))
.unwrap_or_else(|| Ok([0 as c_char; ARR_LENGTH]))?;
let host = self
.list
.host
.map(|x| submit_array_res(x, "host"))
.transpose()?;
let host = host.unwrap_or([0 as c_char; ARR_LENGTH]);
let len = v.len();
let plugin = submit_array_res(self.list.plugin, "plugin")?;
let type_ = submit_array_res(self.list.type_, "type")?;
let meta = to_meta_data(&self.list.meta)?;
let list = value_list_t {
values: v.as_mut_ptr(),
values_len: len,
plugin_instance,
plugin,
type_,
type_instance,
host,
time: self.list.time.map(CdTime::from).unwrap_or(CdTime(0)).into(),
interval: self
.list
.interval
.map(CdTime::from)
.unwrap_or(CdTime(0))
.into(),
meta,
};
match unsafe { plugin_dispatch_values(&list) } {
0 => Ok(()),
i => Err(SubmitError::Dispatch(i)),
}
}
}
fn to_meta_data<'a, 'b: 'a, T>(meta_hm: T) -> Result<*mut meta_data_t, SubmitError>
where
T: IntoIterator<Item = (&'a &'b str, &'a MetaValue)>,
{
let meta = unsafe { meta_data_create() };
let conversion_result = to_meta_data_with_meta(meta_hm, meta);
match conversion_result {
Ok(()) => Ok(meta),
Err(error) => {
unsafe {
meta_data_destroy(meta);
}
Err(error)
}
}
}
fn to_meta_data_with_meta<'a, 'b: 'a, T>(
meta_hm: T,
meta: *mut meta_data_t,
) -> Result<(), SubmitError>
where
T: IntoIterator<Item = (&'a &'b str, &'a MetaValue)>,
{
for (key, value) in meta_hm.into_iter() {
let c_key = CString::new(*key).map_err(|e| SubmitError::Field {
name: "meta key",
err: ArrayError::NullPresent(e.nul_position(), key.to_string()),
})?;
match value {
MetaValue::String(str) => {
let c_value = CString::new(str.as_str()).map_err(|e| SubmitError::Field {
name: "meta value",
err: ArrayError::NullPresent(e.nul_position(), str.to_string()),
})?;
unsafe {
meta_data_add_string(meta, c_key.as_ptr(), c_value.as_ptr());
}
}
MetaValue::SignedInt(i) => unsafe {
meta_data_add_signed_int(meta, c_key.as_ptr(), *i);
},
MetaValue::UnsignedInt(u) => unsafe {
meta_data_add_unsigned_int(meta, c_key.as_ptr(), *u);
},
MetaValue::Double(d) => unsafe {
meta_data_add_double(meta, c_key.as_ptr(), *d);
},
MetaValue::Boolean(b) => unsafe {
meta_data_add_boolean(meta, c_key.as_ptr(), *b);
},
}
}
Ok(())
}
fn from_meta_data(
plugin: &str,
meta: *mut meta_data_t,
) -> Result<HashMap<String, MetaValue>, ReceiveError> {
if meta.is_null() {
return Ok(HashMap::new());
}
let mut c_toc: *mut *mut c_char = ptr::null_mut();
let count_or_err = unsafe { meta_data_toc(meta, &mut c_toc as *mut *mut *mut c_char) };
if count_or_err < 0 {
return Err(ReceiveError::Metadata {
plugin: plugin.to_string(),
field: "toc".to_string(),
msg: "invalid parameters to meta_data_toc",
});
}
let count = count_or_err as usize;
if count == 0 || c_toc.is_null() {
return Ok(HashMap::new());
}
let toc = unsafe { slice::from_raw_parts(c_toc, count) };
let conversion_result = from_meta_data_with_toc(plugin, meta, toc);
for c_key_ptr in toc {
unsafe {
libc::free(*c_key_ptr as *mut c_void);
}
}
unsafe {
libc::free(c_toc as *mut c_void);
}
conversion_result
}
fn from_meta_data_with_toc(
plugin: &str,
meta: *mut meta_data_t,
toc: &[*mut c_char],
) -> Result<HashMap<String, MetaValue>, ReceiveError> {
let mut meta_hm = HashMap::with_capacity(toc.len());
for c_key_ptr in toc {
let (c_key, key, value_type) = unsafe {
let c_key: &CStr = CStr::from_ptr(*c_key_ptr);
let key: String = c_key
.to_str()
.map_err(|e| ReceiveError::Utf8 {
plugin: plugin.to_string(),
field: "metadata key",
err: e,
})?
.to_string();
let value_type: u32 = meta_data_type(meta, c_key.as_ptr()) as u32;
(c_key, key, value_type)
};
match value_type {
MD_TYPE_BOOLEAN => {
let mut c_value = false;
unsafe {
meta_data_get_boolean(meta, c_key.as_ptr(), &mut c_value as *mut bool);
}
meta_hm.insert(key, MetaValue::Boolean(c_value));
}
MD_TYPE_DOUBLE => {
let mut c_value = 0.0;
unsafe {
meta_data_get_double(meta, c_key.as_ptr(), &mut c_value as *mut f64);
}
meta_hm.insert(key, MetaValue::Double(c_value));
}
MD_TYPE_SIGNED_INT => {
let mut c_value = 0i64;
unsafe {
meta_data_get_signed_int(meta, c_key.as_ptr(), &mut c_value as *mut i64);
}
meta_hm.insert(key, MetaValue::SignedInt(c_value));
}
MD_TYPE_STRING => {
let value: String = unsafe {
let mut c_value: *mut c_char = ptr::null_mut();
meta_data_get_string(meta, c_key.as_ptr(), &mut c_value as *mut *mut c_char);
CStr::from_ptr(c_value)
.to_str()
.map_err(|e| ReceiveError::Utf8 {
plugin: plugin.to_string(),
field: "metadata value",
err: e,
})?
.to_string()
};
meta_hm.insert(key, MetaValue::String(value));
}
MD_TYPE_UNSIGNED_INT => {
let mut c_value = 0u64;
unsafe {
meta_data_get_unsigned_int(meta, c_key.as_ptr(), &mut c_value as *mut u64);
}
meta_hm.insert(key, MetaValue::UnsignedInt(c_value));
}
_ => {
return Err(ReceiveError::Metadata {
plugin: plugin.to_string(),
field: key,
msg: "unknown metadata type",
});
}
}
}
Ok(meta_hm)
}
fn submit_array_res(s: &str, name: &'static str) -> Result<[c_char; ARR_LENGTH], SubmitError> {
to_array_res(s).map_err(|e| SubmitError::Field { name, err: e })
}
fn to_array_res(s: &str) -> Result<[c_char; ARR_LENGTH], ArrayError> {
if s.len() >= ARR_LENGTH {
return Err(ArrayError::TooLong(s.len()));
}
let bytes = s.as_bytes();
if let Some(ind) = memchr(0, bytes) {
return Err(ArrayError::NullPresent(ind, s.to_string()));
}
let mut arr = [0; ARR_LENGTH];
arr[0..bytes.len()].copy_from_slice(bytes);
Ok(unsafe { ::std::mem::transmute::<[u8; ARR_LENGTH], [c_char; ARR_LENGTH]>(arr) })
}
fn receive_array<'a>(
s: &'a [c_char; ARR_LENGTH],
plugin: &str,
field: &'static str,
) -> Result<&'a str, ReceiveError> {
from_array(s).map_err(|e| ReceiveError::Utf8 {
plugin: String::from(plugin),
field,
err: e,
})
}
pub fn from_array(s: &[c_char; ARR_LENGTH]) -> Result<&str, Utf8Error> {
unsafe {
let a = s as *const [c_char; ARR_LENGTH] as *const c_char;
CStr::from_ptr(a).to_str()
}
}
pub fn empty_to_none(s: &str) -> Option<&str> {
if s.is_empty() {
None
} else {
Some(s)
}
}
pub fn length(len: usize) -> usize {
len
}
pub fn get_default_interval() -> u64 {
0
}
#[cfg(test)]
mod tests {
use self::cdtime::nanos_to_collectd;
use super::*;
use crate::bindings::data_source_t;
use std::os::raw::c_char;
#[test]
fn test_empty_to_none() {
assert_eq!(None, empty_to_none(""));
let s = "hi";
assert_eq!(Some("hi"), empty_to_none(s));
}
#[test]
fn test_from_array() {
let mut name: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
name[0] = b'h' as c_char;
name[1] = b'i' as c_char;
assert_eq!(Ok("hi"), from_array(&name));
}
#[test]
fn test_to_array() {
let actual = to_array_res("Hi");
assert!(actual.is_ok());
assert_eq!(&actual.unwrap()[..2], &[b'H' as c_char, b'i' as c_char]);
}
#[test]
fn test_to_array_res_nul() {
let actual = to_array_res("hi\0");
assert!(actual.is_err());
}
#[test]
fn test_to_array_res_too_long() {
let actual = to_array_res(
"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",
);
assert!(actual.is_err());
}
#[test]
fn test_submit() {
let values = vec![Value::Gauge(15.0), Value::Gauge(10.0), Value::Gauge(12.0)];
let result = ValueListBuilder::new("my-plugin", "load")
.values(&values)
.submit();
assert_eq!(result.unwrap(), ());
}
#[test]
fn test_recv_value_list_conversion() {
let empty: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
let mut metric: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
metric[0] = b'h' as c_char;
metric[1] = b'o' as c_char;
let mut name: [c_char; ARR_LENGTH] = [0; ARR_LENGTH];
name[0] = b'h' as c_char;
name[1] = b'i' as c_char;
let val = data_source_t {
name,
type_: DS_TYPE_GAUGE as i32,
min: 10.0,
max: 11.0,
};
let mut v = vec![val];
let conv = data_set_t {
type_: metric,
ds_num: 1,
ds: v.as_mut_ptr(),
};
let mut vs = vec![value_t { gauge: 3.0 }];
let list_t = value_list_t {
values: vs.as_mut_ptr(),
values_len: 1,
time: nanos_to_collectd(1_000_000_000),
interval: nanos_to_collectd(1_000_000_000),
host: metric,
plugin: name,
plugin_instance: metric,
type_: metric,
type_instance: empty,
meta: ptr::null_mut(),
};
let actual = ValueList::from(&conv, &list_t).unwrap();
assert_eq!(
actual,
ValueList {
values: vec![ValueReport {
name: "hi",
value: Value::Gauge(3.0),
min: 10.0,
max: 11.0,
}],
plugin_instance: Some("ho"),
plugin: "hi",
type_: "ho",
type_instance: None,
host: "ho",
time: Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1).unwrap(),
interval: Duration::seconds(1),
original_list: &list_t,
original_set: &conv,
meta: HashMap::new(),
}
);
}
}