@@ -71,6 +71,142 @@ func newTestEndpoints(names ...string) []fwksched.Endpoint {
7171 return endpoints
7272}
7373
74+ type allProfilesTraceHandler struct {}
75+
76+ func (* allProfilesTraceHandler ) TypedName () fwkplugin.TypedName {
77+ return fwkplugin.TypedName {Type : "all-profiles-trace-handler" }
78+ }
79+
80+ func (* allProfilesTraceHandler ) Pick (_ context.Context , _ * fwksched.InferenceRequest ,
81+ profiles map [string ]fwksched.SchedulerProfile ,
82+ profileResults map [string ]* fwksched.ProfileRunResult ,
83+ ) map [string ]fwksched.SchedulerProfile {
84+ if len (profileResults ) == 0 {
85+ return profiles
86+ }
87+ return nil
88+ }
89+
90+ func (* allProfilesTraceHandler ) ProcessResults (_ context.Context , _ * fwksched.InferenceRequest ,
91+ profileResults map [string ]* fwksched.ProfileRunResult ,
92+ ) (* fwksched.SchedulingResult , error ) {
93+ return & fwksched.SchedulingResult {
94+ ProfileResults : profileResults ,
95+ PrimaryProfileName : "decode" ,
96+ }, nil
97+ }
98+
99+ type panicTraceFilter struct {}
100+
101+ func (* panicTraceFilter ) TypedName () fwkplugin.TypedName {
102+ return fwkplugin.TypedName {Type : "panic-trace-filter" }
103+ }
104+
105+ func (* panicTraceFilter ) Filter (_ context.Context , _ * fwksched.InferenceRequest ,
106+ _ []fwksched.Endpoint ,
107+ ) []fwksched.Endpoint {
108+ panic ("filter panic" )
109+ }
110+
111+ func TestScheduleNestsFilterSpansUnderNamedProfiles (t * testing.T ) {
112+ recorder := setupSpanRecorder (t )
113+ endpoints := newTestEndpoints ("pod1" )
114+
115+ newProfile := func (name string ) * SchedulerProfile {
116+ plugin := & testPlugin {
117+ typedName : fwkplugin.TypedName {Type : "test-plugin" , Name : name },
118+ FilterRes : []k8stypes.NamespacedName {{Name : "pod1" }},
119+ PickRes : k8stypes.NamespacedName {Name : "pod1" },
120+ }
121+ return NewSchedulerProfile ().WithFilters (plugin ).WithPicker (plugin )
122+ }
123+
124+ scheduler := NewSchedulerWithConfig (NewSchedulerConfig (
125+ & allProfilesTraceHandler {},
126+ map [string ]fwksched.SchedulerProfile {
127+ "decode" : newProfile ("decode" ),
128+ "prefill" : newProfile ("prefill" ),
129+ },
130+ ))
131+
132+ ctx , root := otel .Tracer ("test" ).Start (context .Background (), "root" )
133+ _ , err := scheduler .Schedule (ctx , & fwksched.InferenceRequest {TargetModel : "m1" , RequestID : "r1" }, endpoints )
134+ root .End ()
135+ if err != nil {
136+ t .Fatalf ("Schedule returned error: %v" , err )
137+ }
138+
139+ profileSpans := findSpans (recorder .Ended (), "run_scheduler_profile" )
140+ if len (profileSpans ) != 2 {
141+ t .Fatalf ("got %d run_scheduler_profile spans, want 2" , len (profileSpans ))
142+ }
143+ profileNameBySpanID := make (map [trace.SpanID ]string , len (profileSpans ))
144+ for _ , profileSpan := range profileSpans {
145+ profileName := spanAttributes (profileSpan )["llm_d.epp.scheduling.profile.name" ].AsString ()
146+ profileNameBySpanID [profileSpan .SpanContext ().SpanID ()] = profileName
147+ }
148+
149+ filterSpans := findSpans (recorder .Ended (), "filter_endpoints" )
150+ if len (filterSpans ) != 2 {
151+ t .Fatalf ("got %d filter_endpoints spans, want 2" , len (filterSpans ))
152+ }
153+ seenProfiles := make (map [string ]bool , len (filterSpans ))
154+ for _ , filterSpan := range filterSpans {
155+ profileName , ok := profileNameBySpanID [filterSpan .Parent ().SpanID ()]
156+ if ! ok {
157+ t .Errorf ("filter_endpoints parent %v is not a run_scheduler_profile span" , filterSpan .Parent ().SpanID ())
158+ continue
159+ }
160+ seenProfiles [profileName ] = true
161+ }
162+ for _ , profileName := range []string {"decode" , "prefill" } {
163+ if ! seenProfiles [profileName ] {
164+ t .Errorf ("no filter_endpoints span attributed to profile %q" , profileName )
165+ }
166+ }
167+ }
168+
169+ func TestScheduleEndsProfileSpanWhenFilterPanics (t * testing.T ) {
170+ recorder := setupSpanRecorder (t )
171+ endpoints := newTestEndpoints ("pod1" )
172+ picker := & testPlugin {
173+ typedName : fwkplugin.TypedName {Type : "test-picker" },
174+ PickRes : k8stypes.NamespacedName {Name : "pod1" },
175+ }
176+ profile := NewSchedulerProfile ().WithFilters (& panicTraceFilter {}).WithPicker (picker )
177+ scheduler := NewSchedulerWithConfig (NewSchedulerConfig (
178+ & allProfilesTraceHandler {},
179+ map [string ]fwksched.SchedulerProfile {"decode" : profile },
180+ ))
181+
182+ ctx , root := otel .Tracer ("test" ).Start (context .Background (), "root" )
183+ recovered := func () (value any ) {
184+ defer func () { value = recover () }()
185+ _ , _ = scheduler .Schedule (ctx , & fwksched.InferenceRequest {TargetModel : "m1" , RequestID : "r1" }, endpoints )
186+ return nil
187+ }()
188+ root .End ()
189+ if recovered != "filter panic" {
190+ t .Fatalf ("recovered panic = %v, want filter panic" , recovered )
191+ }
192+
193+ profileSpans := findSpans (recorder .Ended (), "run_scheduler_profile" )
194+ if len (profileSpans ) != 1 {
195+ t .Fatalf ("got %d run_scheduler_profile spans after panic, want 1" , len (profileSpans ))
196+ }
197+ filterSpans := findSpans (recorder .Ended (), "filter_endpoints" )
198+ if len (filterSpans ) != 1 {
199+ t .Fatalf ("got %d filter_endpoints spans after panic, want 1" , len (filterSpans ))
200+ }
201+ if filterSpans [0 ].Parent ().SpanID () != profileSpans [0 ].SpanContext ().SpanID () {
202+ t .Errorf ("filter_endpoints parent = %v, want profile span %v" ,
203+ filterSpans [0 ].Parent ().SpanID (), profileSpans [0 ].SpanContext ().SpanID ())
204+ }
205+ if got := spanAttributes (profileSpans [0 ])["llm_d.epp.scheduling.profile.name" ].AsString (); got != "decode" {
206+ t .Errorf ("profile name = %q, want decode" , got )
207+ }
208+ }
209+
74210func TestRunFilterPluginsSingleSpan (t * testing.T ) {
75211 recorder := setupSpanRecorder (t )
76212
0 commit comments