diff options
author | Keuin <[email protected]> | 2024-02-13 16:34:35 +0800 |
---|---|---|
committer | Keuin <[email protected]> | 2024-02-15 01:53:10 +0800 |
commit | 36bfa4aa45c8dd16e724c36684b69d8023115b76 (patch) | |
tree | bcee878981d1af49f658b305cbf05560b129efee | |
parent | a89cbe5a93aede3703cd5981ea71827b55db0866 (diff) |
-rw-r--r-- | config/config.go | 7 | ||||
-rw-r--r-- | go.mod | 17 | ||||
-rw-r--r-- | go.sum | 42 | ||||
-rw-r--r-- | instrument/exporter.go | 149 | ||||
-rw-r--r-- | ymux.go | 64 | ||||
-rw-r--r-- | ymux.toml | 3 |
6 files changed, 259 insertions, 23 deletions
diff --git a/config/config.go b/config/config.go index 599f12c..5dfc933 100644 --- a/config/config.go +++ b/config/config.go @@ -14,8 +14,11 @@ type Config struct { Prefix string `toml:"prefix"` Proxy string `toml:"proxy"` } `toml:"servers"` - Debug bool `toml:"debug"` - Listen string `toml:"listen"` + Debug bool `toml:"debug"` + Listen string `toml:"listen"` + Metrics struct { + Enabled bool `toml:"enabled"` + } `toml:"metrics"` } func (c Config) Validate() error { @@ -8,6 +8,7 @@ require ( github.com/avast/retry-go v3.0.0+incompatible github.com/gin-gonic/gin v1.9.1 github.com/imroc/req v0.3.2 + github.com/prometheus/client_golang v1.18.0 github.com/rs/zerolog v1.32.0 github.com/samber/lo v1.39.0 github.com/samber/mo v1.11.0 @@ -15,7 +16,9 @@ require ( ) require ( + github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -29,17 +32,21 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -4,9 +4,13 @@ github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -32,8 +36,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/imroc/req v0.3.2 h1:M/JkeU6RPmX+WYvT2vaaOL0K+q8ufL5LxwvJc4xeB4o= github.com/imroc/req v0.3.2/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= @@ -42,7 +46,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= @@ -52,6 +56,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -62,7 +68,15 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -90,26 +104,26 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrument/exporter.go b/instrument/exporter.go new file mode 100644 index 0000000..a8187be --- /dev/null +++ b/instrument/exporter.go @@ -0,0 +1,149 @@ +package instrument + +import ( + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "time" +) + +const ( + infoKey = "instrument" + labelUsername = "username" + labelServerID = "serverId" +) + +func SetInstrument(c *gin.Context, info RequestInfo) { + c.Set(infoKey, info) +} + +// Exporter exports the following metrics for the yggdrasil server: +// - request_process_time_seconds +// - total_request_count +// - success_count +// - fail_count +// - logged_in_count +// - not_logged_in_count +type Exporter struct { + requestProcessTime prometheus.HistogramVec + totalRequestCount prometheus.CounterVec + successCount prometheus.CounterVec + failCount prometheus.CounterVec + loggedInCount prometheus.CounterVec + notLoggedInCount prometheus.CounterVec +} + +func (e *Exporter) initMetrics() { + e.requestProcessTime.WithLabelValues("", "") + e.totalRequestCount.WithLabelValues("", "") + e.successCount.WithLabelValues("", "") + e.failCount.WithLabelValues("", "") + e.loggedInCount.WithLabelValues("", "") + e.notLoggedInCount.WithLabelValues("", "") +} + +func (e *Exporter) Describe(descs chan<- *prometheus.Desc) { + e.requestProcessTime.Describe(descs) + e.totalRequestCount.Describe(descs) + e.successCount.Describe(descs) + e.failCount.Describe(descs) + e.loggedInCount.Describe(descs) + e.notLoggedInCount.Describe(descs) +} + +func (e *Exporter) Collect(metrics chan<- prometheus.Metric) { + e.requestProcessTime.Collect(metrics) + e.totalRequestCount.Collect(metrics) + e.successCount.Collect(metrics) + e.failCount.Collect(metrics) + e.loggedInCount.Collect(metrics) + e.notLoggedInCount.Collect(metrics) +} + +// Instrument incoming `/hasJoined` request. +// The handler must call SetInstrument before returning. +func (e *Exporter) Instrument(c *gin.Context) { + t0 := time.Now() + c.Next() + dur := time.Since(t0) + ri := c.MustGet(infoKey).(RequestInfo) + + labels := prometheus.Labels{ + labelUsername: ri.Username, + labelServerID: ri.ServerID, + } + + e.requestProcessTime.With(labels).Observe(dur.Seconds()) + e.totalRequestCount.With(labels).Inc() + if ri.Success { + e.successCount.With(labels).Inc() + if ri.LoggedIn { + e.loggedInCount.With(labels).Inc() + } else { + e.notLoggedInCount.With(labels).Inc() + } + } else { + e.failCount.With(labels).Inc() + } +} + +type RequestInfo struct { + // ProcessTime is the total time elapsed for the HTTP request + ProcessTime time.Duration + // Success is true if and only if all requests to upstreams has succeeded + Success bool + // Username is from `hasJoined` API call parameter + Username string + // ServerID is from `hasJoined` API call parameter + ServerID string + // LoggedIn is the HTTP response + LoggedIn bool +} + +func NewExporter(r prometheus.Registerer) *Exporter { + const ( + namespace = "ymux" + subsystem = "api" + ) + labels := []string{labelUsername, labelServerID} + exp := &Exporter{ + requestProcessTime: *promauto.With(r).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "request_process_time_seconds", + Help: "time used for serving one /hasJoined request", + }, labels), + totalRequestCount: *promauto.With(r).NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "total_request_count", + Help: "requests processed by this process", + }, labels), + successCount: *promauto.With(r).NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "success_count", + Help: "successful requests", + }, labels), + failCount: *promauto.With(r).NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "fail_count", + Help: "errored requests", + }, labels), + loggedInCount: *promauto.With(r).NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "logged_in_count", + Help: "requests with 204 (not logged in) /hasJoined result", + }, labels), + notLoggedInCount: *promauto.With(r).NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "not_logged_in_count", + Help: "requests with 200 (logged in) /hasJoined result", + }, labels), + } + exp.initMetrics() + return exp +} @@ -5,13 +5,21 @@ import ( "github.com/akamensky/argparse" "github.com/gin-gonic/gin" "github.com/keuin/ymux-go/config" + "github.com/keuin/ymux-go/instrument" "github.com/keuin/ymux-go/yggdrasil" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "os" + "regexp" "strings" + "time" ) +var processStartTime = time.Now() + const applicationJson = "application/json" func main() { @@ -53,7 +61,7 @@ func main() { // we need this to make them happy c.Data(200, applicationJson, []byte(`{}`)) }) - r.GET("/sessionserver/session/minecraft/hasJoined", func(c *gin.Context) { + handlers := []gin.HandlerFunc{func(c *gin.Context) { var args struct { Username string `form:"username"` ServerID string `form:"serverId"` @@ -61,12 +69,22 @@ func main() { err := c.ShouldBindQuery(&args) if err != nil { _ = c.AbortWithError(400, err) + instrument.SetInstrument(c, instrument.RequestInfo{ + Success: false, + Username: args.Username, + ServerID: args.ServerID, + }) return } r, err := s.HasJoined(args.Username, args.ServerID) if err != nil { log.Error().Err(err).Msg("ymux hasJoined API failed") _ = c.AbortWithError(500, err) + instrument.SetInstrument(c, instrument.RequestInfo{ + Success: false, + Username: args.Username, + ServerID: args.ServerID, + }) return } log.Info(). @@ -75,18 +93,60 @@ func main() { Str("yggdrasilServer", r.ServerName). Bool("hasJoined", r.HasJoined()). Msg("ymux hasJoined API OK") + instrument.SetInstrument(c, instrument.RequestInfo{ + Success: true, + Username: args.Username, + ServerID: args.ServerID, + LoggedIn: r.HasJoined(), + }) if r.HasJoined() { c.Data(200, applicationJson, r.RawBody) return } c.Status(204) - }) + }} + + // setup prometheus metrics exporter + if cfg.Metrics.Enabled { + reg := prometheus.NewRegistry() + reg.MustRegister( + collectors.NewBuildInfoCollector(), + collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), + collectors.NewGoCollector( + collectors.WithGoCollectorRuntimeMetrics( + collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")}, + ), + ), + ) + r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(reg, promhttp.HandlerOpts{ + ErrorLog: promZeroLogger{}, + ErrorHandling: promhttp.HTTPErrorOnError, + Registry: nil, + DisableCompression: true, + MaxRequestsInFlight: 0, + Timeout: 0, + EnableOpenMetrics: true, + ProcessStartTime: processStartTime, + }))) + ex := instrument.NewExporter(reg) + handlers = append([]gin.HandlerFunc{ex.Instrument}, handlers...) + log.Info().Msg("prometheus metrics exporter enabled") + } + r.GET("/sessionserver/session/minecraft/hasJoined", handlers...) + err = r.Run(cfg.Listen) if err != nil { panic(fmt.Errorf("error running http server: %w", err)) } } +type promZeroLogger struct { +} + +func (p promZeroLogger) Println(v ...interface{}) { + log.Error().Str("msg", fmt.Sprintln(v...)).Msg("prometheus metrics exporter error") +} + func createServers(cfg *config.Config) ([]yggdrasil.Server, error) { var servers []yggdrasil.Server for _, s := range cfg.Servers { @@ -1,6 +1,9 @@ debug = false listen = "127.0.0.1:32217" +[metrics] +enabled = true + [[servers]] name = "official" prefix = "https://sessionserver.mojang.com" |