ping-monitoring

A prometheus exporter to monitor matrix federation with using the existing pingbot infra.
git clone git://archive.git.mtrnord.blog/MTRNord/ping-monitoring.git
Log | Files | Refs | LICENSE

commit 0db4cd33a4ac99f2255e4bc3bd4822259447e24d
parent 4ceac52b5bd117c006c1dc34a285d70f6d019875
Author: MTRNord <mtrnord1@gmail.com>
Date:   Thu, 15 Feb 2024 17:57:25 +0100

Implement basic ping logic and pong logic

Diffstat:
Mconfig.go | 36++++++++++++++++++++++++++++++++----
Mgo.mod | 21+++++++++++++++------
Mgo.sum | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mmain.go | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mping_json.go | 22+++++++++++-----------
Mping_manager.go | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 323 insertions(+), 30 deletions(-)

diff --git a/config.go b/config.go @@ -5,12 +5,15 @@ import ( "github.com/goccy/go-yaml" log "github.com/sirupsen/logrus" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/id" ) type MatrixConfig struct { Homeserver string `yaml:"homeserver"` Username string `yaml:"username"` Password string `yaml:"password"` + Client *mautrix.Client } type Config struct { @@ -18,10 +21,11 @@ type Config struct { OwnHomeserver MatrixConfig `yaml:"own_homeserver"` RemoteHomeservers []MatrixConfig `yaml:"remote_homeservers"` PingRoom string `yaml:"ping_room"` - PingRateSeconds int `yaml:"ping_rate_seconds"` - PingThresholdSeconds int `yaml:"ping_threshold_seconds"` - PingJsonURL string `yaml:"ping_json_url"` - BlacklistedHomeservers []string `yaml:"blacklisted_homeservers"` + PingRoomID id.RoomID + PingRateSeconds int `yaml:"ping_rate_seconds"` + PingThresholdSeconds int `yaml:"ping_threshold_seconds"` + PingJsonURL string `yaml:"ping_json_url"` + BlacklistedHomeservers []string `yaml:"blacklisted_homeservers"` } func ReadConfig() Config { @@ -97,5 +101,29 @@ func ReadConfig() Config { } } + // Ensure that the Remote Homeservers are not blacklisted + for _, blacklistedHomeserver := range config.BlacklistedHomeservers { + for _, remoteHomeserver := range config.RemoteHomeservers { + if remoteHomeserver.Homeserver == blacklistedHomeserver { + log.Errorf("Remote homeserver %s is blacklisted", remoteHomeserver.Homeserver) + os.Exit(1) + } + } + } + + // Ensure that the Remote Homeservers are different from the Own Homeserver or each other + for _, remoteHomeserver := range config.RemoteHomeservers { + if remoteHomeserver.Homeserver == config.OwnHomeserver.Homeserver { + log.Errorf("Remote homeserver %s is the same as the own homeserver", remoteHomeserver.Homeserver) + os.Exit(1) + } + for _, remoteHomeserver2 := range config.RemoteHomeservers { + if remoteHomeserver.Homeserver == remoteHomeserver2.Homeserver { + log.Errorf("Remote homeserver %s is the same as remote homeserver %s", remoteHomeserver.Homeserver, remoteHomeserver2.Homeserver) + os.Exit(1) + } + } + } + return config } diff --git a/go.mod b/go.mod @@ -6,22 +6,31 @@ require github.com/prometheus/client_golang v1.18.0 require ( github.com/fatih/color v1.10.0 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/rs/zerolog v1.31.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.mau.fi/util v0.3.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + maunium.net/go/maulogger/v2 v2.4.1 // indirect ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/ghodss/yaml v1.0.0 github.com/goccy/go-yaml v1.11.3 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.45.0 github.com/prometheus/procfs v0.12.0 // indirect github.com/sirupsen/logrus v1.9.3 - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + maunium.net/go/mautrix v0.17.0 ) diff --git a/go.sum b/go.sum @@ -2,22 +2,38 @@ 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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +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/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/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= @@ -27,15 +43,40 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne 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/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.mau.fi/util v0.3.0 h1:Lt3lbRXP6ZBqTINK0EieRWor3zEwwwrDT14Z5N8RUCs= +go.mau.fi/util v0.3.0/go.mod h1:9dGsBCCbZJstx16YgnVMVi3O2bOizELoKpugLD4FoGs= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -43,6 +84,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= +maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8= +maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho= +maunium.net/go/mautrix v0.17.0 h1:scc1qlUbzPn+wc+3eAPquyD+3gZwwy/hBANBm+iGKK8= +maunium.net/go/mautrix v0.17.0/go.mod h1:j+puTEQCEydlVxhJ/dQP5chfa26TdvBO7X6F3Ataav8= diff --git a/main.go b/main.go @@ -1,13 +1,24 @@ package main import ( + "context" + "errors" "flag" "net/http" + "os" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/version" log "github.com/sirupsen/logrus" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +const ( + collector = "matrix_ping_exporter" ) func main() { @@ -16,7 +27,16 @@ func main() { flag.StringVar(&address, "address", "0.0.0.0:8080", "Address to listen on") flag.Parse() - prometheus.Register(version.NewCollector("matrix_ping_exporter")) + // Create a matrix client for each homeserver + config.OwnHomeserver.Client = createMatrixClient(config, config.OwnHomeserver) + for i := range config.RemoteHomeservers { + config.RemoteHomeservers[i].Client = createMatrixClient(config, config.RemoteHomeservers[i]) + } + + prometheus.Register(version.NewCollector(collector)) + prometheus.Register(&PingCollector{ + Config: config, + }) http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { h := promhttp.HandlerFor(prometheus.Gatherers{ @@ -30,3 +50,87 @@ func main() { log.Errorf("Failed to start http server: %s", err) } } + +type PongEventRelation struct { + RelType string `json:"rel_type"` + EventID string `json:"event_id"` +} + +type PongEventContent struct { + MsgType string `json:"msgtype"` + Body string `json:"body"` + RelatesTo PongEventRelation `json:"m.relates_to"` +} + +func createMatrixClient(Config Config, Homeserver MatrixConfig) *mautrix.Client { + var startTime = time.Now() + + client, err := mautrix.NewClient(Homeserver.Homeserver, id.UserID(Homeserver.Username), "") + if err != nil { + log.Errorf("Failed to create client for %s: %s", Homeserver.Homeserver, err) + os.Exit(1) + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + _, err = client.Login(ctx, &mautrix.ReqLogin{ + Type: mautrix.AuthTypePassword, + Identifier: mautrix.UserIdentifier{Type: "m.id.user", User: Homeserver.Username}, + Password: Homeserver.Password, + }) + if err != nil { + log.Errorf("Failed to login to %s: %s", Homeserver.Homeserver, err) + os.Exit(1) + } + + // join the ping room + resp, err := client.JoinRoom(ctx, Config.PingRoom, "", nil) + if err != nil { + log.Errorf("Failed to join ping room: %s", err) + os.Exit(1) + } + Config.PingRoomID = resp.RoomID + + syncer := client.Syncer.(*mautrix.DefaultSyncer) + syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) { + if evt.RoomID != Config.PingRoomID { + return + } + if evt.Sender == client.UserID { + return + } + // Make sure the message is not older than startTime + if evt.Timestamp < startTime.Unix()*1000 { + return + } + + if evt.Content.AsMessage().Body == "!ping" { + // Respond to the ping + pong_event_content := PongEventContent{ + MsgType: "m.notice", + Body: "Pong!", + RelatesTo: PongEventRelation{ + RelType: "xyz.maubot.pong", + EventID: evt.ID.String(), + }, + } + _, err := client.SendMessageEvent(ctx, evt.RoomID, event.EventMessage, pong_event_content) + if err != nil { + log.Errorf("Failed to send pong: %s", err) + } + } + }) + + syncCtx, cancelSync := context.WithCancel(context.Background()) + defer cancelSync() + + go func() { + err = client.SyncWithContext(syncCtx) + if err != nil && !errors.Is(err, context.Canceled) { + log.Errorf("Failed to sync: %s", err) + os.Exit(1) + } + }() + + return client +} diff --git a/ping_json.go b/ping_json.go @@ -1,26 +1,26 @@ package main type Ping struct { - Diffs map[string]int `json:"diffs"` - Mean float64 `json:"mean"` - Median float64 `json:"median"` - GMean float64 `json:"gmean"` + Pongs map[string]Pong `json:"pongs"` + Pings []string `json:"pings"` + Mean float64 `json:"mean"` + Median float64 `json:"median"` + GMean float64 `json:"gmean"` } +// A map of homeserver names to ping data type Pings map[string]Ping type Pong struct { - Pings []string `json:"pings"` + Diffs []string `json:"diffs"` Mean float64 `json:"mean"` Median float64 `json:"median"` GMean float64 `json:"gmean"` } -type PongServers []string - type Data struct { - Disclaimer string `json:"disclaimer"` - Pings Pings `json:"pings"` - Mean float64 `json:"mean"` - PongServers PongServers `json:"pongservers"` + Disclaimer string `json:"disclaimer"` + Pings Pings `json:"pings"` + Mean float64 `json:"mean"` + PongServers []string `json:"pongservers"` } diff --git a/ping_manager.go b/ping_manager.go @@ -1 +1,108 @@ package main + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "maunium.net/go/mautrix" +) + +type PingCollector struct { + Config Config + Metrics map[string]*prometheus.Desc + LastCollected time.Time +} + +func (c *PingCollector) Describe(ch chan<- *prometheus.Desc) { + mean := prometheus.NewDesc(prometheus.BuildFQName(collector, "", "mean"), "Mean ping time", []string{"homeserver"}, nil) + median := prometheus.NewDesc(prometheus.BuildFQName(collector, "", "median"), "Median ping time", []string{"homeserver"}, nil) + gmean := prometheus.NewDesc(prometheus.BuildFQName(collector, "", "gmean"), "GMean ping time", []string{"homeserver"}, nil) + c.Metrics = make(map[string]*prometheus.Desc) + c.Metrics["mean"] = mean + c.Metrics["median"] = median + c.Metrics["gmean"] = gmean + log.Infof("Registered metrics") +} + +// For collection of matrix metrics we first need to ping. +// To do that we do 2 things: +// 1. Write `!ping` in the ping room as a message from our own homeserver +// 2. Send `!ping` from all remote homeservers to our ping room +// We then track our event_ids and make sure that at least one event is reaching our own homeserver +// And at least one event id (can be a different one) reaches the other homeservers. +// We also do react on our own `!ping` message and send a `!pong` back to the ping room based on the maubot echobot logic. +func (c *PingCollector) Collect(ch chan<- prometheus.Metric) { + log.Infoln("Starting Collecting metrics") +} + +// This sends a ping into the ping room +// It takes a homeserver config as an argument to know where to send the ping +func (c *PingCollector) SendPing(ctx context.Context, client mautrix.Client, ch chan<- prometheus.Metric) { + if c.LastCollected.Add(time.Duration(c.Config.PingRateSeconds) * time.Second).After(time.Now()) { + log.Infof("Not sending ping as we sent one less than %d seconds ago", c.Config.PingRateSeconds) + return + } + log.Infof("Sending ping as %s", client.UserID) + + // Send the ping + resp, err := client.SendText(ctx, c.Config.PingRoomID, "!ping") + if err != nil { + log.Errorf("Failed to send ping: %s", err) + } + log.Infof("Sent ping as %s with event_id %s", client.UserID, resp.EventID) + + // Poll the json to check if any pongs have been received for this ping + // Otherwise timeout after PingThresholdSeconds + var currentData Data + var gotKnownPong bool = false + +outer: + for time.Now().Before(c.LastCollected.Add(time.Duration(c.Config.PingThresholdSeconds) * time.Second)) { + resp, err := http.Get(c.Config.PingJsonURL) + if err != nil { + log.Errorf("Failed to get ping json: %s", err) + } + + // Parse the json + // If we have a pong for this ping, we can break the loop + // Otherwise we sleep for 1 second and try again + defer resp.Body.Close() + json.NewDecoder(resp.Body).Decode(&currentData) + + // Check if we have a pong for this ping + if ping, ok := currentData.Pings[client.HomeserverURL.Host]; ok { + log.Infof("Got pong for ping as %s", client.UserID) + + // Check if its from a known remote homeserver in ping.Pongs + for homeserver := range ping.Pongs { + if homeserver == client.HomeserverURL.Host { + continue + } + if _, ok := currentData.Pings[homeserver]; ok { + log.Infof("Got pong for ping as %s from %s", client.UserID, homeserver) + // break out of outer loop + gotKnownPong = true + break outer + } + } + } + } + + // If we have not received a pong for this ping, we should log it + if !gotKnownPong { + log.Errorf("Failed to get pong for ping as %s", client.UserID) + } + + // Update mean, median and gmean metrics + // All of these are per homeserver we received a pong from + // They are all of type histogram + for homeserver, ping := range currentData.Pings[client.HomeserverURL.Host].Pongs { + ch <- prometheus.MustNewConstMetric(c.Metrics["mean"], prometheus.GaugeValue, ping.Mean, homeserver) + ch <- prometheus.MustNewConstMetric(c.Metrics["median"], prometheus.GaugeValue, ping.Median, homeserver) + ch <- prometheus.MustNewConstMetric(c.Metrics["gmean"], prometheus.GaugeValue, ping.GMean, homeserver) + } +}