New command: consul debug (#4754)

* agent/debug: add package for debugging, host info

* api: add v1/agent/host endpoint

* agent: add v1/agent/host endpoint

* command/debug: implementation of static capture

* command/debug: tests and only configured targets

* agent/debug: add basic test for host metrics

* command/debug: add methods for dynamic data capture

* api: add debug/pprof endpoints

* command/debug: add pprof

* command/debug: timing, wg, logs to disk

* vendor: add gopsutil/disk

* command/debug: add a usage section

* website: add docs for consul debug

* agent/host: require operator:read

* api/host: improve docs and no retry timing

* command/debug: fail on extra arguments

* command/debug: fixup file permissions to 0644

* command/debug: remove server flags

* command/debug: improve clarity of usage section

* api/debug: add Trace for profiling, fix profile

* command/debug: capture profile and trace at the same time

* command/debug: add index document

* command/debug: use "clusters" in place of members

* command/debug: remove address in output

* command/debug: improve comment on metrics sleep

* command/debug: clarify usage

* agent: always register pprof handlers and protect

This will allow us to avoid a restart of a target agent
for profiling by always registering the pprof handlers.

Given this is a potentially sensitive path, it is protected
with an operator:read ACL and enable debug being
set to true on the target agent. enable_debug still requires
a restart.

If ACLs are disabled, enable_debug is sufficient.

* command/debug: use trace.out instead of .prof

More in line with golang docs.

* agent: fix comment wording

* agent: wrap table driven tests in t.run()
This commit is contained in:
Jack Pearkes 2018-10-17 13:20:35 -07:00
parent 96a35f8abc
commit 197d62c6ca
33 changed files with 3463 additions and 14 deletions

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/consul/agent/cache-types"
"github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/debug"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/ipaddr"
@ -1463,3 +1464,27 @@ type connectAuthorizeResp struct {
Authorized bool // True if authorized, false if not
Reason string // Reason for the Authorized value (whether true or false)
}
// AgentHost
//
// GET /v1/agent/host
//
// Retrieves information about resources available and in-use for the
// host the agent is running on such as CPU, memory, and disk usage. Requires
// a operator:read ACL token.
func (s *HTTPServer) AgentHost(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s.parseToken(req, &token)
rule, err := s.agent.resolveToken(token)
if err != nil {
return nil, err
}
// TODO(pearkes): Is agent:read appropriate here? There could be relatively
// sensitive information made available in this API
if rule != nil && !rule.OperatorRead() {
return nil, acl.ErrPermissionDenied
}
return debug.CollectHostInfo(), nil
}

View File

@ -20,6 +20,7 @@ import (
"github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/config"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/debug"
"github.com/hashicorp/consul/agent/local"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
@ -1877,9 +1878,9 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
json := `
{
"name":"test",
"port":8000,
"enable_tag_override": true,
"name":"test",
"port":8000,
"enable_tag_override": true,
"meta": {
"some": "meta",
"enable_tag_override": "meta is 'opaque' so should not get translated"
@ -1929,9 +1930,9 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
]
},
"sidecar_service": {
"name":"test-proxy",
"port":8001,
"enable_tag_override": true,
"name":"test-proxy",
"port":8001,
"enable_tag_override": true,
"meta": {
"some": "meta",
"enable_tag_override": "sidecar_service.meta is 'opaque' so should not get translated"
@ -2791,7 +2792,7 @@ func TestAgent_RegisterServiceDeregisterService_Sidecar(t *testing.T) {
require := require.New(t)
// Constrain auto ports to 1 available to make it deterministic
hcl := `ports {
hcl := `ports {
sidecar_min_port = 2222
sidecar_max_port = 2222
}
@ -5537,3 +5538,55 @@ func testAllowProxyConfig() string {
}
`
}
func TestAgent_Host(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dc1 := "dc1"
a := NewTestAgent(t.Name(), `
acl_datacenter = "`+dc1+`"
acl_default_policy = "allow"
acl_master_token = "master"
acl_agent_token = "agent"
acl_agent_master_token = "towel"
acl_enforce_version_8 = true
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/agent/host?token=master", nil)
resp := httptest.NewRecorder()
respRaw, err := a.srv.AgentHost(resp, req)
assert.Nil(err)
assert.Equal(http.StatusOK, resp.Code)
assert.NotNil(respRaw)
obj := respRaw.(*debug.HostInfo)
assert.NotNil(obj.CollectionTime)
assert.Empty(obj.Errors)
}
func TestAgent_HostBadACL(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dc1 := "dc1"
a := NewTestAgent(t.Name(), `
acl_datacenter = "`+dc1+`"
acl_default_policy = "deny"
acl_master_token = "root"
acl_agent_token = "agent"
acl_agent_master_token = "towel"
acl_enforce_version_8 = true
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/agent/host?token=agent", nil)
resp := httptest.NewRecorder()
respRaw, err := a.srv.AgentHost(resp, req)
assert.EqualError(err, "ACL not found")
assert.Equal(http.StatusOK, resp.Code)
assert.Nil(respRaw)
}

59
agent/debug/host.go Normal file
View File

@ -0,0 +1,59 @@
package debug
import (
"time"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
)
const (
// DiskUsagePath is the path to check usage of the disk.
// Must be a filessytem path such as "/", not device file path like "/dev/vda1"
DiskUsagePath = "/"
)
// HostInfo includes information about resources on the host as well as
// collection time and
type HostInfo struct {
Memory *mem.VirtualMemoryStat
CPU []cpu.InfoStat
Host *host.InfoStat
Disk *disk.UsageStat
CollectionTime int64
Errors []error
}
// CollectHostInfo queries the host system and returns HostInfo. Any
// errors encountered will be returned in HostInfo.Errors
func CollectHostInfo() *HostInfo {
info := &HostInfo{CollectionTime: time.Now().UTC().UnixNano()}
if h, err := host.Info(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Host = h
}
if v, err := mem.VirtualMemory(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Memory = v
}
if d, err := disk.Usage(DiskUsagePath); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.Disk = d
}
if c, err := cpu.Info(); err != nil {
info.Errors = append(info.Errors, err)
} else {
info.CPU = c
}
return info
}

20
agent/debug/host_test.go Normal file
View File

@ -0,0 +1,20 @@
package debug
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCollectHostInfo(t *testing.T) {
assert := assert.New(t)
host := CollectHostInfo()
assert.Nil(host.Errors)
assert.NotNil(host.CollectionTime)
assert.NotNil(host.Host)
assert.NotNil(host.Disk)
assert.NotNil(host.Memory)
}

View File

@ -142,6 +142,41 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
mux.Handle(pattern, gzipHandler)
}
// handlePProf takes the given pattern and pprof handler
// and wraps it to add authorization and metrics
handlePProf := func(pattern string, handler http.HandlerFunc) {
wrapper := func(resp http.ResponseWriter, req *http.Request) {
var token string
s.parseToken(req, &token)
rule, err := s.agent.resolveToken(token)
if err != nil {
resp.WriteHeader(http.StatusForbidden)
return
}
// If enableDebug is not set, and ACLs are disabled, write
// an unauthorized response
if !enableDebug {
if s.checkACLDisabled(resp, req) {
return
}
}
// If the token provided does not have the necessary permissions,
// write a forbidden response
if rule != nil && !rule.OperatorRead() {
resp.WriteHeader(http.StatusForbidden)
return
}
// Call the pprof handler
handler(resp, req)
}
handleFuncMetrics(pattern, http.HandlerFunc(wrapper))
}
mux.HandleFunc("/", s.Index)
for pattern, fn := range endpoints {
thisFn := fn
@ -151,12 +186,13 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler {
}
handleFuncMetrics(pattern, s.wrap(bound, methods))
}
if enableDebug {
handleFuncMetrics("/debug/pprof/", pprof.Index)
handleFuncMetrics("/debug/pprof/cmdline", pprof.Cmdline)
handleFuncMetrics("/debug/pprof/profile", pprof.Profile)
handleFuncMetrics("/debug/pprof/symbol", pprof.Symbol)
}
// Register wrapped pprof handlers
handlePProf("/debug/pprof/", pprof.Index)
handlePProf("/debug/pprof/cmdline", pprof.Cmdline)
handlePProf("/debug/pprof/profile", pprof.Profile)
handlePProf("/debug/pprof/symbol", pprof.Symbol)
handlePProf("/debug/pprof/trace", pprof.Trace)
if s.IsUIEnabled() {
legacy_ui, err := strconv.ParseBool(os.Getenv("CONSUL_UI_LEGACY"))

View File

@ -13,6 +13,7 @@ func init() {
registerEndpoint("/v1/acl/replication", []string{"GET"}, (*HTTPServer).ACLReplicationStatus)
registerEndpoint("/v1/agent/token/", []string{"PUT"}, (*HTTPServer).AgentToken)
registerEndpoint("/v1/agent/self", []string{"GET"}, (*HTTPServer).AgentSelf)
registerEndpoint("/v1/agent/host", []string{"GET"}, (*HTTPServer).AgentHost)
registerEndpoint("/v1/agent/maintenance", []string{"PUT"}, (*HTTPServer).AgentNodeMaintenance)
registerEndpoint("/v1/agent/reload", []string{"PUT"}, (*HTTPServer).AgentReload)
registerEndpoint("/v1/agent/monitor", []string{"GET"}, (*HTTPServer).AgentMonitor)

View File

@ -20,8 +20,10 @@ import (
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/consul/testutil"
"github.com/hashicorp/go-cleanhttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
)
@ -724,6 +726,104 @@ func TestParseWait(t *testing.T) {
t.Fatalf("Bad: %v", b)
}
}
func TestPProfHandlers_EnableDebug(t *testing.T) {
t.Parallel()
require := require.New(t)
a := NewTestAgent(t.Name(), "enable_debug = true")
defer a.Shutdown()
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/debug/pprof/profile", nil)
a.srv.Handler.ServeHTTP(resp, req)
require.Equal(http.StatusOK, resp.Code)
}
func TestPProfHandlers_DisableDebugNoACLs(t *testing.T) {
t.Parallel()
require := require.New(t)
a := NewTestAgent(t.Name(), "enable_debug = false")
defer a.Shutdown()
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/debug/pprof/profile", nil)
a.srv.Handler.ServeHTTP(resp, req)
require.Equal(http.StatusUnauthorized, resp.Code)
}
func TestPProfHandlers_ACLs(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dc1 := "dc1"
a := NewTestAgent(t.Name(), `
acl_datacenter = "`+dc1+`"
acl_default_policy = "deny"
acl_master_token = "master"
acl_agent_token = "agent"
acl_agent_master_token = "towel"
acl_enforce_version_8 = true
enable_debug = false
`)
cases := []struct {
code int
token string
endpoint string
nilResponse bool
}{
{
code: http.StatusOK,
token: "master",
endpoint: "/debug/pprof/heap",
nilResponse: false,
},
{
code: http.StatusForbidden,
token: "agent",
endpoint: "/debug/pprof/heap",
nilResponse: true,
},
{
code: http.StatusForbidden,
token: "agent",
endpoint: "/debug/pprof/",
nilResponse: true,
},
{
code: http.StatusForbidden,
token: "",
endpoint: "/debug/pprof/",
nilResponse: true,
},
{
code: http.StatusOK,
token: "master",
endpoint: "/debug/pprof/heap",
nilResponse: false,
},
{
code: http.StatusForbidden,
token: "towel",
endpoint: "/debug/pprof/heap",
nilResponse: true,
},
}
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
for i, c := range cases {
t.Run(fmt.Sprintf("case %d (%#v)", i, c), func(t *testing.T) {
req, _ := http.NewRequest("GET", fmt.Sprintf("%s?token=%s", c.endpoint, c.token), nil)
resp := httptest.NewRecorder()
a.srv.Handler.ServeHTTP(resp, req)
assert.Equal(c.code, resp.Code)
})
}
}
func TestParseWait_InvalidTime(t *testing.T) {
t.Parallel()

View File

@ -314,6 +314,24 @@ func (a *Agent) Self() (map[string]map[string]interface{}, error) {
return out, nil
}
// Host is used to retrieve information about the host the
// agent is running on such as CPU, memory, and disk. Requires
// a operator:read ACL token.
func (a *Agent) Host() (map[string]interface{}, error) {
r := a.c.newRequest("GET", "/v1/agent/host")
_, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var out map[string]interface{}
if err := decodeBody(resp, &out); err != nil {
return nil, err
}
return out, nil
}
// Metrics is used to query the agent we are speaking to for
// its current internal metric data
func (a *Agent) Metrics() (*MetricsInfo, error) {

View File

@ -53,6 +53,26 @@ func TestAPI_AgentMetrics(t *testing.T) {
})
}
func TestAPI_AgentHost(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
timer := &retry.Timer{}
retry.RunWith(timer, t, func(r *retry.R) {
host, err := agent.Host()
if err != nil {
r.Fatalf("err: %v", err)
}
// CollectionTime should exist on all responses
if host["CollectionTime"] == nil {
r.Fatalf("missing host response")
}
})
}
func TestAPI_AgentReload(t *testing.T) {
t.Parallel()

106
api/debug.go Normal file
View File

@ -0,0 +1,106 @@
package api
import (
"fmt"
"io/ioutil"
"strconv"
)
// Debug can be used to query the /debug/pprof endpoints to gather
// profiling information about the target agent.Debug
//
// The agent must have enable_debug set to true for profiling to be enabled
// and for these endpoints to function.
type Debug struct {
c *Client
}
// Debug returns a handle that exposes the internal debug endpoints.
func (c *Client) Debug() *Debug {
return &Debug{c}
}
// Heap returns a pprof heap dump
func (d *Debug) Heap() ([]byte, error) {
r := d.c.newRequest("GET", "/debug/pprof/heap")
_, resp, err := d.c.doRequest(r)
if err != nil {
return nil, fmt.Errorf("error making request: %s", err)
}
defer resp.Body.Close()
// We return a raw response because we're just passing through a response
// from the pprof handlers
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error decoding body: %s", err)
}
return body, nil
}
// Profile returns a pprof CPU profile for the specified number of seconds
func (d *Debug) Profile(seconds int) ([]byte, error) {
r := d.c.newRequest("GET", "/debug/pprof/profile")
// Capture a profile for the specified number of seconds
r.params.Set("seconds", strconv.Itoa(seconds))
_, resp, err := d.c.doRequest(r)
if err != nil {
return nil, fmt.Errorf("error making request: %s", err)
}
defer resp.Body.Close()
// We return a raw response because we're just passing through a response
// from the pprof handlers
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error decoding body: %s", err)
}
return body, nil
}
// Trace returns an execution trace
func (d *Debug) Trace(seconds int) ([]byte, error) {
r := d.c.newRequest("GET", "/debug/pprof/trace")
// Capture a trace for the specified number of seconds
r.params.Set("seconds", strconv.Itoa(seconds))
_, resp, err := d.c.doRequest(r)
if err != nil {
return nil, fmt.Errorf("error making request: %s", err)
}
defer resp.Body.Close()
// We return a raw response because we're just passing through a response
// from the pprof handlers
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error decoding body: %s", err)
}
return body, nil
}
// Goroutine returns a pprof goroutine profile
func (d *Debug) Goroutine() ([]byte, error) {
r := d.c.newRequest("GET", "/debug/pprof/goroutine")
_, resp, err := d.c.doRequest(r)
if err != nil {
return nil, fmt.Errorf("error making request: %s", err)
}
defer resp.Body.Close()
// We return a raw response because we're just passing through a response
// from the pprof handlers
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error decoding body: %s", err)
}
return body, nil
}

