| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- package controller
- import (
- "fmt"
- "net/http"
- "os"
- "path/filepath"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/logger"
- "github.com/gin-gonic/gin"
- )
- // PerformanceStats 性能统计信息
- type PerformanceStats struct {
- // 缓存统计
- CacheStats common.DiskCacheStats `json:"cache_stats"`
- // 系统内存统计
- MemoryStats MemoryStats `json:"memory_stats"`
- // 磁盘缓存目录信息
- DiskCacheInfo DiskCacheInfo `json:"disk_cache_info"`
- // 磁盘空间信息
- DiskSpaceInfo common.DiskSpaceInfo `json:"disk_space_info"`
- // 配置信息
- Config PerformanceConfig `json:"config"`
- }
- // MemoryStats 内存统计
- type MemoryStats struct {
- // 已分配内存(字节)
- Alloc uint64 `json:"alloc"`
- // 总分配内存(字节)
- TotalAlloc uint64 `json:"total_alloc"`
- // 系统内存(字节)
- Sys uint64 `json:"sys"`
- // GC 次数
- NumGC uint32 `json:"num_gc"`
- // Goroutine 数量
- NumGoroutine int `json:"num_goroutine"`
- }
- // DiskCacheInfo 磁盘缓存目录信息
- type DiskCacheInfo struct {
- // 缓存目录路径
- Path string `json:"path"`
- // 目录是否存在
- Exists bool `json:"exists"`
- // 文件数量
- FileCount int `json:"file_count"`
- // 总大小(字节)
- TotalSize int64 `json:"total_size"`
- }
- // PerformanceConfig 性能配置
- type PerformanceConfig struct {
- // 是否启用磁盘缓存
- DiskCacheEnabled bool `json:"disk_cache_enabled"`
- // 磁盘缓存阈值(MB)
- DiskCacheThresholdMB int `json:"disk_cache_threshold_mb"`
- // 磁盘缓存最大大小(MB)
- DiskCacheMaxSizeMB int `json:"disk_cache_max_size_mb"`
- // 磁盘缓存路径
- DiskCachePath string `json:"disk_cache_path"`
- // 是否在容器中运行
- IsRunningInContainer bool `json:"is_running_in_container"`
- // MonitorEnabled 是否启用性能监控
- MonitorEnabled bool `json:"monitor_enabled"`
- // MonitorCPUThreshold CPU 使用率阈值(%)
- MonitorCPUThreshold int `json:"monitor_cpu_threshold"`
- // MonitorMemoryThreshold 内存使用率阈值(%)
- MonitorMemoryThreshold int `json:"monitor_memory_threshold"`
- // MonitorDiskThreshold 磁盘使用率阈值(%)
- MonitorDiskThreshold int `json:"monitor_disk_threshold"`
- }
- // GetPerformanceStats 获取性能统计信息
- func GetPerformanceStats(c *gin.Context) {
- // 不再每次获取统计都全量扫描磁盘,依赖原子计数器保证性能
- // 仅在系统启动或显式清理时同步
- cacheStats := common.GetDiskCacheStats()
- // 获取内存统计
- var memStats runtime.MemStats
- runtime.ReadMemStats(&memStats)
- // 获取磁盘缓存目录信息
- diskCacheInfo := getDiskCacheInfo()
- // 获取配置信息
- diskConfig := common.GetDiskCacheConfig()
- monitorConfig := common.GetPerformanceMonitorConfig()
- config := PerformanceConfig{
- DiskCacheEnabled: diskConfig.Enabled,
- DiskCacheThresholdMB: diskConfig.ThresholdMB,
- DiskCacheMaxSizeMB: diskConfig.MaxSizeMB,
- DiskCachePath: diskConfig.Path,
- IsRunningInContainer: common.IsRunningInContainer(),
- MonitorEnabled: monitorConfig.Enabled,
- MonitorCPUThreshold: monitorConfig.CPUThreshold,
- MonitorMemoryThreshold: monitorConfig.MemoryThreshold,
- MonitorDiskThreshold: monitorConfig.DiskThreshold,
- }
- // 获取磁盘空间信息
- // 使用缓存的系统状态,避免频繁调用系统 API
- systemStatus := common.GetSystemStatus()
- diskSpaceInfo := common.DiskSpaceInfo{
- UsedPercent: systemStatus.DiskUsage,
- }
- // 如果需要详细信息,可以按需获取,或者扩展 SystemStatus
- // 这里为了保持接口兼容性,我们仍然调用 GetDiskSpaceInfo,但注意这可能会有性能开销
- // 考虑到 GetPerformanceStats 是管理接口,频率较低,直接调用是可以接受的
- // 但为了一致性,我们也可以考虑从 SystemStatus 中获取部分信息
- diskSpaceInfo = common.GetDiskSpaceInfo()
- stats := PerformanceStats{
- CacheStats: cacheStats,
- MemoryStats: MemoryStats{
- Alloc: memStats.Alloc,
- TotalAlloc: memStats.TotalAlloc,
- Sys: memStats.Sys,
- NumGC: memStats.NumGC,
- NumGoroutine: runtime.NumGoroutine(),
- },
- DiskCacheInfo: diskCacheInfo,
- DiskSpaceInfo: diskSpaceInfo,
- Config: config,
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "data": stats,
- })
- }
- // ClearDiskCache 清理不活跃的磁盘缓存
- func ClearDiskCache(c *gin.Context) {
- // 清理超过 10 分钟未使用的缓存文件
- // 10 分钟是一个安全的阈值,确保正在进行的请求不会被误删
- err := common.CleanupOldDiskCacheFiles(10 * time.Minute)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "不活跃的磁盘缓存已清理",
- })
- }
- // ResetPerformanceStats 重置性能统计
- func ResetPerformanceStats(c *gin.Context) {
- common.ResetDiskCacheStats()
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "统计信息已重置",
- })
- }
- // ForceGC 强制执行 GC
- func ForceGC(c *gin.Context) {
- runtime.GC()
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "GC 已执行",
- })
- }
- // LogFileInfo 日志文件信息
- type LogFileInfo struct {
- Name string `json:"name"`
- Size int64 `json:"size"`
- ModTime time.Time `json:"mod_time"`
- }
- // LogFilesResponse 日志文件列表响应
- type LogFilesResponse struct {
- LogDir string `json:"log_dir"`
- Enabled bool `json:"enabled"`
- FileCount int `json:"file_count"`
- TotalSize int64 `json:"total_size"`
- OldestTime *time.Time `json:"oldest_time,omitempty"`
- NewestTime *time.Time `json:"newest_time,omitempty"`
- Files []LogFileInfo `json:"files"`
- }
- // getLogFiles 读取日志目录中的日志文件列表
- func getLogFiles() ([]LogFileInfo, error) {
- if *common.LogDir == "" {
- return nil, nil
- }
- entries, err := os.ReadDir(*common.LogDir)
- if err != nil {
- return nil, err
- }
- var files []LogFileInfo
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- name := entry.Name()
- if !strings.HasPrefix(name, "oneapi-") || !strings.HasSuffix(name, ".log") {
- continue
- }
- info, err := entry.Info()
- if err != nil {
- continue
- }
- files = append(files, LogFileInfo{
- Name: name,
- Size: info.Size(),
- ModTime: info.ModTime(),
- })
- }
- // 按文件名降序排列(最新在前)
- sort.Slice(files, func(i, j int) bool {
- return files[i].Name > files[j].Name
- })
- return files, nil
- }
- // GetLogFiles 获取日志文件列表
- func GetLogFiles(c *gin.Context) {
- if *common.LogDir == "" {
- common.ApiSuccess(c, LogFilesResponse{Enabled: false})
- return
- }
- files, err := getLogFiles()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- var totalSize int64
- var oldest, newest time.Time
- for i, f := range files {
- totalSize += f.Size
- if i == 0 || f.ModTime.Before(oldest) {
- oldest = f.ModTime
- }
- if i == 0 || f.ModTime.After(newest) {
- newest = f.ModTime
- }
- }
- resp := LogFilesResponse{
- LogDir: *common.LogDir,
- Enabled: true,
- FileCount: len(files),
- TotalSize: totalSize,
- Files: files,
- }
- if len(files) > 0 {
- resp.OldestTime = &oldest
- resp.NewestTime = &newest
- }
- common.ApiSuccess(c, resp)
- }
- // CleanupLogFiles 清理过期日志文件
- func CleanupLogFiles(c *gin.Context) {
- mode := c.Query("mode")
- valueStr := c.Query("value")
- if mode != "by_count" && mode != "by_days" {
- common.ApiErrorMsg(c, "invalid mode, must be by_count or by_days")
- return
- }
- value, err := strconv.Atoi(valueStr)
- if err != nil || value < 1 {
- common.ApiErrorMsg(c, "invalid value, must be a positive integer")
- return
- }
- if *common.LogDir == "" {
- common.ApiErrorMsg(c, "log directory not configured")
- return
- }
- files, err := getLogFiles()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- activeLogPath := logger.GetCurrentLogPath()
- var toDelete []LogFileInfo
- switch mode {
- case "by_count":
- // files 已按名称降序(最新在前),保留前 value 个
- for i, f := range files {
- if i < value {
- continue
- }
- fullPath := filepath.Join(*common.LogDir, f.Name)
- if fullPath == activeLogPath {
- continue
- }
- toDelete = append(toDelete, f)
- }
- case "by_days":
- cutoff := time.Now().AddDate(0, 0, -value)
- for _, f := range files {
- if f.ModTime.Before(cutoff) {
- fullPath := filepath.Join(*common.LogDir, f.Name)
- if fullPath == activeLogPath {
- continue
- }
- toDelete = append(toDelete, f)
- }
- }
- }
- var deletedCount int
- var freedBytes int64
- var failedFiles []string
- for _, f := range toDelete {
- fullPath := filepath.Join(*common.LogDir, f.Name)
- if err := os.Remove(fullPath); err != nil {
- failedFiles = append(failedFiles, f.Name)
- continue
- }
- deletedCount++
- freedBytes += f.Size
- }
- result := gin.H{
- "deleted_count": deletedCount,
- "freed_bytes": freedBytes,
- "failed_files": failedFiles,
- }
- if len(failedFiles) > 0 {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": fmt.Sprintf("部分文件删除失败(%d/%d)", len(failedFiles), len(toDelete)),
- "data": result,
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": result,
- })
- }
- // getDiskCacheInfo 获取磁盘缓存目录信息
- func getDiskCacheInfo() DiskCacheInfo {
- // 使用统一的缓存目录
- dir := common.GetDiskCacheDir()
- info := DiskCacheInfo{
- Path: dir,
- Exists: false,
- }
- entries, err := os.ReadDir(dir)
- if err != nil {
- return info
- }
- info.Exists = true
- info.FileCount = 0
- info.TotalSize = 0
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- info.FileCount++
- if fileInfo, err := entry.Info(); err == nil {
- info.TotalSize += fileInfo.Size()
- }
- }
- return info
- }
|