diff --git a/config.go b/config.go index b14f93a..fbb2f3f 100644 --- a/config.go +++ b/config.go @@ -34,6 +34,7 @@ type AppConfig struct { ArmCallbackUrls []string `yaml:"arm-callback-urls"` DisarmCallbackUrls []string `yaml:"disarm-callback-urls"` AlarmCallbackUrls []string `yaml:"alarm-callback-urls"` + WriteMemoryProfile bool `yaml:"write-memory-profile"` } func (m *SatelChangeType) UnmarshalYAML(unmarshal func(interface{}) error) error { @@ -93,6 +94,7 @@ func getCmdLineParams(config *AppConfig, logger *log.Logger) { allowedTypesRaw := flag.String("allowed-types", "", "Satel change types that are allowed. All other types will be discarded. By default all are allowed. Use \",\" to specify multiple types.") allowedIndexesRaw := flag.String("allowed-indexes", "", "Satel indexes (zones?) that are allowed. All other indexes will be discarded. By default all are allowed. Use \",\" to specify multiple indexes.") satelPoolInterval := flag.Duration("pool-interval", 5*time.Second, "How often should the SATEL device be pooled for changes? Default: 5 seconds.") + writeMemoryProfile := flag.Bool("write-memory-profile", false, "Whether application should dump its memory profile every 24 hours. Default: false") flag.Parse() if len(*satelApiAddr) == 0 || len(*satelApiPort) == 0 || len(*chatIdRaw) == 0 { @@ -131,6 +133,9 @@ func getCmdLineParams(config *AppConfig, logger *log.Logger) { } allowedIndexes = append(allowedIndexes, int(allowedIndex)) } + if writeMemoryProfile != nil { + config.WriteMemoryProfile = *writeMemoryProfile + } satelAddr := fmt.Sprintf("%s:%s", *satelApiAddr, *satelApiPort) @@ -146,6 +151,7 @@ func MakeConfig(logger *log.Logger) AppConfig { config.ArmCallbackUrls = []string{} config.DisarmCallbackUrls = []string{} config.AlarmCallbackUrls = []string{} + config.WriteMemoryProfile = false if len(os.Getenv("NOTIFY_URL_ARM")) != 0 { config.ArmCallbackUrls = append(config.ArmCallbackUrls, os.Getenv("NOTIFY_URL_ARM")) diff --git a/debug_utils.go b/debug_utils.go new file mode 100644 index 0000000..45034f8 --- /dev/null +++ b/debug_utils.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "log" + "os" + "runtime/pprof" + "sync" + "time" +) + +func dumpMemoryProfile(log *log.Logger) { + path := fmt.Sprintf("%s/hswro_alarm_bot_%s.mprof", os.Getenv("RUNTIME_DIRECTORY"), time.Now().Format(time.RFC3339)) + f, err := os.Create(path) + if err != nil { + log.Print("Error dumping memory profile to file: ", err) + } + pprof.WriteHeapProfile(f) + f.Close() + log.Print("Dumped memory profile to: ", path) +} + +func WriteMemoryProfilePeriodically(wg *sync.WaitGroup, log *log.Logger, close <-chan interface{}) { + go func() { + wg.Add(1) + defer wg.Done() + memoryProfileTicker := time.NewTicker(24 * time.Hour) + defer memoryProfileTicker.Stop() + select { + case <-memoryProfileTicker.C: + dumpMemoryProfile(log) + case <-close: + return + } + }() +} diff --git a/main.go b/main.go index 07724d4..90dff75 100644 --- a/main.go +++ b/main.go @@ -88,7 +88,10 @@ func main() { tgEvents <- GenericMessage{e.BasicEvents} } + closeDebugTools := make(chan interface{}) + WriteMemoryProfilePeriodically(&wg, log.New(os.Stderr, "DebugTools", log.Lmicroseconds), closeDebugTools) logger.Print("Closing...") + close(closeDebugTools) close(tgEvents) wg.Wait() if cleanShutdown.Load() {