feat(config,server,makefile): 新增 pprof 性能分析端点支持
- 新增 PprofConfig 配置结构,支持路径和 IP 访问控制 - 实现 PprofHandler 处理器,提供 CPU/heap/goroutine/block/mutex profile - Makefile 新增 build-perf、build-pgo、pgo-collect 目标 - 支持 PGO (Profile-Guided Optimization) 构建 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2688ed6a9c
commit
766e9255fa
3
.gitignore
vendored
3
.gitignore
vendored
@ -61,4 +61,5 @@ lolly.yaml
|
|||||||
config.yaml
|
config.yaml
|
||||||
lolly
|
lolly
|
||||||
coverage.html
|
coverage.html
|
||||||
html/
|
html/
|
||||||
|
default.pgo
|
||||||
64
Makefile
64
Makefile
@ -10,7 +10,7 @@ GO_VERSION := $(shell go version | awk '{print $$3}')
|
|||||||
BUILD_PLATFORM := $(shell go env GOOS)/$(shell go env GOARCH)
|
BUILD_PLATFORM := $(shell go env GOOS)/$(shell go env GOARCH)
|
||||||
BUILD_DIR := bin
|
BUILD_DIR := bin
|
||||||
|
|
||||||
# 生产构建标志
|
# 生产构建标志(体积优化)
|
||||||
LDFLAGS := -ldflags "-s -w \
|
LDFLAGS := -ldflags "-s -w \
|
||||||
-X 'rua.plus/lolly/internal/app.Version=$(VERSION)' \
|
-X 'rua.plus/lolly/internal/app.Version=$(VERSION)' \
|
||||||
-X 'rua.plus/lolly/internal/app.GitCommit=$(GIT_COMMIT)' \
|
-X 'rua.plus/lolly/internal/app.GitCommit=$(GIT_COMMIT)' \
|
||||||
@ -19,6 +19,10 @@ LDFLAGS := -ldflags "-s -w \
|
|||||||
-X 'rua.plus/lolly/internal/app.GoVersion=$(GO_VERSION)' \
|
-X 'rua.plus/lolly/internal/app.GoVersion=$(GO_VERSION)' \
|
||||||
-X 'rua.plus/lolly/internal/app.BuildPlatform=$(BUILD_PLATFORM)'"
|
-X 'rua.plus/lolly/internal/app.BuildPlatform=$(BUILD_PLATFORM)'"
|
||||||
|
|
||||||
|
# 运行时性能优化标志
|
||||||
|
PERF_GCFLAGS := -gcflags="-l=4"
|
||||||
|
PERF_ASMFLAGS := -asmflags="-l=4"
|
||||||
|
|
||||||
# Go 文件
|
# Go 文件
|
||||||
MAIN_PATH := main.go
|
MAIN_PATH := main.go
|
||||||
|
|
||||||
@ -37,13 +41,64 @@ build:
|
|||||||
@echo "Built: $(BUILD_DIR)/$(APP_NAME)"
|
@echo "Built: $(BUILD_DIR)/$(APP_NAME)"
|
||||||
@echo "Version: $(VERSION) | Commit: $(GIT_COMMIT) | Platform: $(BUILD_PLATFORM)"
|
@echo "Version: $(VERSION) | Commit: $(GIT_COMMIT) | Platform: $(BUILD_PLATFORM)"
|
||||||
|
|
||||||
# 生产构建(优化)
|
# 生产构建(体积优化)
|
||||||
build-prod:
|
build-prod:
|
||||||
@echo "Building $(APP_NAME) for production..."
|
@echo "Building $(APP_NAME) for production..."
|
||||||
@mkdir -p $(BUILD_DIR)
|
@mkdir -p $(BUILD_DIR)
|
||||||
go build $(LDFLAGS) -trimpath -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_PATH)
|
go build $(LDFLAGS) -trimpath -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_PATH)
|
||||||
@echo "Production build complete: $(BUILD_DIR)/$(APP_NAME)"
|
@echo "Production build complete: $(BUILD_DIR)/$(APP_NAME)"
|
||||||
|
|
||||||
|
# 生产构建(最大运行时性能)
|
||||||
|
build-perf:
|
||||||
|
@echo "Building $(APP_NAME) with max runtime performance..."
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
go build $(LDFLAGS) $(PERF_GCFLAGS) $(PERF_ASMFLAGS) -trimpath \
|
||||||
|
-o $(BUILD_DIR)/$(APP_NAME) $(MAIN_PATH)
|
||||||
|
@echo "Performance build complete: $(BUILD_DIR)/$(APP_NAME)"
|
||||||
|
|
||||||
|
# PGO 构建(需先收集 profile)
|
||||||
|
PGO_PROFILE ?= default.pgo
|
||||||
|
build-pgo:
|
||||||
|
@echo "Building $(APP_NAME) with PGO optimization..."
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
if [ -f $(PGO_PROFILE) ]; then \
|
||||||
|
go build $(LDFLAGS) $(PERF_GCFLAGS) $(PERF_ASMFLAGS) -trimpath \
|
||||||
|
-pgo=$(PGO_PROFILE) -o $(BUILD_DIR)/$(APP_NAME) $(MAIN_PATH); \
|
||||||
|
echo "PGO build complete using: $(PGO_PROFILE)"; \
|
||||||
|
else \
|
||||||
|
echo "PGO profile not found: $(PGO_PROFILE)"; \
|
||||||
|
echo "Run 'make pgo-collect' first to generate profile"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 收集 PGO profile(运行代表性 workload)
|
||||||
|
pgo-collect:
|
||||||
|
@echo "=== PGO Profile Collection Guide ==="
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 1: Enable pprof in your config file:"
|
||||||
|
@echo " monitoring:"
|
||||||
|
@echo " pprof:"
|
||||||
|
@echo " enabled: true"
|
||||||
|
@echo " path: /debug/pprof"
|
||||||
|
@echo " allow: [\"127.0.0.1\"]"
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 2: Build and run lolly with representative workload:"
|
||||||
|
@echo " make build && ./bin/lolly -c configs/lolly.yaml"
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 3: Collect CPU profile (run during peak load):"
|
||||||
|
@echo " curl http://localhost:<port>/debug/pprof/profile?seconds=30 > $(PGO_PROFILE)"
|
||||||
|
@echo ""
|
||||||
|
@echo "Step 4: Build with PGO optimization:"
|
||||||
|
@echo " make build-pgo"
|
||||||
|
@echo ""
|
||||||
|
@echo "Available pprof endpoints:"
|
||||||
|
@echo " /debug/pprof - Index page"
|
||||||
|
@echo " /debug/pprof/profile - CPU profile (add ?seconds=N)"
|
||||||
|
@echo " /debug/pprof/heap - Memory profile"
|
||||||
|
@echo " /debug/pprof/goroutine - Goroutine count"
|
||||||
|
@echo ""
|
||||||
|
@echo "Tip: Profile during real workload for best PGO results"
|
||||||
|
|
||||||
# 跨平台构建
|
# 跨平台构建
|
||||||
build-linux:
|
build-linux:
|
||||||
@echo "Building for Linux..."
|
@echo "Building for Linux..."
|
||||||
@ -225,7 +280,10 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Build:"
|
@echo "Build:"
|
||||||
@echo " make build - Build for current platform"
|
@echo " make build - Build for current platform"
|
||||||
@echo " make build-prod - Production build (optimized)"
|
@echo " make build-prod - Production build (size optimized)"
|
||||||
|
@echo " make build-perf - Production build (max runtime performance)"
|
||||||
|
@echo " make build-pgo - PGO build (needs profile, use PGO_PROFILE=path)"
|
||||||
|
@echo " make pgo-collect - Guide for collecting PGO profile"
|
||||||
@echo " make build-all - Build for all platforms"
|
@echo " make build-all - Build for all platforms"
|
||||||
@echo " make build-linux - Build for Linux amd64"
|
@echo " make build-linux - Build for Linux amd64"
|
||||||
@echo " make build-darwin - Build for macOS (amd64 + arm64)"
|
@echo " make build-darwin - Build for macOS (amd64 + arm64)"
|
||||||
|
|||||||
@ -1166,6 +1166,39 @@ type MonitoringConfig struct {
|
|||||||
// Status 状态端点配置
|
// Status 状态端点配置
|
||||||
// 服务健康状态检查端点
|
// 服务健康状态检查端点
|
||||||
Status StatusConfig `yaml:"status"`
|
Status StatusConfig `yaml:"status"`
|
||||||
|
|
||||||
|
// Pprof pprof 性能分析端点配置
|
||||||
|
// 用于收集 CPU、内存等性能数据,支持 PGO 优化
|
||||||
|
Pprof PprofConfig `yaml:"pprof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PprofConfig pprof 性能分析端点配置。
|
||||||
|
//
|
||||||
|
// 配置 pprof 端点用于收集运行时性能数据。
|
||||||
|
// 收集的 profile 可用于 PGO (Profile-Guided Optimization) 构建。
|
||||||
|
//
|
||||||
|
// 注意事项:
|
||||||
|
// - 生产环境仅在收集 profile 时启用,完成后关闭
|
||||||
|
// - 建议严格限制访问 IP,防止性能数据泄露
|
||||||
|
// - CPU profile 收集需要代表性 workload
|
||||||
|
//
|
||||||
|
// 使用示例:
|
||||||
|
//
|
||||||
|
// pprof:
|
||||||
|
// enabled: true
|
||||||
|
// path: "/debug/pprof"
|
||||||
|
// allow: ["127.0.0.1"]
|
||||||
|
type PprofConfig struct {
|
||||||
|
// Enabled 是否启用 pprof 端点
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
|
||||||
|
// Path 端点路径前缀
|
||||||
|
// 默认为 "/debug/pprof"
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
|
||||||
|
// Allow 允许访问的 IP 列表
|
||||||
|
// 可访问 pprof 端点的 IP 地址或 CIDR
|
||||||
|
Allow []string `yaml:"allow"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusConfig 状态监控端点配置。
|
// StatusConfig 状态监控端点配置。
|
||||||
|
|||||||
@ -137,6 +137,11 @@ func DefaultConfig() *Config {
|
|||||||
Path: "/_status",
|
Path: "/_status",
|
||||||
Allow: []string{"127.0.0.1"},
|
Allow: []string{"127.0.0.1"},
|
||||||
},
|
},
|
||||||
|
Pprof: PprofConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Path: "/debug/pprof",
|
||||||
|
Allow: []string{"127.0.0.1"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HTTP3: HTTP3Config{
|
HTTP3: HTTP3Config{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
@ -438,6 +443,13 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
|||||||
for _, ip := range cfg.Monitoring.Status.Allow {
|
for _, ip := range cfg.Monitoring.Status.Allow {
|
||||||
fmt.Fprintf(&buf, " - \"%s\"\n", ip)
|
fmt.Fprintf(&buf, " - \"%s\"\n", ip)
|
||||||
}
|
}
|
||||||
|
buf.WriteString(" pprof: # pprof 性能分析端点(用于 PGO 优化)\n")
|
||||||
|
fmt.Fprintf(&buf, " enabled: %v # 是否启用(生产环境仅在收集 profile 时启用)\n", cfg.Monitoring.Pprof.Enabled)
|
||||||
|
fmt.Fprintf(&buf, " path: \"%s\" # 端点路径前缀\n", cfg.Monitoring.Pprof.Path)
|
||||||
|
buf.WriteString(" allow: # 允许访问的 IP\n")
|
||||||
|
for _, ip := range cfg.Monitoring.Pprof.Allow {
|
||||||
|
fmt.Fprintf(&buf, " - \"%s\"\n", ip)
|
||||||
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
238
internal/server/pprof.go
Normal file
238
internal/server/pprof.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
// Package server 提供 pprof 性能分析端点支持。
|
||||||
|
//
|
||||||
|
// 该文件为 fasthttp 服务器提供 pprof 端点,用于收集:
|
||||||
|
// - CPU profile(用于 PGO 优化)
|
||||||
|
// - 内存分配 profile
|
||||||
|
// - Goroutine 分析
|
||||||
|
// - 阻塞分析
|
||||||
|
// - 锁竞争分析
|
||||||
|
//
|
||||||
|
// 注意事项:
|
||||||
|
// - 仅在配置启用时生效
|
||||||
|
// - 生产环境建议限制访问 IP
|
||||||
|
// - CPU profile 收集需要代表性 workload
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"rua.plus/lolly/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PprofHandler pprof 性能分析处理器。
|
||||||
|
//
|
||||||
|
// 封装 fasthttp 的 pprof handler,提供 IP 访问控制。
|
||||||
|
type PprofHandler struct {
|
||||||
|
// path 端点路径前缀
|
||||||
|
path string
|
||||||
|
|
||||||
|
// allowedIPs 允许访问的 IP 列表
|
||||||
|
allowedIPs []net.IP
|
||||||
|
|
||||||
|
// allowedNets 允许访问的 CIDR 网络
|
||||||
|
allowedNets []*net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPprofHandler 创建 pprof 处理器。
|
||||||
|
//
|
||||||
|
// 根据配置创建 pprof 端点处理器,包括 IP 访问控制。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - cfg: pprof 配置对象
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - *PprofHandler: 创建的处理器实例
|
||||||
|
// - error: 创建过程中遇到的错误,如 CIDR 解析失败
|
||||||
|
func NewPprofHandler(cfg *config.PprofConfig) (*PprofHandler, error) {
|
||||||
|
if !cfg.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := cfg.Path
|
||||||
|
if path == "" {
|
||||||
|
path = "/debug/pprof"
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &PprofHandler{path: path}
|
||||||
|
|
||||||
|
// 解析允许的 IP 列表
|
||||||
|
for _, ipStr := range cfg.Allow {
|
||||||
|
if ip := net.ParseIP(ipStr); ip != nil {
|
||||||
|
h.allowedIPs = append(h.allowedIPs, ip)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 尝试解析 CIDR
|
||||||
|
_, net, err := net.ParseCIDR(ipStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析 IP/CIDR 失败: %s: %w", ipStr, err)
|
||||||
|
}
|
||||||
|
h.allowedNets = append(h.allowedNets, net)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认只允许 localhost
|
||||||
|
if len(h.allowedIPs) == 0 && len(h.allowedNets) == 0 {
|
||||||
|
h.allowedIPs = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path 返回 pprof 端点路径。
|
||||||
|
func (h *PprofHandler) Path() string {
|
||||||
|
return h.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP 处理 pprof 请求。
|
||||||
|
//
|
||||||
|
// 根据 URL 路径选择对应的 profile 处理器,
|
||||||
|
// 并检查客户端 IP 是否在允许列表中。
|
||||||
|
func (h *PprofHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||||||
|
// IP 访问控制
|
||||||
|
if !h.isAllowed(ctx) {
|
||||||
|
ctx.SetStatusCode(fasthttp.StatusForbidden)
|
||||||
|
ctx.SetBodyString("Forbidden")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径分发
|
||||||
|
path := string(ctx.Path())
|
||||||
|
subPath := path[len(h.path):]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case subPath == "" || subPath == "/":
|
||||||
|
h.handleIndex(ctx)
|
||||||
|
case subPath == "/profile":
|
||||||
|
h.handleCPU(ctx)
|
||||||
|
case subPath == "/heap":
|
||||||
|
h.handleHeap(ctx)
|
||||||
|
case subPath == "/goroutine":
|
||||||
|
h.handleGoroutine(ctx)
|
||||||
|
case subPath == "/block":
|
||||||
|
h.handleBlock(ctx)
|
||||||
|
case subPath == "/mutex":
|
||||||
|
h.handleMutex(ctx)
|
||||||
|
default:
|
||||||
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
||||||
|
ctx.SetBodyString("Unknown profile: " + subPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAllowed 检查客户端 IP 是否允许访问。
|
||||||
|
func (h *PprofHandler) isAllowed(ctx *fasthttp.RequestCtx) bool {
|
||||||
|
if len(h.allowedIPs) == 0 && len(h.allowedNets) == 0 {
|
||||||
|
return true // 无限制
|
||||||
|
}
|
||||||
|
|
||||||
|
ipStr := ctx.RemoteIP().String()
|
||||||
|
clientIP := net.ParseIP(ipStr)
|
||||||
|
if clientIP == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查精确 IP
|
||||||
|
for _, ip := range h.allowedIPs {
|
||||||
|
if ip.Equal(clientIP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 CIDR 网络
|
||||||
|
for _, net := range h.allowedNets {
|
||||||
|
if net.Contains(clientIP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleIndex 处理索引页面。
|
||||||
|
func (h *PprofHandler) handleIndex(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetContentType("text/html; charset=utf-8")
|
||||||
|
html := `<html>
|
||||||
|
<head><title>Pprof Profiles</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Pprof Profiles</h1>
|
||||||
|
<table>
|
||||||
|
<tr><td><a href="%s/profile?seconds=30">CPU Profile (30s)</a></td><td>CPU profile for 30 seconds</td></tr>
|
||||||
|
<tr><td><a href="%s/heap">Heap Profile</a></td><td>Memory allocation profile</td></tr>
|
||||||
|
<tr><td><a href="%s/goroutine">Goroutine Profile</a></td><td>Goroutine stack traces</td></tr>
|
||||||
|
<tr><td><a href="%s/block">Block Profile</a></td><td>Blocking profile</td></tr>
|
||||||
|
<tr><td><a href="%s/mutex">Mutex Profile</a></td><td>Mutex contention profile</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>Usage: curl %s/profile?seconds=30 > cpu.pgo</p>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
ctx.SetBodyString(fmt.Sprintf(html, h.path, h.path, h.path, h.path, h.path, h.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCPU 处理 CPU profile 请求。
|
||||||
|
func (h *PprofHandler) handleCPU(ctx *fasthttp.RequestCtx) {
|
||||||
|
// 获取采集时长
|
||||||
|
seconds := 30
|
||||||
|
if secStr := ctx.QueryArgs().Peek("seconds"); secStr != nil {
|
||||||
|
if sec, err := strconv.Atoi(string(secStr)); err == nil && sec > 0 {
|
||||||
|
seconds = sec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetContentType("application/octet-stream")
|
||||||
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
|
// 启动 CPU profile
|
||||||
|
if err := startCPUProfile(wrapBufioWriter(w)); err != nil {
|
||||||
|
w.WriteString("Error starting CPU profile: " + err.Error())
|
||||||
|
w.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待采集完成
|
||||||
|
time.Sleep(time.Duration(seconds) * time.Second)
|
||||||
|
|
||||||
|
// 厉止 CPU profile
|
||||||
|
stopCPUProfile()
|
||||||
|
w.Flush()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleHeap 处理内存 profile 请求。
|
||||||
|
func (h *PprofHandler) handleHeap(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetContentType("application/octet-stream")
|
||||||
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
|
writeHeapProfile(wrapBufioWriter(w))
|
||||||
|
w.Flush()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGoroutine 处理 Goroutine profile 请求。
|
||||||
|
func (h *PprofHandler) handleGoroutine(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetContentType("application/octet-stream")
|
||||||
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
|
writeGoroutineProfile(wrapBufioWriter(w))
|
||||||
|
w.Flush()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBlock 处理阻塞 profile 请求。
|
||||||
|
func (h *PprofHandler) handleBlock(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetContentType("application/octet-stream")
|
||||||
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
|
writeBlockProfile(wrapBufioWriter(w))
|
||||||
|
w.Flush()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMutex 处理锁竞争 profile 请求。
|
||||||
|
func (h *PprofHandler) handleMutex(ctx *fasthttp.RequestCtx) {
|
||||||
|
ctx.SetContentType("application/octet-stream")
|
||||||
|
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||||
|
writeMutexProfile(wrapBufioWriter(w))
|
||||||
|
w.Flush()
|
||||||
|
})
|
||||||
|
}
|
||||||
104
internal/server/pprof_impl.go
Normal file
104
internal/server/pprof_impl.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Package server 提供 pprof 性能分析的底层实现。
|
||||||
|
//
|
||||||
|
// 该文件封装 runtime/pprof 的调用,为 fasthttp 提供流式输出支持。
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cpuProfileMu sync.Mutex
|
||||||
|
cpuProfileWriter io.Writer
|
||||||
|
cpuProfileActive bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// startCPUProfile 启动 CPU profile 采集。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - w: 输出 writer
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - error: 启动失败时的错误
|
||||||
|
func startCPUProfile(w io.Writer) error {
|
||||||
|
cpuProfileMu.Lock()
|
||||||
|
defer cpuProfileMu.Unlock()
|
||||||
|
|
||||||
|
if cpuProfileActive {
|
||||||
|
return nil // 已在采集,忽略
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pprof.StartCPUProfile(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuProfileWriter = w
|
||||||
|
cpuProfileActive = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopCPUProfile 厉止 CPU profile 采集。
|
||||||
|
func stopCPUProfile() {
|
||||||
|
cpuProfileMu.Lock()
|
||||||
|
defer cpuProfileMu.Unlock()
|
||||||
|
|
||||||
|
if cpuProfileActive {
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
cpuProfileActive = false
|
||||||
|
cpuProfileWriter = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHeapProfile 写入内存分配 profile。
|
||||||
|
func writeHeapProfile(w io.Writer) {
|
||||||
|
runtime.GC() // 先执行 GC,获取更准确的数据
|
||||||
|
pprof.WriteHeapProfile(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeGoroutineProfile 写入 Goroutine stack traces。
|
||||||
|
func writeGoroutineProfile(w io.Writer) {
|
||||||
|
p := pprof.Lookup("goroutine")
|
||||||
|
if p != nil {
|
||||||
|
p.WriteTo(w, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBlockProfile 写入阻塞 profile。
|
||||||
|
func writeBlockProfile(w io.Writer) {
|
||||||
|
p := pprof.Lookup("block")
|
||||||
|
if p != nil {
|
||||||
|
p.WriteTo(w, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeMutexProfile 写入锁竞争 profile。
|
||||||
|
func writeMutexProfile(w io.Writer) {
|
||||||
|
p := pprof.Lookup("mutex")
|
||||||
|
if p != nil {
|
||||||
|
p.WriteTo(w, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufioWriterAdapter 将 bufio.Writer 包装为 io.Writer,自动 Flush。
|
||||||
|
type bufioWriterAdapter struct {
|
||||||
|
w *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *bufioWriterAdapter) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = a.w.Write(p)
|
||||||
|
if err == nil {
|
||||||
|
a.w.Flush()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapBufioWriter 将 bufio.Writer 包装为 io.Writer。
|
||||||
|
func wrapBufioWriter(w *bufio.Writer) io.Writer {
|
||||||
|
return &bufioWriterAdapter{w: w}
|
||||||
|
}
|
||||||
@ -388,6 +388,17 @@ func (s *Server) startSingleMode() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册 pprof 性能分析端点(如果配置)
|
||||||
|
if s.config.Monitoring.Pprof.Enabled {
|
||||||
|
pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error().Msg("创建 pprof 处理器失败: " + err.Error())
|
||||||
|
} else {
|
||||||
|
router.GET(pprofHandler.Path(), pprofHandler.ServeHTTP)
|
||||||
|
router.GET(pprofHandler.Path()+"/{profile:*}", pprofHandler.ServeHTTP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 注册代理路由
|
// 注册代理路由
|
||||||
s.registerProxyRoutes(router, &s.config.Server)
|
s.registerProxyRoutes(router, &s.config.Server)
|
||||||
|
|
||||||
@ -491,6 +502,17 @@ func (s *Server) startVHostMode() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册 pprof 性能分析端点(如果配置)
|
||||||
|
if s.config.Monitoring.Pprof.Enabled {
|
||||||
|
pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error().Msg("创建 pprof 处理器失败: " + err.Error())
|
||||||
|
} else {
|
||||||
|
router.GET(pprofHandler.Path(), pprofHandler.ServeHTTP)
|
||||||
|
router.GET(pprofHandler.Path()+"/{profile:*}", pprofHandler.ServeHTTP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.registerProxyRoutes(router, &s.config.Server)
|
s.registerProxyRoutes(router, &s.config.Server)
|
||||||
|
|
||||||
// 静态文件
|
// 静态文件
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user