83
api/debug_test.go Normal file
View File

@ -0,0 +1,83 @@
package api
import (
"testing"
"github.com/hashicorp/consul/testutil"
)
func TestAPI_DebugHeap(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.EnableDebug = true
})
defer s.Stop()
debug := c.Debug()
raw, err := debug.Heap()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(raw) <= 0 {
t.Fatalf("no response: %#v", raw)
}
}
func TestAPI_DebugProfile(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.EnableDebug = true
})
defer s.Stop()
debug := c.Debug()
raw, err := debug.Profile(1)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(raw) <= 0 {
t.Fatalf("no response: %#v", raw)
}
}
func TestAPI_DebugGoroutine(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.EnableDebug = true
})
defer s.Stop()
debug := c.Debug()
raw, err := debug.Goroutine()
if err != nil {
t.Fatalf("err: %v", err)
}
if len(raw) <= 0 {
t.Fatalf("no response: %#v", raw)
}
}
func TestAPI_DebugTrace(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.EnableDebug = true
})
defer s.Stop()
debug := c.Debug()
raw, err := debug.Trace(1)
if err != nil {
t.Fatalf("err: %v", err)
}
if len(raw) <= 0 {
t.Fatalf("no response: %#v", raw)
}
}

View File

@ -12,6 +12,7 @@ import (
caset "github.com/hashicorp/consul/command/connect/ca/set"
"github.com/hashicorp/consul/command/connect/envoy"
"github.com/hashicorp/consul/command/connect/proxy"
"github.com/hashicorp/consul/command/debug"
"github.com/hashicorp/consul/command/event"
"github.com/hashicorp/consul/command/exec"
"github.com/hashicorp/consul/command/forceleave"
@ -79,6 +80,7 @@ func init() {
Register("connect ca set-config", func(ui cli.Ui) (cli.Command, error) { return caset.New(ui), nil })
Register("connect proxy", func(ui cli.Ui) (cli.Command, error) { return proxy.New(ui, MakeShutdownCh()), nil })
Register("connect envoy", func(ui cli.Ui) (cli.Command, error) { return envoy.New(ui), nil })
Register("debug", func(ui cli.Ui) (cli.Command, error) { return debug.New(ui, MakeShutdownCh()), nil })
Register("event", func(ui cli.Ui) (cli.Command, error) { return event.New(ui), nil })
Register("exec", func(ui cli.Ui) (cli.Command, error) { return exec.New(ui, MakeShutdownCh()), nil })
Register("force-leave", func(ui cli.Ui) (cli.Command, error) { return forceleave.New(ui), nil })

702
command/debug/debug.go Normal file
View File

@ -0,0 +1,702 @@
package debug
import (
"archive/tar"
"compress/gzip"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
multierror "github.com/hashicorp/go-multierror"
"github.com/mitchellh/cli"
)
const (
// debugInterval is the interval in which to capture dynamic information
// when running debug
debugInterval = 30 * time.Second
// debugDuration is the total duration that debug runs before being
// shut down
debugDuration = 2 * time.Minute
// debugDurationGrace is a period of time added to the specified
// duration to allow intervals to capture within that time
debugDurationGrace = 2 * time.Second
// debugMinInterval is the minimum a user can configure the interval
// to prevent accidental DOS
debugMinInterval = 5 * time.Second
// debugMinDuration is the minimum a user can configure the duration
// to ensure that all information can be collected in time
debugMinDuration = 10 * time.Second
// debugArchiveExtension is the extension for archive files
debugArchiveExtension = ".tar.gz"
// debugProtocolVersion is the version of the package that is
// generated. If this format changes interface, this version
// can be incremented so clients can selectively support packages
debugProtocolVersion = 1
)
func New(ui cli.Ui, shutdownCh <-chan struct{}) *cmd {
ui = &cli.PrefixedUi{
OutputPrefix: "==> ",
InfoPrefix: " ",
ErrorPrefix: "==> ",
Ui: ui,
}
c := &cmd{UI: ui, shutdownCh: shutdownCh}
c.init()
return c
}
type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
shutdownCh <-chan struct{}
// flags
interval time.Duration
duration time.Duration
output string
archive bool
capture []string
client *api.Client
// validateTiming can be used to skip validation of interval, duration. This
// is primarily useful for testing
validateTiming bool
index *debugIndex
}
// debugIndex is used to manage the summary of all data recorded
// during the debug, to be written to json at the end of the run
// and stored at the root. Each attribute corresponds to a file or files.
type debugIndex struct {
// Version of the debug package
Version int
// Version of the target Consul agent
AgentVersion string
Interval string
Duration string
Targets []string
}
func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
defaultFilename := fmt.Sprintf("consul-debug-%d", time.Now().Unix())
c.flags.Var((*flags.AppendSliceValue)(&c.capture), "capture",
fmt.Sprintf("One or more types of information to capture. This can be used "+
"to capture a subset of information, and defaults to capturing "+
"everything available. Possible information for capture: %s. "+
"This can be repeated multiple times.", strings.Join(c.defaultTargets(), ", ")))
c.flags.DurationVar(&c.interval, "interval", debugInterval,
fmt.Sprintf("The interval in which to capture dynamic information such as "+
"telemetry, and profiling. Defaults to %s.", debugInterval))
c.flags.DurationVar(&c.duration, "duration", debugDuration,
fmt.Sprintf("The total time to record information. "+
"Defaults to %s.", debugDuration))
c.flags.BoolVar(&c.archive, "archive", true, "Boolean value for if the files "+
"should be archived and compressed. Setting this to false will skip the "+
"archive step and leave the directory of information on the current path.")
c.flags.StringVar(&c.output, "output", defaultFilename, "The path "+
"to the compressed archive that will be created with the "+
"information after collection.")
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
c.help = flags.Usage(help, c.flags)
c.validateTiming = true
}
func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil {
c.UI.Error(fmt.Sprintf("Error parsing flags: %s", err))
return 1
}
if len(c.flags.Args()) > 0 {
c.UI.Error("debug: Too many arguments provided, expected 0")
return 1
}
// Connect to the agent
client, err := c.http.APIClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
}
c.client = client
version, err := c.prepare()
if err != nil {
c.UI.Error(fmt.Sprintf("Capture validation failed: %v", err))
return 1
}
archiveName := c.output
// Show the user the final file path if archiving
if c.archive {
archiveName = archiveName + debugArchiveExtension
}
c.UI.Output("Starting debugger and capturing static information...")
// Output metadata about target agent
c.UI.Info(fmt.Sprintf(" Agent Version: '%s'", version))
c.UI.Info(fmt.Sprintf(" Interval: '%s'", c.interval))
c.UI.Info(fmt.Sprintf(" Duration: '%s'", c.duration))
c.UI.Info(fmt.Sprintf(" Output: '%s'", archiveName))
c.UI.Info(fmt.Sprintf(" Capture: '%s'", strings.Join(c.capture, ", ")))
// Record some information for the index at the root of the archive
index := &debugIndex{
Version: debugProtocolVersion,
AgentVersion: version,
Interval: c.interval.String(),
Duration: c.duration.String(),
Targets: c.capture,
}
// Add the extra grace period to ensure
// all intervals will be captured within the time allotted
c.duration = c.duration + debugDurationGrace
// Capture static information from the target agent
err = c.captureStatic()
if err != nil {
c.UI.Warn(fmt.Sprintf("Static capture failed: %v", err))
}
// Capture dynamic information from the target agent, blocking for duration
if c.configuredTarget("metrics") || c.configuredTarget("logs") || c.configuredTarget("pprof") {
err = c.captureDynamic()
if err != nil {
c.UI.Error(fmt.Sprintf("Error encountered during collection: %v", err))
}
}
// Write the index document
idxMarshalled, err := json.MarshalIndent(index, "", "\t")
if err != nil {
c.UI.Error(fmt.Sprintf("Error marshalling index document: %v", err))
return 1
}
err = ioutil.WriteFile(fmt.Sprintf("%s/index.json", c.output), idxMarshalled, 0644)
if err != nil {
c.UI.Error(fmt.Sprintf("Error creating index document: %v", err))
return 1
}
// Archive the data if configured to
if c.archive {
err = c.createArchive()
if err != nil {
c.UI.Warn(fmt.Sprintf("Archive creation failed: %v", err))
return 1
}
}
c.UI.Info(fmt.Sprintf("Saved debug archive: %s", archiveName))
return 0
}
// prepare validates agent settings against targets and prepares the environment for capturing
func (c *cmd) prepare() (version string, err error) {
// Ensure realistic duration and intervals exists
if c.validateTiming {
if c.duration < debugMinDuration {
return "", fmt.Errorf("duration must be longer than %s", debugMinDuration)
}
if c.interval < debugMinInterval {
return "", fmt.Errorf("interval must be longer than %s", debugMinDuration)
}
if c.duration < c.interval {
return "", fmt.Errorf("duration (%s) must be longer than interval (%s)", c.duration, c.interval)
}
}
// Retrieve and process agent information necessary to validate
self, err := c.client.Agent().Self()
if err != nil {
return "", fmt.Errorf("error querying target agent: %s. verify connectivity and agent address", err)
}
version, ok := self["Config"]["Version"].(string)
if !ok {
return "", fmt.Errorf("agent response did not contain version key")
}
debugEnabled, ok := self["DebugConfig"]["EnableDebug"].(bool)
if !ok {
return version, fmt.Errorf("agent response did not contain debug key")
}
// If none are specified we will collect information from
// all by default
if len(c.capture) == 0 {
c.capture = c.defaultTargets()
}
if !debugEnabled && c.configuredTarget("pprof") {
cs := c.capture
for i := 0; i < len(cs); i++ {
if cs[i] == "pprof" {
c.capture = append(cs[:i], cs[i+1:]...)
i--
}
}
c.UI.Warn("[WARN] Unable to capture pprof. Set enable_debug to true on target agent to enable profiling.")
}
for _, t := range c.capture {
if !c.allowedTarget(t) {
return version, fmt.Errorf("target not found: %s", t)
}
}
if _, err := os.Stat(c.output); os.IsNotExist(err) {
err := os.MkdirAll(c.output, 0755)
if err != nil {
return version, fmt.Errorf("could not create output directory: %s", err)
}
} else {
return version, fmt.Errorf("output directory already exists: %s", c.output)
}
return version, nil
}
// captureStatic captures static target information and writes it
// to the output path
func (c *cmd) captureStatic() error {
// Collect errors via multierror as we want to gracefully
// fail if an API is inacessible
var errors error
// Collect the named outputs here
outputs := make(map[string]interface{}, 0)
// Capture host information
if c.configuredTarget("host") {
host, err := c.client.Agent().Host()
if err != nil {
errors = multierror.Append(errors, err)
}
outputs["host"] = host
}
// Capture agent information
if c.configuredTarget("agent") {
agent, err := c.client.Agent().Self()
if err != nil {
errors = multierror.Append(errors, err)
}
outputs["agent"] = agent
}
// Capture cluster members information, including WAN
if c.configuredTarget("cluster") {
members, err := c.client.Agent().Members(true)
if err != nil {
errors = multierror.Append(errors, err)
}
outputs["cluster"] = members
}
// Write all outputs to disk as JSON
for output, v := range outputs {
marshaled, err := json.MarshalIndent(v, "", "\t")
if err != nil {
errors = multierror.Append(errors, err)
}
err = ioutil.WriteFile(fmt.Sprintf("%s/%s.json", c.output, output), marshaled, 0644)
if err != nil {
errors = multierror.Append(errors, err)
}
}
return errors
}
// captureDynamic blocks for the duration of the command
// specified by the duration flag, capturing the dynamic
// targets at the interval specified
func (c *cmd) captureDynamic() error {
successChan := make(chan int64)
errCh := make(chan error)
durationChn := time.After(c.duration)
intervalCount := 0
c.UI.Output(fmt.Sprintf("Beginning capture interval %s (%d)", time.Now().Local().String(), intervalCount))
// We'll wait for all of the targets configured to be
// captured before continuing
var wg sync.WaitGroup
capture := func() {
timestamp := time.Now().Local().Unix()
// Make the directory that will store all captured data
// for this interval
timestampDir := fmt.Sprintf("%s/%d", c.output, timestamp)
err := os.MkdirAll(timestampDir, 0755)
if err != nil {
errCh <- err
}
// Capture metrics
if c.configuredTarget("metrics") {
wg.Add(1)
go func() {
metrics, err := c.client.Agent().Metrics()
if err != nil {
errCh <- err
}
marshaled, err := json.MarshalIndent(metrics, "", "\t")
if err != nil {
errCh <- err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/%s.json", timestampDir, "metrics"), marshaled, 0644)
if err != nil {
errCh <- err
}
// We need to sleep for the configured interval in the case
// of metrics being the only target captured. When it is,
// the waitgroup would return on Wait() and repeat without
// waiting for the interval.
time.Sleep(c.interval)
wg.Done()
}()
}
// Capture pprof
if c.configuredTarget("pprof") {
wg.Add(1)
go func() {
// We need to capture profiles and traces at the same time
// and block for both of them
var wgProf sync.WaitGroup
heap, err := c.client.Debug().Heap()
if err != nil {
errCh <- err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/heap.prof", timestampDir), heap, 0644)
if err != nil {
errCh <- err
}
// Capture a profile/trace with a minimum of 1s
s := c.interval.Seconds()
if s < 1 {
s = 1
}
go func() {
wgProf.Add(1)
prof, err := c.client.Debug().Profile(int(s))
if err != nil {
errCh <- err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/profile.prof", timestampDir), prof, 0644)
if err != nil {
errCh <- err
}
wgProf.Done()
}()
go func() {
wgProf.Add(1)
trace, err := c.client.Debug().Trace(int(s))
if err != nil {
errCh <- err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/trace.out", timestampDir), trace, 0644)
if err != nil {
errCh <- err
}
wgProf.Done()
}()
gr, err := c.client.Debug().Goroutine()
if err != nil {
errCh <- err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/goroutine.prof", timestampDir), gr, 0644)
if err != nil {
errCh <- err
}
wgProf.Wait()
wg.Done()
}()
}
// Capture logs
if c.configuredTarget("logs") {
wg.Add(1)
go func() {
endLogChn := make(chan struct{})
logCh, err := c.client.Agent().Monitor("DEBUG", endLogChn, nil)
if err != nil {
errCh <- err
}
// Close the log stream
defer close(endLogChn)
// Create the log file for writing
f, err := os.Create(fmt.Sprintf("%s/%s", timestampDir, "consul.log"))
if err != nil {
errCh <- err
}
defer f.Close()
intervalChn := time.After(c.interval)
OUTER:
for {
select {
case log := <-logCh:
// Append the line to the file
if _, err = f.WriteString(log + "\n"); err != nil {
errCh <- err
break OUTER
}
// Stop collecting the logs after the interval specified
case <-intervalChn:
break OUTER
}
}
wg.Done()
}()
}
// Wait for all captures to complete
wg.Wait()
// Send down the timestamp for UI output
successChan <- timestamp
}
go capture()
for {
select {
case t := <-successChan:
intervalCount++
c.UI.Output(fmt.Sprintf("Capture successful %s (%d)", time.Unix(t, 0).Local().String(), intervalCount))
go capture()
case e := <-errCh:
c.UI.Error(fmt.Sprintf("Capture failure %s", e))
case <-durationChn:
return nil
case <-c.shutdownCh:
return errors.New("stopping collection due to shutdown signal")
}
}
}
// allowedTarget returns a boolean if the target is able to be captured
func (c *cmd) allowedTarget(target string) bool {
for _, dt := range c.defaultTargets() {
if dt == target {
return true
}
}
return false
}
// configuredTarget returns a boolean if the target is configured to be
// captured in the command
func (c *cmd) configuredTarget(target string) bool {
for _, dt := range c.capture {
if dt == target {
return true
}
}
return false
}
// createArchive walks the files in the temporary directory
// and creates a tar file that is gzipped with the contents
func (c *cmd) createArchive() error {
f, err := os.Create(c.output + debugArchiveExtension)
if err != nil {
return fmt.Errorf("failed to create compressed archive: %s", err)
}
defer f.Close()
g := gzip.NewWriter(f)
defer g.Close()
t := tar.NewWriter(f)
defer t.Close()
err = filepath.Walk(c.output, func(file string, fi os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk filepath for archive: %s", err)
}
header, err := tar.FileInfoHeader(fi, fi.Name())
if err != nil {
return fmt.Errorf("failed to create compressed archive header: %s", err)
}
header.Name = filepath.Join(filepath.Base(c.output), strings.TrimPrefix(file, c.output))
if err := t.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write compressed archive header: %s", err)
}
// Only copy files
if !fi.Mode().IsRegular() {
return nil
}
f, err := os.Open(file)
if err != nil {
return fmt.Errorf("failed to open target files for archive: %s", err)
}
if _, err := io.Copy(t, f); err != nil {
return fmt.Errorf("failed to copy files for archive: %s", err)
}
f.Close()
return nil
})
if err != nil {
return fmt.Errorf("failed to walk output path for archive: %s", err)
}
// Remove directory that has been archived
err = os.RemoveAll(c.output)
if err != nil {
return fmt.Errorf("failed to remove archived directory: %s", err)
}
return nil
}
// defaultTargets specifies the list of all targets that
// will be captured by default
func (c *cmd) defaultTargets() []string {
return append(c.dynamicTargets(), c.staticTargets()...)
}
// dynamicTargets returns all the supported targets
// that are retrieved at the interval specified
func (c *cmd) dynamicTargets() []string {
return []string{"metrics", "logs", "pprof"}
}
// staticTargets returns all the supported targets
// that are retrieved at the start of the command execution
func (c *cmd) staticTargets() []string {
return []string{"host", "agent", "cluster"}
}
func (c *cmd) Synopsis() string {
return synopsis
}
func (c *cmd) Help() string {
return c.help
}
const synopsis = "Records a debugging archive for operators"
const help = `
Usage: consul debug [options]
Monitors a Consul agent for the specified period of time, recording
information about the agent, cluster, and environment to an archive
written to the specified path.
If ACLs are enabled, an 'operator:read' token must be supplied in order
to perform this operation.
To create a debug archive in the current directory for the default
duration and interval, capturing all information available:
$ consul debug
The command stores captured data at the configured output path
through the duration, and will archive the data at the same
path if interrupted.
Flags can be used to customize the duration and interval of the
operation. Duration is the total time to capture data for from the target
agent and interval controls how often dynamic data such as metrics
are scraped.
$ consul debug -interval=20s -duration=1m
The capture flag can be specified multiple times to limit information
retrieved.
$ consul debug -capture metrics -capture agent
By default, the archive containing the debugging information is
saved to the current directory as a .tar.gz file. The
output path can be specified, as well as an option to disable
archiving, leaving the directory intact.
$ consul debug -output=/foo/bar/my-debugging -archive=false
Note: Information collected by this command has the potential
to be highly sensitive. Sensitive material such as ACL tokens and
other commonly secret material are redacted automatically, but we
strongly recommend review of the data within the archive prior to
transmitting it.
For a full list of options and examples, please see the Consul
documentation.
`

482
command/debug/debug_test.go Normal file
View File

@ -0,0 +1,482 @@
package debug
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/logger"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/consul/testutil"
"github.com/mitchellh/cli"
)
func TestDebugCommand_noTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(cli.NewMockUi(), nil).Help(), '\t') {
t.Fatal("help has tabs")
}
}
func TestDebugCommand(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), `
enable_debug = true
`)
a.Agent.LogWriter = logger.NewLogWriter(512)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug", testDir)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-duration=100ms",
"-interval=50ms",
}
code := cmd.Run(args)
if code != 0 {
t.Errorf("should exit 0, got code: %d", code)
}
errOutput := ui.ErrorWriter.String()
if errOutput != "" {
t.Errorf("expected no error output, got %q", errOutput)
}
}
func TestDebugCommand_Archive(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), `
enable_debug = true
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug", testDir)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-capture=agent",
}
if code := cmd.Run(args); code != 0 {
t.Fatalf("should exit 0, got code: %d", code)
}
archivePath := fmt.Sprintf("%s%s", outputPath, debugArchiveExtension)
file, err := os.Open(archivePath)
if err != nil {
t.Fatalf("failed to open archive: %s", err)
}
tr := tar.NewReader(file)
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("failed to read file in archive: %s", err)
}
// ignore the outer directory
if h.Name == "debug" {
continue
}
// should only contain this one capture target
if h.Name != "debug/agent.json" && h.Name != "debug/index.json" {
t.Fatalf("archive contents do not match: %s", h.Name)
}
}
}
func TestDebugCommand_ArgsBad(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
ui := cli.NewMockUi()
cmd := New(ui, nil)
args := []string{
"foo",
"bad",
}
if code := cmd.Run(args); code == 0 {
t.Fatalf("should exit non-zero, got code: %d", code)
}
errOutput := ui.ErrorWriter.String()
if !strings.Contains(errOutput, "Too many arguments") {
t.Errorf("expected error output, got %q", errOutput)
}
}
func TestDebugCommand_OutputPathBad(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := ""
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-duration=100ms",
"-interval=50ms",
}
if code := cmd.Run(args); code == 0 {
t.Fatalf("should exit non-zero, got code: %d", code)
}
errOutput := ui.ErrorWriter.String()
if !strings.Contains(errOutput, "no such file or directory") {
t.Errorf("expected error output, got %q", errOutput)
}
}
func TestDebugCommand_OutputPathExists(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), "")
a.Agent.LogWriter = logger.NewLogWriter(512)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug", testDir)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-duration=100ms",
"-interval=50ms",
}
// Make a directory that conflicts with the output path
err := os.Mkdir(outputPath, 0755)
if err != nil {
t.Fatalf("duplicate test directory creation failed: %s", err)
}
if code := cmd.Run(args); code == 0 {
t.Fatalf("should exit non-zero, got code: %d", code)
}
errOutput := ui.ErrorWriter.String()
if !strings.Contains(errOutput, "directory already exists") {
t.Errorf("expected error output, got %q", errOutput)
}
}
func TestDebugCommand_CaptureTargets(t *testing.T) {
t.Parallel()
cases := map[string]struct {
// used in -target param
targets []string
// existence verified after execution
files []string
// non-existence verified after execution
excludedFiles []string
}{
"single": {
[]string{"agent"},
[]string{"agent.json"},
[]string{"host.json", "cluster.json"},
},
"static": {
[]string{"agent", "host", "cluster"},
[]string{"agent.json", "host.json", "cluster.json"},
[]string{"*/metrics.json"},
},
"metrics-only": {
[]string{"metrics"},
[]string{"*/metrics.json"},
[]string{"agent.json", "host.json", "cluster.json"},
},
"all-but-pprof": {
[]string{
"metrics",
"logs",
"host",
"agent",
"cluster",
},
[]string{
"host.json",
"agent.json",
"cluster.json",
"*/metrics.json",
"*/consul.log",
},
[]string{},
},
}
for name, tc := range cases {
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), `
enable_debug = true
`)
a.Agent.LogWriter = logger.NewLogWriter(512)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug-%s", testDir, name)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-archive=false",
"-duration=100ms",
"-interval=50ms",
}
for _, t := range tc.targets {
args = append(args, "-capture="+t)
}
if code := cmd.Run(args); code != 0 {
t.Fatalf("should exit 0, got code: %d", code)
}
errOutput := ui.ErrorWriter.String()
if errOutput != "" {
t.Errorf("expected no error output, got %q", errOutput)
}
// Ensure the debug data was written
_, err := os.Stat(outputPath)
if err != nil {
t.Fatalf("output path should exist: %s", err)
}
// Ensure the captured static files exist
for _, f := range tc.files {
path := fmt.Sprintf("%s/%s", outputPath, f)
// Glob ignores file system errors
fs, _ := filepath.Glob(path)
if len(fs) <= 0 {
t.Fatalf("%s: output data should exist for %s", name, f)
}
}
// Ensure any excluded files do not exist
for _, f := range tc.excludedFiles {
path := fmt.Sprintf("%s/%s", outputPath, f)
// Glob ignores file system errors
fs, _ := filepath.Glob(path)
if len(fs) > 0 {
t.Fatalf("%s: output data should not exist for %s", name, f)
}
}
}
}
func TestDebugCommand_ProfilesExist(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), `
enable_debug = true
`)
a.Agent.LogWriter = logger.NewLogWriter(512)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug", testDir)
println(outputPath)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
// CPU profile has a minimum of 1s
"-archive=false",
"-duration=1s",
"-interval=1s",
"-capture=pprof",
}
if code := cmd.Run(args); code != 0 {
t.Fatalf("should exit 0, got code: %d", code)
}
profiles := []string{"heap.prof", "profile.prof", "goroutine.prof", "trace.out"}
// Glob ignores file system errors
for _, v := range profiles {
fs, _ := filepath.Glob(fmt.Sprintf("%s/*/%s", outputPath, v))
if len(fs) == 0 {
t.Errorf("output data should exist for %s", v)
}
}
errOutput := ui.ErrorWriter.String()
if errOutput != "" {
t.Errorf("expected no error output, got %s", errOutput)
}
}
func TestDebugCommand_ValidateTiming(t *testing.T) {
t.Parallel()
cases := map[string]struct {
duration string
interval string
output string
code int
}{
"both": {
"20ms",
"10ms",
"duration must be longer",
1,
},
"short interval": {
"10s",
"10ms",
"interval must be longer",
1,
},
"lower duration": {
"20s",
"30s",
"must be longer than interval",
1,
},
}
for name, tc := range cases {
// Because we're only testng validation, we want to shut down
// the valid duration test to avoid hanging
shutdownCh := make(chan struct{})
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, shutdownCh)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-duration=" + tc.duration,
"-interval=" + tc.interval,
"-capture=agent",
}
code := cmd.Run(args)
if code != tc.code {
t.Errorf("%s: should exit %d, got code: %d", name, tc.code, code)
}
errOutput := ui.ErrorWriter.String()
if !strings.Contains(errOutput, tc.output) {
t.Errorf("%s: expected error output '%s', got '%q'", name, tc.output, errOutput)
}
}
}
func TestDebugCommand_DebugDisabled(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "debug")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t.Name(), `
enable_debug = false
`)
a.Agent.LogWriter = logger.NewLogWriter(512)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui, nil)
cmd.validateTiming = false
outputPath := fmt.Sprintf("%s/debug", testDir)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-output=" + outputPath,
"-archive=false",
// CPU profile has a minimum of 1s
"-duration=1s",
"-interval=1s",
}
if code := cmd.Run(args); code != 0 {
t.Fatalf("should exit 0, got code: %d", code)
}
profiles := []string{"heap.prof", "profile.prof", "goroutine.prof", "trace.out"}
// Glob ignores file system errors
for _, v := range profiles {
fs, _ := filepath.Glob(fmt.Sprintf("%s/*/%s", outputPath, v))
if len(fs) > 0 {
t.Errorf("output data should not exist for %s", v)
}
}
errOutput := ui.ErrorWriter.String()
if !strings.Contains(errOutput, "Unable to capture pprof") {
t.Errorf("expected warn output, got %s", errOutput)
}
}

View File

@ -98,6 +98,7 @@ type TestServerConfig struct {
VerifyOutgoing bool `json:"verify_outgoing,omitempty"`
EnableScriptChecks bool `json:"enable_script_checks,omitempty"`
Connect map[string]interface{} `json:"connect,omitempty"`
EnableDebug bool `json:"enable_debug,omitempty"`
ReadyTimeout time.Duration `json:"-"`
Stdout, Stderr io.Writer `json:"-"`
Args []string `json:"-"`

61
vendor/github.com/shirou/gopsutil/LICENSE generated vendored Normal file
View File

@ -0,0 +1,61 @@
gopsutil is distributed under BSD license reproduced below.
Copyright (c) 2014, WAKAYAMA Shirou
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the gopsutil authors nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------
internal/common/binary.go in the gopsutil is copied and modifid from golang/encoding/binary.go.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

64
vendor/github.com/shirou/gopsutil/disk/disk.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package disk
import (
"encoding/json"
"github.com/shirou/gopsutil/internal/common"
)
var invoke common.Invoker
func init() {
invoke = common.Invoke{}
}
type UsageStat struct {
Path string `json:"path"`
Fstype string `json:"fstype"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
InodesTotal uint64 `json:"inodesTotal"`
InodesUsed uint64 `json:"inodesUsed"`
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
}
type PartitionStat struct {
Device string `json:"device"`
Mountpoint string `json:"mountpoint"`
Fstype string `json:"fstype"`
Opts string `json:"opts"`
}
type IOCountersStat struct {
ReadCount uint64 `json:"readCount"`
MergedReadCount uint64 `json:"mergedReadCount"`
WriteCount uint64 `json:"writeCount"`
MergedWriteCount uint64 `json:"mergedWriteCount"`
ReadBytes uint64 `json:"readBytes"`
WriteBytes uint64 `json:"writeBytes"`
ReadTime uint64 `json:"readTime"`
WriteTime uint64 `json:"writeTime"`
IopsInProgress uint64 `json:"iopsInProgress"`
IoTime uint64 `json:"ioTime"`
WeightedIO uint64 `json:"weightedIO"`
Name string `json:"name"`
SerialNumber string `json:"serialNumber"`
}
func (d UsageStat) String() string {
s, _ := json.Marshal(d)
return string(s)
}
func (d PartitionStat) String() string {
s, _ := json.Marshal(d)
return string(s)
}
func (d IOCountersStat) String() string {
s, _ := json.Marshal(d)
return string(s)
}

