collectd_plugin/api/
mod.rs

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/// The value of a metadata entry associated with a [ValueList].
31/// Metadata can be added using [ValueListBuilder::metadata] method.
32#[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/// The value that a plugin reports can be any one of the following types
52#[derive(Debug, Clone, Copy, PartialEq)]
53pub enum Value {
54    /// A COUNTER value is for continuous incrementing counters like the ifInOctets counter in a router.
55    /// The COUNTER data source assumes that the observed value never decreases, except when it
56    /// overflows. The update function takes the overflow into account. If a counter is reset to
57    /// zero, for example because an application was restarted, the wrap-around calculation may
58    /// result in a huge rate. Thus setting a reasonable maximum value is essential when using
59    /// COUNTER data sources. Because of this, COUNTER data sources are only recommended for
60    /// counters that wrap-around often, for example 32 bit octet counters of a busy switch port.
61    Counter(u64),
62
63    /// A GAUGE value is simply stored as-is. This is the right choice for values which may
64    /// increase as well as decrease, such as temperatures or the amount of memory used
65    Gauge(f64),
66
67    /// DERIVE will store the derivative of the observed values source. If the data type has a
68    /// minimum of zero, negative rates will be discarded. Using DERIVE is a good idea for
69    /// measuring cgroup's cpuacct.usage as that stores the total number of CPU nanoseconds by all
70    /// tasks in the cgroup; the change (derivative) in CPU nanoseconds is more interesting than
71    /// the current value.
72    Derive(i64),
73
74    /// ABSOLUTE is for counters which get reset upon reading. This is used for fast counters which
75    /// tend to overflow. So instead of reading them normally you reset them after every read to
76    /// make sure you have a maximum time available before the next overflow.
77    Absolute(u64),
78}
79
80impl Value {
81    /// Returns if an underlying value is nan
82    ///
83    /// ```
84    /// # use collectd_plugin::Value;
85    /// assert_eq!(true, Value::Gauge(::std::f64::NAN).is_nan());
86    /// assert_eq!(false, Value::Gauge(0.0).is_nan());
87    /// assert_eq!(false, Value::Derive(0).is_nan());
88    /// ```
89    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/// Name and value of a reported metric
120#[derive(Debug, PartialEq, Clone, Copy)]
121pub struct ValueReport<'a> {
122    /// Name of the metric. If values has a length of 1, this is often just "value"
123    pub name: &'a str,
124
125    /// The value reported
126    pub value: Value,
127
128    /// Minimum value seen in an interval
129    pub min: f64,
130
131    /// Maximum value seen in an interval
132    pub max: f64,
133}
134
135/// Contains values and metadata that collectd has collected from plugins
136#[derive(Debug, PartialEq, Clone)]
137pub struct ValueList<'a> {
138    pub values: Vec<ValueReport<'a>>,
139
140    /// The plugin that submitted this value. This would be your `PluginManager` when submitting
141    /// values
142    pub plugin: &'a str,
143
144    /// Distinguishes entities that yield metrics. Each core would be a different instance of the
145    /// same plugin, as each core reports "idle", "user", "system" metrics.
146    pub plugin_instance: Option<&'a str>,
147
148    /// This is the string found in types.db, determines how many values are expected and how they
149    /// should be interpreted
150    pub type_: &'a str,
151
152    /// The type instance is used to separate values of identical type which nonetheless belong to
153    /// one another. For instance, even though "free", "used", and "total" all have types of
154    /// "Memory" they are different type instances.
155    pub type_instance: Option<&'a str>,
156
157    /// The hostname where the values were collectd
158    pub host: &'a str,
159
160    /// The timestamp at which the value was collected
161    pub time: DateTime<Utc>,
162
163    /// The interval in which new values are to be expected
164    pub interval: Duration,
165
166    /// Metadata associated to the reported values
167    pub meta: HashMap<String, MetaValue>,
168
169    // Keep the original list and set around for calculating rates on demand
170    original_list: *const value_list_t,
171    original_set: *const data_set_t,
172}
173
174impl<'a> ValueList<'a> {
175    /// Collectd does not automatically convert `Derived` values into a rate. This is why many
176    /// write plugins have a `StoreRates` config option so that these rates are calculated on
177    /// demand from collectd's internal cache. This function will return a vector that can supercede
178    /// the `values` field that contains the rate of all non-gauge values. Values that are gauges
179    /// remain unchanged, so one doesn't need to resort back to `values` field as this function
180    /// will return everything prepped for submission.
181    pub fn rates(&self) -> Result<Cow<'_, Vec<ValueReport<'a>>>, CacheRateError> {
182        // As an optimization step, if we know all values are gauges there is no need to call out
183        // to uc_get_rate as no values will be changed
184        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/// Creates a value list to report values to collectd.
290#[derive(Debug, PartialEq, Clone)]
291pub struct ValueListBuilder<'a> {
292    list: SubmitValueList<'a>,
293}
294
295impl<'a> ValueListBuilder<'a> {
296    /// Primes a value list for submission. `plugin` will most likely be the name from the
297    /// `PluginManager` and `type_` is the datatype found in types.db
298    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    /// A set of observed values that belong to the same plugin and type instance
315    pub fn values(mut self, values: &'a [Value]) -> ValueListBuilder<'a> {
316        self.list.values = values;
317        self
318    }
319
320    /// Distinguishes entities that yield metrics. Each core would be a different instance of the
321    /// same plugin, as each core reports "idle", "user", "system" metrics.
322    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    /// The type instance is used to separate values of identical type which nonetheless belong to
328    /// one another. For instance, even though "free", "used", and "total" all have types of
329    /// "Memory" they are different type instances.
330    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    /// Override the machine's hostname that the observed values will be attributed to. Best to
336    /// override when observing values from another machine
337    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    /// The timestamp at which the value was collected. Overrides the default time, which is when
343    /// collectd receives the values from `submit`. Use only if there is a significant delay is
344    /// metrics gathering or if submitting values from the past.
345    pub fn time(mut self, dt: DateTime<Utc>) -> ValueListBuilder<'a> {
346        self.list.time = Some(dt);
347        self
348    }
349
350    /// The interval in which new values are to be expected. This is typically handled at a global
351    /// or plugin level. Use at your own discretion.
352    pub fn interval(mut self, interval: Duration) -> ValueListBuilder<'a> {
353        self.list.interval = Some(interval);
354        self
355    }
356
357    /// Add a metadata entry.
358    ///
359    /// Multiple entries can be added by calling this method. If the same key is used, only the last
360    /// entry is kept.
361    pub fn metadata(mut self, key: &'a str, value: MetaValue) -> ValueListBuilder<'a> {
362        self.list.meta.insert(key, value);
363        self
364    }
365
366    /// Submits the observed values to collectd and returns errors if encountered
367    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        // If a custom host is not provided by the plugin, we default to the global
388        // hostname. In versions prior to collectd 5.7, it was required to propagate the
389        // global hostname (hostname_g) in the submission. In collectd 5.7, one could
390        // submit an empty array or hostname_g and they would equate to the same thing. In
391        // collectd 5.8, hostname_g had the type signature changed so it could no longer be
392        // submitted and would cause garbage to be read (and thus could have very much
393        // unintended side effects)
394        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
601/// Collectd stores textual data in fixed sized arrays, so this function will convert a string
602/// slice into array compatible with collectd's text fields. Be aware that `ARR_LENGTH` is 64
603/// before collectd 5.7
604fn to_array_res(s: &str) -> Result<[c_char; ARR_LENGTH], ArrayError> {
605    // By checking if the length is greater than or *equal* to, we guarantee a trailing null
606    if s.len() >= ARR_LENGTH {
607        return Err(ArrayError::TooLong(s.len()));
608    }
609
610    let bytes = s.as_bytes();
611
612    // Using memchr to find a null and work around it is 10x faster than
613    // using a CString to get the bytes_with_nul and cut the time to submit
614    // values to collectd in half.
615    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
636/// Turns a fixed size character array into string slice, if possible
637pub 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
644/// Returns if the string is empty or not
645pub 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}