From d0183f460ee880539682f7c83af76863302fa132 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 29 Sep 2026 15:19:30 +0800 Subject: [PATCH] feat: add vLLM monitoring (#13942) --- agent/app/api/v2/monitor.go | 11 +- agent/app/api/v2/monitor_vllm.go | 45 +++ agent/app/dto/monitor.go | 50 ++- agent/app/model/monitor.go | 35 ++ agent/app/repo/monitor.go | 33 +- agent/app/repo/monitor_vllm.go | 78 ++++ agent/app/service/entry.go | 9 +- agent/app/service/monitor.go | 233 +++++++++--- agent/app/service/monitor_vllm.go | 279 +++++++++++++++ agent/cron/cron.go | 22 ++ agent/global/global.go | 19 +- agent/init/db/db.go | 1 + agent/init/migration/migrate.go | 2 + agent/init/migration/migrations/init.go | 25 ++ agent/router/ro_ai.go | 3 + .../utils/ai_tools/accelerator/accelerator.go | 8 - agent/utils/ai_tools/vllm/metrics.go | 335 ++++++++++++++++++ agent/utils/re/re.go | 4 + frontend/src/api/interface/ai.ts | 1 + frontend/src/api/interface/host.ts | 6 + frontend/src/api/modules/host.ts | 4 +- .../components/v-charts/components/Line.vue | 3 +- frontend/src/lang/modules/en.ts | 33 +- frontend/src/lang/modules/es-es.ts | 34 +- frontend/src/lang/modules/fa.ts | 33 +- frontend/src/lang/modules/ja.ts | 33 +- frontend/src/lang/modules/ko.ts | 33 +- frontend/src/lang/modules/lo.ts | 32 +- frontend/src/lang/modules/ms.ts | 33 +- frontend/src/lang/modules/pt-br.ts | 34 +- frontend/src/lang/modules/ru.ts | 33 +- frontend/src/lang/modules/tr.ts | 34 +- frontend/src/lang/modules/zh-Hant.ts | 32 +- frontend/src/lang/modules/zh.ts | 32 +- frontend/src/views/ai/gpu/history/index.vue | 159 ++++++--- .../views/host/monitor/setting/days/index.vue | 5 +- .../src/views/host/monitor/setting/index.vue | 162 +++++++-- .../host/monitor/setting/interval/index.vue | 6 +- frontend/src/views/log/operation/index.vue | 7 + 39 files changed, 1744 insertions(+), 197 deletions(-) create mode 100644 agent/app/api/v2/monitor_vllm.go create mode 100644 agent/app/repo/monitor_vllm.go create mode 100644 agent/app/service/monitor_vllm.go create mode 100644 agent/utils/ai_tools/vllm/metrics.go diff --git a/agent/app/api/v2/monitor.go b/agent/app/api/v2/monitor.go index e1c8ecc07..6012a5af7 100644 --- a/agent/app/api/v2/monitor.go +++ b/agent/app/api/v2/monitor.go @@ -32,14 +32,19 @@ func (b *BaseApi) LoadMonitor(c *gin.Context) { } // @Tags Monitor -// @Summary Clean monitor data +// @Summary Clean host or GPU monitor data +// @Param request body dto.MonitorClean true "request" // @Success 200 // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/monitor/clean [post] -// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"} +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 [type] 监控数据","formatEN":"clean [type] monitoring data"} func (b *BaseApi) CleanMonitor(c *gin.Context) { - if err := monitorService.CleanData(); err != nil { + var req dto.MonitorClean + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := monitorService.CleanData(req.Type); err != nil { helper.InternalServer(c, err) return } diff --git a/agent/app/api/v2/monitor_vllm.go b/agent/app/api/v2/monitor_vllm.go new file mode 100644 index 000000000..199554af5 --- /dev/null +++ b/agent/app/api/v2/monitor_vllm.go @@ -0,0 +1,45 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) LoadVLLMMonitor(c *gin.Context) { + var req dto.MonitorVLLMSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := monitorService.LoadVLLMMonitorData(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +func (b *BaseApi) LoadVLLMCurrent(c *gin.Context) { + var req dto.MonitorVLLMCurrent + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := monitorService.LoadVLLMCurrent(c.Request.Context(), req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +func (b *BaseApi) CleanVLLMMonitor(c *gin.Context) { + var req dto.MonitorVLLMClean + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := monitorService.CleanVLLMMonitor(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/monitor.go b/agent/app/dto/monitor.go index 195d3e5b2..0aa83f4cb 100644 --- a/agent/app/dto/monitor.go +++ b/agent/app/dto/monitor.go @@ -1,6 +1,10 @@ package dto -import "time" +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" +) type MonitorSearch struct { Param string `json:"param" validate:"required,oneof=all cpu memory load io network"` @@ -26,19 +30,26 @@ type Process struct { } type MonitorSetting struct { - MonitorStatus string `json:"monitorStatus"` - MonitorStoreDays string `json:"monitorStoreDays"` - MonitorInterval string `json:"monitorInterval"` - DefaultNetwork string `json:"defaultNetwork"` - DefaultIO string `json:"defaultIO"` + GPUMonitorStatus string `json:"gpuMonitorStatus"` + GPUMonitorStoreDays string `json:"gpuMonitorStoreDays"` + GPUMonitorInterval string `json:"gpuMonitorInterval"` + VLLMMonitorStatus string `json:"vllmMonitorStatus"` + VLLMMonitorStoreDays string `json:"vllmMonitorStoreDays"` + VLLMMonitorInterval string `json:"vllmMonitorInterval"` + MonitorStatus string `json:"monitorStatus"` + MonitorStoreDays string `json:"monitorStoreDays"` + MonitorInterval string `json:"monitorInterval"` + DefaultNetwork string `json:"defaultNetwork"` + DefaultIO string `json:"defaultIO"` } type MonitorSettingUpdate struct { - Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork DefaultIO"` + Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval GPUMonitorStatus GPUMonitorStoreDays GPUMonitorInterval VLLMMonitorStatus VLLMMonitorStoreDays VLLMMonitorInterval DefaultNetwork DefaultIO"` Value string `json:"value"` } type MonitorGPUOptions struct { + Supported bool `json:"supported"` GPUType string `json:"gpuType"` ChartHide []GPUChartHide `json:"chartHide"` Options []string `json:"options"` @@ -114,3 +125,28 @@ type GPUProcess struct { ProcessName string `json:"processName"` UsedMemory string `json:"usedMemory"` } + +type MonitorVLLMSearch struct { + AppInstallID uint `json:"appInstallID" validate:"required"` + StartTime time.Time `json:"startTime" validate:"required"` + EndTime time.Time `json:"endTime" validate:"required"` + Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"` +} + +type MonitorVLLMData struct { + SampleCount int64 `json:"sampleCount"` + BucketSeconds int64 `json:"bucketSeconds"` + Points []model.MonitorVLLM `json:"points"` +} + +type MonitorVLLMCurrent struct { + AppInstallID uint `json:"appInstallID" validate:"required"` +} + +type MonitorVLLMClean struct { + AppInstallID uint `json:"appInstallID" validate:"required"` +} + +type MonitorClean struct { + Type string `json:"type" validate:"required,oneof=host gpu"` +} diff --git a/agent/app/model/monitor.go b/agent/app/model/monitor.go index 6344b787e..43ad63ec8 100644 --- a/agent/app/model/monitor.go +++ b/agent/app/model/monitor.go @@ -1,5 +1,7 @@ package model +import "time" + type MonitorBase struct { BaseModel Cpu float64 `json:"cpu"` @@ -75,3 +77,36 @@ type MonitorGPU struct { FanSpeed *float64 `json:"fanSpeed"` Processes string `json:"processes"` } + +type MonitorVLLM struct { + ID uint `json:"-" gorm:"primarykey;autoIncrement"` + CreatedAt time.Time `json:"createdAt"` + AppInstallID uint `json:"appInstallID"` + Status string `json:"status"` + RawMetrics string `json:"-"` + HistogramDeltas string `json:"-"` + + Running *float64 `json:"running"` + Waiting *float64 `json:"waiting"` + CacheUsage *float64 `json:"cacheUsage"` + PromptThroughput *float64 `json:"promptThroughput"` + GenerationThroughput *float64 `json:"generationThroughput"` + RequestThroughput *float64 `json:"requestThroughput"` + TimeToFirstToken *float64 `json:"timeToFirstToken"` + TimePerOutputToken *float64 `json:"timePerOutputToken"` + RequestLatency *float64 `json:"requestLatency"` + PrefillTime *float64 `json:"prefillTime"` + DecodeTime *float64 `json:"decodeTime"` + TimeToFirstTokenP50 *float64 `json:"timeToFirstTokenP50"` + TimeToFirstTokenP90 *float64 `json:"timeToFirstTokenP90"` + TimeToFirstTokenP95 *float64 `json:"timeToFirstTokenP95"` + TimeToFirstTokenP99 *float64 `json:"timeToFirstTokenP99"` + TimePerOutputTokenP50 *float64 `json:"timePerOutputTokenP50"` + TimePerOutputTokenP90 *float64 `json:"timePerOutputTokenP90"` + TimePerOutputTokenP95 *float64 `json:"timePerOutputTokenP95"` + TimePerOutputTokenP99 *float64 `json:"timePerOutputTokenP99"` + RequestLatencyP50 *float64 `json:"requestLatencyP50"` + RequestLatencyP90 *float64 `json:"requestLatencyP90"` + RequestLatencyP95 *float64 `json:"requestLatencyP95"` + RequestLatencyP99 *float64 `json:"requestLatencyP99"` +} diff --git a/agent/app/repo/monitor.go b/agent/app/repo/monitor.go index a5b2c5f4c..8da1820da 100644 --- a/agent/app/repo/monitor.go +++ b/agent/app/repo/monitor.go @@ -21,8 +21,9 @@ type GPUHistoryPoint struct { } type IMonitorRepo interface { + CleanHost() error + CleanGPU() error GetBase(opts ...DBOption) ([]model.MonitorBase, error) - GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) CountGPU(opts ...DBOption) (int64, error) GetGPUHistory(start time.Time, bucketSeconds int64, aggregation string, opts ...DBOption) ([]GPUHistoryPoint, error) GetGPUDevices() ([]model.MonitorGPU, error) @@ -38,7 +39,6 @@ type IMonitorRepo interface { DelMonitorIO(timeForDelete time.Time) error DelMonitorNet(timeForDelete time.Time) error - WithByProductName(name string) DBOption WithByGPUDevice(deviceID, name string, legacy bool) DBOption } @@ -46,6 +46,19 @@ func NewIMonitorRepo() IMonitorRepo { return &MonitorRepo{} } +func (s *MonitorRepo) CleanHost() error { + for _, item := range []interface{}{&model.MonitorBase{}, &model.MonitorIO{}, &model.MonitorNetwork{}} { + if err := global.MonitorDB.Where("1 = 1").Delete(item).Error; err != nil { + return err + } + } + return nil +} + +func (s *MonitorRepo) CleanGPU() error { + return global.GPUMonitorDB.Where("1 = 1").Delete(&model.MonitorGPU{}).Error +} + func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) { var data []model.MonitorBase db := global.MonitorDB @@ -73,16 +86,6 @@ func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, erro err := db.Find(&data).Error return data, err } -func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) { - var data []model.MonitorGPU - db := global.GPUMonitorDB - for _, opt := range opts { - db = opt(db) - } - err := db.Find(&data).Error - return data, err -} - func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error { return global.MonitorDB.Create(&model).Error } @@ -111,12 +114,6 @@ func (s *MonitorRepo) DelMonitorGPU(timeForDelete time.Time) error { return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error } -func (s *MonitorRepo) WithByProductName(name string) DBOption { - return func(g *gorm.DB) *gorm.DB { - return g.Where("product_name = ?", name) - } -} - func (u *MonitorRepo) GetGPUDevices() ([]model.MonitorGPU, error) { var data []model.MonitorGPU err := global.GPUMonitorDB.Model(&model.MonitorGPU{}).Select("device_id, product_name, device_type").Group("device_id, product_name, device_type").Order("product_name, device_id").Find(&data).Error diff --git a/agent/app/repo/monitor_vllm.go b/agent/app/repo/monitor_vllm.go new file mode 100644 index 000000000..a03a79c8a --- /dev/null +++ b/agent/app/repo/monitor_vllm.go @@ -0,0 +1,78 @@ +package repo + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type VLLMMonitorRepo struct{} + +type VLLMHistoryPoint struct { + model.MonitorVLLM + Bucket int64 + HistogramSamples string +} + +func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error { + return global.VLLMMonitorDB.Create(point).Error +} + +func (r *VLLMMonitorRepo) Latest(id uint) (model.MonitorVLLM, error) { + var point model.MonitorVLLM + db := global.VLLMMonitorDB.Where("app_install_id = ?", id) + err := db.Order("created_at DESC, id DESC").First(&point).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return point, nil + } + return point, err +} + +func (r *VLLMMonitorRepo) CleanTarget(id uint) error { + return global.VLLMMonitorDB.Where("app_install_id = ?", id).Delete(&model.MonitorVLLM{}).Error +} + +func (r *VLLMMonitorRepo) DeleteBefore(before time.Time) error { + return global.VLLMMonitorDB.Where("created_at < ?", before).Delete(&model.MonitorVLLM{}).Error +} + +func (r *VLLMMonitorRepo) Count(id uint, start, end time.Time) (int64, error) { + var count int64 + db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end) + err := db.Count(&count).Error + return count, err +} + +func (r *VLLMMonitorRepo) History(id uint, start, end time.Time, seconds int64, aggregation string) ([]VLLMHistoryPoint, error) { + db := global.VLLMMonitorDB.Model(&model.MonitorVLLM{}).Where("app_install_id = ? AND created_at >= ? AND created_at <= ?", id, start, end) + metrics := []string{"running", "waiting", "cache_usage", "prompt_throughput", "generation_throughput", "request_throughput", "time_to_first_token", "time_per_output_token", "request_latency", "prefill_time", "decode_time", "time_to_first_token_p50", "time_to_first_token_p90", "time_to_first_token_p95", "time_to_first_token_p99", "time_per_output_token_p50", "time_per_output_token_p90", "time_per_output_token_p95", "time_per_output_token_p99", "request_latency_p50", "request_latency_p90", "request_latency_p95", "request_latency_p99"} + var columns []string + if seconds > 0 { + operation := "AVG" + if aggregation == "max" { + operation = "MAX" + } + columns = []string{fmt.Sprintf("(CAST(strftime('%%s', created_at) AS INTEGER) - %d) / %d AS bucket", start.Unix(), seconds)} + for _, column := range metrics { + if aggregation != "max" && (strings.HasPrefix(column, "time_to_first_token_p") || strings.HasPrefix(column, "time_per_output_token_p") || strings.HasPrefix(column, "request_latency_p")) { + continue + } + columns = append(columns, operation+"("+column+") AS "+column) + } + if aggregation != "max" { + columns = append(columns, "json_group_array(json(NULLIF(histogram_deltas, ''))) AS histogram_samples") + } + db = db.Group("bucket").Order("bucket ASC") + } else { + columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...) + db = db.Order("created_at ASC, id ASC") + } + var points []VLLMHistoryPoint + err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error + return points, err +} diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go index 7a6b3d554..661bb70ca 100644 --- a/agent/app/service/entry.go +++ b/agent/app/service/entry.go @@ -32,10 +32,11 @@ var ( scriptRepo = repo.NewIScriptRepo() cronjobRepo = repo.NewICronjobRepo() - hostRepo = repo.NewIHostRepo() - ftpRepo = repo.NewIFtpRepo() - clamRepo = repo.NewIClamRepo() - monitorRepo = repo.NewIMonitorRepo() + hostRepo = repo.NewIHostRepo() + ftpRepo = repo.NewIFtpRepo() + clamRepo = repo.NewIClamRepo() + monitorRepo = repo.NewIMonitorRepo() + vllmMonitorRepo = &repo.VLLMMonitorRepo{} settingRepo = repo.NewISettingRepo() forwardingRuleRepo = repo.NewIForwardingRuleRepo() diff --git a/agent/app/service/monitor.go b/agent/app/service/monitor.go index d0f5f9156..f31876c82 100644 --- a/agent/app/service/monitor.go +++ b/agent/app/service/monitor.go @@ -33,11 +33,14 @@ import ( type MonitorService struct { DiskIO chan ([]disk.IOCountersStat) NetIO chan ([]net.IOCountersStat) + ctx context.Context } var ( - monitorCancel context.CancelFunc - hostSysPath = loadHostSysPath() + monitorSettingMutex sync.Mutex + gpuMonitorMutex sync.Mutex + monitorCancel context.CancelFunc + hostSysPath = loadHostSysPath() blockDevicePartitionCache sync.Map ) @@ -47,8 +50,11 @@ type IMonitorService interface { LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error) LoadSetting() (*dto.MonitorSetting, error) UpdateSetting(key, value string) error - CleanData() error + CleanData(monitorType string) error + LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) + LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) + CleanVLLMMonitor(req dto.MonitorVLLMClean) error LoadGPUOptions() dto.MonitorGPUOptions LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) @@ -60,6 +66,7 @@ func NewIMonitorService() IMonitorService { return &MonitorService{ DiskIO: make(chan []disk.IOCountersStat, 2), NetIO: make(chan []net.IOCountersStat, 2), + ctx: context.Background(), } } @@ -128,18 +135,21 @@ func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorDa func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions { var data dto.MonitorGPUOptions + exist, client := accelerator.New() + if !exist { + return data + } seen := make(map[string]bool) - if exist, client := accelerator.New(); exist { - snapshot, err := client.Collect(context.Background()) - if err != nil { - global.LOG.Warnf("Load accelerator options failed: %v", err) - } else { - data = loadGPUOptions(snapshot) - for _, item := range data.ChartHide { - seen[item.DeviceID] = true - } + snapshot, err := client.Collect(context.Background()) + if err != nil { + global.LOG.Warnf("Load accelerator options failed: %v", err) + } else { + data = loadGPUOptions(snapshot) + for _, item := range data.ChartHide { + seen[item.DeviceID] = true } } + data.Supported = true devices, err := monitorRepo.GetGPUDevices() if err != nil { global.LOG.Warnf("Load accelerator history options failed: %v", err) @@ -282,9 +292,27 @@ func (m *MonitorService) LoadSetting() (*dto.MonitorSetting, error) { } func (m *MonitorService) UpdateSetting(key, value string) error { + monitorSettingMutex.Lock() + defer monitorSettingMutex.Unlock() + switch key { + case "MonitorStatus", "GPUMonitorStatus", "VLLMMonitorStatus": + if value != constant.StatusEnable && value != constant.StatusDisable { + return fmt.Errorf("invalid monitoring status") + } + case "MonitorInterval", "GPUMonitorInterval", "VLLMMonitorInterval": + interval, err := strconv.Atoi(value) + if err != nil || interval < 10 || interval > 43200 { + return fmt.Errorf("monitoring interval must be between 10 and 43200 seconds") + } + case "MonitorStoreDays", "GPUMonitorStoreDays", "VLLMMonitorStoreDays": + days, err := strconv.Atoi(value) + if err != nil || days < 1 { + return fmt.Errorf("monitoring retention must be a positive integer") + } + } switch key { case "MonitorStatus": - if value == constant.StatusEnable && global.MonitorCronID == 0 { + if value == constant.StatusEnable && monitorCancel == nil { interval, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")) if err != nil { return err @@ -293,8 +321,9 @@ func (m *MonitorService) UpdateSetting(key, value string) error { return err } } - if value == constant.StatusDisable && global.MonitorCronID != 0 { + if value == constant.StatusDisable && monitorCancel != nil { monitorCancel() + monitorCancel = nil global.Cron.Remove(cron.EntryID(global.MonitorCronID)) global.MonitorCronID = 0 } @@ -303,30 +332,80 @@ func (m *MonitorService) UpdateSetting(key, value string) error { if err != nil { return err } - if status.Value == constant.StatusEnable && global.MonitorCronID != 0 { + if status.Value == constant.StatusEnable && monitorCancel != nil { if err := StartMonitor(true, value); err != nil { return err } } + case "GPUMonitorStatus": + if value == constant.StatusEnable && global.GPUMonitorCronID == 0 { + interval, err := settingRepo.GetValueByKey("GPUMonitorInterval") + if err != nil { + return err + } + if err := StartGPUMonitor(interval); err != nil { + return err + } + } + if value == constant.StatusDisable && global.GPUMonitorCronID != 0 { + global.Cron.Remove(global.GPUMonitorCronID) + global.GPUMonitorCronID = 0 + } + case "GPUMonitorInterval": + status, err := settingRepo.GetValueByKey("GPUMonitorStatus") + if err != nil { + return err + } + if status == constant.StatusEnable { + if err := StartGPUMonitor(value); err != nil { + return err + } + } + case "VLLMMonitorStatus": + if value == constant.StatusEnable && global.VLLMMonitorCronID == 0 { + interval, err := settingRepo.GetValueByKey("VLLMMonitorInterval") + if err != nil { + return err + } + if err := StartVLLMMonitor(interval); err != nil { + return err + } + } + if value == constant.StatusDisable && global.VLLMMonitorCronID != 0 { + global.Cron.Remove(global.VLLMMonitorCronID) + global.VLLMMonitorCronID = 0 + } + case "VLLMMonitorInterval": + status, err := settingRepo.GetValueByKey("VLLMMonitorStatus") + if err != nil { + return err + } + if status == constant.StatusEnable { + if err := StartVLLMMonitor(value); err != nil { + return err + } + } } return settingRepo.Update(key, value) } -func (m *MonitorService) CleanData() error { - if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil { - return err +func (m *MonitorService) CleanData(monitorType string) error { + switch monitorType { + case "host": + return monitorRepo.CleanHost() + case "gpu": + gpuMonitorMutex.Lock() + defer gpuMonitorMutex.Unlock() + return monitorRepo.CleanGPU() + default: + return fmt.Errorf("unsupported monitoring cleanup type: %s", monitorType) } - if err := global.MonitorDB.Exec("DELETE FROM monitor_ios").Error; err != nil { - return err - } - if err := global.MonitorDB.Exec("DELETE FROM monitor_networks").Error; err != nil { - return err - } - _ = global.GPUMonitorDB.Exec("DELETE FROM monitor_gpus").Error - return nil } func (m *MonitorService) Run() { + if m.ctx.Err() != nil { + return + } var itemModel model.MonitorBase totalPercent, _ := cpu.Percent(3*time.Second, false) if len(totalPercent) == 1 { @@ -362,7 +441,6 @@ func (m *MonitorService) Run() { m.loadDiskIO() m.loadNetIO() - m.saveGPUData() MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays")) if err != nil { @@ -373,30 +451,34 @@ func (m *MonitorService) Run() { _ = monitorRepo.DelMonitorBase(timeForDelete) _ = monitorRepo.DelMonitorIO(timeForDelete) _ = monitorRepo.DelMonitorNet(timeForDelete) - _ = monitorRepo.DelMonitorGPU(timeForDelete) } func (m *MonitorService) loadDiskIO() { - ioStat, _ := disk.IOCounters() + ioStat, _ := disk.IOCountersWithContext(m.ctx) var diskIOList []disk.IOCountersStat for _, io := range ioStat { diskIOList = append(diskIOList, io) } diskIOList = append(diskIOList, sumDiskIOCounters(ioStat)) - m.DiskIO <- diskIOList + select { + case <-m.ctx.Done(): + case m.DiskIO <- diskIOList: + } } func (m *MonitorService) loadNetIO() { - netStat, _ := net.IOCounters(true) - netStatAll, _ := net.IOCounters(false) + netStat, _ := net.IOCountersWithContext(m.ctx, true) + netStatAll, _ := net.IOCountersWithContext(m.ctx, false) var netList []net.IOCountersStat netList = append(netList, netStat...) netList = append(netList, netStatAll...) - m.NetIO <- netList + select { + case <-m.ctx.Done(): + case m.NetIO <- netList: + } } func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) { - defer close(m.DiskIO) for { select { case <-ctx.Done(): @@ -446,14 +528,17 @@ func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) { } } _ = monitorRepo.BatchCreateMonitorIO(ioList) - m.DiskIO <- ioStat2 + select { + case <-ctx.Done(): + return + case m.DiskIO <- ioStat2: + } } } } } func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) { - defer close(m.NetIO) for { select { case <-ctx.Done(): @@ -483,7 +568,11 @@ func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) } _ = monitorRepo.BatchCreateMonitorNet(netList) - m.NetIO <- netStat2 + select { + case <-ctx.Done(): + return + case m.NetIO <- netStat2: + } } } } @@ -592,27 +681,38 @@ func loadTopMem() []dto.Process { } func StartMonitor(removeBefore bool, interval string) error { + intervalItem, err := strconv.Atoi(interval) + if err != nil || intervalItem < 10 || intervalItem > 43200 { + return fmt.Errorf("invalid host monitoring interval: %s", interval) + } if removeBefore { monitorCancel() global.Cron.Remove(cron.EntryID(global.MonitorCronID)) } - intervalItem, err := strconv.Atoi(interval) - if err != nil { - return err - } - - service := NewIMonitorService() ctx, cancel := context.WithCancel(context.Background()) - monitorCancel = cancel + service := &MonitorService{ + DiskIO: make(chan []disk.IOCountersStat, 2), + NetIO: make(chan []net.IOCountersStat, 2), + ctx: ctx, + } now := time.Now() nextMinute := now.Truncate(time.Minute).Add(time.Minute) - time.AfterFunc(time.Until(nextMinute), func() { + timer := time.AfterFunc(time.Until(nextMinute), func() { + monitorSettingMutex.Lock() + defer monitorSettingMutex.Unlock() + if ctx.Err() != nil { + return + } monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %ss", interval), service) if err != nil { return } global.MonitorCronID = monitorID }) + monitorCancel = func() { + cancel() + timer.Stop() + } service.Run() @@ -669,15 +769,50 @@ func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions { return data } -func (m *MonitorService) saveGPUData() { - status, err := settingRepo.GetValueByKey("MonitorStatus") +func StartGPUMonitor(interval string) error { + seconds, err := strconv.Atoi(interval) + if err != nil || seconds < 10 || seconds > 43200 { + return fmt.Errorf("invalid GPU monitoring interval: %s", interval) + } + service := &MonitorService{} + job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveGPUData)) + id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() }) if err != nil { - global.LOG.Errorf("load monitor status failed: %v", err) + return err + } + if global.GPUMonitorCronID != 0 { + global.Cron.Remove(global.GPUMonitorCronID) + } + global.GPUMonitorCronID = id + return nil +} + +func (m *MonitorService) saveGPUData() { + if !gpuMonitorMutex.TryLock() { + return + } + defer gpuMonitorMutex.Unlock() + status, err := settingRepo.GetValueByKey("GPUMonitorStatus") + if err != nil { + global.LOG.Errorf("Load GPU monitoring status failed: %v", err) return } if status != constant.StatusEnable { return } + retention, err := settingRepo.GetValueByKey("GPUMonitorStoreDays") + if err != nil { + global.LOG.Errorf("Load GPU monitoring retention failed: %v", err) + return + } + days, err := strconv.Atoi(retention) + if err != nil || days < 1 { + global.LOG.Errorf("Invalid GPU monitoring retention: %s", retention) + return + } + if err := monitorRepo.DelMonitorGPU(time.Now().AddDate(0, 0, -days)); err != nil { + global.LOG.Errorf("Clean GPU monitoring data failed: %v", err) + } exist, client := accelerator.New() if !exist { return @@ -691,7 +826,7 @@ func (m *MonitorService) saveGPUData() { global.LOG.Warnf("load accelerator monitor data partially failed, err: %v", warning) } intervalSeconds := 0 - if setting, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")); err == nil { + if setting, err := settingRepo.Get(settingRepo.WithByKey("GPUMonitorInterval")); err == nil { intervalSeconds, _ = strconv.Atoi(setting.Value) } list := make([]model.MonitorGPU, 0, len(snapshot.Devices)) diff --git a/agent/app/service/monitor_vllm.go b/agent/app/service/monitor_vllm.go new file mode 100644 index 000000000..e1944c4a8 --- /dev/null +++ b/agent/app/service/monitor_vllm.go @@ -0,0 +1,279 @@ +package service + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/vllm" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/robfig/cron/v3" + "golang.org/x/sync/errgroup" +) + +var vllmMonitorMutex sync.Mutex +var vllmMetricsClient = &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext, IdleConnTimeout: 30 * time.Second}, + CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, +} + +func (m *MonitorService) LoadVLLMMonitorData(req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error) { + data := dto.MonitorVLLMData{Points: []model.MonitorVLLM{}} + if !req.EndTime.After(req.StartTime) { + return data, fmt.Errorf("invalid vLLM history request") + } + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + if err != nil { + return data, err + } + if install.App.Key != "vllm" { + return data, fmt.Errorf("not a vLLM installation") + } + + loc, err := time.LoadLocation(common.LoadTimeZoneByCmd()) + if err != nil { + return data, err + } + req.StartTime, req.EndTime = req.StartTime.In(loc), req.EndTime.In(loc) + data.SampleCount, err = vllmMonitorRepo.Count(req.AppInstallID, req.StartTime, req.EndTime) + if err != nil || data.SampleCount == 0 { + return data, err + } + if data.SampleCount > 1200 { + data.BucketSeconds = (req.EndTime.Unix() - req.StartTime.Unix() + 600) / 600 + } + points, err := vllmMonitorRepo.History(req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation) + if err != nil { + return data, err + } + if data.BucketSeconds > 0 { + if req.Aggregation != "max" { + for i := range points { + if err := vllm.AggregateHistograms(points[i].HistogramSamples, &points[i].MonitorVLLM); err != nil { + return data, err + } + } + } + next := 0 + for bucket := int64(0); bucket <= (req.EndTime.Unix()-req.StartTime.Unix())/data.BucketSeconds; bucket++ { + var point model.MonitorVLLM + if next < len(points) && points[next].Bucket == bucket { + point = points[next].MonitorVLLM + next++ + } + point.AppInstallID = req.AppInstallID + point.CreatedAt = time.Unix(req.StartTime.Unix()+bucket*data.BucketSeconds, 0).In(loc) + if bucket == 0 { + point.CreatedAt = req.StartTime + } + 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 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}) + } + data.Points = append(data.Points, point.MonitorVLLM) + } + } + return data, nil +} + +func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + if err != nil { + return model.MonitorVLLM{}, err + } + if install.App.Key != "vllm" { + return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation") + } + previous, err := vllmMonitorRepo.Latest(req.AppInstallID) + if err != nil { + return model.MonitorVLLM{}, err + } + point, err := collectVLLMMetrics(ctx, install, previous) + if err != nil { + global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err) + } + return point, nil +} + +func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + if err != nil { + return err + } + if install.App.Key != "vllm" { + return fmt.Errorf("not a vLLM installation") + } + vllmMonitorMutex.Lock() + defer vllmMonitorMutex.Unlock() + return vllmMonitorRepo.CleanTarget(req.AppInstallID) +} + +func StartVLLMMonitor(interval string) error { + seconds, err := strconv.Atoi(interval) + if err != nil || seconds < 10 || seconds > 43200 { + return fmt.Errorf("invalid vLLM monitoring interval: %s", interval) + } + service := &MonitorService{} + job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData)) + id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() }) + if err != nil { + return err + } + if global.VLLMMonitorCronID != 0 { + global.Cron.Remove(global.VLLMMonitorCronID) + } + global.VLLMMonitorCronID = id + return nil +} + +func (m *MonitorService) saveVLLMData() { + if !vllmMonitorMutex.TryLock() { + return + } + defer vllmMonitorMutex.Unlock() + status, err := settingRepo.GetValueByKey("VLLMMonitorStatus") + if err != nil { + global.LOG.Errorf("Load vLLM monitoring status failed: %v", err) + return + } + if status != constant.StatusEnable { + return + } + retention, err := settingRepo.GetValueByKey("VLLMMonitorStoreDays") + if err != nil { + global.LOG.Errorf("Load vLLM monitoring retention failed: %v", err) + return + } + days, err := strconv.Atoi(retention) + if err != nil || days < 1 { + global.LOG.Errorf("Invalid vLLM monitoring retention: %s", retention) + return + } + if err := vllmMonitorRepo.DeleteBefore(time.Now().AddDate(0, 0, -days)); err != nil { + global.LOG.Errorf("Clean vLLM monitoring data failed: %v", err) + } + apps, err := appRepo.GetBy(appRepo.WithKey("vllm")) + if err != nil { + global.LOG.Errorf("Load vLLM apps for monitoring failed: %v", err) + return + } + if len(apps) == 0 { + return + } + ids := make([]uint, 0, len(apps)) + for _, app := range apps { + ids = append(ids, app.ID) + } + installs, err := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppIdsIn(ids)) + if err != nil { + global.LOG.Errorf("Load vLLM instances for monitoring failed: %v", err) + return + } + var group errgroup.Group + group.SetLimit(4) + for _, install := range installs { + group.Go(func() error { + previous, err := vllmMonitorRepo.Latest(install.ID) + if err != nil { + return err + } + point, err := collectVLLMMetrics(context.Background(), install, previous) + if err != nil { + global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err) + } + return vllmMonitorRepo.Create(&point) + }) + } + if err := group.Wait(); err != nil { + global.LOG.Errorf("Save vLLM monitoring data failed: %v", err) + } +} + +func collectVLLMMetrics(ctx context.Context, install model.AppInstall, previous model.MonitorVLLM) (model.MonitorVLLM, error) { + point := model.MonitorVLLM{AppInstallID: install.ID, CreatedAt: time.Now(), Status: "unavailable"} + if install.HttpPort <= 0 || install.HttpPort > 65535 { + return point, fmt.Errorf("invalid vLLM port") + } + var env map[string]interface{} + if err := json.Unmarshal([]byte(install.Env), &env); err != nil { + return point, err + } + host, _ := env[constant.HostIP].(string) + host = strings.Trim(host, "[]") + switch host { + case "", "0.0.0.0": + host = "127.0.0.1" + case "::": + host = "::1" + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+net.JoinHostPort(host, strconv.Itoa(install.HttpPort))+"/metrics", nil) + if err != nil { + return point, err + } + response, err := vllmMetricsClient.Do(req) + if err != nil { + return point, err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return point, fmt.Errorf("vLLM metrics returned HTTP %d", response.StatusCode) + } + body, err := io.ReadAll(io.LimitReader(response.Body, 8*1024*1024+1)) + if err != nil { + return point, err + } + if len(body) > 8*1024*1024 { + return point, fmt.Errorf("vLLM metrics response too large") + } + metrics, err := vllm.Parse(bytes.NewReader(body)) + if err != nil { + return point, err + } + point.CreatedAt = time.Now() + raw, err := json.Marshal(metrics) + if err != nil { + return point, err + } + var before vllm.Metrics + elapsed := point.CreatedAt.Sub(previous.CreatedAt).Seconds() + if previous.Status == "ok" && elapsed > 0 { + if err := json.Unmarshal([]byte(previous.RawMetrics), &before); err != nil { + return point, err + } + } + calculated, err := vllm.Calculate(metrics, before, elapsed) + if err != nil { + return point, err + } + calculated.AppInstallID = install.ID + calculated.CreatedAt = point.CreatedAt + calculated.RawMetrics = string(raw) + calculated.Status = "ok" + return calculated, nil +} diff --git a/agent/cron/cron.go b/agent/cron/cron.go index 2296a1a43..f658cb954 100644 --- a/agent/cron/cron.go +++ b/agent/cron/cron.go @@ -38,6 +38,28 @@ func Run() { } } + var gpuStatus, gpuInterval model.Setting + if err := global.DB.Where("key = ?", "GPUMonitorStatus").First(&gpuStatus).Error; err != nil { + global.LOG.Errorf("Load GPU monitoring status failed: %v", err) + } else if gpuStatus.Value == constant.StatusEnable { + if err := global.DB.Where("key = ?", "GPUMonitorInterval").First(&gpuInterval).Error; err != nil { + global.LOG.Errorf("Load GPU monitoring interval failed: %v", err) + } else if err := service.StartGPUMonitor(gpuInterval.Value); err != nil { + global.LOG.Errorf("Start GPU monitoring failed: %v", err) + } + } + + var vllmStatus, vllmInterval model.Setting + if err := global.DB.Where("key = ?", "VLLMMonitorStatus").First(&vllmStatus).Error; err != nil { + global.LOG.Errorf("Load vLLM monitoring status failed: %v", err) + } else if vllmStatus.Value == constant.StatusEnable { + if err := global.DB.Where("key = ?", "VLLMMonitorInterval").First(&vllmInterval).Error; err != nil { + global.LOG.Errorf("Load vLLM monitoring interval failed: %v", err) + } else if err := service.StartVLLMMonitor(vllmInterval.Value); err != nil { + global.LOG.Errorf("Start vLLM monitoring failed: %v", err) + } + } + if _, err := global.Cron.AddJob("@daily", job.NewWebsiteJob()); err != nil { global.LOG.Errorf("can not add website corn job: %s", err.Error()) } diff --git a/agent/global/global.go b/agent/global/global.go index 930978c29..ad7badaca 100644 --- a/agent/global/global.go +++ b/agent/global/global.go @@ -14,12 +14,13 @@ import ( ) var ( - DB *gorm.DB - MonitorDB *gorm.DB - GPUMonitorDB *gorm.DB - TaskDB *gorm.DB - CoreDB *gorm.DB - AlertDB *gorm.DB + DB *gorm.DB + MonitorDB *gorm.DB + GPUMonitorDB *gorm.DB + VLLMMonitorDB *gorm.DB + TaskDB *gorm.DB + CoreDB *gorm.DB + AlertDB *gorm.DB LOG *logrus.Logger CONF ServerConfig @@ -29,8 +30,10 @@ var ( Dir SystemDir - Cron *cron.Cron - MonitorCronID cron.EntryID + Cron *cron.Cron + MonitorCronID cron.EntryID + VLLMMonitorCronID cron.EntryID + GPUMonitorCronID cron.EntryID IsMaster bool diff --git a/agent/init/db/db.go b/agent/init/db/db.go index 783222332..3516f9685 100644 --- a/agent/init/db/db.go +++ b/agent/init/db/db.go @@ -13,6 +13,7 @@ func Init() { global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task") global.MonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "monitor.db"), "monitor") global.GPUMonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "gpu_monitor.db"), "gpu_monitor") + global.VLLMMonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "vllm_monitor.db"), "vllm_monitor") global.AlertDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "alert.db"), "alert") if _, err := os.Stat(path.Join(global.Dir.DbDir, "core.db")); err == nil { diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index a74552362..cf03b84fa 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -112,6 +112,8 @@ func agentDBMigrations() []*gormigrate.Migration { migrations.AddDockerPortGuardReadOnly, migrations.MigrateFirewallPortWhitelistSources, migrations.AddAcceleratorMetrics, + migrations.AddVLLMMonitor, + migrations.AddMonitorSettings, } } diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 930a3931b..e166659a1 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1915,3 +1915,28 @@ var AddAcceleratorMetrics = &gormigrate.Migration{ return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{}) }, } + +var AddVLLMMonitor = &gormigrate.Migration{ + ID: "20260928-vllm-monitor", + Migrate: func(tx *gorm.DB) error { + return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{}) + }, +} + +var AddMonitorSettings = &gormigrate.Migration{ + ID: "20260929-monitor-settings", + Migrate: func(tx *gorm.DB) error { + for _, key := range []string{"MonitorStatus", "MonitorInterval", "MonitorStoreDays"} { + var setting model.Setting + if err := tx.Where("key = ?", key).First(&setting).Error; err != nil { + return err + } + for _, prefix := range []string{"GPU", "VLLM"} { + if err := tx.Where("key = ?", prefix+key).FirstOrCreate(&model.Setting{Key: prefix + key, Value: setting.Value}).Error; err != nil { + return err + } + } + } + return nil + }, +} diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go index dd8ce2b95..1c68336ea 100644 --- a/agent/router/ro_ai.go +++ b/agent/router/ro_ai.go @@ -27,6 +27,9 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo) aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor) aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions) + aiToolsRouter.POST("/vllm/monitor/search", baseApi.LoadVLLMMonitor) + aiToolsRouter.POST("/vllm/monitor/current", baseApi.LoadVLLMCurrent) + aiToolsRouter.POST("/vllm/monitor/clean", baseApi.CleanVLLMMonitor) aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers) aiToolsRouter.POST("/mcp/server/detail", baseApi.LoadMcpServerDetail) diff --git a/agent/utils/ai_tools/accelerator/accelerator.go b/agent/utils/ai_tools/accelerator/accelerator.go index 89f7ae24d..f6cc3f335 100644 --- a/agent/utils/ai_tools/accelerator/accelerator.go +++ b/agent/utils/ai_tools/accelerator/accelerator.go @@ -23,14 +23,6 @@ type providerResult struct { } func New() (bool, Client) { - return newClient() -} - -func NewAll() (bool, Client) { - return New() -} - -func newClient() (bool, Client) { client := Client{} if available, gpuClient := gpu.New(); available { client.providers = append(client.providers, gpuProvider{client: gpuClient}) diff --git a/agent/utils/ai_tools/vllm/metrics.go b/agent/utils/ai_tools/vllm/metrics.go new file mode 100644 index 000000000..352f7bb0b --- /dev/null +++ b/agent/utils/ai_tools/vllm/metrics.go @@ -0,0 +1,335 @@ +package vllm + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "math" + "sort" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type Metrics map[string]map[string]float64 + +type histogram struct { + Sum float64 `json:"sum"` + Count float64 `json:"count"` + Buckets map[string]float64 `json:"buckets,omitempty"` +} + +func Parse(reader io.Reader) (Metrics, error) { + metrics := Metrics{} + scanner := bufio.NewScanner(reader) + scanner.Buffer(make([]byte, 4096), 1024*1024) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if !strings.HasPrefix(line, "vllm:") { + continue + } + match := re.GetRegex(re.VLLMMetricSamplePattern).FindStringSubmatch(line) + if match == nil { + return nil, fmt.Errorf("invalid vLLM metric sample") + } + name := strings.TrimPrefix(match[1], "vllm:") + switch name { + case "num_requests_running", "num_requests_waiting", "kv_cache_usage_perc", "gpu_cache_usage_perc", "prompt_tokens_total", "generation_tokens_total", "request_success_total": + default: + base := strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(name, "_sum"), "_count"), "_bucket") + if base == name { + continue + } + switch base { + case "time_to_first_token_seconds", "time_per_output_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds": + default: + continue + } + } + value, err := strconv.ParseFloat(match[3], 64) + if err != nil { + return nil, fmt.Errorf("invalid vLLM metric value: %w", err) + } + if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 { + continue + } + if metrics[name] == nil { + metrics[name] = map[string]float64{} + } + labels := re.GetRegex(re.VLLMMetricLabelPattern).FindAllStringSubmatch(match[2], -1) + parts := make([]string, 0, len(labels)) + for _, label := range labels { + parts = append(parts, label[1]+"="+label[2]) + } + sort.Strings(parts) + metrics[name]["{"+strings.Join(parts, ",")+"}"] = value + } + if err := scanner.Err(); err != nil { + return nil, err + } + if len(metrics) == 0 { + return nil, fmt.Errorf("no supported vLLM metrics") + } + return metrics, nil +} + +func Calculate(current, previous Metrics, elapsed float64) (model.MonitorVLLM, error) { + result := model.MonitorVLLM{} + for name, target := range map[string]**float64{ + "num_requests_running": &result.Running, + "num_requests_waiting": &result.Waiting, + } { + if len(current[name]) == 0 { + continue + } + value := 0.0 + for _, sample := range current[name] { + value += sample + } + *target = &value + } + cache := current["kv_cache_usage_perc"] + if len(cache) == 0 { + cache = current["gpu_cache_usage_perc"] + } + if len(cache) > 0 { + value := 0.0 + for _, sample := range cache { + value += sample + } + value = value / float64(len(cache)) * 100 + result.CacheUsage = &value + } + if elapsed <= 0 { + return result, nil + } + for name, target := range map[string]**float64{ + "prompt_tokens_total": &result.PromptThroughput, + "generation_tokens_total": &result.GenerationThroughput, + "request_success_total": &result.RequestThroughput, + } { + if value := counterDelta(current[name], previous[name]); value != nil { + rate := *value / elapsed + *target = &rate + } + } + histograms := map[string]histogram{} + for _, name := range []string{"time_to_first_token_seconds", "inter_token_latency_seconds", "e2e_request_latency_seconds", "request_prefill_time_seconds", "request_decode_time_seconds"} { + source := name + if name == "inter_token_latency_seconds" && len(current[name+"_count"]) == 0 { + source = "time_per_output_token_seconds" + } + sum := counterDelta(current[source+"_sum"], previous[source+"_sum"]) + count := counterDelta(current[source+"_count"], previous[source+"_count"]) + if sum == nil || count == nil { + continue + } + histograms[name] = histogram{Sum: *sum, Count: *count, Buckets: bucketDeltas(current[source+"_bucket"], previous[source+"_bucket"], *count)} + } + applyHistograms(&result, histograms) + encoded, err := json.Marshal(histograms) + if err != nil { + return model.MonitorVLLM{}, err + } + result.HistogramDeltas = string(encoded) + return result, nil +} + +func AggregateHistograms(encoded string, result *model.MonitorVLLM) error { + var samples []map[string]histogram + if err := json.Unmarshal([]byte(encoded), &samples); err != nil { + return err + } + merged := map[string]histogram{} + for _, sample := range samples { + for name, item := range sample { + if item.Count == 0 { + continue + } + total, exists := merged[name] + if !exists { + merged[name] = item + continue + } + total.Sum += item.Sum + total.Count += item.Count + if len(total.Buckets) != len(item.Buckets) { + total.Buckets = nil + } + for bound, count := range total.Buckets { + addition, ok := item.Buckets[bound] + if !ok { + total.Buckets = nil + break + } + total.Buckets[bound] = count + addition + } + merged[name] = total + } + } + applyHistograms(result, merged) + return nil +} + +func applyHistograms(result *model.MonitorVLLM, histograms map[string]histogram) { + for _, item := range []struct { + name string + mean **float64 + quantiles []**float64 + }{ + {"time_to_first_token_seconds", &result.TimeToFirstToken, []**float64{&result.TimeToFirstTokenP50, &result.TimeToFirstTokenP90, &result.TimeToFirstTokenP95, &result.TimeToFirstTokenP99}}, + {"inter_token_latency_seconds", &result.TimePerOutputToken, []**float64{&result.TimePerOutputTokenP50, &result.TimePerOutputTokenP90, &result.TimePerOutputTokenP95, &result.TimePerOutputTokenP99}}, + {"e2e_request_latency_seconds", &result.RequestLatency, []**float64{&result.RequestLatencyP50, &result.RequestLatencyP90, &result.RequestLatencyP95, &result.RequestLatencyP99}}, + {"request_prefill_time_seconds", &result.PrefillTime, nil}, + {"request_decode_time_seconds", &result.DecodeTime, nil}, + } { + histogram, ok := histograms[item.name] + if !ok || histogram.Count <= 0 { + continue + } + mean := histogram.Sum / histogram.Count + *item.mean = &mean + if len(item.quantiles) == 0 { + continue + } + quantiles := histogramQuantiles(histogram.Buckets) + for i, target := range item.quantiles { + *target = quantiles[i] + } + } +} + +func counterDelta(current, previous map[string]float64) *float64 { + if len(current) == 0 || len(current) != len(previous) { + return nil + } + value := 0.0 + for label, sample := range current { + before, ok := previous[label] + if !ok || sample < before { + return nil + } + value += sample - before + } + if math.IsInf(value, 0) || math.IsNaN(value) { + return nil + } + return &value +} + +func bucketDeltas(current, previous map[string]float64, count float64) map[string]float64 { + if len(current) == 0 || len(current) != len(previous) { + return nil + } + groups := map[string]map[string]float64{} + for labels, sample := range current { + before, ok := previous[labels] + if !ok || sample < before { + return nil + } + bound := "" + var identity []string + for _, label := range re.GetRegex(re.VLLMMetricLabelPattern).FindAllStringSubmatch(labels, -1) { + if label[1] == "le" { + raw, err := strconv.Unquote(label[2]) + if err != nil { + return nil + } + upper, err := strconv.ParseFloat(raw, 64) + if err != nil || math.IsNaN(upper) || upper < 0 { + return nil + } + bound = strconv.FormatFloat(upper, 'g', -1, 64) + } else { + identity = append(identity, label[1]+"="+label[2]) + } + } + if bound == "" { + return nil + } + group := strings.Join(identity, ",") + if groups[group] == nil { + groups[group] = map[string]float64{} + } + if _, exists := groups[group][bound]; exists { + return nil + } + groups[group][bound] = sample - before + } + result := map[string]float64{} + for _, buckets := range groups { + if _, ok := buckets["+Inf"]; !ok || len(buckets) < 2 { + return nil + } + if len(result) > 0 && len(result) != len(buckets) { + return nil + } + for bound := range result { + if _, ok := buckets[bound]; !ok { + return nil + } + } + bounds := make([]float64, 0, len(buckets)) + for bound := range buckets { + value, _ := strconv.ParseFloat(bound, 64) + bounds = append(bounds, value) + } + sort.Float64s(bounds) + previousCount := 0.0 + for _, bound := range bounds { + key := strconv.FormatFloat(bound, 'g', -1, 64) + value := buckets[key] + if value < previousCount { + if previousCount-value > 1e-12*(previousCount+value) { + return nil + } + value = previousCount + } + result[key] += value + previousCount = value + } + } + if math.Abs(result["+Inf"]-count) > 1e-12*(result["+Inf"]+count) { + return nil + } + return result +} + +func histogramQuantiles(buckets map[string]float64) [4]*float64 { + var quantiles [4]*float64 + if len(buckets) < 2 || buckets["+Inf"] <= 0 { + return quantiles + } + bounds := make([]float64, 0, len(buckets)-1) + for bound := range buckets { + value, err := strconv.ParseFloat(bound, 64) + if err != nil || math.IsNaN(value) || value < 0 { + return quantiles + } + if !math.IsInf(value, 1) { + bounds = append(bounds, value) + } + } + if len(bounds) == 0 { + return quantiles + } + sort.Float64s(bounds) + for i, q := range [...]float64{0.5, 0.9, 0.95, 0.99} { + rank := q * buckets["+Inf"] + lower, before := 0.0, 0.0 + value := bounds[len(bounds)-1] + for _, upper := range bounds { + count := buckets[strconv.FormatFloat(upper, 'g', -1, 64)] + if count >= rank && count > before { + value = lower + (upper-lower)*(rank-before)/(count-before) + break + } + lower, before = upper, count + } + quantiles[i] = &value + } + return quantiles +} diff --git a/agent/utils/re/re.go b/agent/utils/re/re.go index 8caa5e2b6..f2413f056 100644 --- a/agent/utils/re/re.go +++ b/agent/utils/re/re.go @@ -6,6 +6,8 @@ import ( ) const ( + VLLMMetricSamplePattern = `^([a-zA-Z_:][a-zA-Z0-9_:]*)(\{(?:[^"\\]|"(?:\\.|[^"\\])*")*\})?\s+(\S+)(?:\s+.*)?$` + VLLMMetricLabelPattern = `([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*("(?:[^"\\]|\\.)*")` NumberAlphaPattern = `(\d+)([A-Za-z]+)` ComposeDisallowedCharsPattern = `[^a-z0-9_-]+` ComposeNamePattern = `^[a-z0-9][a-z0-9_-]{0,255}$` @@ -61,6 +63,8 @@ var regexMap = make(map[string]*regexp.Regexp) func Init() { patterns := []string{ + VLLMMetricSamplePattern, + VLLMMetricLabelPattern, NumberAlphaPattern, ComposeDisallowedCharsPattern, ComposeNamePattern, diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index 63dd4b5ee..e3705da86 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -117,6 +117,7 @@ export namespace AI { endTime: Date; } export interface MonitorGPUOptions { + supported: boolean; gpuType: string; options: Array; chartHide: Array; diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 6581e813d..041137151 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -65,6 +65,12 @@ export namespace Host { } export interface MonitorSetting { + gpuMonitorStatus: string; + gpuMonitorStoreDays: string; + gpuMonitorInterval: string; + vllmMonitorStatus: string; + vllmMonitorStoreDays: string; + vllmMonitorInterval: string; defaultNetwork: string; defaultIO: string; monitorStatus: string; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index c75434b3f..f0ef7d9ac 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -28,8 +28,8 @@ export const getIOOptions = (currentNode?: string) => { currentNode ? { headers: { CurrentNode: currentNode } } : {}, ); }; -export const cleanMonitors = () => { - return http.post(`/hosts/monitor/clean`, {}); +export const cleanMonitors = (type: 'host' | 'gpu') => { + return http.post(`/hosts/monitor/clean`, { type }); }; export const loadMonitorSetting = (currentNode?: string) => { return http.get( diff --git a/frontend/src/components/v-charts/components/Line.vue b/frontend/src/components/v-charts/components/Line.vue index 3a75b9e8d..77619cd68 100644 --- a/frontend/src/components/v-charts/components/Line.vue +++ b/frontend/src/components/v-charts/components/Line.vue @@ -229,7 +229,7 @@ function initChart() { const option = { title: [ { - left: 'center', + left: props.option.titleLeft ?? 'center', text: props.option.title, show: props.option.title, textStyle: { @@ -257,6 +257,7 @@ function initChart() { itemWidth: 8, icon: 'circle', ...props.option.legend, + selected: { ...props.option.legend?.selected, ...itemSelect?.selected }, textStyle: { ...itemSelect?.textStyle, ...props.option.legend?.textStyle, diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 5254005bb..431c54363 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1507,6 +1507,34 @@ const message = { ignoreEos: 'Ignore EOS', extraHeaders: 'Extra Headers', }, + vllmMonitor: { + cleanHelper: + 'Clear monitoring history only for this vLLM instance? Other instances, host and GPU monitoring are unaffected.', + overview: 'Overview', + latencyAnalysis: 'Latency analysis', + requests: 'Requests', + tokenThroughput: 'Token throughput', + running: 'Running requests', + waiting: 'Waiting requests', + cacheUsage: 'KV cache usage', + promptThroughput: 'Input token throughput', + generationThroughput: 'Output token throughput', + requestThroughput: 'Completed requests', + timeToFirstToken: 'Mean time to first token', + timePerOutputToken: 'Mean inter-token latency', + requestLatency: 'Mean request latency', + unavailable: 'Metrics are unavailable. Check that the instance is running and /metrics is accessible.', + loadFailed: 'Failed to load monitoring data. Please retry.', + helper: 'Manage collection, interval and retention in monitoring settings.', + cache: 'KV cache', + throughput: 'Request throughput', + phaseTime: 'Prefill and decode time', + prefillTime: 'Mean prefill time', + decodeTime: 'Mean decode time', + requestLatencyChart: 'End-to-end request latency', + timeToFirstTokenChart: 'Time to first token', + timePerOutputTokenChart: 'Inter-token latency', + }, gpu: { memoryActivity: 'Memory read/write activity', hotspotTemperature: 'Hotspot temperature', @@ -2119,6 +2147,9 @@ const message = { cleanLogscope: 'Log retention', }, monitor: { + hostMonitor: 'Host Monitoring', + gpuMonitor: 'GPU Monitoring', + vllmMonitor: 'vLLM Monitoring', globalFilter: 'Global Filter', enableMonitor: 'Monitoring Status', storeDays: 'Retention Days', @@ -2127,7 +2158,7 @@ const message = { defaultIO: 'Default Disk', defaultIOHelper: 'Default disk option displayed in monitoring and overview interfaces', cleanMonitor: 'Clear Monitoring Records', - cleanHelper: 'This operation will clear all monitoring records including GPU. Continue?', + cleanHelper: 'This operation will clear all history for {0}. Continue?', avgLoad: 'Load average', loadDetail: 'Load detail', resourceUsage: 'Utilization', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index df78f4de5..a45334d04 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1516,6 +1516,35 @@ const message = { ignoreEos: 'Ignorar EOS', extraHeaders: 'Encabezados adicionales', }, + vllmMonitor: { + cleanHelper: + '¿Borrar solo el historial de monitoreo de esta instancia de vLLM? No afecta a otras instancias ni al monitoreo del host o de GPU.', + overview: 'Resumen', + latencyAnalysis: 'Análisis de latencia', + requests: 'Solicitudes', + tokenThroughput: 'Rendimiento de tokens', + running: 'Solicitudes en ejecución', + waiting: 'Solicitudes en espera', + cacheUsage: 'Uso de caché KV', + promptThroughput: 'Rendimiento de tokens de entrada', + generationThroughput: 'Rendimiento de tokens de salida', + requestThroughput: 'Solicitudes completadas', + timeToFirstToken: 'Latencia media del primer token', + timePerOutputToken: 'Latencia media entre tokens', + requestLatency: 'Latencia media de solicitud', + unavailable: + 'Métricas no disponibles. Compruebe que la instancia esté en ejecución y que /metrics sea accesible.', + loadFailed: 'No se pudieron cargar los datos. Inténtelo de nuevo.', + helper: 'Configura la recopilación, el intervalo y la retención en los ajustes de monitorización.', + cache: 'Caché KV', + throughput: 'Solicitudes/s', + phaseTime: 'Tiempo de precarga y decodificación', + prefillTime: 'Tiempo medio de precarga', + decodeTime: 'Tiempo medio de decodificación', + requestLatencyChart: 'Latencia de solicitud de extremo a extremo', + timeToFirstTokenChart: 'Tiempo hasta el primer token', + timePerOutputTokenChart: 'Latencia entre tokens', + }, gpu: { memoryActivity: 'Actividad de lectura/escritura de memoria', hotspotTemperature: 'Temperatura del punto caliente', @@ -2155,6 +2184,9 @@ const message = { cleanLogscope: 'Retención de logs', }, monitor: { + hostMonitor: 'Monitoreo del host', + gpuMonitor: 'Monitoreo de GPU', + vllmMonitor: 'Monitoreo de vLLM', globalFilter: 'Filtro Global', enableMonitor: 'Estado de Monitoreo', storeDays: 'Días de Retención', @@ -2164,7 +2196,7 @@ const message = { defaultIO: 'Disco Predeterminado', defaultIOHelper: 'Opción de disco predeterminada mostrada en las interfaces de monitoreo y resumen', cleanMonitor: 'Limpiar Registros de Monitoreo', - cleanHelper: 'Esta operación borrará todos los registros de monitoreo, incluidos los de GPU. ¿Continuar?', + cleanHelper: 'Esta operación borrará todo el historial de {0}. ¿Continuar?', avgLoad: 'Carga promedio', loadDetail: 'Detalle de carga', resourceUsage: 'Utilización', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index 978ff0186..06cc4f72e 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -1492,6 +1492,34 @@ const message = { ignoreEos: 'نادیده گرفتن EOS', extraHeaders: 'هدرهای اضافی', }, + vllmMonitor: { + cleanHelper: + 'فقط سابقه نظارت این نمونه vLLM پاک شود؟ سایر نمونه\u200cها و نظارت میزبان و GPU تغییری نمی\u200cکنند.', + overview: 'نمای کلی', + latencyAnalysis: 'تحلیل تأخیر', + requests: 'تعداد درخواست‌ها', + tokenThroughput: 'توان عملیاتی توکن', + running: 'درخواست‌های در حال اجرا', + waiting: 'درخواست‌های در انتظار', + cacheUsage: 'مصرف حافظه نهان KV', + promptThroughput: 'گذردهی توکن ورودی', + generationThroughput: 'گذردهی توکن خروجی', + requestThroughput: 'نرخ درخواست‌های تکمیل‌شده', + timeToFirstToken: 'میانگین تأخیر توکن اول', + timePerOutputToken: 'میانگین تأخیر بین توکن‌ها', + requestLatency: 'میانگین تأخیر درخواست', + unavailable: 'معیارها در دسترس نیستند. از اجرای نمونه و دسترسی به /metrics مطمئن شوید.', + loadFailed: 'بارگیری داده‌های پایش ناموفق بود. دوباره تلاش کنید.', + helper: 'فعال‌سازی جمع‌آوری، فاصله زمانی و مدت نگهداری را در تنظیمات پایش تغییر دهید.', + cache: 'حافظه نهان KV', + throughput: 'گذردهی درخواست', + phaseTime: 'زمان پیش‌پرکردن و رمزگشایی', + prefillTime: 'میانگین زمان پیش‌پرکردن', + decodeTime: 'میانگین زمان رمزگشایی', + requestLatencyChart: 'تأخیر سرتاسری درخواست', + timeToFirstTokenChart: 'زمان تا توکن اول', + timePerOutputTokenChart: 'تأخیر بین توکن‌ها', + }, gpu: { memoryActivity: 'فعالیت خواندن/نوشتن حافظه', hotspotTemperature: 'دمای نقطه داغ', @@ -2103,6 +2131,9 @@ const message = { cleanLogscope: 'نگهداری لاگ', }, monitor: { + hostMonitor: 'نظارت میزبان', + gpuMonitor: 'نظارت GPU', + vllmMonitor: 'نظارت vLLM', globalFilter: 'فیلتر جهانی', enableMonitor: 'وضعیت نظارت', storeDays: 'روزهای نگهداری', @@ -2111,7 +2142,7 @@ const message = { defaultIO: 'دیسک پیش‌فرض', defaultIOHelper: 'گزینه دیسک پیش‌فرض که در رابط‌های نظارت و نمای کلی نمایش داده می‌شود', cleanMonitor: 'پاک‌سازی رکوردهای نظارت', - cleanHelper: 'این عملیات همه رکوردهای نظارت از جمله GPU را پاک می‌کند. ادامه می‌دهید؟', + cleanHelper: 'این عملیات تمام سوابق {0} را پاک می‌کند. ادامه می‌دهید؟', avgLoad: 'میانگین بار', loadDetail: 'جزئیات بار', resourceUsage: 'استفاده از منابع', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index b219d06d7..19039590d 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1495,6 +1495,34 @@ const message = { ignoreEos: 'EOS を無視', extraHeaders: '追加ヘッダー', }, + vllmMonitor: { + cleanHelper: + 'この vLLM インスタンスの監視履歴のみを削除しますか?他のインスタンス、ホスト、GPU の監視には影響しません。', + overview: '概要', + latencyAnalysis: 'レイテンシ分析', + requests: 'リクエスト数', + tokenThroughput: 'トークンスループット', + running: '実行中のリクエスト', + waiting: '待機中のリクエスト', + cacheUsage: 'KVキャッシュ使用率', + promptThroughput: '入力トークンスループット', + generationThroughput: '出力トークンスループット', + requestThroughput: '完了リクエスト数/秒', + timeToFirstToken: '平均初回トークン遅延', + timePerOutputToken: '平均トークン間遅延', + requestLatency: '平均リクエスト遅延', + unavailable: '指標を取得できません。インスタンスが稼働し、/metrics にアクセスできるか確認してください。', + loadFailed: '監視データの読み込みに失敗しました。再試行してください。', + helper: '収集の有効化、間隔、保存期間は監視設定で変更できます。', + cache: 'KVキャッシュ', + throughput: 'リクエストスループット', + phaseTime: 'プリフィルとデコードの所要時間', + prefillTime: '平均プリフィル時間', + decodeTime: '平均デコード時間', + requestLatencyChart: 'エンドツーエンドのリクエスト遅延', + timeToFirstTokenChart: '初回トークン遅延', + timePerOutputTokenChart: 'トークン間遅延', + }, gpu: { memoryActivity: 'メモリ読み書き稼働率', hotspotTemperature: 'ホットスポット温度', @@ -2108,6 +2136,9 @@ const message = { cleanLogscope: 'ログ保持', }, monitor: { + hostMonitor: 'ホスト監視', + gpuMonitor: 'GPU 監視', + vllmMonitor: 'vLLM 監視', globalFilter: 'グローバルフィルター', enableMonitor: '監視ステータス', storeDays: '保存日数', @@ -2116,7 +2147,7 @@ const message = { defaultIO: 'デフォルトディスク', defaultIOHelper: '監視および概要インターフェースに表示されるデフォルトのディスクオプション', cleanMonitor: '監視記録をクリア', - cleanHelper: 'この操作により、GPUを含むすべての監視記録がクリアされます。続行しますか?', + cleanHelper: 'この操作は {0} の履歴をすべて削除します。続行しますか?', avgLoad: 'ロード平均', loadDetail: '詳細を読み込みます', resourceUsage: '利用', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 6f52b5d40..15e8413ab 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1481,6 +1481,34 @@ const message = { ignoreEos: 'EOS 무시', extraHeaders: '추가 헤더', }, + vllmMonitor: { + cleanHelper: + '현재 vLLM 인스턴스의 모니터링 기록만 삭제하시겠습니까? 다른 인스턴스, 호스트 및 GPU 모니터링에는 영향을 주지 않습니다.', + overview: '개요', + latencyAnalysis: '지연 시간 분석', + requests: '요청 수', + tokenThroughput: '토큰 처리량', + running: '실행 중인 요청', + waiting: '대기 중인 요청', + cacheUsage: 'KV 캐시 사용률', + promptThroughput: '입력 토큰 처리량', + generationThroughput: '출력 토큰 처리량', + requestThroughput: '완료 요청 속도', + timeToFirstToken: '평균 첫 토큰 지연', + timePerOutputToken: '평균 토큰 간 지연', + requestLatency: '평균 요청 지연', + unavailable: '지표를 수집할 수 없습니다. 인스턴스 실행 상태와 /metrics 접근 가능 여부를 확인하세요.', + loadFailed: '모니터링 데이터를 불러오지 못했습니다. 다시 시도하세요.', + helper: '수집 활성화, 간격 및 보존 기간은 모니터링 설정에서 조정합니다.', + cache: 'KV 캐시', + throughput: '요청 처리량', + phaseTime: '프리필 및 디코딩 시간', + prefillTime: '평균 프리필 시간', + decodeTime: '평균 디코딩 시간', + requestLatencyChart: '종단 간 요청 지연', + timeToFirstTokenChart: '첫 토큰 지연', + timePerOutputTokenChart: '토큰 간 지연', + }, gpu: { memoryActivity: '메모리 읽기/쓰기 활성률', hotspotTemperature: '핫스폿 온도', @@ -2082,6 +2110,9 @@ const message = { cleanLogscope: '로그 보관 기간', }, monitor: { + hostMonitor: '호스트 모니터링', + gpuMonitor: 'GPU 모니터링', + vllmMonitor: 'vLLM 모니터링', globalFilter: '전역 필터', enableMonitor: '모니터링 상태', storeDays: '보관 일수', @@ -2090,7 +2121,7 @@ const message = { defaultIO: '기본 디스크', defaultIOHelper: '모니터링 및 개요 인터페이스에 표시되는 기본 디스크 옵션', cleanMonitor: '모니터링 기록 지우기', - cleanHelper: '이 작업은 GPU를 포함한 모든 모니터링 기록을 지웁니다. 계속하시겠습니까?', + cleanHelper: '이 작업은 {0}의 모든 기록을 삭제합니다. 계속하시겠습니까?', avgLoad: '평균 부하', loadDetail: '부하 세부사항', resourceUsage: '자원 사용률', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 731ffee83..d37a74456 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -1474,6 +1474,33 @@ const message = { ignoreEos: 'ລະເວັ້ນ EOS', extraHeaders: 'Headers ເພີ່ມເຕີມ', }, + vllmMonitor: { + cleanHelper: 'ລຶບສະເພາະປະຫວັດການຕິດຕາມຂອງ vLLM ນີ້ບໍ? ບໍ່ກະທົບຕໍ່ອິນສະແຕນອື່ນ, ໂຮສ ຫຼື GPU.', + overview: 'ພາບລວມ', + latencyAnalysis: 'ການວິເຄາະຄວາມລ່າຊ້າ', + requests: 'ຈຳນວນຄຳຮ້ອງ', + tokenThroughput: 'ອັດຕາປະມວນຜົນໂທເຄັນ', + running: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງເຮັດວຽກ', + waiting: 'ຄຳຮ້ອງຂໍທີ່ກຳລັງລໍຖ້າ', + cacheUsage: 'ການໃຊ້ງານແຄດ KV', + promptThroughput: 'ອັດຕາ Token ຂາເຂົ້າ', + generationThroughput: 'ອັດຕາ Token ຂາອອກ', + requestThroughput: 'ອັດຕາຄຳຮ້ອງຂໍສຳເລັດ', + timeToFirstToken: 'ເວລາລໍຖ້າສະເລ່ຍຂອງ Token ທຳອິດ', + timePerOutputToken: 'ເວລາລໍຖ້າສະເລ່ຍລະຫວ່າງ Token', + requestLatency: 'ເວລາລໍຖ້າສະເລ່ຍຂອງຄຳຮ້ອງຂໍ', + unavailable: 'ບໍ່ສາມາດເກັບຕົວຊີ້ວັດໄດ້. ກວດສອບວ່າອິນສະແຕນເຮັດວຽກ ແລະ ເຂົ້າເຖິງ /metrics ໄດ້.', + loadFailed: 'ໂຫຼດຂໍ້ມູນຕິດຕາມບໍ່ສຳເລັດ. ກະລຸນາລອງອີກຄັ້ງ.', + helper: 'ປັບການເປີດໃຊ້ການເກັບຂໍ້ມູນ, ໄລຍະຫ່າງ ແລະ ຈຳນວນມື້ເກັບຮັກສາໃນການຕັ້ງຄ່າການຕິດຕາມ.', + cache: 'ແຄດ KV', + throughput: 'ຄຳຮ້ອງຂໍ/ວິນາທີ', + phaseTime: 'ເວລາເຕີມລ່ວງໜ້າ ແລະ ຖອດລະຫັດ', + prefillTime: 'ເວລາເຕີມລ່ວງໜ້າສະເລ່ຍ', + decodeTime: 'ເວລາຖອດລະຫັດສະເລ່ຍ', + requestLatencyChart: 'ເວລາລໍຖ້າຄຳຮ້ອງຂໍຕັ້ງແຕ່ຕົ້ນຈົນຈົບ', + timeToFirstTokenChart: 'ເວລາລໍຖ້າ Token ທຳອິດ', + timePerOutputTokenChart: 'ເວລາລໍຖ້າລະຫວ່າງ Token', + }, gpu: { memoryActivity: 'ກິດຈະກຳອ່ານ/ຂຽນໜ່ວຍຄວາມຈຳ', hotspotTemperature: 'ອຸນຫະພູມຈຸດຮ້ອນ', @@ -2062,6 +2089,9 @@ const message = { cleanLogscope: 'ການເກັບຮັກສາບັນທຶກ', }, monitor: { + hostMonitor: 'ການຕິດຕາມໂຮສ', + gpuMonitor: 'ການຕິດຕາມ GPU', + vllmMonitor: 'ການຕິດຕາມ vLLM', globalFilter: 'ຕົວກອງທົ່ວໂລກ', enableMonitor: 'ສະຖານະການຕິດຕາມ', storeDays: 'ຈຳນວນມື້ທີ່ເກັບໄວ້', @@ -2070,7 +2100,7 @@ const message = { defaultIO: 'Disk ເລີ່ມຕົ້ນ', defaultIOHelper: 'Disk ທີ່ຈະສະແດງໃນໜ້າ monitor ແລະ ພາບລວມ', cleanMonitor: 'ລ້າງບັນທຶກການຕິດຕາມ', - cleanHelper: 'ການດຳເນີນການນີ້ຈະລ້າງບັນທຶກການຕິດຕາມທັງໝົດລວມເຖິງ GPU. ຕ້ອງການຕໍ່ຫຼືບໍ່?', + cleanHelper: 'ການດຳເນີນການນີ້ຈະລຶບປະຫວັດທັງໝົດຂອງ {0}. ສືບຕໍ່ບໍ?', avgLoad: 'ຄ່າ Load ສະເລ່ຍ', loadDetail: 'ລາຍລະອຽດ Load', resourceUsage: 'ການນຳໃຊ້', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index f91da8600..c32453ca0 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1521,6 +1521,34 @@ const message = { ignoreEos: 'Abaikan EOS', extraHeaders: 'Pengepala Tambahan', }, + vllmMonitor: { + cleanHelper: + 'Padamkan sejarah pemantauan untuk instans vLLM ini sahaja? Instans lain serta pemantauan hos dan GPU tidak terjejas.', + overview: 'Gambaran keseluruhan', + latencyAnalysis: 'Analisis kependaman', + requests: 'Permintaan', + tokenThroughput: 'Daya pemprosesan token', + running: 'Permintaan berjalan', + waiting: 'Permintaan menunggu', + cacheUsage: 'Penggunaan cache KV', + promptThroughput: 'Daya pemprosesan token input', + generationThroughput: 'Daya pemprosesan token output', + requestThroughput: 'Kadar permintaan selesai', + timeToFirstToken: 'Purata kependaman token pertama', + timePerOutputToken: 'Purata kependaman antara token', + requestLatency: 'Purata kependaman permintaan', + unavailable: 'Metrik tidak tersedia. Pastikan tika sedang berjalan dan /metrics boleh dicapai.', + loadFailed: 'Gagal memuatkan data pemantauan. Sila cuba lagi.', + helper: 'Urus pengumpulan, sela dan tempoh penyimpanan dalam tetapan pemantauan.', + cache: 'Cache KV', + throughput: 'Permintaan/s', + phaseTime: 'Masa praisi dan penyahkodan', + prefillTime: 'Purata masa praisi', + decodeTime: 'Purata masa penyahkodan', + requestLatencyChart: 'Kependaman permintaan hujung ke hujung', + timeToFirstTokenChart: 'Masa ke token pertama', + timePerOutputTokenChart: 'Kependaman antara token', + }, gpu: { memoryActivity: 'Aktiviti baca/tulis memori', hotspotTemperature: 'Suhu titik panas', @@ -2145,6 +2173,9 @@ const message = { cleanLogscope: 'Tempoh simpan log', }, monitor: { + hostMonitor: 'Pemantauan Hos', + gpuMonitor: 'Pemantauan GPU', + vllmMonitor: 'Pemantauan vLLM', globalFilter: 'Penapis Global', enableMonitor: 'Status Pemantauan', storeDays: 'Hari Penyimpanan', @@ -2154,7 +2185,7 @@ const message = { defaultIO: 'Cakera Lalai', defaultIOHelper: 'Pilihan cakera lalai yang dipaparkan dalam antara muka pemantauan dan gambaran keseluruhan', cleanMonitor: 'Kosongkan Rekod Pemantauan', - cleanHelper: 'Operasi ini akan membersihkan semua rekod pemantauan termasuk GPU. Teruskan?', + cleanHelper: 'Operasi ini akan memadamkan semua sejarah untuk {0}. Teruskan?', avgLoad: 'Purata beban', loadDetail: 'Butiran beban', resourceUsage: 'Penggunaan sumber', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index d40f29f0c..ae65b3f91 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1517,6 +1517,35 @@ const message = { ignoreEos: 'Ignorar EOS', extraHeaders: 'Cabeçalhos extras', }, + vllmMonitor: { + cleanHelper: + 'Limpar apenas o histórico de monitoramento desta instância do vLLM? Outras instâncias e o monitoramento do host e da GPU não serão afetados.', + overview: 'Visão geral', + latencyAnalysis: 'Análise de latência', + requests: 'Requisições', + tokenThroughput: 'Vazão de tokens', + running: 'Solicitações em execução', + waiting: 'Solicitações em espera', + cacheUsage: 'Uso do cache KV', + promptThroughput: 'Taxa de tokens de entrada', + generationThroughput: 'Taxa de tokens de saída', + requestThroughput: 'Solicitações concluídas', + timeToFirstToken: 'Latência média do primeiro token', + timePerOutputToken: 'Latência média entre tokens', + requestLatency: 'Latência média da solicitação', + unavailable: + 'Métricas indisponíveis. Verifique se a instância está em execução e se /metrics está acessível.', + loadFailed: 'Falha ao carregar os dados. Tente novamente.', + helper: 'Ajuste a coleta, o intervalo e a retenção nas configurações de monitoramento.', + cache: 'Cache KV', + throughput: 'Solicitações/s', + phaseTime: 'Tempo de preenchimento e decodificação', + prefillTime: 'Tempo médio de preenchimento', + decodeTime: 'Tempo médio de decodificação', + requestLatencyChart: 'Latência da solicitação de ponta a ponta', + timeToFirstTokenChart: 'Tempo até o primeiro token', + timePerOutputTokenChart: 'Latência entre tokens', + }, gpu: { memoryActivity: 'Atividade de leitura/escrita da memória', hotspotTemperature: 'Temperatura do ponto quente', @@ -2150,6 +2179,9 @@ const message = { cleanLogscope: 'Retenção de logs', }, monitor: { + hostMonitor: 'Monitoramento do host', + gpuMonitor: 'Monitoramento da GPU', + vllmMonitor: 'Monitoramento do vLLM', globalFilter: 'Filtro Global', enableMonitor: 'Status de Monitoramento', storeDays: 'Dias de Retenção', @@ -2158,7 +2190,7 @@ const message = { defaultIO: 'Disco Padrão', defaultIOHelper: 'Opção de disco padrão exibida nas interfaces de monitoramento e visão geral', cleanMonitor: 'Limpar Registros de Monitoramento', - cleanHelper: 'Esta operação limpará todos os registros de monitoramento, incluindo GPU. Continuar?', + cleanHelper: 'Esta operação limpará todo o histórico de {0}. Continuar?', avgLoad: 'Média de carga', loadDetail: 'Detalhes da carga', resourceUsage: 'Utilização', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 1d9a6e109..13b914261 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1510,6 +1510,34 @@ const message = { ignoreEos: 'Игнорировать EOS', extraHeaders: 'Дополнительные заголовки', }, + vllmMonitor: { + cleanHelper: + 'Удалить историю мониторинга только этого экземпляра vLLM? Другие экземпляры, мониторинг хоста и GPU не будут затронуты.', + overview: 'Обзор', + latencyAnalysis: 'Анализ задержек', + requests: 'Количество запросов', + tokenThroughput: 'Скорость обработки токенов', + running: 'Выполняемые запросы', + waiting: 'Ожидающие запросы', + cacheUsage: 'Использование KV-кэша', + promptThroughput: 'Скорость входных токенов', + generationThroughput: 'Скорость выходных токенов', + requestThroughput: 'Завершённые запросы', + timeToFirstToken: 'Среднее время до первого токена', + timePerOutputToken: 'Средняя задержка между токенами', + requestLatency: 'Средняя задержка запроса', + unavailable: 'Метрики недоступны. Проверьте, что экземпляр запущен и /metrics доступен.', + loadFailed: 'Не удалось загрузить данные мониторинга. Повторите попытку.', + helper: 'Сбор, интервал и срок хранения настраиваются в параметрах мониторинга.', + cache: 'KV-кэш', + throughput: 'Запросов/с', + phaseTime: 'Время предзаполнения и декодирования', + prefillTime: 'Среднее время предзаполнения', + decodeTime: 'Среднее время декодирования', + requestLatencyChart: 'Сквозная задержка запроса', + timeToFirstTokenChart: 'Время до первого токена', + timePerOutputTokenChart: 'Задержка между токенами', + }, gpu: { memoryActivity: 'Активность чтения/записи памяти', hotspotTemperature: 'Температура горячей точки', @@ -2135,6 +2163,9 @@ const message = { cleanLogscope: 'Хранение логов', }, monitor: { + hostMonitor: 'Мониторинг хоста', + gpuMonitor: 'Мониторинг GPU', + vllmMonitor: 'Мониторинг vLLM', globalFilter: 'Глобальный Фильтр', enableMonitor: 'Статус Мониторинга', storeDays: 'Дни Хранения', @@ -2143,7 +2174,7 @@ const message = { defaultIO: 'Диск по Умолчанию', defaultIOHelper: 'Опция диска по умолчанию, отображаемая в интерфейсах мониторинга и обзора', cleanMonitor: 'Очистить Записи Мониторинга', - cleanHelper: 'Эта операция очистит все записи мониторинга, включая GPU. Продолжить?', + cleanHelper: 'Эта операция удалит всю историю для {0}. Продолжить?', avgLoad: 'Средняя нагрузка', loadDetail: 'Детали нагрузки', resourceUsage: 'Использование', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 1ccff27ca..107b784c0 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1517,6 +1517,35 @@ const message = { ignoreEos: "EOS'u yok say", extraHeaders: 'Ek Başlıklar', }, + vllmMonitor: { + cleanHelper: + 'Yalnızca bu vLLM örneğinin izleme geçmişi temizlensin mi? Diğer örnekler, ana makine ve GPU izlemesi etkilenmez.', + overview: 'Genel bakış', + latencyAnalysis: 'Gecikme analizi', + requests: 'İstek sayısı', + tokenThroughput: 'Token işlem hızı', + running: 'Çalışan istekler', + waiting: 'Bekleyen istekler', + cacheUsage: 'KV önbellek kullanımı', + promptThroughput: 'Girdi token hızı', + generationThroughput: 'Çıktı token hızı', + requestThroughput: 'Tamamlanan istek hızı', + timeToFirstToken: 'Ortalama ilk token gecikmesi', + timePerOutputToken: 'Ortalama token arası gecikme', + requestLatency: 'Ortalama istek gecikmesi', + unavailable: + 'Metrikler kullanılamıyor. Örneğin çalıştığını ve /metrics yolunun erişilebilir olduğunu kontrol edin.', + loadFailed: 'İzleme verileri yüklenemedi. Tekrar deneyin.', + helper: 'Toplama, aralık ve saklama süresini izleme ayarlarından yönetin.', + cache: 'KV önbelleği', + throughput: 'İstek hızı', + phaseTime: 'Ön doldurma ve çözümleme süresi', + prefillTime: 'Ortalama ön doldurma süresi', + decodeTime: 'Ortalama çözümleme süresi', + requestLatencyChart: 'Uçtan uca istek gecikmesi', + timeToFirstTokenChart: 'İlk token süresi', + timePerOutputTokenChart: 'Token arası gecikme', + }, gpu: { memoryActivity: 'Bellek okuma/yazma etkinliği', hotspotTemperature: 'Sıcak nokta sıcaklığı', @@ -2143,6 +2172,9 @@ const message = { cleanLogscope: 'Günlük saklama', }, monitor: { + hostMonitor: 'Ana Makine İzleme', + gpuMonitor: 'GPU İzleme', + vllmMonitor: 'vLLM İzleme', globalFilter: 'Genel Filtre', enableMonitor: 'İzleme Durumu', storeDays: 'Saklama Günleri', @@ -2151,7 +2183,7 @@ const message = { defaultIO: 'Varsayılan Disk', defaultIOHelper: 'İzleme ve genel bakış arayüzlerinde görüntülenen varsayılan disk seçeneği', cleanMonitor: 'İzleme Kayıtlarını Temizle', - cleanHelper: 'Bu işlem GPU dahil tüm izleme kayıtlarını temizleyecektir. Devam etmek istiyor musunuz?', + cleanHelper: 'Bu işlem {0} için tüm geçmişi silecektir. Devam edilsin mi?', avgLoad: 'Ortalama yük', loadDetail: 'Yük detayı', resourceUsage: 'Kullanım', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index bf868465f..6e0c1b81a 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1428,6 +1428,33 @@ const message = { ignoreEos: '忽略 EOS', extraHeaders: '額外請求標頭', }, + vllmMonitor: { + cleanHelper: '僅清空目前 vLLM 實例的監控歷史,不影響其他實例、主機或 GPU 監控,是否繼續?', + overview: '概覽', + latencyAnalysis: '延遲分析', + requests: '請求數', + tokenThroughput: 'Token 吞吐量', + running: '執行中請求', + waiting: '等待中請求', + cacheUsage: 'KV Cache 使用率', + promptThroughput: '輸入 Token 吞吐', + generationThroughput: '輸出 Token 吞吐', + requestThroughput: '完成請求速率', + timeToFirstToken: '平均首 Token 延遲', + timePerOutputToken: '平均 Token 間延遲', + requestLatency: '平均請求延遲', + unavailable: '無法採集指標,請確認實例已啟動且 /metrics 可存取。', + loadFailed: '監控資料載入失敗,請重試。', + helper: '採集開關、頻率和儲存天數統一在監控設定中調整。', + cache: 'KV 快取', + throughput: '請求吞吐', + phaseTime: '預填充與解碼耗時', + prefillTime: '平均預填充耗時', + decodeTime: '平均解碼耗時', + requestLatencyChart: '端到端請求延遲', + timeToFirstTokenChart: '首 Token 延遲', + timePerOutputTokenChart: 'Token 間延遲', + }, gpu: { memoryActivity: '顯示記憶體讀寫活躍率', hotspotTemperature: '熱點溫度', @@ -2001,6 +2028,9 @@ const message = { cleanLogscope: '清理類型', }, monitor: { + hostMonitor: '主機監控', + gpuMonitor: 'GPU 監控', + vllmMonitor: 'vLLM 監控', globalFilter: '全域過濾', enableMonitor: '監控狀態', storeDays: '保存天數', @@ -2009,7 +2039,7 @@ const message = { defaultIO: '預設磁碟', defaultIOHelper: '預設監控和概覽介面顯示的磁碟選項', cleanMonitor: '清空監控記錄', - cleanHelper: '該操作將清空包括 GPU 在內的所有監控記錄,是否繼續?', + cleanHelper: '此操作將清空 {0} 的所有歷史記錄,是否繼續?', avgLoad: '平均負載', loadDetail: '負載詳情', resourceUsage: '資源使用率', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 59bba8f5e..10c11856f 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1441,6 +1441,33 @@ const message = { ignoreEos: '忽略 EOS', extraHeaders: '额外请求头', }, + vllmMonitor: { + cleanHelper: '仅清空当前 vLLM 实例的监控历史,不影响其他实例、主机或 GPU 监控,是否继续?', + overview: '概览', + latencyAnalysis: '延迟分析', + requests: '请求数', + tokenThroughput: 'Token 吞吐', + running: '运行中请求', + waiting: '等待中请求', + cacheUsage: 'KV Cache 使用率', + promptThroughput: '输入 Token 吞吐', + generationThroughput: '输出 Token 吞吐', + requestThroughput: '完成请求速率', + timeToFirstToken: '平均首 Token 延迟', + timePerOutputToken: '平均 Token 间延迟', + requestLatency: '平均请求延迟', + unavailable: '无法采集指标,请确认实例已启动且 /metrics 可访问。', + loadFailed: '监控数据加载失败,请重试。', + helper: '采集开关、频率和保存天数在监控设置的 vLLM 监控中独立调整。', + cache: 'KV 缓存', + throughput: '请求吞吐', + phaseTime: '预填充与解码耗时', + prefillTime: '平均预填充耗时', + decodeTime: '平均解码耗时', + requestLatencyChart: '端到端请求延迟', + timeToFirstTokenChart: '首 Token 延迟', + timePerOutputTokenChart: 'Token 间延迟', + }, gpu: { memoryActivity: '显存读写活跃率', hotspotTemperature: '热点温度', @@ -2030,6 +2057,9 @@ const message = { cleanLogscope: '清理类型', }, monitor: { + hostMonitor: '主机监控', + gpuMonitor: 'GPU 监控', + vllmMonitor: 'vLLM 监控', globalFilter: '全局筛选', enableMonitor: '监控状态', storeDays: '保存天数', @@ -2038,7 +2068,7 @@ const message = { defaultIO: '默认磁盘', defaultIOHelper: '默认监控和概览界面显示的磁盘选项', cleanMonitor: '清空监控记录', - cleanHelper: '该操作将清空包括 GPU 在内的所有监控记录,是否继续?', + cleanHelper: '该操作将清空 {0} 的所有历史记录,是否继续?', avgLoad: '平均负载', loadDetail: '负载详情', diff --git a/frontend/src/views/ai/gpu/history/index.vue b/frontend/src/views/ai/gpu/history/index.vue index 5f316a1c8..de25b035d 100644 --- a/frontend/src/views/ai/gpu/history/index.vue +++ b/frontend/src/views/ai/gpu/history/index.vue @@ -1,66 +1,96 @@