111
vendor/github.com/shirou/gopsutil/disk/disk_darwin.go generated vendored Normal file
View File

@ -0,0 +1,111 @@
// +build darwin
package disk
import (
"path"
"syscall"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
)
func Partitions(all bool) ([]PartitionStat, error) {
var ret []PartitionStat
count, err := Getfsstat(nil, MntWait)
if err != nil {
return ret, err
}
fs := make([]Statfs_t, count)
_, err = Getfsstat(fs, MntWait)
for _, stat := range fs {
opts := "rw"
if stat.Flags&MntReadOnly != 0 {
opts = "ro"
}
if stat.Flags&MntSynchronous != 0 {
opts += ",sync"
}
if stat.Flags&MntNoExec != 0 {
opts += ",noexec"
}
if stat.Flags&MntNoSuid != 0 {
opts += ",nosuid"
}
if stat.Flags&MntUnion != 0 {
opts += ",union"
}
if stat.Flags&MntAsync != 0 {
opts += ",async"
}
if stat.Flags&MntSuidDir != 0 {
opts += ",suiddir"
}
if stat.Flags&MntSoftDep != 0 {
opts += ",softdep"
}
if stat.Flags&MntNoSymFollow != 0 {
opts += ",nosymfollow"
}
if stat.Flags&MntGEOMJournal != 0 {
opts += ",gjounalc"
}
if stat.Flags&MntMultilabel != 0 {
opts += ",multilabel"
}
if stat.Flags&MntACLs != 0 {
opts += ",acls"
}
if stat.Flags&MntNoATime != 0 {
opts += ",noattime"
}
if stat.Flags&MntClusterRead != 0 {
opts += ",nocluster"
}
if stat.Flags&MntClusterWrite != 0 {
opts += ",noclusterw"
}
if stat.Flags&MntNFS4ACLs != 0 {
opts += ",nfs4acls"
}
d := PartitionStat{
Device: common.IntToString(stat.Mntfromname[:]),
Mountpoint: common.IntToString(stat.Mntonname[:]),
Fstype: common.IntToString(stat.Fstypename[:]),
Opts: opts,
}
if all == false {
if !path.IsAbs(d.Device) || !common.PathExists(d.Device) {
continue
}
}
ret = append(ret, d)
}
return ret, nil
}
func IOCounters() (map[string]IOCountersStat, error) {
return nil, common.ErrNotImplementedError
}
func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := syscall.Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func getFsType(stat syscall.Statfs_t) string {
return common.IntToString(stat.Fstypename[:])
}

