-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b20fdbc
commit bce581e
Showing
10 changed files
with
278 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,9 @@ | ||
FROM golang:latest as build | ||
|
||
WORKDIR /src | ||
COPY go.mod /src/go.mod | ||
COPY main.go /src/main.go | ||
COPY . /src | ||
|
||
RUN go build -o /memory-leak | ||
RUN go build -o /leak | ||
|
||
FROM busybox:latest | ||
COPY --from=build /memory-leak /memory-leak | ||
CMD ["/memory-leak"] | ||
COPY --from=build /leak /leak |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package memory | ||
|
||
import ( | ||
"fmt" | ||
"github.com/strategicpause/memory-leak/metrics" | ||
"time" | ||
) | ||
|
||
type Params struct { | ||
MaxMemoryInBytes uint64 | ||
BlockSizeInBytes uint64 | ||
PauseTimeInSeconds time.Duration | ||
} | ||
|
||
func memoryLeak(params *Params) error { | ||
PrintParams(params) | ||
|
||
numEntries := int(params.MaxMemoryInBytes / params.BlockSizeInBytes) | ||
list := make([][]byte, numEntries) | ||
|
||
for i := 0; i < numEntries; i++ { | ||
list[i] = make([]byte, params.BlockSizeInBytes) | ||
for j := 0; j < int(params.BlockSizeInBytes); j++ { | ||
list[i][j] = 0 | ||
} | ||
metrics.PrintMemory() | ||
time.Sleep(params.PauseTimeInSeconds) | ||
} | ||
|
||
fmt.Println("Done") | ||
|
||
return nil | ||
} | ||
|
||
func PrintParams(params *Params) { | ||
fmt.Printf("MaxMemory = %v MiB\tBlockSize = %v MiB\tPauseTime = %v.\n", | ||
metrics.BToMiB(params.MaxMemoryInBytes), metrics.BToMiB(params.BlockSizeInBytes), params.PauseTimeInSeconds) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package memory | ||
|
||
import ( | ||
"errors" | ||
"github.com/inhies/go-bytesize" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
const MaxMemoryName = "max-memory" | ||
const BlockSizeName = "block-size" | ||
const PauseDurationName = "pause" | ||
|
||
func Register() cli.Command { | ||
return cli.Command{ | ||
Name: "memory", | ||
Usage: "Reproduces a memory leak.", | ||
Action: action, | ||
Flags: flags(), | ||
} | ||
} | ||
|
||
func flags() []cli.Flag { | ||
return []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: MaxMemoryName, | ||
Usage: "Specify the maximum amount of memory to acquire.", | ||
Value: "1GB", | ||
}, | ||
cli.StringFlag{ | ||
Name: BlockSizeName, | ||
Usage: "Specify the block size of memory which will be allocated at any given time.", | ||
Value: "10MB", | ||
}, | ||
cli.DurationFlag{ | ||
Name: PauseDurationName, | ||
Usage: "Time between allocations in seconds.", | ||
Value: 1, | ||
}, | ||
} | ||
} | ||
|
||
func action(ctx *cli.Context) error { | ||
maxMemory, err := bytesize.Parse(ctx.String(MaxMemoryName)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
blockSize, err := bytesize.Parse(ctx.String(BlockSizeName)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if blockSize > maxMemory { | ||
return errors.New("block-size must be less than or equal to max-memory") | ||
} | ||
|
||
params := &Params{ | ||
MaxMemoryInBytes: uint64(maxMemory), | ||
BlockSizeInBytes: uint64(blockSize), | ||
PauseTimeInSeconds: ctx.Duration(PauseDurationName), | ||
} | ||
|
||
return memoryLeak(params) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package tcp | ||
|
||
import ( | ||
"fmt" | ||
"github.com/strategicpause/memory-leak/metrics" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
func tcpLeak() error { | ||
go func() { | ||
fmt.Println("Listening on port", Port) | ||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
_, _ = w.Write([]byte(time.Now().String())) | ||
}) | ||
_ = http.ListenAndServe(Port, nil) | ||
}() | ||
for { | ||
go func() { | ||
req := Must(http.NewRequest("GET", "http://localhost:8080/", nil)) | ||
client := http.Client{} | ||
Must(client.Do(req)) | ||
metrics.PrintMemory() | ||
}() | ||
} | ||
} | ||
|
||
func Must[T any](obj T, err error) T { | ||
if err != nil { | ||
panic(err) | ||
} | ||
return obj | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package tcp | ||
|
||
import ( | ||
"github.com/urfave/cli" | ||
) | ||
|
||
const Port = ":8080" | ||
|
||
func Register() cli.Command { | ||
return cli.Command{ | ||
Name: "tcp", | ||
Usage: "Reproduces a TCP socket leak.", | ||
Action: action, | ||
Flags: flags(), | ||
} | ||
} | ||
|
||
func flags() []cli.Flag { | ||
return []cli.Flag{} | ||
} | ||
|
||
func action(_ *cli.Context) error { | ||
return tcpLeak() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
module memory-leak | ||
module github.com/strategicpause/memory-leak | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf | ||
github.com/urfave/cli v1.22.14 | ||
) | ||
|
||
require ( | ||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | ||
github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= | ||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= | ||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= | ||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= | ||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"runtime" | ||
"sync" | ||
"time" | ||
) | ||
|
||
const ( | ||
MiB = 1024 * 1024 | ||
Port = ":8080" | ||
TcpLeak = true | ||
"github.com/strategicpause/memory-leak/command/memory" | ||
"github.com/strategicpause/memory-leak/command/tcp" | ||
"github.com/urfave/cli" | ||
"log" | ||
"os" | ||
) | ||
|
||
func main() { | ||
if TcpLeak { | ||
tcpLeak() | ||
} else { | ||
memoryLeak() | ||
app := &cli.App{ | ||
Commands: RegisterCommands(), | ||
} | ||
|
||
} | ||
|
||
func tcpLeak() { | ||
wg := sync.WaitGroup{} | ||
wg.Add(1) | ||
go func() { | ||
fmt.Println("Listening on port", Port) | ||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte(time.Now().String())) | ||
}) | ||
http.ListenAndServe(Port, nil) | ||
wg.Done() | ||
}() | ||
for { | ||
go func() { | ||
req := Must(http.NewRequest("GET", "http://localhost:8080/", nil)) | ||
client := http.Client{} | ||
Must(client.Do(req)) | ||
PrintMemory() | ||
}() | ||
if err := app.Run(os.Args); err != nil { | ||
log.Fatal(err) | ||
} | ||
wg.Wait() | ||
} | ||
|
||
func Must[T any](obj T, err error) T { | ||
if err != nil { | ||
panic(err) | ||
func RegisterCommands() cli.Commands { | ||
return cli.Commands{ | ||
memory.Register(), | ||
tcp.Register(), | ||
} | ||
return obj | ||
} | ||
|
||
func memoryLeak() { | ||
// Allocate 10 GiB in 100 MiB increments | ||
list := make([][]byte, 100) | ||
for i := 0; i < 100; i++ { | ||
list[i] = make([]byte, 100*MiB) | ||
for j := 0; j < 100*MiB; j++ { | ||
list[i][j] = 0 | ||
} | ||
PrintMemory() | ||
time.Sleep(time.Second) | ||
} | ||
fmt.Println("Done") | ||
} | ||
|
||
func PrintMemory() { | ||
var m runtime.MemStats | ||
runtime.ReadMemStats(&m) | ||
fmt.Printf("TotalAlloc = %v MiB\tSys = %v MiB\n", bToMiB(m.TotalAlloc), bToMiB(m.Sys)) | ||
} | ||
|
||
func bToMiB(b uint64) uint64 { | ||
return b / 1024 / 1024 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package metrics | ||
|
||
import ( | ||
"fmt" | ||
"runtime" | ||
) | ||
|
||
func PrintMemory() { | ||
var m runtime.MemStats | ||
runtime.ReadMemStats(&m) | ||
|
||
var avgGCTime uint64 | ||
if m.NumGC > 0 { | ||
avgGCTime = NsToUs(m.PauseTotalNs / uint64(m.NumGC)) | ||
} | ||
|
||
fmt.Printf("TotalAlloc = %v MiB\tHeapAlloc = %v MiB\tSys = %v MiB\tNextGC = %v MiB\tPauseTotalUs = %v\tNumGC = %v\tAvgGCTime = %v\n", | ||
// Total number of bytes allocated for the heap. Does not decrease when objects are freed. | ||
BToMiB(m.TotalAlloc), | ||
// Total number of bytes allocated on the heap. This includes reachable objects and objects | ||
// not yet freed by the GC. This value should decrease when objects are cleaned by the GC. | ||
BToMiB(m.HeapAlloc), | ||
// The total number of bytes obtained from the OS. | ||
BToMiB(m.Sys), | ||
// The target heap size of the next GC cycle. The GC goal is to kep HeapAlloc <= NextGC. | ||
BToMiB(m.NextGC), | ||
// The cumulative nanoseconds in GC stop-the-world pauses since the program started. | ||
NsToUs(m.PauseTotalNs), | ||
// Number of completed GC cycles. | ||
m.NumGC, | ||
// Average time spent in GC across. Pretty hacky way to get some idea of how GC time is being spent during the leak. | ||
avgGCTime) | ||
} | ||
|
||
func BToMiB[T uint64 | uint32](b T) uint64 { | ||
return uint64(b) / (1024 * 1024) | ||
} | ||
|
||
func NsToUs(s uint64) uint64 { | ||
return s / 1000 | ||
} |