Formalize Memory Monitoring

by extracting the interval and threshold into configuration options.

Related to f670b07e.
This commit is contained in:
Maximilian Paß
2023-10-06 14:50:14 +02:00
parent ca42369057
commit 14b012486d
3 changed files with 31 additions and 16 deletions

View File

@ -85,8 +85,8 @@ func shutdownSentry() {
} }
func initProfiling(options config.Profiling) (cancel func()) { func initProfiling(options config.Profiling) (cancel func()) {
if options.Enabled { if options.CPUEnabled {
profile, err := os.Create(options.File) profile, err := os.Create(options.CPUFile)
if err != nil { if err != nil {
log.WithError(err).Error("Error while opening the profile file") log.WithError(err).Error("Error while opening the profile file")
} }
@ -97,7 +97,7 @@ func initProfiling(options config.Profiling) (cancel func()) {
} }
cancel = func() { cancel = func() {
if options.Enabled { if options.CPUEnabled {
log.Debug("Stopping CPU profiler") log.Debug("Stopping CPU profiler")
pprof.StopCPUProfile() pprof.StopCPUProfile()
if err := profile.Close(); err != nil { if err := profile.Close(); err != nil {
@ -112,18 +112,20 @@ func initProfiling(options config.Profiling) (cancel func()) {
} }
// watchMemoryAndAlert monitors the memory usage of Poseidon and sends an alert if it exceeds a threshold. // watchMemoryAndAlert monitors the memory usage of Poseidon and sends an alert if it exceeds a threshold.
func watchMemoryAndAlert() { func watchMemoryAndAlert(options config.Profiling) {
// We assume that Poseidon usually takes about 50-300 MB of memory. Therefore, we specify the threshold of 1 GB. if options.MemoryInterval == 0 {
// Improve: Make this value dynamic or relative. return
const threshold = 1 * 1000 * 1000 * 1000 }
const interval = 5 * time.Second
var exceeded bool
for { for {
var stats runtime.MemStats var stats runtime.MemStats
runtime.ReadMemStats(&stats) runtime.ReadMemStats(&stats)
log.WithField("heap", stats.HeapAlloc).Trace("Current Memory Usage") log.WithField("heap", stats.HeapAlloc).Trace("Current Memory Usage")
if stats.HeapAlloc >= threshold { const megabytesToBytes = 1000 * 1000
if !exceeded && stats.HeapAlloc >= uint64(options.MemoryThreshold)*megabytesToBytes {
exceeded = true
log.WithField("heap", stats.HeapAlloc).Warn("Memory Threshold exceeded") log.WithField("heap", stats.HeapAlloc).Warn("Memory Threshold exceeded")
err := pprof.Lookup("heap").WriteTo(os.Stderr, 1) err := pprof.Lookup("heap").WriteTo(os.Stderr, 1)
@ -135,10 +137,13 @@ func watchMemoryAndAlert() {
if err != nil { if err != nil {
log.WithError(err).Warn("Failed to log the goroutines") log.WithError(err).Warn("Failed to log the goroutines")
} }
} else if exceeded {
exceeded = false
log.WithField("heap", stats.HeapAlloc).Info("Memory Threshold no longer exceeded")
} }
select { select {
case <-time.After(interval): case <-time.After(time.Duration(options.MemoryInterval) * time.Millisecond):
continue continue
case <-context.Background().Done(): case <-context.Background().Done():
return return
@ -270,13 +275,13 @@ func main() {
log.WithError(err).Warn("Could not initialize configuration") log.WithError(err).Warn("Could not initialize configuration")
} }
logging.InitializeLogging(config.Config.Logger.Level, config.Config.Logger.Formatter) logging.InitializeLogging(config.Config.Logger.Level, config.Config.Logger.Formatter)
initSentry(&config.Config.Sentry, config.Config.Profiling.Enabled) initSentry(&config.Config.Sentry, config.Config.Profiling.CPUEnabled)
cancelInflux := monitoring.InitializeInfluxDB(&config.Config.InfluxDB) cancelInflux := monitoring.InitializeInfluxDB(&config.Config.InfluxDB)
defer cancelInflux() defer cancelInflux()
stopProfiling := initProfiling(config.Config.Profiling) stopProfiling := initProfiling(config.Config.Profiling)
go watchMemoryAndAlert() go watchMemoryAndAlert(config.Config.Profiling)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
server := initServer(ctx) server := initServer(ctx)

View File

@ -64,10 +64,15 @@ logger:
# Configuration of the embedded profiler # Configuration of the embedded profiler
profiling: profiling:
# Enables the runtime profiler # Enables the runtime profiler
enabled: false cpuenabled: false
# The file to which the profile is written to. # The file to which the profile is written to.
# The default location `cmd/poseidon/default.pgo` will be picked up during the build process to create a profile-guided build. # The default location `cmd/poseidon/default.pgo` will be picked up during the build process to create a profile-guided build.
file: cmd/poseidon/default.pgo cpufile: cmd/poseidon/default.pgo
# If set, a memory watchdog will be started that monitors the memory usage of Poseidon and alerts if the threshold is exceeded.
# The value defines the interval in milliseconds in which the memory usage is checked.
memoryinterval: 30_000
# The Threshold in MB of memory usage at which Poseidon will start alerting.
memorythreshold: 1_000
# Configuration of the sentry logging # Configuration of the sentry logging
sentry: sentry:

View File

@ -55,6 +55,9 @@ var (
Level: "INFO", Level: "INFO",
Formatter: dto.FormatterText, Formatter: dto.FormatterText,
}, },
Profiling: Profiling{
MemoryThreshold: 1_000,
},
Sentry: sentry.ClientOptions{ Sentry: sentry.ClientOptions{
AttachStacktrace: true, AttachStacktrace: true,
}, },
@ -130,8 +133,10 @@ type Logger struct {
// Profiling configures the usage of a runtime profiler to create optimized binaries. // Profiling configures the usage of a runtime profiler to create optimized binaries.
type Profiling struct { type Profiling struct {
Enabled bool CPUEnabled bool
File string CPUFile string
MemoryInterval uint
MemoryThreshold uint
} }
// InfluxDB configures the usage of an Influx db monitoring. // InfluxDB configures the usage of an Influx db monitoring.