This commit is contained in:
Aire-One 2024-08-13 17:47:05 +02:00
parent 13d4df3e3a
commit 6e46013a9a
8 changed files with 277 additions and 0 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[*.{json,yaml}]
indent_size = 2

16
compose.yml Normal file
View File

@ -0,0 +1,16 @@
---
services:
prometheus:
image: prom/prometheus
ports:
- 9090:9090
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
extra_hosts:
- "host.docker.internal:host-gateway"
grafana:
image: grafana/grafana
ports:
- 3000:3000

7
config.yaml Normal file
View File

@ -0,0 +1,7 @@
---
targets:
- name: Home
url: https://aireone.xyz
interval: 2
- name: Gitea
url: https://gitea.aireone.xyz

7
cspell.json Normal file
View File

@ -0,0 +1,7 @@
{
"words": [
"gocron",
"labtime",
"promhttp"
]
}

23
go.mod Normal file
View File

@ -0,0 +1,23 @@
module aireone.xyz/labtime
go 1.22.4
require (
github.com/go-co-op/gocron/v2 v2.11.0
github.com/prometheus/client_golang v1.19.1
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/sys v0.17.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

31
go.sum Normal file
View File

@ -0,0 +1,31 @@
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/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

173
main.go Normal file
View File

@ -0,0 +1,173 @@
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"net/http/httptrace"
"os"
"os/signal"
"time"
"github.com/go-co-op/gocron/v2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"gopkg.in/yaml.v3"
)
type Config struct {
// List of targets to ping
Targets []struct {
// Name of the target. Used to identify the target from Prometheus.
Name string `yaml:"name"`
// URL of the target. The target should be accessible from the machine running the exporter. The URL should contain the protocol (http:// or https://) and the port if it's not the default one.
Url string `yaml:"url"`
// Interval to ping the target. Default is 5 seconds
Interval int `yaml:"interval,omitempty"`
} `yaml:"targets"`
}
func NewConfig(configPath string) (*Config, error) {
// Create config structure
config := &Config{}
// Open config file
file, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer file.Close()
// Init new YAML decode
d := yaml.NewDecoder(file)
// Start YAML decoding from file
if err := d.Decode(&config); err != nil {
return nil, err
}
return config, nil
}
func Ping(url string) (int, time.Duration, error) {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return 0, 0, err
}
var start, connect, dns, tlsHandshake time.Time
trace := &httptrace.ClientTrace{
DNSStart: func(dsi httptrace.DNSStartInfo) { dns = time.Now() },
DNSDone: func(ddi httptrace.DNSDoneInfo) {
fmt.Printf("DNS Done: %v\n", time.Since(dns))
},
TLSHandshakeStart: func() { tlsHandshake = time.Now() },
TLSHandshakeDone: func(cs tls.ConnectionState, err error) {
fmt.Printf("TLS Handshake: %v\n", time.Since(tlsHandshake))
},
ConnectStart: func(network, addr string) { connect = time.Now() },
ConnectDone: func(network, addr string, err error) {
fmt.Printf("Connect time: %v\n", time.Since(connect))
},
GotFirstResponseByte: func() {
fmt.Printf("Time from start to first byte: %v\n", time.Since(start))
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
start = time.Now()
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return 0, 0, err
}
resp.Body.Close()
return resp.StatusCode, time.Since(start), nil
}
func main() {
// Load config
config, err := NewConfig("config.yaml")
if err != nil {
log.Fatalf("Error loading config: %s", err)
}
// Print config
fmt.Printf("%+v\n", config)
// create a scheduler
s, err := gocron.NewScheduler()
if err != nil {
// handle error
}
// Prometheus metrics
responseTimeMonitor := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "labtime_response_time_duration",
Help: "The ping time.",
}, []string{"target_name"})
prometheus.MustRegister(responseTimeMonitor)
// Intercept the signal to stop the program
go func() {
sigchan := make(chan os.Signal)
signal.Notify(sigchan, os.Interrupt)
<-sigchan
log.Println("Program killed !")
// do last actions and wait for all write operations to end
// when you're done, shut it down
err = s.Shutdown()
if err != nil {
// handle error
}
os.Exit(0)
}()
// add a job to the scheduler
for _, target := range config.Targets {
j, err := s.NewJob(
gocron.DurationJob(
func(interval int) time.Duration {
if interval == 0 {
return time.Duration(5) * time.Second
}
return time.Duration(interval) * time.Second
}(target.Interval),
),
gocron.NewTask(
func(url string) {
status, elapsedTime, err := Ping(url)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s - Status: %d in %v\n", target.Name, status, elapsedTime.Seconds())
// push to Prometheus
responseTimeMonitor.With(prometheus.Labels{"target_name": target.Name}).Set(elapsedTime.Seconds())
},
target.Url,
),
)
if err != nil {
// handle error
}
// each job has a unique id
fmt.Printf("Job %s started with ID: %s\n", target.Name, j.ID().String())
}
// start the scheduler
s.Start()
// // block until you are ready to shut down
// select {
// // case <-time.After(time.Minute):
// }
// Serve Prometheus metrics
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":2112", nil)
}

5
prometheus.yml Normal file
View File

@ -0,0 +1,5 @@
---
scrape_configs:
- job_name: 'labtime'
static_configs:
- targets: ['host.docker.internal:2112']