Files
monibuca/plugin/debug/index.go
2024-12-16 20:06:39 +08:00

294 lines
6.9 KiB
Go

package plugin_debug
import (
"context"
"fmt"
"io"
"net/http"
"net/http/pprof"
"os"
"runtime"
runtimePPROF "runtime/pprof"
"sort"
"strings"
"time"
myproc "github.com/cloudwego/goref/pkg/proc"
"github.com/go-delve/delve/pkg/config"
"github.com/go-delve/delve/service/debugger"
"google.golang.org/protobuf/types/known/emptypb"
"m7s.live/v5"
"m7s.live/v5/plugin/debug/pb"
debug "m7s.live/v5/plugin/debug/pkg"
"m7s.live/v5/plugin/debug/pkg/profile"
)
var _ = m7s.InstallPlugin[DebugPlugin](&pb.Api_ServiceDesc, pb.RegisterApiHandler)
var conf, _ = config.LoadConfig()
type DebugPlugin struct {
pb.UnimplementedApiServer
m7s.Plugin
ProfileDuration time.Duration `default:"10s" desc:"profile持续时间"`
Profile string `desc:"采集profile存储文件"`
ChartPeriod time.Duration `default:"1s" desc:"图表更新周期"`
Grfout string `default:"grf.out" desc:"grf输出文件"`
}
type WriteToFile struct {
header http.Header
io.Writer
}
func (w *WriteToFile) Header() http.Header {
// return w.w.Header()
return w.header
}
// func (w *WriteToFile) Write(p []byte) (int, error) {
// // w.w.Write(p)
// return w.Writer.Write(p)
// }
func (w *WriteToFile) WriteHeader(statusCode int) {
// w.w.WriteHeader(statusCode)
}
func (p *DebugPlugin) OnInit() error {
if p.Profile != "" {
go func() {
file, err := os.Create(p.Profile)
if err != nil {
return
}
defer file.Close()
p.Info("cpu profile start")
err = runtimePPROF.StartCPUProfile(file)
time.Sleep(p.ProfileDuration)
runtimePPROF.StopCPUProfile()
p.Info("cpu profile done")
}()
}
return nil
}
func (p *DebugPlugin) Pprof_Trace(w http.ResponseWriter, r *http.Request) {
r.URL.Path = "/debug" + r.URL.Path
pprof.Trace(w, r)
}
func (p *DebugPlugin) Pprof_profile(w http.ResponseWriter, r *http.Request) {
r.URL.Path = "/debug" + r.URL.Path
pprof.Profile(w, r)
}
func (p *DebugPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/pprof" {
http.Redirect(w, r, "/debug/pprof/", http.StatusFound)
return
}
r.URL.Path = "/debug" + r.URL.Path
pprof.Index(w, r)
}
func (p *DebugPlugin) Charts_(w http.ResponseWriter, r *http.Request) {
r.URL.Path = "/static" + strings.TrimPrefix(r.URL.Path, "/charts")
staticFSHandler.ServeHTTP(w, r)
}
func (p *DebugPlugin) Charts_data(w http.ResponseWriter, r *http.Request) {
dataHandler(w, r)
}
func (p *DebugPlugin) Charts_datafeed(w http.ResponseWriter, r *http.Request) {
s.dataFeedHandler(w, r)
}
func (p *DebugPlugin) Grf(w http.ResponseWriter, r *http.Request) {
dConf := debugger.Config{
AttachPid: os.Getpid(),
Backend: "default",
CoreFile: "",
DebugInfoDirectories: conf.DebugInfoDirectories,
AttachWaitFor: "",
AttachWaitForInterval: 1,
AttachWaitForDuration: 0,
}
dbg, err := debugger.New(&dConf, nil)
defer dbg.Detach(false)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err = myproc.ObjectReference(dbg.Target(), p.Grfout); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Write([]byte("ok"))
}
func (p *DebugPlugin) GetHeap(ctx context.Context, empty *emptypb.Empty) (*pb.HeapResponse, error) {
// 创建临时文件用于存储堆信息
f, err := os.CreateTemp("", "heap")
if err != nil {
return nil, err
}
defer os.Remove(f.Name())
defer f.Close()
// 获取堆信息
runtime.GC()
if err := runtimePPROF.WriteHeapProfile(f); err != nil {
return nil, err
}
// 读取堆信息
f.Seek(0, 0)
prof, err := profile.Parse(f)
if err != nil {
return nil, err
}
// 准备响应数据
resp := &pb.HeapResponse{
Data: &pb.HeapData{
Stats: &pb.HeapStats{},
Objects: make([]*pb.HeapObject, 0),
Edges: make([]*pb.HeapEdge, 0),
},
}
// 创建类型映射用于聚合统计
typeMap := make(map[string]*pb.HeapObject)
var totalSize int64
// 处理每个样本
for _, sample := range prof.Sample {
size := sample.Value[1] // 内存大小
if size == 0 {
continue
}
// 获取分配类型信息
var typeName string
if len(sample.Location) > 0 && len(sample.Location[0].Line) > 0 {
if fn := sample.Location[0].Line[0].Function; fn != nil {
typeName = fn.Name
}
}
// 创建或更新堆对象
obj, exists := typeMap[typeName]
if !exists {
obj = &pb.HeapObject{
Type: typeName,
Address: fmt.Sprintf("%p", sample),
Refs: make([]string, 0),
}
typeMap[typeName] = obj
resp.Data.Objects = append(resp.Data.Objects, obj)
}
obj.Count++
obj.Size += size
totalSize += size
// 构建引用关系
for i := 1; i < len(sample.Location); i++ {
loc := sample.Location[i]
if len(loc.Line) == 0 || loc.Line[0].Function == nil {
continue
}
callerName := loc.Line[0].Function.Name
// 跳过系统函数
if callerName == "" || strings.HasPrefix(callerName, "runtime.") {
continue
}
// 添加边
edge := &pb.HeapEdge{
From: callerName,
To: typeName,
FieldName: callerName,
}
resp.Data.Edges = append(resp.Data.Edges, edge)
// 将调用者添加到引用列表
if !contains(obj.Refs, callerName) {
obj.Refs = append(obj.Refs, callerName)
}
}
}
// 计算百分比
for _, obj := range resp.Data.Objects {
if totalSize > 0 {
obj.SizePerc = float64(obj.Size) / float64(totalSize) * 100
}
}
// 按大小排序
sort.Slice(resp.Data.Objects, func(i, j int) bool {
return resp.Data.Objects[i].Size > resp.Data.Objects[j].Size
})
// 获取运行时内存统计
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
// 填充内存统计信息
resp.Data.Stats.Alloc = ms.Alloc
resp.Data.Stats.TotalAlloc = ms.TotalAlloc
resp.Data.Stats.Sys = ms.Sys
resp.Data.Stats.NumGC = ms.NumGC
resp.Data.Stats.HeapAlloc = ms.HeapAlloc
resp.Data.Stats.HeapSys = ms.HeapSys
resp.Data.Stats.HeapIdle = ms.HeapIdle
resp.Data.Stats.HeapInuse = ms.HeapInuse
resp.Data.Stats.HeapReleased = ms.HeapReleased
resp.Data.Stats.HeapObjects = ms.HeapObjects
resp.Data.Stats.GcCPUFraction = ms.GCCPUFraction
return resp, nil
}
// 辅助函数:检查字符串切片是否包含特定字符串
func contains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
func (p *DebugPlugin) GetHeapGraph(ctx context.Context, empty *emptypb.Empty) (*pb.HeapGraphResponse, error) {
// 创建临时文件用于存储堆信息
f, err := os.CreateTemp("", "heap")
if err != nil {
return nil, err
}
defer os.Remove(f.Name())
defer f.Close()
// 获取堆信息
runtime.GC()
if err := runtimePPROF.WriteHeapProfile(f); err != nil {
return nil, err
}
// 读取堆信息
f.Seek(0, 0)
profile, err := profile.Parse(f)
if err != nil {
return nil, err
}
// Generate dot graph.
dot, err := debug.GetDotGraph(profile)
if err != nil {
return nil, err
}
return &pb.HeapGraphResponse{
Data: dot,
}, nil
}