提交 b8757d18 作者: Juan Batiz-Benet

diag/net: visualizing in d3 and dot

Try it out:

```
ipfs net diag --vis=d3 | diagnostics/d3/d3view
```

Notes: this is not the best way to do it, because it
breaks `--encoding=json`. Not sure what the best way is, and
right now this provides more utility than the other.
上级 6959b4f0
...@@ -3,11 +3,12 @@ package commands ...@@ -3,11 +3,12 @@ package commands
import ( import (
"bytes" "bytes"
"io" "io"
"strings"
"text/template" "text/template"
"time" "time"
cmds "github.com/jbenet/go-ipfs/commands" cmds "github.com/jbenet/go-ipfs/commands"
util "github.com/jbenet/go-ipfs/util" diag "github.com/jbenet/go-ipfs/diagnostics"
) )
type DiagnosticConnection struct { type DiagnosticConnection struct {
...@@ -17,6 +18,12 @@ type DiagnosticConnection struct { ...@@ -17,6 +18,12 @@ type DiagnosticConnection struct {
Count int Count int
} }
var (
visD3 = "d3"
visDot = "dot"
visFmts = []string{visD3, visDot}
)
type DiagnosticPeer struct { type DiagnosticPeer struct {
ID string ID string
UptimeSeconds uint64 UptimeSeconds uint64
...@@ -49,6 +56,10 @@ connected peers and latencies between them. ...@@ -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) { Run: func(req cmds.Request) (interface{}, error) {
n, err := req.Context().GetNode() n, err := req.Context().GetNode()
if err != nil { if err != nil {
...@@ -59,48 +70,60 @@ connected peers and latencies between them. ...@@ -59,48 +70,60 @@ connected peers and latencies between them.
return nil, errNotOnline return nil, errNotOnline
} }
info, err := n.Diagnostics.GetDiagnostic(time.Second * 20) vis, _, err := req.Option("vis").String()
if err != nil { if err != nil {
return nil, err return nil, err
} }
output := make([]DiagnosticPeer, len(info)) info, err := n.Diagnostics.GetDiagnostic(time.Second * 20)
for i, peer := range info { if err != nil {
connections := make([]DiagnosticConnection, len(peer.Connections)) return nil, err
for j, conn := range peer.Connections { }
connections[j] = DiagnosticConnection{
ID: conn.ID,
NanosecondsLatency: uint64(conn.Latency.Nanoseconds()),
Count: conn.Count,
}
}
output[i] = DiagnosticPeer{ switch vis {
ID: peer.ID, case visD3:
UptimeSeconds: uint64(peer.LifeSpan.Seconds()), return bytes.NewReader(diag.GetGraphJson(info)), nil
BandwidthBytesIn: peer.BwIn, case visDot:
BandwidthBytesOut: peer.BwOut, var buf bytes.Buffer
Connections: connections, w := diag.DotWriter{W: &buf}
} err := w.WriteGraph(info)
return io.Reader(&buf), err
} }
return &DiagnosticOutput{output}, nil return stdDiagOutputMarshal(standardDiagOutput(info))
}, },
Type: DiagnosticOutput{}, }
Marshalers: cmds.MarshalerMap{
cmds.Text: func(r cmds.Response) (io.Reader, error) { func stdDiagOutputMarshal(output *DiagnosticOutput) (io.Reader, error) {
output, ok := r.Output().(*DiagnosticOutput) var buf bytes.Buffer
if !ok { err := printDiagnostics(&buf, output)
return nil, util.ErrCast() if err != nil {
} return nil, err
var buf bytes.Buffer }
err := printDiagnostics(&buf, output) return &buf, nil
if err != nil { }
return nil, err
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 &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 { func printDiagnostics(out io.Writer, info *DiagnosticOutput) error {
......
#!/bin/sh
# put stdin in temp file
file=`mktemp -t d3view`
cat >"$file"
# add file to ipfs
hash=$(ipfs add -q "$file" </dev/null | tail -n1)
# this viewer is the hash of go-ipfs/diagnostics/d3/viewer.html
viewer="QmaY6Lq9MEhDfWUc1VfHcu9aLWSyvi4VDLvWQXLoVZ4Mau"
# the ipfs gateway to use
gatewayHTTP="http://ipfs.benet.ai:8080"
gatewayIPFS="/ip4/104.236.32.22/tcp/4001/Qme7peMbkRH8qzb9TMXSoRwVmVDZz3Z4dseRXAyBwBmxA7"
# make sure you're reachable (no NAT yet)
ipfs swarm connect "$gatewayIPFS" </dev/null >/dev/null
# output the url at the gateway
url="$gatewayHTTP/ipfs/$viewer#$hash"
echo "$url"
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<h1>Ipfs Visualization</h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var hash = window.location.hash.substring(1)
var width = 960,
height = 800;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-50)
.linkDistance(90)
.gravity(0.01)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json(hash, function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {return 1.0 + Math.log(d.value)})
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
</script>
package diagnostics package diagnostics
import "encoding/json" import (
"encoding/json"
"fmt"
"io"
)
type node struct { type node struct {
Name string `json:"name"` Name string `json:"name"`
...@@ -19,7 +23,7 @@ func GetGraphJson(dinfo []*DiagInfo) []byte { ...@@ -19,7 +23,7 @@ func GetGraphJson(dinfo []*DiagInfo) []byte {
var nodes []*node var nodes []*node
for _, di := range dinfo { for _, di := range dinfo {
names[di.ID] = len(nodes) 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}) nodes = append(nodes, &node{Name: di.ID, Value: val})
} }
...@@ -54,3 +58,80 @@ func GetGraphJson(dinfo []*DiagInfo) []byte { ...@@ -54,3 +58,80 @@ func GetGraphJson(dinfo []*DiagInfo) []byte {
return b 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
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论