From 05025b17bae6abe8da7522174af7ec2ad86a6db5 Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 15 Sep 2022 09:39:56 +0200 Subject: [PATCH 01/20] Convert tags into attributes. --- src/sinks/new_relic/model.rs | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 74d4b6d905143..4021138f8ef14 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, convert::TryFrom, fmt::Debug, time::SystemTime}; +use std::{collections::{HashMap, BTreeMap}, convert::TryFrom, fmt::Debug, time::SystemTime}; use chrono::{DateTime, Utc}; use ordered_float::NotNan; @@ -21,12 +21,15 @@ type DataStore = HashMap>; pub struct MetricsApiModel(pub Vec); impl MetricsApiModel { - pub fn new(metric_array: Vec<(Value, Value, Value)>) -> Self { + pub fn new(metric_array: Vec<(Value, Value, Value, Option)>) -> Self { let mut metric_data_array = vec![]; - for (m_name, m_value, m_timestamp) in metric_array { + for (m_name, m_value, m_timestamp, m_attributes) in metric_array { let mut metric_data = KeyValData::new(); metric_data.insert("name".to_owned(), m_name); metric_data.insert("value".to_owned(), m_value); + if let Some(attr) = m_attributes { + metric_data.insert("attributes".to_owned(), attr); + } match m_timestamp { Value::Timestamp(ts) => { metric_data.insert("timestamp".to_owned(), Value::from(ts.timestamp())); @@ -57,30 +60,31 @@ impl TryFrom> for MetricsApiModel { for buf_event in buf_events { if let Event::Metric(metric) = buf_event { - // Future improvement: put metric type. If type = count, NR metric model requiere an interval.ms field, that is not provided by the Vector Metric model. - match metric.value() { - MetricValue::Gauge { value } => { - metric_array.push(( - Value::from(metric.name().to_owned()), - Value::from( - NotNan::new(*value).map_err(|_| { - NewRelicSinkError::new("NaN value not supported") - })?, - ), - Value::from(metric.timestamp()), - )); + // Generate BTreeMap from &BTreeMap + let attr = if let Some(tags) = metric.tags() { + let mut bt = BTreeMap::new(); + for (key, value) in tags { + bt.insert(key.clone(), Value::from(value.clone())); } - MetricValue::Counter { value } => { + Some(Value::from(bt)) + } + else { + None + }; + + match metric.value() { + MetricValue::Gauge { value } | MetricValue::Counter { value } => { metric_array.push(( Value::from(metric.name().to_owned()), Value::from( NotNan::new(*value).map_err(|_| { - NewRelicSinkError::new("NaN value not supported") + NewRelicSinkError::new(format!("NaN value not supported, metric name: {} , value: {}", metric.name(), *value).as_str()) })?, ), Value::from(metric.timestamp()), + attr, )); - } + }, _ => { // Unrecognized metric type } From 087d4bf1724d7da67bc6e8f77fc1b3abe759955c Mon Sep 17 00:00:00 2001 From: Andreu Date: Fri, 16 Sep 2022 15:10:45 +0200 Subject: [PATCH 02/20] Decompose metric into parts to reuse tags instead of cloning. --- src/sinks/new_relic/model.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 4021138f8ef14..23bedbb096b69 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -60,11 +60,12 @@ impl TryFrom> for MetricsApiModel { for buf_event in buf_events { if let Event::Metric(metric) = buf_event { - // Generate BTreeMap from &BTreeMap - let attr = if let Some(tags) = metric.tags() { + // Generate Value::Object() from BTreeMap + let (series, data, _) = metric.into_parts(); + let attr = if let Some(tags) = series.tags { let mut bt = BTreeMap::new(); for (key, value) in tags { - bt.insert(key.clone(), Value::from(value.clone())); + bt.insert(key, Value::from(value)); } Some(Value::from(bt)) } @@ -72,16 +73,16 @@ impl TryFrom> for MetricsApiModel { None }; - match metric.value() { + match data.value { MetricValue::Gauge { value } | MetricValue::Counter { value } => { metric_array.push(( - Value::from(metric.name().to_owned()), + Value::from(series.name.name), Value::from( - NotNan::new(*value).map_err(|_| { - NewRelicSinkError::new(format!("NaN value not supported, metric name: {} , value: {}", metric.name(), *value).as_str()) + NotNan::new(value).map_err(|_| { + NewRelicSinkError::new("NaN value not supported") })?, ), - Value::from(metric.timestamp()), + Value::from(data.time.timestamp), attr, )); }, From 2dd26681a3c203f5bcf8860a963bb6e80e2595ae Mon Sep 17 00:00:00 2001 From: Andreu Date: Fri, 16 Sep 2022 16:16:30 +0200 Subject: [PATCH 03/20] Remove unnecessary blocks. --- src/sinks/new_relic/sink.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sinks/new_relic/sink.rs b/src/sinks/new_relic/sink.rs index 3af42c2347022..4d73be56ebe87 100644 --- a/src/sinks/new_relic/sink.rs +++ b/src/sinks/new_relic/sink.rs @@ -100,12 +100,8 @@ impl RequestBuilder> for NewRelicRequestBuilder { let finalizers = input.take_finalizers(); let api_model = || -> Result { match self.credentials.api { - NewRelicApi::Events => { - Ok(NewRelicApiModel::Events(EventsApiModel::try_from(input)?)) - } - NewRelicApi::Metrics => { - Ok(NewRelicApiModel::Metrics(MetricsApiModel::try_from(input)?)) - } + NewRelicApi::Events => Ok(NewRelicApiModel::Events(EventsApiModel::try_from(input)?)), + NewRelicApi::Metrics => Ok(NewRelicApiModel::Metrics(MetricsApiModel::try_from(input)?)), NewRelicApi::Logs => Ok(NewRelicApiModel::Logs(LogsApiModel::try_from(input)?)), } }(); From 6020efb9b8a3bcefae9816e4ea23c81fc5c60c60 Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 22 Sep 2022 16:42:50 +0200 Subject: [PATCH 04/20] Set metric type. --- src/sinks/new_relic/model.rs | 85 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 23bedbb096b69..1ead672f12cf6 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -5,7 +5,7 @@ use ordered_float::NotNan; use serde::{Deserialize, Serialize}; use super::NewRelicSinkError; -use crate::event::{Event, MetricValue, Value}; +use crate::event::{Event, MetricValue, Value, MetricKind}; #[derive(Debug)] pub enum NewRelicApiModel { @@ -21,33 +21,9 @@ type DataStore = HashMap>; pub struct MetricsApiModel(pub Vec); impl MetricsApiModel { - pub fn new(metric_array: Vec<(Value, Value, Value, Option)>) -> Self { - let mut metric_data_array = vec![]; - for (m_name, m_value, m_timestamp, m_attributes) in metric_array { - let mut metric_data = KeyValData::new(); - metric_data.insert("name".to_owned(), m_name); - metric_data.insert("value".to_owned(), m_value); - if let Some(attr) = m_attributes { - metric_data.insert("attributes".to_owned(), attr); - } - match m_timestamp { - Value::Timestamp(ts) => { - metric_data.insert("timestamp".to_owned(), Value::from(ts.timestamp())); - } - Value::Integer(i) => { - metric_data.insert("timestamp".to_owned(), Value::from(i)); - } - _ => { - metric_data.insert( - "timestamp".to_owned(), - Value::from(DateTime::::from(SystemTime::now()).timestamp()), - ); - } - } - metric_data_array.push(metric_data); - } + pub fn new(metric_array: Vec) -> Self { let mut metric_store = DataStore::new(); - metric_store.insert("metrics".to_owned(), metric_data_array); + metric_store.insert("metrics".to_owned(), metric_array); Self(vec![metric_store]) } } @@ -73,28 +49,51 @@ impl TryFrom> for MetricsApiModel { None }; - match data.value { - MetricValue::Gauge { value } | MetricValue::Counter { value } => { - metric_array.push(( - Value::from(series.name.name), - Value::from( - NotNan::new(value).map_err(|_| { - NewRelicSinkError::new("NaN value not supported") - })?, - ), - Value::from(data.time.timestamp), - attr, - )); - }, - _ => { - // Unrecognized metric type + let mut metric_data = KeyValData::new(); + + if let MetricValue::Gauge { value } | MetricValue::Counter { value } = data.value { + metric_data.insert("name".to_owned(), Value::from(series.name.name)); + metric_data.insert("value".to_owned(), Value::from( + NotNan::new(value).map_err(|_| { + NewRelicSinkError::new("NaN value not supported") + })?, + )); + metric_data.insert("timestamp".to_owned(), + if let Some(ts) = data.time.timestamp { + Value::from(ts.timestamp()) + } + else { + Value::from(DateTime::::from(SystemTime::now()).timestamp()) + } + ); + if let Some(attr) = attr { + metric_data.insert("atttributes".to_owned(), attr); } } + + match (data.value, data.kind) { + (MetricValue::Counter { .. }, MetricKind::Incremental) => { + if let Some(interval_ms) = data.time.interval_ms { + metric_data.insert("interval.ms".to_owned(), Value::from(interval_ms.get() as i64)); + } + else { + // Incremental counter without an interval is worthless, skip this metric + continue; + } + metric_data.insert("type".to_owned(), Value::from("count")); + }, + (MetricValue::Gauge { .. } | MetricValue::Counter { .. }, MetricKind::Absolute) => { + metric_data.insert("type".to_owned(), Value::from("gauge")); + }, + _ => {} + } + + metric_array.push(metric_data); } } if !metric_array.is_empty() { - Ok(MetricsApiModel::new(metric_array)) + Ok(Self::new(metric_array)) } else { Err(NewRelicSinkError::new("No valid metrics to generate")) } From 9e7d57c43f31c8ad6acb126f75983b4a173a596e Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 22 Sep 2022 16:49:22 +0200 Subject: [PATCH 05/20] Fix field name typo. --- src/sinks/new_relic/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 1ead672f12cf6..e8c1484bc4462 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -67,7 +67,7 @@ impl TryFrom> for MetricsApiModel { } ); if let Some(attr) = attr { - metric_data.insert("atttributes".to_owned(), attr); + metric_data.insert("attributes".to_owned(), attr); } } From 567f2368a7275a86810c1652a5c792fcb5292571 Mon Sep 17 00:00:00 2001 From: Andreu Date: Fri, 23 Sep 2022 12:22:52 +0200 Subject: [PATCH 06/20] Remove unnecessary brackets. --- src/sinks/new_relic/config.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sinks/new_relic/config.rs b/src/sinks/new_relic/config.rs index 385ffe41d92f9..766a28dadb78b 100644 --- a/src/sinks/new_relic/config.rs +++ b/src/sinks/new_relic/config.rs @@ -210,9 +210,7 @@ impl NewRelicCredentials { }, NewRelicApi::Metrics => match self.region { NewRelicRegion::Us => Uri::from_static("https://metric-api.newrelic.com/metric/v1"), - NewRelicRegion::Eu => { - Uri::from_static("https://metric-api.eu.newrelic.com/metric/v1") - } + NewRelicRegion::Eu => Uri::from_static("https://metric-api.eu.newrelic.com/metric/v1"), }, NewRelicApi::Logs => match self.region { NewRelicRegion::Us => Uri::from_static("https://log-api.newrelic.com/log/v1"), From b7557695e58a6990fda7a4e673a8a11514e16f92 Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 20 Oct 2022 10:04:42 +0200 Subject: [PATCH 07/20] Cargo fmt. --- src/sinks/new_relic/config.rs | 4 ++- src/sinks/new_relic/model.rs | 49 +++++++++++++++++++++-------------- src/sinks/new_relic/sink.rs | 8 ++++-- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/sinks/new_relic/config.rs b/src/sinks/new_relic/config.rs index 766a28dadb78b..385ffe41d92f9 100644 --- a/src/sinks/new_relic/config.rs +++ b/src/sinks/new_relic/config.rs @@ -210,7 +210,9 @@ impl NewRelicCredentials { }, NewRelicApi::Metrics => match self.region { NewRelicRegion::Us => Uri::from_static("https://metric-api.newrelic.com/metric/v1"), - NewRelicRegion::Eu => Uri::from_static("https://metric-api.eu.newrelic.com/metric/v1"), + NewRelicRegion::Eu => { + Uri::from_static("https://metric-api.eu.newrelic.com/metric/v1") + } }, NewRelicApi::Logs => match self.region { NewRelicRegion::Us => Uri::from_static("https://log-api.newrelic.com/log/v1"), diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index e8c1484bc4462..a01bdc7f32ac7 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -1,11 +1,16 @@ -use std::{collections::{HashMap, BTreeMap}, convert::TryFrom, fmt::Debug, time::SystemTime}; +use std::{ + collections::{BTreeMap, HashMap}, + convert::TryFrom, + fmt::Debug, + time::SystemTime, +}; use chrono::{DateTime, Utc}; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; use super::NewRelicSinkError; -use crate::event::{Event, MetricValue, Value, MetricKind}; +use crate::event::{Event, MetricKind, MetricValue, Value}; #[derive(Debug)] pub enum NewRelicApiModel { @@ -44,8 +49,7 @@ impl TryFrom> for MetricsApiModel { bt.insert(key, Value::from(value)); } Some(Value::from(bt)) - } - else { + } else { None }; @@ -53,18 +57,20 @@ impl TryFrom> for MetricsApiModel { if let MetricValue::Gauge { value } | MetricValue::Counter { value } = data.value { metric_data.insert("name".to_owned(), Value::from(series.name.name)); - metric_data.insert("value".to_owned(), Value::from( - NotNan::new(value).map_err(|_| { - NewRelicSinkError::new("NaN value not supported") - })?, - )); - metric_data.insert("timestamp".to_owned(), + metric_data.insert( + "value".to_owned(), + Value::from( + NotNan::new(value) + .map_err(|_| NewRelicSinkError::new("NaN value not supported"))?, + ), + ); + metric_data.insert( + "timestamp".to_owned(), if let Some(ts) = data.time.timestamp { Value::from(ts.timestamp()) - } - else { + } else { Value::from(DateTime::::from(SystemTime::now()).timestamp()) - } + }, ); if let Some(attr) = attr { metric_data.insert("attributes".to_owned(), attr); @@ -74,17 +80,22 @@ impl TryFrom> for MetricsApiModel { match (data.value, data.kind) { (MetricValue::Counter { .. }, MetricKind::Incremental) => { if let Some(interval_ms) = data.time.interval_ms { - metric_data.insert("interval.ms".to_owned(), Value::from(interval_ms.get() as i64)); - } - else { + metric_data.insert( + "interval.ms".to_owned(), + Value::from(interval_ms.get() as i64), + ); + } else { // Incremental counter without an interval is worthless, skip this metric continue; } metric_data.insert("type".to_owned(), Value::from("count")); - }, - (MetricValue::Gauge { .. } | MetricValue::Counter { .. }, MetricKind::Absolute) => { + } + ( + MetricValue::Gauge { .. } | MetricValue::Counter { .. }, + MetricKind::Absolute, + ) => { metric_data.insert("type".to_owned(), Value::from("gauge")); - }, + } _ => {} } diff --git a/src/sinks/new_relic/sink.rs b/src/sinks/new_relic/sink.rs index 4d73be56ebe87..3af42c2347022 100644 --- a/src/sinks/new_relic/sink.rs +++ b/src/sinks/new_relic/sink.rs @@ -100,8 +100,12 @@ impl RequestBuilder> for NewRelicRequestBuilder { let finalizers = input.take_finalizers(); let api_model = || -> Result { match self.credentials.api { - NewRelicApi::Events => Ok(NewRelicApiModel::Events(EventsApiModel::try_from(input)?)), - NewRelicApi::Metrics => Ok(NewRelicApiModel::Metrics(MetricsApiModel::try_from(input)?)), + NewRelicApi::Events => { + Ok(NewRelicApiModel::Events(EventsApiModel::try_from(input)?)) + } + NewRelicApi::Metrics => { + Ok(NewRelicApiModel::Metrics(MetricsApiModel::try_from(input)?)) + } NewRelicApi::Logs => Ok(NewRelicApiModel::Logs(LogsApiModel::try_from(input)?)), } }(); From 48f16d87d58989bdd6bbf85e2cad62b5056baf0b Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 3 Nov 2022 12:43:23 +0100 Subject: [PATCH 08/20] Update src/sinks/new_relic/model.rs Co-authored-by: Bruce Guenter --- src/sinks/new_relic/model.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index a01bdc7f32ac7..1d429a414a5e6 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -43,15 +43,12 @@ impl TryFrom> for MetricsApiModel { if let Event::Metric(metric) = buf_event { // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); - let attr = if let Some(tags) = series.tags { - let mut bt = BTreeMap::new(); - for (key, value) in tags { - bt.insert(key, Value::from(value)); - } - Some(Value::from(bt)) - } else { - None - }; + let attr = series.tags.map(|tags| { + Value::from(tags + .into_iter() + .map(|(key, value)| (key, Value::from(value))) + .collect::()) + }); let mut metric_data = KeyValData::new(); From af14a7d2c523f023585cb41b35d1f8eb79e38fbd Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 3 Nov 2022 12:43:54 +0100 Subject: [PATCH 09/20] Update src/sinks/new_relic/model.rs Co-authored-by: Bruce Guenter --- src/sinks/new_relic/model.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 1d429a414a5e6..818e65b16a53f 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -63,11 +63,11 @@ impl TryFrom> for MetricsApiModel { ); metric_data.insert( "timestamp".to_owned(), - if let Some(ts) = data.time.timestamp { - Value::from(ts.timestamp()) - } else { - Value::from(DateTime::::from(SystemTime::now()).timestamp()) - }, + Value::from(data + .time + .timestamp + .unwrap_or_else(|| DateTime::::from(SystemTime::now())) + .timestamp()), ); if let Some(attr) = attr { metric_data.insert("attributes".to_owned(), attr); From 137943c8246a868d3cebf13a48ddcc8dcb220c9e Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 3 Nov 2022 12:46:30 +0100 Subject: [PATCH 10/20] Fix typo. --- src/sinks/new_relic/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 818e65b16a53f..2da0ff086031c 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -47,7 +47,7 @@ impl TryFrom> for MetricsApiModel { Value::from(tags .into_iter() .map(|(key, value)| (key, Value::from(value))) - .collect::()) + .collect::>()) }); let mut metric_data = KeyValData::new(); From 2a2282c9dfb2af9dfb03af579eab1bf547b2a658 Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 3 Nov 2022 14:33:51 +0100 Subject: [PATCH 11/20] Add tests for newly supported metrics. --- src/sinks/new_relic/tests.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/sinks/new_relic/tests.rs b/src/sinks/new_relic/tests.rs index 8b35dadd16cfd..8677215235be4 100644 --- a/src/sinks/new_relic/tests.rs +++ b/src/sinks/new_relic/tests.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, convert::TryFrom, time::SystemTime}; +use std::{collections::HashMap, convert::TryFrom, time::SystemTime, num::NonZeroU32}; use chrono::{DateTime, Utc}; use futures::{future::ready, stream}; @@ -176,7 +176,7 @@ fn generate_metric_api_model() { MetricsApiModel::try_from(vec![event]).expect("Failed mapping metrics into API model"); let metrics = model.0[0] .get("metrics") - .expect("Logs data store not present"); + .expect("Metric data store not present"); assert_eq!(metrics.len(), 1); assert!(metrics[0].get("name").is_some()); @@ -200,7 +200,7 @@ fn generate_metric_api_model() { MetricsApiModel::try_from(vec![event]).expect("Failed mapping metrics into API model"); let metrics = model.0[0] .get("metrics") - .expect("Logs data store not present"); + .expect("Metric data store not present"); assert_eq!(metrics.len(), 1); assert!(metrics[0].get("name").is_some()); @@ -211,4 +211,31 @@ fn generate_metric_api_model() { assert!(metrics[0].get("value").is_some()); assert_eq!(metrics[0].get("value").unwrap(), &Value::from(100.0)); assert!(metrics[0].get("timestamp").is_some()); + + // Incremental counter + let m = Metric::new( + "my_metric", + MetricKind::Incremental, + MetricValue::Counter { value: 100.0 }, + ) + .with_timestamp(Some(DateTime::::from(SystemTime::now()))) + .with_interval_ms(NonZeroU32::new(1000)); + let event = Event::Metric(m); + let model = + MetricsApiModel::try_from(vec![event]).expect("Failed mapping metrics into API model"); + let metrics = model.0[0] + .get("metrics") + .expect("Metric data store not present"); + + assert_eq!(metrics.len(), 1); + assert!(metrics[0].get("name").is_some()); + assert_eq!( + metrics[0].get("name").unwrap().to_string_lossy(), + "my_metric".to_owned() + ); + assert!(metrics[0].get("value").is_some()); + assert_eq!(metrics[0].get("value").unwrap(), &Value::from(100.0)); + assert!(metrics[0].get("timestamp").is_some()); + assert!(metrics[0].get("interval.ms").is_some()); + assert_eq!(metrics[0].get("interval.ms").unwrap(), &Value::from(1000)); } From b72faedb1def6caf90f3fc1dd4177a0741e73995 Mon Sep 17 00:00:00 2001 From: Andreu Date: Thu, 3 Nov 2022 14:54:31 +0100 Subject: [PATCH 12/20] Simplify metric type cases. --- src/sinks/new_relic/model.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 2da0ff086031c..b332d911dcc4a 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -50,9 +50,10 @@ impl TryFrom> for MetricsApiModel { .collect::>()) }); - let mut metric_data = KeyValData::new(); - + // We only handle gauge and counter metrics if let MetricValue::Gauge { value } | MetricValue::Counter { value } = data.value { + let mut metric_data = KeyValData::new(); + // Set name, value, and timestamp metric_data.insert("name".to_owned(), Value::from(series.name.name)); metric_data.insert( "value".to_owned(), @@ -72,10 +73,8 @@ impl TryFrom> for MetricsApiModel { if let Some(attr) = attr { metric_data.insert("attributes".to_owned(), attr); } - } - - match (data.value, data.kind) { - (MetricValue::Counter { .. }, MetricKind::Incremental) => { + // Set type and type related attributes + if let (MetricValue::Counter { .. }, MetricKind::Incremental) = (data.value, data.kind) { if let Some(interval_ms) = data.time.interval_ms { metric_data.insert( "interval.ms".to_owned(), @@ -86,17 +85,13 @@ impl TryFrom> for MetricsApiModel { continue; } metric_data.insert("type".to_owned(), Value::from("count")); - } - ( - MetricValue::Gauge { .. } | MetricValue::Counter { .. }, - MetricKind::Absolute, - ) => { + } else { + // Anything that's not an incremental counter is considered a gauge, that is gauge and absolute counters metrics. metric_data.insert("type".to_owned(), Value::from("gauge")); } - _ => {} - } - metric_array.push(metric_data); + metric_array.push(metric_data); + } } } From 41b41ab95caaa36368aadb8e5f994c865f0b7be0 Mon Sep 17 00:00:00 2001 From: Andreu Date: Mon, 12 Dec 2022 16:31:41 +0100 Subject: [PATCH 13/20] Cargo fmt. --- src/sinks/new_relic/model.rs | 24 ++++++++++++++---------- src/sinks/new_relic/tests.rs | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index b332d911dcc4a..f647a93359e4f 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -44,10 +44,11 @@ impl TryFrom> for MetricsApiModel { // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); let attr = series.tags.map(|tags| { - Value::from(tags - .into_iter() - .map(|(key, value)| (key, Value::from(value))) - .collect::>()) + Value::from( + tags.into_iter() + .map(|(key, value)| (key, Value::from(value))) + .collect::>(), + ) }); // We only handle gauge and counter metrics @@ -64,17 +65,20 @@ impl TryFrom> for MetricsApiModel { ); metric_data.insert( "timestamp".to_owned(), - Value::from(data - .time - .timestamp - .unwrap_or_else(|| DateTime::::from(SystemTime::now())) - .timestamp()), + Value::from( + data.time + .timestamp + .unwrap_or_else(|| DateTime::::from(SystemTime::now())) + .timestamp(), + ), ); if let Some(attr) = attr { metric_data.insert("attributes".to_owned(), attr); } // Set type and type related attributes - if let (MetricValue::Counter { .. }, MetricKind::Incremental) = (data.value, data.kind) { + if let (MetricValue::Counter { .. }, MetricKind::Incremental) = + (data.value, data.kind) + { if let Some(interval_ms) = data.time.interval_ms { metric_data.insert( "interval.ms".to_owned(), diff --git a/src/sinks/new_relic/tests.rs b/src/sinks/new_relic/tests.rs index 8677215235be4..a99609b3a2327 100644 --- a/src/sinks/new_relic/tests.rs +++ b/src/sinks/new_relic/tests.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, convert::TryFrom, time::SystemTime, num::NonZeroU32}; +use std::{collections::HashMap, convert::TryFrom, num::NonZeroU32, time::SystemTime}; use chrono::{DateTime, Utc}; use futures::{future::ready, stream}; From 62addd3d49aab0c5549e2bf95c5f1ad038213c13 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Thu, 3 Aug 2023 13:55:20 -0700 Subject: [PATCH 14/20] Update to handle new tag model New Relic only accepts key/value tags so we use `iter_single()` here. Signed-off-by: Jesse Szwedko --- src/sinks/new_relic/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index f647a93359e4f..713259ddbcdcb 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -45,8 +45,8 @@ impl TryFrom> for MetricsApiModel { let (series, data, _) = metric.into_parts(); let attr = series.tags.map(|tags| { Value::from( - tags.into_iter() - .map(|(key, value)| (key, Value::from(value))) + tags.iter_single() + .map(|(key, value)| (key.to_string(), Value::from(value))) .collect::>(), ) }); From 1f2bc8346092932a96e26f7d7cc5160f7b2764b2 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Fri, 4 Aug 2023 08:01:33 -0700 Subject: [PATCH 15/20] PR feedback Signed-off-by: Jesse Szwedko --- src/sinks/new_relic/model.rs | 40 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 713259ddbcdcb..0ba85039db128 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -43,17 +43,30 @@ impl TryFrom> for MetricsApiModel { if let Event::Metric(metric) = buf_event { // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); - let attr = series.tags.map(|tags| { - Value::from( - tags.iter_single() - .map(|(key, value)| (key.to_string(), Value::from(value))) - .collect::>(), - ) - }); // We only handle gauge and counter metrics if let MetricValue::Gauge { value } | MetricValue::Counter { value } = data.value { let mut metric_data = KeyValData::new(); + + // Set type and type related attributes + if let (MetricValue::Counter { .. }, MetricKind::Incremental) = + (&data.value, &data.kind) + { + if let Some(interval_ms) = data.time.interval_ms { + metric_data.insert( + "interval.ms".to_owned(), + Value::from(interval_ms.get() as i64), + ); + } else { + // Incremental counter without an interval is worthless, skip this metric + continue; + } + metric_data.insert("type".to_owned(), Value::from("count")); + } else { + // Anything that's not an incremental counter is considered a gauge, that is gauge and absolute counters metrics. + metric_data.insert("type".to_owned(), Value::from("gauge")); + } + // Set name, value, and timestamp metric_data.insert("name".to_owned(), Value::from(series.name.name)); metric_data.insert( @@ -72,9 +85,18 @@ impl TryFrom> for MetricsApiModel { .timestamp(), ), ); - if let Some(attr) = attr { - metric_data.insert("attributes".to_owned(), attr); + + if let Some(tags) = series.tags { + metric_data.insert( + "attributes".to_owned(), + Value::from( + tags.iter_single() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::>(), + ), + ); } + // Set type and type related attributes if let (MetricValue::Counter { .. }, MetricKind::Incremental) = (data.value, data.kind) From f256b33d33d07ff6d925e80684b484f6f3289ec0 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Fri, 11 Aug 2023 15:13:14 -0400 Subject: [PATCH 16/20] refactor --- Cargo.lock | 2 +- src/sinks/new_relic/model.rs | 127 +++++++++++++++-------------------- 2 files changed, 55 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63cffea6b49db..e48ba38426549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,7 +499,7 @@ dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", "strum", - "syn 2.0.27", + "syn 2.0.28", "thiserror", ] diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 0ba85039db128..6b378fdd13a2d 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -37,89 +37,70 @@ impl TryFrom> for MetricsApiModel { type Error = NewRelicSinkError; fn try_from(buf_events: Vec) -> Result { - let mut metric_array = vec![]; - - for buf_event in buf_events { - if let Event::Metric(metric) = buf_event { + let metric_array: Vec<_> = buf_events + .into_iter() + .filter_map(|e| e.try_into_metric()) + .filter_map(|metric| { // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); - // We only handle gauge and counter metrics - if let MetricValue::Gauge { value } | MetricValue::Counter { value } = data.value { - let mut metric_data = KeyValData::new(); + let mut metric_data = KeyValData::new(); - // Set type and type related attributes - if let (MetricValue::Counter { .. }, MetricKind::Incremental) = - (&data.value, &data.kind) - { - if let Some(interval_ms) = data.time.interval_ms { - metric_data.insert( - "interval.ms".to_owned(), - Value::from(interval_ms.get() as i64), - ); - } else { - // Incremental counter without an interval is worthless, skip this metric - continue; - } - metric_data.insert("type".to_owned(), Value::from("count")); - } else { - // Anything that's not an incremental counter is considered a gauge, that is gauge and absolute counters metrics. - metric_data.insert("type".to_owned(), Value::from("gauge")); + // We only handle gauge and counter metrics + // Extract value & type and set type-related attributes + let (value, metric_type) = match (data.value, &data.kind, &data.time.interval_ms) { + ( + MetricValue::Counter { value }, + MetricKind::Incremental, + Some(interval_ms), + ) => { + metric_data.insert( + "interval.ms".to_owned(), + Value::from(interval_ms.get() as i64), + ); + (value, "count") } - - // Set name, value, and timestamp - metric_data.insert("name".to_owned(), Value::from(series.name.name)); - metric_data.insert( - "value".to_owned(), - Value::from( - NotNan::new(value) - .map_err(|_| NewRelicSinkError::new("NaN value not supported"))?, - ), - ); + (MetricValue::Counter { value }, MetricKind::Absolute, _) => (value, "gauge"), + (MetricValue::Gauge { value }, _, _) => (value, "gauge"), + _ => { + // Note that this includes incremental counters without an interval + return None; + } + }; + + // Set name, type, value, timestamp, and attributes + metric_data.insert("name".to_owned(), Value::from(series.name.name)); + metric_data.insert("type".to_owned(), Value::from(metric_type)); + let value = match NotNan::new(value) { + Ok(value) => value, + Err(_) => { + return None; + } + }; + metric_data.insert("value".to_owned(), Value::from(value)); + metric_data.insert( + "timestamp".to_owned(), + Value::from( + data.time + .timestamp + .unwrap_or_else(|| DateTime::::from(SystemTime::now())) + .timestamp(), + ), + ); + if let Some(tags) = series.tags { metric_data.insert( - "timestamp".to_owned(), + "attributes".to_owned(), Value::from( - data.time - .timestamp - .unwrap_or_else(|| DateTime::::from(SystemTime::now())) - .timestamp(), + tags.iter_single() + .map(|(key, value)| (key.to_string(), Value::from(value))) + .collect::>(), ), ); - - if let Some(tags) = series.tags { - metric_data.insert( - "attributes".to_owned(), - Value::from( - tags.iter_single() - .map(|(key, value)| (key.to_string(), Value::from(value))) - .collect::>(), - ), - ); - } - - // Set type and type related attributes - if let (MetricValue::Counter { .. }, MetricKind::Incremental) = - (data.value, data.kind) - { - if let Some(interval_ms) = data.time.interval_ms { - metric_data.insert( - "interval.ms".to_owned(), - Value::from(interval_ms.get() as i64), - ); - } else { - // Incremental counter without an interval is worthless, skip this metric - continue; - } - metric_data.insert("type".to_owned(), Value::from("count")); - } else { - // Anything that's not an incremental counter is considered a gauge, that is gauge and absolute counters metrics. - metric_data.insert("type".to_owned(), Value::from("gauge")); - } - - metric_array.push(metric_data); } - } - } + + Some(metric_data) + }) + .collect(); if !metric_array.is_empty() { Ok(Self::new(metric_array)) From 0ecfa81dba98323a6efca883bdb5a32918481f64 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Tue, 22 Aug 2023 16:50:38 -0400 Subject: [PATCH 17/20] add dropped event metrics --- src/sinks/new_relic/model.rs | 123 ++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 29 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 6b378fdd13a2d..762eeb1742b7d 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -8,6 +8,7 @@ use std::{ use chrono::{DateTime, Utc}; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; +use vector_common::internal_event::{ComponentEventsDropped, INTENTIONAL, UNINTENTIONAL}; use super::NewRelicSinkError; use crate::event::{Event, MetricKind, MetricValue, Value}; @@ -37,10 +38,18 @@ impl TryFrom> for MetricsApiModel { type Error = NewRelicSinkError; fn try_from(buf_events: Vec) -> Result { + let mut num_non_metric_events = 0; + let mut num_missing_interval = 0; + let mut num_nan_value = 0; + let metric_array: Vec<_> = buf_events .into_iter() - .filter_map(|e| e.try_into_metric()) - .filter_map(|metric| { + .filter_map(|event| { + let metric = event.try_into_metric().or_else(|| { + num_non_metric_events += 1; + None + })?; + // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); @@ -48,20 +57,20 @@ impl TryFrom> for MetricsApiModel { // We only handle gauge and counter metrics // Extract value & type and set type-related attributes - let (value, metric_type) = match (data.value, &data.kind, &data.time.interval_ms) { - ( - MetricValue::Counter { value }, - MetricKind::Incremental, - Some(interval_ms), - ) => { + let (value, metric_type) = match (data.value, &data.kind) { + (MetricValue::Counter { value }, MetricKind::Incremental) => { + let interval_ms = data.time.interval_ms.or_else(|| { + num_missing_interval += 1; + None + })?; metric_data.insert( "interval.ms".to_owned(), Value::from(interval_ms.get() as i64), ); (value, "count") } - (MetricValue::Counter { value }, MetricKind::Absolute, _) => (value, "gauge"), - (MetricValue::Gauge { value }, _, _) => (value, "gauge"), + (MetricValue::Counter { value }, MetricKind::Absolute) => (value, "gauge"), + (MetricValue::Gauge { value }, _) => (value, "gauge"), _ => { // Note that this includes incremental counters without an interval return None; @@ -71,12 +80,10 @@ impl TryFrom> for MetricsApiModel { // Set name, type, value, timestamp, and attributes metric_data.insert("name".to_owned(), Value::from(series.name.name)); metric_data.insert("type".to_owned(), Value::from(metric_type)); - let value = match NotNan::new(value) { - Ok(value) => value, - Err(_) => { - return None; - } - }; + let value = NotNan::new(value).ok().or_else(|| { + num_nan_value += 1; + None + })?; metric_data.insert("value".to_owned(), Value::from(value)); metric_data.insert( "timestamp".to_owned(), @@ -102,6 +109,25 @@ impl TryFrom> for MetricsApiModel { }) .collect(); + if num_non_metric_events > 0 { + emit!(ComponentEventsDropped:: { + count: num_non_metric_events, + reason: "non-metric event" + }); + } + if num_nan_value > 0 { + emit!(ComponentEventsDropped:: { + count: num_nan_value, + reason: "NaN value not supported" + }); + } + if num_missing_interval > 0 { + emit!(ComponentEventsDropped:: { + count: num_missing_interval, + reason: "incremental counter missing interval" + }); + } + if !metric_array.is_empty() { Ok(Self::new(metric_array)) } else { @@ -123,9 +149,17 @@ impl TryFrom> for EventsApiModel { type Error = NewRelicSinkError; fn try_from(buf_events: Vec) -> Result { - let mut events_array = vec![]; - for buf_event in buf_events { - if let Event::Log(log) = buf_event { + let mut num_non_log_events = 0; + let mut num_nan_value = 0; + + let events_array: Vec> = buf_events + .into_iter() + .filter_map(|event| { + let log = event.try_into_log().or_else(|| { + num_non_log_events += 1; + None + })?; + let mut event_model = KeyValData::new(); for (k, v) in log.convert_to_fields() { event_model.insert(k, v.clone()); @@ -146,8 +180,9 @@ impl TryFrom> for EventsApiModel { if let Some(f) = n.as_f64() { event_model.insert( k, - Value::from(NotNan::new(f).map_err(|_| { - NewRelicSinkError::new("NaN value not supported") + Value::from(NotNan::new(f).ok().or_else(|| { + num_nan_value += 1; + None })?), ); } else { @@ -157,7 +192,9 @@ impl TryFrom> for EventsApiModel { serde_json::Value::Bool(b) => { event_model.insert(k, Value::from(b)); } - _ => {} + _ => { + // Note that arrays and nested objects are silently dropped. + } } } event_model.remove("message"); @@ -169,8 +206,21 @@ impl TryFrom> for EventsApiModel { .insert("eventType".to_owned(), Value::from("VectorSink".to_owned())); } - events_array.push(event_model); - } + Some(event_model) + }) + .collect(); + + if num_non_log_events > 0 { + emit!(ComponentEventsDropped:: { + count: num_non_log_events, + reason: "non-log event" + }); + } + if num_nan_value > 0 { + emit!(ComponentEventsDropped:: { + count: num_nan_value, + reason: "NaN value not supported" + }); } if !events_array.is_empty() { @@ -196,9 +246,16 @@ impl TryFrom> for LogsApiModel { type Error = NewRelicSinkError; fn try_from(buf_events: Vec) -> Result { - let mut logs_array = vec![]; - for buf_event in buf_events { - if let Event::Log(log) = buf_event { + let mut num_non_log_events = 0; + + let logs_array: Vec> = buf_events + .into_iter() + .filter_map(|event| { + let log = event.try_into_log().or_else(|| { + num_non_log_events += 1; + None + })?; + let mut log_model = KeyValData::new(); for (k, v) in log.convert_to_fields() { log_model.insert(k, v.clone()); @@ -209,8 +266,16 @@ impl TryFrom> for LogsApiModel { Value::from("log from vector".to_owned()), ); } - logs_array.push(log_model); - } + + Some(log_model) + }) + .collect(); + + if num_non_log_events > 0 { + emit!(ComponentEventsDropped:: { + count: num_non_log_events, + reason: "non-log event" + }); } if !logs_array.is_empty() { From c3769db6a66ec815a22505c25e625cd5be25b809 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Tue, 22 Aug 2023 16:59:03 -0400 Subject: [PATCH 18/20] track unsupported metric types as well --- src/sinks/new_relic/model.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 762eeb1742b7d..d22f9feb94de6 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -41,6 +41,7 @@ impl TryFrom> for MetricsApiModel { let mut num_non_metric_events = 0; let mut num_missing_interval = 0; let mut num_nan_value = 0; + let mut num_unsupported_metric_type = 0; let metric_array: Vec<_> = buf_events .into_iter() @@ -60,6 +61,7 @@ impl TryFrom> for MetricsApiModel { let (value, metric_type) = match (data.value, &data.kind) { (MetricValue::Counter { value }, MetricKind::Incremental) => { let interval_ms = data.time.interval_ms.or_else(|| { + // Incremental counter without an interval is worthless, skip this metric num_missing_interval += 1; None })?; @@ -72,7 +74,8 @@ impl TryFrom> for MetricsApiModel { (MetricValue::Counter { value }, MetricKind::Absolute) => (value, "gauge"), (MetricValue::Gauge { value }, _) => (value, "gauge"), _ => { - // Note that this includes incremental counters without an interval + // Unsupported metric type + num_unsupported_metric_type += 1; return None; } }; @@ -115,6 +118,12 @@ impl TryFrom> for MetricsApiModel { reason: "non-metric event" }); } + if num_unsupported_metric_type > 0 { + emit!(ComponentEventsDropped:: { + count: num_unsupported_metric_type, + reason: "unsupported metric type" + }); + } if num_nan_value > 0 { emit!(ComponentEventsDropped:: { count: num_nan_value, From c11c9589fd75780b1decdb207431dd5d1980e5bd Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Mon, 11 Sep 2023 16:24:14 +0200 Subject: [PATCH 19/20] feedback --- src/sinks/new_relic/model.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 6d92e460f0ee4..33c87f0dbcbd4 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -47,10 +47,10 @@ impl TryFrom> for MetricsApiModel { let metric_array: Vec<_> = buf_events .into_iter() .filter_map(|event| { - let metric = event.try_into_metric().or_else(|| { + let Some(metric) = event.try_into_metric() else { num_non_metric_events += 1; - None - })?; + return None; + }; // Generate Value::Object() from BTreeMap let (series, data, _) = metric.into_parts(); @@ -165,10 +165,10 @@ impl TryFrom> for EventsApiModel { let events_array: Vec> = buf_events .into_iter() .filter_map(|event| { - let log = event.try_into_log().or_else(|| { + let Some(log) = event.try_into_log() else { num_non_log_events += 1; - None - })?; + return None; + }; let mut event_model = KeyValData::new(); for (k, v) in log.convert_to_fields() { @@ -261,10 +261,10 @@ impl TryFrom> for LogsApiModel { let logs_array: Vec> = buf_events .into_iter() .filter_map(|event| { - let log = event.try_into_log().or_else(|| { + let Some(log) = event.try_into_log() else { num_non_log_events += 1; - None - })?; + return None; + }; let mut log_model = KeyValData::new(); for (k, v) in log.convert_to_fields() { From 4e4943f80f760be9a8bb4947752155292d905cf8 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Mon, 11 Sep 2023 16:28:10 +0200 Subject: [PATCH 20/20] more feedback --- src/sinks/new_relic/model.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sinks/new_relic/model.rs b/src/sinks/new_relic/model.rs index 33c87f0dbcbd4..5a8b2fc800897 100644 --- a/src/sinks/new_relic/model.rs +++ b/src/sinks/new_relic/model.rs @@ -61,11 +61,11 @@ impl TryFrom> for MetricsApiModel { // Extract value & type and set type-related attributes let (value, metric_type) = match (data.value, &data.kind) { (MetricValue::Counter { value }, MetricKind::Incremental) => { - let interval_ms = data.time.interval_ms.or_else(|| { + let Some(interval_ms) = data.time.interval_ms else { // Incremental counter without an interval is worthless, skip this metric num_missing_interval += 1; - None - })?; + return None; + }; metric_data.insert( "interval.ms".to_owned(), Value::from(interval_ms.get() as i64), @@ -84,10 +84,10 @@ impl TryFrom> for MetricsApiModel { // Set name, type, value, timestamp, and attributes metric_data.insert("name".to_owned(), Value::from(series.name.name)); metric_data.insert("type".to_owned(), Value::from(metric_type)); - let value = NotNan::new(value).ok().or_else(|| { + let Some(value) = NotNan::new(value).ok() else { num_nan_value += 1; - None - })?; + return None; + }; metric_data.insert("value".to_owned(), Value::from(value)); metric_data.insert( "timestamp".to_owned(),