@@ -128,36 +128,12 @@ pub async fn dump_stats() -> Result<(), Error> {
128128
129129#[ cfg( test) ]
130130pub mod tests {
131- use crate :: {
132- Error , LabelProm as _, dump_stats,
133- metrics:: { self , Metrics } ,
134- } ;
131+ use crate :: { Error , LabelProm as _, dump_stats} ;
135132 use axum:: { http:: StatusCode , response:: IntoResponse as _} ;
136- use combine:: EasyParser as _;
137133 use commandeer_test:: commandeer;
138- use lustre_collector:: { Record , TargetVariant , parser:: parse} ;
139- use prometheus_client:: { encoding:: text:: encode, registry:: Registry } ;
140- use prometheus_parse:: { Sample , Scrape } ;
134+ use lustre_collector:: TargetVariant ;
135+ use prometheus_parse:: Scrape ;
141136 use serial_test:: serial;
142- use std:: {
143- collections:: HashSet ,
144- path:: { Path , PathBuf } ,
145- } ;
146-
147- // These metrics are ignored for the comparison with the previous implementation
148- // since they are new and not present in the previous implementation.
149- const IGNORED_METRICS : & [ & str ] = & [
150- "lustre_cache_hit_total" ,
151- "lustre_cache_access_total" ,
152- "lustre_cache_miss_total" ,
153- "lustre_get_page_total" ,
154- "lustre_health_healthy" ,
155- "lustre_many_credits_total" ,
156- "lustre_stats_time_max" ,
157- "lustre_stats_time_min" ,
158- "lustre_stats_time_total" ,
159- "target_info" ,
160- ] ;
161137
162138 #[ test]
163139 fn test_error_into_response ( ) {
@@ -181,109 +157,6 @@ pub mod tests {
181157 dump_stats ( ) . await . unwrap ( ) ;
182158 }
183159
184- /// There are various differences between the current snapshots and the otel snapshots.
185- /// It is imperative that the metrics between both snapshots are the same. However,
186- /// we cannot do a direct comparison of the text as there are several differences in the
187- /// way the data is encoded:
188- /// 1. Metric descriptions: The otel implementation did not have trailing periods, while
189- /// the prometheus-client crate adds a period to the end of all metric descriptions.
190- /// 2. Label ordering: Labels are not sorted alphabetically in the otel implementation,
191- /// while prometheus-client sorts them.
192- /// 3. EOF marker: The otel version did not contain the `# EOF` line that is present
193- /// in the current implementation.
194- /// 4. Removed metrics: The `target_info` metric has been removed in the new implementation.
195- /// 5. Removed labels: The `otel_scope_name` label has been removed from all metrics.
196- ///
197- /// This test ensures that the current snapshots still match the otel snapshots by normalizing
198- /// each line in both snapshot files before performing a comparison.
199- #[ test]
200- fn compare_snapshots_to_existing_otel_snapshots ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
201- insta:: glob!( "otel_snapshots/" , "*.otelsnap" , |path| {
202- let snap_name = path. file_name( ) . unwrap( ) ;
203- let snap_file = path
204- . parent( )
205- . unwrap( )
206- . parent( )
207- . unwrap( )
208- . join( "snapshots" )
209- . join( snap_name. to_string_lossy( ) . replace( ".otelsnap" , ".snap" ) ) ;
210- let otel_metrics = read_metrics_from_snapshot( path) ;
211- let metrics = read_metrics_from_snapshot( & snap_file) ;
212-
213- compare_metrics( & otel_metrics, & metrics) ;
214- } ) ;
215-
216- Ok ( ( ) )
217- }
218-
219- pub ( super ) fn compare_metrics ( metrics1 : & Scrape , metrics2 : & Scrape ) {
220- // Skip OTEL specific metric and updated metrics.
221- let set1: HashSet < _ > = metrics1
222- . samples
223- . iter ( )
224- . filter ( |s| !IGNORED_METRICS . contains ( & s. metric . as_str ( ) ) )
225- . map ( normalize_sample)
226- . collect ( ) ;
227-
228- let set2: HashSet < _ > = metrics2
229- . samples
230- . iter ( )
231- . filter ( |s| !IGNORED_METRICS . contains ( & s. metric . as_str ( ) ) )
232- . map ( normalize_sample)
233- . collect ( ) ;
234-
235- let only_in_first: Vec < _ > = set1. difference ( & set2) . collect ( ) ;
236-
237- let only_in_second: Vec < _ > = set2. difference ( & set1) . collect ( ) ;
238-
239- let metric_value_comparison = if only_in_first. is_empty ( ) && only_in_second. is_empty ( ) {
240- true
241- } else {
242- if !only_in_first. is_empty ( ) {
243- println ! ( "Metrics only in first file:" ) ;
244-
245- for metric in only_in_first {
246- println ! ( "{metric:?}" ) ;
247- }
248- }
249-
250- if !only_in_second. is_empty ( ) {
251- println ! ( "Metrics only in second file:" ) ;
252-
253- for metric in only_in_second {
254- println ! ( "{metric:?}" ) ;
255- }
256- }
257-
258- false
259- } ;
260-
261- // Assert metrics values/labels are exactly the same
262- assert ! (
263- metric_value_comparison,
264- "Metrics values/labels are not the same"
265- ) ;
266-
267- // Normalize and compare metrics help
268- let normalized_docs1 = normalize_docs ( & metrics1. docs ) ;
269- let normalized_docs2 = normalize_docs ( & metrics2. docs ) ;
270-
271- pretty_assertions:: assert_eq!(
272- normalized_docs1,
273- normalized_docs2,
274- "Metrics help are not the same"
275- ) ;
276- }
277-
278- pub ( super ) fn historical_snapshot_path ( name : & str ) -> PathBuf {
279- PathBuf :: from_iter ( [
280- env ! ( "CARGO_MANIFEST_DIR" ) ,
281- "src" ,
282- "historical_snapshots" ,
283- name,
284- ] )
285- }
286-
287160 pub fn get_scrape ( x : String ) -> Scrape {
288161 // According to the Prometheus text exposition format specification,
289162 // curly braces {} are required even for empty label sets.
@@ -300,81 +173,4 @@ pub mod tests {
300173
301174 Scrape :: parse ( x) . unwrap ( )
302175 }
303-
304- pub ( super ) fn read_metrics_from_snapshot ( path : & Path ) -> Scrape {
305- let x = insta:: Snapshot :: from_file ( path) . unwrap ( ) ;
306-
307- let insta:: internals:: SnapshotContents :: Text ( x) = x. contents ( ) else {
308- panic ! ( "Snapshot is not text" ) ;
309- } ;
310-
311- get_scrape ( x. to_string ( ) )
312- }
313-
314- fn parse_lustre_metrics ( contents : & str ) -> String {
315- let ( records, _) = parse ( )
316- . easy_parse ( contents)
317- . map_err ( |err| err. map_position ( |p| p. translate_position ( contents) ) )
318- . unwrap ( ) ;
319-
320- build_lustre_stats ( & records)
321- }
322-
323- fn encode_lustre_stats_from_fixture ( content : & str ) -> String {
324- let records = serde_json:: from_str ( content) . unwrap ( ) ;
325-
326- build_lustre_stats ( & records)
327- }
328-
329- fn build_lustre_stats ( x : & Vec < Record > ) -> String {
330- let mut registry = Registry :: default ( ) ;
331- let mut metrics = Metrics :: default ( ) ;
332-
333- metrics:: build_lustre_stats ( x, & mut metrics) ;
334-
335- metrics. register_metric ( & mut registry) ;
336-
337- let mut stats = String :: new ( ) ;
338-
339- encode ( & mut stats, & registry) . unwrap ( ) ;
340-
341- stats
342- }
343-
344- fn normalize_sample ( sample : & Sample ) -> ( String , Vec < ( String , String ) > , String ) {
345- let mut sorted_labels: Vec < _ > = sample
346- . labels
347- . iter ( )
348- . filter ( |( k, _) | * k != "otel_scope_name" )
349- . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) )
350- . collect ( ) ;
351-
352- sorted_labels. sort ( ) ;
353-
354- let value_str = match sample. value {
355- prometheus_parse:: Value :: Counter ( f) => format ! ( "Counter({f})" ) ,
356- prometheus_parse:: Value :: Gauge ( f) => format ! ( "Gauge({f})" ) ,
357- _ => "0.0" . to_string ( ) ,
358- } ;
359-
360- ( sample. metric . clone ( ) , sorted_labels, value_str)
361- }
362-
363- fn normalize_docs ( docs : & std:: collections:: HashMap < String , String > ) -> Vec < ( String , String ) > {
364- // Ignore updated metrics since OTEL move.
365- let mut sorted_docs: Vec < _ > = docs
366- . iter ( )
367- . filter_map ( |( k, v) | {
368- if !IGNORED_METRICS . contains ( & k. as_str ( ) ) {
369- Some ( ( k. clone ( ) , v. strip_suffix ( "." ) . unwrap_or ( v) . to_string ( ) ) )
370- } else {
371- None
372- }
373- } )
374- . collect ( ) ;
375-
376- sorted_docs. sort_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ; // Sort by key
377-
378- sorted_docs
379- }
380176}
0 commit comments