From 690cb4828093dbd92257be7536801360b07d8843 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Fri, 16 Oct 2026 13:36:29 +0800 Subject: [PATCH] feat(vllm): refine monitor snapshots and historical request counts (#14109) --- agent/app/dto/monitor.go | 6 +++ agent/app/model/monitor.go | 2 + agent/app/repo/monitor_vllm.go | 12 ++++- agent/app/service/monitor.go | 2 +- agent/app/service/monitor_vllm.go | 62 ++++++++++++++----------- agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 7 +++ frontend/src/lang/modules/en.ts | 8 ++++ frontend/src/lang/modules/es-es.ts | 8 ++++ frontend/src/lang/modules/fa.ts | 8 ++++ frontend/src/lang/modules/ja.ts | 8 ++++ frontend/src/lang/modules/ko.ts | 8 ++++ frontend/src/lang/modules/lo.ts | 8 ++++ frontend/src/lang/modules/ms.ts | 8 ++++ frontend/src/lang/modules/pt-br.ts | 8 ++++ frontend/src/lang/modules/ru.ts | 8 ++++ frontend/src/lang/modules/tr.ts | 8 ++++ frontend/src/lang/modules/zh-Hant.ts | 8 ++++ frontend/src/lang/modules/zh.ts | 8 ++++ 19 files changed, 157 insertions(+), 31 deletions(-) diff --git a/agent/app/dto/monitor.go b/agent/app/dto/monitor.go index 6392e5564..2bf977ea2 100644 --- a/agent/app/dto/monitor.go +++ b/agent/app/dto/monitor.go @@ -1,6 +1,7 @@ package dto import ( + "encoding/json" "time" "github.com/1Panel-dev/1Panel/agent/app/model" @@ -144,6 +145,11 @@ type MonitorVLLMCurrent struct { AppInstallID uint `json:"appInstallID" validate:"required"` } +type MonitorVLLMCurrentData struct { + model.MonitorVLLM + Metrics map[string]json.RawMessage `json:"metrics,omitempty"` +} + type MonitorVLLMClean struct { AppInstallID uint `json:"appInstallID" validate:"required"` } diff --git a/agent/app/model/monitor.go b/agent/app/model/monitor.go index ebe2f3cb7..d1c26a9e8 100644 --- a/agent/app/model/monitor.go +++ b/agent/app/model/monitor.go @@ -86,7 +86,9 @@ type MonitorVLLM struct { RawMetrics string `json:"-"` HistogramDeltas string `json:"-"` IntervalSeconds float64 `json:"intervalSeconds" gorm:"-"` + SampleIntervalSeconds int `json:"-"` RequestDeltas string `json:"-"` + CompletedRequests *float64 `json:"completedRequests" gorm:"-"` QueueTime *float64 `json:"queueTime"` AverageGenerationTokens *float64 `json:"averageGenerationTokens"` PrefixCacheHitRate *float64 `json:"prefixCacheHitRate"` diff --git a/agent/app/repo/monitor_vllm.go b/agent/app/repo/monitor_vllm.go index a8c2087ec..3c19f8844 100644 --- a/agent/app/repo/monitor_vllm.go +++ b/agent/app/repo/monitor_vllm.go @@ -16,7 +16,8 @@ type VLLMMonitorRepo struct{} type VLLMHistoryPoint struct { model.MonitorVLLM - Bucket int64 + Bucket int64 + CompletedRequests *float64 } func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error { @@ -77,12 +78,19 @@ func (r *VLLMMonitorRepo) History(ctx context.Context, id uint, start, end time. } db = db.Group("bucket").Order("bucket ASC") } else { - columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...) + columns = append([]string{"id", "created_at", "app_install_id", "status", "sample_interval_seconds"}, metrics...) if group == "" || group == "requests" { columns = append(columns, "histogram_deltas", "request_deltas") } db = db.Order("created_at ASC, id ASC") } + if group == "" || group == "overview" { + count := "CASE WHEN json_valid(request_deltas) THEN CASE WHEN json_extract(request_deltas, '$.seconds') > 0 THEN ROUND(request_throughput * json_extract(request_deltas, '$.seconds')) END END" + if seconds > 0 { + count = "SUM(" + count + ")" + } + columns = append(columns, count+" AS completed_requests") + } var points []VLLMHistoryPoint err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error return points, err diff --git a/agent/app/service/monitor.go b/agent/app/service/monitor.go index 852efb06a..a3df565ce 100644 --- a/agent/app/service/monitor.go +++ b/agent/app/service/monitor.go @@ -55,7 +55,7 @@ type IMonitorService interface { LoadIOOptions() []string LoadNetworkOptions() []string LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) - LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) + LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error) CleanVLLMMonitor(req dto.MonitorVLLMClean) error LoadGPUOptions() dto.MonitorGPUOptions LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) diff --git a/agent/app/service/monitor_vllm.go b/agent/app/service/monitor_vllm.go index d780edf52..4ba408d3a 100644 --- a/agent/app/service/monitor_vllm.go +++ b/agent/app/service/monitor_vllm.go @@ -60,6 +60,9 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito if err != nil { return data, err } + for i := range points { + points[i].MonitorVLLM.CompletedRequests = points[i].CompletedRequests + } if data.BucketSeconds > 0 { aggregates := map[int64]*vllm.HistoryAccumulator{} err = vllmMonitorRepo.WalkDeltas(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group, func(bucket int64, histograms, requests string) error { @@ -91,15 +94,6 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito data.Points = append(data.Points, point) } } else { - setting, err := settingRepo.GetValueByKey("VLLMMonitorInterval") - if err != nil { - return data, err - } - intervalSeconds, err := strconv.Atoi(setting) - if err != nil || intervalSeconds <= 0 { - return data, fmt.Errorf("invalid vLLM monitoring interval: %s", setting) - } - interval := time.Duration(intervalSeconds) * time.Second for i, point := range points { if err := ctx.Err(); err != nil { return data, err @@ -109,8 +103,20 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito return data, err } aggregate.Apply(&point.MonitorVLLM, "max") - if i > 0 && point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval { - data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: points[i-1].CreatedAt.Add(interval), AppInstallID: req.AppInstallID}) + if i > 0 { + previous := points[i-1] + interval := time.Duration(max(previous.SampleIntervalSeconds, point.SampleIntervalSeconds)) * time.Second + if interval == 0 { + if i > 1 { + interval = previous.CreatedAt.Sub(points[i-2].CreatedAt) + } + if i+1 < len(points) { + interval = max(interval, points[i+1].CreatedAt.Sub(point.CreatedAt)) + } + } + if interval > 0 && point.CreatedAt.Sub(previous.CreatedAt) > 2*interval { + data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: previous.CreatedAt.Add(interval), AppInstallID: req.AppInstallID}) + } } data.Points = append(data.Points, point.MonitorVLLM) } @@ -118,31 +124,30 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito return data, nil } -func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) { +func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error) { install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) if err != nil { - return model.MonitorVLLM{}, err + return dto.MonitorVLLMCurrentData{}, err } if install.App.Key != "vllm" { - return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation") + return dto.MonitorVLLMCurrentData{}, fmt.Errorf("not a vLLM installation") } - previous, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{}) + point, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{}) + data := dto.MonitorVLLMCurrentData{MonitorVLLM: point} if err != nil { global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err) - return previous, nil + return data, nil } - timer := time.NewTimer(time.Second) - defer timer.Stop() - select { - case <-ctx.Done(): - return model.MonitorVLLM{}, ctx.Err() - case <-timer.C: + if err := json.Unmarshal([]byte(point.RawMetrics), &data.Metrics); err != nil { + return data, err } - point, err := collectVLLMMetrics(ctx, install, previous) - if err != nil { - global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err) + for name := range data.Metrics { + if name == "request_prompt_tokens_sum" || name == "request_prompt_tokens_count" || + (!strings.HasSuffix(name, "_total") && !strings.HasSuffix(name, "_sum") && !strings.HasSuffix(name, "_count")) { + delete(data.Metrics, name) + } } - return point, nil + return data, nil } func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error { @@ -164,7 +169,7 @@ func StartVLLMMonitor(interval string) error { return fmt.Errorf("invalid vLLM monitoring interval: %s", interval) } service := &MonitorService{} - job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData)) + job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(func() { service.saveVLLMData(seconds) })) id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() }) if err != nil { return err @@ -176,7 +181,7 @@ func StartVLLMMonitor(interval string) error { return nil } -func (m *MonitorService) saveVLLMData() { +func (m *MonitorService) saveVLLMData(intervalSeconds int) { if !vllmMonitorMutex.TryLock() { return } @@ -228,6 +233,7 @@ func (m *MonitorService) saveVLLMData() { return err } point, err := collectVLLMMetrics(context.Background(), install, previous) + point.SampleIntervalSeconds = intervalSeconds if err != nil { global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err) } diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index a750287fa..8570a1937 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -114,6 +114,7 @@ func agentDBMigrations() []*gormigrate.Migration { migrations.AddVLLMMonitor, migrations.AddMonitorSettings, migrations.AddVLLMRequestMetrics, + migrations.AddVLLMSampleInterval, } } diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 78f846423..0a80f3ed9 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1881,3 +1881,10 @@ var AddVLLMRequestMetrics = &gormigrate.Migration{ return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{}) }, } + +var AddVLLMSampleInterval = &gormigrate.Migration{ + ID: "20261016-vllm-sample-interval", + Migrate: func(tx *gorm.DB) error { + return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{}) + }, +} diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index c3d2d98df..023182701 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1517,6 +1517,14 @@ const message = { extraHeaders: 'Extra Headers', }, vllmMonitor: { + concurrency: 'Current concurrency', + sampleMoment: 'At sampling time', + sampleWindow: 'Sample interval', + requestUnit: 'requests', + processing: 'Processing', + queued: 'Queued', + requestTiming: 'Request timing', + completedRequests: 'Requests completed in interval', tokensPerSecond: 'tokens/s', requestsPerSecond: 'Requests/s', finishReasonLabels: { diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index de9981b85..6cf8923e7 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1526,6 +1526,14 @@ const message = { extraHeaders: 'Encabezados adicionales', }, vllmMonitor: { + concurrency: 'Concurrencia actual', + sampleMoment: 'Al tomar la muestra', + sampleWindow: 'Intervalo de muestreo', + requestUnit: 'solicitudes', + processing: 'En proceso', + queued: 'En cola', + requestTiming: 'Tiempo de las solicitudes', + completedRequests: 'Solicitudes completadas en el intervalo', tokensPerSecond: 'tokens/s', requestsPerSecond: 'Solicitudes/s', finishReasonLabels: { diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 824f6517c..993521945 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -1502,6 +1502,14 @@ const message = { extraHeaders: 'هدرهای اضافی', }, vllmMonitor: { + concurrency: 'درخواست‌های هم‌زمان فعلی', + sampleMoment: 'در لحظه نمونه‌برداری', + sampleWindow: 'بازه نمونه‌برداری', + requestUnit: 'درخواست', + processing: 'در حال پردازش', + queued: 'در صف', + requestTiming: 'زمان پردازش درخواست', + completedRequests: 'تعداد درخواست‌های تکمیل‌شده در بازه', tokensPerSecond: 'tokens/ثانیه', requestsPerSecond: 'درخواست/ثانیه', finishReasonLabels: { diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 962a78342..5a2235ad8 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1505,6 +1505,14 @@ const message = { extraHeaders: '追加ヘッダー', }, vllmMonitor: { + concurrency: '現在の同時リクエスト', + sampleMoment: 'サンプリング時点', + sampleWindow: '集計区間', + requestUnit: '件', + processing: '処理中', + queued: '待機中', + requestTiming: 'リクエスト処理時間', + completedRequests: '区間内の完了リクエスト数', tokensPerSecond: 'tokens/秒', requestsPerSecond: 'リクエスト/秒', finishReasonLabels: { diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index ddbc5891a..9103a3725 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1490,6 +1490,14 @@ const message = { extraHeaders: '추가 헤더', }, vllmMonitor: { + concurrency: '현재 동시 요청', + sampleMoment: '샘플링 시점', + sampleWindow: '집계 구간', + requestUnit: '개 요청', + processing: '처리 중', + queued: '대기 중', + requestTiming: '요청 소요 시간', + completedRequests: '구간 내 완료 요청 수', tokensPerSecond: 'tokens/초', requestsPerSecond: '요청/초', finishReasonLabels: { diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 56d52e1f7..876254904 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -1484,6 +1484,14 @@ const message = { extraHeaders: 'Headers ເພີ່ມເຕີມ', }, vllmMonitor: { + concurrency: 'ຄຳຮ້ອງຂໍພ້ອມກັນໃນປັດຈຸບັນ', + sampleMoment: 'ໃນເວລາເກັບຕົວຢ່າງ', + sampleWindow: 'ຊ່ວງເວລາເກັບຕົວຢ່າງ', + requestUnit: 'ຄຳຮ້ອງຂໍ', + processing: 'ກຳລັງປະມວນຜົນ', + queued: 'ກຳລັງລໍຖ້າ', + requestTiming: 'ເວລາປະມວນຜົນຄຳຮ້ອງຂໍ', + completedRequests: 'ຈຳນວນຄຳຮ້ອງຂໍທີ່ສຳເລັດໃນຊ່ວງເວລາ', tokensPerSecond: 'tokens/ວິນາທີ', requestsPerSecond: 'ຄຳຮ້ອງຂໍ/ວິນາທີ', finishReasonLabels: { diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 17a12c69e..be5a7c1f9 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1531,6 +1531,14 @@ const message = { extraHeaders: 'Pengepala Tambahan', }, vllmMonitor: { + concurrency: 'Permintaan serentak semasa', + sampleMoment: 'Pada masa pensampelan', + sampleWindow: 'Selang pensampelan', + requestUnit: 'permintaan', + processing: 'Sedang diproses', + queued: 'Dalam baris gilir', + requestTiming: 'Tempoh permintaan', + completedRequests: 'Permintaan selesai dalam selang masa', tokensPerSecond: 'tokens/s', requestsPerSecond: 'Permintaan/s', finishReasonLabels: { diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 0d6792b74..ba1bc68fa 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1527,6 +1527,14 @@ const message = { extraHeaders: 'Cabeçalhos extras', }, vllmMonitor: { + concurrency: 'Concorrência atual', + sampleMoment: 'No momento da coleta', + sampleWindow: 'Intervalo de coleta', + requestUnit: 'solicitações', + processing: 'Em processamento', + queued: 'Na fila', + requestTiming: 'Tempo das solicitações', + completedRequests: 'Solicitações concluídas no intervalo', tokensPerSecond: 'tokens/s', requestsPerSecond: 'Solicitações/s', finishReasonLabels: { diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 030858330..197493503 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1520,6 +1520,14 @@ const message = { extraHeaders: 'Дополнительные заголовки', }, vllmMonitor: { + concurrency: 'Текущие параллельные запросы', + sampleMoment: 'На момент замера', + sampleWindow: 'Интервал замера', + requestUnit: 'запросов', + processing: 'В обработке', + queued: 'В очереди', + requestTiming: 'Время обработки запросов', + completedRequests: 'Завершённые запросы за интервал', tokensPerSecond: 'tokens/с', requestsPerSecond: 'Запросов/с', finishReasonLabels: { diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 0299c505d..674e015a9 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1527,6 +1527,14 @@ const message = { extraHeaders: 'Ek Başlıklar', }, vllmMonitor: { + concurrency: 'Mevcut eşzamanlı istekler', + sampleMoment: 'Örnekleme anında', + sampleWindow: 'Örnekleme aralığı', + requestUnit: 'istek', + processing: 'İşleniyor', + queued: 'Kuyrukta', + requestTiming: 'İstek süreleri', + completedRequests: 'Aralıkta tamamlanan istek sayısı', tokensPerSecond: 'tokens/sn', requestsPerSecond: 'İstek/sn', finishReasonLabels: { diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 0e89b4273..a7e060458 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1437,6 +1437,14 @@ const message = { extraHeaders: '額外請求標頭', }, vllmMonitor: { + concurrency: '目前並行請求', + sampleMoment: '取樣時刻', + sampleWindow: '統計區間', + requestUnit: '個請求', + processing: '處理中', + queued: '排隊中', + requestTiming: '請求耗時', + completedRequests: '區間完成請求數', tokensPerSecond: 'tokens/秒', requestsPerSecond: '請求/秒', finishReasonLabels: { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 8af92e703..4fd45d145 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1450,6 +1450,14 @@ const message = { extraHeaders: '额外请求头', }, vllmMonitor: { + concurrency: '当前并发', + sampleMoment: '采样时刻', + sampleWindow: '统计区间', + requestUnit: '个请求', + processing: '处理中', + queued: '排队中', + requestTiming: '请求耗时', + completedRequests: '区间完成请求数', tokensPerSecond: 'tokens/秒', requestsPerSecond: '请求/秒', finishReasonLabels: {