[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