package session import ( "bufio" "fmt" "io" "net/http" "sort" "sync" "sync/atomic" "github.com/xjasonlyu/tun2socks/common/log" "github.com/xjasonlyu/tun2socks/common/queue" "github.com/xjasonlyu/tun2socks/common/stats" ) const maxCompletedSessions = 100 var ( ServeAddr = "localhost:6001" ServePath = "/stats/session/plain" StatsVersion = "" ) type simpleSessionStater struct { server *http.Server activeSessionMap sync.Map completedSessionQueue *queue.Queue } func NewSimpleSessionStater() stats.SessionStater { return &simpleSessionStater{ completedSessionQueue: queue.New(maxCompletedSessions), } } func (s *simpleSessionStater) sessionStatsHandler(resp http.ResponseWriter, req *http.Request) { // Slice of active sessions var activeSessions []*stats.Session s.activeSessionMap.Range(func(key, value interface{}) bool { activeSessions = append(activeSessions, value.(*stats.Session)) return true }) // Slice of completed sessions var completedSessions []*stats.Session for _, item := range s.completedSessionQueue.Copy() { if sess, ok := item.(*stats.Session); ok { completedSessions = append(completedSessions, sess) } } tablePrint := func(w io.Writer, sessions []*stats.Session) { // Sort by session start time. sort.Slice(sessions, func(i, j int) bool { return sessions[i].SessionStart.Sub(sessions[j].SessionStart) < 0 }) _, _ = fmt.Fprintf(w, "") _, _ = fmt.Fprintf(w, "") sort.Slice(sessions, func(i, j int) bool { return sessions[i].SessionStart.After(sessions[j].SessionStart) }) for _, sess := range sessions { _, _ = fmt.Fprintf(w, "", process(sess.Process), sess.Network, date(sess.SessionStart), duration(sess.SessionStart, sess.SessionClose), // sess.DialerAddr, sess.ClientAddr, sess.TargetAddr, byteCountSI(atomic.LoadInt64(&sess.UploadBytes)), byteCountSI(atomic.LoadInt64(&sess.DownloadBytes)), ) } _, _ = fmt.Fprintf(w, "
ProcessNetworkDateDurationClient AddrTarget AddrUploadDownload
%v%v%v%v%v%v%v%v
") } w := bufio.NewWriter(resp) _, _ = fmt.Fprintf(w, "") _, _ = fmt.Fprintf(w, `Go-tun2socks Sessions`) _, _ = fmt.Fprintf(w, "

Go-tun2socks %s

", StatsVersion) _, _ = fmt.Fprintf(w, "

Now: %s ; Uptime: %s

", now(), uptime()) _, _ = fmt.Fprintf(w, "

Active sessions %d

", len(activeSessions)) tablePrint(w, activeSessions) _, _ = fmt.Fprintf(w, "

") _, _ = fmt.Fprintf(w, "

Recently completed sessions %d

", len(completedSessions)) tablePrint(w, completedSessions) _, _ = fmt.Fprintf(w, "") _ = w.Flush() } func (s *simpleSessionStater) Start() error { log.Debugf("Start session stater") mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, ServePath, 301) }) mux.HandleFunc(ServePath, s.sessionStatsHandler) s.server = &http.Server{Addr: ServeAddr, Handler: mux} go s.server.ListenAndServe() return nil } func (s *simpleSessionStater) Stop() error { log.Debugf("Stop session stater") return s.server.Close() } func (s *simpleSessionStater) AddSession(key interface{}, session *stats.Session) { s.activeSessionMap.Store(key, session) } func (s *simpleSessionStater) GetSession(key interface{}) *stats.Session { if sess, ok := s.activeSessionMap.Load(key); ok { return sess.(*stats.Session) } return nil } func (s *simpleSessionStater) RemoveSession(key interface{}) { if sess, ok := s.activeSessionMap.Load(key); ok { // move to completed sessions s.completedSessionQueue.Put(sess) if s.completedSessionQueue.Len() > maxCompletedSessions { s.completedSessionQueue.Pop() } // delete s.activeSessionMap.Delete(key) } }