[lxc-devel] [lxd/master] Implement built-in pprof server

stgraber on Github lxc-bot at linuxcontainers.org
Tue Jul 10 16:50:04 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180710/c02802dc/attachment.bin>
-------------- next part --------------
From 2dc38fae2e4ec597c8562e14a31b2b04f5b12154 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Jul 2018 10:34:50 -0400
Subject: [PATCH 1/2] lxd/storage: Fix double quoting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_ceph.go | 2 +-
 lxd/storage_lvm.go  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index e5dfdd15f..fda817a01 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2420,7 +2420,7 @@ func (s *storageCeph) StorageEntitySetQuota(volumeType int, size int64, data int
 		ctName := c.Name()
 		if c.IsRunning() {
 			msg := fmt.Sprintf(`Cannot resize RBD storage volume `+
-				`for container \"%s\" when it is running`,
+				`for container "%s" when it is running`,
 				ctName)
 			logger.Errorf(msg)
 			return fmt.Errorf(msg)
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 18ea22f44..6d6f51180 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -2152,7 +2152,7 @@ func (s *storageLvm) StorageEntitySetQuota(volumeType int, size int64, data inte
 		ctName := c.Name()
 		if c.IsRunning() {
 			msg := fmt.Sprintf(`Cannot resize LVM storage volume `+
-				`for container \"%s\" when it is running`,
+				`for container "%s" when it is running`,
 				ctName)
 			logger.Errorf(msg)
 			return fmt.Errorf(msg)

From 943bde0e3750f80ec79fa4434c9eb4d68d9e8fd6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Jul 2018 12:45:11 -0400
Subject: [PATCH 2/2] lxd: Add built-in pprof server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This commit adds a new core.debug_address option (node specific) which
controls a built-in HTTP server to be used with the "pprof" tool or
directly from a web browser (/debug/pprof is the main endpoint).

This is a much more flexible option that can be live-enabled by our
users when running into a problem and replaces the old cpu-profile,
memory-profile and print-goroutines command line options.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md          |   6 +++
 doc/server.md                  |   3 +-
 lxd/api_1.0.go                 |  14 ++++--
 lxd/daemon.go                  |   8 ++-
 lxd/debug/cpu.go               |  30 ------------
 lxd/debug/cpu_test.go          |  33 -------------
 lxd/debug/goroutines.go        |  34 -------------
 lxd/debug/memory.go            |  58 ----------------------
 lxd/debug/memory_test.go       |  54 --------------------
 lxd/debug/start.go             |  56 ---------------------
 lxd/endpoints/endpoints.go     |  18 +++++++
 lxd/endpoints/pprof.go         | 109 +++++++++++++++++++++++++++++++++++++++++
 lxd/main_daemon.go             |  23 +--------
 lxd/node/config.go             |  30 +++++++++++-
 scripts/bash/lxd-client        |   2 +-
 shared/version/api.go          |   1 +
 test/main.sh                   |   2 -
 test/suites/profiling.sh       |  44 -----------------
 test/suites/static_analysis.sh |   1 -
 19 files changed, 183 insertions(+), 343 deletions(-)
 delete mode 100644 lxd/debug/cpu.go
 delete mode 100644 lxd/debug/cpu_test.go
 delete mode 100644 lxd/debug/goroutines.go
 delete mode 100644 lxd/debug/memory.go
 delete mode 100644 lxd/debug/memory_test.go
 delete mode 100644 lxd/debug/start.go
 create mode 100644 lxd/endpoints/pprof.go
 delete mode 100644 test/suites/profiling.sh

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index a16527207..4ee151e48 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -540,3 +540,9 @@ sockets.
 ## container\_protection\_delete
 Enables setting the `security.protection.delete` field which prevents containers
 from being deleted if set to true. Snapshots are not affected by this setting.
+
+## pprof\_http
+This adds a new core.debug\_address config option to start a debugging HTTP server.
+
+That server currently includes a pprof API and replaces the old
+cpu-profile, memory-profile and print-goroutines debug options.
diff --git a/doc/server.md b/doc/server.md
index a24668b58..d71ceccaf 100644
--- a/doc/server.md
+++ b/doc/server.md
@@ -11,7 +11,8 @@ currently supported:
 Key                             | Type      | Default   | API extension            | Description
 :--                             | :---      | :------   | :------------            | :----------
 cluster.offline\_threshold      | integer   | 20        | clustering               | Number of seconds after which an unresponsive node is considered offline
-core.https\_address             | string    | -         | -                        | Address to bind for the remote API
+core.debug\_address             | string    | -         | pprof\_http              | Address to bind the pprof debug server to (HTTP)
+core.https\_address             | string    | -         | -                        | Address to bind for the remote API (HTTPs)
 core.https\_allowed\_credentials| boolean   | -         | -                        | Whether to set Access-Control-Allow-Credentials http header value to "true"
 core.https\_allowed\_headers    | string    | -         | -                        | Access-Control-Allow-Headers http header value
 core.https\_allowed\_methods    | string    | -         | -                        | Access-Control-Allow-Methods http header value
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 2fc1b6751..ca0f06247 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -273,8 +273,7 @@ func api10Patch(d *Daemon, r *http.Request) Response {
 }
 
 func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
-	// The HTTPS address and maas machine are the only config keys that we
-	// want to save in the node-level database, so handle it here.
+	// First deal with config specific to the local daemon
 	nodeValues := map[string]interface{}{}
 
 	for key := range node.ConfigSchema {
@@ -291,7 +290,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 		var err error
 		newNodeConfig, err = node.ConfigLoad(tx)
 		if err != nil {
-			return errors.Wrap(err, "failed to load node config")
+			return errors.Wrap(err, "Failed to load node config")
 		}
 		if patch {
 			nodeChanged, err = newNodeConfig.Patch(nodeValues)
@@ -309,13 +308,14 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 		}
 	}
 
+	// Then deal with cluster wide configuration
 	var clusterChanged map[string]string
 	var newClusterConfig *cluster.Config
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		var err error
 		newClusterConfig, err = cluster.ConfigLoad(tx)
 		if err != nil {
-			return errors.Wrap(err, "failed to load cluster config")
+			return errors.Wrap(err, "Failed to load cluster config")
 		}
 		if patch {
 			clusterChanged, err = newClusterConfig.Patch(req.Config)
@@ -333,6 +333,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 		}
 	}
 
+	// Notify the other nodes about changes
 	notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
 	if err != nil {
 		return SmartError(err)
@@ -401,6 +402,11 @@ func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]str
 			if err != nil {
 				return err
 			}
+		case "core.debug_address":
+			err := d.endpoints.PprofUpdateAddress(value)
+			if err != nil {
+				return err
+			}
 		}
 	}
 	if maasChanged {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 7344f8d5b..31c7bd0d1 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -441,7 +441,12 @@ func (d *Daemon) init() error {
 
 	address, err := node.HTTPSAddress(d.db)
 	if err != nil {
-		return errors.Wrap(err, "failed to fetch node address")
+		return errors.Wrap(err, "Failed to fetch node address")
+	}
+
+	debugAddress, err := node.DebugAddress(d.db)
+	if err != nil {
+		return errors.Wrap(err, "Failed to fetch debug address")
 	}
 
 	/* Setup the web server */
@@ -453,6 +458,7 @@ func (d *Daemon) init() error {
 		DevLxdServer:         DevLxdServer(d),
 		LocalUnixSocketGroup: d.config.Group,
 		NetworkAddress:       address,
+		DebugAddress:         debugAddress,
 	}
 	d.endpoints, err = endpoints.Up(config)
 	if err != nil {
diff --git a/lxd/debug/cpu.go b/lxd/debug/cpu.go
deleted file mode 100644
index 9d76d2d2f..000000000
--- a/lxd/debug/cpu.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package debug
-
-import (
-	"fmt"
-	"os"
-	"runtime/pprof"
-
-	"golang.org/x/net/context"
-)
-
-// CPU starts the Go CPU profiler, dumping to the given file.
-func CPU(filename string) Activity {
-	return func() (activityFunc, error) {
-		if filename == "" {
-			return nil, nil
-		}
-
-		file, err := os.Create(filename)
-		if err != nil {
-			return nil, fmt.Errorf("Error opening cpu profile file: %v", err)
-		}
-
-		pprof.StartCPUProfile(file)
-		f := func(ctx context.Context) {
-			<-ctx.Done()
-			pprof.StopCPUProfile()
-		}
-		return f, nil
-	}
-}
diff --git a/lxd/debug/cpu_test.go b/lxd/debug/cpu_test.go
deleted file mode 100644
index 2a4d4290b..000000000
--- a/lxd/debug/cpu_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package debug_test
-
-import (
-	"io/ioutil"
-	"os"
-	"testing"
-
-	"github.com/lxc/lxd/lxd/debug"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func TestCPU(t *testing.T) {
-	// Create a temporary file.
-	file, err := ioutil.TempFile("", "lxd-util-")
-	assert.NoError(t, err)
-	file.Close()
-	defer os.Remove(file.Name())
-
-	stop, err := debug.Start(debug.CPU(file.Name()))
-	require.NoError(t, err)
-	stop()
-
-	// The CPU profiling data actually exists on disk.
-	_, err = os.Stat(file.Name())
-	assert.NoError(t, err)
-}
-
-func TestCPU_CannotCreateFile(t *testing.T) {
-	stop, err := debug.Start(debug.CPU("/a/path/that/does/not/exists"))
-	assert.Contains(t, err.Error(), "Error opening cpu profile file")
-	stop()
-}
diff --git a/lxd/debug/goroutines.go b/lxd/debug/goroutines.go
deleted file mode 100644
index fd8919fb1..000000000
--- a/lxd/debug/goroutines.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package debug
-
-import (
-	"time"
-
-	"github.com/lxc/lxd/lxd/task"
-	"github.com/lxc/lxd/shared/logger"
-	"golang.org/x/net/context"
-)
-
-// Goroutines starts a task to print the goroutines stack at the given interval.
-func Goroutines(seconds int) Activity {
-	return func() (activityFunc, error) {
-		if seconds <= 0 {
-			return nil, nil
-		}
-
-		schedule := task.Every(time.Duration(seconds) * time.Second)
-
-		f := func(ctx context.Context) {
-			stop, _ := task.Start(goroutinesTaskFunc, schedule)
-			<-ctx.Done()
-
-			// Stop the task, giving it some little time to finish
-			// if it's in the middle of a print.
-			stop(100 * time.Millisecond)
-		}
-		return f, nil
-	}
-}
-
-func goroutinesTaskFunc(context.Context) {
-	logger.Debugf(logger.GetStack())
-}
diff --git a/lxd/debug/memory.go b/lxd/debug/memory.go
deleted file mode 100644
index 6926cc515..000000000
--- a/lxd/debug/memory.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package debug
-
-import (
-	"os"
-	"os/signal"
-	"runtime/pprof"
-	"syscall"
-
-	"github.com/lxc/lxd/shared/logger"
-	"golang.org/x/net/context"
-)
-
-// Memory is a debug activity that perpetually watches for SIGUSR1 signals and
-// dumps the memory to the given file whenever the signal is received.
-//
-// If the given filename is the empty string, no profiler is started.
-func Memory(filename string) Activity {
-	return func() (activityFunc, error) {
-		if filename == "" {
-			return nil, nil
-		}
-
-		signals := make(chan os.Signal, 1)
-		signal.Notify(signals, syscall.SIGUSR1)
-
-		f := func(ctx context.Context) {
-			memoryWatcher(ctx, signals, filename)
-			signal.Stop(signals)
-		}
-
-		return f, nil
-	}
-}
-
-// Watch for SIGUSR1 and trigger memoryDump().
-func memoryWatcher(ctx context.Context, signals <-chan os.Signal, filename string) {
-	for {
-		select {
-		case sig := <-signals:
-			logger.Debugf("Received '%s signal', dumping memory", sig)
-			memoryDump(filename)
-		case <-ctx.Done():
-			logger.Debugf("Shutdown memory profiler")
-			return
-		}
-	}
-}
-
-// Dump the current memory info to the given file.
-func memoryDump(filename string) {
-	f, err := os.Create(filename)
-	if err != nil {
-		logger.Debugf("Error opening memory profile file '%s': %s", filename, err)
-		return
-	}
-	pprof.WriteHeapProfile(f)
-	f.Close()
-}
diff --git a/lxd/debug/memory_test.go b/lxd/debug/memory_test.go
deleted file mode 100644
index 9a8f89f7e..000000000
--- a/lxd/debug/memory_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package debug_test
-
-import (
-	"io/ioutil"
-	"os"
-	"syscall"
-	"testing"
-	"time"
-
-	log "github.com/lxc/lxd/shared/log15"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-
-	"github.com/lxc/lxd/lxd/debug"
-	"github.com/lxc/lxd/shared/logging"
-)
-
-func TestMemory(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.
-	stop, err := debug.Start(debug.Memory(file.Name()))
-	require.NoError(t, err)
-	syscall.Kill(os.Getpid(), syscall.SIGUSR1)
-	record := logging.WaitRecord(records, time.Second)
-	require.NotNil(t, record)
-	assert.Equal(t, "Received 'user defined signal 1 signal', dumping memory", record.Msg)
-
-	go stop()
-	record = logging.WaitRecord(records, time.Second)
-	require.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)
-}
-
-func TestMemory_EmptyFilename(t *testing.T) {
-	stop, err := debug.Start(debug.Memory(""))
-	require.NoError(t, err)
-	stop()
-}
diff --git a/lxd/debug/start.go b/lxd/debug/start.go
deleted file mode 100644
index d908f4c6a..000000000
--- a/lxd/debug/start.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package debug
-
-import (
-	"sync"
-
-	"golang.org/x/net/context"
-)
-
-// Start the given LXD daemon debug activities.
-//
-// Return a function that can be used to stop all debug activities that were
-// started, along with an error if any activity could not be started.
-func Start(activities ...Activity) (func(), error) {
-	// First create the debug activities functions that will be run in
-	// individual goroutines. If any error happens here we bail out before
-	// actually spawning any activity.
-	functions := make([]activityFunc, len(activities))
-	for i, activity := range activities {
-		f, err := activity()
-		if err != nil {
-			return func() {}, err
-		}
-		functions[i] = f
-	}
-
-	// Now spawn the debug activities.
-	ctx, cancel := context.WithCancel(context.Background())
-	wg := sync.WaitGroup{}
-	for i := range functions {
-		f := functions[i]
-		if f == nil {
-			// There's actually nothing to execute.
-			continue
-		}
-		wg.Add(1)
-		go func() {
-			f(ctx)
-			wg.Done()
-		}()
-	}
-
-	stop := func() {
-		cancel()
-		wg.Wait()
-	}
-	return stop, nil
-}
-
-// Activity creates a specific debug activity function, returning an error if it
-// can't be created for some reason.
-type Activity func() (activityFunc, error)
-
-// A function that executes a specific debug activity.
-//
-// It must terminate gracefully whenever the given context is done.
-type activityFunc func(context.Context)
diff --git a/lxd/endpoints/endpoints.go b/lxd/endpoints/endpoints.go
index 4b4192670..ea9bc0f6a 100644
--- a/lxd/endpoints/endpoints.go
+++ b/lxd/endpoints/endpoints.go
@@ -46,6 +46,11 @@ type Config struct {
 	//
 	// It can be updated after the endpoints are up using UpdateNetworkAddress().
 	NetworkAddress string
+
+	// DebugSetAddress sets the address for the pprof endpoint.
+	//
+	// It can be updated after the endpoints are up using UpdateDebugAddress().
+	DebugAddress string
 }
 
 // Up brings up all applicable LXD endpoints and starts accepting HTTP
@@ -140,6 +145,7 @@ func (e *Endpoints) up(config *Config) error {
 		devlxd:  config.DevLxdServer,
 		local:   config.RestServer,
 		network: config.RestServer,
+		pprof:   pprofCreateServer(),
 	}
 	e.cert = config.Cert
 
@@ -177,6 +183,16 @@ func (e *Endpoints) up(config *Config) error {
 		e.listeners[network] = networkCreateListener(config.NetworkAddress, e.cert)
 	}
 
+	if config.DebugAddress != "" {
+		e.listeners[pprof], err = pprofCreateListener(config.DebugAddress)
+		if err != nil {
+			return err
+		}
+
+		logger.Infof("Starting pprof handler:")
+		e.serveHTTP(pprof)
+	}
+
 	logger.Infof("Starting /dev/lxd handler:")
 	e.serveHTTP(devlxd)
 
@@ -283,6 +299,7 @@ const (
 	local kind = iota
 	devlxd
 	network
+	pprof
 )
 
 // Human-readable descriptions of the various kinds of endpoints.
@@ -290,4 +307,5 @@ var descriptions = map[kind]string{
 	local:   "Unix socket",
 	devlxd:  "devlxd socket",
 	network: "TCP socket",
+	pprof:   "pprof socket",
 }
diff --git a/lxd/endpoints/pprof.go b/lxd/endpoints/pprof.go
new file mode 100644
index 000000000..e9a3181f1
--- /dev/null
+++ b/lxd/endpoints/pprof.go
@@ -0,0 +1,109 @@
+package endpoints
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"time"
+
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared/logger"
+
+	_ "net/http/pprof"  // pprof magic
+)
+
+func pprofCreateServer() *http.Server {
+	// Undo the magic that importing pprof does
+	pprofMux := http.DefaultServeMux
+	http.DefaultServeMux = http.NewServeMux()
+
+	// Setup an http server
+	srv := &http.Server{
+		Handler: pprofMux,
+	}
+
+	return srv
+}
+
+func pprofCreateListener(address string) (net.Listener, error) {
+	return net.Listen("tcp", address)
+}
+
+// PprofAddress returns the network addresss of the pprof endpoint, or an empty string if there's no pprof endpoint
+func (e *Endpoints) PprofAddress() string {
+	e.mu.RLock()
+	defer e.mu.RUnlock()
+
+	listener := e.listeners[pprof]
+	if listener == nil {
+		return ""
+	}
+
+	return listener.Addr().String()
+}
+
+// PprofUpdateAddress updates the address for the pprof endpoint, shutting it down and restarting it.
+func (e *Endpoints) PprofUpdateAddress(address string) error {
+	if address != "" {
+		address = util.CanonicalNetworkAddress(address)
+	}
+
+	oldAddress := e.NetworkAddress()
+	if address == oldAddress {
+		return nil
+	}
+
+	logger.Infof("Update pprof address")
+
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	// Close the previous socket
+	e.closeListener(pprof)
+
+	// If turning off listening, we're done
+	if address == "" {
+		return nil
+	}
+
+	// Attempt to setup the new listening socket
+	getListener := func(address string) (*net.Listener, error) {
+		var err error
+		var listener net.Listener
+
+		for i := 0; i < 10; i++ { // Ten retries over a second seems reasonable.
+			listener, err = net.Listen("tcp", address)
+			if err == nil {
+				break
+			}
+
+			time.Sleep(100 * time.Millisecond)
+		}
+
+		if err != nil {
+			return nil, fmt.Errorf("Cannot listen on http socket: %v", err)
+		}
+
+		return &listener, nil
+	}
+
+	// If setting a new address, setup the listener
+	if address != "" {
+		listener, err := getListener(address)
+		if err != nil {
+			// Attempt to revert to the previous address
+			listener, err1 := getListener(oldAddress)
+			if err1 == nil {
+				e.listeners[pprof] = *listener
+				e.serveHTTP(pprof)
+			}
+
+			return err
+		}
+
+		e.listeners[pprof] = *listener
+		e.serveHTTP(pprof)
+	}
+
+	return nil
+}
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index 2a5d324ed..d6b3b1c9f 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/spf13/cobra"
 
-	dbg "github.com/lxc/lxd/lxd/debug"
 	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -19,11 +18,6 @@ type cmdDaemon struct {
 
 	// Common options
 	flagGroup string
-
-	// Debug options
-	flagCPUProfile      string
-	flagMemoryProfile   string
-	flagPrintGoroutines int
 }
 
 func (c *cmdDaemon) Command() *cobra.Command {
@@ -41,9 +35,6 @@ func (c *cmdDaemon) Command() *cobra.Command {
 `
 	cmd.RunE = c.Run
 	cmd.Flags().StringVar(&c.flagGroup, "group", "", "The group of users that will be allowed to talk to LXD"+"``")
-	cmd.Flags().StringVar(&c.flagCPUProfile, "cpu-profile", "", "Enable CPU profiling, writing into the specified file"+"``")
-	cmd.Flags().StringVar(&c.flagMemoryProfile, "memory-profile", "", "Enable memory profiling, writing into the specified file"+"``")
-	cmd.Flags().IntVar(&c.flagPrintGoroutines, "print-goroutines", 0, "How often to print all the goroutines"+"``")
 
 	return cmd
 }
@@ -54,18 +45,6 @@ func (c *cmdDaemon) Run(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("This must be run as root")
 	}
 
-	// Start debug activities as per command line flags, if any.
-	stop, err := dbg.Start(
-		dbg.CPU(c.flagCPUProfile),
-		dbg.Memory(c.flagMemoryProfile),
-		dbg.Goroutines(c.flagPrintGoroutines),
-	)
-	if err != nil {
-		return err
-	}
-
-	defer stop()
-
 	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
 	for _, p := range neededPrograms {
 		_, err := exec.LookPath(p)
@@ -79,7 +58,7 @@ func (c *cmdDaemon) Run(cmd *cobra.Command, args []string) error {
 	conf.Trace = c.global.flagLogTrace
 	d := NewDaemon(conf, sys.DefaultOS())
 
-	err = d.Init()
+	err := d.Init()
 	if err != nil {
 		return err
 	}
diff --git a/lxd/node/config.go b/lxd/node/config.go
index 087da756c..186c8056a 100644
--- a/lxd/node/config.go
+++ b/lxd/node/config.go
@@ -37,6 +37,11 @@ func (c *Config) HTTPSAddress() string {
 	return c.m.GetString("core.https_address")
 }
 
+// DebugAddress returns the address and port to setup the pprof listener on
+func (c *Config) DebugAddress() string {
+	return c.m.GetString("core.debug_address")
+}
+
 // MAASMachine returns the MAAS machine this instance is associated with, if
 // any.
 func (c *Config) MAASMachine() string {
@@ -60,6 +65,7 @@ func (c *Config) Patch(patch map[string]interface{}) (map[string]string, error)
 	for name, value := range patch {
 		values[name] = value
 	}
+
 	return c.update(values)
 }
 
@@ -75,9 +81,26 @@ func HTTPSAddress(node *db.Node) (string, error) {
 	if err != nil {
 		return "", err
 	}
+
 	return config.HTTPSAddress(), nil
 }
 
+// DebugAddress is a convenience for loading the node configuration and
+// returning the value of core.debug_address.
+func DebugAddress(node *db.Node) (string, error) {
+	var config *Config
+	err := node.Transaction(func(tx *db.NodeTx) error {
+		var err error
+		config, err = ConfigLoad(tx)
+		return err
+	})
+	if err != nil {
+		return "", err
+	}
+
+	return config.DebugAddress(), nil
+}
+
 func (c *Config) update(values map[string]interface{}) (map[string]string, error) {
 	changed, err := c.m.Change(values)
 	if err != nil {
@@ -94,9 +117,12 @@ func (c *Config) update(values map[string]interface{}) (map[string]string, error
 
 // ConfigSchema defines available server configuration keys.
 var ConfigSchema = config.Schema{
-	// Network address for this LXD server.
+	// Network address for this LXD server
 	"core.https_address": {},
 
-	// MAAS machine this LXD instance is associated with.
+	// Network address for the debug server
+	"core.debug_address": {},
+
+	// MAAS machine this LXD instance is associated with
 	"maas.machine": {},
 }
diff --git a/scripts/bash/lxd-client b/scripts/bash/lxd-client
index 54d8d7eb8..ceed02fa2 100644
--- a/scripts/bash/lxd-client
+++ b/scripts/bash/lxd-client
@@ -68,7 +68,7 @@ _have lxc && {
       core.https_allowed_headers core.https_allowed_methods \
       core.https_allowed_origin core.macaroon.endpoint core.proxy_https \
       core.proxy_http core.proxy_ignore_hosts core.trust_password \
-      cluster.offline_threshold \
+      core.debug_address cluster.offline_threshold \
       images.auto_update_cached images.auto_update_interval \
       images.compression_algorithm images.remote_cache_expiry \
       maas.api.url maas.api.key maas.machine"
diff --git a/shared/version/api.go b/shared/version/api.go
index 3a673206f..2b83d1291 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -113,6 +113,7 @@ var APIExtensions = []string{
 	"network_state",
 	"proxy_unix_dac_properties",
 	"container_protection_delete",
+	"pprof_http",
 }
 
 // APIExtensionsCount returns the number of available API extensions.
diff --git a/test/main.sh b/test/main.sh
index 34ac7ee58..352141169 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -188,8 +188,6 @@ run_test test_devlxd "/dev/lxd"
 run_test test_fuidshift "fuidshift"
 run_test test_migration "migration"
 run_test test_fdleak "fd leak"
-run_test test_cpu_profiling "CPU profiling"
-run_test test_mem_profiling "memory profiling"
 run_test test_storage "storage"
 run_test test_init_auto "lxd init auto"
 run_test test_init_interactive "lxd init interactive"
diff --git a/test/suites/profiling.sh b/test/suites/profiling.sh
deleted file mode 100644
index 006d1badc..000000000
--- a/test/suites/profiling.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-test_cpu_profiling() {
-  LXD3_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-  chmod +x "${LXD3_DIR}"
-  spawn_lxd "${LXD3_DIR}" false --cpu-profile "${LXD3_DIR}/cpu.out"
-  lxdpid=$(cat "${LXD3_DIR}/lxd.pid")
-  kill -TERM "${lxdpid}"
-  wait "${lxdpid}"
-     #|| true
-  export PPROF_TMPDIR="${TEST_DIR}/pprof"
-  echo top5 | go tool pprof "$(which lxd)" "${LXD3_DIR}/cpu.out"
-  echo ""
-
-  # Cleanup following manual kill
-  rm -f "${LXD3_DIR}/unix.socket"
-  find "${LXD3_DIR}" -name shmounts -exec "umount" "-l" "{}" \; >/dev/null 2>&1 || true
-
-  kill_lxd "${LXD3_DIR}"
-}
-
-test_mem_profiling() {
-  LXD4_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-  chmod +x "${LXD4_DIR}"
-  spawn_lxd "${LXD4_DIR}" false --memory-profile "${LXD4_DIR}/mem"
-  lxdpid=$(cat "${LXD4_DIR}/lxd.pid")
-
-  if [ -e "${LXD4_DIR}/mem" ]; then
-    false
-  fi
-
-  kill -USR1 "${lxdpid}"
-
-  timeout=50
-  while [ "${timeout}" != "0" ]; do
-    [ -e "${LXD4_DIR}/mem" ] && break
-    sleep 0.1
-    timeout=$((timeout-1))
-  done
-
-  export PPROF_TMPDIR="${TEST_DIR}/pprof"
-  echo top5 | go tool pprof "$(which lxd)" "${LXD4_DIR}/mem"
-  echo ""
-
-  kill_lxd "${LXD4_DIR}"
-}
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 2aa61bf96..8ef94b506 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -72,7 +72,6 @@ test_static_analysis() {
       golint -set_exit_status lxd/db/node
       golint -set_exit_status lxd/db/query
       golint -set_exit_status lxd/db/schema
-      golint -set_exit_status lxd/debug
       golint -set_exit_status lxd/endpoints
       golint -set_exit_status lxd/maas
       #golint -set_exit_status lxd/migration


More information about the lxc-devel mailing list