View File

@ -0,0 +1,58 @@
// +build darwin
// +build amd64
package disk
const (
MntWait = 1
MfsNameLen = 15 /* length of fs type name, not inc. nul */
MNameLen = 90 /* length of buffer for returned name */
MFSTYPENAMELEN = 16 /* length of fs type name including null */
MAXPATHLEN = 1024
MNAMELEN = MAXPATHLEN
SYS_GETFSSTAT64 = 347
)
type Fsid struct{ val [2]int32 } /* file system id type */
type uid_t int32
// sys/mount.h
const (
MntReadOnly = 0x00000001 /* read only filesystem */
MntSynchronous = 0x00000002 /* filesystem written synchronously */
MntNoExec = 0x00000004 /* can't exec from filesystem */
MntNoSuid = 0x00000008 /* don't honor setuid bits on fs */
MntUnion = 0x00000020 /* union with underlying filesystem */
MntAsync = 0x00000040 /* filesystem written asynchronously */
MntSuidDir = 0x00100000 /* special handling of SUID on dirs */
MntSoftDep = 0x00200000 /* soft updates being done */
MntNoSymFollow = 0x00400000 /* do not follow symlinks */
MntGEOMJournal = 0x02000000 /* GEOM journal support enabled */
MntMultilabel = 0x04000000 /* MAC support for individual objects */
MntACLs = 0x08000000 /* ACL support enabled */
MntNoATime = 0x10000000 /* disable update of file access time */
MntClusterRead = 0x40000000 /* disable cluster read */
MntClusterWrite = 0x80000000 /* disable cluster write */
MntNFS4ACLs = 0x00000010
)
type Statfs_t struct {
Bsize uint32
Iosize int32
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Fsid Fsid
Owner uint32
Type uint32
Flags uint32
Fssubtype uint32
Fstypename [16]int8
Mntonname [1024]int8
Mntfromname [1024]int8
Reserved [8]uint32
}

