Skip to content

Commit 890257f

Browse files
authored
Merge pull request #1290 from ripienaar/report_leafs
Adds new "server report leafs"
2 parents 519fb95 + 40b7744 commit 890257f

File tree

3 files changed

+144
-46
lines changed

3 files changed

+144
-46
lines changed

cli/server_report_command.go

+106-5
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,23 @@ func configureServerReportCommand(srv *fisk.CmdClause) {
8484
acct := report.Command("accounts", "Report on account activity").Alias("acct").Action(c.reportAccount)
8585
acct.Arg("account", "Account to produce a report for").StringVar(&c.account)
8686
acct.Arg("limit", "Limit the responses to a certain amount of servers").IntVar(&c.waitFor)
87-
addFilterOpts(acct)
8887
acct.Flag("sort", "Sort by a specific property (in-bytes,out-bytes,in-msgs,out-msgs,conns,subs)").Default("subs").EnumVar(&c.sort, "in-bytes", "out-bytes", "in-msgs", "out-msgs", "conns", "subs")
8988
acct.Flag("top", "Limit results to the top results").Default("1000").IntVar(&c.topk)
89+
addFilterOpts(acct)
9090
acct.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
9191

9292
conns := report.Command("connections", "Report on connections").Alias("conn").Alias("connz").Alias("conns").Action(c.reportConnections)
9393
conns.Arg("limit", "Limit the responses to a certain amount of servers").IntVar(&c.waitFor)
9494
conns.Flag("account", "Limit report to a specific account").StringVar(&c.account)
95-
addFilterOpts(conns)
9695
conns.Flag("sort", "Sort by a specific property (in-bytes,out-bytes,in-msgs,out-msgs,uptime,cid,subs)").Default("subs").EnumVar(&c.sort, "in-bytes", "out-bytes", "in-msgs", "out-msgs", "uptime", "cid", "subs")
9796
conns.Flag("top", "Limit results to the top results").Default("1000").IntVar(&c.topk)
9897
conns.Flag("subject", "Limits responses only to those connections with matching subscription interest").StringVar(&c.subject)
9998
conns.Flag("username", "Limits responses only to those connections for a specific authentication username").StringVar(&c.user)
10099
conns.Flag("state", "Limits responses only to those connections that are in a specific state (open, closed, all)").PlaceHolder("STATE").Default("open").EnumVar(&c.stateFilter, "open", "closed", "all")
101100
conns.Flag("closed-reason", "Filter results based on a closed reason").PlaceHolder("REASON").StringVar(&c.filterReason)
102-
conns.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
103101
conns.Flag("filter", "Expression based filter for connections").StringVar(&c.filterExpression)
102+
addFilterOpts(conns)
103+
conns.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
104104

105105
cpu := report.Command("cpu", "Report on CPU usage").Action(c.reportCPU)
106106
addFilterOpts(cpu)
@@ -109,28 +109,129 @@ func configureServerReportCommand(srv *fisk.CmdClause) {
109109
gateways := report.Command("gateways", "Repost on Gateway (Super Cluster) connections").Alias("super").Alias("gateway").Action(c.reportGateway)
110110
gateways.Flag("filter-name", "Limits responses to a certain name").StringVar(&c.gatewayName)
111111
gateways.Flag("sort", "Sorts by a specific property (server,cluster)").Default("cluster").EnumVar(&c.sort, "server", "cluster")
112+
addFilterOpts(gateways)
112113

113114
health := report.Command("health", "Report on Server health").Action(c.reportHealth)
114115
health.Flag("js-enabled", "Checks that JetStream should be enabled on all servers").Short('J').BoolVar(&c.jsEnabled)
115116
health.Flag("server-only", "Restricts the health check to the JetStream server only, do not check streams and consumers").Short('S').BoolVar(&c.jsServerOnly)
116117
health.Flag("account", "Check only a specific Account").StringVar(&c.account)
117118
health.Flag("stream", "Check only a specific Stream").StringVar(&c.stream)
118119
health.Flag("consumer", "Check only a specific Consumer").StringVar(&c.consumer)
120+
addFilterOpts(health)
119121

120122
jsz := report.Command("jetstream", "Report on JetStream activity").Alias("jsz").Alias("js").Action(c.reportJetStream)
121123
jsz.Arg("limit", "Limit the responses to a certain amount of servers").IntVar(&c.waitFor)
122-
addFilterOpts(jsz)
123124
jsz.Flag("account", "Produce the report for a specific account").StringVar(&c.account)
124125
jsz.Flag("sort", "Sort by a specific property (name,cluster,streams,consumers,msgs,mbytes,mem,file,api,err").Default("cluster").EnumVar(&c.sort, "name", "cluster", "streams", "consumers", "msgs", "mbytes", "bytes", "mem", "file", "store", "api", "err")
125126
jsz.Flag("compact", "Compact server names").Default("true").BoolVar(&c.compact)
127+
addFilterOpts(jsz)
128+
129+
leafs := report.Command("leafnodes", "Report on Leafnode connections").Alias("leaf").Alias("leafz").Action(c.reportLeafs)
130+
leafs.Flag("account", "Produce the report for a specific account").StringVar(&c.account)
131+
leafs.Flag("sort", "Sort by a specific property (server,name,account,subs,in-bytes,out-bytes,in-msgs,out-msgs)").EnumVar(&c.sort, "server", "name", "account", "subs", "in-bytes", "out-bytes", "in-msgs", "out-msgs")
132+
addFilterOpts(leafs)
126133

127134
mem := report.Command("mem", "Report on Memory usage").Action(c.reportMem)
128135
addFilterOpts(mem)
129136
mem.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
130137

131138
routes := report.Command("routes", "Report on Route (Cluster) connections").Alias("route").Action(c.reportRoute)
132-
routes.Flag("cluster", "Limits the report to a specific cluster").StringVar(&c.cluster)
133139
routes.Flag("sort", "Sort by a specific property (server,cluster,name,account,subs,in-bytes,out-bytes)").EnumVar(&c.sort, "server", "cluster", "name", "account", "subs", "in-bytes", "out-bytes")
140+
addFilterOpts(routes)
141+
}
142+
143+
func (c *SrvReportCmd) reportLeafs(_ *fisk.ParseContext) error {
144+
nc, _, err := prepareHelper("", natsOpts()...)
145+
if err != nil {
146+
return err
147+
}
148+
149+
req := server.LeafzEventOptions{
150+
LeafzOptions: server.LeafzOptions{
151+
Account: c.account,
152+
},
153+
EventFilterOptions: c.reqFilter(),
154+
}
155+
156+
results, err := doReq(req, "$SYS.REQ.SERVER.PING.LEAFZ", c.waitFor, nc)
157+
if err != nil {
158+
return err
159+
}
160+
161+
type leaf struct {
162+
server *server.ServerInfo
163+
leafs *server.LeafInfo
164+
}
165+
166+
var leafs []*leaf
167+
for _, result := range results {
168+
s := &server.ServerAPILeafzResponse{}
169+
err := json.Unmarshal(result, s)
170+
if err != nil {
171+
return err
172+
}
173+
174+
if s.Error != nil {
175+
return fmt.Errorf("%v", s.Error.Error())
176+
}
177+
178+
for _, l := range s.Data.Leafs {
179+
leafs = append(leafs, &leaf{
180+
server: s.Server,
181+
leafs: l,
182+
})
183+
}
184+
}
185+
186+
sort.Slice(leafs, func(i, j int) bool {
187+
switch c.sort {
188+
case "name":
189+
return c.boolReverse(leafs[i].leafs.Name < leafs[j].leafs.Name)
190+
case "account":
191+
return c.boolReverse(leafs[i].leafs.Account < leafs[j].leafs.Account)
192+
case "subs":
193+
return c.boolReverse(leafs[i].leafs.NumSubs < leafs[j].leafs.NumSubs)
194+
case "in-bytes":
195+
return c.boolReverse(leafs[i].leafs.InBytes < leafs[j].leafs.InBytes)
196+
case "out-bytes":
197+
return c.boolReverse(leafs[i].leafs.OutBytes < leafs[j].leafs.OutBytes)
198+
case "in-msgs":
199+
return c.boolReverse(leafs[i].leafs.InMsgs < leafs[j].leafs.InMsgs)
200+
case "out-msgs":
201+
return c.boolReverse(leafs[i].leafs.OutMsgs < leafs[j].leafs.OutMsgs)
202+
default:
203+
return c.boolReverse(leafs[i].server.Name < leafs[j].server.Name)
204+
}
205+
})
206+
207+
tbl := iu.NewTableWriter(opts(), "Leafnode Report")
208+
tbl.AddHeaders("Server", "Name", "Account", "Address", "RTT", "Msgs In", "Msgs Out", "Bytes In", "Bytes Out", "Subs", "Compressed", "Spoke")
209+
210+
for _, lz := range leafs {
211+
acct := lz.leafs.Account
212+
if len(acct) > 23 {
213+
acct = fmt.Sprintf("%s...%s", acct[0:10], acct[len(acct)-10:])
214+
}
215+
216+
tbl.AddRow(
217+
lz.server.Name,
218+
lz.leafs.Name,
219+
acct,
220+
fmt.Sprintf("%s:%d", lz.leafs.IP, lz.leafs.Port),
221+
lz.leafs.RTT,
222+
f(lz.leafs.InMsgs),
223+
f(lz.leafs.OutMsgs),
224+
fiBytes(uint64(lz.leafs.InBytes)),
225+
fiBytes(uint64(lz.leafs.OutBytes)),
226+
f(lz.leafs.NumSubs),
227+
f(lz.leafs.Compression),
228+
f(lz.leafs.IsSpoke),
229+
)
230+
}
231+
232+
fmt.Println(tbl.Render())
233+
234+
return nil
134235
}
135236

136237
func (c *SrvReportCmd) parseRtt(rtt string, crit time.Duration) string {

go.mod

+12-13
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ require (
88
github.com/choria-io/fisk v0.6.5-0.20250211084745-e47f4036cf7f
99
github.com/choria-io/scaffold v0.0.2
1010
github.com/dustin/go-humanize v1.0.1
11-
github.com/emicklei/dot v1.6.4
11+
github.com/emicklei/dot v1.8.0
1212
github.com/expr-lang/expr v1.16.9
1313
github.com/fatih/color v1.18.0
1414
github.com/ghodss/yaml v1.0.0
15-
github.com/google/go-cmp v0.6.0
15+
github.com/google/go-cmp v0.7.0
1616
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
1717
github.com/gosuri/uiprogress v0.0.1
18-
github.com/jedib0t/go-pretty/v6 v6.6.6
18+
github.com/jedib0t/go-pretty/v6 v6.6.7
1919
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
2020
github.com/klauspost/compress v1.18.0
2121
github.com/nats-io/jsm.go v0.1.1-0.20250220111643-ccd9863e0621
@@ -24,12 +24,12 @@ require (
2424
github.com/nats-io/nats.go v1.39.1
2525
github.com/nats-io/nkeys v0.4.10
2626
github.com/nats-io/nuid v1.0.1
27-
github.com/prometheus/client_golang v1.21.0
27+
github.com/prometheus/client_golang v1.21.1
2828
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
29-
github.com/synadia-io/jwt-auth-builder.go v0.0.4
29+
github.com/synadia-io/jwt-auth-builder.go v0.0.6
3030
github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f
31-
golang.org/x/crypto v0.33.0
32-
golang.org/x/term v0.29.0
31+
golang.org/x/crypto v0.36.0
32+
golang.org/x/term v0.30.0
3333
gopkg.in/gizak/termui.v1 v1.0.0-20151021151108-e62b5929642a
3434
gopkg.in/yaml.v3 v3.0.1
3535
)
@@ -61,12 +61,11 @@ require (
6161
github.com/rivo/uniseg v0.4.7 // indirect
6262
github.com/shopspring/decimal v1.4.0 // indirect
6363
github.com/spf13/cast v1.7.1 // indirect
64-
go.uber.org/automaxprocs v1.6.0 // indirect
65-
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
66-
golang.org/x/net v0.35.0 // indirect
67-
golang.org/x/sys v0.30.0 // indirect
68-
golang.org/x/text v0.22.0 // indirect
69-
golang.org/x/time v0.10.0 // indirect
64+
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
65+
golang.org/x/net v0.37.0 // indirect
66+
golang.org/x/sys v0.31.0 // indirect
67+
golang.org/x/text v0.23.0 // indirect
68+
golang.org/x/time v0.11.0 // indirect
7069
google.golang.org/protobuf v1.36.5 // indirect
7170
gopkg.in/yaml.v2 v2.4.0 // indirect
7271
)

0 commit comments

Comments
 (0)