mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-26 23:05:55 +08:00
294 lines
6.9 KiB
Go
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
|
|
}
|