View File

@ -0,0 +1,58 @@
// +build darwin
// +build arm64
package disk
const (
MntWait = 1
MfsNameLen = 15 /* length of fs type name, not inc. nul */
MNameLen = 90 /* length of buffer for returned name */
MFSTYPENAMELEN = 16 /* length of fs type name including null */
MAXPATHLEN = 1024
MNAMELEN = MAXPATHLEN
SYS_GETFSSTAT64 = 347
)
type Fsid struct{ val [2]int32 } /* file system id type */
type uid_t int32
// sys/mount.h
const (
MntReadOnly = 0x00000001 /* read only filesystem */
MntSynchronous = 0x00000002 /* filesystem written synchronously */
MntNoExec = 0x00000004 /* can't exec from filesystem */
MntNoSuid = 0x00000008 /* don't honor setuid bits on fs */
MntUnion = 0x00000020 /* union with underlying filesystem */
MntAsync = 0x00000040 /* filesystem written asynchronously */
MntSuidDir = 0x00100000 /* special handling of SUID on dirs */
MntSoftDep = 0x00200000 /* soft updates being done */
MntNoSymFollow = 0x00400000 /* do not follow symlinks */
MntGEOMJournal = 0x02000000 /* GEOM journal support enabled */
MntMultilabel = 0x04000000 /* MAC support for individual objects */
MntACLs = 0x08000000 /* ACL support enabled */
MntNoATime = 0x10000000 /* disable update of file access time */
MntClusterRead = 0x40000000 /* disable cluster read */
MntClusterWrite = 0x80000000 /* disable cluster write */
MntNFS4ACLs = 0x00000010
)
type Statfs_t struct {
Bsize uint32
Iosize int32
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Fsid Fsid
Owner uint32
Type uint32
Flags uint32
Fssubtype uint32
Fstypename [16]int8
Mntonname [1024]int8
Mntfromname [1024]int8
Reserved [8]uint32
}

View File

@ -0,0 +1,17 @@
// +build !darwin,!linux,!freebsd,!openbsd,!windows
package disk
import "github.com/shirou/gopsutil/internal/common"
func IOCounters() (map[string]IOCountersStat, error) {
return nil, common.ErrNotImplementedError
}
func Partitions(all bool) ([]PartitionStat, error) {
return []PartitionStat{}, common.ErrNotImplementedError
}
func Usage(path string) (*UsageStat, error) {
return nil, common.ErrNotImplementedError
}

176
vendor/github.com/shirou/gopsutil/disk/disk_freebsd.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
// +build freebsd
package disk
import (
"bytes"
"encoding/binary"
"path"
"strconv"
"syscall"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
)
func Partitions(all bool) ([]PartitionStat, error) {
var ret []PartitionStat
// get length
count, err := syscall.Getfsstat(nil, MNT_WAIT)
if err != nil {
return ret, err
}
fs := make([]Statfs, count)
_, err = Getfsstat(fs, MNT_WAIT)
for _, stat := range fs {
opts := "rw"
if stat.Flags&MNT_RDONLY != 0 {
opts = "ro"
}
if stat.Flags&MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.Flags&MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.Flags&MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.Flags&MNT_UNION != 0 {
opts += ",union"
}
if stat.Flags&MNT_ASYNC != 0 {
opts += ",async"
}
if stat.Flags&MNT_SUIDDIR != 0 {
opts += ",suiddir"
}
if stat.Flags&MNT_SOFTDEP != 0 {
opts += ",softdep"
}
if stat.Flags&MNT_NOSYMFOLLOW != 0 {
opts += ",nosymfollow"
}
if stat.Flags&MNT_GJOURNAL != 0 {
opts += ",gjounalc"
}
if stat.Flags&MNT_MULTILABEL != 0 {
opts += ",multilabel"
}
if stat.Flags&MNT_ACLS != 0 {
opts += ",acls"
}
if stat.Flags&MNT_NOATIME != 0 {
opts += ",noattime"
}
if stat.Flags&MNT_NOCLUSTERR != 0 {
opts += ",nocluster"
}
if stat.Flags&MNT_NOCLUSTERW != 0 {
opts += ",noclusterw"
}
if stat.Flags&MNT_NFS4ACLS != 0 {
opts += ",nfs4acls"
}
d := PartitionStat{
Device: common.IntToString(stat.Mntfromname[:]),
Mountpoint: common.IntToString(stat.Mntonname[:]),
Fstype: common.IntToString(stat.Fstypename[:]),
Opts: opts,
}
if all == false {
if !path.IsAbs(d.Device) || !common.PathExists(d.Device) {
continue
}
}
ret = append(ret, d)
}
return ret, nil
}
func IOCounters() (map[string]IOCountersStat, error) {
// statinfo->devinfo->devstat
// /usr/include/devinfo.h
ret := make(map[string]IOCountersStat)
r, err := syscall.Sysctl("kern.devstat.all")
if err != nil {
return nil, err
}
buf := []byte(r)
length := len(buf)
count := int(uint64(length) / uint64(sizeOfDevstat))
buf = buf[8:] // devstat.all has version in the head.
// parse buf to Devstat
for i := 0; i < count; i++ {
b := buf[i*sizeOfDevstat : i*sizeOfDevstat+sizeOfDevstat]
d, err := parseDevstat(b)
if err != nil {
continue
}
un := strconv.Itoa(int(d.Unit_number))
name := common.IntToString(d.Device_name[:]) + un
ds := IOCountersStat{
ReadCount: d.Operations[DEVSTAT_READ],
WriteCount: d.Operations[DEVSTAT_WRITE],
ReadBytes: d.Bytes[DEVSTAT_READ],
WriteBytes: d.Bytes[DEVSTAT_WRITE],
ReadTime: uint64(d.Duration[DEVSTAT_READ].Compute() * 1000),
WriteTime: uint64(d.Duration[DEVSTAT_WRITE].Compute() * 1000),
IoTime: uint64(d.Busy_time.Compute() * 1000),
Name: name,
}
ret[name] = ds
}
return ret, nil
}
func (b Bintime) Compute() float64 {
BINTIME_SCALE := 5.42101086242752217003726400434970855712890625e-20
return float64(b.Sec) + float64(b.Frac)*BINTIME_SCALE
}
// BT2LD(time) ((long double)(time).sec + (time).frac * BINTIME_SCALE)
// Getfsstat is borrowed from pkg/syscall/syscall_freebsd.go
// change Statfs_t to Statfs in order to get more information
func Getfsstat(buf []Statfs, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf))
}
r0, _, e1 := syscall.Syscall(syscall.SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func parseDevstat(buf []byte) (Devstat, error) {
var ds Devstat
br := bytes.NewReader(buf)
// err := binary.Read(br, binary.LittleEndian, &ds)
err := common.Read(br, binary.LittleEndian, &ds)
if err != nil {
return ds, err
}
return ds, nil
}
func getFsType(stat syscall.Statfs_t) string {
return common.IntToString(stat.Fstypename[:])
}

View File

@ -0,0 +1,112 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package disk
const (
sizeofPtr = 0x4
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x4
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_UNION = 0x00000020
MNT_ASYNC = 0x00000040
MNT_SUIDDIR = 0x00100000
MNT_SOFTDEP = 0x00200000
MNT_NOSYMFOLLOW = 0x00400000
MNT_GJOURNAL = 0x02000000
MNT_MULTILABEL = 0x04000000
MNT_ACLS = 0x08000000
MNT_NOATIME = 0x10000000
MNT_NOCLUSTERR = 0x40000000
MNT_NOCLUSTERW = 0x80000000
MNT_NFS4ACLS = 0x00000010
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
MNT_SUSPEND = 4
)
const (
sizeOfDevstat = 0xf0
)
type (
_C_short int16
_C_int int32
_C_long int32
_C_long_long int64
_C_long_double int64
)
type Statfs struct {
Version uint32
Type uint32
Flags uint64
Bsize uint64
Iosize uint64
Blocks uint64
Bfree uint64
Bavail int64
Files uint64
Ffree int64
Syncwrites uint64
Asyncwrites uint64
Syncreads uint64
Asyncreads uint64
Spare [10]uint64
Namemax uint32
Owner uint32
Fsid Fsid
Charspare [80]int8
Fstypename [16]int8
Mntfromname [88]int8
Mntonname [88]int8
}
type Fsid struct {
Val [2]int32
}
type Devstat struct {
Sequence0 uint32
Allocated int32
Start_count uint32
End_count uint32
Busy_from Bintime
Dev_links _Ctype_struct___0
Device_number uint32
Device_name [16]int8
Unit_number int32
Bytes [4]uint64
Operations [4]uint64
Duration [4]Bintime
Busy_time Bintime
Creation_time Bintime
Block_size uint32
Tag_types [3]uint64
Flags uint32
Device_type uint32
Priority uint32
Id *byte
Sequence1 uint32
}
type Bintime struct {
Sec int32
Frac uint64
}
type _Ctype_struct___0 struct {
Empty uint32
}

View File

@ -0,0 +1,115 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package disk
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_UNION = 0x00000020
MNT_ASYNC = 0x00000040
MNT_SUIDDIR = 0x00100000
MNT_SOFTDEP = 0x00200000
MNT_NOSYMFOLLOW = 0x00400000
MNT_GJOURNAL = 0x02000000
MNT_MULTILABEL = 0x04000000
MNT_ACLS = 0x08000000
MNT_NOATIME = 0x10000000
MNT_NOCLUSTERR = 0x40000000
MNT_NOCLUSTERW = 0x80000000
MNT_NFS4ACLS = 0x00000010
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
MNT_SUSPEND = 4
)
const (
sizeOfDevstat = 0x120
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
_C_long_double int64
)
type Statfs struct {
Version uint32
Type uint32
Flags uint64
Bsize uint64
Iosize uint64
Blocks uint64
Bfree uint64
Bavail int64
Files uint64
Ffree int64
Syncwrites uint64
Asyncwrites uint64
Syncreads uint64
Asyncreads uint64
Spare [10]uint64
Namemax uint32
Owner uint32
Fsid Fsid
Charspare [80]int8
Fstypename [16]int8
Mntfromname [88]int8
Mntonname [88]int8
}
type Fsid struct {
Val [2]int32
}
type Devstat struct {
Sequence0 uint32
Allocated int32
Start_count uint32
End_count uint32
Busy_from Bintime
Dev_links _Ctype_struct___0
Device_number uint32
Device_name [16]int8
Unit_number int32
Bytes [4]uint64
Operations [4]uint64
Duration [4]Bintime
Busy_time Bintime
Creation_time Bintime
Block_size uint32
Pad_cgo_0 [4]byte
Tag_types [3]uint64
Flags uint32
Device_type uint32
Priority uint32
Pad_cgo_1 [4]byte
ID *byte
Sequence1 uint32
Pad_cgo_2 [4]byte
}
type Bintime struct {
Sec int64
Frac uint64
}
type _Ctype_struct___0 struct {
Empty uint64
}

393
vendor/github.com/shirou/gopsutil/disk/disk_linux.go generated vendored Normal file
View File

