|
@@ -4,107 +4,37 @@
|
|
|
package nginx
|
|
|
|
|
|
import (
|
|
|
- "fmt"
|
|
|
- "io"
|
|
|
- "math"
|
|
|
+ "errors"
|
|
|
"net/http"
|
|
|
- "os"
|
|
|
- "os/exec"
|
|
|
- "regexp"
|
|
|
- "runtime"
|
|
|
- "strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
|
|
"github.com/gin-gonic/gin"
|
|
|
- "github.com/shirou/gopsutil/v4/process"
|
|
|
+ "github.com/uozi-tech/cosy"
|
|
|
"github.com/uozi-tech/cosy/logger"
|
|
|
)
|
|
|
|
|
|
// NginxPerformanceInfo 存储 Nginx 性能相关信息
|
|
|
type NginxPerformanceInfo struct {
|
|
|
// 基本状态信息
|
|
|
- Active int `json:"active"` // 活动连接数
|
|
|
- Accepts int `json:"accepts"` // 总握手次数
|
|
|
- Handled int `json:"handled"` // 总连接次数
|
|
|
- Requests int `json:"requests"` // 总请求数
|
|
|
- Reading int `json:"reading"` // 读取客户端请求数
|
|
|
- Writing int `json:"writing"` // 响应数
|
|
|
- Waiting int `json:"waiting"` // 驻留进程(等待请求)
|
|
|
+ nginx.StubStatusData
|
|
|
|
|
|
// 进程相关信息
|
|
|
- Workers int `json:"workers"` // 工作进程数
|
|
|
- Master int `json:"master"` // 主进程数
|
|
|
- Cache int `json:"cache"` // 缓存管理进程数
|
|
|
- Other int `json:"other"` // 其他Nginx相关进程数
|
|
|
- CPUUsage float64 `json:"cpu_usage"` // CPU 使用率
|
|
|
- MemoryUsage float64 `json:"memory_usage"` // 内存使用率(MB)
|
|
|
+ nginx.NginxProcessInfo
|
|
|
|
|
|
// 配置信息
|
|
|
- WorkerProcesses int `json:"worker_processes"` // worker_processes 配置
|
|
|
- WorkerConnections int `json:"worker_connections"` // worker_connections 配置
|
|
|
+ nginx.NginxConfigInfo
|
|
|
}
|
|
|
|
|
|
-// GetDetailedStatus 获取 Nginx 详细状态信息
|
|
|
-func GetDetailedStatus(c *gin.Context) {
|
|
|
- // 检查 Nginx 是否运行
|
|
|
- pidPath := nginx.GetPIDPath()
|
|
|
- running := true
|
|
|
- if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 {
|
|
|
- running = false
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "running": false,
|
|
|
- "message": "Nginx is not running",
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 获取 stub_status 模块数据
|
|
|
- stubStatusInfo, err := getStubStatusInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get stub_status info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 获取进程信息
|
|
|
- processInfo, err := getNginxProcessInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get process info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 获取配置信息
|
|
|
- configInfo, err := getNginxConfigInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get config info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 组合所有信息
|
|
|
- info := NginxPerformanceInfo{
|
|
|
- Active: stubStatusInfo["active"],
|
|
|
- Accepts: stubStatusInfo["accepts"],
|
|
|
- Handled: stubStatusInfo["handled"],
|
|
|
- Requests: stubStatusInfo["requests"],
|
|
|
- Reading: stubStatusInfo["reading"],
|
|
|
- Writing: stubStatusInfo["writing"],
|
|
|
- Waiting: stubStatusInfo["waiting"],
|
|
|
- Workers: processInfo["workers"].(int),
|
|
|
- Master: processInfo["master"].(int),
|
|
|
- Cache: processInfo["cache"].(int),
|
|
|
- Other: processInfo["other"].(int),
|
|
|
- CPUUsage: processInfo["cpu_usage"].(float64),
|
|
|
- MemoryUsage: processInfo["memory_usage"].(float64),
|
|
|
- WorkerProcesses: configInfo["worker_processes"],
|
|
|
- WorkerConnections: configInfo["worker_connections"],
|
|
|
- }
|
|
|
-
|
|
|
- c.JSON(http.StatusOK, gin.H{
|
|
|
- "running": running,
|
|
|
- "info": info,
|
|
|
- })
|
|
|
+// GetDetailStatus 获取 Nginx 详细状态信息
|
|
|
+func GetDetailStatus(c *gin.Context) {
|
|
|
+ response := nginx.GetPerformanceData()
|
|
|
+ c.JSON(http.StatusOK, response)
|
|
|
}
|
|
|
|
|
|
-// StreamDetailedStatus 使用 SSE 流式推送 Nginx 详细状态信息
|
|
|
-func StreamDetailedStatus(c *gin.Context) {
|
|
|
+// StreamDetailStatus 使用 SSE 流式推送 Nginx 详细状态信息
|
|
|
+func StreamDetailStatus(c *gin.Context) {
|
|
|
// 设置 SSE 的响应头
|
|
|
c.Header("Content-Type", "text/event-stream")
|
|
|
c.Header("Cache-Control", "no-cache")
|
|
@@ -140,312 +70,63 @@ func StreamDetailedStatus(c *gin.Context) {
|
|
|
|
|
|
// sendPerformanceData 发送一次性能数据
|
|
|
func sendPerformanceData(c *gin.Context) error {
|
|
|
- // 检查 Nginx 是否运行
|
|
|
- pidPath := nginx.GetPIDPath()
|
|
|
- running := true
|
|
|
- if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 {
|
|
|
- running = false
|
|
|
- // 发送 Nginx 未运行的状态
|
|
|
- c.SSEvent("message", gin.H{
|
|
|
- "running": false,
|
|
|
- "message": "Nginx is not running",
|
|
|
- })
|
|
|
- // 刷新缓冲区,确保数据立即发送
|
|
|
- c.Writer.Flush()
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- // 获取性能数据
|
|
|
- stubStatusInfo, err := getStubStatusInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get stub_status info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- processInfo, err := getNginxProcessInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get process info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- configInfo, err := getNginxConfigInfo()
|
|
|
- if err != nil {
|
|
|
- logger.Warn("Failed to get config info:", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 组合所有信息
|
|
|
- info := NginxPerformanceInfo{
|
|
|
- Active: stubStatusInfo["active"],
|
|
|
- Accepts: stubStatusInfo["accepts"],
|
|
|
- Handled: stubStatusInfo["handled"],
|
|
|
- Requests: stubStatusInfo["requests"],
|
|
|
- Reading: stubStatusInfo["reading"],
|
|
|
- Writing: stubStatusInfo["writing"],
|
|
|
- Waiting: stubStatusInfo["waiting"],
|
|
|
- Workers: processInfo["workers"].(int),
|
|
|
- Master: processInfo["master"].(int),
|
|
|
- Cache: processInfo["cache"].(int),
|
|
|
- Other: processInfo["other"].(int),
|
|
|
- CPUUsage: processInfo["cpu_usage"].(float64),
|
|
|
- MemoryUsage: processInfo["memory_usage"].(float64),
|
|
|
- WorkerProcesses: configInfo["worker_processes"],
|
|
|
- WorkerConnections: configInfo["worker_connections"],
|
|
|
- }
|
|
|
+ response := nginx.GetPerformanceData()
|
|
|
|
|
|
// 发送 SSE 事件
|
|
|
- c.SSEvent("message", gin.H{
|
|
|
- "running": running,
|
|
|
- "info": info,
|
|
|
- })
|
|
|
+ c.SSEvent("message", response)
|
|
|
|
|
|
// 刷新缓冲区,确保数据立即发送
|
|
|
c.Writer.Flush()
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// 获取 stub_status 模块数据
|
|
|
-func getStubStatusInfo() (map[string]int, error) {
|
|
|
- result := map[string]int{
|
|
|
- "active": 0, "accepts": 0, "handled": 0, "requests": 0,
|
|
|
- "reading": 0, "writing": 0, "waiting": 0,
|
|
|
- }
|
|
|
-
|
|
|
- // 默认尝试访问 stub_status 页面
|
|
|
- statusURL := "http://localhost/stub_status"
|
|
|
-
|
|
|
- // 创建 HTTP 客户端
|
|
|
- client := &http.Client{
|
|
|
- Timeout: 5 * time.Second,
|
|
|
- }
|
|
|
-
|
|
|
- // 发送请求获取 stub_status 数据
|
|
|
- resp, err := client.Get(statusURL)
|
|
|
- if err != nil {
|
|
|
- return result, fmt.Errorf("failed to get stub status: %v", err)
|
|
|
- }
|
|
|
- defer resp.Body.Close()
|
|
|
-
|
|
|
- // 读取响应内容
|
|
|
- body, err := io.ReadAll(resp.Body)
|
|
|
- if err != nil {
|
|
|
- return result, fmt.Errorf("failed to read response body: %v", err)
|
|
|
- }
|
|
|
-
|
|
|
- // 解析响应内容
|
|
|
- statusContent := string(body)
|
|
|
+// CheckStubStatus 获取 Nginx stub_status 模块状态
|
|
|
+func CheckStubStatus(c *gin.Context) {
|
|
|
+ stubStatus := nginx.GetStubStatus()
|
|
|
|
|
|
- // 匹配活动连接数
|
|
|
- activeRe := regexp.MustCompile(`Active connections:\s+(\d+)`)
|
|
|
- if matches := activeRe.FindStringSubmatch(statusContent); len(matches) > 1 {
|
|
|
- result["active"], _ = strconv.Atoi(matches[1])
|
|
|
- }
|
|
|
-
|
|
|
- // 匹配请求统计信息
|
|
|
- serverRe := regexp.MustCompile(`(\d+)\s+(\d+)\s+(\d+)`)
|
|
|
- if matches := serverRe.FindStringSubmatch(statusContent); len(matches) > 3 {
|
|
|
- result["accepts"], _ = strconv.Atoi(matches[1])
|
|
|
- result["handled"], _ = strconv.Atoi(matches[2])
|
|
|
- result["requests"], _ = strconv.Atoi(matches[3])
|
|
|
- }
|
|
|
-
|
|
|
- // 匹配读写等待数
|
|
|
- connRe := regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`)
|
|
|
- if matches := connRe.FindStringSubmatch(statusContent); len(matches) > 3 {
|
|
|
- result["reading"], _ = strconv.Atoi(matches[1])
|
|
|
- result["writing"], _ = strconv.Atoi(matches[2])
|
|
|
- result["waiting"], _ = strconv.Atoi(matches[3])
|
|
|
- }
|
|
|
-
|
|
|
- return result, nil
|
|
|
+ c.JSON(http.StatusOK, stubStatus)
|
|
|
}
|
|
|
|
|
|
-// 获取 Nginx 进程信息
|
|
|
-func getNginxProcessInfo() (map[string]interface{}, error) {
|
|
|
- result := map[string]interface{}{
|
|
|
- "workers": 0,
|
|
|
- "master": 0,
|
|
|
- "cache": 0,
|
|
|
- "other": 0,
|
|
|
- "cpu_usage": 0.0,
|
|
|
- "memory_usage": 0.0,
|
|
|
+// ToggleStubStatus 启用或禁用 stub_status 模块
|
|
|
+func ToggleStubStatus(c *gin.Context) {
|
|
|
+ var json struct {
|
|
|
+ Enable bool `json:"enable"`
|
|
|
}
|
|
|
|
|
|
- // 查找所有 Nginx 进程
|
|
|
- processes, err := process.Processes()
|
|
|
- if err != nil {
|
|
|
- return result, fmt.Errorf("failed to get processes: %v", err)
|
|
|
- }
|
|
|
-
|
|
|
- totalMemory := 0.0
|
|
|
- workerCount := 0
|
|
|
- masterCount := 0
|
|
|
- cacheCount := 0
|
|
|
- otherCount := 0
|
|
|
- nginxProcesses := []*process.Process{}
|
|
|
-
|
|
|
- // 获取系统CPU核心数
|
|
|
- numCPU := runtime.NumCPU()
|
|
|
-
|
|
|
- // 获取Nginx主进程的PID
|
|
|
- var masterPID int32 = -1
|
|
|
- for _, p := range processes {
|
|
|
- name, err := p.Name()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- cmdline, err := p.Cmdline()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 检查是否是Nginx主进程
|
|
|
- if strings.Contains(strings.ToLower(name), "nginx") &&
|
|
|
- (strings.Contains(cmdline, "master process") ||
|
|
|
- !strings.Contains(cmdline, "worker process")) &&
|
|
|
- p.Pid > 0 {
|
|
|
- masterPID = p.Pid
|
|
|
- masterCount++
|
|
|
- nginxProcesses = append(nginxProcesses, p)
|
|
|
-
|
|
|
- // 获取内存使用情况 - 使用RSS代替
|
|
|
- // 注意:理想情况下我们应该使用USS(仅包含进程独占内存),但gopsutil不直接支持
|
|
|
- mem, err := p.MemoryInfo()
|
|
|
- if err == nil && mem != nil {
|
|
|
- // 转换为 MB
|
|
|
- memoryUsage := float64(mem.RSS) / 1024 / 1024
|
|
|
- totalMemory += memoryUsage
|
|
|
- }
|
|
|
-
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 遍历所有进程,区分工作进程和其他Nginx进程
|
|
|
- for _, p := range processes {
|
|
|
- if p.Pid == masterPID {
|
|
|
- continue // 已经计算过主进程
|
|
|
- }
|
|
|
-
|
|
|
- name, err := p.Name()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 只处理Nginx相关进程
|
|
|
- if !strings.Contains(strings.ToLower(name), "nginx") {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 添加到Nginx进程列表
|
|
|
- nginxProcesses = append(nginxProcesses, p)
|
|
|
-
|
|
|
- // 获取父进程PID
|
|
|
- ppid, err := p.Ppid()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- cmdline, err := p.Cmdline()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // 获取内存使用情况 - 使用RSS代替
|
|
|
- // 注意:理想情况下我们应该使用USS(仅包含进程独占内存),但gopsutil不直接支持
|
|
|
- mem, err := p.MemoryInfo()
|
|
|
- if err == nil && mem != nil {
|
|
|
- // 转换为 MB
|
|
|
- memoryUsage := float64(mem.RSS) / 1024 / 1024
|
|
|
- totalMemory += memoryUsage
|
|
|
- }
|
|
|
-
|
|
|
- // 区分工作进程、缓存进程和其他进程
|
|
|
- if ppid == masterPID || strings.Contains(cmdline, "worker process") {
|
|
|
- workerCount++
|
|
|
- } else if strings.Contains(cmdline, "cache") {
|
|
|
- cacheCount++
|
|
|
- } else {
|
|
|
- otherCount++
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 重新计算CPU使用率,更接近top命令的计算方式
|
|
|
- // 首先进行初始CPU时间测量
|
|
|
- times1 := make(map[int32]float64)
|
|
|
- for _, p := range nginxProcesses {
|
|
|
- times, err := p.Times()
|
|
|
- if err == nil {
|
|
|
- // CPU时间 = 用户时间 + 系统时间
|
|
|
- times1[p.Pid] = times.User + times.System
|
|
|
- }
|
|
|
+ if !cosy.BindAndValid(c, &json) {
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- // 等待一小段时间
|
|
|
- time.Sleep(100 * time.Millisecond)
|
|
|
-
|
|
|
- // 再次测量CPU时间
|
|
|
- totalCPUPercent := 0.0
|
|
|
- for _, p := range nginxProcesses {
|
|
|
- times, err := p.Times()
|
|
|
- if err != nil {
|
|
|
- continue
|
|
|
- }
|
|
|
+ stubStatus := nginx.GetStubStatus()
|
|
|
|
|
|
- // 计算CPU时间差
|
|
|
- currentTotal := times.User + times.System
|
|
|
- if previousTotal, ok := times1[p.Pid]; ok {
|
|
|
- // 计算这段时间内的CPU使用百分比(考虑多核)
|
|
|
- cpuDelta := currentTotal - previousTotal
|
|
|
- // 计算每秒CPU使用率(考虑采样时间)
|
|
|
- cpuPercent := (cpuDelta / 0.1) * 100.0 / float64(numCPU)
|
|
|
- totalCPUPercent += cpuPercent
|
|
|
- }
|
|
|
+ // 如果当前状态与期望状态相同,则无需操作
|
|
|
+ if stubStatus.Enabled == json.Enable {
|
|
|
+ c.JSON(http.StatusOK, stubStatus)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- // 四舍五入到整数,更符合top显示方式
|
|
|
- totalCPUPercent = math.Round(totalCPUPercent)
|
|
|
-
|
|
|
- // 四舍五入内存使用量到两位小数
|
|
|
- totalMemory = math.Round(totalMemory*100) / 100
|
|
|
-
|
|
|
- result["workers"] = workerCount
|
|
|
- result["master"] = masterCount
|
|
|
- result["cache"] = cacheCount
|
|
|
- result["other"] = otherCount
|
|
|
- result["cpu_usage"] = totalCPUPercent
|
|
|
- result["memory_usage"] = totalMemory
|
|
|
-
|
|
|
- return result, nil
|
|
|
-}
|
|
|
-
|
|
|
-// 获取 Nginx 配置信息
|
|
|
-func getNginxConfigInfo() (map[string]int, error) {
|
|
|
- result := map[string]int{
|
|
|
- "worker_processes": 1,
|
|
|
- "worker_connections": 1024,
|
|
|
+ var err error
|
|
|
+ if json.Enable {
|
|
|
+ err = nginx.EnableStubStatus()
|
|
|
+ } else {
|
|
|
+ err = nginx.DisableStubStatus()
|
|
|
}
|
|
|
|
|
|
- // 获取 worker_processes 配置
|
|
|
- cmd := exec.Command("nginx", "-T")
|
|
|
- output, err := cmd.CombinedOutput()
|
|
|
if err != nil {
|
|
|
- return result, fmt.Errorf("failed to get nginx config: %v", err)
|
|
|
+ cosy.ErrHandler(c, err)
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- // 解析 worker_processes
|
|
|
- wpRe := regexp.MustCompile(`worker_processes\s+(\d+|auto);`)
|
|
|
- if matches := wpRe.FindStringSubmatch(string(output)); len(matches) > 1 {
|
|
|
- if matches[1] == "auto" {
|
|
|
- result["worker_processes"] = runtime.NumCPU()
|
|
|
- } else {
|
|
|
- result["worker_processes"], _ = strconv.Atoi(matches[1])
|
|
|
- }
|
|
|
+ // 重新加载 Nginx 配置
|
|
|
+ reloadOutput := nginx.Reload()
|
|
|
+ if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") ||
|
|
|
+ strings.Contains(strings.ToLower(reloadOutput), "failed")) {
|
|
|
+ cosy.ErrHandler(c, errors.New("Reload Nginx failed"))
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- // 解析 worker_connections
|
|
|
- wcRe := regexp.MustCompile(`worker_connections\s+(\d+);`)
|
|
|
- if matches := wcRe.FindStringSubmatch(string(output)); len(matches) > 1 {
|
|
|
- result["worker_connections"], _ = strconv.Atoi(matches[1])
|
|
|
- }
|
|
|
+ // 检查操作后的状态
|
|
|
+ newStubStatus := nginx.GetStubStatus()
|
|
|
|
|
|
- return result, nil
|
|
|
+ c.JSON(http.StatusOK, newStubStatus)
|
|
|
}
|