performance.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package controller
  2. import (
  3. "net/http"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "github.com/QuantumNous/new-api/common"
  12. "github.com/QuantumNous/new-api/logger"
  13. "github.com/gin-gonic/gin"
  14. )
  15. // PerformanceStats 性能统计信息
  16. type PerformanceStats struct {
  17. // 缓存统计
  18. CacheStats common.DiskCacheStats `json:"cache_stats"`
  19. // 系统内存统计
  20. MemoryStats MemoryStats `json:"memory_stats"`
  21. // 磁盘缓存目录信息
  22. DiskCacheInfo DiskCacheInfo `json:"disk_cache_info"`
  23. // 磁盘空间信息
  24. DiskSpaceInfo common.DiskSpaceInfo `json:"disk_space_info"`
  25. // 配置信息
  26. Config PerformanceConfig `json:"config"`
  27. }
  28. // MemoryStats 内存统计
  29. type MemoryStats struct {
  30. // 已分配内存(字节)
  31. Alloc uint64 `json:"alloc"`
  32. // 总分配内存(字节)
  33. TotalAlloc uint64 `json:"total_alloc"`
  34. // 系统内存(字节)
  35. Sys uint64 `json:"sys"`
  36. // GC 次数
  37. NumGC uint32 `json:"num_gc"`
  38. // Goroutine 数量
  39. NumGoroutine int `json:"num_goroutine"`
  40. }
  41. // DiskCacheInfo 磁盘缓存目录信息
  42. type DiskCacheInfo struct {
  43. // 缓存目录路径
  44. Path string `json:"path"`
  45. // 目录是否存在
  46. Exists bool `json:"exists"`
  47. // 文件数量
  48. FileCount int `json:"file_count"`
  49. // 总大小(字节)
  50. TotalSize int64 `json:"total_size"`
  51. }
  52. // PerformanceConfig 性能配置
  53. type PerformanceConfig struct {
  54. // 是否启用磁盘缓存
  55. DiskCacheEnabled bool `json:"disk_cache_enabled"`
  56. // 磁盘缓存阈值(MB)
  57. DiskCacheThresholdMB int `json:"disk_cache_threshold_mb"`
  58. // 磁盘缓存最大大小(MB)
  59. DiskCacheMaxSizeMB int `json:"disk_cache_max_size_mb"`
  60. // 磁盘缓存路径
  61. DiskCachePath string `json:"disk_cache_path"`
  62. // 是否在容器中运行
  63. IsRunningInContainer bool `json:"is_running_in_container"`
  64. // MonitorEnabled 是否启用性能监控
  65. MonitorEnabled bool `json:"monitor_enabled"`
  66. // MonitorCPUThreshold CPU 使用率阈值(%)
  67. MonitorCPUThreshold int `json:"monitor_cpu_threshold"`
  68. // MonitorMemoryThreshold 内存使用率阈值(%)
  69. MonitorMemoryThreshold int `json:"monitor_memory_threshold"`
  70. // MonitorDiskThreshold 磁盘使用率阈值(%)
  71. MonitorDiskThreshold int `json:"monitor_disk_threshold"`
  72. }
  73. // GetPerformanceStats 获取性能统计信息
  74. func GetPerformanceStats(c *gin.Context) {
  75. // 不再每次获取统计都全量扫描磁盘,依赖原子计数器保证性能
  76. // 仅在系统启动或显式清理时同步
  77. cacheStats := common.GetDiskCacheStats()
  78. // 获取内存统计
  79. var memStats runtime.MemStats
  80. runtime.ReadMemStats(&memStats)
  81. // 获取磁盘缓存目录信息
  82. diskCacheInfo := getDiskCacheInfo()
  83. // 获取配置信息
  84. diskConfig := common.GetDiskCacheConfig()
  85. monitorConfig := common.GetPerformanceMonitorConfig()
  86. config := PerformanceConfig{
  87. DiskCacheEnabled: diskConfig.Enabled,
  88. DiskCacheThresholdMB: diskConfig.ThresholdMB,
  89. DiskCacheMaxSizeMB: diskConfig.MaxSizeMB,
  90. DiskCachePath: diskConfig.Path,
  91. IsRunningInContainer: common.IsRunningInContainer(),
  92. MonitorEnabled: monitorConfig.Enabled,
  93. MonitorCPUThreshold: monitorConfig.CPUThreshold,
  94. MonitorMemoryThreshold: monitorConfig.MemoryThreshold,
  95. MonitorDiskThreshold: monitorConfig.DiskThreshold,
  96. }
  97. // 获取磁盘空间信息
  98. // 使用缓存的系统状态,避免频繁调用系统 API
  99. systemStatus := common.GetSystemStatus()
  100. diskSpaceInfo := common.DiskSpaceInfo{
  101. UsedPercent: systemStatus.DiskUsage,
  102. }
  103. // 如果需要详细信息,可以按需获取,或者扩展 SystemStatus
  104. // 这里为了保持接口兼容性,我们仍然调用 GetDiskSpaceInfo,但注意这可能会有性能开销
  105. // 考虑到 GetPerformanceStats 是管理接口,频率较低,直接调用是可以接受的
  106. // 但为了一致性,我们也可以考虑从 SystemStatus 中获取部分信息
  107. diskSpaceInfo = common.GetDiskSpaceInfo()
  108. stats := PerformanceStats{
  109. CacheStats: cacheStats,
  110. MemoryStats: MemoryStats{
  111. Alloc: memStats.Alloc,
  112. TotalAlloc: memStats.TotalAlloc,
  113. Sys: memStats.Sys,
  114. NumGC: memStats.NumGC,
  115. NumGoroutine: runtime.NumGoroutine(),
  116. },
  117. DiskCacheInfo: diskCacheInfo,
  118. DiskSpaceInfo: diskSpaceInfo,
  119. Config: config,
  120. }
  121. c.JSON(http.StatusOK, gin.H{
  122. "success": true,
  123. "data": stats,
  124. })
  125. }
  126. // ClearDiskCache 清理不活跃的磁盘缓存
  127. func ClearDiskCache(c *gin.Context) {
  128. // 清理超过 10 分钟未使用的缓存文件
  129. // 10 分钟是一个安全的阈值,确保正在进行的请求不会被误删
  130. err := common.CleanupOldDiskCacheFiles(10 * time.Minute)
  131. if err != nil {
  132. common.ApiError(c, err)
  133. return
  134. }
  135. c.JSON(http.StatusOK, gin.H{
  136. "success": true,
  137. "message": "不活跃的磁盘缓存已清理",
  138. })
  139. }
  140. // ResetPerformanceStats 重置性能统计
  141. func ResetPerformanceStats(c *gin.Context) {
  142. common.ResetDiskCacheStats()
  143. c.JSON(http.StatusOK, gin.H{
  144. "success": true,
  145. "message": "统计信息已重置",
  146. })
  147. }
  148. // ForceGC 强制执行 GC
  149. func ForceGC(c *gin.Context) {
  150. runtime.GC()
  151. c.JSON(http.StatusOK, gin.H{
  152. "success": true,
  153. "message": "GC 已执行",
  154. })
  155. }
  156. // LogFileInfo 日志文件信息
  157. type LogFileInfo struct {
  158. Name string `json:"name"`
  159. Size int64 `json:"size"`
  160. ModTime time.Time `json:"mod_time"`
  161. }
  162. // LogFilesResponse 日志文件列表响应
  163. type LogFilesResponse struct {
  164. LogDir string `json:"log_dir"`
  165. Enabled bool `json:"enabled"`
  166. FileCount int `json:"file_count"`
  167. TotalSize int64 `json:"total_size"`
  168. OldestTime *time.Time `json:"oldest_time,omitempty"`
  169. NewestTime *time.Time `json:"newest_time,omitempty"`
  170. Files []LogFileInfo `json:"files"`
  171. }
  172. // getLogFiles 读取日志目录中的日志文件列表
  173. func getLogFiles() ([]LogFileInfo, error) {
  174. if *common.LogDir == "" {
  175. return nil, nil
  176. }
  177. entries, err := os.ReadDir(*common.LogDir)
  178. if err != nil {
  179. return nil, err
  180. }
  181. var files []LogFileInfo
  182. for _, entry := range entries {
  183. if entry.IsDir() {
  184. continue
  185. }
  186. name := entry.Name()
  187. if !strings.HasPrefix(name, "oneapi-") || !strings.HasSuffix(name, ".log") {
  188. continue
  189. }
  190. info, err := entry.Info()
  191. if err != nil {
  192. continue
  193. }
  194. files = append(files, LogFileInfo{
  195. Name: name,
  196. Size: info.Size(),
  197. ModTime: info.ModTime(),
  198. })
  199. }
  200. // 按文件名降序排列(最新在前)
  201. sort.Slice(files, func(i, j int) bool {
  202. return files[i].Name > files[j].Name
  203. })
  204. return files, nil
  205. }
  206. // GetLogFiles 获取日志文件列表
  207. func GetLogFiles(c *gin.Context) {
  208. if *common.LogDir == "" {
  209. common.ApiSuccess(c, LogFilesResponse{Enabled: false})
  210. return
  211. }
  212. files, err := getLogFiles()
  213. if err != nil {
  214. common.ApiError(c, err)
  215. return
  216. }
  217. var totalSize int64
  218. var oldest, newest time.Time
  219. for i, f := range files {
  220. totalSize += f.Size
  221. if i == 0 || f.ModTime.Before(oldest) {
  222. oldest = f.ModTime
  223. }
  224. if i == 0 || f.ModTime.After(newest) {
  225. newest = f.ModTime
  226. }
  227. }
  228. resp := LogFilesResponse{
  229. LogDir: *common.LogDir,
  230. Enabled: true,
  231. FileCount: len(files),
  232. TotalSize: totalSize,
  233. Files: files,
  234. }
  235. if len(files) > 0 {
  236. resp.OldestTime = &oldest
  237. resp.NewestTime = &newest
  238. }
  239. common.ApiSuccess(c, resp)
  240. }
  241. // CleanupLogFiles 清理过期日志文件
  242. func CleanupLogFiles(c *gin.Context) {
  243. mode := c.Query("mode")
  244. valueStr := c.Query("value")
  245. if mode != "by_count" && mode != "by_days" {
  246. common.ApiErrorMsg(c, "invalid mode, must be by_count or by_days")
  247. return
  248. }
  249. value, err := strconv.Atoi(valueStr)
  250. if err != nil || value < 1 {
  251. common.ApiErrorMsg(c, "invalid value, must be a positive integer")
  252. return
  253. }
  254. if *common.LogDir == "" {
  255. common.ApiErrorMsg(c, "log directory not configured")
  256. return
  257. }
  258. files, err := getLogFiles()
  259. if err != nil {
  260. common.ApiError(c, err)
  261. return
  262. }
  263. activeLogPath := logger.GetCurrentLogPath()
  264. var toDelete []LogFileInfo
  265. switch mode {
  266. case "by_count":
  267. // files 已按名称降序(最新在前),保留前 value 个
  268. for i, f := range files {
  269. if i < value {
  270. continue
  271. }
  272. fullPath := filepath.Join(*common.LogDir, f.Name)
  273. if fullPath == activeLogPath {
  274. continue
  275. }
  276. toDelete = append(toDelete, f)
  277. }
  278. case "by_days":
  279. cutoff := time.Now().AddDate(0, 0, -value)
  280. for _, f := range files {
  281. if f.ModTime.Before(cutoff) {
  282. fullPath := filepath.Join(*common.LogDir, f.Name)
  283. if fullPath == activeLogPath {
  284. continue
  285. }
  286. toDelete = append(toDelete, f)
  287. }
  288. }
  289. }
  290. var deletedCount int
  291. var freedBytes int64
  292. var failedFiles []string
  293. for _, f := range toDelete {
  294. fullPath := filepath.Join(*common.LogDir, f.Name)
  295. if err := os.Remove(fullPath); err != nil {
  296. failedFiles = append(failedFiles, f.Name)
  297. continue
  298. }
  299. deletedCount++
  300. freedBytes += f.Size
  301. }
  302. c.JSON(http.StatusOK, gin.H{
  303. "success": true,
  304. "message": "",
  305. "data": gin.H{
  306. "deleted_count": deletedCount,
  307. "freed_bytes": freedBytes,
  308. "failed_files": failedFiles,
  309. },
  310. })
  311. }
  312. // getDiskCacheInfo 获取磁盘缓存目录信息
  313. func getDiskCacheInfo() DiskCacheInfo {
  314. // 使用统一的缓存目录
  315. dir := common.GetDiskCacheDir()
  316. info := DiskCacheInfo{
  317. Path: dir,
  318. Exists: false,
  319. }
  320. entries, err := os.ReadDir(dir)
  321. if err != nil {
  322. return info
  323. }
  324. info.Exists = true
  325. info.FileCount = 0
  326. info.TotalSize = 0
  327. for _, entry := range entries {
  328. if entry.IsDir() {
  329. continue
  330. }
  331. info.FileCount++
  332. if fileInfo, err := entry.Info(); err == nil {
  333. info.TotalSize += fileInfo.Size()
  334. }
  335. }
  336. return info
  337. }