github.com/s-matyukevich/consul@v1.4.5/command/rtt/rtt.go (about) 1 package rtt 2 3 import ( 4 "flag" 5 "fmt" 6 "strings" 7 8 "github.com/hashicorp/consul/command/flags" 9 "github.com/hashicorp/consul/lib" 10 "github.com/hashicorp/serf/coordinate" 11 "github.com/mitchellh/cli" 12 ) 13 14 func New(ui cli.Ui) *cmd { 15 c := &cmd{UI: ui} 16 c.init() 17 return c 18 } 19 20 type cmd struct { 21 UI cli.Ui 22 flags *flag.FlagSet 23 http *flags.HTTPFlags 24 help string 25 26 // flags 27 wan bool 28 } 29 30 func (c *cmd) init() { 31 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 32 c.flags.BoolVar(&c.wan, "wan", false, 33 "Use WAN coordinates instead of LAN coordinates.") 34 35 c.http = &flags.HTTPFlags{} 36 flags.Merge(c.flags, c.http.ClientFlags()) 37 c.help = flags.Usage(help, c.flags) 38 } 39 40 func (c *cmd) Run(args []string) int { 41 if err := c.flags.Parse(args); err != nil { 42 return 1 43 } 44 45 // They must provide at least one node. 46 nodes := c.flags.Args() 47 if len(nodes) < 1 || len(nodes) > 2 { 48 c.UI.Error("One or two node names must be specified") 49 c.UI.Error("") 50 c.UI.Error(c.Help()) 51 return 1 52 } 53 54 // Create and test the HTTP client. 55 client, err := c.http.APIClient() 56 if err != nil { 57 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 58 return 1 59 } 60 coordClient := client.Coordinate() 61 62 var source string 63 var coord1, coord2 *coordinate.Coordinate 64 if c.wan { 65 source = "WAN" 66 67 // Default the second node to the agent if none was given. 68 if len(nodes) < 2 { 69 agent := client.Agent() 70 self, err := agent.Self() 71 if err != nil { 72 c.UI.Error(fmt.Sprintf("Unable to look up agent info: %s", err)) 73 return 1 74 } 75 76 node, dc := self["Config"]["NodeName"], self["Config"]["Datacenter"] 77 nodes = append(nodes, fmt.Sprintf("%s.%s", node, dc)) 78 } 79 80 // Parse the input nodes. 81 parts1 := strings.Split(nodes[0], ".") 82 parts2 := strings.Split(nodes[1], ".") 83 if len(parts1) != 2 || len(parts2) != 2 { 84 c.UI.Error("Node names must be specified as <node name>.<datacenter> with -wan") 85 return 1 86 } 87 node1, dc1 := parts1[0], parts1[1] 88 node2, dc2 := parts2[0], parts2[1] 89 90 // Pull all the WAN coordinates. 91 dcs, err := coordClient.Datacenters() 92 if err != nil { 93 c.UI.Error(fmt.Sprintf("Error getting coordinates: %s", err)) 94 return 1 95 } 96 97 // See if the requested nodes are in there. We only compare 98 // coordinates in the same areas. 99 var area1, area2 string 100 for _, dc := range dcs { 101 for _, entry := range dc.Coordinates { 102 if dc.Datacenter == dc1 && entry.Node == node1 { 103 area1 = dc.AreaID 104 coord1 = entry.Coord 105 } 106 if dc.Datacenter == dc2 && entry.Node == node2 { 107 area2 = dc.AreaID 108 coord2 = entry.Coord 109 } 110 111 if area1 == area2 && coord1 != nil && coord2 != nil { 112 goto SHOW_RTT 113 } 114 } 115 } 116 117 // Nil out the coordinates so we don't display across areas if 118 // we didn't find anything. 119 coord1, coord2 = nil, nil 120 } else { 121 source = "LAN" 122 123 // Default the second node to the agent if none was given. 124 if len(nodes) < 2 { 125 agent := client.Agent() 126 node, err := agent.NodeName() 127 if err != nil { 128 c.UI.Error(fmt.Sprintf("Unable to look up agent info: %s", err)) 129 return 1 130 } 131 nodes = append(nodes, node) 132 } 133 134 // Pull all the LAN coordinates. 135 entries, _, err := coordClient.Nodes(nil) 136 if err != nil { 137 c.UI.Error(fmt.Sprintf("Error getting coordinates: %s", err)) 138 return 1 139 } 140 141 // Index all the coordinates by segment. 142 cs1, cs2 := make(lib.CoordinateSet), make(lib.CoordinateSet) 143 for _, entry := range entries { 144 if entry.Node == nodes[0] { 145 cs1[entry.Segment] = entry.Coord 146 } 147 if entry.Node == nodes[1] { 148 cs2[entry.Segment] = entry.Coord 149 } 150 } 151 152 // See if there's a compatible set of coordinates. 153 coord1, coord2 = cs1.Intersect(cs2) 154 if coord1 != nil && coord2 != nil { 155 goto SHOW_RTT 156 } 157 } 158 159 // Make sure we found both coordinates. 160 if coord1 == nil { 161 c.UI.Error(fmt.Sprintf("Could not find a coordinate for node %q", nodes[0])) 162 return 1 163 } 164 if coord2 == nil { 165 c.UI.Error(fmt.Sprintf("Could not find a coordinate for node %q", nodes[1])) 166 return 1 167 } 168 169 SHOW_RTT: 170 171 // Report the round trip time. 172 dist := fmt.Sprintf("%.3f ms", coord1.DistanceTo(coord2).Seconds()*1000.0) 173 c.UI.Output(fmt.Sprintf("Estimated %s <-> %s rtt: %s (using %s coordinates)", nodes[0], nodes[1], dist, source)) 174 return 0 175 } 176 177 func (c *cmd) Synopsis() string { 178 return synopsis 179 } 180 181 func (c *cmd) Help() string { 182 return c.help 183 } 184 185 const synopsis = "Estimates network round trip time between nodes" 186 const help = ` 187 Usage: consul rtt [options] node1 [node2] 188 189 Estimates the round trip time between two nodes using Consul's network 190 coordinate model of the cluster. 191 192 At least one node name is required. If the second node name isn't given, it 193 is set to the agent's node name. Note that these are node names as known to 194 Consul as "consul members" would show, not IP addresses. 195 196 By default, the two nodes are assumed to be nodes in the local datacenter 197 and the LAN coordinates are used. If the -wan option is given, then the WAN 198 coordinates are used, and the node names must be suffixed by a period and 199 the datacenter (eg. "myserver.dc1"). 200 201 It is not possible to measure between LAN coordinates and WAN coordinates 202 because they are maintained by independent Serf gossip areas, so they are 203 not compatible. 204 `