Skip to content

Commit ae3ab40

Browse files
author
Sune Kirkeby
authored
Implement memory-profiler command-line flag. (#24)
Add `-memory-profiler` command-line flag to enable pprof memory-dumps at peak usage.
1 parent 0a4c035 commit ae3ab40

File tree

3 files changed

+121
-3
lines changed

3 files changed

+121
-3
lines changed

api/rankdb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"github.com/goadesign/goa"
2727
lru "github.com/hashicorp/golang-lru"
2828
shutdown "github.com/klauspost/shutdown2"
29-
"github.com/mattn/go-colorable"
29+
colorable "github.com/mattn/go-colorable"
3030
"github.com/sirupsen/logrus"
3131
)
3232

cmd/rankdb/main.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ import (
1818
"github.com/Vivino/rankdb/api"
1919
"github.com/Vivino/rankdb/log"
2020
"github.com/Vivino/rankdb/log/loggoa"
21+
"github.com/Vivino/rankdb/memprofiler"
2122
goalogrus "github.com/goadesign/goa/logging/logrus"
2223
shutdown "github.com/klauspost/shutdown2"
2324
"github.com/sirupsen/logrus"
2425
)
2526

2627
var (
27-
configPath = flag.String("config", "./conf/conf.toml", "Path for config to use.")
28-
enableDebug = flag.Bool("restart", false, "Enable rapid restart mode, press ' and <return>.")
28+
configPath = flag.String("config", "./conf/conf.toml", "Path for config to use.")
29+
enableDebug = flag.Bool("restart", false, "Enable rapid restart mode, press ' and <return>.")
30+
enableMemoryProfiler = flag.Bool("memory-profiler", false, "Enable memory profiler")
2931

3032
// SIGUSR2 signal if available.
3133
usr2Signal os.Signal
@@ -58,6 +60,10 @@ func main() {
5860
})
5961
})
6062

63+
if *enableMemoryProfiler {
64+
go memprofiler.Run(ctx, "/var/tmp/rankdb/memory-dumps")
65+
}
66+
6167
if *enableDebug {
6268
go func() {
6369
for {

memprofiler/memory_profiler.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package memprofiler
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
"runtime/pprof"
10+
"strings"
11+
"time"
12+
13+
"github.com/Vivino/rankdb/log"
14+
shutdown "github.com/klauspost/shutdown2"
15+
)
16+
17+
const dumpTimeLayout = "2006-01-02-150405"
18+
19+
// Run monitors the memory-usage of your application, and dumps profiles at peak usage.
20+
func Run(ctx context.Context, path string) {
21+
// Don't dump in the quiet period while starting up.
22+
const (
23+
quietPeriod = 3 * time.Minute
24+
checkEvery = 10 * time.Second
25+
minRise = 100 << 20
26+
)
27+
28+
stats := runtime.MemStats{}
29+
var high uint64
30+
if path == "" {
31+
return
32+
}
33+
ctx, cancel := shutdown.CancelCtx(log.WithValues(ctx, "module", "RAM Monitor"))
34+
defer cancel()
35+
36+
err := os.MkdirAll(path, os.ModePerm)
37+
if err != nil {
38+
log.Error(ctx, "Unable to create memory dump folder")
39+
}
40+
41+
t := time.NewTicker(checkEvery)
42+
defer t.Stop()
43+
go cleanOld(ctx, path)
44+
45+
quietEnds := time.Now().Add(quietPeriod)
46+
for {
47+
select {
48+
case <-t.C:
49+
runtime.ReadMemStats(&stats)
50+
if stats.Alloc <= high+(minRise) {
51+
continue
52+
}
53+
high = stats.Alloc
54+
if time.Now().Before(quietEnds) {
55+
log.Info(
56+
log.WithValues(ctx, "alloc_mb", stats.Alloc>>20),
57+
"In quiet period, skipping dump")
58+
continue
59+
}
60+
61+
timeStamp := time.Now().Format(dumpTimeLayout)
62+
fn := filepath.Join(path, fmt.Sprintf("%s-%dMB.bin", timeStamp, high>>20))
63+
64+
log.Info(
65+
log.WithValues(ctx, "filename", fn, "alloc_mb", stats.Alloc>>20),
66+
"Memory peak, dumping memory profile")
67+
f, err := os.Create(fn)
68+
if err != nil {
69+
log.Error(
70+
log.WithValues(ctx, "error", err),
71+
"could not create memory profile")
72+
}
73+
if err := pprof.WriteHeapProfile(f); err != nil {
74+
log.Error(
75+
log.WithValues(ctx, "error", err),
76+
"could not write memory profile")
77+
}
78+
case <-ctx.Done():
79+
return
80+
}
81+
}
82+
}
83+
84+
// cleanOld will remove all dumps more than 30 days old.
85+
func cleanOld(ctx context.Context, path string) {
86+
t := time.NewTicker(time.Hour)
87+
defer t.Stop()
88+
for {
89+
select {
90+
case <-t.C:
91+
timeStamp := time.Now().Add(-31 * 24 * time.Hour).Format(dumpTimeLayout)
92+
log.Info(ctx, "Deleting old dumps containing %q", timeStamp)
93+
94+
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
95+
if err == nil || info == nil {
96+
return err
97+
}
98+
if !info.IsDir() && strings.Contains(info.Name(), timeStamp) {
99+
return os.Remove(info.Name())
100+
}
101+
return nil
102+
})
103+
if err != nil {
104+
log.Error(
105+
log.WithValues(ctx, "error", err),
106+
"error deleting old dumps")
107+
}
108+
case <-ctx.Done():
109+
return
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)