api: add server web api for statistics

This commit is contained in:
fatedier
2017-03-23 02:01:25 +08:00
parent 9e683fe446
commit a4fece3f51
14 changed files with 738 additions and 426 deletions

View File

@@ -1,3 +1,17 @@
// 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 (
@@ -253,10 +267,13 @@ func (ctl *Control) stoper() {
for _, pxy := range ctl.proxies {
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
StatsCloseProxy(pxy.GetConf().GetBaseInfo().ProxyType)
}
ctl.allShutdown.Done()
ctl.conn.Info("client exit success")
StatsCloseClient()
}
func (ctl *Control) manager() {
@@ -296,6 +313,7 @@ func (ctl *Control) manager() {
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} else {
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
StatsNewProxy(m.ProxyName, m.ProxyType)
}
ctl.sendCh <- resp
case *msg.Ping:

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// 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.
@@ -22,6 +22,8 @@ import (
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/julienschmidt/httprouter"
)
var (
@@ -31,20 +33,27 @@ var (
func RunDashboardServer(addr string, port int64) (err error) {
// url router
mux := http.NewServeMux()
router := httprouter.New()
// api, see dashboard_api.go
//mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
//mux.HandleFunc("/api/proxies", apiProxies)
router.GET("/api/serverinfo", apiServerInfo)
router.GET("/api/proxy/tcp", apiProxyTcp)
router.GET("/api/proxy/udp", apiProxyUdp)
router.GET("/api/proxy/http", apiProxyHttp)
router.GET("/api/proxy/https", apiProxyHttps)
router.GET("/api/proxy/flow/:name", apiProxyFlow)
// view, see dashboard_view.go
mux.Handle("/favicon.ico", http.FileServer(assets.FileSystem))
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
//mux.HandleFunc("/", use(viewDashboard, basicAuth))
//router.GET("/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
//router.GET("/", use(viewDashboard, basicAuth))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
Addr: address,
Handler: mux,
Handler: router,
ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout,
}
@@ -64,7 +73,6 @@ func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFu
for _, m := range middleware {
h = m(h)
}
return h
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 fatedier, fatedier@gmail.com
// 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.
@@ -14,14 +14,15 @@
package server
/*
import (
"encoding/json"
"fmt"
"net/http"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/utils/log"
"github.com/julienschmidt/httprouter"
)
type GeneralResponse struct {
@@ -29,41 +30,187 @@ type GeneralResponse struct {
Msg string `json:"msg"`
}
func apiReload(w http.ResponseWriter, r *http.Request) {
var buf []byte
res := &GeneralResponse{}
// api/serverinfo
type ServerInfoResp struct {
GeneralResponse
VhostHttpPort int64 `json:"vhost_http_port"`
VhostHttpsPort int64 `json:"vhost_https_port"`
AuthTimeout int64 `json:"auth_timeout"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
TotalFlowIn int64 `json:"total_flow_in"`
TotalFlowOut int64 `json:"total_flow_out"`
CurConns int64 `json:"cur_conns"`
ClientCounts int64 `json:"client_counts"`
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
}
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res ServerInfoResp
)
defer func() {
log.Info("Http response [/api/reload]: %s", string(buf))
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/reload]")
err := ReloadConf(ConfigFile)
if err != nil {
res.Code = 2
res.Msg = fmt.Sprintf("%v", err)
log.Error("frps reload error: %v", err)
log.Info("Http request: [/api/serverinfo]")
cfg := config.ServerCommonCfg
serverStats := StatsGetServer()
res = ServerInfoResp{
VhostHttpPort: cfg.VhostHttpPort,
VhostHttpsPort: cfg.VhostHttpsPort,
AuthTimeout: cfg.AuthTimeout,
SubdomainHost: cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount,
HeartBeatTimeout: cfg.HeartBeatTimeout,
TotalFlowIn: serverStats.TotalFlowIn,
TotalFlowOut: serverStats.TotalFlowOut,
CurConns: serverStats.CurConns,
ClientCounts: serverStats.ClientCounts,
ProxyTypeCounts: serverStats.ProxyTypeCounts,
}
buf, _ = json.Marshal(res)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
type ProxiesResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Proxies []*metric.ServerMetric `json:"proxies"`
// Get proxy info.
type ProxyStatsInfo struct {
Conf config.ProxyConf `json:"conf"`
TodayFlowIn int64 `json:"today_flow_in"`
TodayFlowOut int64 `json:"today_flow_out"`
CurConns int64 `json:"cur_conns"`
Status string `json:"status"`
}
func apiProxies(w http.ResponseWriter, r *http.Request) {
var buf []byte
res := &ProxiesResponse{}
type GetProxyInfoResp struct {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"`
}
// api/proxy/tcp
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxies]: code [%d]", res.Code)
log.Info("Http response [/api/proxy/tcp]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/tcp]")
log.Info("Http request: [/api/proxies]")
res.Proxies = metric.GetAllProxyMetrics()
buf, _ = json.Marshal(res)
res.Proxies = getProxyStatsByType(consts.TcpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/udp
func apiProxyUdp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/udp]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/udp]")
res.Proxies = getProxyStatsByType(consts.UdpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/http
func apiProxyHttp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/http]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/http]")
res.Proxies = getProxyStatsByType(consts.HttpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/https
func apiProxyHttps(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/https]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/https]")
res.Proxies = getProxyStatsByType(consts.HttpsProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
proxyStats := StatsGetProxiesByType(proxyType)
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
for _, ps := range proxyStats {
proxyInfo := &ProxyStatsInfo{}
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok {
proxyInfo.Conf = pxy.GetConf()
proxyInfo.Status = consts.Online
} else {
proxyInfo.Status = consts.Offline
}
proxyInfo.TodayFlowIn = ps.TodayFlowIn
proxyInfo.TodayFlowOut = ps.TodayFlowOut
proxyInfo.CurConns = ps.CurConns
proxyInfos = append(proxyInfos, proxyInfo)
}
return
}
// api/proxy/:name/flow
type GetProxyFlowResp struct {
GeneralResponse
Name string `json:"name"`
FlowIn []int64 `json:"flow_in"`
FlowOut []int64 `json:"flow_out"`
}
func apiProxyFlow(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyFlowResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/flow/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/flow/:name]")
res.Name = name
proxyFlowInfo := StatsGetProxyFlow(name)
if proxyFlowInfo == nil {
res.Code = 1
res.Msg = "no proxy info found"
} else {
res.FlowIn = proxyFlowInfo.FlowIn
res.FlowOut = proxyFlowInfo.FlowOut
}
buf, _ = json.Marshal(&res)
w.Write(buf)
}
*/

View File

@@ -1,3 +1,17 @@
// 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 (

241
server/metric.go Normal file
View File

@@ -0,0 +1,241 @@
// 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"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/metric"
)
const (
ReserveDays = 7
)
var globalStats *ServerStatistics
type ServerStatistics struct {
TotalFlowIn metric.DateCounter
TotalFlowOut metric.DateCounter
CurConns metric.Counter
ClientCounts metric.Counter
ProxyTypeCounts map[string]metric.Counter
ProxyStatistics map[string]*ProxyStatistics
mu sync.Mutex
}
type ProxyStatistics struct {
ProxyType string
FlowIn metric.DateCounter
FlowOut metric.DateCounter
CurConns metric.Counter
}
func init() {
globalStats = &ServerStatistics{
TotalFlowIn: metric.NewDateCounter(ReserveDays),
TotalFlowOut: metric.NewDateCounter(ReserveDays),
CurConns: metric.NewCounter(),
ClientCounts: metric.NewCounter(),
ProxyTypeCounts: make(map[string]metric.Counter),
ProxyStatistics: make(map[string]*ProxyStatistics),
}
}
func StatsNewClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Inc(1)
}
}
func StatsCloseClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Dec(1)
}
}
func StatsNewProxy(name string, proxyType string) {
if config.ServerCommonCfg.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 = &ProxyStatistics{
ProxyType: proxyType,
CurConns: metric.NewCounter(),
FlowIn: metric.NewDateCounter(ReserveDays),
FlowOut: metric.NewDateCounter(ReserveDays),
}
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsCloseProxy(proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
counter.Dec(1)
}
}
}
func StatsOpenConnection(name string) {
if config.ServerCommonCfg.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 config.ServerCommonCfg.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 StatsAddFlowIn(name string, flowIn int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalFlowIn.Inc(flowIn)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.FlowIn.Inc(flowIn)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsAddFlowOut(name string, flowOut int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalFlowOut.Inc(flowOut)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.FlowOut.Inc(flowOut)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
// Functions for getting server stats.
type ServerStats struct {
TotalFlowIn int64
TotalFlowOut int64
CurConns int64
ClientCounts int64
ProxyTypeCounts map[string]int64
}
func StatsGetServer() *ServerStats {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
s := &ServerStats{
TotalFlowIn: globalStats.TotalFlowIn.TodayCount(),
TotalFlowOut: globalStats.TotalFlowOut.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
TodayFlowIn int64
TodayFlowOut int64
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,
TodayFlowIn: proxyStats.FlowIn.TodayCount(),
TodayFlowOut: proxyStats.FlowOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
}
res = append(res, ps)
}
return res
}
type ProxyFlowInfo struct {
Name string
FlowIn []int64
FlowOut []int64
}
func StatsGetProxyFlow(name string) (res *ProxyFlowInfo) {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
res = &ProxyFlowInfo{
Name: name,
}
res.FlowIn = proxyStats.FlowIn.GetLastDaysCount(ReserveDays)
res.FlowOut = proxyStats.FlowOut.GetLastDaysCount(ReserveDays)
}
return
}

View File

@@ -1,3 +1,17 @@
// 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 (
@@ -402,6 +416,11 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
}
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
tcp.Join(local, userConn)
StatsOpenConnection(pxy.GetName())
inCount, outCount := tcp.Join(local, userConn)
StatsCloseConnection(pxy.GetName())
StatsAddFlowIn(pxy.GetName(), inCount)
StatsAddFlowOut(pxy.GetName(), outCount)
pxy.Debug("join connections closed")
}

View File

@@ -1,3 +1,17 @@
// 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 (
@@ -14,6 +28,8 @@ import (
"github.com/fatedier/frp/utils/vhost"
)
var ServerService *Service
// Server service.
type Service struct {
// Accept connections from client.
@@ -172,6 +188,9 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
ctlConn.AddLogPrefix(loginMsg.RunId)
ctl.Start()
// for statistics
StatsNewClient()
return
}