The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3802
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === This branch contains refactoring work in preparation of the LXD clustering feature. High-level goals are: - Remove the use of global variables when they hinder testing. In particular it should be possible to create and start more than one Daemon object in-process, to allow Go-level testing of clustering-related behavior (this is overall easier than using bash, and makes it possible to cover subtle scenarios that would be otherwise hard to setup). - Increase testing coverage of code that will need to be changed to implement clustering. This reduces the risk of regressions of existing logic and provides faster feedback than bash integration tests when iterating on specific code areas. - Improve code organization by making use of sub-packages. These create strong boundaries that make dependencies more clear, contribute in reducing coupling between components and layers, help in reasoning about code, and provide faster testing feedback since only the code area that has been changed will need to be re-compiled (as opposed to the whole code base, as it mostly happens right now in master). It's not meant for immediate merging, since we want to wait for the current backporting efforts against stable-2.0 to settle down, to avoid creating additional churn.
From 28f34daafcbd9c9b626107fa812fa96cf3913083 Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <free.ekanay...@canonical.com> Date: Mon, 21 Aug 2017 22:36:08 +0000 Subject: [PATCH] Move lxd/debug.go to lxd/util/debug.go and improve its test coverage. The new MemProfiler function works exactly as the old one except that it's also possible to cancel the underlying goroutine (useful for cleaning up state between tests). --- lxd/debug.go | 30 ------------------------------ lxd/main_daemon.go | 4 +++- lxd/util/debug.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ lxd/util/debug_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ shared/logging/log.go | 24 ++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 31 deletions(-) delete mode 100644 lxd/debug.go create mode 100644 lxd/util/debug.go create mode 100644 lxd/util/debug_test.go diff --git a/lxd/debug.go b/lxd/debug.go deleted file mode 100644 index d255e346c..000000000 --- a/lxd/debug.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "os" - "os/signal" - "runtime/pprof" - "syscall" - - "github.com/lxc/lxd/shared/logger" -) - -func doMemDump(memProfile string) { - f, err := os.Create(memProfile) - if err != nil { - logger.Debugf("Error opening memory profile file '%s': %s", err) - return - } - pprof.WriteHeapProfile(f) - f.Close() -} - -func memProfiler(memProfile string) { - ch := make(chan os.Signal) - signal.Notify(ch, syscall.SIGUSR1) - for { - sig := <-ch - logger.Debugf("Received '%s signal', dumping memory.", sig) - doMemDump(memProfile) - } -} diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go index 9bd9f10e7..d8da996ef 100644 --- a/lxd/main_daemon.go +++ b/lxd/main_daemon.go @@ -9,6 +9,7 @@ import ( "syscall" "time" + "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/logger" ) @@ -30,7 +31,8 @@ func cmdDaemon(args *Args) error { } if args.MemProfile != "" { - go memProfiler(args.MemProfile) + shutdown := util.MemProfiler(args.MemProfile) + defer close(shutdown) } neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"} diff --git a/lxd/util/debug.go b/lxd/util/debug.go new file mode 100644 index 000000000..6ac091730 --- /dev/null +++ b/lxd/util/debug.go @@ -0,0 +1,45 @@ +package util + +import ( + "os" + "os/signal" + "runtime/pprof" + "syscall" + + "github.com/lxc/lxd/shared/logger" +) + +func doMemDump(memProfile string) { + f, err := os.Create(memProfile) + if err != nil { + logger.Debugf("Error opening memory profile file '%s': %s", err) + return + } + pprof.WriteHeapProfile(f) + f.Close() +} + +// MemProfiler spawns a goroutine that perpetually watches for SIGUSR1 signals +// and dumps the memory in the given file whenever the signal is received. It +// returns a channel that, once closed, will stop the goroutine. +func MemProfiler(memProfile string) chan struct{} { + signals := make(chan os.Signal, 1) + shutdown := make(chan struct{}) + signal.Notify(signals, syscall.SIGUSR1) + + go func() { + for { + select { + case sig := <-signals: + logger.Debugf("Received '%s signal', dumping memory.", sig) + doMemDump(memProfile) + case <-shutdown: + logger.Debugf("Shutdown memory profiler.") + signal.Stop(signals) + break + } + } + }() + + return shutdown +} diff --git a/lxd/util/debug_test.go b/lxd/util/debug_test.go new file mode 100644 index 000000000..992f4f2cd --- /dev/null +++ b/lxd/util/debug_test.go @@ -0,0 +1,46 @@ +package util_test + +import ( + "io/ioutil" + "os" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/assert" + log "gopkg.in/inconshreveable/log15.v2" + + "github.com/lxc/lxd/lxd/util" + "github.com/lxc/lxd/shared/logging" +) + +func TestMemProfile(t *testing.T) { + // Create a logger that will block when emitting records. + records := make(chan *log.Record) + logger := log.New() + logger.SetHandler(log.ChannelHandler(records)) + defer logging.SetLogger(logger)() + + // Create a temporary file. + file, err := ioutil.TempFile("", "lxd-util-") + assert.NoError(t, err) + file.Close() + defer os.Remove(file.Name()) + + // Spawn the profiler and check that it dumps the memmory to the given + // file and stops when we close the shutdown channel. + shutdown := util.MemProfiler(file.Name()) + syscall.Kill(os.Getpid(), syscall.SIGUSR1) + record := logging.WaitRecord(records, time.Second) + assert.NotNil(t, record) + assert.Equal(t, "Received 'user defined signal 1 signal', dumping memory.", record.Msg) + + close(shutdown) + record = logging.WaitRecord(records, time.Second) + assert.NotNil(t, record) + assert.Equal(t, "Shutdown memory profiler.", record.Msg) + + // The memory dump actually exists on disk. + _, err = os.Stat(file.Name()) + assert.NoError(t, err) +} diff --git a/shared/logging/log.go b/shared/logging/log.go index 86023412e..b35346a86 100644 --- a/shared/logging/log.go +++ b/shared/logging/log.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "time" log "gopkg.in/inconshreveable/log15.v2" "gopkg.in/inconshreveable/log15.v2/term" @@ -81,6 +82,29 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa return Log, nil } +// SetLogger installs the given logger as global logger. It returns a function +// that can be used to restore whatever logger was installed beforehand. +func SetLogger(newLogger logger.Logger) func() { + origLog := logger.Log + logger.Log = newLogger + return func() { + logger.Log = origLog + } +} + +// WaitRecord blocks until a log.Record is received on the given channel. It +// returns the emitted record, or nil if no record was received within the +// given timeout. Useful in conjunction with log.ChannelHandler, for +// asynchronous testing. +func WaitRecord(ch chan *log.Record, timeout time.Duration) *log.Record { + select { + case record := <-ch: + return record + case <-time.After(timeout): + return nil + } +} + // AddContext will return a copy of the logger with extra context added func AddContext(logger logger.Logger, ctx log.Ctx) logger.Logger { log15logger, ok := logger.(log.Logger)
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel