forked from ipfs/kubo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
diag.go
205 lines (174 loc) · 4.95 KB
/
diag.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package commands
import (
"bytes"
"errors"
"io"
"strings"
"text/template"
"time"
cmds "github.com/ipfs/go-ipfs/commands"
diag "github.com/ipfs/go-ipfs/diagnostics"
)
type DiagnosticConnection struct {
ID string
// TODO use milliseconds or microseconds for human readability
NanosecondsLatency uint64
Count int
}
var (
visD3 = "d3"
visDot = "dot"
visFmts = []string{visD3, visDot}
)
type DiagnosticPeer struct {
ID string
UptimeSeconds uint64
BandwidthBytesIn uint64
BandwidthBytesOut uint64
Connections []DiagnosticConnection
}
type DiagnosticOutput struct {
Peers []DiagnosticPeer
}
var DefaultDiagnosticTimeout = time.Second * 20
var DiagCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Generates diagnostic reports",
},
Subcommands: map[string]*cmds.Command{
"net": diagNetCmd,
},
}
var diagNetCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Generates a network diagnostics report",
ShortDescription: `
Sends out a message to each node in the network recursively
requesting a listing of data about them including number of
connected peers and latencies between them.
The given timeout will be decremented 2s at every network hop,
ensuring peers try to return their diagnostics before the initiator's
timeout. If the timeout is too small, some peers may not be reached.
30s and 60s are reasonable timeout values, though network vary.
The default timeout is 20 seconds.
The 'vis' option may be used to change the output format.
four formats are supported:
* plain text - easy to read
* d3 - json ready to be fed into d3view
* dot - graphviz format
The d3 format will output a json object ready to be consumed by
the chord network viewer, available at the following hash:
/ipfs/QmbesKpGyQGd5jtJFUGEB1ByPjNFpukhnKZDnkfxUiKn38
To view your diag output, 'ipfs add' the d3 vis output, and
open the following link:
http://gateway.ipfs.io/ipfs/QmbesKpGyQGd5jtJFUGEB1ByPjNFpukhnKZDnkfxUiKn38/chord#<your hash>
The dot format can be fed into graphviz and other programs
that consume the dot format to generate graphs of the network.
`,
},
Options: []cmds.Option{
cmds.StringOption("timeout", "diagnostic timeout duration"),
cmds.StringOption("vis", "output vis. one of: "+strings.Join(visFmts, ", ")),
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.Context().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
if !n.OnlineMode() {
res.SetError(errNotOnline, cmds.ErrClient)
return
}
vis, _, err := req.Option("vis").String()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
timeoutS, _, err := req.Option("timeout").String()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
timeout := DefaultDiagnosticTimeout
if timeoutS != "" {
t, err := time.ParseDuration(timeoutS)
if err != nil {
res.SetError(errors.New("error parsing timeout"), cmds.ErrNormal)
return
}
timeout = t
}
info, err := n.Diagnostics.GetDiagnostic(timeout)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
switch vis {
case visD3:
res.SetOutput(bytes.NewReader(diag.GetGraphJson(info)))
case visDot:
var buf bytes.Buffer
w := diag.DotWriter{W: &buf}
err := w.WriteGraph(info)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(io.Reader(&buf))
default:
output, err := stdDiagOutputMarshal(standardDiagOutput(info))
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(output)
}
},
}
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,
}
}
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 {
diagTmpl := `
{{ range $peer := .Peers }}
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 }}
{{end}}
`
templ, err := template.New("DiagnosticOutput").Parse(diagTmpl)
if err != nil {
return err
}
err = templ.Execute(out, info)
if err != nil {
return err
}
return nil
}