package main import ( "flag" "fmt" "github.com/labstack/echo-contrib/echoprometheus" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus" "net/http" ) type Telemetry struct { Slug string `json:"slug"` EventType string `json:"type"` QualityChangeUp bool `json:"isUp"` } type metrics struct { buffering *prometheus.CounterVec recovery *prometheus.CounterVec qualityUp *prometheus.CounterVec qualityDown *prometheus.CounterVec } const ( typeBuffering = "buffering" typeRecovery = "recovery" typeQualitySwitch = "quality_switch" ) func main() { m, err := initMetrics() if err != nil { panic(err) } listenAddress := ":2342" flag.StringVar(&listenAddress, "listen", listenAddress, "address to listen on") flag.Parse() e := echo.New() e.Use(middleware.CORS()) e.Use(middleware.Decompress()) e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.BodyLimit("64K")) e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(5))) e.Use(echoprometheus.NewMiddleware("http")) e.GET("/metrics", echoprometheus.NewHandler()) e.POST("/", telemetryHandler(m)) e.Logger.Fatal(e.Start(listenAddress)) } func telemetryHandler(m metrics) echo.HandlerFunc { return func(c echo.Context) error { t := new([]Telemetry) if err := c.Bind(t); err != nil { return c.String(http.StatusBadRequest, "bad request") } for _, v := range *t { switch v.EventType { case typeBuffering: m.buffering.WithLabelValues(v.Slug).Inc() case typeRecovery: m.recovery.WithLabelValues(v.Slug).Inc() case typeQualitySwitch: if v.QualityChangeUp { m.qualityUp.WithLabelValues(v.Slug).Inc() } else { m.qualityDown.WithLabelValues(v.Slug).Inc() } } } return c.NoContent(http.StatusOK) } } func initMetrics() (metrics, error) { metricBuffering := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "telemetry", Subsystem: "player", Name: "buffering", }, []string{"slug"}) metricRecovery := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "telemetry", Subsystem: "player", Name: "recovery", }, []string{"slug"}) metricQualitySwitchUp := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "telemetry", Subsystem: "player", Name: "quality_switch_up", }, []string{"slug"}) metricQualitySwitchDown := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "telemetry", Subsystem: "player", Name: "quality_switch_down", }, []string{"slug"}) if err := prometheus.Register(metricBuffering); err != nil { return metrics{}, fmt.Errorf("failed to register buffering metric: %w", err) } if err := prometheus.Register(metricRecovery); err != nil { return metrics{}, fmt.Errorf("failed to register recovery metric: %w", err) } if err := prometheus.Register(metricQualitySwitchUp); err != nil { return metrics{}, fmt.Errorf("failed to register quality_switch_up metric: %w", err) } if err := prometheus.Register(metricQualitySwitchDown); err != nil { return metrics{}, fmt.Errorf("failed to register quality_switch_down metric: %w", err) } return metrics{ buffering: metricBuffering, recovery: metricRecovery, qualityUp: metricQualitySwitchUp, qualityDown: metricQualitySwitchDown, }, nil }