[lxc-devel] [lxd/master] WIP: clustering-related refactoring

freeekanayaka on Github lxc-bot at linuxcontainers.org
Fri Sep 15 14:53:53 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1584 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170915/4618871a/attachment.bin>
-------------- next part --------------
From 28f34daafcbd9c9b626107fa812fa96cf3913083 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at 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)


More information about the lxc-devel mailing list