diff --git a/core/commands/diag.go b/core/commands/diag.go index e817f939f..ca4285871 100644 --- a/core/commands/diag.go +++ b/core/commands/diag.go @@ -3,11 +3,12 @@ package commands import ( "bytes" "io" + "strings" "text/template" "time" cmds "github.com/jbenet/go-ipfs/commands" - util "github.com/jbenet/go-ipfs/util" + diag "github.com/jbenet/go-ipfs/diagnostics" ) type DiagnosticConnection struct { @@ -17,6 +18,12 @@ type DiagnosticConnection struct { Count int } +var ( + visD3 = "d3" + visDot = "dot" + visFmts = []string{visD3, visDot} +) + type DiagnosticPeer struct { ID string UptimeSeconds uint64 @@ -49,6 +56,10 @@ connected peers and latencies between them. `, }, + Options: []cmds.Option{ + cmds.StringOption("vis", "output vis. one of: "+strings.Join(visFmts, ", ")), + }, + Run: func(req cmds.Request) (interface{}, error) { n, err := req.Context().GetNode() if err != nil { @@ -59,48 +70,60 @@ connected peers and latencies between them. return nil, errNotOnline } + vis, _, err := req.Option("vis").String() + if err != nil { + return nil, err + } + info, err := n.Diagnostics.GetDiagnostic(time.Second * 20) if err != nil { return nil, err } - output := make([]DiagnosticPeer, len(info)) - for i, peer := range info { - connections := make([]DiagnosticConnection, len(peer.Connections)) - for j, conn := range peer.Connections { - connections[j] = DiagnosticConnection{ - ID: conn.ID, - NanosecondsLatency: uint64(conn.Latency.Nanoseconds()), - Count: conn.Count, - } - } + switch vis { + case visD3: + return bytes.NewReader(diag.GetGraphJson(info)), nil + case visDot: + var buf bytes.Buffer + w := diag.DotWriter{W: &buf} + err := w.WriteGraph(info) + return io.Reader(&buf), err + } - output[i] = DiagnosticPeer{ - ID: peer.ID, - UptimeSeconds: uint64(peer.LifeSpan.Seconds()), - BandwidthBytesIn: peer.BwIn, - BandwidthBytesOut: peer.BwOut, - Connections: connections, + return stdDiagOutputMarshal(standardDiagOutput(info)) + }, +} + +func stdDiagOutputMarshal(output *DiagnosticOutput) (io.Reader, error) { + var buf bytes.Buffer + err := printDiagnostics(&buf, output) + if err != nil { + return nil, err + } + return &buf, nil +} + +func standardDiagOutput(info []*diag.DiagInfo) *DiagnosticOutput { + output := make([]DiagnosticPeer, len(info)) + for i, peer := range info { + connections := make([]DiagnosticConnection, len(peer.Connections)) + for j, conn := range peer.Connections { + connections[j] = DiagnosticConnection{ + ID: conn.ID, + NanosecondsLatency: uint64(conn.Latency.Nanoseconds()), + Count: conn.Count, } } - return &DiagnosticOutput{output}, nil - }, - Type: DiagnosticOutput{}, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(r cmds.Response) (io.Reader, error) { - output, ok := r.Output().(*DiagnosticOutput) - if !ok { - return nil, util.ErrCast() - } - var buf bytes.Buffer - err := printDiagnostics(&buf, output) - if err != nil { - return nil, err - } - return &buf, nil - }, - }, + output[i] = DiagnosticPeer{ + ID: peer.ID, + UptimeSeconds: uint64(peer.LifeSpan.Seconds()), + BandwidthBytesIn: peer.BwIn, + BandwidthBytesOut: peer.BwOut, + Connections: connections, + } + } + return &DiagnosticOutput{output} } func printDiagnostics(out io.Writer, info *DiagnosticOutput) error { diff --git a/diagnostics/d3/chord.html b/diagnostics/d3/chord.html new file mode 100644 index 000000000..2dade1aaa --- /dev/null +++ b/diagnostics/d3/chord.html @@ -0,0 +1,169 @@ + + + + + + diff --git a/diagnostics/d3/d3view b/diagnostics/d3/d3view new file mode 100755 index 000000000..66b0057a5 --- /dev/null +++ b/diagnostics/d3/d3view @@ -0,0 +1,24 @@ +#!/bin/sh + +# put stdin in temp file +file=`mktemp -t d3view` +cat >"$file" + +# add file to ipfs +hash=$(ipfs add -q "$file" /dev/null + +# output the url at the gateway +url="$gatewayHTTP/ipfs/$viewer#$hash" +echo "$url" diff --git a/diagnostics/d3/force.html b/diagnostics/d3/force.html new file mode 100644 index 000000000..03251ca65 --- /dev/null +++ b/diagnostics/d3/force.html @@ -0,0 +1,59 @@ + + + + +

Ipfs Visualization

+ + diff --git a/diagnostics/vis.go b/diagnostics/vis.go index def418e5c..77cf29dc5 100644 --- a/diagnostics/vis.go +++ b/diagnostics/vis.go @@ -1,6 +1,10 @@ package diagnostics -import "encoding/json" +import ( + "encoding/json" + "fmt" + "io" +) type node struct { Name string `json:"name"` @@ -19,7 +23,7 @@ func GetGraphJson(dinfo []*DiagInfo) []byte { var nodes []*node for _, di := range dinfo { names[di.ID] = len(nodes) - val := di.BwIn + di.BwOut + val := di.BwIn + di.BwOut + 10 nodes = append(nodes, &node{Name: di.ID, Value: val}) } @@ -54,3 +58,80 @@ func GetGraphJson(dinfo []*DiagInfo) []byte { return b } + +type DotWriter struct { + W io.Writer + err error +} + +// Write writes a buffer to the internal writer. +// It handles errors as in: http://blog.golang.org/errors-are-values +func (w *DotWriter) Write(buf []byte) (n int, err error) { + if w.err == nil { + n, w.err = w.W.Write(buf) + } + return n, w.err +} + +// WriteS writes a string +func (w *DotWriter) WriteS(s string) (n int, err error) { + return w.Write([]byte(s)) +} + +func (w *DotWriter) WriteNetHeader(dinfo []*DiagInfo) error { + label := fmt.Sprintf("Nodes: %d\\l", len(dinfo)) + + w.WriteS("subgraph cluster_L { ") + w.WriteS("L [shape=box fontsize=32 label=\"" + label + "\"] ") + w.WriteS("}\n") + return w.err +} + +func (w *DotWriter) WriteNode(i int, di *DiagInfo) error { + box := "[label=\"%s\n%d conns\" fontsize=8 shape=box tooltip=\"%s (%d conns)\"]" + box = fmt.Sprintf(box, di.ID, len(di.Connections), di.ID, len(di.Connections)) + + w.WriteS(fmt.Sprintf("N%d %s\n", i, box)) + return w.err +} + +func (w *DotWriter) WriteEdge(i, j int, di *DiagInfo, conn connDiagInfo) error { + + n := fmt.Sprintf("%s ... %s (%d)", di.ID, conn.ID, conn.Latency) + s := "[label=\" %d\" weight=%d tooltip=\"%s\" labeltooltip=\"%s\" style=\"dotted\"]" + s = fmt.Sprintf(s, conn.Latency, conn.Count, n, n) + + w.WriteS(fmt.Sprintf("N%d -> N%d %s\n", i, j, s)) + return w.err +} + +func (w *DotWriter) WriteGraph(dinfo []*DiagInfo) error { + w.WriteS("digraph \"diag-net\" {\n") + w.WriteNetHeader(dinfo) + + idx := make(map[string]int) + for i, di := range dinfo { + if _, found := idx[di.ID]; found { + log.Debugf("DotWriter skipped duplicate %s", di.ID) + continue + } + + idx[di.ID] = i + w.WriteNode(i, di) + } + + for i, di := range dinfo { + for _, conn := range di.Connections { + j, found := idx[conn.ID] + if !found { // if we didnt get it earlier... + j = len(idx) + idx[conn.ID] = j + } + + w.WriteEdge(i, j, di, conn) + } + } + + w.WriteS("}") + return w.err +}