mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
feat: add vLLM monitoring (#13942)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
+15
-18
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
+184
-49
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
+11
-8
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -112,6 +112,8 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddDockerPortGuardReadOnly,
|
||||
migrations.MigrateFirewallPortWhitelistSources,
|
||||
migrations.AddAcceleratorMetrics,
|
||||
migrations.AddVLLMMonitor,
|
||||
migrations.AddMonitorSettings,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -117,6 +117,7 @@ export namespace AI {
|
||||
endTime: Date;
|
||||
}
|
||||
export interface MonitorGPUOptions {
|
||||
supported: boolean;
|
||||
gpuType: string;
|
||||
options: Array<string>;
|
||||
chartHide: Array<ChartHide>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Host.MonitorSetting>(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'استفاده از منابع',
|
||||
|
||||
@@ -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: '利用',
|
||||
|
||||
@@ -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: '자원 사용률',
|
||||
|
||||
@@ -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: 'ການນຳໃຊ້',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: 'Использование',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '資源使用率',
|
||||
|
||||
@@ -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: '负载详情',
|
||||
|
||||
@@ -1,66 +1,96 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<RouterMenu />
|
||||
<el-card class="history-toolbar">
|
||||
<div class="flex flex-wrap gap-3 items-center">
|
||||
<el-date-picker
|
||||
v-model="timeRangeGlobal"
|
||||
type="datetimerange"
|
||||
style="max-width: 100%; width: 360px; flex-grow: 0"
|
||||
range-separator="-"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
:clearable="false"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
@change="changeTimeRange"
|
||||
/>
|
||||
<el-select style="max-width: 100%" class="p-w-300" v-model="selectedDevice" @change="search">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-radio-group v-if="history && history.bucketSeconds > 0" v-model="aggregation" @change="search">
|
||||
<el-radio-button value="avg">{{ $t('aiTools.gpu.historyAverage') }}</el-radio-button>
|
||||
<el-radio-button value="max">{{ $t('aiTools.gpu.historyPeak') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<template v-if="supported">
|
||||
<el-card class="history-toolbar">
|
||||
<div class="flex flex-wrap gap-3 items-center">
|
||||
<el-date-picker
|
||||
v-model="timeRangeGlobal"
|
||||
type="datetimerange"
|
||||
style="max-width: 100%; width: 360px; flex-grow: 0"
|
||||
range-separator="-"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
:clearable="false"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
@change="changeTimeRange"
|
||||
/>
|
||||
<el-select style="max-width: 100%" class="p-w-300" v-model="selectedDevice" @change="search">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-radio-group v-if="history && history.bucketSeconds > 0" v-model="aggregation" @change="search">
|
||||
<el-radio-button value="avg">{{ $t('aiTools.gpu.historyAverage') }}</el-radio-button>
|
||||
<el-radio-button value="max">{{ $t('aiTools.gpu.historyPeak') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<TableRefresh @search="refresh" />
|
||||
</div>
|
||||
<div v-if="history?.sampleCount" class="history-summary">
|
||||
{{
|
||||
history.bucketSeconds > 0
|
||||
? $t('aiTools.gpu.historyAggregated', {
|
||||
seconds: history.bucketSeconds,
|
||||
count: history.sampleCount,
|
||||
})
|
||||
: $t('aiTools.gpu.historyRaw', { count: history.sampleCount })
|
||||
}}
|
||||
</div>
|
||||
</el-card>
|
||||
<el-alert
|
||||
v-if="currentDevice?.legacy"
|
||||
:title="$t('aiTools.gpu.legacyHistory')"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<el-alert
|
||||
v-if="failed"
|
||||
:title="$t('aiTools.gpu.historyLoadFailed')"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<el-empty v-else-if="!loading && !history?.sampleCount" :description="$t('commons.msg.noneData')" />
|
||||
<el-empty
|
||||
v-else-if="history?.sampleCount && !charts.length"
|
||||
:description="$t('aiTools.gpu.historyMetricEmpty')"
|
||||
/>
|
||||
<el-row v-else-if="history?.sampleCount" :gutter="12">
|
||||
<el-col v-for="chart in charts" :key="chart.id" :xs="24" :md="12">
|
||||
<el-card class="card-interval">
|
||||
<template #header>{{ chart.title }}</template>
|
||||
<v-charts :id="chart.id" height="320px" type="line" :option="chart.option" :dataZoom="true" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<LayoutContent v-else-if="!loading" :title="$t('aiTools.gpu.gpu')" :divider="true">
|
||||
<template #rightToolBar>
|
||||
<TableRefresh @search="refresh" />
|
||||
</div>
|
||||
<div v-if="history?.sampleCount" class="history-summary">
|
||||
{{
|
||||
history.bucketSeconds > 0
|
||||
? $t('aiTools.gpu.historyAggregated', {
|
||||
seconds: history.bucketSeconds,
|
||||
count: history.sampleCount,
|
||||
})
|
||||
: $t('aiTools.gpu.historyRaw', { count: history.sampleCount })
|
||||
}}
|
||||
</div>
|
||||
</el-card>
|
||||
<el-alert
|
||||
v-if="currentDevice?.legacy"
|
||||
:title="$t('aiTools.gpu.legacyHistory')"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<el-alert v-if="failed" :title="$t('aiTools.gpu.historyLoadFailed')" type="error" :closable="false" show-icon />
|
||||
<el-empty v-else-if="!loading && !history?.sampleCount" :description="$t('commons.msg.noneData')" />
|
||||
<el-empty
|
||||
v-else-if="history?.sampleCount && !charts.length"
|
||||
:description="$t('aiTools.gpu.historyMetricEmpty')"
|
||||
/>
|
||||
<el-row v-else-if="history?.sampleCount" :gutter="12">
|
||||
<el-col v-for="chart in charts" :key="chart.id" :xs="24" :md="12">
|
||||
<el-card class="card-interval">
|
||||
<template #header>{{ chart.title }}</template>
|
||||
<v-charts :id="chart.id" height="320px" type="line" :option="chart.option" :dataZoom="true" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<el-alert
|
||||
v-if="failed"
|
||||
:title="$t('aiTools.gpu.historyLoadFailed')"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<div v-else class="app-warn">
|
||||
<div class="flx-center">
|
||||
<span>{{ $t('aiTools.gpu.gpuHelper') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { loadGPUMonitor, getGPUOptions } from '@/api/modules/ai';
|
||||
import RouterMenu from '@/views/ai/gpu/index.vue';
|
||||
import { shortcuts } from '@/utils/shortcuts';
|
||||
@@ -71,6 +101,7 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isMobile } = useGlobalStore();
|
||||
const loading = ref(false);
|
||||
const failed = ref(false);
|
||||
const supported = ref(false);
|
||||
const options = ref<(AI.ChartHide & { value: string; label: string })[]>([]);
|
||||
const selectedDevice = ref('');
|
||||
const aggregation = ref<'avg' | 'max'>('avg');
|
||||
@@ -273,7 +304,10 @@ const search = async () => {
|
||||
history.value = undefined;
|
||||
failed.value = false;
|
||||
const device = currentDevice.value;
|
||||
if (!device || !timeRangeGlobal.value) return;
|
||||
if (!device || !timeRangeGlobal.value) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await loadGPUMonitor({
|
||||
@@ -296,11 +330,15 @@ const changeTimeRange = () => {
|
||||
void search();
|
||||
};
|
||||
const loadOptions = async () => {
|
||||
const id = ++requestID;
|
||||
history.value = undefined;
|
||||
loading.value = true;
|
||||
failed.value = false;
|
||||
try {
|
||||
const response = await getGPUOptions();
|
||||
options.value = (response.data.chartHide || []).map((item) => ({
|
||||
if (id !== requestID) return;
|
||||
supported.value = response.data.supported;
|
||||
options.value = (supported.value ? response.data.chartHide || [] : []).map((item) => ({
|
||||
...item,
|
||||
value: item.deviceID || `legacy:${item.productName}`,
|
||||
label: `${item.productName} · ${item.deviceID || i18n.global.t('aiTools.gpu.legacyDevice')}`,
|
||||
@@ -308,9 +346,9 @@ const loadOptions = async () => {
|
||||
if (!currentDevice.value) selectedDevice.value = options.value[0]?.value || '';
|
||||
await search();
|
||||
} catch {
|
||||
failed.value = true;
|
||||
if (id === requestID) failed.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
if (id === requestID) loading.value = false;
|
||||
}
|
||||
};
|
||||
const refresh = () => {
|
||||
@@ -364,6 +402,9 @@ const loadProcessType = (val: string) => {
|
||||
};
|
||||
|
||||
onMounted(loadOptions);
|
||||
onBeforeUnmount(() => {
|
||||
requestID++;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -26,8 +26,10 @@ import { updateMonitorSetting } from '@/api/modules/host';
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
interface DialogProps {
|
||||
key: 'MonitorStoreDays' | 'GPUMonitorStoreDays' | 'VLLMMonitorStoreDays';
|
||||
monitorStoreDays: number;
|
||||
}
|
||||
const settingKey = ref<DialogProps['key']>('MonitorStoreDays');
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
@@ -38,6 +40,7 @@ const form = reactive({
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
settingKey.value = params.key;
|
||||
form.monitorStoreDays = params.monitorStoreDays;
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
@@ -47,7 +50,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
await updateMonitorSetting('MonitorStoreDays', form.monitorStoreDays + '')
|
||||
await updateMonitorSetting(settingKey.value, form.monitorStoreDays + '')
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
handleClose();
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
<div>
|
||||
<MonitorRouter />
|
||||
|
||||
<LayoutContent v-loading="loading" :title="$t('menu.settings', 2)" :divider="true">
|
||||
<LayoutContent v-loading="loading" :title="$t('menu.settings', 2)">
|
||||
<template #main>
|
||||
<el-form :model="form" @submit.prevent ref="panelFormRef" label-position="left" label-width="160px">
|
||||
<el-form :model="form" @submit.prevent label-position="left" label-width="160px">
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="12">
|
||||
<h3 class="mb-4">{{ $t('monitor.hostMonitor') }}</h3>
|
||||
<el-form-item :label="$t('monitor.enableMonitor')" prop="monitorStatus">
|
||||
<el-switch
|
||||
v-permission
|
||||
v-node-admin
|
||||
@change="onSaveStatus"
|
||||
@change="onSaveStatus('MonitorStatus', form.monitorStatus)"
|
||||
v-model="form.monitorStatus"
|
||||
active-value="Enable"
|
||||
inactive-value="Disable"
|
||||
@@ -21,7 +22,12 @@
|
||||
<el-form-item :label="$t('monitor.storeDays')" prop="monitorStoreDays">
|
||||
<el-input disabled v-model="form.monitorStoreDays">
|
||||
<template #append>
|
||||
<el-button v-permission v-node-admin @click="onChangeStoreDays" icon="Setting">
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="onChangeStoreDays('MonitorStoreDays', form.monitorStoreDays)"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -30,7 +36,12 @@
|
||||
<el-form-item :label="$t('monitor.interval')" prop="monitorIntervalItem">
|
||||
<el-input disabled v-model="form.monitorIntervalItem">
|
||||
<template #append>
|
||||
<el-button v-permission v-node-admin @click="onChangeInterval" icon="Setting">
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="onChangeInterval('MonitorInterval', form.monitorInterval)"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -57,10 +68,97 @@
|
||||
<span class="input-help">{{ $t('monitor.defaultIOHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-permission v-node-admin @click="onClean()" icon="Delete">
|
||||
<el-button v-permission v-node-admin @click="onClean('host')" icon="Delete">
|
||||
{{ $t('monitor.cleanMonitor') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-divider />
|
||||
<h3 class="mb-4">{{ $t('monitor.gpuMonitor') }}</h3>
|
||||
<el-form-item :label="$t('monitor.enableMonitor')" prop="gpuMonitorStatus">
|
||||
<el-switch
|
||||
v-permission
|
||||
v-node-admin
|
||||
@change="onSaveStatus('GPUMonitorStatus', form.gpuMonitorStatus)"
|
||||
v-model="form.gpuMonitorStatus"
|
||||
active-value="Enable"
|
||||
inactive-value="Disable"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('monitor.storeDays')" prop="gpuMonitorStoreDays">
|
||||
<el-input disabled v-model="form.gpuMonitorStoreDays">
|
||||
<template #append>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="onChangeStoreDays('GPUMonitorStoreDays', form.gpuMonitorStoreDays)"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('monitor.interval')" prop="gpuMonitorIntervalItem">
|
||||
<el-input disabled v-model="form.gpuMonitorIntervalItem">
|
||||
<template #append>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="onChangeInterval('GPUMonitorInterval', form.gpuMonitorInterval)"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-permission v-node-admin @click="onClean('gpu')" icon="Delete">
|
||||
{{ $t('monitor.cleanMonitor') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-divider />
|
||||
<h3 class="mb-4">{{ $t('monitor.vllmMonitor') }}</h3>
|
||||
<el-form-item :label="$t('monitor.enableMonitor')" prop="vllmMonitorStatus">
|
||||
<el-switch
|
||||
v-permission
|
||||
v-node-admin
|
||||
@change="onSaveStatus('VLLMMonitorStatus', form.vllmMonitorStatus)"
|
||||
v-model="form.vllmMonitorStatus"
|
||||
active-value="Enable"
|
||||
inactive-value="Disable"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('monitor.storeDays')" prop="vllmMonitorStoreDays">
|
||||
<el-input disabled v-model="form.vllmMonitorStoreDays">
|
||||
<template #append>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="
|
||||
onChangeStoreDays('VLLMMonitorStoreDays', form.vllmMonitorStoreDays)
|
||||
"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('monitor.interval')" prop="vllmMonitorIntervalItem">
|
||||
<el-input disabled v-model="form.vllmMonitorIntervalItem">
|
||||
<template #append>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@click="onChangeInterval('VLLMMonitorInterval', form.vllmMonitorInterval)"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@@ -76,7 +174,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox, FormInstance } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { cleanMonitors, loadMonitorSetting, updateMonitorSetting } from '@/api/modules/host';
|
||||
import MonitorRouter from '@/views/host/monitor/index.vue';
|
||||
import Interval from '@/views/host/monitor/setting/interval/index.vue';
|
||||
@@ -88,16 +186,21 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { splitTimeFromSecond, transTimeUnit } from '@/utils/validate';
|
||||
const loading = ref();
|
||||
const form = reactive({
|
||||
gpuMonitorStatus: 'Disable',
|
||||
gpuMonitorStoreDays: 30,
|
||||
gpuMonitorInterval: 300,
|
||||
gpuMonitorIntervalItem: '',
|
||||
vllmMonitorStatus: 'Disable',
|
||||
vllmMonitorStoreDays: 30,
|
||||
vllmMonitorInterval: 300,
|
||||
vllmMonitorIntervalItem: '',
|
||||
monitorStatus: 'Disable',
|
||||
monitorStoreDays: 30,
|
||||
monitorInterval: 300,
|
||||
timeItem: 5,
|
||||
timeUnit: 'm',
|
||||
monitorIntervalItem: '',
|
||||
defaultNetwork: '',
|
||||
defaultIO: '',
|
||||
});
|
||||
const panelFormRef = ref<FormInstance>();
|
||||
|
||||
const intervalRef = ref();
|
||||
const daysRef = ref();
|
||||
@@ -106,12 +209,20 @@ const ioRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
const res = await loadMonitorSetting();
|
||||
form.gpuMonitorStatus = res.data.gpuMonitorStatus;
|
||||
form.gpuMonitorStoreDays = Number(res.data.gpuMonitorStoreDays);
|
||||
form.gpuMonitorInterval = Number(res.data.gpuMonitorInterval);
|
||||
const gpuInterval = splitTimeFromSecond(form.gpuMonitorInterval);
|
||||
form.gpuMonitorIntervalItem = transTimeUnit(gpuInterval.timeItem + gpuInterval.timeUnit);
|
||||
form.vllmMonitorStatus = res.data.vllmMonitorStatus;
|
||||
form.vllmMonitorStoreDays = Number(res.data.vllmMonitorStoreDays);
|
||||
form.vllmMonitorInterval = Number(res.data.vllmMonitorInterval);
|
||||
const vllmInterval = splitTimeFromSecond(form.vllmMonitorInterval);
|
||||
form.vllmMonitorIntervalItem = transTimeUnit(vllmInterval.timeItem + vllmInterval.timeUnit);
|
||||
form.monitorStatus = res.data.monitorStatus;
|
||||
form.monitorInterval = Number(res.data.monitorInterval);
|
||||
let item = splitTimeFromSecond(form.monitorInterval);
|
||||
form.timeItem = item.timeItem;
|
||||
form.timeUnit = item.timeUnit;
|
||||
form.monitorIntervalItem = transTimeUnit(form.timeItem + form.timeUnit);
|
||||
const item = splitTimeFromSecond(form.monitorInterval);
|
||||
form.monitorIntervalItem = transTimeUnit(item.timeItem + item.timeUnit);
|
||||
|
||||
form.monitorStoreDays = Number(res.data.monitorStoreDays);
|
||||
form.defaultNetwork =
|
||||
@@ -119,23 +230,25 @@ const search = async () => {
|
||||
form.defaultIO = res.data.defaultIO === 'all' ? i18n.global.t('commons.table.all') : res.data.defaultIO;
|
||||
};
|
||||
|
||||
const onSaveStatus = async () => {
|
||||
const onSaveStatus = async (key: 'MonitorStatus' | 'GPUMonitorStatus' | 'VLLMMonitorStatus', value: string) => {
|
||||
loading.value = true;
|
||||
await updateMonitorSetting('MonitorStatus', form.monitorStatus)
|
||||
await updateMonitorSetting(key, value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(async () => {
|
||||
loading.value = false;
|
||||
await search();
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeStoreDays = () => {
|
||||
daysRef.value.acceptParams({ monitorStoreDays: form.monitorStoreDays });
|
||||
const onChangeStoreDays = (key: 'MonitorStoreDays' | 'GPUMonitorStoreDays' | 'VLLMMonitorStoreDays', value: number) => {
|
||||
daysRef.value.acceptParams({ key, monitorStoreDays: value });
|
||||
};
|
||||
const onChangeInterval = () => {
|
||||
intervalRef.value.acceptParams({ timeItem: form.timeItem, timeUnit: form.timeUnit });
|
||||
const onChangeInterval = (key: 'MonitorInterval' | 'GPUMonitorInterval' | 'VLLMMonitorInterval', value: number) => {
|
||||
const interval = splitTimeFromSecond(value);
|
||||
intervalRef.value.acceptParams({ key, timeItem: interval.timeItem, timeUnit: interval.timeUnit });
|
||||
};
|
||||
const onChangeNetwork = () => {
|
||||
networkRef.value.acceptParams({ defaultNetwork: form.defaultNetwork });
|
||||
@@ -144,14 +257,15 @@ const onChangeIO = () => {
|
||||
ioRef.value.acceptParams({ defaultIO: form.defaultIO });
|
||||
};
|
||||
|
||||
const onClean = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('monitor.cleanHelper'), i18n.global.t('monitor.cleanMonitor'), {
|
||||
const onClean = async (type: 'host' | 'gpu') => {
|
||||
const name = i18n.global.t(type === 'host' ? 'monitor.hostMonitor' : 'monitor.gpuMonitor');
|
||||
ElMessageBox.confirm(i18n.global.t('monitor.cleanHelper', [name]), i18n.global.t('monitor.cleanMonitor'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await cleanMonitors()
|
||||
await cleanMonitors(type)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
||||
@@ -34,9 +34,11 @@ import { transferTimeToSecond } from '@/utils/validate';
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
interface DialogProps {
|
||||
key: 'MonitorInterval' | 'GPUMonitorInterval' | 'VLLMMonitorInterval';
|
||||
timeItem: number;
|
||||
timeUnit: string;
|
||||
}
|
||||
const settingKey = ref<DialogProps['key']>('MonitorInterval');
|
||||
const drawerVisible = ref();
|
||||
const loading = ref();
|
||||
|
||||
@@ -60,8 +62,10 @@ const rules = reactive({
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
settingKey.value = params.key;
|
||||
form.timeItem = params.timeItem;
|
||||
form.timeUnit = params.timeUnit;
|
||||
loadInterval();
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
@@ -74,7 +78,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
await updateMonitorSetting('MonitorInterval', form.monitorInterval + '')
|
||||
await updateMonitorSetting(settingKey.value, form.monitorInterval + '')
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
handleClose();
|
||||
|
||||
@@ -296,6 +296,13 @@ const exactReplacements: Record<string, string> = {
|
||||
OpsReportAutoExport: 'xpack.opsReport.page.autoExport',
|
||||
ComplexityVerification: 'setting.complexity',
|
||||
MFAStatus: 'setting.mfa',
|
||||
GPUMonitorStatus: 'monitor.enableMonitor',
|
||||
GPUMonitorStoreDays: 'monitor.storeDays',
|
||||
GPUMonitorInterval: 'monitor.interval',
|
||||
VLLMMonitorStatus: 'monitor.enableMonitor',
|
||||
VLLMMonitorStoreDays: 'monitor.storeDays',
|
||||
VLLMMonitorInterval: 'monitor.interval',
|
||||
MonitorInterval: 'monitor.interval',
|
||||
MonitorStatus: 'monitor.enableMonitor',
|
||||
MonitorStoreDays: 'monitor.storeDays',
|
||||
ApiInterfaceStatus: 'setting.apiInterface',
|
||||
|
||||
Reference in New Issue
Block a user