Skip to content

Commit bdbd6b3

Browse files
committed
address comments from @shmuelk
Signed-off-by: Guangya Liu <gyliu513@gmail.com>
1 parent f9d7b57 commit bdbd6b3

7 files changed

Lines changed: 43 additions & 92 deletions

File tree

cmd/pd-sidecar/main.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,16 @@ package main
1717

1818
import (
1919
"context"
20-
"errors"
21-
"fmt"
22-
"net/http"
23-
"time"
2420

25-
"github.com/prometheus/client_golang/prometheus/promhttp"
2621
"github.com/spf13/pflag"
2722
ctrl "sigs.k8s.io/controller-runtime"
2823
"sigs.k8s.io/controller-runtime/pkg/log"
29-
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
3024

3125
"github.com/llm-d/llm-d-router/pkg/common/observability/tracing"
32-
sidecarmetrics "github.com/llm-d/llm-d-router/pkg/sidecar/metrics"
3326
"github.com/llm-d/llm-d-router/pkg/sidecar/proxy"
3427
"github.com/llm-d/llm-d-router/pkg/sidecar/version"
3528
)
3629

37-
// metricsShutdownTimeout bounds graceful shutdown of the metrics server so a
38-
// scraper holding a connection at process exit cannot block termination.
39-
const metricsShutdownTimeout = 5 * time.Second
40-
41-
// serveMetrics starts a standalone Prometheus metrics HTTP server serving
42-
// controller-runtime's registry, where sidecar metrics are registered.
43-
func serveMetrics(ctx context.Context, port int) error {
44-
mux := http.NewServeMux()
45-
mux.Handle("/metrics", promhttp.HandlerFor(ctrlmetrics.Registry, promhttp.HandlerOpts{EnableOpenMetrics: true}))
46-
srv := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: mux}
47-
go func() {
48-
<-ctx.Done()
49-
shutdownCtx, cancel := context.WithTimeout(context.Background(), metricsShutdownTimeout)
50-
defer cancel()
51-
_ = srv.Shutdown(shutdownCtx)
52-
}()
53-
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
54-
return fmt.Errorf("metrics server: %w", err)
55-
}
56-
return nil
57-
}
58-
5930
func main() {
6031
// Initialize options with defaults
6132
opts := proxy.NewOptions()
@@ -97,16 +68,6 @@ func main() {
9768
}
9869
}
9970

100-
if opts.MetricsPort > 0 {
101-
sidecarmetrics.Register()
102-
go func() {
103-
logger.Info("starting metrics server", "port", opts.MetricsPort)
104-
if err := serveMetrics(ctx, opts.MetricsPort); err != nil {
105-
logger.Error(err, "metrics server failed")
106-
}
107-
}()
108-
}
109-
11071
logger.Info("Proxy starting", "Built on", version.BuildRef, "From Git SHA", version.CommitSHA)
11172
logger.Info("Proxy configuration", "config", opts.Config)
11273

pkg/sidecar/metrics/metrics.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package metrics defines the Prometheus metrics exposed by the P/D sidecar
18-
// proxy. Metrics are registered with controller-runtime's registry so the
19-
// sidecar's /metrics endpoint serves them alongside Go runtime metrics, matching
20-
// EPP. The subsystem mirrors the sidecar span namespace (llm_d.pd_proxy.*).
17+
// Package metrics defines the Prometheus metrics exposed by the disaggregation
18+
// sidecar proxy. Metrics are registered with controller-runtime's registry and
19+
// served on the sidecar's /metrics route alongside Go runtime metrics, matching
20+
// EPP. The sidecar handles encode, prefill, and decode disaggregation.
2121
package metrics
2222

