commit 1136ac0b01b67967c5b65abf96aa0e6b03b9a1b6 Author: Simon Rieger Date: Sun Mar 9 23:25:18 2025 +0100 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9d1db55 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +TRAEWELLING_TOKEN= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ca05ed2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + traewelling-metrics: + build: + context: go/. + dockerfile: Dockerfile + #ports: + # - "8080:8080" # Exponiere Port 8080 für Prometheus-Scraping + restart: always # Neustart bei Fehlern oder Updates + environment: + - TRAEWELLING_TOKEN=${TRAEWELLING_TOKEN} + networks: + default: + dns: + ipv4_address: 172.28.0.245 + +networks: + dns: + external: true diff --git a/go/Dockerfile b/go/Dockerfile new file mode 100644 index 0000000..a287108 --- /dev/null +++ b/go/Dockerfile @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1 + +# Build the application from source +FROM golang:1.21 AS build-stage + +WORKDIR /app + +COPY * ./ +RUN go mod download + +RUN CGO_ENABLED=0 GOOS=linux go build -o /main + +# Run the tests in the container +FROM build-stage AS run-test-stage +RUN go test -v ./... + +# Deploy the application binary into a lean image +FROM gcr.io/distroless/base-debian11 AS build-release-stage + +WORKDIR / + +COPY --from=build-stage /main /main + +EXPOSE 8080 + +USER nonroot:nonroot + +ENTRYPOINT ["/main"] diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..b9e477d --- /dev/null +++ b/go/go.mod @@ -0,0 +1,17 @@ +module traewelling-exporter + +go 1.19 + +require github.com/prometheus/client_golang v1.21.1 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/sys v0.28.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect +) diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..428bf62 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,26 @@ +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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go/main.go b/go/main.go new file mode 100644 index 0000000..785efd6 --- /dev/null +++ b/go/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + totalTrips = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "traewelling_total_trips", + Help: "Gesamtanzahl der Fahrten", + }) + totalDistance = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "traewelling_total_distance_km", + Help: "Gesamtdistanz aller Fahrten in Kilometern", + }) + averageDuration = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "traewelling_average_duration_minutes", + Help: "Durchschnittliche Dauer der Fahrten in Minuten", + }) +) + +type DashboardResponse struct { + Data []struct { + ID int `json:"id"` + Train struct { + Trip int `json:"trip"` + Distance float64 `json:"distance"` + Duration int `json:"duration"` + Category string `json:"category"` + LineName string `json:"lineName"` + JourneyNumber int `json:"journeyNumber"` + Origin struct { + Name string `json:"name"` + } `json:"origin"` + Destination struct { + Name string `json:"name"` + } `json:"destination"` + } `json:"train"` + } `json:"data"` +} + +func fetchTraewellingData() (*DashboardResponse, error) { + url := "https://traewelling.de/api/v1/dashboard" + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("Fehler beim Erstellen der Anfrage: %v", err) + } + + token := os.Getenv("TRAEWELLING_TOKEN") + if token == "" { + return nil, fmt.Errorf("TRAEWELLING_TOKEN ist nicht gesetzt") + } + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Fehler beim Senden der Anfrage: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Fehlerhafte Antwort: %d", resp.StatusCode) + } + + var apiResponse DashboardResponse + err = json.NewDecoder(resp.Body).Decode(&apiResponse) + if err != nil { + return nil, fmt.Errorf("Fehler beim Parsen der JSON-Daten: %v", err) + } + + return &apiResponse, nil +} + +func updateMetrics() { + data, err := fetchTraewellingData() + if err != nil { + fmt.Printf("Fehler beim Abrufen der Daten: %v\n", err) + return + } + + var totalTripsCount int + var totalDistanceSum float64 + var totalDurationSum int + + for _, trip := range data.Data { + totalTripsCount++ + totalDistanceSum += trip.Train.Distance / 1000 + totalDurationSum += trip.Train.Duration + } + + totalTrips.Set(float64(totalTripsCount)) + totalDistance.Set(totalDistanceSum) + if totalTripsCount > 0 { + averageDuration.Set(float64(totalDurationSum) / float64(totalTripsCount)) + } +} + +func main() { + go func() { + for { + updateMetrics() + time.Sleep(30 * time.Second) + } + }() + + http.Handle("/metrics", promhttp.Handler()) + fmt.Println("Server läuft auf Port 8080...") + http.ListenAndServe(":8080", nil) +}