Skip to content

Commit

Permalink
Merge pull request #2277 from ipfs/feat/active-cmds
Browse files Browse the repository at this point in the history
commands diagnostics
  • Loading branch information
whyrusleeping committed Feb 2, 2016
2 parents 62ba5eb + 4753e6c commit f5e7a3b
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 5 deletions.
16 changes: 13 additions & 3 deletions commands/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,16 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, cfg *ServerConfig) http.Ha
panic("must provide a valid ServerConfig")
}

// setup request logger
ctx.ReqLog = new(cmds.ReqLog)

// Wrap the internal handler with CORS handling-middleware.
// Create a handler for the API.
internal := internalHandler{ctx, root, cfg}
internal := internalHandler{
ctx: ctx,
root: root,
cfg: cfg,
}
c := cors.New(*cfg.cORSOpts)
return &Handler{internal, c.Handler(internal)}
}
Expand Down Expand Up @@ -158,6 +165,9 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

rlog := i.ctx.ReqLog.Add(req)
defer rlog.Finish()

//ps: take note of the name clash - commands.Context != context.Context
req.SetInvocContext(i.ctx)

Expand Down Expand Up @@ -201,8 +211,8 @@ func guessMimeType(res cmds.Response) (string, error) {
func sendResponse(w http.ResponseWriter, r *http.Request, res cmds.Response, req cmds.Request) {
h := w.Header()
// Expose our agent to allow identification
h.Set("Server", "go-ipfs/" + config.CurrentVersionNumber)
h.Set("Server", "go-ipfs/"+config.CurrentVersionNumber)

mime, err := guessMimeType(res)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down
109 changes: 109 additions & 0 deletions commands/reqlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package commands

import (
"strings"
"sync"
"time"
)

type ReqLogEntry struct {
StartTime time.Time
EndTime time.Time
Active bool
Command string
Options map[string]interface{}
Args []string
ID int

req Request
log *ReqLog
}

func (r *ReqLogEntry) Finish() {
log := r.log
log.lock.Lock()
defer log.lock.Unlock()

r.Active = false
r.EndTime = time.Now()
r.log.maybeCleanup()

// remove references to save memory
r.req = nil
r.log = nil

}

func (r *ReqLogEntry) Copy() *ReqLogEntry {
out := *r
out.log = nil
return &out
}

type ReqLog struct {
Requests []*ReqLogEntry
nextID int
lock sync.Mutex
}

func (rl *ReqLog) Add(req Request) *ReqLogEntry {
rl.lock.Lock()
defer rl.lock.Unlock()

rle := &ReqLogEntry{
StartTime: time.Now(),
Active: true,
Command: strings.Join(req.Path(), "/"),
Options: req.Options(),
Args: req.Arguments(),
ID: rl.nextID,
req: req,
log: rl,
}

rl.nextID++
rl.Requests = append(rl.Requests, rle)
return rle
}

func (rl *ReqLog) maybeCleanup() {
// only do it every so often or it might
// become a perf issue
if len(rl.Requests) == 0 {
rl.cleanup()
}
}

func (rl *ReqLog) cleanup() {
var i int
// drop all logs at are inactive and more than an hour old
for ; i < len(rl.Requests); i++ {
req := rl.Requests[i]
if req.Active || req.EndTime.Add(time.Hour).After(time.Now()) {
break
}
}

if i > 0 {
var j int
for i < len(rl.Requests) {
rl.Requests[j] = rl.Requests[i]
j++
i++
}
rl.Requests = rl.Requests[:len(rl.Requests)-i]
}
}

// Report generates a copy of all the entries in the requestlog
func (rl *ReqLog) Report() []*ReqLogEntry {
rl.lock.Lock()
defer rl.lock.Unlock()
out := make([]*ReqLogEntry, len(rl.Requests))

for i, e := range rl.Requests {
out[i] = e.Copy()
}

return out
}
1 change: 1 addition & 0 deletions commands/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type OptMap map[string]interface{}
type Context struct {
Online bool
ConfigRoot string
ReqLog *ReqLog

config *config.Config
LoadConfig func(path string) (*config.Config, error)
Expand Down
82 changes: 82 additions & 0 deletions core/commands/active.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package commands

import (
"bytes"
"fmt"
"io"
"sort"
"text/tabwriter"
"time"

cmds "github.com/ipfs/go-ipfs/commands"
)

var ActiveReqsCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List commands run on this ipfs node",
ShortDescription: `
Lists running and recently run commands.
`,
},
Run: func(req cmds.Request, res cmds.Response) {
res.SetOutput(req.InvocContext().ReqLog.Report())
},
Options: []cmds.Option{
cmds.BoolOption("v", "verbose", "print more verbose output"),
},
Marshalers: map[cmds.EncodingType]cmds.Marshaler{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
out, ok := res.Output().(*[]*cmds.ReqLogEntry)
if !ok {
log.Errorf("%#v", res.Output())
return nil, cmds.ErrIncorrectType
}
buf := new(bytes.Buffer)

verbose, _, _ := res.Request().Option("v").Bool()

w := tabwriter.NewWriter(buf, 4, 4, 2, ' ', 0)
if verbose {
fmt.Fprint(w, "ID\t")
}
fmt.Fprint(w, "Command\t")
if verbose {
fmt.Fprint(w, "Arguments\tOptions\t")
}
fmt.Fprintln(w, "Active\tStartTime\tRunTime")

for _, req := range *out {
if verbose {
fmt.Fprintf(w, "%d\t", req.ID)
}
fmt.Fprintf(w, "%s\t", req.Command)
if verbose {
fmt.Fprintf(w, "%v\t[", req.Args)
var keys []string
for k, _ := range req.Options {
keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
fmt.Fprintf(w, "%s=%v,", k, req.Options[k])
}
fmt.Fprintf(w, "]\t")
}

var live time.Duration
if req.Active {
live = time.Now().Sub(req.StartTime)
} else {
live = req.EndTime.Sub(req.StartTime)
}
t := req.StartTime.Format(time.Stamp)
fmt.Fprintf(w, "%t\t%s\t%s\n", req.Active, t, live)
}
w.Flush()

return buf, nil
},
},
Type: []*cmds.ReqLogEntry{},
}
5 changes: 3 additions & 2 deletions core/commands/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ var DiagCmd = &cmds.Command{
},