2323
import (
@@ -31,18 +31,19 @@ import (
3131
metricsutil "github.com/llm-d/llm-d-router/pkg/common/observability/metrics"
3232
)
3333

34-
// subsystem is the Prometheus subsystem for sidecar metrics, aligned with the
35-
// llm_d.pd_proxy.* span namespace.
36-
const subsystem = "llm_d_pd_proxy"
34+
// subsystem is the Prometheus subsystem for the disaggregation sidecar metrics.
35+
const subsystem = "llm_d_disagg_sidecar"
3736

38-
// Stage labels for prefill/decode error attribution.
37+
// Stage labels for encode/prefill/decode error attribution.
3938
const (
39+
StageEncode = "encode"
4040
StagePrefill = "prefill"
4141
StageDecode = "decode"
4242
)
4343

44-
// latencyBuckets covers prefill and decode wall-clock latency in seconds, from a
45-
// few milliseconds to several minutes (decode can stream for a long time).
44+
// latencyBuckets covers encode, prefill, and decode wall-clock latency in
45+
// seconds, from a few milliseconds to several minutes (decode can stream for a
46+
// long time).
4647
var latencyBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300}
4748

4849
var (
@@ -64,6 +65,16 @@ var (
6465
[]string{"connector"},
6566
)
6667

68+
encodeDuration = prometheus.NewHistogramVec(
69+
prometheus.HistogramOpts{
70+
Subsystem: subsystem,
71+
Name: "encode_duration_seconds",
72+
Help: metricsutil.HelpMsgWithStability("Encode stage latency in seconds, by connector.", compbasemetrics.ALPHA),
73+
Buckets: latencyBuckets,
74+
},
75+
[]string{"connector"},
76+
)
77+
6778
prefillDuration = prometheus.NewHistogramVec(
6879
prometheus.HistogramOpts{
6980
Subsystem: subsystem,
@@ -103,6 +114,7 @@ func Register() {
103114
ctrlmetrics.Registry.MustRegister(
104115
requestsTotal,
105116
disaggRequestsTotal,
117+
encodeDuration,
106118
prefillDuration,
107119
decodeDuration,
108120
errorsTotal,
@@ -120,6 +132,11 @@ func RecordDisagg(connector string) {
120132
disaggRequestsTotal.WithLabelValues(connector).Inc()
121133
}
122134

135+
// RecordEncodeDuration records encode stage latency for the given connector.
136+
func RecordEncodeDuration(connector string, d time.Duration) {
137+
encodeDuration.WithLabelValues(connector).Observe(d.Seconds())
138+
}
139+
123140
// RecordPrefillDuration records prefill stage latency for the given connector.
124141
func RecordPrefillDuration(connector string, d time.Duration) {
125142
prefillDuration.WithLabelValues(connector).Observe(d.Seconds())

pkg/sidecar/metrics/metrics_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,15 @@ func TestRecordDisagg(t *testing.T) {
4545
}
4646

4747
func TestRecordDurations(t *testing.T) {
48+
encodeDuration.Reset()
4849
prefillDuration.Reset()
4950
decodeDuration.Reset()
5051

52+
RecordEncodeDuration("nixl", 50*time.Millisecond)
5153
RecordPrefillDuration("nixlv2", 100*time.Millisecond)
5254
RecordDecodeDuration("nixlv2", 250*time.Millisecond)
5355

56+
require.Equal(t, 1, promtestutil.CollectAndCount(encodeDuration))
5457
require.Equal(t, 1, promtestutil.CollectAndCount(prefillDuration))
5558
require.Equal(t, 1, promtestutil.CollectAndCount(decodeDuration))
5659
}
@@ -61,9 +64,11 @@ func TestRecordError(t *testing.T) {
6164
RecordError("sglang", StagePrefill)
6265
RecordError("sglang", StagePrefill)
6366
RecordError("sglang", StageDecode)
67+
RecordError("nixl", StageEncode)
6468

6569
require.Equal(t, 2.0, promtestutil.ToFloat64(errorsTotal.WithLabelValues("sglang", StagePrefill)))
6670
require.Equal(t, 1.0, promtestutil.ToFloat64(errorsTotal.WithLabelValues("sglang", StageDecode)))
71+
require.Equal(t, 1.0, promtestutil.ToFloat64(errorsTotal.WithLabelValues("nixl", StageEncode)))
6772
}
6873

6974
// Register must be idempotent so repeated calls do not panic on duplicate

pkg/sidecar/proxy/connector_ec_nixl.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import (
66
"fmt"
77
"net/http"
88
"sync"
9+
"time"
910

1011
"github.com/google/uuid"
1112
logging "github.com/llm-d/llm-d-router/pkg/common/observability/logging"
13+
"github.com/llm-d/llm-d-router/pkg/sidecar/metrics"
1214
)
1315

1416
// fanoutEncoderCollect fans out per-image encoder requests and merges
@@ -102,14 +104,17 @@ func (s *Server) handleECNIXL(w http.ResponseWriter, r *http.Request, prefillEnd
102104

103105
// Step 1: fan out to encoders, collect per-image ec_transfer_params.
104106
if len(encodeEndPoints) > 0 {
107+
encodeStart := time.Now()
105108
params, contributed, total, err := s.fanoutEncoderCollect(r.Context(), completionRequest, encodeEndPoints, requestID)
106109
if err != nil {
110+
metrics.RecordError(s.config.ECConnector, metrics.StageEncode)
107111
s.logger.Error(err, "encoder processing failed", "requestID", requestID)
108112
if err := errorBadGateway(err, w); err != nil {
109113
s.logger.Error(err, "failed to send error response to client")
110114
}
111115
return
112116
}
117+
metrics.RecordEncodeDuration(s.config.ECConnector, time.Since(encodeStart))
113118
if total > 0 {
114119
// All-missing degrades silently to primer-mode; warn so the
115120
// operator sees the regression.

pkg/sidecar/proxy/options.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ const (
7373
inlineConfiguration = "configuration"
7474
configurationFile = "configuration-file"
7575
tracingFlag = "tracing"
76-
metricsPortFlag = "metrics-port"
7776

7877
// Deprecated flags
7978
connector = "connector"
@@ -93,7 +92,6 @@ const (
9392
defaultVLLMPort = "8001"
9493
defaultDataParallelSize = 1
9594
defaultMooncakeBootstrapPort = 8998
96-
defaultMetricsPort = 9090
9795

9896
// TLS stages
9997
prefillStage = "prefiller"
@@ -127,7 +125,6 @@ type yamlConfiguration struct {
127125
PrefillRetryBackoff string `json:"prefill-retry-backoff,omitempty"`
128126
DecodeChunkSize int `json:"decode-chunk-size,omitempty"`
129127
Tracing *bool `json:"tracing,omitempty"`
130-
MetricsPort *int `json:"metrics-port,omitempty"`
131128
}
132129

133130
// Options holds the CLI-facing configuration for the pd-sidecar proxy.
@@ -216,7 +213,6 @@ func NewOptions() *Options {
216213
PoolGroup: routing.InferencePoolAPIGroup,
217214
DecodeChunkSize: 0,
218215
Tracing: false,
219-
MetricsPort: defaultMetricsPort,
220216
// MoRI-IO defaults: off, preserving existing NIXLv2 behaviour.
221217
// Port defaults match vLLM's MoRI-IO connector defaults.
222218
MoRIIOWriteMode: false,
@@ -266,7 +262,6 @@ func (opts *Options) AddFlags(fs *pflag.FlagSet) {
266262
fs.StringVar(&opts.PoolGroup, poolGroup, opts.PoolGroup, "group of the InferencePool this Endpoint Picker is associated with.")
267263
fs.IntVar(&opts.DecodeChunkSize, decodeChunkSize, opts.DecodeChunkSize, "enables chunked decode mode when > 0; value is the token budget per chunk. For best performance should be a multiple of the block size.")
268264
fs.BoolVar(&opts.Tracing, tracingFlag, opts.Tracing, "Enable OpenTelemetry tracing")
269-
fs.IntVar(&opts.MetricsPort, metricsPortFlag, opts.MetricsPort, "the port the Prometheus /metrics endpoint listens on (0 disables it)")
270265

271266
// MoRI-IO WRITE-mode flags. Only meaningful with --kv-connector=nixlv2
272267
// against vLLM engines running MoRI-IO in WRITE mode.
@@ -551,11 +546,6 @@ func (opts *Options) Validate() error {
551546
return fmt.Errorf("--mooncake-bootstrap-port must be between 1 and 65535, got %d", opts.MooncakeBootstrapPort)
552547
}
553548

554-
// Validate metrics port (0 disables the metrics server)
555-
if opts.MetricsPort < 0 || opts.MetricsPort > 65535 {
556-
return fmt.Errorf("--metrics-port must be between 0 and 65535 (0 disables it), got %d", opts.MetricsPort)
557-
}
558-
559549
// Validate SSRF protection requirements
560550
if opts.EnableSSRFProtection {
561551
if opts.InferencePoolNamespace == "" || opts.InferencePoolName == "" {
@@ -647,9 +637,6 @@ func (opts *Options) mergeYAMLConfiguration(cfg yamlConfiguration) {
647637
if cfg.MooncakeBootstrapPort != 0 && !opts.isFlagSet(mooncakeBootstrapPortFlag) {
648638
opts.MooncakeBootstrapPort = cfg.MooncakeBootstrapPort
649639
}
650-
if cfg.MetricsPort != nil && !opts.isFlagSet(metricsPortFlag) {
651-
opts.MetricsPort = *cfg.MetricsPort
652-
}
653640
if cfg.DataParallelSize != 0 && !opts.isFlagSet(dataParallelSize) {
654641
opts.DataParallelSize = cfg.DataParallelSize
655642
}

pkg/sidecar/proxy/options_test.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -621,32 +621,6 @@ func TestValidateConnector(t *testing.T) {
621621
}
622622
}
623623

624-
func TestValidateMetricsPort(t *testing.T) {
625-
tests := []struct {
626-
name string
627-
metricsPort int
628-
wantErr bool
629-
}{
630-
{"default", defaultMetricsPort, false},
631-
{"disabled", 0, false},
632-
{"max", 65535, false},
633-
{"negative", -1, true},
634-
{"too large", 65536, true},
635-
}
636-
637-
for _, tt := range tests {
638-
t.Run(tt.name, func(t *testing.T) {
639-
opts := NewOptions()
640-
opts.MetricsPort = tt.metricsPort
641-
_ = opts.Complete() // Complete must be called before Validate
642-
err := opts.Validate()
643-
if (err != nil) != tt.wantErr {
644-
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
645-
}
646-
})
647-
}
648-
}
649-
650624
func TestValidateTLSStages(t *testing.T) {
651625
tests := []struct {
652626
name string

pkg/sidecar/proxy/proxy.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ import (
3131

3232
"github.com/go-logr/logr"
3333
lru "github.com/hashicorp/golang-lru/v2"
34+
"github.com/prometheus/client_golang/prometheus/promhttp"
3435
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
3536
"golang.org/x/sync/errgroup"
3637
"sigs.k8s.io/controller-runtime/pkg/log"
38+
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
3739

3840
"github.com/llm-d/llm-d-router/pkg/sidecar/constants"
41+
"github.com/llm-d/llm-d-router/pkg/sidecar/metrics"
3942
)
4043

4144
const (
@@ -189,10 +192,6 @@ type Config struct {
189192
// InsecureSkipVerifyForDecoder configures the proxy to skip TLS verification for requests to the decoder.
190193
InsecureSkipVerifyForDecoder bool
191194

192-
// MetricsPort is the port the Prometheus /metrics endpoint listens on.
193-
// 0 disables the metrics server.
194-
MetricsPort int
195-
196195
// SecureServing enables TLS for the sidecar server itself.
197196
SecureServing bool
198197
// CertPath is the path to TLS certificates for the sidecar server.
@@ -490,6 +489,9 @@ func (s *Server) createRoutes() *http.ServeMux {
490489
mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
491490
w.WriteHeader(http.StatusOK)
492491
})
492+
493+
metrics.Register()
494+
mux.Handle("GET /metrics", promhttp.HandlerFor(ctrlmetrics.Registry, promhttp.HandlerOpts{EnableOpenMetrics: true}))
493495
mux.HandleFunc("POST "+ChatCompletionsPath, s.disaggregatedPrefillHandler(APITypeChatCompletions))
494496
mux.HandleFunc("POST "+CompletionsPath, s.disaggregatedPrefillHandler(APITypeChatCompletions))
495497
mux.HandleFunc("POST "+MessagesPath, s.disaggregatedPrefillHandler(APITypeChatCompletions))

0 commit comments

Comments
 (0)