11// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22// SPDX-License-Identifier: Apache-2.0
3- //
4- // Licensed under the Apache License, Version 2.0 (the "License");
5- // you may not use this file except in compliance with the License.
6- // You may obtain a copy of the License at
7- //
8- // http://www.apache.org/licenses/LICENSE-2.0
9- //
10- // Unless required by applicable law or agreed to in writing, software
11- // distributed under the License is distributed on an "AS IS" BASIS,
12- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13- // See the License for the specific language governing permissions and
14- // limitations under the License.
153
164use dynamo_llm:: http:: service:: metrics:: { self , Endpoint } ;
175use dynamo_llm:: http:: service:: service_v2:: HttpService ;
186use dynamo_runtime:: CancellationToken ;
7+ use serial_test:: serial;
198use std:: { env, time:: Duration } ;
209
2110#[ tokio:: test]
11+ #[ serial]
2212async fn metrics_prefix_default_then_env_override ( ) {
2313 // Case 1: default prefix
2414 env:: remove_var ( metrics:: METRICS_PREFIX_ENV ) ;
2515 let svc1 = HttpService :: builder ( ) . port ( 9101 ) . build ( ) . unwrap ( ) ;
2616 let token1 = CancellationToken :: new ( ) ;
2717 let _h1 = svc1. spawn ( token1. clone ( ) ) . await ;
28- tokio :: time :: sleep ( Duration :: from_millis ( 100 ) ) . await ;
18+ wait_for_metrics_ready ( 9101 ) . await ;
2919
3020 // Populate labeled metrics
3121 let s1 = svc1. state_clone ( ) ;
32- { let _g = s1. metrics_clone ( ) . create_inflight_guard ( "test-model" , Endpoint :: ChatCompletions , false ) ; }
33- let body1 = reqwest:: get ( "http://localhost:9101/metrics" ) . await . unwrap ( ) . text ( ) . await . unwrap ( ) ;
22+ {
23+ let _g = s1. metrics_clone ( ) . create_inflight_guard (
24+ "test-model" ,
25+ Endpoint :: ChatCompletions ,
26+ false ,
27+ ) ;
28+ }
29+ let body1 = reqwest:: get ( "http://localhost:9101/metrics" )
30+ . await
31+ . unwrap ( )
32+ . text ( )
33+ . await
34+ . unwrap ( ) ;
3435 assert ! ( body1. contains( "dynamo_frontend_requests_total" ) ) ;
3536 token1. cancel ( ) ;
3637
@@ -39,12 +40,65 @@ async fn metrics_prefix_default_then_env_override() {
3940 let svc2 = HttpService :: builder ( ) . port ( 9102 ) . build ( ) . unwrap ( ) ;
4041 let token2 = CancellationToken :: new ( ) ;
4142 let _h2 = svc2. spawn ( token2. clone ( ) ) . await ;
42- tokio :: time :: sleep ( Duration :: from_millis ( 100 ) ) . await ;
43+ wait_for_metrics_ready ( 9102 ) . await ;
4344
4445 // Populate labeled metrics
4546 let s2 = svc2. state_clone ( ) ;
46- { let _g = s2. metrics_clone ( ) . create_inflight_guard ( "test-model" , Endpoint :: ChatCompletions , true ) ; }
47- let body2 = reqwest:: get ( "http://localhost:9102/metrics" ) . await . unwrap ( ) . text ( ) . await . unwrap ( ) ;
48- assert ! ( body2. contains( "custom_prefix" ) ) ;
47+ {
48+ let _g =
49+ s2. metrics_clone ( )
50+ . create_inflight_guard ( "test-model" , Endpoint :: ChatCompletions , true ) ;
51+ }
52+ // Single fetch and assert
53+ let body2 = reqwest:: get ( "http://localhost:9102/metrics" )
54+ . await
55+ . unwrap ( )
56+ . text ( )
57+ . await
58+ . unwrap ( ) ;
59+ assert ! ( body2. contains( "custom_prefix_requests_total" ) ) ;
60+ assert ! ( !body2. contains( "dynamo_frontend_requests_total" ) ) ;
4961 token2. cancel ( ) ;
62+
63+ // Case 3: invalid env prefix is sanitized
64+ env:: set_var ( metrics:: METRICS_PREFIX_ENV , "nv-llm/http service" ) ;
65+ let svc3 = HttpService :: builder ( ) . port ( 9103 ) . build ( ) . unwrap ( ) ;
66+ let token3 = CancellationToken :: new ( ) ;
67+ let _h3 = svc3. spawn ( token3. clone ( ) ) . await ;
68+ wait_for_metrics_ready ( 9103 ) . await ;
69+
70+ let s3 = svc3. state_clone ( ) ;
71+ {
72+ let _g =
73+ s3. metrics_clone ( )
74+ . create_inflight_guard ( "test-model" , Endpoint :: ChatCompletions , true ) ;
75+ }
76+ let body3 = reqwest:: get ( "http://localhost:9103/metrics" )
77+ . await
78+ . unwrap ( )
79+ . text ( )
80+ . await
81+ . unwrap ( ) ;
82+ assert ! ( body3. contains( "nv_llm_http_service_requests_total" ) ) ;
83+ assert ! ( !body3. contains( "dynamo_frontend_requests_total" ) ) ;
84+ token3. cancel ( ) ;
85+
86+ // Cleanup env to avoid leaking state
87+ env:: remove_var ( metrics:: METRICS_PREFIX_ENV ) ;
88+ }
89+
90+ // Poll /metrics until ready or timeout
91+ async fn wait_for_metrics_ready ( port : u16 ) {
92+ let url = format ! ( "http://localhost:{}/metrics" , port) ;
93+ let start = tokio:: time:: Instant :: now ( ) ;
94+ let timeout = Duration :: from_secs ( 5 ) ;
95+ loop {
96+ if start. elapsed ( ) > timeout {
97+ panic ! ( "Timed out waiting for metrics endpoint at {}" , url) ;
98+ }
99+ match reqwest:: get ( & url) . await {
100+ Ok ( resp) if resp. status ( ) . is_success ( ) => break ,
101+ _ => tokio:: time:: sleep ( Duration :: from_millis ( 50 ) ) . await ,
102+ }
103+ }
50104}
0 commit comments