master
go 197 lines 5.4 KB
Raw
1 package commands
2
3 import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "time"
9
10 cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
11
12 humanize "github.com/dustin/go-humanize"
13 cmds "github.com/ipfs/go-ipfs-cmds"
14 metrics "github.com/libp2p/go-libp2p/core/metrics"
15 peer "github.com/libp2p/go-libp2p/core/peer"
16 protocol "github.com/libp2p/go-libp2p/core/protocol"
17 )
18
19 var StatsCmd = &cmds.Command{
20 Helptext: cmds.HelpText{
21 Tagline: "Query IPFS statistics.",
22 ShortDescription: `'ipfs stats' is a set of commands to help look at statistics
23 for your IPFS node.
24 `,
25 LongDescription: `'ipfs stats' is a set of commands to help look at statistics
26 for your IPFS node.`,
27 },
28
29 Subcommands: map[string]*cmds.Command{
30 "bw": statBwCmd,
31 "repo": repoStatCmd,
32 "bitswap": bitswapStatCmd,
33 "dht": statDhtCmd,
34 "provide": statProvideCmd,
35 "reprovide": statReprovideCmd,
36 },
37 }
38
39 const (
40 statPeerOptionName = "peer"
41 statProtoOptionName = "proto"
42 statPollOptionName = "poll"
43 statIntervalOptionName = "interval"
44 )
45
46 var statBwCmd = &cmds.Command{
47 Helptext: cmds.HelpText{
48 Tagline: "Print IPFS bandwidth information.",
49 ShortDescription: `'ipfs stats bw' prints bandwidth information for the ipfs daemon.
50 It displays: TotalIn, TotalOut, RateIn, RateOut.
51 `,
52 LongDescription: `'ipfs stats bw' prints bandwidth information for the ipfs daemon.
53 It displays: TotalIn, TotalOut, RateIn, RateOut.
54
55 By default, overall bandwidth and all protocols are shown. To limit bandwidth
56 to a particular peer, use the 'peer' option along with that peer's multihash
57 id. To specify a specific protocol, use the 'proto' option. The 'peer' and
58 'proto' options cannot be specified simultaneously. The protocols that are
59 queried using this method are outlined in the specification:
60 https://github.com/libp2p/specs/blob/master/_archive/7-properties.md#757-protocol-multicodecs
61
62 Example protocol options:
63 - /ipfs/id/1.0.0
64 - /ipfs/bitswap
65 - /ipfs/dht
66
67 Example:
68
69 > ipfs stats bw -t /ipfs/bitswap
70 Bandwidth
71 TotalIn: 5.0MB
72 TotalOut: 0B
73 RateIn: 343B/s
74 RateOut: 0B/s
75 > ipfs stats bw -p QmepgFW7BHEtU4pZJdxaNiv75mKLLRQnPi1KaaXmQN4V1a
76 Bandwidth
77 TotalIn: 4.9MB
78 TotalOut: 12MB
79 RateIn: 0B/s
80 RateOut: 0B/s
81 `,
82 },
83 Options: []cmds.Option{
84 cmds.StringOption(statPeerOptionName, "p", "Specify a peer to print bandwidth for."),
85 cmds.StringOption(statProtoOptionName, "t", "Specify a protocol to print bandwidth for."),
86 cmds.BoolOption(statPollOptionName, "Print bandwidth at an interval."),
87 cmds.StringOption(statIntervalOptionName, "i", `Time interval to wait between updating output, if 'poll' is true.
88
89 This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are:
90 "ns", "us" (or "µs"), "ms", "s", "m", "h".`).WithDefault("1s"),
91 },
92
93 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
94 nd, err := cmdenv.GetNode(env)
95 if err != nil {
96 return err
97 }
98
99 // Must be online!
100 if !nd.IsOnline {
101 return cmds.Errorf(cmds.ErrClient, "unable to run offline: %s", ErrNotOnline)
102 }
103
104 if nd.Reporter == nil {
105 return errors.New("bandwidth reporter disabled in config")
106 }
107
108 pstr, pfound := req.Options[statPeerOptionName].(string)
109 tstr, tfound := req.Options["proto"].(string)
110 if pfound && tfound {
111 return cmds.Errorf(cmds.ErrClient, "please only specify peer OR protocol")
112 }
113
114 var pid peer.ID
115 if pfound {
116 checkpid, err := peer.Decode(pstr)
117 if err != nil {
118 return err
119 }
120 pid = checkpid
121 }
122
123 timeS, _ := req.Options[statIntervalOptionName].(string)
124 interval, err := time.ParseDuration(timeS)
125 if err != nil {
126 return err
127 }
128
129 doPoll, _ := req.Options[statPollOptionName].(bool)
130 for {
131 if pfound {
132 stats := nd.Reporter.GetBandwidthForPeer(pid)
133 if err := res.Emit(&stats); err != nil {
134 return err
135 }
136 } else if tfound {
137 protoID := protocol.ID(tstr)
138 stats := nd.Reporter.GetBandwidthForProtocol(protoID)
139 if err := res.Emit(&stats); err != nil {
140 return err
141 }
142 } else {
143 totals := nd.Reporter.GetBandwidthTotals()
144 if err := res.Emit(&totals); err != nil {
145 return err
146 }
147 }
148 if !doPoll {
149 return nil
150 }
151 select {
152 case <-time.After(interval):
153 case <-req.Context.Done():
154 return req.Context.Err()
155 }
156 }
157 },
158 Type: metrics.Stats{},
159 PostRun: cmds.PostRunMap{
160 cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
161 polling, _ := res.Request().Options[statPollOptionName].(bool)
162
163 if polling {
164 fmt.Fprintln(os.Stdout, "Total Up Total Down Rate Up Rate Down")
165 }
166 for {
167 v, err := res.Next()
168 if err != nil {
169 if err == io.EOF {
170 return nil
171 }
172 return err
173 }
174
175 bs := v.(*metrics.Stats)
176
177 if !polling {
178 printStats(os.Stdout, bs)
179 return nil
180 }
181
182 fmt.Fprintf(os.Stdout, "%8s ", humanize.Bytes(uint64(bs.TotalOut)))
183 fmt.Fprintf(os.Stdout, "%8s ", humanize.Bytes(uint64(bs.TotalIn)))
184 fmt.Fprintf(os.Stdout, "%8s/s ", humanize.Bytes(uint64(bs.RateOut)))
185 fmt.Fprintf(os.Stdout, "%8s/s \r", humanize.Bytes(uint64(bs.RateIn)))
186 }
187 },
188 },
189 }
190
191 func printStats(out io.Writer, bs *metrics.Stats) {
192 fmt.Fprintln(out, "Bandwidth")
193 fmt.Fprintf(out, "TotalIn: %s\n", humanize.Bytes(uint64(bs.TotalIn)))
194 fmt.Fprintf(out, "TotalOut: %s\n", humanize.Bytes(uint64(bs.TotalOut)))
195 fmt.Fprintf(out, "RateIn: %s/s\n", humanize.Bytes(uint64(bs.RateIn)))
196 fmt.Fprintf(out, "RateOut: %s/s\n", humanize.Bytes(uint64(bs.RateOut)))
197 }