mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
metric-api: Add prometheus metrics
This commit is contained in:
parent
05c4f94982
commit
08e72b12da
@ -13,7 +13,7 @@ RUN set -ex \
|
|||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api,with_metric_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
4
Makefile
4
Makefile
@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_metric_api,with_quic,with_utls
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
@ -208,4 +208,4 @@ clean:
|
|||||||
update:
|
update:
|
||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
@ -118,10 +118,20 @@ func OutboundTag(detour Outbound) string {
|
|||||||
|
|
||||||
type V2RayServer interface {
|
type V2RayServer interface {
|
||||||
Service
|
Service
|
||||||
StatsService() V2RayStatsService
|
StatsService() PacketTracking
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayStatsService interface {
|
type MetricService interface {
|
||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
Service
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
PacketTracking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketTracking interface {
|
||||||
|
WithConnCounters(inbound, outbound, user string) ConnAdapter[net.Conn]
|
||||||
|
WithPacketConnCounters(inbound, outbound, user string) ConnAdapter[N.PacketConn]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnAdapter
|
||||||
|
// Transform a connection to another connection. The T should be either of
|
||||||
|
// net.Conn or N.PacketConn.
|
||||||
|
type ConnAdapter[T any] func(T) T
|
||||||
|
12
box.go
12
box.go
@ -61,6 +61,7 @@ func New(options Options) (*Box, error) {
|
|||||||
var needCacheFile bool
|
var needCacheFile bool
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
|
var needMetricAPI bool
|
||||||
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
|
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
|
||||||
needCacheFile = true
|
needCacheFile = true
|
||||||
}
|
}
|
||||||
@ -70,6 +71,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
|
if experimentalOptions.MetricAPI.Enabled() {
|
||||||
|
needMetricAPI = true
|
||||||
|
}
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if options.PlatformInterface != nil {
|
if options.PlatformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
@ -183,6 +187,14 @@ func New(options Options) (*Box, error) {
|
|||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
preServices2["v2ray api"] = v2rayServer
|
preServices2["v2ray api"] = v2rayServer
|
||||||
}
|
}
|
||||||
|
if needMetricAPI {
|
||||||
|
metricServer, err := experimental.NewMetricServer(logFactory.NewLogger("metric-api"), common.PtrValueOrDefault(experimentalOptions.MetricAPI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create metric api server")
|
||||||
|
}
|
||||||
|
router.SetMetricServer(metricServer)
|
||||||
|
preServices2["metric api"] = metricServer
|
||||||
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
inbounds: inbounds,
|
||||||
|
@ -54,7 +54,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api", "with_metric_api")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
19
docs/configuration/experimental/metric-api.md
Normal file
19
docs/configuration/experimental/metric-api.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"listen": ":8080",
|
||||||
|
"path": "/metrics"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
Prometheus metrics API listening address, disabled if empty.
|
||||||
|
|
||||||
|
#### path
|
||||||
|
|
||||||
|
Prometheus scrape path, `/metrics` will be used if empty.
|
19
docs/configuration/experimental/metric-api.zh.md
Normal file
19
docs/configuration/experimental/metric-api.zh.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"listen": ":8080",
|
||||||
|
"path": "/metrics"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### listen
|
||||||
|
|
||||||
|
Prometheus 指标监听地址,如果为空则禁用。
|
||||||
|
|
||||||
|
#### path
|
||||||
|
|
||||||
|
HTTP 路径,如果为空则使用 `/metrics`。本路径可用于 Prometheus exporter 进行抓取。
|
@ -65,6 +65,7 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||||
|
| `with_metric_api` | :material-check: | Build with Prometheus metric API support, see [Experimental](/configuration/experimental#metric-api-fields). |
|
||||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||||
|
@ -65,6 +65,7 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
|
||||||
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
|
||||||
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
|
||||||
|
| `with_metric_api` | :material-check: | Build with Prometheus metric API support, see [Experimental](/configuration/experimental#metric-api-fields). |
|
||||||
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
| `with_v2ray_api` | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
|
||||||
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
|
||||||
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |
|
||||||
|
24
experimental/metricapi.go
Normal file
24
experimental/metricapi.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package experimental
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetricServerConstructor = func(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error)
|
||||||
|
|
||||||
|
var metricServerConstructor MetricServerConstructor
|
||||||
|
|
||||||
|
func RegisterMetricServerConstructor(constructor MetricServerConstructor) {
|
||||||
|
metricServerConstructor = constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetricServer(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error) {
|
||||||
|
if metricServerConstructor == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
return metricServerConstructor(logger, options)
|
||||||
|
}
|
72
experimental/metrics/server.go
Normal file
72
experimental/metrics/server.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.MetricService = (*metricServer)(nil)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
experimental.RegisterMetricServerConstructor(NewServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type metricServer struct {
|
||||||
|
http *http.Server
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
opts option.MetricOptions
|
||||||
|
|
||||||
|
packetCountersInbound *prometheus.CounterVec
|
||||||
|
packetCountersOutbound *prometheus.CounterVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(logger log.Logger, opts option.MetricOptions) (adapter.MetricService, error) {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
_server := &http.Server{
|
||||||
|
Addr: opts.Listen,
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
if opts.Path == "" {
|
||||||
|
opts.Path = "/metrics"
|
||||||
|
}
|
||||||
|
r.Get(opts.Path, promhttp.Handler().ServeHTTP)
|
||||||
|
server := &metricServer{
|
||||||
|
http: _server,
|
||||||
|
logger: logger,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
err := server.registerMetrics()
|
||||||
|
return server, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricServer) Start() error {
|
||||||
|
if !s.opts.Enabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := s.http.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("metrics api listen error", err)
|
||||||
|
} else {
|
||||||
|
s.logger.Info("metrics api listening at ", s.http.Addr, s.opts.Path)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricServer) Close() error {
|
||||||
|
if !s.opts.Enabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return common.Close(common.PtrOrNil(s.http))
|
||||||
|
}
|
52
experimental/metrics/tracker.go
Normal file
52
experimental/metrics/tracker.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *metricServer) registerMetrics() error {
|
||||||
|
s.packetCountersInbound = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Name: "inbound_packet_bytes",
|
||||||
|
Help: "Total bytes of inbound packets",
|
||||||
|
}, []string{"inbound", "user"})
|
||||||
|
|
||||||
|
s.packetCountersOutbound = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Name: "outbound_packet_bytes",
|
||||||
|
Help: "Total bytes of outbound packets",
|
||||||
|
}, []string{"outbound", "user"})
|
||||||
|
var err error
|
||||||
|
err = prometheus.Register(s.packetCountersInbound)
|
||||||
|
err = prometheus.Register(s.packetCountersOutbound)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricServer) WithConnCounters(inbound, outbound, user string) adapter.ConnAdapter[net.Conn] {
|
||||||
|
incRead, incWrite := s.getPacketCounters(inbound, outbound, user)
|
||||||
|
return func(conn net.Conn) net.Conn {
|
||||||
|
return bufio.NewCounterConn(conn, []N.CountFunc{incRead}, []N.CountFunc{incWrite})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricServer) WithPacketConnCounters(inbound, outbound, user string) adapter.ConnAdapter[N.PacketConn] {
|
||||||
|
incRead, incWrite := s.getPacketCounters(inbound, outbound, user)
|
||||||
|
return func(conn N.PacketConn) N.PacketConn {
|
||||||
|
return bufio.NewCounterPacketConn(conn, []N.CountFunc{incRead}, []N.CountFunc{incWrite})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricServer) getPacketCounters(inbound, outbound, user string) (
|
||||||
|
readCounters N.CountFunc,
|
||||||
|
writeCounters N.CountFunc,
|
||||||
|
) {
|
||||||
|
return func(n int64) {
|
||||||
|
s.packetCountersInbound.WithLabelValues(inbound, user).Add(float64(n))
|
||||||
|
}, func(n int64) {
|
||||||
|
s.packetCountersOutbound.WithLabelValues(outbound, user).Add(float64(n))
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,6 @@ func (s *Server) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) StatsService() adapter.V2RayStatsService {
|
func (s *Server) StatsService() adapter.PacketTracking {
|
||||||
return s.statsService
|
return s.statsService
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.V2RayStatsService = (*StatsService)(nil)
|
_ adapter.PacketTracking = (*StatsService)(nil)
|
||||||
_ StatsServiceServer = (*StatsService)(nil)
|
_ StatsServiceServer = (*StatsService)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatsService struct {
|
type StatsService struct {
|
||||||
@ -60,14 +60,14 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn {
|
func (s *StatsService) getPacketCounters(inbound, outbound, user string) (incRead N.CountFunc, inWrite N.CountFunc) {
|
||||||
var readCounter []*atomic.Int64
|
var readCounter []*atomic.Int64
|
||||||
var writeCounter []*atomic.Int64
|
var writeCounter []*atomic.Int64
|
||||||
countInbound := inbound != "" && s.inbounds[inbound]
|
countInbound := inbound != "" && s.inbounds[inbound]
|
||||||
countOutbound := outbound != "" && s.outbounds[outbound]
|
countOutbound := outbound != "" && s.outbounds[outbound]
|
||||||
countUser := user != "" && s.users[user]
|
countUser := user != "" && s.users[user]
|
||||||
if !countInbound && !countOutbound && !countUser {
|
if !countInbound && !countOutbound && !countUser {
|
||||||
return conn
|
return func(n int64) {}, func(n int64) {}
|
||||||
}
|
}
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
if countInbound {
|
if countInbound {
|
||||||
@ -83,33 +83,29 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
return func(n int64) {
|
||||||
|
for _, c := range readCounter {
|
||||||
|
c.Add(n)
|
||||||
|
}
|
||||||
|
}, func(n int64) {
|
||||||
|
for _, c := range writeCounter {
|
||||||
|
c.Add(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
func (s *StatsService) WithConnCounters(inbound, outbound, user string) adapter.ConnAdapter[net.Conn] {
|
||||||
var readCounter []*atomic.Int64
|
rd, wr := s.getPacketCounters(inbound, outbound, user)
|
||||||
var writeCounter []*atomic.Int64
|
return func(conn net.Conn) net.Conn {
|
||||||
countInbound := inbound != "" && s.inbounds[inbound]
|
return bufio.NewCounterConn(conn, []N.CountFunc{rd}, []N.CountFunc{wr})
|
||||||
countOutbound := outbound != "" && s.outbounds[outbound]
|
|
||||||
countUser := user != "" && s.users[user]
|
|
||||||
if !countInbound && !countOutbound && !countUser {
|
|
||||||
return conn
|
|
||||||
}
|
}
|
||||||
s.access.Lock()
|
}
|
||||||
if countInbound {
|
|
||||||
readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink"))
|
func (s *StatsService) WithPacketConnCounters(inbound, outbound, user string) adapter.ConnAdapter[N.PacketConn] {
|
||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink"))
|
rd, wr := s.getPacketCounters(inbound, outbound, user)
|
||||||
|
return func(conn N.PacketConn) N.PacketConn {
|
||||||
|
return bufio.NewCounterPacketConn(conn, []N.CountFunc{rd}, []N.CountFunc{wr})
|
||||||
}
|
}
|
||||||
if countOutbound {
|
|
||||||
readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink"))
|
|
||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink"))
|
|
||||||
}
|
|
||||||
if countUser {
|
|
||||||
readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink"))
|
|
||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
|
||||||
}
|
|
||||||
s.access.Unlock()
|
|
||||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||||
|
5
go.mod
5
go.mod
@ -50,7 +50,7 @@ require (
|
|||||||
golang.org/x/sys v0.22.0
|
golang.org/x/sys v0.22.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.63.2
|
google.golang.org/grpc v1.63.2
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.34.2
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ require (
|
|||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/libdns/libdns v0.2.2 // indirect
|
github.com/libdns/libdns v0.2.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
@ -81,6 +81,7 @@ require (
|
|||||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.20.2 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -53,6 +53,7 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
|||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
@ -91,6 +92,8 @@ github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE
|
|||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||||
|
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
@ -213,6 +216,7 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
|||||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
5
include/metricapi.go
Normal file
5
include/metricapi.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_metric_api
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import _ "github.com/sagernet/sing-box/experimental/v2rayapi"
|
17
include/metricapi_stub.go
Normal file
17
include/metricapi_stub.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//go:build !with_metric_api
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
experimental.RegisterMetricServerConstructor(func(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error) {
|
||||||
|
return nil, E.New(`metric api is not included in this build, rebuild with -tags with_metric_api`)
|
||||||
|
})
|
||||||
|
}
|
@ -5,6 +5,7 @@ type ExperimentalOptions struct {
|
|||||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||||
Debug *DebugOptions `json:"debug,omitempty"`
|
Debug *DebugOptions `json:"debug,omitempty"`
|
||||||
|
MetricAPI *MetricOptions `json:"metrics,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFileOptions struct {
|
type CacheFileOptions struct {
|
||||||
@ -50,3 +51,12 @@ type V2RayStatsServiceOptions struct {
|
|||||||
Outbounds []string `json:"outbounds,omitempty"`
|
Outbounds []string `json:"outbounds,omitempty"`
|
||||||
Users []string `json:"users,omitempty"`
|
Users []string `json:"users,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetricOptions struct {
|
||||||
|
Listen string `json:"listen,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MetricOptions) Enabled() bool {
|
||||||
|
return m != nil && m.Listen != ""
|
||||||
|
}
|
||||||
|
@ -93,6 +93,7 @@ type Router struct {
|
|||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
|
metricServer adapter.MetricService
|
||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
needWIFIState bool
|
needWIFIState bool
|
||||||
needPackageManager bool
|
needPackageManager bool
|
||||||
@ -921,9 +922,12 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
}
|
}
|
||||||
if r.v2rayServer != nil {
|
if r.v2rayServer != nil {
|
||||||
if statsService := r.v2rayServer.StatsService(); statsService != nil {
|
if statsService := r.v2rayServer.StatsService(); statsService != nil {
|
||||||
conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
conn = statsService.WithConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.metricServer != nil {
|
||||||
|
conn = r.metricServer.WithConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
|
||||||
|
}
|
||||||
return detour.NewConnection(ctx, conn, metadata)
|
return detour.NewConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1097,9 +1101,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}
|
}
|
||||||
if r.v2rayServer != nil {
|
if r.v2rayServer != nil {
|
||||||
if statsService := r.v2rayServer.StatsService(); statsService != nil {
|
if statsService := r.v2rayServer.StatsService(); statsService != nil {
|
||||||
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn)
|
conn = statsService.WithPacketConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.metricServer != nil {
|
||||||
|
conn = r.metricServer.WithPacketConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
|
||||||
|
}
|
||||||
if metadata.FakeIP {
|
if metadata.FakeIP {
|
||||||
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
|
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
|
||||||
}
|
}
|
||||||
@ -1265,6 +1272,10 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
|||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) SetMetricServer(server adapter.MetricService) {
|
||||||
|
r.metricServer = server
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||||
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user