@ -0,0 +1,393 @@
// +build linux
package disk
import (
"fmt"
"os/exec"
"strconv"
"strings"
"syscall"
"github.com/shirou/gopsutil/internal/common"
)
const (
SectorSize = 512
)
const (
// man statfs
ADFS_SUPER_MAGIC = 0xadf5
AFFS_SUPER_MAGIC = 0xADFF
BDEVFS_MAGIC = 0x62646576
BEFS_SUPER_MAGIC = 0x42465331
BFS_MAGIC = 0x1BADFACE
BINFMTFS_MAGIC = 0x42494e4d
BTRFS_SUPER_MAGIC = 0x9123683E
CGROUP_SUPER_MAGIC = 0x27e0eb
CIFS_MAGIC_NUMBER = 0xFF534D42
CODA_SUPER_MAGIC = 0x73757245
COH_SUPER_MAGIC = 0x012FF7B7
CRAMFS_MAGIC = 0x28cd3d45
DEBUGFS_MAGIC = 0x64626720
DEVFS_SUPER_MAGIC = 0x1373
DEVPTS_SUPER_MAGIC = 0x1cd1
EFIVARFS_MAGIC = 0xde5e81e4
EFS_SUPER_MAGIC = 0x00414A53
EXT_SUPER_MAGIC = 0x137D
EXT2_OLD_SUPER_MAGIC = 0xEF51
EXT2_SUPER_MAGIC = 0xEF53
EXT3_SUPER_MAGIC = 0xEF53
EXT4_SUPER_MAGIC = 0xEF53
FUSE_SUPER_MAGIC = 0x65735546
FUTEXFS_SUPER_MAGIC = 0xBAD1DEA
HFS_SUPER_MAGIC = 0x4244
HOSTFS_SUPER_MAGIC = 0x00c0ffee
HPFS_SUPER_MAGIC = 0xF995E849
HUGETLBFS_MAGIC = 0x958458f6
ISOFS_SUPER_MAGIC = 0x9660
JFFS2_SUPER_MAGIC = 0x72b6
JFS_SUPER_MAGIC = 0x3153464a
MINIX_SUPER_MAGIC = 0x137F /* orig. minix */
MINIX_SUPER_MAGIC2 = 0x138F /* 30 char minix */
MINIX2_SUPER_MAGIC = 0x2468 /* minix V2 */
MINIX2_SUPER_MAGIC2 = 0x2478 /* minix V2, 30 char names */
MINIX3_SUPER_MAGIC = 0x4d5a /* minix V3 fs, 60 char names */
MQUEUE_MAGIC = 0x19800202
MSDOS_SUPER_MAGIC = 0x4d44
NCP_SUPER_MAGIC = 0x564c
NFS_SUPER_MAGIC = 0x6969
NILFS_SUPER_MAGIC = 0x3434
NTFS_SB_MAGIC = 0x5346544e
OCFS2_SUPER_MAGIC = 0x7461636f
OPENPROM_SUPER_MAGIC = 0x9fa1
PIPEFS_MAGIC = 0x50495045
PROC_SUPER_MAGIC = 0x9fa0
PSTOREFS_MAGIC = 0x6165676C
QNX4_SUPER_MAGIC = 0x002f
QNX6_SUPER_MAGIC = 0x68191122
RAMFS_MAGIC = 0x858458f6
REISERFS_SUPER_MAGIC = 0x52654973
ROMFS_MAGIC = 0x7275
SELINUX_MAGIC = 0xf97cff8c
SMACK_MAGIC = 0x43415d53
SMB_SUPER_MAGIC = 0x517B
SOCKFS_MAGIC = 0x534F434B
SQUASHFS_MAGIC = 0x73717368
SYSFS_MAGIC = 0x62656572
SYSV2_SUPER_MAGIC = 0x012FF7B6
SYSV4_SUPER_MAGIC = 0x012FF7B5
TMPFS_MAGIC = 0x01021994
UDF_SUPER_MAGIC = 0x15013346
UFS_MAGIC = 0x00011954
USBDEVICE_SUPER_MAGIC = 0x9fa2
V9FS_MAGIC = 0x01021997
VXFS_SUPER_MAGIC = 0xa501FCF5
XENFS_SUPER_MAGIC = 0xabba1974
XENIX_SUPER_MAGIC = 0x012FF7B4
XFS_SUPER_MAGIC = 0x58465342
_XIAFS_SUPER_MAGIC = 0x012FD16D
AFS_SUPER_MAGIC = 0x5346414F
AUFS_SUPER_MAGIC = 0x61756673
ANON_INODE_FS_SUPER_MAGIC = 0x09041934
CEPH_SUPER_MAGIC = 0x00C36400
ECRYPTFS_SUPER_MAGIC = 0xF15F
FAT_SUPER_MAGIC = 0x4006
FHGFS_SUPER_MAGIC = 0x19830326
FUSEBLK_SUPER_MAGIC = 0x65735546
FUSECTL_SUPER_MAGIC = 0x65735543
GFS_SUPER_MAGIC = 0x1161970
GPFS_SUPER_MAGIC = 0x47504653
MTD_INODE_FS_SUPER_MAGIC = 0x11307854
INOTIFYFS_SUPER_MAGIC = 0x2BAD1DEA
ISOFS_R_WIN_SUPER_MAGIC = 0x4004
ISOFS_WIN_SUPER_MAGIC = 0x4000
JFFS_SUPER_MAGIC = 0x07C0
KAFS_SUPER_MAGIC = 0x6B414653
LUSTRE_SUPER_MAGIC = 0x0BD00BD0
NFSD_SUPER_MAGIC = 0x6E667364
PANFS_SUPER_MAGIC = 0xAAD7AAEA
RPC_PIPEFS_SUPER_MAGIC = 0x67596969
SECURITYFS_SUPER_MAGIC = 0x73636673
UFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100
VMHGFS_SUPER_MAGIC = 0xBACBACBC
VZFS_SUPER_MAGIC = 0x565A4653
ZFS_SUPER_MAGIC = 0x2FC12FC1
)
// coreutils/src/stat.c
var fsTypeMap = map[int64]string{
ADFS_SUPER_MAGIC: "adfs", /* 0xADF5 local */
AFFS_SUPER_MAGIC: "affs", /* 0xADFF local */
AFS_SUPER_MAGIC: "afs", /* 0x5346414F remote */
ANON_INODE_FS_SUPER_MAGIC: "anon-inode FS", /* 0x09041934 local */
AUFS_SUPER_MAGIC: "aufs", /* 0x61756673 remote */
// AUTOFS_SUPER_MAGIC: "autofs", /* 0x0187 local */
BEFS_SUPER_MAGIC: "befs", /* 0x42465331 local */
BDEVFS_MAGIC: "bdevfs", /* 0x62646576 local */
BFS_MAGIC: "bfs", /* 0x1BADFACE local */
BINFMTFS_MAGIC: "binfmt_misc", /* 0x42494E4D local */
BTRFS_SUPER_MAGIC: "btrfs", /* 0x9123683E local */
CEPH_SUPER_MAGIC: "ceph", /* 0x00C36400 remote */
CGROUP_SUPER_MAGIC: "cgroupfs", /* 0x0027E0EB local */
CIFS_MAGIC_NUMBER: "cifs", /* 0xFF534D42 remote */
CODA_SUPER_MAGIC: "coda", /* 0x73757245 remote */
COH_SUPER_MAGIC: "coh", /* 0x012FF7B7 local */
CRAMFS_MAGIC: "cramfs", /* 0x28CD3D45 local */
DEBUGFS_MAGIC: "debugfs", /* 0x64626720 local */
DEVFS_SUPER_MAGIC: "devfs", /* 0x1373 local */
DEVPTS_SUPER_MAGIC: "devpts", /* 0x1CD1 local */
ECRYPTFS_SUPER_MAGIC: "ecryptfs", /* 0xF15F local */
EFS_SUPER_MAGIC: "efs", /* 0x00414A53 local */
EXT_SUPER_MAGIC: "ext", /* 0x137D local */
EXT2_SUPER_MAGIC: "ext2/ext3", /* 0xEF53 local */
EXT2_OLD_SUPER_MAGIC: "ext2", /* 0xEF51 local */
FAT_SUPER_MAGIC: "fat", /* 0x4006 local */
FHGFS_SUPER_MAGIC: "fhgfs", /* 0x19830326 remote */
FUSEBLK_SUPER_MAGIC: "fuseblk", /* 0x65735546 remote */
FUSECTL_SUPER_MAGIC: "fusectl", /* 0x65735543 remote */
FUTEXFS_SUPER_MAGIC: "futexfs", /* 0x0BAD1DEA local */
GFS_SUPER_MAGIC: "gfs/gfs2", /* 0x1161970 remote */
GPFS_SUPER_MAGIC: "gpfs", /* 0x47504653 remote */
HFS_SUPER_MAGIC: "hfs", /* 0x4244 local */
HPFS_SUPER_MAGIC: "hpfs", /* 0xF995E849 local */
HUGETLBFS_MAGIC: "hugetlbfs", /* 0x958458F6 local */
MTD_INODE_FS_SUPER_MAGIC: "inodefs", /* 0x11307854 local */
INOTIFYFS_SUPER_MAGIC: "inotifyfs", /* 0x2BAD1DEA local */
ISOFS_SUPER_MAGIC: "isofs", /* 0x9660 local */
ISOFS_R_WIN_SUPER_MAGIC: "isofs", /* 0x4004 local */
ISOFS_WIN_SUPER_MAGIC: "isofs", /* 0x4000 local */
JFFS_SUPER_MAGIC: "jffs", /* 0x07C0 local */
JFFS2_SUPER_MAGIC: "jffs2", /* 0x72B6 local */
JFS_SUPER_MAGIC: "jfs", /* 0x3153464A local */
KAFS_SUPER_MAGIC: "k-afs", /* 0x6B414653 remote */
LUSTRE_SUPER_MAGIC: "lustre", /* 0x0BD00BD0 remote */
MINIX_SUPER_MAGIC: "minix", /* 0x137F local */
MINIX_SUPER_MAGIC2: "minix (30 char.)", /* 0x138F local */
MINIX2_SUPER_MAGIC: "minix v2", /* 0x2468 local */
MINIX2_SUPER_MAGIC2: "minix v2 (30 char.)", /* 0x2478 local */
MINIX3_SUPER_MAGIC: "minix3", /* 0x4D5A local */
MQUEUE_MAGIC: "mqueue", /* 0x19800202 local */
MSDOS_SUPER_MAGIC: "msdos", /* 0x4D44 local */
NCP_SUPER_MAGIC: "novell", /* 0x564C remote */
NFS_SUPER_MAGIC: "nfs", /* 0x6969 remote */
NFSD_SUPER_MAGIC: "nfsd", /* 0x6E667364 remote */
NILFS_SUPER_MAGIC: "nilfs", /* 0x3434 local */
NTFS_SB_MAGIC: "ntfs", /* 0x5346544E local */
OPENPROM_SUPER_MAGIC: "openprom", /* 0x9FA1 local */
OCFS2_SUPER_MAGIC: "ocfs2", /* 0x7461636f remote */
PANFS_SUPER_MAGIC: "panfs", /* 0xAAD7AAEA remote */
PIPEFS_MAGIC: "pipefs", /* 0x50495045 remote */
PROC_SUPER_MAGIC: "proc", /* 0x9FA0 local */
PSTOREFS_MAGIC: "pstorefs", /* 0x6165676C local */
QNX4_SUPER_MAGIC: "qnx4", /* 0x002F local */
QNX6_SUPER_MAGIC: "qnx6", /* 0x68191122 local */
RAMFS_MAGIC: "ramfs", /* 0x858458F6 local */
REISERFS_SUPER_MAGIC: "reiserfs", /* 0x52654973 local */
ROMFS_MAGIC: "romfs", /* 0x7275 local */
RPC_PIPEFS_SUPER_MAGIC: "rpc_pipefs", /* 0x67596969 local */
SECURITYFS_SUPER_MAGIC: "securityfs", /* 0x73636673 local */
SELINUX_MAGIC: "selinux", /* 0xF97CFF8C local */
SMB_SUPER_MAGIC: "smb", /* 0x517B remote */
SOCKFS_MAGIC: "sockfs", /* 0x534F434B local */
SQUASHFS_MAGIC: "squashfs", /* 0x73717368 local */
SYSFS_MAGIC: "sysfs", /* 0x62656572 local */
SYSV2_SUPER_MAGIC: "sysv2", /* 0x012FF7B6 local */
SYSV4_SUPER_MAGIC: "sysv4", /* 0x012FF7B5 local */
TMPFS_MAGIC: "tmpfs", /* 0x01021994 local */
UDF_SUPER_MAGIC: "udf", /* 0x15013346 local */
UFS_MAGIC: "ufs", /* 0x00011954 local */
UFS_BYTESWAPPED_SUPER_MAGIC: "ufs", /* 0x54190100 local */
USBDEVICE_SUPER_MAGIC: "usbdevfs", /* 0x9FA2 local */
V9FS_MAGIC: "v9fs", /* 0x01021997 local */
VMHGFS_SUPER_MAGIC: "vmhgfs", /* 0xBACBACBC remote */
VXFS_SUPER_MAGIC: "vxfs", /* 0xA501FCF5 local */
VZFS_SUPER_MAGIC: "vzfs", /* 0x565A4653 local */
XENFS_SUPER_MAGIC: "xenfs", /* 0xABBA1974 local */
XENIX_SUPER_MAGIC: "xenix", /* 0x012FF7B4 local */
XFS_SUPER_MAGIC: "xfs", /* 0x58465342 local */
_XIAFS_SUPER_MAGIC: "xia", /* 0x012FD16D local */
ZFS_SUPER_MAGIC: "zfs", /* 0x2FC12FC1 local */
}
// Partitions returns disk partitions. If all is false, returns
// physical devices only (e.g. hard disks, cd-rom drives, USB keys)
// and ignore all others (e.g. memory partitions such as /dev/shm)
//
// should use setmntent(3) but this implement use /etc/mtab file
func Partitions(all bool) ([]PartitionStat, error) {
filename := common.HostEtc("mtab")
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
}
fs, err := getFileSystems()
if err != nil {
return nil, err
}
ret := make([]PartitionStat, 0, len(lines))
for _, line := range lines {
fields := strings.Fields(line)
d := PartitionStat{
Device: fields[0],
Mountpoint: fields[1],
Fstype: fields[2],
Opts: fields[3],
}
if all == false {
if d.Device == "none" || !common.StringsHas(fs, d.Fstype) {
continue
}
}
ret = append(ret, d)
}
return ret, nil
}
// getFileSystems returns supported filesystems from /proc/filesystems
func getFileSystems() ([]string, error) {
filename := common.HostProc("filesystems")
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
}
var ret []string
for _, line := range lines {
if !strings.HasPrefix(line, "nodev") {
ret = append(ret, strings.TrimSpace(line))
continue
}
t := strings.Split(line, "\t")
if len(t) != 2 || t[1] != "zfs" {
continue
}
ret = append(ret, strings.TrimSpace(t[1]))
}
return ret, nil
}
func IOCounters() (map[string]IOCountersStat, error) {
filename := common.HostProc("diskstats")
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
}
ret := make(map[string]IOCountersStat, 0)
empty := IOCountersStat{}
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 14 {
// malformed line in /proc/diskstats, avoid panic by ignoring.
continue
}
name := fields[2]
reads, err := strconv.ParseUint((fields[3]), 10, 64)
if err != nil {
return ret, err
}
mergedReads, err := strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return ret, err
}
rbytes, err := strconv.ParseUint((fields[5]), 10, 64)
if err != nil {
return ret, err
}
rtime, err := strconv.ParseUint((fields[6]), 10, 64)
if err != nil {
return ret, err
}
writes, err := strconv.ParseUint((fields[7]), 10, 64)
if err != nil {
return ret, err
}
mergedWrites, err := strconv.ParseUint((fields[8]), 10, 64)
if err != nil {
return ret, err
}
wbytes, err := strconv.ParseUint((fields[9]), 10, 64)
if err != nil {
return ret, err
}
wtime, err := strconv.ParseUint((fields[10]), 10, 64)
if err != nil {
return ret, err
}
iopsInProgress, err := strconv.ParseUint((fields[11]), 10, 64)
if err != nil {
return ret, err
}
iotime, err := strconv.ParseUint((fields[12]), 10, 64)
if err != nil {
return ret, err
}
weightedIO, err := strconv.ParseUint((fields[13]), 10, 64)
if err != nil {
return ret, err
}
d := IOCountersStat{
ReadBytes: rbytes * SectorSize,
WriteBytes: wbytes * SectorSize,
ReadCount: reads,
WriteCount: writes,
MergedReadCount: mergedReads,
MergedWriteCount: mergedWrites,
ReadTime: rtime,
WriteTime: wtime,
IopsInProgress: iopsInProgress,
IoTime: iotime,
WeightedIO: weightedIO,
}
if d == empty {
continue
}
d.Name = name
d.SerialNumber = GetDiskSerialNumber(name)
ret[name] = d
}
return ret, nil
}
// GetDiskSerialNumber returns Serial Number of given device or empty string
// on error. Name of device is expected, eg. /dev/sda
func GetDiskSerialNumber(name string) string {
n := fmt.Sprintf("--name=%s", name)
udevadm, err := exec.LookPath("/sbin/udevadm")
if err != nil {
return ""
}
out, err := invoke.Command(udevadm, "info", "--query=property", n)
// does not return error, just an empty string
if err != nil {
return ""
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
values := strings.Split(line, "=")
if len(values) < 2 || values[0] != "ID_SERIAL" {
// only get ID_SERIAL, not ID_SERIAL_SHORT
continue
}
return values[1]
}
return ""
}
func getFsType(stat syscall.Statfs_t) string {
t := int64(stat.Type)
ret, ok := fsTypeMap[t]
if !ok {
return ""
}
return ret
}

