github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/diag.go (about)

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