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

main.go (5145B)


      1 package main
      2 
      3 import (
      4 	"context"
      5 	"errors"
      6 	"flag"
      7 	"net/http"
      8 	"os"
      9 	"time"
     10 
     11 	"github.com/prometheus/client_golang/prometheus"
     12 	"github.com/prometheus/client_golang/prometheus/promhttp"
     13 	"github.com/prometheus/common/version"
     14 	log "github.com/sirupsen/logrus"
     15 	"maunium.net/go/mautrix"
     16 	"maunium.net/go/mautrix/event"
     17 	"maunium.net/go/mautrix/id"
     18 )
     19 
     20 const (
     21 	collector = "matrix_ping_exporter"
     22 )
     23 
     24 func main() {
     25 	var config = ReadConfig()
     26 	var address = config.Address
     27 	flag.StringVar(&address, "address", "0.0.0.0:8080", "Address to listen on")
     28 	flag.Parse()
     29 
     30 	// Create a matrix client for each homeserver
     31 	config.OwnHomeserver.Client = createMatrixClient(&config, &config.OwnHomeserver)
     32 	for i := range config.RemoteHomeservers {
     33 		config.RemoteHomeservers[i].Client = createMatrixClient(&config, &config.RemoteHomeservers[i])
     34 	}
     35 
     36 	pingCollector := &PingCollector{
     37 		Config: &config,
     38 		Mean: prometheus.NewGaugeVec(prometheus.GaugeOpts{
     39 			Namespace: collector,
     40 			Name:      "mean",
     41 			Help:      "Mean ping time",
     42 		}, []string{"homeserver", "origin", "direction"}),
     43 		Median: prometheus.NewGaugeVec(prometheus.GaugeOpts{
     44 			Namespace: collector,
     45 			Name:      "median",
     46 			Help:      "Median ping time",
     47 		}, []string{"homeserver", "origin", "direction"}),
     48 		GMean: prometheus.NewGaugeVec(prometheus.GaugeOpts{
     49 			Namespace: collector,
     50 			Name:      "gmean",
     51 			Help:      "GMean ping time",
     52 		}, []string{"homeserver", "origin", "direction"}),
     53 		Failures: prometheus.NewCounterVec(prometheus.CounterOpts{
     54 			Namespace: collector,
     55 			Name:      "failures",
     56 			Help:      "Ping failures",
     57 		}, []string{"origin", "direction"}),
     58 	}
     59 
     60 	go func() {
     61 		pingCollector.UpdateData()
     62 		time.Sleep(time.Duration(config.PingRateSeconds+1) * time.Second)
     63 		for {
     64 			pingCollector.UpdateData()
     65 			time.Sleep(time.Duration(config.PingRateSeconds+1) * time.Second)
     66 		}
     67 	}()
     68 
     69 	reg := prometheus.NewRegistry()
     70 	reg.MustRegister(version.NewCollector(collector))
     71 	reg.MustRegister(pingCollector)
     72 
     73 	http.Handle("/metrics", promhttp.HandlerFor(
     74 		reg,
     75 		promhttp.HandlerOpts{},
     76 	))
     77 
     78 	log.Infof("Starting http server - %s", address)
     79 	if err := http.ListenAndServe(address, nil); err != nil {
     80 		log.Errorf("Failed to start http server: %s", err)
     81 	}
     82 }
     83 
     84 type PongEventRelation struct {
     85 	RelType string `json:"rel_type"`
     86 	EventID string `json:"event_id"`
     87 }
     88 
     89 type PongEventContent struct {
     90 	MsgType   string            `json:"msgtype"`
     91 	Body      string            `json:"body"`
     92 	RelatesTo PongEventRelation `json:"m.relates_to"`
     93 }
     94 
     95 func createMatrixClient(config *Config, homeserver *MatrixConfig) *mautrix.Client {
     96 	var startTime = time.Now()
     97 
     98 	client, err := mautrix.NewClient(homeserver.Homeserver, id.UserID(homeserver.Username), "")
     99 	if err != nil {
    100 		log.Errorf("Failed to create client for %s: %s", homeserver.Homeserver, err)
    101 		os.Exit(1)
    102 	}
    103 
    104 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    105 	defer cancel()
    106 	if homeserver.AccessToken == "" {
    107 		loginresp, err := client.Login(ctx, &mautrix.ReqLogin{
    108 			Type:               mautrix.AuthTypePassword,
    109 			Identifier:         mautrix.UserIdentifier{Type: "m.id.user", User: homeserver.Username},
    110 			Password:           homeserver.Password,
    111 			StoreCredentials:   true,
    112 			StoreHomeserverURL: true,
    113 		})
    114 		if err != nil {
    115 			log.Errorf("Failed to login to %s: %s", homeserver.Homeserver, err)
    116 			os.Exit(1)
    117 		}
    118 		log.Infof("Logged in as %s", loginresp.UserID)
    119 
    120 		// Tell user to store the access token and device id and remove the password from the config
    121 		log.Warnf("Please store the access token and device id for %s and remove the password from the config", homeserver.Homeserver)
    122 		log.Warnf("Access token: %s", loginresp.AccessToken)
    123 		log.Warnf("Device ID: %s", loginresp.DeviceID)
    124 	} else {
    125 		client.AccessToken = homeserver.AccessToken
    126 		client.DeviceID = id.DeviceID(homeserver.DeviceID)
    127 	}
    128 
    129 	// join the ping room
    130 	resp, err := client.JoinRoom(ctx, config.PingRoom, "", nil)
    131 	if err != nil {
    132 		log.Errorf("Failed to join ping room: %s", err)
    133 		os.Exit(1)
    134 	}
    135 	log.Infof("Joined ping room %s", resp.RoomID)
    136 	config.PingRoomID = resp.RoomID
    137 
    138 	syncer := client.Syncer.(*mautrix.DefaultSyncer)
    139 	syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
    140 		if evt.RoomID != config.PingRoomID {
    141 			return
    142 		}
    143 		if evt.Sender == client.UserID {
    144 			return
    145 		}
    146 		// Make sure the message is not older than startTime
    147 		if evt.Timestamp < startTime.Unix()*1000 {
    148 			return
    149 		}
    150 
    151 		if evt.Content.AsMessage().Body == "!ping" {
    152 			log.Infoln("Received ping message in pingroom by", evt.Sender)
    153 			// Respond to the ping
    154 			pong_event_content := PongEventContent{
    155 				MsgType: "m.notice",
    156 				Body:    "Pong!",
    157 				RelatesTo: PongEventRelation{
    158 					RelType: "xyz.maubot.pong",
    159 					EventID: evt.ID.String(),
    160 				},
    161 			}
    162 			_, err := client.SendMessageEvent(ctx, evt.RoomID, event.EventMessage, pong_event_content)
    163 			if err != nil {
    164 				log.Errorf("Failed to send pong: %s", err)
    165 			}
    166 		}
    167 	})
    168 
    169 	go func() {
    170 		err = client.Sync()
    171 		if err != nil && !errors.Is(err, context.Canceled) {
    172 			log.Errorf("Failed to sync: %s", err)
    173 			os.Exit(1)
    174 		}
    175 	}()
    176 
    177 	return client
    178 }