diag.go 3.99 KB
Newer Older
1 2 3
package commands

import (
4
	"bytes"
5
	"errors"
6
	"io"
7
	"strings"
8
	"text/template"
9 10 11
	"time"

	cmds "github.com/jbenet/go-ipfs/commands"
12
	diag "github.com/jbenet/go-ipfs/diagnostics"
13 14 15
)

type DiagnosticConnection struct {
16 17 18
	ID string
	// TODO use milliseconds or microseconds for human readability
	NanosecondsLatency uint64
Jeromy's avatar
Jeromy committed
19
	Count              int
20 21
}

22 23 24 25 26 27
var (
	visD3   = "d3"
	visDot  = "dot"
	visFmts = []string{visD3, visDot}
)

28
type DiagnosticPeer struct {
29 30 31 32 33
	ID                string
	UptimeSeconds     uint64
	BandwidthBytesIn  uint64
	BandwidthBytesOut uint64
	Connections       []DiagnosticConnection
34 35 36 37 38 39
}

type DiagnosticOutput struct {
	Peers []DiagnosticPeer
}

40 41
var DefaultDiagnosticTimeout = time.Second * 20

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42
var DiagCmd = &cmds.Command{
43 44 45
	Helptext: cmds.HelpText{
		Tagline: "Generates diagnostic reports",
	},
Brian Tiger Chow's avatar
Brian Tiger Chow committed
46

Brian Tiger Chow's avatar
Brian Tiger Chow committed
47 48 49 50 51 52
	Subcommands: map[string]*cmds.Command{
		"net": diagNetCmd,
	},
}

var diagNetCmd = &cmds.Command{
53 54 55 56
	Helptext: cmds.HelpText{
		Tagline: "Generates a network diagnostics report",
		ShortDescription: `
Sends out a message to each node in the network recursively
57 58
requesting a listing of data about them including number of
connected peers and latencies between them.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
59
`,
60
	},
61

62
	Options: []cmds.Option{
63
		cmds.StringOption("timeout", "diagnostic timeout duration"),
64 65 66
		cmds.StringOption("vis", "output vis. one of: "+strings.Join(visFmts, ", ")),
	},

67
	Run: func(req cmds.Request, res cmds.Response) {
68 69
		n, err := req.Context().GetNode()
		if err != nil {
70 71
			res.SetError(err, cmds.ErrNormal)
			return
72
		}
73

Brian Tiger Chow's avatar
Brian Tiger Chow committed
74
		if !n.OnlineMode() {
75 76
			res.SetError(errNotOnline, cmds.ErrClient)
			return
77 78
		}

79
		vis, _, err := req.Option("vis").String()
80
		if err != nil {
81 82
			res.SetError(err, cmds.ErrNormal)
			return
83 84
		}

85 86
		timeoutS, _, err := req.Option("timeout").String()
		if err != nil {
87 88
			res.SetError(err, cmds.ErrNormal)
			return
89 90 91 92 93
		}
		timeout := DefaultDiagnosticTimeout
		if timeoutS != "" {
			t, err := time.ParseDuration(timeoutS)
			if err != nil {
94 95
				res.SetError(errors.New("error parsing timeout"), cmds.ErrNormal)
				return
96 97 98 99 100
			}
			timeout = t
		}

		info, err := n.Diagnostics.GetDiagnostic(timeout)
101
		if err != nil {
102 103
			res.SetError(err, cmds.ErrNormal)
			return
104
		}
105

106 107
		switch vis {
		case visD3:
108
			res.SetOutput(bytes.NewReader(diag.GetGraphJson(info)))
109 110 111 112
		case visDot:
			var buf bytes.Buffer
			w := diag.DotWriter{W: &buf}
			err := w.WriteGraph(info)
113 114 115 116 117
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(io.Reader(&buf))
118 119
		}

120 121 122 123 124 125
		output, err := stdDiagOutputMarshal(standardDiagOutput(info))
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(output)
126
	},
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
}

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,
147
			}
148 149 150 151 152 153 154 155 156 157 158
		}

		output[i] = DiagnosticPeer{
			ID:                peer.ID,
			UptimeSeconds:     uint64(peer.LifeSpan.Seconds()),
			BandwidthBytesIn:  peer.BwIn,
			BandwidthBytesOut: peer.BwOut,
			Connections:       connections,
		}
	}
	return &DiagnosticOutput{output}
159 160
}

161 162 163
func printDiagnostics(out io.Writer, info *DiagnosticOutput) error {
	diagTmpl := `
{{ range $peer := .Peers }}
Jeromy's avatar
Jeromy committed
164 165
ID {{ $peer.ID }} up {{ $peer.UptimeSeconds }} seconds connected to {{ len .Connections }}:{{ range $connection := .Connections }}
	ID {{ $connection.ID }} connections: {{ $connection.Count }} latency: {{ $connection.NanosecondsLatency }} ns{{ end }}
166 167 168 169 170 171 172 173 174 175 176
{{end}}
`

	templ, err := template.New("DiagnosticOutput").Parse(diagTmpl)
	if err != nil {
		return err
	}

	err = templ.Execute(out, info)
	if err != nil {
		return err
177
	}
178 179

	return nil
180
}