stat.go 5.65 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3 4 5 6
package commands

import (
	"errors"
	"fmt"
	"io"
Jan Winkelmann's avatar
Jan Winkelmann committed
7
	"os"
Jeromy's avatar
Jeromy committed
8 9
	"time"

Steven Allen's avatar
Steven Allen committed
10
	cmds "gx/ipfs/QmP9vZfc5WSjfGTXmwX2EcicMFzmZ6fXn7HTdKYat6ccmH/go-ipfs-cmds"
Jakub Sztandera's avatar
Jakub Sztandera committed
11
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Steven Allen's avatar
Steven Allen committed
12 13
	cmdkit "gx/ipfs/QmQp2a2Hhb7F6eK2A5hN8f9aJy4mtkEikL9Zj4cgB7d1dD/go-ipfs-cmdkit"
	peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer"
14
	protocol "gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol"
Steven Allen's avatar
Steven Allen committed
15
	metrics "gx/ipfs/QmaL2WYJGbWKqHoLujoi9GQ5jj4JVFrBqHUBWmEYzJPVWT/go-libp2p-metrics"
Jeromy's avatar
Jeromy committed
16 17 18
)

var StatsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
19
	Helptext: cmdkit.HelpText{
20
		Tagline: "Query IPFS statistics.",
21
		ShortDescription: `'ipfs stats' is a set of commands to help look at statistics
22
for your IPFS node.
23
`,
Jeromy's avatar
Jeromy committed
24
		LongDescription: `'ipfs stats' is a set of commands to help look at statistics
25
for your IPFS node.`,
Jeromy's avatar
Jeromy committed
26 27 28
	},

	Subcommands: map[string]*cmds.Command{
29 30 31
		"bw":      statBwCmd,
		"repo":    repoStatCmd,
		"bitswap": bitswapStatCmd,
Jeromy's avatar
Jeromy committed
32 33 34 35
	},
}

var statBwCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
36
	Helptext: cmdkit.HelpText{
Jeromy's avatar
Jeromy committed
37
		Tagline: "Print ipfs bandwidth information.",
38 39 40 41 42 43
		ShortDescription: `'ipfs stats bw' prints bandwidth information for the ipfs daemon.
It displays: TotalIn, TotalOut, RateIn, RateOut.
		`,
		LongDescription: `'ipfs stats bw' prints bandwidth information for the ipfs daemon.
It displays: TotalIn, TotalOut, RateIn, RateOut.

44 45 46 47 48
By default, overall bandwidth and all protocols are shown. To limit bandwidth
to a particular peer, use the 'peer' option along with that peer's multihash
id. To specify a specific protocol, use the 'proto' option. The 'peer' and
'proto' options cannot be specified simultaneously. The protocols that are
queried using this method are outlined in the specification:
49
https://github.com/libp2p/specs/blob/master/7-properties.md#757-protocol-multicodecs
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70

Example protocol options:
  - /ipfs/id/1.0.0
  - /ipfs/bitswap
  - /ipfs/dht

Example:

    > ipfs stats bw -t /ipfs/bitswap
    Bandwidth
    TotalIn: 5.0MB
    TotalOut: 0B
    RateIn: 343B/s
    RateOut: 0B/s
    > ipfs stats bw -p QmepgFW7BHEtU4pZJdxaNiv75mKLLRQnPi1KaaXmQN4V1a
    Bandwidth
    TotalIn: 4.9MB
    TotalOut: 12MB
    RateIn: 0B/s
    RateOut: 0B/s
`,
Jeromy's avatar
Jeromy committed
71
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
72 73 74
	Options: []cmdkit.Option{
		cmdkit.StringOption("peer", "p", "Specify a peer to print bandwidth for."),
		cmdkit.StringOption("proto", "t", "Specify a protocol to print bandwidth for."),
75
		cmdkit.BoolOption("poll", "Print bandwidth at an interval."),
Jan Winkelmann's avatar
Jan Winkelmann committed
76
		cmdkit.StringOption("interval", "i", `Time interval to wait between updating output, if 'poll' is true.
77 78

    This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are:
79
    "ns", "us" (or "µs"), "ms", "s", "m", "h".`).WithDefault("1s"),
Jeromy's avatar
Jeromy committed
80 81
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
82
	Run: func(req cmds.Request, res cmds.ResponseEmitter) {
Jeromy's avatar
Jeromy committed
83
		nd, err := req.InvocContext().GetNode()
Jeromy's avatar
Jeromy committed
84
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
85
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
86 87 88 89 90
			return
		}

		// Must be online!
		if !nd.OnlineMode() {
Jan Winkelmann's avatar
Jan Winkelmann committed
91
			res.SetError(errNotOnline, cmdkit.ErrClient)
Jeromy's avatar
Jeromy committed
92 93 94
			return
		}

95
		if nd.Reporter == nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
96
			res.SetError(fmt.Errorf("bandwidth reporter disabled in config"), cmdkit.ErrNormal)
97 98 99
			return
		}

Jeromy's avatar
Jeromy committed
100 101
		pstr, pfound, err := req.Option("peer").String()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
102
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
103 104 105 106 107
			return
		}

		tstr, tfound, err := req.Option("proto").String()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
