diff --git a/server/events.go b/server/events.go index ba79a8473d9..8005a0d2c1a 100644 --- a/server/events.go +++ b/server/events.go @@ -21,9 +21,11 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/rand" "net/http" "runtime" + "runtime/debug" "strconv" "strings" "sync" @@ -376,6 +378,8 @@ type ServerStats struct { Gateways []*GatewayStat `json:"gateways,omitempty"` ActiveServers int `json:"active_servers,omitempty"` JetStream *JetStreamVarz `json:"jetstream,omitempty"` + MemLimit int64 `json:"gomemlimit,omitempty"` + MaxProcs int `json:"gomaxprocs,omitempty"` } // RouteStat holds route statistics. @@ -821,6 +825,10 @@ func (s *Server) updateServerUsage(v *ServerStats) { var vss int64 pse.ProcUsage(&v.CPU, &v.Mem, &vss) v.Cores = runtime.NumCPU() + v.MaxProcs = runtime.GOMAXPROCS(-1) + if mm := debug.SetMemoryLimit(-1); mm < math.MaxInt64 { + v.MemLimit = mm + } } // Generate a route stat for our statz update. diff --git a/server/events_test.go b/server/events_test.go index a470b77e64c..3dc82485869 100644 --- a/server/events_test.go +++ b/server/events_test.go @@ -24,6 +24,8 @@ import ( "net/http/httptest" "os" "reflect" + "runtime" + "runtime/debug" "strings" "sync" "sync/atomic" @@ -3771,3 +3773,34 @@ func TestServerEventsPingStatsSlowConsumersStats(t *testing.T) { }) } } + +func TestServerEventsStatszMaxProcsMemLimit(t *testing.T) { + // We want to prove that our set values are reflected in STATSZ, + // so we can't use constants that might match the system that + // the test is run on. + omp, omm := runtime.GOMAXPROCS(-1), debug.SetMemoryLimit(-1) + mp, mm := runtime.GOMAXPROCS(omp*2)*2, debug.SetMemoryLimit(omm/2)/2 + + // When we're done, put everything back. + defer runtime.GOMAXPROCS(omp) + defer debug.SetMemoryLimit(omm) + + s, opts := runTrustedServer(t) + defer s.Shutdown() + + acc, akp := createAccount(s) + s.setSystemAccount(acc) + + url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port) + ncs, err := nats.Connect(url, createUserCreds(t, s, akp)) + require_NoError(t, err) + defer ncs.Close() + + msg, err := ncs.Request("$SYS.REQ.SERVER.PING.STATSZ", nil, time.Second) + require_NoError(t, err) + + var stats ServerStatsMsg + require_NoError(t, json.Unmarshal(msg.Data, &stats)) + require_Equal(t, stats.Stats.MaxProcs, mp) + require_Equal(t, stats.Stats.MemLimit, mm) +} diff --git a/server/monitor.go b/server/monitor.go index 995c98680b9..900e5ae6970 100644 --- a/server/monitor.go +++ b/server/monitor.go @@ -23,12 +23,14 @@ import ( "encoding/json" "expvar" "fmt" + "math" "net" "net/http" "net/url" "os" "path/filepath" "runtime" + "runtime/debug" "runtime/pprof" "slices" "sort" @@ -1215,6 +1217,7 @@ type Varz struct { Mem int64 `json:"mem"` Cores int `json:"cores"` MaxProcs int `json:"gomaxprocs"` + MemLimit int64 `json:"gomemlimit,omitempty"` CPU float64 `json:"cpu"` Connections int `json:"connections"` TotalConnections uint64 `json:"total_connections"` @@ -1605,6 +1608,9 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz { TrustedOperatorsJwt: opts.operatorJWT, TrustedOperatorsClaim: opts.TrustedOperators, } + if mm := debug.SetMemoryLimit(-1); mm < math.MaxInt64 { + varz.MemLimit = mm + } // If this is a leaf without cluster, reset the cluster name (that is otherwise // set to the server name). if s.leafNoCluster {