158
vendor/github.com/shirou/gopsutil/disk/disk_openbsd.go generated vendored Normal file
View File

@ -0,0 +1,158 @@
// +build openbsd
package disk
import (
"bytes"
"encoding/binary"
"path"
"syscall"
"unsafe"
"github.com/shirou/gopsutil/internal/common"
)
func Partitions(all bool) ([]PartitionStat, error) {
var ret []PartitionStat
// get length
count, err := syscall.Getfsstat(nil, MNT_WAIT)
if err != nil {
return ret, err
}
fs := make([]Statfs, count)
_, err = Getfsstat(fs, MNT_WAIT)
for _, stat := range fs {
opts := "rw"
if stat.F_flags&MNT_RDONLY != 0 {
opts = "ro"
}
if stat.F_flags&MNT_SYNCHRONOUS != 0 {
opts += ",sync"
}
if stat.F_flags&MNT_NOEXEC != 0 {
opts += ",noexec"
}
if stat.F_flags&MNT_NOSUID != 0 {
opts += ",nosuid"
}
if stat.F_flags&MNT_NODEV != 0 {
opts += ",nodev"
}
if stat.F_flags&MNT_ASYNC != 0 {
opts += ",async"
}
d := PartitionStat{
Device: common.IntToString(stat.F_mntfromname[:]),
Mountpoint: common.IntToString(stat.F_mntonname[:]),
Fstype: common.IntToString(stat.F_fstypename[:]),
Opts: opts,
}
if all == false {
if !path.IsAbs(d.Device) || !common.PathExists(d.Device) {
continue
}
}
ret = append(ret, d)
}
return ret, nil
}
func IOCounters() (map[string]IOCountersStat, error) {
ret := make(map[string]IOCountersStat)
r, err := syscall.Sysctl("hw.diskstats")
if err != nil {
return nil, err
}
buf := []byte(r)
length := len(buf)
count := int(uint64(length) / uint64(sizeOfDiskstats))
// parse buf to Diskstats
for i := 0; i < count; i++ {
b := buf[i*sizeOfDiskstats : i*sizeOfDiskstats+sizeOfDiskstats]
d, err := parseDiskstats(b)
if err != nil {
continue
}
name := common.IntToString(d.Name[:])
ds := IOCountersStat{
ReadCount: d.Rxfer,
WriteCount: d.Wxfer,
ReadBytes: d.Rbytes,
WriteBytes: d.Wbytes,
Name: name,
}
ret[name] = ds
}
return ret, nil
}
// BT2LD(time) ((long double)(time).sec + (time).frac * BINTIME_SCALE)
// Getfsstat is borrowed from pkg/syscall/syscall_freebsd.go
// change Statfs_t to Statfs in order to get more information
func Getfsstat(buf []Statfs, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statfs{}) * uintptr(len(buf))
}
r0, _, e1 := syscall.Syscall(syscall.SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
func parseDiskstats(buf []byte) (Diskstats, error) {
var ds Diskstats
br := bytes.NewReader(buf)
// err := binary.Read(br, binary.LittleEndian, &ds)
err := common.Read(br, binary.LittleEndian, &ds)
if err != nil {
return ds, err
}
return ds, nil
}
func Usage(path string) (*UsageStat, error) {
stat := syscall.Statfs_t{}
err := syscall.Statfs(path, &stat)
if err != nil {
return nil, err
}
bsize := stat.F_bsize
ret := &UsageStat{
Path: path,
Fstype: getFsType(stat),
Total: (uint64(stat.F_blocks) * uint64(bsize)),
Free: (uint64(stat.F_bavail) * uint64(bsize)),
InodesTotal: (uint64(stat.F_files)),
InodesFree: (uint64(stat.F_ffree)),
}
ret.InodesUsed = (ret.InodesTotal - ret.InodesFree)
ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
ret.Used = (uint64(stat.F_blocks) - uint64(stat.F_bfree)) * uint64(bsize)
ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0
return ret, nil
}
func getFsType(stat syscall.Statfs_t) string {
return common.IntToString(stat.F_fstypename[:])
}

View File

@ -0,0 +1,91 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_openbsd.go
package disk
const (
sizeofPtr = 0x8
sizeofShort = 0x2
sizeofInt = 0x4
sizeofLong = 0x8
sizeofLongLong = 0x8
sizeofLongDouble = 0x8
DEVSTAT_NO_DATA = 0x00
DEVSTAT_READ = 0x01
DEVSTAT_WRITE = 0x02
DEVSTAT_FREE = 0x03
MNT_RDONLY = 0x00000001
MNT_SYNCHRONOUS = 0x00000002
MNT_NOEXEC = 0x00000004
MNT_NOSUID = 0x00000008
MNT_NODEV = 0x00000010
MNT_ASYNC = 0x00000040
MNT_WAIT = 1
MNT_NOWAIT = 2
MNT_LAZY = 3
)
const (
sizeOfDiskstats = 0x70
)
type (
_C_short int16
_C_int int32
_C_long int64
_C_long_long int64
_C_long_double int64
)
type Statfs struct {
F_flags uint32
F_bsize uint32
F_iosize uint32
Pad_cgo_0 [4]byte
F_blocks uint64
F_bfree uint64
F_bavail int64
F_files uint64
F_ffree uint64
F_favail int64
F_syncwrites uint64
F_syncreads uint64
F_asyncwrites uint64
F_asyncreads uint64
F_fsid Fsid
F_namemax uint32
F_owner uint32
F_ctime uint64
F_fstypename [16]int8
F_mntonname [90]int8
F_mntfromname [90]int8
F_mntfromspec [90]int8
Pad_cgo_1 [2]byte
Mount_info [160]byte
}
type Diskstats struct {
Name [16]int8
Busy int32
Pad_cgo_0 [4]byte
Rxfer uint64
Wxfer uint64
Seek uint64
Rbytes uint64
Wbytes uint64
Attachtime Timeval
Timestamp Timeval
Time Timeval
}
type Fsid struct {
Val [2]int32
}
type Timeval struct {
Sec int64
Usec int64
}
type Diskstat struct{}
type Bintime struct{}

45
vendor/github.com/shirou/gopsutil/disk/disk_unix.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// +build freebsd linux darwin
package disk
import "syscall"
func Usage(path string) (*UsageStat, error) {
stat := syscall.Statfs_t{}
err := syscall.Statfs(path, &stat)
if err != nil {
return nil, err
}
bsize := stat.Bsize
ret := &UsageStat{
Path: path,
Fstype: getFsType(stat),
Total: (uint64(stat.Blocks) * uint64(bsize)),
Free: (uint64(stat.Bavail) * uint64(bsize)),
InodesTotal: (uint64(stat.Files)),
InodesFree: (uint64(stat.Ffree)),
}
// if could not get InodesTotal, return empty
if ret.InodesTotal < ret.InodesFree {
return ret, nil
}
ret.InodesUsed = (ret.InodesTotal - ret.InodesFree)
ret.Used = (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(bsize)
if ret.InodesTotal == 0 {
ret.InodesUsedPercent = 0
} else {
ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
}
if ret.Total == 0 {
ret.UsedPercent = 0
} else {
ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0
}
return ret, nil
}

155
vendor/github.com/shirou/gopsutil/disk/disk_windows.go generated vendored Normal file
View File

@ -0,0 +1,155 @@
// +build windows
package disk
import (
"bytes"
"syscall"
"unsafe"
"github.com/StackExchange/wmi"
"github.com/shirou/gopsutil/internal/common"
)
var (
procGetDiskFreeSpaceExW = common.Modkernel32.NewProc("GetDiskFreeSpaceExW")
procGetLogicalDriveStringsW = common.Modkernel32.NewProc("GetLogicalDriveStringsW")
procGetDriveType = common.Modkernel32.NewProc("GetDriveTypeW")
provGetVolumeInformation = common.Modkernel32.NewProc("GetVolumeInformationW")
)
var (
FileFileCompression = int64(16) // 0x00000010
FileReadOnlyVolume = int64(524288) // 0x00080000
)
type Win32_PerfFormattedData struct {
Name string
AvgDiskBytesPerRead uint64
AvgDiskBytesPerWrite uint64
AvgDiskReadQueueLength uint64
AvgDiskWriteQueueLength uint64
AvgDisksecPerRead uint64
AvgDisksecPerWrite uint64
}
const WaitMSec = 500
func Usage(path string) (*UsageStat, error) {
ret := &UsageStat{}
lpFreeBytesAvailable := int64(0)
lpTotalNumberOfBytes := int64(0)
lpTotalNumberOfFreeBytes := int64(0)
diskret, _, err := procGetDiskFreeSpaceExW.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))
if diskret == 0 {
return nil, err
}
ret = &UsageStat{
Path: path,
Total: uint64(lpTotalNumberOfBytes),
Free: uint64(lpTotalNumberOfFreeBytes),
Used: uint64(lpTotalNumberOfBytes) - uint64(lpTotalNumberOfFreeBytes),
UsedPercent: (float64(lpTotalNumberOfBytes) - float64(lpTotalNumberOfFreeBytes)) / float64(lpTotalNumberOfBytes) * 100,
// InodesTotal: 0,
// InodesFree: 0,
// InodesUsed: 0,
// InodesUsedPercent: 0,
}
return ret, nil
}
func Partitions(all bool) ([]PartitionStat, error) {
var ret []PartitionStat
lpBuffer := make([]byte, 254)
diskret, _, err := procGetLogicalDriveStringsW.Call(
uintptr(len(lpBuffer)),
uintptr(unsafe.Pointer(&lpBuffer[0])))
if diskret == 0 {
return ret, err
}
for _, v := range lpBuffer {
if v >= 65 && v <= 90 {
path := string(v) + ":"
if path == "A:" || path == "B:" { // skip floppy drives
continue
}
typepath, _ := syscall.UTF16PtrFromString(path)
typeret, _, _ := procGetDriveType.Call(uintptr(unsafe.Pointer(typepath)))
if typeret == 0 {
return ret, syscall.GetLastError()
}
// 2: DRIVE_REMOVABLE 3: DRIVE_FIXED 5: DRIVE_CDROM
if typeret == 2 || typeret == 3 || typeret == 5 {
lpVolumeNameBuffer := make([]byte, 256)
lpVolumeSerialNumber := int64(0)
lpMaximumComponentLength := int64(0)
lpFileSystemFlags := int64(0)
lpFileSystemNameBuffer := make([]byte, 256)
volpath, _ := syscall.UTF16PtrFromString(string(v) + ":/")
driveret, _, err := provGetVolumeInformation.Call(
uintptr(unsafe.Pointer(volpath)),
uintptr(unsafe.Pointer(&lpVolumeNameBuffer[0])),
uintptr(len(lpVolumeNameBuffer)),
uintptr(unsafe.Pointer(&lpVolumeSerialNumber)),
uintptr(unsafe.Pointer(&lpMaximumComponentLength)),
uintptr(unsafe.Pointer(&lpFileSystemFlags)),
uintptr(unsafe.Pointer(&lpFileSystemNameBuffer[0])),
uintptr(len(lpFileSystemNameBuffer)))
if driveret == 0 {
if typeret == 5 {
continue //device is not ready will happen if there is no disk in the drive
}
return ret, err
}
opts := "rw"
if lpFileSystemFlags&FileReadOnlyVolume != 0 {
opts = "ro"
}
if lpFileSystemFlags&FileFileCompression != 0 {
opts += ".compress"
}
d := PartitionStat{
Mountpoint: path,
Device: path,
Fstype: string(bytes.Replace(lpFileSystemNameBuffer, []byte("\x00"), []byte(""), -1)),
Opts: opts,
}
ret = append(ret, d)
}
}
}
return ret, nil
}
func IOCounters() (map[string]IOCountersStat, error) {
ret := make(map[string]IOCountersStat, 0)
var dst []Win32_PerfFormattedData
err := wmi.Query("SELECT * FROM Win32_PerfFormattedData_PerfDisk_LogicalDisk ", &dst)
if err != nil {
return ret, err
}
for _, d := range dst {
if len(d.Name) > 3 { // not get _Total or Harddrive
continue
}
ret[d.Name] = IOCountersStat{
Name: d.Name,
ReadCount: uint64(d.AvgDiskReadQueueLength),
WriteCount: d.AvgDiskWriteQueueLength,
ReadBytes: uint64(d.AvgDiskBytesPerRead),
WriteBytes: uint64(d.AvgDiskBytesPerWrite),
ReadTime: d.AvgDisksecPerRead,
WriteTime: d.AvgDisksecPerWrite,
}
}
return ret, nil
}

