From 92f0b9edd4d2642b66b79344cf40c8b9dba80250 Mon Sep 17 00:00:00 2001 From: Frrist Date: Thu, 2 May 2019 17:08:49 -0400 Subject: [PATCH] add inspector command for system inspection (#2663) * add inspector command and tests --- commands/daemon.go | 1 + commands/env.go | 13 +- commands/inspector.go | 247 ++++++++++++++++++++++++++++++ commands/inspector_daemon_test.go | 52 +++++++ commands/inspector_test.go | 61 ++++++++ commands/main.go | 2 + go.mod | 1 + go.sum | 2 + tools/fast/action_inspector.go | 93 +++++++++++ 9 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 commands/inspector.go create mode 100644 commands/inspector_daemon_test.go create mode 100644 commands/inspector_test.go create mode 100644 tools/fast/action_inspector.go diff --git a/commands/daemon.go b/commands/daemon.go index 919386f82b..36a82d29e9 100644 --- a/commands/daemon.go +++ b/commands/daemon.go @@ -137,6 +137,7 @@ func runAPIAndWait(ctx context.Context, nd *node.Node, config *config.Config, re // TODO: should this be the passed in context? Issue 2641 blockMiningAPI: nd.BlockMiningAPI, ctx: context.Background(), + inspectorAPI: NewInspectorAPI(nd.Repo), porcelainAPI: nd.PorcelainAPI, retrievalAPI: nd.RetrievalAPI, storageAPI: nd.StorageAPI, diff --git a/commands/env.go b/commands/env.go index 1006970c50..97489bf3dc 100644 --- a/commands/env.go +++ b/commands/env.go @@ -18,6 +18,7 @@ type Env struct { porcelainAPI *porcelain.API retrievalAPI *retrieval.API storageAPI *storage.API + inspectorAPI *Inspector } var _ cmds.Environment = (*Env)(nil) @@ -33,20 +34,26 @@ func GetPorcelainAPI(env cmds.Environment) *porcelain.API { return ce.porcelainAPI } -// GetBlockAPI returns the block protocol api from the given environment +// GetBlockAPI returns the block protocol api from the given environment. func GetBlockAPI(env cmds.Environment) *block.MiningAPI { ce := env.(*Env) return ce.blockMiningAPI } -// GetRetrievalAPI returns the retrieval protocol api from the given environment +// GetRetrievalAPI returns the retrieval protocol api from the given environment. func GetRetrievalAPI(env cmds.Environment) *retrieval.API { ce := env.(*Env) return ce.retrievalAPI } -// GetStorageAPI returns the storage protocol api from the given environment +// GetStorageAPI returns the storage protocol api from the given environment. func GetStorageAPI(env cmds.Environment) *storage.API { ce := env.(*Env) return ce.storageAPI } + +// GetInspectorAPI returns the inspector api from the given environment. +func GetInspectorAPI(env cmds.Environment) *Inspector { + ce := env.(*Env) + return ce.inspectorAPI +} diff --git a/commands/inspector.go b/commands/inspector.go new file mode 100644 index 0000000000..8442e773a3 --- /dev/null +++ b/commands/inspector.go @@ -0,0 +1,247 @@ +package commands + +import ( + "os" + "runtime" + + cmdkit "github.com/ipfs/go-ipfs-cmdkit" + cmds "github.com/ipfs/go-ipfs-cmds" + sysi "github.com/whyrusleeping/go-sysinfo" + + "github.com/filecoin-project/go-filecoin/config" + "github.com/filecoin-project/go-filecoin/repo" +) + +var inspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Show info about the filecoin node", + }, + Subcommands: map[string]*cmds.Command{ + "all": allInspectCmd, + "runtime": runtimeInspectCmd, + "disk": diskInspectCmd, + "memory": memoryInspectCmd, + "config": configInspectCmd, + "environment": envInspectCmd, + }, +} +var allInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print all diagnostic information.", + ShortDescription: ` +Prints out information about filecoin process and its environment. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + var allInfo AllInspectorInfo + allInfo.Runtime = GetInspectorAPI(env).Runtime() + + dsk, err := GetInspectorAPI(env).Disk() + if err != nil { + return err + } + allInfo.Disk = dsk + + mem, err := GetInspectorAPI(env).Memory() + if err != nil { + return err + } + allInfo.Memory = mem + allInfo.Config = GetInspectorAPI(env).Config() + allInfo.Environment = GetInspectorAPI(env).Environment() + return cmds.EmitOnce(res, allInfo) + }, +} + +var runtimeInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print runtime diagnostic information.", + ShortDescription: ` +Prints out information about the golang runtime. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + out := GetInspectorAPI(env).Runtime() + return cmds.EmitOnce(res, out) + }, +} + +var diskInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print filesystem usage information.", + ShortDescription: ` +Prints out information about the filesystem. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + out, err := GetInspectorAPI(env).Disk() + if err != nil { + return err + } + return cmds.EmitOnce(res, out) + }, +} + +var memoryInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print memory usage information.", + ShortDescription: ` +Prints out information about memory usage. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + out, err := GetInspectorAPI(env).Memory() + if err != nil { + return err + } + return cmds.EmitOnce(res, out) + }, +} + +var configInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print in-memory config information.", + ShortDescription: ` +Prints out information about your filecoin nodes config. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + out := GetInspectorAPI(env).Config() + return cmds.EmitOnce(res, out) + }, +} + +var envInspectCmd = &cmds.Command{ + Helptext: cmdkit.HelpText{ + Tagline: "Print filecoin environment information.", + ShortDescription: ` +Prints out information about your filecoin nodes environment. +`, + }, + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { + out := GetInspectorAPI(env).Environment() + return cmds.EmitOnce(res, out) + }, +} + +// NewInspectorAPI returns a `Inspector` used to inspect the go-filecoin node. +func NewInspectorAPI(r repo.Repo) *Inspector { + return &Inspector{ + repo: r, + } +} + +// Inspector contains information used to inspect the go-filecoin node. +type Inspector struct { + repo repo.Repo +} + +// AllInspectorInfo contains all information the inspector can gather. +type AllInspectorInfo struct { + Config *config.Config + Runtime *RuntimeInfo + Environment *EnvironmentInfo + Disk *DiskInfo + Memory *MemoryInfo +} + +// RuntimeInfo contains information about the golang runtime. +type RuntimeInfo struct { + OS string + Arch string + Version string + Compiler string + NumProc int + GoMaxProcs int + NumGoRoutines int + NumCGoCalls int64 +} + +// EnvironmentInfo contains information about the environment filecoin is running in. +type EnvironmentInfo struct { + FilAPI string `json:"FIL_API"` + FilPath string `json:"FIL_PATH"` + GoPath string `json:"GOPATH"` +} + +// DiskInfo contains information about disk usage and type. +type DiskInfo struct { + Free uint64 + Total uint64 + FSType string +} + +// MemoryInfo contains information about memory usage. +type MemoryInfo struct { + Swap uint64 + Virtual uint64 +} + +// Runtime returns infrormation about the golang runtime. +func (g *Inspector) Runtime() *RuntimeInfo { + return &RuntimeInfo{ + OS: runtime.GOOS, + Arch: runtime.GOARCH, + Version: runtime.Version(), + Compiler: runtime.Compiler, + NumProc: runtime.NumCPU(), + GoMaxProcs: runtime.GOMAXPROCS(0), + NumGoRoutines: runtime.NumGoroutine(), + NumCGoCalls: runtime.NumCgoCall(), + } +} + +// Environment returns information about the environment filecoin is running in. +func (g *Inspector) Environment() *EnvironmentInfo { + return &EnvironmentInfo{ + FilAPI: os.Getenv("FIL_API"), + FilPath: os.Getenv("FIL_PATH"), + GoPath: os.Getenv("GOPATH"), + } +} + +// Disk return information about filesystem the filecoin nodes repo is on. +func (g *Inspector) Disk() (*DiskInfo, error) { + fsr, ok := g.repo.(*repo.FSRepo) + if !ok { + // we are using a in memory repo + return &DiskInfo{ + Free: 0, + Total: 0, + FSType: "0", + }, nil + } + + p, err := fsr.Path() + if err != nil { + return nil, err + } + + dinfo, err := sysi.DiskUsage(p) + if err != nil { + return nil, err + } + + return &DiskInfo{ + Free: dinfo.Free, + Total: dinfo.Total, + FSType: dinfo.FsType, + }, nil +} + +// Memory return information about system meory usage. +func (g *Inspector) Memory() (*MemoryInfo, error) { + meminfo, err := sysi.MemoryInfo() + if err != nil { + return nil, err + } + return &MemoryInfo{ + Swap: meminfo.Swap, + Virtual: meminfo.Used, + }, nil +} + +// Config return the current config values of the filecoin node. +func (g *Inspector) Config() *config.Config { + return g.repo.Config() +} diff --git a/commands/inspector_daemon_test.go b/commands/inspector_daemon_test.go new file mode 100644 index 0000000000..ae9cedd3c6 --- /dev/null +++ b/commands/inspector_daemon_test.go @@ -0,0 +1,52 @@ +package commands_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + tf "github.com/filecoin-project/go-filecoin/testhelpers/testflags" + "github.com/filecoin-project/go-filecoin/tools/fast" + "github.com/filecoin-project/go-filecoin/tools/fast/fastesting" +) + +func TestInspectConfig(t *testing.T) { + tf.IntegrationTest(t) + + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second)) + defer cancel() + + // Get basic testing environment + ctx, env := fastesting.NewTestEnvironment(ctx, t, fast.EnvironmentOpts{}) + + // Teardown after test ends + defer func() { + err := env.Teardown(ctx) + require.NoError(t, err) + }() + + nd := env.RequireNewNodeStarted() + + icfg, err := nd.InspectConfig(ctx) + require.NoError(t, err) + + rcfg, err := nd.Config() + require.NoError(t, err) + + // API is not the same since the FAST plugin reads the config file from disk, + // this is a limitation of FAST. + // FAST sets API.Address to /ip4/0.0.0.0/0 in the config file to avoid port collisions. + // The Inspector returns an in memory representation of the config that has + // received a port assignment from the kernel, meaning it is no longer /ip4/0.0.0.0/0 + assert.Equal(t, rcfg.Bootstrap, icfg.Bootstrap) + assert.Equal(t, rcfg.Datastore, icfg.Datastore) + assert.Equal(t, rcfg.Swarm, icfg.Swarm) + assert.Equal(t, rcfg.Mining, icfg.Mining) + assert.Equal(t, rcfg.Wallet, icfg.Wallet) + assert.Equal(t, rcfg.Heartbeat, icfg.Heartbeat) + assert.Equal(t, rcfg.Net, icfg.Net) + assert.Equal(t, rcfg.Mpool, icfg.Mpool) +} diff --git a/commands/inspector_test.go b/commands/inspector_test.go new file mode 100644 index 0000000000..41ef7c55ef --- /dev/null +++ b/commands/inspector_test.go @@ -0,0 +1,61 @@ +package commands_test + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/go-filecoin/commands" + "github.com/filecoin-project/go-filecoin/config" + "github.com/filecoin-project/go-filecoin/repo" + tf "github.com/filecoin-project/go-filecoin/testhelpers/testflags" +) + +func TestRuntime(t *testing.T) { + tf.UnitTest(t) + + mr := repo.NewInMemoryRepo() + g := commands.NewInspectorAPI(mr) + rt := g.Runtime() + + assert.Equal(t, runtime.GOOS, rt.OS) + assert.Equal(t, runtime.GOARCH, rt.Arch) + assert.Equal(t, runtime.Version(), rt.Version) + assert.Equal(t, runtime.Compiler, rt.Compiler) + assert.Equal(t, runtime.NumCPU(), rt.NumProc) + assert.Equal(t, runtime.GOMAXPROCS(0), rt.GoMaxProcs) + assert.Equal(t, runtime.NumCgoCall(), rt.NumCGoCalls) +} + +func TestDisk(t *testing.T) { + tf.UnitTest(t) + + mr := repo.NewInMemoryRepo() + g := commands.NewInspectorAPI(mr) + d, err := g.Disk() + + assert.NoError(t, err) + assert.Equal(t, uint64(0), d.Free) + assert.Equal(t, uint64(0), d.Total) + assert.Equal(t, "0", d.FSType) +} + +func TestMemory(t *testing.T) { + tf.UnitTest(t) + + mr := repo.NewInMemoryRepo() + g := commands.NewInspectorAPI(mr) + + _, err := g.Memory() + assert.NoError(t, err) +} + +func TestConfig(t *testing.T) { + tf.UnitTest(t) + + mr := repo.NewInMemoryRepo() + g := commands.NewInspectorAPI(mr) + c := g.Config() + assert.Equal(t, config.NewDefaultConfig(), c) +} diff --git a/commands/main.go b/commands/main.go index 92cbd2d1c1..e695b36ee8 100644 --- a/commands/main.go +++ b/commands/main.go @@ -126,6 +126,7 @@ MESSAGE COMMANDS go-filecoin mpool - Manage the message pool TOOL COMMANDS + go-filecoin inspect - Show info about the go-filecoin node go-filecoin log - Interact with the daemon event log output go-filecoin protocol - Show protocol parameter details go-filecoin version - Show go-filecoin version information @@ -165,6 +166,7 @@ var rootSubcmdsDaemon = map[string]*cmds.Command{ "dag": dagCmd, "dht": dhtCmd, "id": idCmd, + "inspect": inspectCmd, "log": logCmd, "message": msgCmd, "miner": minerCmd, diff --git a/go.mod b/go.mod index e25c603d70..7ed16fd46f 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,7 @@ require ( github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 github.com/stretchr/testify v1.3.0 github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc + github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.1.0 diff --git a/go.sum b/go.sum index c9819d3ec6..0237d92115 100644 --- a/go.sum +++ b/go.sum @@ -576,6 +576,8 @@ github.com/whyrusleeping/go-smux-multistream v2.0.2+incompatible/go.mod h1:dRWHH github.com/whyrusleeping/go-smux-yamux v2.0.8+incompatible/go.mod h1:6qHUzBXUbB9MXmw3AUdB52L8sEb/hScCqOdW2kj/wuI= github.com/whyrusleeping/go-smux-yamux v2.0.9+incompatible h1:nVkExQ7pYlN9e45LcqTCOiDD0904fjtm0flnHZGbXkw= github.com/whyrusleeping/go-smux-yamux v2.0.9+incompatible/go.mod h1:6qHUzBXUbB9MXmw3AUdB52L8sEb/hScCqOdW2kj/wuI= +github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 h1:ctS9Anw/KozviCCtK6VWMz5kPL9nbQzbQY4yfqlIV4M= +github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1/go.mod h1:tKH72zYNt/exx6/5IQO6L9LoQ0rEjd5SbbWaDTs9Zso= github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= diff --git a/tools/fast/action_inspector.go b/tools/fast/action_inspector.go new file mode 100644 index 0000000000..51854585b9 --- /dev/null +++ b/tools/fast/action_inspector.go @@ -0,0 +1,93 @@ +package fast + +import ( + "context" + + "github.com/filecoin-project/go-filecoin/commands" + "github.com/filecoin-project/go-filecoin/config" +) + +// InspectAll runs the `inspect all` command against the filecoin process +func (f *Filecoin) InspectAll(ctx context.Context, options ...ActionOption) (*commands.AllInspectorInfo, error) { + var out commands.AllInspectorInfo + + args := []string{"go-filecoin", "inspect", "all"} + + for _, option := range options { + args = append(args, option()...) + } + + if err := f.RunCmdJSONWithStdin(ctx, nil, &out, args...); err != nil { + return nil, err + } + + return &out, nil +} + +// InspectRuntime runs the `inspect runtime` command against the filecoin process +func (f *Filecoin) InspectRuntime(ctx context.Context, options ...ActionOption) (*commands.RuntimeInfo, error) { + var out commands.RuntimeInfo + + args := []string{"go-filecoin", "inspect", "runtime"} + + for _, option := range options { + args = append(args, option()...) + } + + if err := f.RunCmdJSONWithStdin(ctx, nil, &out, args...); err != nil { + return nil, err + } + + return &out, nil +} + +// InspectDisk runs the `inspect disk` command against the filecoin process +func (f *Filecoin) InspectDisk(ctx context.Context, options ...ActionOption) (*commands.DiskInfo, error) { + var out commands.DiskInfo + + args := []string{"go-filecoin", "inspect", "disk"} + + for _, option := range options { + args = append(args, option()...) + } + + if err := f.RunCmdJSONWithStdin(ctx, nil, &out, args...); err != nil { + return nil, err + } + + return &out, nil +} + +// InspectMemory runs the `inspect memory` command against the filecoin process +func (f *Filecoin) InspectMemory(ctx context.Context, options ...ActionOption) (*commands.MemoryInfo, error) { + var out commands.MemoryInfo + + args := []string{"go-filecoin", "inspect", "memory"} + + for _, option := range options { + args = append(args, option()...) + } + + if err := f.RunCmdJSONWithStdin(ctx, nil, &out, args...); err != nil { + return nil, err + } + + return &out, nil +} + +// InspectConfig runs the `inspect config` command against the filecoin process +func (f *Filecoin) InspectConfig(ctx context.Context, options ...ActionOption) (*config.Config, error) { + var out config.Config + + args := []string{"go-filecoin", "inspect", "config"} + + for _, option := range options { + args = append(args, option()...) + } + + if err := f.RunCmdJSONWithStdin(ctx, nil, &out, args...); err != nil { + return nil, err + } + + return &out, nil +}