// Copyright 2017 fatedier, fatedier@gmail.com // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "sync" "time" "github.com/fatedier/frp/g" "github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/metric" ) const ( ReserveDays = 7 ) var globalStats *ServerStatistics type ServerStatistics struct { TotalTrafficIn metric.DateCounter TotalTrafficOut metric.DateCounter CurConns metric.Counter // counter for clients ClientCounts metric.Counter // counter for proxy types ProxyTypeCounts map[string]metric.Counter // statistics for different proxies // key is proxy name ProxyStatistics map[string]*ProxyStatistics mu sync.Mutex } type ProxyStatistics struct { Name string ProxyType string TrafficIn metric.DateCounter TrafficOut metric.DateCounter CurConns metric.Counter LastStartTime time.Time LastCloseTime time.Time } func init() { globalStats = &ServerStatistics{ TotalTrafficIn: metric.NewDateCounter(ReserveDays), TotalTrafficOut: metric.NewDateCounter(ReserveDays), CurConns: metric.NewCounter(), ClientCounts: metric.NewCounter(), ProxyTypeCounts: make(map[string]metric.Counter), ProxyStatistics: make(map[string]*ProxyStatistics), } go func() { for { time.Sleep(12 * time.Hour) log.Debug("start to clear useless proxy statistics data...") StatsClearUselessInfo() log.Debug("finish to clear useless proxy statistics data") } }() } func StatsClearUselessInfo() { // To check if there are proxies that closed than 7 days and drop them. globalStats.mu.Lock() defer globalStats.mu.Unlock() for name, data := range globalStats.ProxyStatistics { if !data.LastCloseTime.IsZero() && time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour { delete(globalStats.ProxyStatistics, name) log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String()) } } } func StatsNewClient() { if g.GlbServerCfg.DashboardPort != 0 { globalStats.ClientCounts.Inc(1) } } func StatsCloseClient() { if g.GlbServerCfg.DashboardPort != 0 { globalStats.ClientCounts.Dec(1) } } func StatsNewProxy(name string, proxyType string) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.mu.Lock() defer globalStats.mu.Unlock() counter, ok := globalStats.ProxyTypeCounts[proxyType] if !ok { counter = metric.NewCounter() } counter.Inc(1) globalStats.ProxyTypeCounts[proxyType] = counter proxyStats, ok := globalStats.ProxyStatistics[name] if !(ok && proxyStats.ProxyType == proxyType) { proxyStats = &ProxyStatistics{ Name: name, ProxyType: proxyType, CurConns: metric.NewCounter(), TrafficIn: metric.NewDateCounter(ReserveDays), TrafficOut: metric.NewDateCounter(ReserveDays), } globalStats.ProxyStatistics[name] = proxyStats } proxyStats.LastStartTime = time.Now() } } func StatsCloseProxy(proxyName string, proxyType string) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.mu.Lock() defer globalStats.mu.Unlock() if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok { counter.Dec(1) } if proxyStats, ok := globalStats.ProxyStatistics[proxyName]; ok { proxyStats.LastCloseTime = time.Now() } } } func StatsOpenConnection(name string) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.CurConns.Inc(1) globalStats.mu.Lock() defer globalStats.mu.Unlock() proxyStats, ok := globalStats.ProxyStatistics[name] if ok { proxyStats.CurConns.Inc(1) globalStats.ProxyStatistics[name] = proxyStats } } } func StatsCloseConnection(name string) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.CurConns.Dec(1) globalStats.mu.Lock() defer globalStats.mu.Unlock() proxyStats, ok := globalStats.ProxyStatistics[name] if ok { proxyStats.CurConns.Dec(1) globalStats.ProxyStatistics[name] = proxyStats } } } func StatsAddTrafficIn(name string, trafficIn int64) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.TotalTrafficIn.Inc(trafficIn) globalStats.mu.Lock() defer globalStats.mu.Unlock() proxyStats, ok := globalStats.ProxyStatistics[name] if ok { proxyStats.TrafficIn.Inc(trafficIn) globalStats.ProxyStatistics[name] = proxyStats } } } func StatsAddTrafficOut(name string, trafficOut int64) { if g.GlbServerCfg.DashboardPort != 0 { globalStats.TotalTrafficOut.Inc(trafficOut) globalStats.mu.Lock() defer globalStats.mu.Unlock() proxyStats, ok := globalStats.ProxyStatistics[name] if ok { proxyStats.TrafficOut.Inc(trafficOut) globalStats.ProxyStatistics[name] = proxyStats } } } // Functions for getting server stats. type ServerStats struct { TotalTrafficIn int64 TotalTrafficOut int64 CurConns int64 ClientCounts int64 ProxyTypeCounts map[string]int64 } func StatsGetServer() *ServerStats { globalStats.mu.Lock() defer globalStats.mu.Unlock() s := &ServerStats{ TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(), TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(), CurConns: globalStats.CurConns.Count(), ClientCounts: globalStats.ClientCounts.Count(), ProxyTypeCounts: make(map[string]int64), } for k, v := range globalStats.ProxyTypeCounts { s.ProxyTypeCounts[k] = v.Count() } return s } type ProxyStats struct { Name string Type string TodayTrafficIn int64 TodayTrafficOut int64 LastStartTime string LastCloseTime string CurConns int64 } func StatsGetProxiesByType(proxyType string) []*ProxyStats { res := make([]*ProxyStats, 0) globalStats.mu.Lock() defer globalStats.mu.Unlock() for name, proxyStats := range globalStats.ProxyStatistics { if proxyStats.ProxyType != proxyType { continue } ps := &ProxyStats{ Name: name, Type: proxyStats.ProxyType, TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), CurConns: proxyStats.CurConns.Count(), } if !proxyStats.LastStartTime.IsZero() { ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") } if !proxyStats.LastCloseTime.IsZero() { ps.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") } res = append(res, ps) } return res } func StatsGetProxiesByTypeAndName(proxyType string, proxyName string) (res *ProxyStats) { globalStats.mu.Lock() defer globalStats.mu.Unlock() for name, proxyStats := range globalStats.ProxyStatistics { if proxyStats.ProxyType != proxyType { continue } if name != proxyName { continue } res = &ProxyStats{ Name: name, Type: proxyStats.ProxyType, TodayTrafficIn: proxyStats.TrafficIn.TodayCount(), TodayTrafficOut: proxyStats.TrafficOut.TodayCount(), CurConns: proxyStats.CurConns.Count(), } if !proxyStats.LastStartTime.IsZero() { res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05") } if !proxyStats.LastCloseTime.IsZero() { res.LastCloseTime = proxyStats.LastCloseTime.Format("01-02 15:04:05") } break } return } type ProxyTrafficInfo struct { Name string TrafficIn []int64 TrafficOut []int64 } func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) { globalStats.mu.Lock() defer globalStats.mu.Unlock() proxyStats, ok := globalStats.ProxyStatistics[name] if ok { res = &ProxyTrafficInfo{ Name: name, } res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays) res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays) } return }