Skip to content

Commit 2cb1910

Browse files
authored
Merge pull request #874 from ripienaar/connz_filter
Support filtering connections using expr in report connz
2 parents 6738260 + 1725c87 commit 2cb1910

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

cli/cheats/server.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ nats server report connz --account WEATHER
1414
nats server report connz --sort in-msgs
1515
nats server report connz --top 10 --sort in-msgs
1616

17+
# To limit connections report to surveyor connections and all from a specific IP using https://expr.medv.io/docs/Language-Definition
18+
nats server report connz --filter 'lower(conns.name) matches "surveyor" || conns.ip == "46.101.44.80"'
19+
1720
# To report on accounts
1821
nats server report accounts
1922
nats server report accounts --account WEATHER --sort in-msgs --top 10

cli/server_report_command.go

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"fmt"
1919
"sort"
2020

21+
"github.com/antonmedv/expr"
2122
"github.com/choria-io/fisk"
2223
"github.com/dustin/go-humanize"
2324
"github.com/fatih/color"
@@ -28,16 +29,17 @@ import (
2829
type SrvReportCmd struct {
2930
json bool
3031

31-
account string
32-
waitFor int
33-
sort string
34-
topk int
35-
reverse bool
36-
compact bool
37-
subject string
38-
server string
39-
cluster string
40-
tags []string
32+
filterExpression string
33+
account string
34+
waitFor int
35+
sort string
36+
topk int
37+
reverse bool
38+
compact bool
39+
subject string
40+
server string
41+
cluster string
42+
tags []string
4143
}
4244

4345
type srvReportAccountInfo struct {
@@ -72,6 +74,7 @@ func configureServerReportCommand(srv *fisk.CmdClause) {
7274
conns.Flag("top", "Limit results to the top results").Default("1000").IntVar(&c.topk)
7375
conns.Flag("subject", "Limits responses only to those connections with matching subscription interest").StringVar(&c.subject)
7476
conns.Flag("json", "Produce JSON output").Short('j').UnNegatableBoolVar(&c.json)
77+
conns.Flag("filter", "Expression based filter for connections").StringVar(&c.filterExpression)
7578

7679
acct := report.Command("accounts", "Report on account activity").Alias("acct").Action(c.reportAccount)
7780
acct.Arg("account", "Account to produce a report for").StringVar(&c.account)
@@ -492,7 +495,7 @@ func (c *SrvReportCmd) reportConnections(_ *fisk.ParseContext) error {
492495
return fmt.Errorf("did not get results from any servers")
493496
}
494497

495-
conns := connz.flatConnInfo()
498+
conns := connz.flatConnInfo(c.filterExpression)
496499

497500
if c.json {
498501
printJSON(conns)
@@ -624,10 +627,39 @@ func (c *SrvReportCmd) renderConnections(report []connInfo) {
624627

625628
type connzList []*server.ServerAPIConnzResponse
626629

627-
func (c connzList) flatConnInfo() []connInfo {
630+
func (c connzList) flatConnInfo(filter string) []connInfo {
628631
var conns []connInfo
629632
for _, conn := range c {
633+
srv := structWithoutOmitEmpty(*conn.Server)
634+
630635
for _, c := range conn.Data.Conns {
636+
if filter != "" {
637+
ci := structWithoutOmitEmpty(*c)
638+
env := map[string]any{
639+
"server": srv,
640+
"Server": conn.Server,
641+
"conns": ci,
642+
"Conns": c,
643+
}
644+
645+
program, err := expr.Compile(filter, expr.Env(env), expr.AsBool())
646+
fisk.FatalIfError(err, "Invalid expression: %v", err)
647+
648+
out, err := expr.Run(program, env)
649+
if err != nil {
650+
fisk.FatalIfError(err, "Invalid expression: %v", err)
651+
}
652+
653+
should, ok := out.(bool)
654+
if !ok {
655+
fisk.FatalIfError(err, "expression did not return a boolean")
656+
}
657+
658+
if !should {
659+
continue
660+
}
661+
}
662+
631663
conns = append(conns, connInfo{c, conn.Server})
632664
}
633665
}

cli/util.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"net/textproto"
2828
"os"
2929
"os/exec"
30+
"reflect"
3031
"regexp"
3132
"sort"
3233
"strconv"
@@ -1324,3 +1325,38 @@ func filterDataThroughCmd(data []byte, filter, subject, stream string) ([]byte,
13241325
// maybe we want to do something on error?
13251326
return runner.CombinedOutput()
13261327
}
1328+
1329+
// given a non pointer instance of a type with a lot of omitempty json tags will return a new instance without those
1330+
//
1331+
// does not handle nested values
1332+
func structWithoutOmitEmpty(s any) any {
1333+
st := reflect.TypeOf(s)
1334+
1335+
// It's a pointer struct, convert to the value that it points to.
1336+
if st.Kind() == reflect.Ptr {
1337+
st = st.Elem()
1338+
}
1339+
1340+
fs := []reflect.StructField{}
1341+
for i := 0; i < st.NumField(); i++ {
1342+
field := st.Field(i)
1343+
field.Tag = reflect.StructTag(strings.ReplaceAll(string(field.Tag), ",omitempty", ""))
1344+
fs = append(fs, field)
1345+
}
1346+
1347+
st2 := reflect.StructOf(fs)
1348+
v := reflect.ValueOf(s)
1349+
1350+
j, err := json.Marshal(v.Convert(st2).Interface())
1351+
if err != nil {
1352+
panic(err)
1353+
}
1354+
1355+
var res any
1356+
err = json.Unmarshal(j, &res)
1357+
if err != nil {
1358+
panic(err)
1359+
}
1360+
1361+
return res
1362+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ require (
4747
github.com/prometheus/client_model v0.4.0 // indirect
4848
github.com/prometheus/procfs v0.11.1 // indirect
4949
github.com/rivo/uniseg v0.4.4 // indirect
50+
github.com/sevlyar/retag v0.0.0-20190429052747-c3f10e304082 // indirect
5051
golang.org/x/net v0.15.0 // indirect
5152
golang.org/x/sys v0.12.0 // indirect
5253
golang.org/x/text v0.13.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
112112
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
113113
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
114114
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
115+
github.com/sevlyar/retag v0.0.0-20190429052747-c3f10e304082 h1:fj05fHX+p6w6xqPfvEjFtdu95JwguF0Kg1cz/sht8+U=
116+
github.com/sevlyar/retag v0.0.0-20190429052747-c3f10e304082/go.mod h1:mOWh3Kdot9kBKCLbKcJTzIBBEPKRJAq2lk03eVVDmco=
115117
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
116118
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
117119
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

0 commit comments

Comments
 (0)