summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.go7
-rw-r--r--go.mod17
-rw-r--r--go.sum42
-rw-r--r--instrument/exporter.go149
-rw-r--r--ymux.go64
-rw-r--r--ymux.toml3
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 {
diff --git a/go.mod b/go.mod
index f39a409..14af530 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index 3a30aca..98ef872 100644
--- a/go.sum
+++ b/go.sum
@@ -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
+}
diff --git a/ymux.go b/ymux.go
index 8b09fc8..eede160 100644
--- a/ymux.go
+++ b/ymux.go
@@ -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 {
diff --git a/ymux.toml b/ymux.toml
index d18bcfe..39ee70a 100644
--- a/ymux.toml
+++ b/ymux.toml
@@ -1,6 +1,9 @@
debug = false
listen = "127.0.0.1:32217"
+[metrics]
+enabled = true
+
[[servers]]
name = "official"
prefix = "https://sessionserver.mojang.com"