108
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
109 110 111
			return
		}
		if pfound && tfound {
Jan Winkelmann's avatar
Jan Winkelmann committed
112
			res.SetError(errors.New("please only specify peer OR protocol"), cmdkit.ErrClient)
Jeromy's avatar
Jeromy committed
113 114 115 116 117 118 119
			return
		}

		var pid peer.ID
		if pfound {
			checkpid, err := peer.IDB58Decode(pstr)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
120
				res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
121 122 123 124 125
				return
			}
			pid = checkpid
		}

126
		timeS, _, err := req.Option("interval").String()
Jeromy's avatar
Jeromy committed
127
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
128
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
129 130
			return
		}
131 132
		interval, err := time.ParseDuration(timeS)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
133
			res.SetError(err, cmdkit.ErrNormal)
134
			return
Jeromy's avatar
Jeromy committed
135 136 137 138
		}

		doPoll, _, err := req.Option("poll").Bool()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
139
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
140 141 142
			return
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
143 144 145 146 147 148 149 150 151 152 153
		for {
			if pfound {
				stats := nd.Reporter.GetBandwidthForPeer(pid)
				res.Emit(&stats)
			} else if tfound {
				protoId := protocol.ID(tstr)
				stats := nd.Reporter.GetBandwidthForProtocol(protoId)
				res.Emit(&stats)
			} else {
				totals := nd.Reporter.GetBandwidthTotals()
				res.Emit(&totals)
Jeromy's avatar
Jeromy committed
154
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
155 156 157 158 159 160 161 162 163 164
			if !doPoll {
				return
			}
			select {
			case <-time.After(interval):
			case <-req.Context().Done():
				return
			}
		}

Jeromy's avatar
Jeromy committed
165 166
	},
	Type: metrics.Stats{},
Jan Winkelmann's avatar
Jan Winkelmann committed
167 168 169
	PostRun: cmds.PostRunMap{
		cmds.CLI: func(req cmds.Request, re cmds.ResponseEmitter) cmds.ResponseEmitter {
			reNext, res := cmds.NewChanResponsePair(req)
Jeromy's avatar
Jeromy committed
170

Jan Winkelmann's avatar
Jan Winkelmann committed
171 172
			go func() {
				defer re.Close()
Jeromy's avatar
Jeromy committed
173

Jan Winkelmann's avatar
Jan Winkelmann committed
174 175 176
				polling, _, err := res.Request().Option("poll").Bool()
				if err != nil {
					return
Jeromy's avatar
Jeromy committed
177
				}
Jan Winkelmann's avatar
Jan Winkelmann committed
178 179 180 181 182 183

				fmt.Fprintln(os.Stdout, "Total Up    Total Down  Rate Up     Rate Down")
				for {
					v, err := res.Next()
					if !cmds.HandleError(err, res, re) {
						break
Jeromy's avatar
Jeromy committed
184 185
					}

Jan Winkelmann's avatar
Jan Winkelmann committed
186 187 188 189 190 191 192 193 194 195 196 197 198
					bs := v.(*metrics.Stats)

					if !polling {
						printStats(os.Stdout, bs)
						return
					}

					fmt.Fprintf(os.Stdout, "%8s    ", humanize.Bytes(uint64(bs.TotalOut)))
					fmt.Fprintf(os.Stdout, "%8s    ", humanize.Bytes(uint64(bs.TotalIn)))
					fmt.Fprintf(os.Stdout, "%8s/s  ", humanize.Bytes(uint64(bs.RateOut)))
					fmt.Fprintf(os.Stdout, "%8s/s  \n", humanize.Bytes(uint64(bs.RateIn)))
				}
			}()
Jeromy's avatar
Jeromy committed
199

Jan Winkelmann's avatar
Jan Winkelmann committed
200
			return reNext
Jeromy's avatar
Jeromy committed
201 202 203 204 205 206 207 208 209 210 211
		},
	},
}

func printStats(out io.Writer, bs *metrics.Stats) {
	fmt.Fprintln(out, "Bandwidth")
	fmt.Fprintf(out, "TotalIn: %s\n", humanize.Bytes(uint64(bs.TotalIn)))
	fmt.Fprintf(out, "TotalOut: %s\n", humanize.Bytes(uint64(bs.TotalOut)))
	fmt.Fprintf(out, "RateIn: %s/s\n", humanize.Bytes(uint64(bs.RateIn)))
	fmt.Fprintf(out, "RateOut: %s/s\n", humanize.Bytes(uint64(bs.RateOut)))
}