|
| 1 | +package pprof |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "path/filepath" |
| 7 | + "runtime" |
| 8 | + "runtime/pprof" |
| 9 | + "time" |
| 10 | + |
| 11 | + "github.com/actiontech/sqle/sqle/log" |
| 12 | +) |
| 13 | + |
| 14 | +const ( |
| 15 | + pprofDirName = "pprof" |
| 16 | +) |
| 17 | + |
| 18 | +// CollectHeapProfile 采集堆内存 profile 并保存到文件 |
| 19 | +func CollectHeapProfile(logPath string) error { |
| 20 | + return collectProfile("heap", logPath, func(f *os.File) error { |
| 21 | + runtime.GC() |
| 22 | + return pprof.WriteHeapProfile(f) |
| 23 | + }) |
| 24 | +} |
| 25 | + |
| 26 | +// CollectGoroutineProfile 采集 goroutine profile 并保存到文件 |
| 27 | +func CollectGoroutineProfile(logPath string) error { |
| 28 | + return collectProfile("goroutine", logPath, func(f *os.File) error { |
| 29 | + return pprof.Lookup("goroutine").WriteTo(f, 0) |
| 30 | + }) |
| 31 | +} |
| 32 | + |
| 33 | +// CollectAllocsProfile 采集内存分配 profile 并保存到文件 |
| 34 | +func CollectAllocsProfile(logPath string) error { |
| 35 | + return collectProfile("allocs", logPath, func(f *os.File) error { |
| 36 | + return pprof.Lookup("allocs").WriteTo(f, 0) |
| 37 | + }) |
| 38 | +} |
| 39 | + |
| 40 | +// CollectBlockProfile 采集阻塞 profile 并保存到文件 |
| 41 | +func CollectBlockProfile(logPath string) error { |
| 42 | + return collectProfile("block", logPath, func(f *os.File) error { |
| 43 | + return pprof.Lookup("block").WriteTo(f, 0) |
| 44 | + }) |
| 45 | +} |
| 46 | + |
| 47 | +// CollectMutexProfile 采集互斥锁 profile 并保存到文件 |
| 48 | +func CollectMutexProfile(logPath string) error { |
| 49 | + return collectProfile("mutex", logPath, func(f *os.File) error { |
| 50 | + return pprof.Lookup("mutex").WriteTo(f, 0) |
| 51 | + }) |
| 52 | +} |
| 53 | + |
| 54 | +// CollectCPUProfile 采集 CPU profile 并保存到文件(持续指定秒数) |
| 55 | +func CollectCPUProfile(logPath string, duration time.Duration) error { |
| 56 | + return collectProfile("cpu", logPath, func(f *os.File) error { |
| 57 | + if err := pprof.StartCPUProfile(f); err != nil { |
| 58 | + return err |
| 59 | + } |
| 60 | + time.Sleep(duration) |
| 61 | + pprof.StopCPUProfile() |
| 62 | + return nil |
| 63 | + }) |
| 64 | +} |
| 65 | + |
| 66 | +// collectProfile 通用的 profile 采集函数 |
| 67 | +func collectProfile(profileType, logPath string, writeFunc func(*os.File) error) error { |
| 68 | + pprofDir := filepath.Join(logPath, pprofDirName) |
| 69 | + if err := os.MkdirAll(pprofDir, 0755); err != nil { |
| 70 | + return fmt.Errorf("failed to create pprof directory: %v", err) |
| 71 | + } |
| 72 | + |
| 73 | + timestamp := time.Now().Format("20060102_150405") |
| 74 | + filename := fmt.Sprintf("%s_%s.prof", profileType, timestamp) |
| 75 | + filePath := filepath.Join(pprofDir, filename) |
| 76 | + |
| 77 | + f, err := os.Create(filePath) |
| 78 | + if err != nil { |
| 79 | + return fmt.Errorf("failed to create profile file: %v", err) |
| 80 | + } |
| 81 | + defer f.Close() |
| 82 | + |
| 83 | + if err := writeFunc(f); err != nil { |
| 84 | + return fmt.Errorf("failed to write profile: %v", err) |
| 85 | + } |
| 86 | + |
| 87 | + log.Logger().Infof("pprof %s profile saved to: %s", profileType, filePath) |
| 88 | + return nil |
| 89 | +} |
| 90 | + |
| 91 | +// CollectAllProfiles 采集所有类型的 profile(除了 CPU,因为 CPU 需要持续时间) |
| 92 | +func CollectAllProfiles(logPath string) error { |
| 93 | + profiles := []struct { |
| 94 | + name string |
| 95 | + fn func(string) error |
| 96 | + }{ |
| 97 | + {"heap", CollectHeapProfile}, |
| 98 | + {"goroutine", CollectGoroutineProfile}, |
| 99 | + {"allocs", CollectAllocsProfile}, |
| 100 | + {"block", CollectBlockProfile}, |
| 101 | + {"mutex", CollectMutexProfile}, |
| 102 | + } |
| 103 | + |
| 104 | + var lastErr error |
| 105 | + for _, p := range profiles { |
| 106 | + if err := p.fn(logPath); err != nil { |
| 107 | + log.Logger().Errorf("failed to collect %s profile: %v", p.name, err) |
| 108 | + lastErr = err |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + return lastErr |
| 113 | +} |
| 114 | + |
| 115 | +// StartPeriodicCollection 启动定期自动采集 pprof profile |
| 116 | +// interval: 采集间隔时间,如果为 0 则不启用定期采集 |
| 117 | +func StartPeriodicCollection(logPath string, interval time.Duration) { |
| 118 | + if interval <= 0 { |
| 119 | + return |
| 120 | + } |
| 121 | + |
| 122 | + go func() { |
| 123 | + ticker := time.NewTicker(interval) |
| 124 | + defer ticker.Stop() |
| 125 | + |
| 126 | + log.Logger().Infof("Starting periodic pprof collection, interval: %v", interval) |
| 127 | + |
| 128 | + for range ticker.C { |
| 129 | + log.Logger().Infof("Periodic pprof collection triggered") |
| 130 | + if err := CollectAllProfiles(logPath); err != nil { |
| 131 | + log.Logger().Errorf("Periodic pprof collection failed: %v", err) |
| 132 | + } |
| 133 | + } |
| 134 | + }() |
| 135 | +} |
0 commit comments