ping_manager.go (5823B)
1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "net/url" 8 "sync" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 log "github.com/sirupsen/logrus" 13 "maunium.net/go/mautrix" 14 "maunium.net/go/mautrix/id" 15 ) 16 17 type PingCollector struct { 18 Config *Config 19 Mean *prometheus.GaugeVec 20 Median *prometheus.GaugeVec 21 GMean *prometheus.GaugeVec 22 Failures *prometheus.CounterVec 23 LastCollected map[string]time.Time 24 } 25 26 func (c *PingCollector) Describe(ch chan<- *prometheus.Desc) { 27 c.Mean.Describe(ch) 28 c.Median.Describe(ch) 29 c.GMean.Describe(ch) 30 c.Failures.Describe(ch) 31 log.Infof("Registered metrics") 32 } 33 34 func (c *PingCollector) Collect(ch chan<- prometheus.Metric) { 35 c.Mean.Collect(ch) 36 c.Median.Collect(ch) 37 c.GMean.Collect(ch) 38 c.Failures.Collect(ch) 39 } 40 41 // For collection of matrix metrics we first need to ping. 42 // To do that we do 2 things: 43 // 1. Write `!ping` in the ping room as a message from our own homeserver 44 // 2. Send `!ping` from all remote homeservers to our ping room 45 // We then track our event_ids and make sure that at least one event is reaching our own homeserver 46 // And at least one event id (can be a different one) reaches the other homeservers. 47 // We also do react on our own `!ping` message and send a `!pong` back to the ping room based on the maubot echobot logic. 48 func (c *PingCollector) UpdateData() { 49 log.Infoln("Starting Collecting metrics") 50 var wg sync.WaitGroup 51 wg.Add(1) 52 53 // Send ping from our own homeserver 54 go c.SendPing(context.Background(), c.Config.OwnHomeserver.Client, &wg) 55 56 // Send ping from all remote homeservers 57 for _, homeserver := range c.Config.RemoteHomeservers { 58 wg.Add(1) 59 go c.SendPing(context.Background(), homeserver.Client, &wg) 60 } 61 62 wg.Wait() 63 } 64 65 // This sends a ping into the ping room 66 // It takes a homeserver config as an argument to know where to send the ping 67 func (c *PingCollector) SendPing(ctx context.Context, client *mautrix.Client, wg *sync.WaitGroup) { 68 defer wg.Done() 69 if c.LastCollected[client.UserID.Homeserver()].Add(time.Duration(c.Config.PingRateSeconds) * time.Second).After(time.Now()) { 70 log.Infof("Not sending ping as we sent one less than %d seconds ago", c.Config.PingRateSeconds) 71 return 72 } 73 if c.LastCollected == nil { 74 c.LastCollected = make(map[string]time.Time) 75 } 76 c.LastCollected[client.UserID.Homeserver()] = time.Now() 77 log.Infof("Sending ping as %s", client.UserID) 78 if c.Config.PingRoomID == "" { 79 log.Errorf("No ping room ID found") 80 return 81 } 82 83 // Send the ping 84 resp, err := client.SendText(ctx, c.Config.PingRoomID, "!ping") 85 if err != nil { 86 log.Errorf("Failed to send ping: %s", err) 87 } 88 log.Infof("Sent ping as %s with event_id %s", client.UserID, resp.EventID) 89 eventID := resp.EventID 90 91 // Poll the json to check if any pongs have been received for this ping 92 // Otherwise timeout after PingThresholdSeconds 93 var currentData Data 94 var gotKnownPong bool = false 95 var pingTime time.Time = time.Now() 96 u, err := url.Parse(c.Config.OwnHomeserver.Homeserver) 97 if err != nil { 98 log.Errorf("Failed to parse homeserver url: %s", err) 99 return 100 } 101 direction := "outgoing" 102 if client.HomeserverURL.Host != u.Host { 103 direction = "incoming" 104 } 105 106 outer: 107 for time.Now().Before(pingTime.Add(time.Duration(c.Config.PingThresholdSeconds) * time.Second)) { 108 resp, err := http.Get(c.Config.PingJsonURL) 109 if err != nil { 110 log.Errorf("Failed to get ping json: %s", err) 111 } 112 113 // Parse the json 114 // If we have a pong for this ping, we can break the loop 115 // Otherwise we sleep for 1 second and try again 116 defer resp.Body.Close() 117 json.NewDecoder(resp.Body).Decode(¤tData) 118 119 // Check if we have a pong for this ping 120 log.Infof("Checking for pong for ping as %s", client.UserID.Homeserver()) 121 if ping, ok := currentData.Pings[client.UserID.Homeserver()]; ok { 122 hs_parsed := id.UserID(c.Config.OwnHomeserver.Username).Homeserver() 123 if _, ok := ping.Pongs[hs_parsed].Diffs[eventID.String()]; ok { 124 log.Infof("Got pong for ping as %s", client.UserID) 125 gotKnownPong = true 126 break outer 127 } 128 // Check if its from a known remote homeserver in ping.Pongs 129 for _, homeserver := range c.Config.RemoteHomeservers { 130 hs_parsed := id.UserID(homeserver.Username).Homeserver() 131 if _, ok := ping.Pongs[hs_parsed].Diffs[eventID.String()]; ok { 132 log.Infof("Got pong for ping as %s", client.UserID) 133 gotKnownPong = true 134 break outer 135 } 136 } 137 } else { 138 log.Warnf("No pong for ping as %s", client.UserID) 139 } 140 141 // Sleep for 1 second 142 time.Sleep(1 * time.Second) 143 } 144 145 // If we have not received a pong for this ping, we should log it 146 if !gotKnownPong { 147 log.Errorf("Failed to get pong for ping as %s", client.UserID) 148 c.Failures.WithLabelValues(client.UserID.Homeserver(), direction).Inc() 149 } 150 151 // Wait 5s before we collect the final data 152 time.Sleep(5 * time.Second) 153 pingresp, err := http.Get(c.Config.PingJsonURL) 154 if err != nil { 155 log.Errorf("Failed to get ping json: %s", err) 156 } 157 158 // Parse the json 159 // If we have a pong for this ping, we can break the loop 160 // Otherwise we sleep for 1 second and try again 161 defer pingresp.Body.Close() 162 json.NewDecoder(pingresp.Body).Decode(¤tData) 163 164 // Update mean, median and gmean metrics 165 // All of these are per homeserver we received a pong from 166 // They are all of type Gauge (Should they be a historgram?) 167 for homeserver, ping := range currentData.Pings[client.UserID.Homeserver()].Pongs { 168 direction := "outgoing" 169 if client.HomeserverURL.Host != u.Host { 170 direction = "incoming" 171 } 172 c.Mean.WithLabelValues(homeserver, client.UserID.Homeserver(), direction).Set(ping.Mean) 173 c.Median.WithLabelValues(homeserver, client.UserID.Homeserver(), direction).Set(ping.Median) 174 c.GMean.WithLabelValues(homeserver, client.UserID.Homeserver(), direction).Set(ping.GMean) 175 } 176 }