Add new stats metric

This commit is contained in:
9seconds
2019-10-08 11:55:57 +03:00
parent d44474012a
commit 6c7edfb7db
21 changed files with 477 additions and 510 deletions
+50
View File
@@ -0,0 +1,50 @@
package stats
import (
"net"
"github.com/9seconds/mtg/conntypes"
)
type IngressTrafficInterface interface {
IngressTraffic(int)
}
type EgressTrafficInterface interface {
EgressTraffic(int)
}
type ClientConnectedInterface interface {
ClientConnected(conntypes.ConnectionType, *net.TCPAddr)
}
type ClientDisconnectedInterface interface {
ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr)
}
type TelegramConnectedInterface interface {
TelegramConnected(conntypes.DC, *net.TCPAddr)
}
type TelegramDisconnectedInterface interface {
TelegramDisconnected(conntypes.DC, *net.TCPAddr)
}
type CrashInterface interface {
Crash()
}
type AntiReplayDetectedInterface interface {
AntiReplayDetected()
}
type Interface interface {
IngressTrafficInterface
EgressTrafficInterface
ClientConnectedInterface
ClientDisconnectedInterface
TelegramConnectedInterface
TelegramDisconnectedInterface
CrashInterface
AntiReplayDetectedInterface
}
+57
View File
@@ -0,0 +1,57 @@
package stats
import (
"net"
"github.com/9seconds/mtg/conntypes"
)
type multiStats []Interface
func (m multiStats) IngressTraffic(traffic int) {
for i := range m {
go m[i].IngressTraffic(traffic)
}
}
func (m multiStats) EgressTraffic(traffic int) {
for i := range m {
go m[i].EgressTraffic(traffic)
}
}
func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientConnected(connectionType, addr)
}
}
func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientDisconnected(connectionType, addr)
}
}
func (m multiStats) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
for i := range m {
go m[i].TelegramConnected(dc, addr)
}
}
func (m multiStats) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
for i := range m {
go m[i].TelegramDisconnected(dc, addr)
}
}
func (m multiStats) Crash() {
for i := range m {
go m[i].Crash()
}
}
func (m multiStats) AntiReplayDetected() {
for i := range m {
go m[i].AntiReplayDetected()
}
}
+5 -54
View File
@@ -7,69 +7,20 @@ import (
"net/http"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
)
type Stats interface {
IngressTraffic(int)
EgressTraffic(int)
ClientConnected(conntypes.ConnectionType, *net.TCPAddr)
ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr)
Crash()
AntiReplayDetected()
}
type multiStats []Stats
func (m multiStats) IngressTraffic(traffic int) {
for i := range m {
go m[i].IngressTraffic(traffic)
}
}
func (m multiStats) EgressTraffic(traffic int) {
for i := range m {
go m[i].EgressTraffic(traffic)
}
}
func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientConnected(connectionType, addr)
}
}
func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientDisconnected(connectionType, addr)
}
}
func (m multiStats) Crash() {
for i := range m {
go m[i].Crash()
}
}
func (m multiStats) AntiReplayDetected() {
for i := range m {
go m[i].AntiReplayDetected()
}
}
var S Stats
var Stats Interface
func Init(ctx context.Context) error {
mux := http.NewServeMux()
instanceJSON := newStatsJSON(mux)
instancePrometheus, err := newStatsPrometheus(mux)
if err != nil {
return fmt.Errorf("cannot initialize prometheus: %w", err)
}
stats := []Stats{instanceJSON, instancePrometheus}
if config.C.StatsdStats.Addr.IP != nil {
stats := []Interface{instancePrometheus}
if config.C.StatsdAddr != nil {
instanceStatsd, err := newStatsStatsd()
if err != nil {
return fmt.Errorf("cannot inialize statsd: %w", err)
@@ -77,7 +28,7 @@ func Init(ctx context.Context) error {
stats = append(stats, instanceStatsd)
}
listener, err := net.Listen("tcp", config.C.StatsAddr.String())
listener, err := net.Listen("tcp", config.C.StatsBind.String())
if err != nil {
return fmt.Errorf("cannot initialize stats server: %w", err)
}
@@ -91,7 +42,7 @@ func Init(ctx context.Context) error {
srv.Shutdown(context.Background()) // nolint: errcheck
}()
S = multiStats(stats)
Stats = multiStats(stats)
return nil
}
-131
View File
@@ -1,131 +0,0 @@
package stats
import (
"encoding/json"
"net"
"net/http"
"strconv"
"sync/atomic"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
)
type statsJSON struct {
Connections statsJSONConnections `json:"connections"`
Traffic statsJSONTraffic `json:"traffic"`
Uptime statsJSONUptime `json:"uptime"`
Crashes uint32 `json:"crashes"`
AntiReplays uint32 `json:"anti_replay_detected"`
}
type statsBaseJSONConnections struct {
All statsJSONConnectionType `json:"all"`
Abridged statsJSONConnectionType `json:"abridged"`
Intermediate statsJSONConnectionType `json:"intermediate"`
Secured statsJSONConnectionType `json:"secured"`
}
type statsJSONConnections struct {
statsBaseJSONConnections
}
type statsJSONConnectionType struct {
IPv4 uint32 `json:"ipv4"`
IPv6 uint32 `json:"ipv6"`
}
func (c statsJSONConnections) MarshalJSON() ([]byte, error) {
c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secured.IPv4
c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secured.IPv6
return json.Marshal(c.statsBaseJSONConnections)
}
type statsJSONTraffic struct {
Ingress uint64 `json:"ingress"`
Egress uint64 `json:"egress"`
}
type statsJSONUptime time.Time
func (s statsJSONUptime) MarshalJSON() ([]byte, error) {
seconds := strconv.Itoa(int(time.Since(time.Time(s)).Seconds()))
return []byte(seconds), nil
}
func (s *statsJSON) IngressTraffic(traffic int) {
atomic.AddUint64(&s.Traffic.Ingress, uint64(traffic))
}
func (s *statsJSON) EgressTraffic(traffic int) {
atomic.AddUint64(&s.Traffic.Egress, uint64(traffic))
}
func (s *statsJSON) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, 1)
}
func (s *statsJSON) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, ^uint32(0))
}
func (s *statsJSON) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value uint32) {
var connections *statsJSONConnectionType
switch connectionType {
case conntypes.ConnectionTypeAbridged:
connections = &s.Connections.Abridged
case conntypes.ConnectionTypeSecure:
connections = &s.Connections.Secured
default:
connections = &s.Connections.Intermediate
}
if addr.IP.To4() != nil {
atomic.AddUint32(&connections.IPv4, value)
} else {
atomic.AddUint32(&connections.IPv6, value)
}
}
func (s *statsJSON) Crash() {
atomic.AddUint32(&s.Crashes, 1)
}
func (s *statsJSON) AntiReplayDetected() {
atomic.AddUint32(&s.AntiReplays, 1)
}
func newStatsJSON(mux *http.ServeMux) Stats {
instance := &statsJSON{
Uptime: statsJSONUptime(time.Now()),
}
logger := zap.S().Named("stats")
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
first, err := json.Marshal(instance)
if err != nil {
logger.Errorw("Cannot encode json", "error", err)
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
return
}
interim := map[string]interface{}{}
if err := json.Unmarshal(first, &interim); err != nil {
panic(err)
}
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err := encoder.Encode(interim); err != nil {
logger.Errorw("Cannot encode json", "error", err)
}
})
return instance
}
+46 -17
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"net"
"net/http"
"strconv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -13,10 +14,11 @@ import (
)
type statsPrometheus struct {
connections *prometheus.GaugeVec
traffic *prometheus.GaugeVec
crashes prometheus.Gauge
antiReplays prometheus.Gauge
connections *prometheus.GaugeVec
telegramConnections *prometheus.GaugeVec
traffic *prometheus.GaugeVec
crashes prometheus.Gauge
antiReplays prometheus.Counter
}
func (s *statsPrometheus) IngressTraffic(traffic int) {
@@ -38,18 +40,39 @@ func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.Connection
func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType,
addr *net.TCPAddr,
increment float64) {
var labels [2]string
labels := [...]string{
"intermediate",
"ipv4",
}
switch connectionType {
case conntypes.ConnectionTypeAbridged:
labels[0] = "abridged"
case conntypes.ConnectionTypeSecure:
labels[0] = "secured"
default:
labels[0] = "intermediate"
}
labels[1] = "ipv4"
if addr.IP.To4() == nil {
labels[1] = "ipv6" // nolint: goconst
}
s.connections.WithLabelValues(labels[:]...).Add(increment)
}
func (s *statsPrometheus) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, 1.0)
}
func (s *statsPrometheus) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, -1.0)
}
func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment float64) {
labels := [...]string{
strconv.Itoa(int(dc)),
"ipv4",
}
if addr.IP.To4() == nil {
labels[1] = "ipv6"
}
@@ -65,26 +88,32 @@ func (s *statsPrometheus) AntiReplayDetected() {
s.antiReplays.Inc()
}
func newStatsPrometheus(mux *http.ServeMux) (Stats, error) {
registry := prometheus.NewRegistry()
func newStatsPrometheus(mux *http.ServeMux) (Interface, error) {
registry := prometheus.NewPedanticRegistry()
instance := &statsPrometheus{
connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.PrometheusStats.Prefix,
Namespace: config.C.StatsNamespace,
Name: "connections",
Help: "Current number of connections to the proxy.",
Help: "Current number of client connections to the proxy.",
}, []string{"type", "protocol"}),
telegramConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.StatsNamespace,
Name: "telegram_connections",
Help: "Current number of telegram connections established by this proxy.",
}, []string{"dc", "protocol"}),
traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.PrometheusStats.Prefix,
Namespace: config.C.StatsNamespace,
Name: "traffic",
Help: "Traffic passed through the proxy in bytes.",
}, []string{"direction"}),
crashes: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: config.C.PrometheusStats.Prefix,
Namespace: config.C.StatsNamespace,
Name: "crashes",
Help: "How many crashes happened.",
}),
antiReplays: prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: config.C.PrometheusStats.Prefix,
antiReplays: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: config.C.StatsNamespace,
Name: "anti_replays",
Help: "How many anti replay attacks were prevented.",
}),
@@ -104,7 +133,7 @@ func newStatsPrometheus(mux *http.ServeMux) (Stats, error) {
}
handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
mux.Handle("/prometheus", handler)
mux.Handle("/", handler)
return instance, nil
}
+36 -13
View File
@@ -3,6 +3,7 @@ package stats
import (
"fmt"
"net"
"strconv"
"strings"
"gopkg.in/alexcesaro/statsd.v2"
@@ -32,19 +33,41 @@ func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType
}
func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, value int) {
var labels [3]string
labels := [...]string{
"connections",
"intermediate",
"ipv4",
}
labels[0] = "connections"
switch connectionType {
case conntypes.ConnectionTypeAbridged:
labels[1] = "abridged"
case conntypes.ConnectionTypeSecure:
labels[1] = "secured"
default:
labels[1] = "intermediate"
}
labels[2] = "ipv4"
if addr.IP.To4() == nil {
labels[2] = "ipv6"
}
s.client.Count(strings.Join(labels[:], "."), value)
}
func (s *statsStatsd) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, 1)
}
func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, -1)
}
func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, value int) {
labels := [...]string{
"telegram",
strconv.Itoa(int(dc)),
"ipv4",
}
if addr.IP.To4() == nil {
labels[2] = "ipv6"
}
@@ -60,17 +83,17 @@ func (s *statsStatsd) AntiReplayDetected() {
s.client.Increment("anti_replays")
}
func newStatsStatsd() (Stats, error) {
func newStatsStatsd() (Interface, error) {
options := []statsd.Option{
statsd.Prefix(config.C.StatsdStats.Prefix),
statsd.Network(config.C.StatsdStats.Addr.Network()),
statsd.Address(config.C.StatsdStats.Addr.String()),
statsd.TagsFormat(config.C.StatsdStats.TagsFormat),
statsd.Prefix(config.C.StatsNamespace),
statsd.Network(config.C.StatsdNetwork),
statsd.Address(config.C.StatsBind.String()),
statsd.TagsFormat(config.C.StatsdTagsFormat),
}
if len(config.C.StatsdStats.Tags) > 0 {
tags := make([]string, len(config.C.StatsdStats.Tags)*2)
for k, v := range config.C.StatsdStats.Tags {
if len(config.C.StatsdTags) > 0 {
tags := make([]string, len(config.C.StatsdTags)*2)
for k, v := range config.C.StatsdTags {
tags = append(tags, k, v)
}
options = append(options, statsd.Tags(tags...))