mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 10:25:52 +02:00
feat: added status check and control functions for the Nginx stub_status module and optimized the performance data acquisition logic
This commit is contained in:
parent
32d7c74835
commit
2d0961f1a3
16 changed files with 720 additions and 543 deletions
48
internal/nginx/config_info.go
Normal file
48
internal/nginx/config_info.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type NginxConfigInfo struct {
|
||||
WorkerProcesses int `json:"worker_processes"`
|
||||
WorkerConnections int `json:"worker_connections"`
|
||||
}
|
||||
|
||||
// GetNginxWorkerConfigInfo Get Nginx config info of worker_processes and worker_connections
|
||||
func GetNginxWorkerConfigInfo() (*NginxConfigInfo, error) {
|
||||
result := &NginxConfigInfo{
|
||||
WorkerProcesses: 1,
|
||||
WorkerConnections: 1024,
|
||||
}
|
||||
|
||||
// Get worker_processes config
|
||||
cmd := exec.Command("nginx", "-T")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return result, errors.Wrap(err, "failed to get nginx config")
|
||||
}
|
||||
|
||||
// Parse worker_processes
|
||||
wpRe := regexp.MustCompile(`worker_processes\s+(\d+|auto);`)
|
||||
if matches := wpRe.FindStringSubmatch(string(output)); len(matches) > 1 {
|
||||
if matches[1] == "auto" {
|
||||
result.WorkerProcesses = runtime.NumCPU()
|
||||
} else {
|
||||
result.WorkerProcesses, _ = strconv.Atoi(matches[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse worker_connections
|
||||
wcRe := regexp.MustCompile(`worker_connections\s+(\d+);`)
|
||||
if matches := wcRe.FindStringSubmatch(string(output)); len(matches) > 1 {
|
||||
result.WorkerConnections, _ = strconv.Atoi(matches[1])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -115,3 +116,11 @@ func execCommand(name string, cmd ...string) (out string) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IsNginxRunning() bool {
|
||||
pidPath := GetPIDPath()
|
||||
if fileInfo, err := os.Stat(pidPath); err != nil || fileInfo.Size() == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
55
internal/nginx/performance.go
Normal file
55
internal/nginx/performance.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package nginx
|
||||
|
||||
import "github.com/uozi-tech/cosy/logger"
|
||||
|
||||
type NginxPerformanceInfo struct {
|
||||
StubStatusData
|
||||
NginxProcessInfo
|
||||
NginxConfigInfo
|
||||
}
|
||||
|
||||
type NginxPerformanceResponse struct {
|
||||
StubStatusEnabled bool `json:"stub_status_enabled"`
|
||||
Running bool `json:"running"`
|
||||
Info NginxPerformanceInfo `json:"info"`
|
||||
}
|
||||
|
||||
func GetPerformanceData() NginxPerformanceResponse {
|
||||
// Check if Nginx is running
|
||||
running := IsNginxRunning()
|
||||
if !running {
|
||||
return NginxPerformanceResponse{
|
||||
StubStatusEnabled: false,
|
||||
Running: false,
|
||||
Info: NginxPerformanceInfo{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get Nginx status information
|
||||
stubStatusEnabled, statusInfo, err := GetStubStatusData()
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get Nginx status:", err)
|
||||
}
|
||||
|
||||
// Get Nginx process information
|
||||
processInfo, err := GetNginxProcessInfo()
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get Nginx process info:", err)
|
||||
}
|
||||
|
||||
// Get Nginx config information
|
||||
configInfo, err := GetNginxWorkerConfigInfo()
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get Nginx config info:", err)
|
||||
}
|
||||
|
||||
return NginxPerformanceResponse{
|
||||
StubStatusEnabled: stubStatusEnabled,
|
||||
Running: running,
|
||||
Info: NginxPerformanceInfo{
|
||||
StubStatusData: *statusInfo,
|
||||
NginxProcessInfo: *processInfo,
|
||||
NginxConfigInfo: *configInfo,
|
||||
},
|
||||
}
|
||||
}
|
178
internal/nginx/process_info.go
Normal file
178
internal/nginx/process_info.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
)
|
||||
|
||||
type NginxProcessInfo struct {
|
||||
Workers int `json:"workers"`
|
||||
Master int `json:"master"`
|
||||
Cache int `json:"cache"`
|
||||
Other int `json:"other"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
}
|
||||
|
||||
// GetNginxProcessInfo Get Nginx process information
|
||||
func GetNginxProcessInfo() (*NginxProcessInfo, error) {
|
||||
result := &NginxProcessInfo{
|
||||
Workers: 0,
|
||||
Master: 0,
|
||||
Cache: 0,
|
||||
Other: 0,
|
||||
CPUUsage: 0.0,
|
||||
MemoryUsage: 0.0,
|
||||
}
|
||||
|
||||
// Find all Nginx processes
|
||||
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{}
|
||||
|
||||
// Get the number of system CPU cores
|
||||
numCPU := runtime.NumCPU()
|
||||
|
||||
// Get the PID of the Nginx master process
|
||||
var masterPID int32 = -1
|
||||
for _, p := range processes {
|
||||
name, err := p.Name()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cmdline, err := p.Cmdline()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it is the Nginx master process
|
||||
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)
|
||||
|
||||
// Get the memory usage
|
||||
mem, err := p.MemoryInfo()
|
||||
if err == nil && mem != nil {
|
||||
// Convert to MB
|
||||
memoryUsage := float64(mem.RSS) / 1024 / 1024
|
||||
totalMemory += memoryUsage
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all processes, distinguishing between worker processes and other Nginx processes
|
||||
for _, p := range processes {
|
||||
if p.Pid == masterPID {
|
||||
continue // Already calculated the master process
|
||||
}
|
||||
|
||||
name, err := p.Name()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only process Nginx related processes
|
||||
if !strings.Contains(strings.ToLower(name), "nginx") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add to the Nginx process list
|
||||
nginxProcesses = append(nginxProcesses, p)
|
||||
|
||||
// Get the parent process PID
|
||||
ppid, err := p.Ppid()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cmdline, err := p.Cmdline()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the memory usage
|
||||
mem, err := p.MemoryInfo()
|
||||
if err == nil && mem != nil {
|
||||
// Convert to MB
|
||||
memoryUsage := float64(mem.RSS) / 1024 / 1024
|
||||
totalMemory += memoryUsage
|
||||
}
|
||||
|
||||
// Distinguish between worker processes, cache processes, and other processes
|
||||
if ppid == masterPID || strings.Contains(cmdline, "worker process") {
|
||||
workerCount++
|
||||
} else if strings.Contains(cmdline, "cache") {
|
||||
cacheCount++
|
||||
} else {
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the CPU usage
|
||||
// First, measure the initial CPU time
|
||||
times1 := make(map[int32]float64)
|
||||
for _, p := range nginxProcesses {
|
||||
times, err := p.Times()
|
||||
if err == nil {
|
||||
// CPU time = user time + system time
|
||||
times1[p.Pid] = times.User + times.System
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a short period of time
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Measure the CPU time again
|
||||
totalCPUPercent := 0.0
|
||||
for _, p := range nginxProcesses {
|
||||
times, err := p.Times()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate the CPU time difference
|
||||
currentTotal := times.User + times.System
|
||||
if previousTotal, ok := times1[p.Pid]; ok {
|
||||
// Calculate the CPU usage percentage during this period (considering multiple cores)
|
||||
cpuDelta := currentTotal - previousTotal
|
||||
// Calculate the CPU usage per second (considering the sampling time)
|
||||
cpuPercent := (cpuDelta / 0.1) * 100.0 / float64(numCPU)
|
||||
totalCPUPercent += cpuPercent
|
||||
}
|
||||
}
|
||||
|
||||
// Round to the nearest integer, which is more consistent with the top display
|
||||
totalCPUPercent = math.Round(totalCPUPercent)
|
||||
|
||||
// Round the memory usage to two decimal places
|
||||
totalMemory = math.Round(totalMemory*100) / 100
|
||||
|
||||
result.Workers = workerCount
|
||||
result.Master = masterCount
|
||||
result.Cache = cacheCount
|
||||
result.Other = otherCount
|
||||
result.CPUUsage = totalCPUPercent
|
||||
result.MemoryUsage = totalMemory
|
||||
|
||||
return result, nil
|
||||
}
|
199
internal/nginx/stub_status.go
Normal file
199
internal/nginx/stub_status.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// StubStatusInfo Store the stub_status module status
|
||||
type StubStatusInfo struct {
|
||||
Enabled bool `json:"stub_status_enabled"` // stub_status module is enabled
|
||||
URL string `json:"stub_status_url"` // stub_status access address
|
||||
}
|
||||
|
||||
type StubStatusData 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"`
|
||||
}
|
||||
|
||||
const (
|
||||
StubStatusPort = 51828
|
||||
StubStatusPath = "/stub_status"
|
||||
StubStatusHost = "localhost"
|
||||
StubStatusProtocol = "http"
|
||||
StubStatusAllow = "127.0.0.1"
|
||||
StubStatusDeny = "all"
|
||||
StubStatusConfigName = "stub_status_nginx-ui.conf"
|
||||
)
|
||||
|
||||
// GetStubStatusData Get the stub_status module data
|
||||
func GetStubStatusData() (bool, *StubStatusData, error) {
|
||||
result := &StubStatusData{
|
||||
Active: 0,
|
||||
Accepts: 0,
|
||||
Handled: 0,
|
||||
Requests: 0,
|
||||
Reading: 0,
|
||||
Writing: 0,
|
||||
Waiting: 0,
|
||||
}
|
||||
|
||||
// Get the stub_status status information
|
||||
enabled, statusURL := IsStubStatusEnabled()
|
||||
if !enabled {
|
||||
return false, result, fmt.Errorf("stub_status is not enabled")
|
||||
}
|
||||
|
||||
// Create an HTTP client
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
// Send a request to get the stub_status data
|
||||
resp, err := client.Get(statusURL)
|
||||
if err != nil {
|
||||
return enabled, result, fmt.Errorf("failed to get stub status: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read the response content
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return enabled, result, fmt.Errorf("failed to read response body: %v", err)
|
||||
}
|
||||
|
||||
// Parse the response content
|
||||
statusContent := string(body)
|
||||
|
||||
// Match the active connection number
|
||||
activeRe := regexp.MustCompile(`Active connections:\s+(\d+)`)
|
||||
if matches := activeRe.FindStringSubmatch(statusContent); len(matches) > 1 {
|
||||
result.Active, _ = strconv.Atoi(matches[1])
|
||||
}
|
||||
|
||||
// Match the request statistics information
|
||||
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])
|
||||
}
|
||||
|
||||
// Match the read and write waiting numbers
|
||||
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 enabled, result, nil
|
||||
}
|
||||
|
||||
// GetStubStatus Get the stub_status module status
|
||||
func GetStubStatus() *StubStatusInfo {
|
||||
enabled, statusURL := IsStubStatusEnabled()
|
||||
return &StubStatusInfo{
|
||||
Enabled: enabled,
|
||||
URL: statusURL,
|
||||
}
|
||||
}
|
||||
|
||||
// IsStubStatusEnabled Check if the stub_status module is enabled and return the access address
|
||||
// Only check the stub_status_nginx-ui.conf configuration file
|
||||
func IsStubStatusEnabled() (bool, string) {
|
||||
stubStatusConfPath := GetConfPath("conf.d", StubStatusConfigName)
|
||||
if _, err := os.Stat(stubStatusConfPath); os.IsNotExist(err) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
ngxConfig, err := ParseNgxConfig(stubStatusConfPath)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Find the stub_status configuration
|
||||
for _, server := range ngxConfig.Servers {
|
||||
protocol := StubStatusProtocol
|
||||
host := StubStatusHost
|
||||
port := strconv.Itoa(StubStatusPort)
|
||||
|
||||
for _, location := range server.Locations {
|
||||
// Check if the location content contains stub_status
|
||||
if strings.Contains(location.Content, "stub_status") {
|
||||
stubStatusURL := fmt.Sprintf("%s://%s:%s%s", protocol, host, port, StubStatusPath)
|
||||
return true, stubStatusURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// EnableStubStatus Enable stub_status module
|
||||
func EnableStubStatus() error {
|
||||
enabled, _ := IsStubStatusEnabled()
|
||||
if enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CreateStubStatusConfig()
|
||||
}
|
||||
|
||||
// DisableStubStatus Disable stub_status module
|
||||
func DisableStubStatus() error {
|
||||
stubStatusConfPath := GetConfPath("conf.d", StubStatusConfigName)
|
||||
if _, err := os.Stat(stubStatusConfPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Remove(stubStatusConfPath)
|
||||
}
|
||||
|
||||
// CreateStubStatusConfig Create a new stub_status configuration file
|
||||
func CreateStubStatusConfig() error {
|
||||
httpConfPath := GetConfPath("conf.d", StubStatusConfigName)
|
||||
|
||||
stubStatusConfig := `
|
||||
# DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI
|
||||
# Nginx stub_status configuration for Nginx-UI
|
||||
# Modified at ` + time.Now().Format("2006-01-02 15:04:05") + `
|
||||
|
||||
server {
|
||||
listen 51828; # Use non-standard port to avoid conflicts
|
||||
server_name localhost;
|
||||
|
||||
# Status monitoring interface
|
||||
location /stub_status {
|
||||
stub_status;
|
||||
allow 127.0.0.1; # Only allow local access
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
`
|
||||
ngxConfig, err := ParseNgxConfigByContent(stubStatusConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse new nginx config")
|
||||
}
|
||||
|
||||
ngxConfig.FileName = httpConfPath
|
||||
configText, err := ngxConfig.BuildConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to build nginx config")
|
||||
}
|
||||
|
||||
return os.WriteFile(httpConfPath, []byte(configText), 0644)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue