stat.go 5.99 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3 4 5 6 7 8 9
package commands

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"time"

Jakub Sztandera's avatar
Jakub Sztandera committed
10
	humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
Jeromy's avatar
Jeromy committed
11 12

	cmds "github.com/ipfs/go-ipfs/commands"
Steven Allen's avatar
Steven Allen committed
13
	metrics "gx/ipfs/QmQbh3Rb7KM37As3vkHYnEFnzkVXNCP8EYGtHz6g2fXk14/go-libp2p-metrics"
14 15
	u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
	peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
16
	protocol "gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol"
Jeromy's avatar
Jeromy committed
17 18 19 20
)

var StatsCmd = &cmds.Command{
	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
`,
Jeromy's avatar
Jeromy committed
25
		LongDescription: `'ipfs stats' is a set of commands to help look at statistics
26
for your IPFS node.`,
Jeromy's avatar
Jeromy committed
27 28 29
	},

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

var statBwCmd = &cmds.Command{
	Helptext: cmds.HelpText{
Jeromy's avatar
Jeromy committed
38
		Tagline: "Print ipfs bandwidth information.",
39 40 41 42 43 44
		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.

45 46 47 48 49
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:
50
https://github.com/libp2p/specs/blob/master/7-properties.md#757-protocol-multicodecs
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

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
72 73
	},
	Options: []cmds.Option{
74 75
		cmds.StringOption("peer", "p", "Specify a peer to print bandwidth for."),
		cmds.StringOption("proto", "t", "Specify a protocol to print bandwidth for."),
76 77 78 79 80
		cmds.BoolOption("poll", "Print bandwidth at an interval.").Default(false),
		cmds.StringOption("interval", "i", `Time interval to wait between updating output, if 'poll' is true.

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

	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
84
		nd, err := req.InvocContext().GetNode()
Jeromy's avatar
Jeromy committed
85 86 87 88 89 90 91 92 93 94 95
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		// Must be online!
		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

96 97 98 99 100
		if nd.Reporter == nil {
			res.SetError(fmt.Errorf("bandwidth reporter disabled in config"), cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
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
		pstr, pfound, err := req.Option("peer").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		tstr, tfound, err := req.Option("proto").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if pfound && tfound {
			res.SetError(errors.New("please only specify peer OR protocol"), cmds.ErrClient)
			return
		}

		var pid peer.ID
		if pfound {
			checkpid, err := peer.IDB58Decode(pstr)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			pid = checkpid
		}

127
		timeS, _, err := req.Option("interval").String()
Jeromy's avatar
Jeromy committed
128 129 130 131
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
132 133 134 135
		interval, err := time.ParseDuration(timeS)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
Jeromy's avatar
Jeromy committed
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
		}

		doPoll, _, err := req.Option("poll").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))

		go func() {
			defer close(out)
			for {
				if pfound {
					stats := nd.Reporter.GetBandwidthForPeer(pid)
					out <- &stats
				} else if tfound {
					protoId := protocol.ID(tstr)
					stats := nd.Reporter.GetBandwidthForProtocol(protoId)
					out <- &stats
				} else {
					totals := nd.Reporter.GetBandwidthTotals()
					out <- &totals
				}
				if !doPoll {
					return
				}
				select {
				case <-time.After(interval):
Jeromy's avatar
Jeromy committed
166
				case <-req.Context().Done():
Jeromy's avatar
Jeromy committed
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
					return
				}
			}
		}()
	},
	Type: metrics.Stats{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			outCh, ok := res.Output().(<-chan interface{})
			if !ok {
				return nil, u.ErrCast()
			}

			polling, _, err := res.Request().Option("poll").Bool()
			if err != nil {
				return nil, err
			}

			first := true
			marshal := func(v interface{}) (io.Reader, error) {
				bs, ok := v.(*metrics.Stats)
				if !ok {
					return nil, u.ErrCast()
				}
				out := new(bytes.Buffer)
				if !polling {
					printStats(out, bs)
				} else {
					if first {
196
						fmt.Fprintln(out, "Total Up    Total Down  Rate Up     Rate Down")
Jeromy's avatar
Jeromy committed
197 198 199
						first = false
					}
					fmt.Fprint(out, "\r")
200 201 202 203 204
					// In the worst case scenario, the humanized output is of form "xxx.x xB", which is 8 characters long
					fmt.Fprintf(out, "%8s    ", humanize.Bytes(uint64(bs.TotalOut)))
					fmt.Fprintf(out, "%8s    ", humanize.Bytes(uint64(bs.TotalIn)))
					fmt.Fprintf(out, "%8s/s  ", humanize.Bytes(uint64(bs.RateOut)))
					fmt.Fprintf(out, "%8s/s  ", humanize.Bytes(uint64(bs.RateIn)))
Jeromy's avatar
Jeromy committed
205 206 207 208 209 210 211 212
				}
				return out, nil

			}

			return &cmds.ChannelMarshaler{
				Channel:   outCh,
				Marshaler: marshal,
213
				Res:       res,
Jeromy's avatar
Jeromy committed
214 215 216 217 218 219 220 221 222 223 224 225
			}, nil
		},
	},
}

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)))
}