diff --git a/experimental/clashapi/compatible/map.go b/experimental/clashapi/compatible/map.go index 9f4b3671..199edd89 100644 --- a/experimental/clashapi/compatible/map.go +++ b/experimental/clashapi/compatible/map.go @@ -7,6 +7,15 @@ type Map[K comparable, V any] struct { m sync.Map } +func (m *Map[K, V]) Len() int { + var count int + m.m.Range(func(key, value any) bool { + count++ + return true + }) + return count +} + func (m *Map[K, V]) Load(key K) (V, bool) { v, ok := m.m.Load(key) if !ok { diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 3a315c93..2c422b9d 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -189,6 +189,10 @@ func (s *Server) HistoryStorage() *urltest.HistoryStorage { return s.urlTestHistory } +func (s *Server) TrafficManager() *trafficontrol.Manager { + return s.trafficManager +} + func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) { tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule) return tracker, tracker diff --git a/experimental/clashapi/trafficontrol/manager.go b/experimental/clashapi/trafficontrol/manager.go index 8df624a9..17efe1ab 100644 --- a/experimental/clashapi/trafficontrol/manager.go +++ b/experimental/clashapi/trafficontrol/manager.go @@ -55,6 +55,14 @@ func (m *Manager) Now() (up int64, down int64) { return m.uploadBlip.Load(), m.downloadBlip.Load() } +func (m *Manager) Total() (up int64, down int64) { + return m.uploadTotal.Load(), m.downloadTotal.Load() +} + +func (m *Manager) Connections() int { + return m.connections.Len() +} + func (m *Manager) Snapshot() *Snapshot { var connections []tracker m.connections.Range(func(_ string, value tracker) bool { diff --git a/experimental/libbox/command_status.go b/experimental/libbox/command_status.go index 28fe5c61..d7438fae 100644 --- a/experimental/libbox/command_status.go +++ b/experimental/libbox/command_status.go @@ -7,22 +7,40 @@ import ( "time" "github.com/sagernet/sing-box/common/dialer/conntrack" + "github.com/sagernet/sing-box/experimental/clashapi" E "github.com/sagernet/sing/common/exceptions" ) type StatusMessage struct { - Memory int64 - Goroutines int32 - Connections int32 + Memory int64 + Goroutines int32 + ConnectionsIn int32 + ConnectionsOut int32 + TrafficAvailable bool + Uplink int64 + Downlink int64 + UplinkTotal int64 + DownlinkTotal int64 } -func readStatus() StatusMessage { +func (s *CommandServer) readStatus() StatusMessage { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) var message StatusMessage message.Memory = int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased) message.Goroutines = int32(runtime.NumGoroutine()) - message.Connections = int32(conntrack.Count()) + message.ConnectionsOut = int32(conntrack.Count()) + + if s.service != nil { + if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil { + message.TrafficAvailable = true + trafficManager := clashServer.(*clashapi.Server).TrafficManager() + message.Uplink, message.Downlink = trafficManager.Now() + message.UplinkTotal, message.DownlinkTotal = trafficManager.Total() + message.ConnectionsIn = int32(trafficManager.Connections()) + } + } + return message } @@ -36,7 +54,7 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error { defer ticker.Stop() ctx := connKeepAlive(conn) for { - err = binary.Write(conn, binary.BigEndian, readStatus()) + err = binary.Write(conn, binary.BigEndian, s.readStatus()) if err != nil { return err }