1
vendor/vendor.json vendored
View File

@ -258,6 +258,7 @@
{"path":"github.com/ryanuber/go-glob","checksumSHA1":"6JP37UqrI0H80Gpk0Y2P+KXgn5M=","revision":"256dc444b735e061061cf46c809487313d5b0065","revisionTime":"2017-01-28T01:21:29Z"},
{"path":"github.com/sean-/seed","checksumSHA1":"A/YUMbGg1LHIeK2+NLZBt+MIAao=","revision":"3c72d44db0c567f7c901f9c5da5fe68392227750","revisionTime":"2017-02-08T16:47:21Z"},
{"path":"github.com/shirou/gopsutil/cpu","checksumSHA1":"zW2k8E1gkuySzTz2eXuSEDhpffY=","revision":"32b6636de04b303274daac3ca2b10d3b0e4afc35","revisionTime":"2017-02-04T05:36:48Z"},
{"path":"github.com/shirou/gopsutil/disk","checksumSHA1":"wxkkOLGCVJ/qrh+eSSFyIW2kTd8=","revision":"b62e301a8b9958eebb7299683eb57fab229a9501","revisionTime":"2017-02-08T02:55:55Z"},
{"path":"github.com/shirou/gopsutil/host","checksumSHA1":"GsqEEmGv6sj8DreS2SYXRkoZ9NI=","revision":"b62e301a8b9958eebb7299683eb57fab229a9501","revisionTime":"2017-02-08T02:55:55Z"},
{"path":"github.com/shirou/gopsutil/internal/common","checksumSHA1":"hz9RxkaV3Tnju2eiHBWO/Yv7n5c=","revision":"32b6636de04b303274daac3ca2b10d3b0e4afc35","revisionTime":"2017-02-04T05:36:48Z"},
{"path":"github.com/shirou/gopsutil/mem","checksumSHA1":"XQwjGKI51Y3aQ3/jNyRh9Gnprgg=","revision":"32b6636de04b303274daac3ca2b10d3b0e4afc35","revisionTime":"2017-02-04T05:36:48Z"},

View File

@ -936,7 +936,8 @@ default will automatically work with some tooling.
be checked using the agent's credentials. This was added in Consul 1.0.1 and defaults to false.
* <a name="enable_debug"></a><a href="#enable_debug">`enable_debug`</a> When set, enables some
additional debugging features. Currently, this is only used to set the runtime profiling HTTP endpoints.
additional debugging features. Currently, this is only used to access runtime profiling HTTP endpoints, which
are available with an `operator:read` ACL regardles of the value of `enable_debug`.
* <a name="enable_script_checks"></a><a href="#enable_script_checks">`enable_script_checks`</a> Equivalent to the
[`-enable-script-checks` command-line flag](#_enable_script_checks).

View File

@ -0,0 +1,122 @@
---
layout: "docs"
page_title: "Commands: Debug"
sidebar_current: "docs-commands-debug"
---
# Consul Debug
Command: `consul debug`
The `consul debug` command monitors a Consul agent for the specified period of
time, recording information about the agent, cluster, and environment to an archive
written to the current directory.
Providing support for complex issues encountered by Consul operators often
requires a large amount of debugging information to be retrieved. This command
aims to shortcut that coordination and provide a simple workflow for accessing
data about Consul agent, cluster, and environment to enable faster
isolation and debugging of issues.
This command requires an `operator:read` ACL token in order to retrieve the
data from the target agent, if ACLs are enabled.
If the command is interrupted, as it could be given a long duration but
require less time than expected, it will attempt to archive the current
captured data.
## Security and Privacy
By default, ACL tokens, private keys, and other sensitive material related
to Consul is sanitized and not available in this archive. However, other
information about the environment the target agent is running in is available
in plain text within the archive.
It is recommended to validate the contents of the archive and redact any
material classified as sensitive to the target environment, or use the `-capture`
flag to not retrieve it initially.
Additionally, we recommend securely transmitting this archive via encryption
or otherwise.
## Usage
`Usage: consul debug [options]`
By default, the debug command will capture an archive at the current path for
all targets for 2 minutes.
#### API Options
<%= partial "docs/commands/http_api_options_client" %>
#### Command Options
* `-duration` - Optional, the total time to capture data for from the target agent. Must
be greater than the interval and longer than 10 seconds. Defaults to 2 minutes.
* `-interval` - Optional, the interval at which to capture dynamic data, such as logs
and metrics. Must be longer than 5 seconds. Defaults to 30 seconds.
* `-capture` - Optional, can be specified multiple times for each [capture target](#capture-targets)
and will only record that information in the archive.
* `-output` - Optional, the full path of where to write the directory of data and
resulting archive. Defaults to the current directory.
* `-archive` - Optional, if the tool show archive the directory of data into a
compressed tar file. Defaults to true.
## Capture Targets
The `-capture` flag can be specified multiple times to capture specific
information when `debug` is running. By default, it captures all information.
| Target | Description |
| ------ | ---------------------------- |
| `agent` | Version and configuration information about the agent. |
| `host` | Information about resources on the host running the target agent such as CPU, memory, and disk. |
| `cluster` | A list of all the WAN and LAN members in the cluster. |
| `metrics` | Metrics from the in-memory metrics endpoint in the target, captured at the interval. |
| `logs` | `DEBUG` level logs for the target agent, captured for the interval. |
| `pprof` | Golang heap, CPU, goroutine, and trace profiling. This information is not retrieved unless [`enable_debug`](/docs/agent/options.html#enable_debug) is set to `true` on the target agent. |
## Examples
This command can be run from any host with the Consul binary, but requires
network access to the target agent in order to retrieve data. Once retrieved,
the data is written to the the specified path (defaulting to the current
directory) on the host where the command runs.
By default the command will capture all available data from the default
agent address on loopback for 2 minutes at 30 second intervals.
```text
$ consul debug
...
```
In this example, the archive is collected from a different agent on the
network using the standard Consul CLI flag to change the API address.
```text
$ consul debug -http-addr=10.0.1.10:8500
...
```
The capture flag can be specified to only record a subset of data
about the agent and environment.
```text
$ consul debug -capture agent -capture host -capture logs
...
```
The duration of the command and interval of capturing dynamic
information (such as metrics) can be specified with the `-interval`
and `-duration` flags.
```text
$ consul debug -interval=15s -duration=1m
...
```

View File

@ -84,6 +84,9 @@
</li>
</ul>
</li>
<li<%= sidebar_current("docs-commands-debug") %>>
<a href="/docs/commands/debug.html">debug</a>
</li>
<li<%= sidebar_current("docs-commands-event") %>>
<a href="/docs/commands/event.html">event</a>
</li>