()
| 28 | } |
| 29 | |
| 30 | func (s *Server) logStats() { |
| 31 | s.mu.Lock() |
| 32 | active := len(s.sessions) |
| 33 | clients := len(s.activity) |
| 34 | s.mu.Unlock() |
| 35 | |
| 36 | // goroutines is the headline leak indicator: each session spawns 3 |
| 37 | // (read, write, rxLoop), so under healthy operation goroutines should |
| 38 | // track ~3×active plus a small constant. A monotonic climb while active |
| 39 | // stays flat is the signature of the leak fixed in the openSession read |
| 40 | // goroutine path — keep this in the always-on stats line so regressions |
| 41 | // surface in service logs without needing pprof. |
| 42 | log.Printf("[stats] goroutines=%d active=%d clients=%d sessions(open=%d close=%d) frames(in=%d out=%d) bytes(in=%s out=%s) requests=%d dials(ok=%d fail=%d) rst_sent=%d decode_fail=%d", |
| 43 | runtime.NumGoroutine(), |
| 44 | active, clients, |
| 45 | s.stats.sessionsOpen.Load(), s.stats.sessionsClose.Load(), |
| 46 | s.stats.framesIn.Load(), s.stats.framesOut.Load(), |
| 47 | humanBytes(s.stats.bytesIn.Load()), humanBytes(s.stats.bytesOut.Load()), |
| 48 | s.stats.requests.Load(), |
| 49 | s.stats.dialsOK.Load(), s.stats.dialsFail.Load(), |
| 50 | s.stats.rstSent.Load(), |
| 51 | s.stats.decodeFailures.Load(), |
| 52 | ) |
| 53 | } |
| 54 | |
| 55 | // humanBytes formats a byte count as a short human-readable string. Mirrors |
| 56 | // the carrier's helper but kept package-local to avoid an inter-package |
no test coverage detected