Compare commits

...

27 commits
0.1.1 ... main

Author SHA1 Message Date
d9939c8701
update changelog
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
2024-04-23 08:31:13 +02:00
37f297b4dc Merge pull request 'switch docker image to distroless' (#9) from distroless-docker into main
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
Reviewed-on: #9
2024-04-23 08:29:22 +02:00
15310ee2ff
switch docker image to distroless
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-23 07:52:15 +02:00
7d66fe6fb0
update changelog to reflect breaking changes
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
ci/woodpecker/tag/docker-deploy Pipeline was successful
2024-04-19 12:14:14 +02:00
96121f3487
update version
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
2024-04-19 12:11:31 +02:00
0887112c44
update README and add docker compose
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
2024-04-19 12:07:41 +02:00
127f21e445
update CHANGELOG and CONTRIBUTING 2024-04-19 12:01:14 +02:00
44e84bd8e1 Merge pull request 'Add in-memory cache' (#5) from feature-in-memory-cache into main, closes #4
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
Reviewed-on: #5
2024-04-19 11:55:24 +02:00
265cbd5abf
add caching functionality
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-19 11:40:05 +02:00
2a574d6f3b
add test for cache miss
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-19 11:10:00 +02:00
36e9aa49f7
fix deadlock with waiting mutex 2024-04-19 11:09:06 +02:00
c713e16ed2
add test CI
All checks were successful
ci/woodpecker/push/golang-test Pipeline was successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-19 10:57:38 +02:00
3cc1653e11
add makefile
All checks were successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-19 09:02:38 +02:00
f34056f241
add cache file and a unit test 2024-04-19 09:02:33 +02:00
dcc11fca09 Merge pull request 'configure timeouts for http.Server' (#3) from feature-http-timeouts into main, closes #2
Reviewed-on: #3
2024-04-18 11:22:57 +02:00
a21ebe5af2
configure timeouts for http.Server (#1)
All checks were successful
ci/woodpecker/pr/docker-deploy Pipeline was successful
2024-04-18 11:10:56 +02:00
d68724a470
filter pipeline
Some checks failed
ci/woodpecker/tag/docker-deploy Pipeline failed
2024-04-18 10:50:22 +02:00
c71e965481
eemove lint
All checks were successful
ci/woodpecker/push/docker-deploy Pipeline was successful
2024-04-18 10:48:44 +02:00
f103bf3d94
add lint ci
Some checks failed
ci/woodpecker/push/docker-deploy Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
2024-04-18 10:47:08 +02:00
47825aa2f6
add woodpecker CI
All checks were successful
ci/woodpecker/push/docker-deploy Pipeline was successful
2024-04-18 10:43:50 +02:00
730326ede8
add woodpecker CI 2024-04-18 10:13:09 +02:00
e84f93efca
update changelog 2024-04-17 20:14:09 +02:00
0ca9b4e06c Merge branch 'feature-small-updates' into 'main'
update small changes

See merge request adoralaura/ntppool-exporter!2
2024-04-17 18:10:05 +00:00
bddc702ef3
fix MISSING in Debug log 2024-04-17 20:08:30 +02:00
046324f098
add binary folders to gitignore 2024-04-17 20:07:24 +02:00
2d88d8a13f
remove unneeded label 2024-04-17 20:07:00 +02:00
775eb974ab
update metric name 2024-04-17 20:06:42 +02:00
16 changed files with 293 additions and 29 deletions

3
.gitignore vendored
View file

@ -22,6 +22,9 @@ go.work
# vscode Go debugging files # vscode Go debugging files
__debug_* __debug_*
ntppool-exporter
bin/
# vscode editor config # vscode editor config
.vscode/ .vscode/

View file

@ -0,0 +1,35 @@
when:
- event: [pull_request, tag]
steps:
docker-deploy-pr:
when:
- event: pull_request
image: woodpeckerci/plugin-docker-buildx
settings:
dockerfile: Dockerfile
platforms: linux/arm/v7,linux/arm64/v8,linux/amd64
repo: lauralani/ntppool-exporter
registry: https://index.docker.io/v1/
tag: pr-${CI_COMMIT_PULL_REQUEST}
username: lauralani
password:
from_secret: dockerhub_token
docker-deploy-tag:
when:
event: tag
image: woodpeckerci/plugin-docker-buildx
settings:
dockerfile: Dockerfile
platforms: linux/arm/v7,linux/arm64/v8,linux/amd64
repo: lauralani/ntppool-exporter
registry: https://index.docker.io/v1/
auto_tag: true
username: lauralani
password:
from_secret: dockerhub_token

View file

@ -0,0 +1,9 @@
when:
- event: push
steps:
test:
image: golang:1.22-bullseye
commands:
- go mod download
- go test -v ./...

View file

@ -6,22 +6,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Changed
- changed docker base image to gcr.io/distroless/static-debian12
## [0.2.0] - 2024-04-19
### ⚠️ Breaking Changes
- renamed the score metric from `ntppool_score` to `ntppool_server_score`
- removed unneccessary `server` label in metrics
### Added
- added in-memory cache to reduce API strain (#4)
### Changed
- fixed some debug logs
- update readme to reflect new repo
## [0.1.1] - 2024-04-17 ## [0.1.1] - 2024-04-17
### Fixed ### Fixed
- Fix nil references when http request fails (#2) - Fix nil references when http request fails (#2)
## [0.1.0] - 2024-04-17 ## [0.1.0] - 2024-04-17
### Added ### Added
- This CHANGELOG file to document all significant changes - This CHANGELOG file to document all significant changes
- LICENSE - LICENSE
- CONTRIBUTING guidelines - CONTRIBUTING guidelines
- MAINTAINERS file - MAINTAINERS file
- minimal implementation of the exporter - minimal implementation of the exporter
[unreleased]: https://gitlab.com/adoralaura/ntppool-exporter/compare/0.1.1...HEAD [unreleased]: https://gitlab.com/adoralaura/ntppool-exporter/compare/0.2.0...HEAD
[0.2.0]: https://gitlab.com/adoralaura/ntppool-exporter/compare/0.1.1...0.2.0
[0.1.1]: https://gitlab.com/adoralaura/ntppool-exporter/compare/0.1.0...0.1.1 [0.1.1]: https://gitlab.com/adoralaura/ntppool-exporter/compare/0.1.0...0.1.1
[0.1.0]: https://gitlab.com/adoralaura/ntppool-exporter/releases/tag/0.1.0 [0.1.0]: https://gitlab.com/adoralaura/ntppool-exporter/releases/tag/0.1.0

View file

@ -1,12 +1,12 @@
# Contributing # Contributing
We use GitLab to manage reviews of pull requests. I use my own [Forgejo Instance](https://code.lila.network) to manage issues and pull requests.
* If you have a trivial fix or improvement, go ahead and create a pull request, * If you have a trivial fix or improvement, go ahead and create a pull request,
addressing (with `@...`) the maintainer of this repository (see addressing (with `@...`) the maintainer of this repository (see
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
* If you plan to do something more involved, first please create [a new issue](https://gitlab.com/adoralaura/ntppool-exporter/-/issues/new). * If you plan to do something more involved, first please [send me a mail]( mailto:adora@lila.network?subject=%5Bntppool-exporter%5D).
# What to contribute # What to contribute

View file

@ -20,10 +20,9 @@ RUN go build -o ntppool-exporter main.go
RUN chmod +x ntppool-exporter RUN chmod +x ntppool-exporter
FROM debian:12 AS prod FROM gcr.io/distroless/static-debian12 AS prod
WORKDIR /app WORKDIR /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /var/app/ntppool-exporter /bin/ COPY --from=build /var/app/ntppool-exporter /bin/
LABEL maintainer="Adora Laura Kalb <adora@lila.network>" LABEL maintainer="Adora Laura Kalb <adora@lila.network>"

3
Makefile Normal file
View file

@ -0,0 +1,3 @@
test:
go test ./...

View file

@ -2,16 +2,13 @@
This exporter is @adoralaura s try to display server scores from servers in [ntppool.org](https://www.ntppool.org) in a format which [Prometheus](https://prometheus.io/) can ingest. This exporter is @adoralaura s try to display server scores from servers in [ntppool.org](https://www.ntppool.org) in a format which [Prometheus](https://prometheus.io/) can ingest.
# Usage # Usage
## Installation ## Installation
Binaries can be downloaded soon from the [GitLab releases page](https://gitlab.com/adoralaura/ntppool-exporter/-/releases) and need no Pre-built docker images can be found at [Docker Hub](https://hub.docker.com/r/lauralani/ntppool-exporter). A sample `docker-compose.yml` can be [found here](docker-compose.yml).
special installation
Binaries can be built by cloning this repository and run `go build main.go` with [Golang](https://go.dev/) installed.
## Running ## Running

View file

@ -1 +0,0 @@
0.1.1

93
cache/cache.go vendored Normal file
View file

@ -0,0 +1,93 @@
/*
Copyright 2024 Adora Laura Kalb <adora@lila.network>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"net/netip"
"sync"
"time"
)
var (
GlobalScoreCache *ScoreCache
cacheExpiredAfter = 5 * time.Minute
)
type ServerScore struct {
expiresAt time.Time
Score float64
}
type ScoreCache struct {
stop chan struct{}
mu sync.RWMutex
scores map[netip.Addr]ServerScore
}
type CacheMissError struct{}
func (m *CacheMissError) Error() string {
return "User is not in cache!"
}
func newCacheMissError() *CacheMissError {
return &CacheMissError{}
}
func NewScoreCache() *ScoreCache {
lc := &ScoreCache{
scores: make(map[netip.Addr]ServerScore),
stop: make(chan struct{}),
}
return lc
}
func (sc *ScoreCache) Add(score float64, ip netip.Addr, ts time.Time) {
ssc := ServerScore{Score: score, expiresAt: ts.Add(cacheExpiredAfter)}
sc.mu.Lock()
sc.scores[ip] = ssc
sc.mu.Unlock()
}
func (lc *ScoreCache) Get(ip netip.Addr) (ServerScore, error) {
now := time.Now()
lc.mu.RLock()
cachedScore, ok := lc.scores[ip]
if !ok {
lc.mu.RUnlock()
return ServerScore{}, newCacheMissError()
}
lc.mu.RUnlock()
if now.After(cachedScore.expiresAt) {
lc.delete(ip)
return ServerScore{}, newCacheMissError()
}
return cachedScore, nil
}
func (lc *ScoreCache) delete(ip netip.Addr) {
lc.mu.Lock()
delete(lc.scores, ip)
lc.mu.Unlock()
}

49
cache/cache_test.go vendored Normal file
View file

@ -0,0 +1,49 @@
/*
Copyright 2024 Adora Laura Kalb <adora@lila.network>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"net/netip"
"testing"
"time"
)
func TestCacheGet(t *testing.T) {
const testFloat = 1.23456
cache := NewScoreCache()
cache.Add(testFloat, netip.MustParseAddr("1.2.3.4"), time.Now().Add(5*time.Minute))
score, _ := cache.Get(netip.MustParseAddr("1.2.3.4"))
if score.Score != testFloat {
t.Fatalf("cache.Get(\"1.2.3.4\") = %f, want %f", score.Score, testFloat)
}
}
func TestCacheMiss(t *testing.T) {
now := time.Now()
const testFloat = 1.23456
expiredTime := now.Add(-(time.Minute * 10))
cache := NewScoreCache()
cache.Add(testFloat, netip.MustParseAddr("1.2.3.4"), expiredTime)
_, err := cache.Get(netip.MustParseAddr("1.2.3.4"))
if err == nil {
t.Fatalf("cache.Get(\"1.2.3.4\") = got nil, want error")
}
}

View file

@ -27,6 +27,7 @@ import (
"github.com/go-kit/log" "github.com/go-kit/log"
"github.com/go-kit/log/level" "github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.adora.codes/ntppool-exporter/cache"
"golang.adora.codes/ntppool-exporter/models" "golang.adora.codes/ntppool-exporter/models"
) )
@ -52,7 +53,7 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements Prometheus.Collector. // Collect implements Prometheus.Collector.
func (c Collector) Collect(ch chan<- prometheus.Metric) { func (c Collector) Collect(ch chan<- prometheus.Metric) {
logger := log.With(c.logger, "scraper") logger := log.With(c.logger, "module", "scraper")
level.Debug(logger).Log("msg", "Starting scrape") level.Debug(logger).Log("msg", "Starting scrape")
start := time.Now() start := time.Now()
c.collect(ch, logger) c.collect(ch, logger)
@ -61,7 +62,24 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
} }
func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) { func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) {
httpError := false score, err := cache.GlobalScoreCache.Get(c.target)
if err == nil {
level.Debug(logger).Log("msg", "Serving score from cache",
"server", c.target.String(), "score", score.Score)
serverScoreMetric := prometheus.NewDesc("ntppool_server_score",
"Shows the server score currently assigned at ntppool.org",
nil, nil)
m1 := prometheus.MustNewConstMetric(serverScoreMetric, prometheus.GaugeValue, score.Score)
m1 = prometheus.NewMetricWithTimestamp(time.Now(), m1)
ch <- m1
return
}
level.Debug(logger).Log("msg", "Cache miss, querying API",
"server", c.target.String())
var serverApiScore float64 = 0 var serverApiScore float64 = 0
const apiEndpoint = "https://www.ntppool.org/scores/" const apiEndpoint = "https://www.ntppool.org/scores/"
const apiQuery = "/json?limit=1&monitor=24" const apiQuery = "/json?limit=1&monitor=24"
@ -73,7 +91,7 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) {
if err != nil { if err != nil {
level.Error(logger).Log("msg", "Error sending HTTP request", "url", url, "message", err) level.Error(logger).Log("msg", "Error sending HTTP request", "url", url, "message", err)
httpError = true return
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
@ -97,14 +115,16 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) {
return return
} }
if !httpError { serverApiScore = response.History[0].Score
serverApiScore = response.History[0].Score
} cache.GlobalScoreCache.Add(serverApiScore, c.target, time.Now())
level.Debug(logger).Log("msg", "Added score to cache",
"server", c.target.String(), "score", score.Score)
// TODO: Test or delete // TODO: Test or delete
serverScoreMetric := prometheus.NewDesc("ntppool_score", serverScoreMetric := prometheus.NewDesc("ntppool_server_score",
"Shows the server score currently assigned at ntppool.org", "Shows the server score currently assigned at ntppool.org",
nil, prometheus.Labels{"server": c.target.String()}) nil, nil)
// TODO: Test or delete // TODO: Test or delete
//c.metrics.NtpppolServerScore.Add(serverApiScore) //c.metrics.NtpppolServerScore.Add(serverApiScore)

8
docker-compose.yml Normal file
View file

@ -0,0 +1,8 @@
services:
ntppool_exporter:
image: lauralani/ntppool-exporter:latest
restart: always
ports:
- '127.0.0.1:43609:43609'
# Add this to enable debug logging:
# command: --log.level=debug

25
helpers/helpers.go Normal file
View file

@ -0,0 +1,25 @@
/*
Copyright 2024 Adora Laura Kalb <adora@lila.network>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package helpers
import (
"time"
)
func UnixToTime(i int64) time.Time {
return time.Unix(i, 0)
}

17
main.go
View file

@ -20,6 +20,7 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"os" "os"
"time"
"github.com/alecthomas/kingpin/v2" "github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log" "github.com/go-kit/log"
@ -32,11 +33,13 @@ import (
"github.com/prometheus/common/version" "github.com/prometheus/common/version"
"github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web"
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
"golang.adora.codes/ntppool-exporter/cache"
"golang.adora.codes/ntppool-exporter/collector" "golang.adora.codes/ntppool-exporter/collector"
) )
const ( const (
namespace = "ntppool" namespace = "ntppool"
appVersion = "0.2.0"
) )
var ( var (
@ -87,6 +90,7 @@ func handler(w http.ResponseWriter, r *http.Request, logger log.Logger, exporter
} }
func main() { func main() {
version.Version = appVersion
promlogConfig := &promlog.Config{} promlogConfig := &promlog.Config{}
flag.AddFlags(kingpin.CommandLine, promlogConfig) flag.AddFlags(kingpin.CommandLine, promlogConfig)
kingpin.Version(version.Print("ntppool_exporter")) kingpin.Version(version.Print("ntppool_exporter"))
@ -94,7 +98,9 @@ func main() {
kingpin.Parse() kingpin.Parse()
logger := promlog.New(promlogConfig) logger := promlog.New(promlogConfig)
level.Info(logger).Log("msg", "Starting ntppool_exporter", "version", version.Info()) cache.GlobalScoreCache = cache.NewScoreCache()
level.Info(logger).Log("msg", "Starting ntppool_exporter", "version", appVersion)
level.Info(logger).Log("build_context", version.BuildContext()) level.Info(logger).Log("build_context", version.BuildContext())
exporterMetrics := collector.Metrics{ exporterMetrics := collector.Metrics{
@ -146,7 +152,12 @@ func main() {
http.Handle("/", landingPage) http.Handle("/", landingPage)
} }
srv := &http.Server{} srv := &http.Server{
ReadHeaderTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
}
if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil { if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil {
level.Error(logger).Log("msg", "Error starting HTTP server", "err", err) level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
os.Exit(1) os.Exit(1)

View file

@ -34,8 +34,8 @@ type ApiResponse struct {
} }
type ApiResponseHistory struct { type ApiResponseHistory struct {
Timestamp int `json:"ts"` TimestampInt int64 `json:"ts"`
Step int `json:"step"` Step int `json:"step"`
Score float64 `json:"score"` Score float64 `json:"score"`
MonitorID int `json:"monitor_id"` MonitorID int `json:"monitor_id"`
} }