Subcommands: map[string]*cmds.Command{
"net": diagNetCmd,
"sys": sysDiagCmd,
"net": diagNetCmd,
"sys": sysDiagCmd,
"cmds": ActiveReqsCmd,
},
}

Expand Down
52 changes: 52 additions & 0 deletions test/sharness/t0065-active-requests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/sh
#
# Copyright (c) 2016 Jeromy Johnson
# MIT Licensed; see the LICENSE file in this repository.
#

test_description="Test active request commands"

. lib/test-lib.sh

test_init_ipfs
test_launch_ipfs_daemon

test_expect_success "command works" '
ipfs diag cmds > cmd_out
'

test_expect_success "invoc shows up in output" '
grep "diag/cmds" cmd_out > /dev/null
'

test_expect_success "start longer running command" '
ipfs log tail &
LOGPID=$!
'

test_expect_success "long running command shows up" '
ipfs diag cmds > cmd_out2
'

test_expect_success "output looks good" '
grep "log/tail" cmd_out2 | grep "true" > /dev/null
'

test_expect_success "kill log cmd" '
kill $LOGPID
go-sleep 0.5s
kill $LOGPID
wait $LOGPID || true
'

test_expect_success "long running command inactive" '
ipfs diag cmds > cmd_out3
'

test_expect_success "command shows up as inactive" '
grep "log/tail" cmd_out3 | grep "false"
'

test_kill_ipfs_daemon
test_done

0 comments on commit f5e7a3b

Please sign in to comment.