Skip to content

Commit 5b76e18

Browse files
committed
rearrange
1 parent 9be90b5 commit 5b76e18

File tree

5 files changed

+410
-293
lines changed

5 files changed

+410
-293
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pub mod logs_asserter;
2-
pub mod metrics_asserter;
2+
pub mod metric_helpers;
33
pub mod test_utils;
44
pub mod trace_asserter;
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
use crate::test_utils;
2+
use anyhow::Result;
3+
use anyhow::{Context, Ok};
4+
use opentelemetry_otlp::MetricExporter;
5+
use opentelemetry_sdk::metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider};
6+
use opentelemetry_sdk::Resource;
7+
use serde_json::Value;
8+
use std::fs;
9+
use std::fs::File;
10+
use std::io::BufReader;
11+
use std::io::Read;
12+
use std::time::Duration;
13+
14+
static RESULT_PATH: &str = "actual/metrics.json";
15+
pub const SLEEP_DURATION: Duration = Duration::from_secs(5);
16+
17+
///
18+
/// Creates an exporter using the appropriate HTTP or gRPC client based on
19+
/// the configured features.
20+
///
21+
fn create_exporter() -> MetricExporter {
22+
let exporter_builder = MetricExporter::builder();
23+
24+
#[cfg(feature = "tonic-client")]
25+
let exporter_builder = exporter_builder.with_tonic();
26+
#[cfg(not(feature = "tonic-client"))]
27+
#[cfg(any(
28+
feature = "hyper-client",
29+
feature = "reqwest-client",
30+
feature = "reqwest-blocking-client"
31+
))]
32+
let exporter_builder = exporter_builder.with_http();
33+
34+
exporter_builder
35+
.build()
36+
.expect("Failed to build MetricExporter")
37+
}
38+
39+
/// Initializes the OpenTelemetry metrics pipeline
40+
fn init_meter_provider() -> SdkMeterProvider {
41+
let exporter = create_exporter();
42+
let reader = PeriodicReader::builder(exporter)
43+
.with_interval(Duration::from_secs(2))
44+
.build();
45+
let resource = Resource::builder_empty()
46+
.with_service_name("metrics-integration-test")
47+
.build();
48+
let meter_provider = MeterProviderBuilder::default()
49+
.with_resource(resource)
50+
.with_reader(reader)
51+
.build();
52+
opentelemetry::global::set_meter_provider(meter_provider.clone());
53+
meter_provider
54+
}
55+
56+
///
57+
/// Performs setup for metrics tests using the Tokio runtime.
58+
///
59+
pub async fn setup_metrics_tokio() -> SdkMeterProvider {
60+
let _ = test_utils::start_collector_container().await;
61+
// Truncate results
62+
_ = File::create(RESULT_PATH).expect("it's good");
63+
64+
init_meter_provider()
65+
}
66+
67+
///
68+
/// Performs setup for metrics tests.
69+
///
70+
pub fn setup_metrics_non_tokio(
71+
initialize_metric_in_tokio: bool,
72+
) -> (SdkMeterProvider, tokio::runtime::Runtime) {
73+
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
74+
let meter_provider: SdkMeterProvider = if initialize_metric_in_tokio {
75+
// Initialize the logger provider inside the Tokio runtime
76+
rt.block_on(async {
77+
// Setup the collector container inside Tokio runtime
78+
let _ = test_utils::start_collector_container().await;
79+
init_meter_provider()
80+
})
81+
} else {
82+
rt.block_on(async {
83+
let _ = test_utils::start_collector_container().await;
84+
});
85+
86+
// Initialize the logger provider outside the Tokio runtime
87+
init_meter_provider()
88+
};
89+
90+
(meter_provider, rt)
91+
}
92+
93+
///
94+
/// Check that the results contain the given string.
95+
///
96+
pub fn assert_metrics_results_contains(expected_content: &str) -> Result<()> {
97+
// let contents = fs::read_to_string(test_utils::METRICS_FILE)?;
98+
let file = File::open(test_utils::METRICS_FILE)?;
99+
let mut contents = String::new();
100+
let mut reader = std::io::BufReader::new(&file);
101+
reader.read_to_string(&mut contents)?;
102+
assert!(contents.contains(expected_content));
103+
Ok(())
104+
}
105+
106+
///
107+
/// Retrieves the latest metrics for the given scope. Each test should use
108+
/// its own scope, so that we can easily pull the data for it out from the rest
109+
/// of the data.
110+
///
111+
/// This will also retrieve the resource attached to the scope.
112+
///
113+
pub fn fetch_latest_metrics_for_scope(scope_name: &str) -> Result<Value> {
114+
// Open the file and fetch the contents
115+
let contents = fs::read_to_string(test_utils::METRICS_FILE)?;
116+
117+
// Find the last parseable metrics line that contains the desired scope
118+
let json_line = contents
119+
.lines()
120+
.rev()
121+
.find_map(|line| {
122+
// Attempt to parse the line as JSON
123+
serde_json::from_str::<Value>(line)
124+
.ok()
125+
.and_then(|mut json_line| {
126+
// Check if it contains the specified scope
127+
if let Some(resource_metrics) = json_line
128+
.get_mut("resourceMetrics")
129+
.and_then(|v| v.as_array_mut())
130+
{
131+
resource_metrics.retain_mut(|resource| {
132+
if let Some(scope_metrics) = resource
133+
.get_mut("scopeMetrics")
134+
.and_then(|v| v.as_array_mut())
135+
{
136+
scope_metrics.retain(|scope| {
137+
scope
138+
.get("scope")
139+
.and_then(|s| s.get("name"))
140+
.and_then(|name| name.as_str())
141+
.map_or(false, |n| n == scope_name)
142+
});
143+
144+
// Keep the resource only if it has any matching `ScopeMetrics`
145+
!scope_metrics.is_empty()
146+
} else {
147+
false
148+
}
149+
});
150+
151+
// If any resource metrics remain, return this line
152+
if !resource_metrics.is_empty() {
153+
return Some(json_line);
154+
}
155+
}
156+
157+
None
158+
})
159+
})
160+
.with_context(|| {
161+
format!(
162+
"No valid JSON line containing scope `{}` found.",
163+
scope_name
164+
)
165+
})?;
166+
167+
Ok(json_line)
168+
}
169+
170+
///
171+
/// Check that the metrics for the given scope match what we expect. This
172+
/// includes zeroing out timestamps, which we reasonably expect not to match.
173+
///
174+
pub fn validate_metrics_against_results(scope_name: &str) -> Result<()> {
175+
// Define the results file path
176+
let results_file_path = format!("./expected/metrics/{}.json", scope_name);
177+
178+
// Fetch the actual metrics for the given scope
179+
let actual_metrics = fetch_latest_metrics_for_scope(scope_name)
180+
.context(format!("Failed to fetch metrics for scope: {}", scope_name))?;
181+
182+
// Read the expected metrics from the results file
183+
let expected_metrics = {
184+
let file = File::open(&results_file_path).context(format!(
185+
"Failed to open results file: {}",
186+
results_file_path
187+
))?;
188+
read_metrics_from_json(file)
189+
}?;
190+
191+
// Compare the actual metrics with the expected metrics
192+
MetricsAsserter::new(actual_metrics, expected_metrics).assert();
193+
194+
Ok(())
195+
}
196+
197+
pub fn read_metrics_from_json(file: File) -> Result<Value> {
198+
// Create a buffered reader for the file
199+
let mut reader = BufReader::new(file);
200+
let mut contents = String::new();
201+
202+
// Read the file contents into a string
203+
reader
204+
.read_to_string(&mut contents)
205+
.expect("Failed to read json file");
206+
207+
// Parse the contents into a JSON Value
208+
let metrics_data: Value = serde_json::from_str(&contents)?;
209+
Ok(metrics_data)
210+
}
211+
212+
pub struct MetricsAsserter {
213+
results: Value,
214+
expected: Value,
215+
}
216+
217+
impl MetricsAsserter {
218+
pub fn new(results: Value, expected: Value) -> Self {
219+
MetricsAsserter { results, expected }
220+
}
221+
222+
pub fn assert(mut self) {
223+
// Normalize JSON by cleaning out timestamps
224+
Self::zero_out_timestamps(&mut self.results);
225+
Self::zero_out_timestamps(&mut self.expected);
226+
227+
// Perform the assertion
228+
assert_eq!(
229+
self.results, self.expected,
230+
"Metrics did not match. Results: {:#?}, Expected: {:#?}",
231+
self.results, self.expected
232+
);
233+
}
234+
235+
/// Recursively removes or zeros out timestamp fields in the JSON
236+
fn zero_out_timestamps(value: &mut Value) {
237+
match value {
238+
Value::Object(map) => {
239+
for (key, val) in map.iter_mut() {
240+
if key == "startTimeUnixNano" || key == "timeUnixNano" {
241+
*val = Value::String("0".to_string());
242+
} else {
243+
Self::zero_out_timestamps(val);
244+
}
245+
}
246+
}
247+
Value::Array(array) => {
248+
for item in array.iter_mut() {
249+
Self::zero_out_timestamps(item);
250+
}
251+
}
252+
_ => {}
253+
}
254+
}
255+
}

opentelemetry-otlp/tests/integration_test/src/metrics_asserter.rs

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)