模拟Golang应用内存泄漏的示例程序
package main
import (
"github.com/pyroscope-io/client/pyroscope"
"log"
"os"
"runtime"
"time"
)
type demo struct {
slice []byte
}
var demos = make([]*demo, 0, 1024)
var demosNormal = make([]*demo, 0, 1024)
var beforeTime time.Time
func main() {
runtime.SetBlockProfileRate(100)
runtime.SetMutexProfileFraction(100)
serverAddress := os.Getenv("PYROSCOPE_SERVER_ADDRESS")
if serverAddress == "" {
serverAddress = "http://localhost:4040"
}
appName := os.Getenv("PYROSCOPE_APPLICATION_NAME")
if appName == "" {
appName = "go-leak-app"
}
_, err := pyroscope.Start(pyroscope.Config{
ApplicationName: appName,
ServerAddress: serverAddress,
AuthToken: os.Getenv("PYROSCOPE_AUTH_TOKEN"),
Logger: pyroscope.StandardLogger,
Tags: map[string]string{"hostname": os.Getenv("HOSTNAME"), "environment": "test", "version": "1.0"},
})
if err != nil {
log.Fatalf("error starting pyroscope profiler: %v", err)
}
for {
memNormal()
memLeak()
time.Sleep(time.Second)
}
}
func memNormal() {
if len(demosNormal) > 600 {
time.Sleep(time.Nanosecond)
return
}
demosNormal = append(demosNormal, &demo{
slice: make([]byte, 4000),
})
}
func memLeak() {
now := time.Now()
if now.Sub(beforeTime).Minutes() > 60 {
demos = make([]*demo, 0, 1024)
println("clear cache data")
beforeTime = now
}
demos = append(demos, &demo{
slice: make([]byte, 4000),
})
}该段代码包含三部分:
main函数:程序的入口,每秒执行一次memNormal函数与memLeak函数。main函数中还包含性能监控接入代码。
memNormal函数:模拟正常程序运行。在程序运行的前10分钟内,每秒增加4000字节内存;在后续时间段内,不再额外增加内存。
memLeak函数:模拟内存泄漏情景。在程序运行的所有时间内,每秒增加4000字节内存。
模拟程序以1小时为1个周期,周期结束后将释放所有内存。
问题发现与排查过程
本排查过程基于上述示例程序的数据。
通过数据查询进行排查
您可以通过性能监控>数据查询页面,排查问题。更多信息,请参见数据查询。
service:golang_leak_applanguage:gotype:profile_memvalueTypes:inuse_space通过数据对比进行排查
您在数据查询页面,单击快速对比后,系统将同步您已完成的配置(元数据筛选条件、标签筛选条件、聚合策略、元数据时间范围、主时间范围)到数据对比页面。更多信息,请参见数据对比。
设置预设同比时间为过去30min。
观察并聚焦memNormal函数与memLeak函数的具体变化。


通过上述对比可知memNormal函数过去和现在的内存没有变化,但是占用总内存比例却减少了38%,同时memLeak函数的当前值比过去半小时的值涨了0.43 GB内存,占用总内存比例增加了37%。