Compare commits

...

30 commits
0.1.0 ... 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
9fbe505f33
updaet changelog and bump version 2024-04-17 19:59:34 +02:00
68823aa602 Merge branch '2-nil-reference-pointer-exception-when-getting-queried-by-prometheus' into 'main'
Resolve "Nil reference pointer exception when getting queried by Prometheus"

Closes #2

See merge request adoralaura/ntppool-exporter!1
2024-04-17 17:38:21 +00:00
02f0c19ba5 Resolve "Nil reference pointer exception when getting queried by Prometheus" 2024-04-17 17:38:21 +00:00
16 changed files with 308 additions and 34 deletions

3
.gitignore vendored
View file

@ -22,6 +22,9 @@ go.work
# vscode Go debugging files
__debug_*
ntppool-exporter
bin/
# vscode editor config
.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,16 +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).
## [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
### Fixed
- Fix nil references when http request fails (#2)
## [0.1.0] - 2024-04-17
### Added
- This CHANGELOG file to document all significant changes
- LICENSE
- CONTRIBUTING guidelines
- MAINTAINERS file
- minimal implementation of the exporter
[unreleased]: https://gitlab.com/adoralaura/ntppool-exporter/compare/v0.1.0...HEAD
[0.1.0]: https://gitlab.com/adoralaura/ntppool-exporter/releases/tag/v0.1.0
[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.0]: https://gitlab.com/adoralaura/ntppool-exporter/releases/tag/0.1.0

View file

@ -1,12 +1,12 @@
# 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,
addressing (with `@...`) the maintainer of this repository (see
[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

View file

@ -1,8 +1,9 @@
FROM golang:1.22-alpine AS dev
FROM golang:1.22-bullseye AS dev
COPY . /var/app
WORKDIR /var/app
ENV GO111MODULE="on" \
CGO_ENABLED=0 \
GOOS=linux
@ -13,12 +14,13 @@ ENTRYPOINT ["sh"]
FROM dev as build
RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --assume-yes ca-certificates
RUN go mod download && go mod verify
RUN go build -o ntppool-exporter main.go$
RUN go build -o ntppool-exporter main.go
RUN chmod +x ntppool-exporter
FROM scratch AS prod
FROM gcr.io/distroless/static-debian12 AS prod
WORKDIR /app
COPY --from=build /var/app/ntppool-exporter /bin/

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.
# Usage
## Installation
Binaries can be downloaded soon from the [GitLab releases page](https://gitlab.com/adoralaura/ntppool-exporter/-/releases) and need no
special installation
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).
Binaries can be built by cloning this repository and run `go build main.go` with [Golang](https://go.dev/) installed.
## Running

View file

@ -1 +0,0 @@
0.1.0

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/level"
"github.com/prometheus/client_golang/prometheus"
"golang.adora.codes/ntppool-exporter/cache"
"golang.adora.codes/ntppool-exporter/models"
)
@ -52,7 +53,7 @@ func (c Collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements Prometheus.Collector.
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")
start := time.Now()
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) {
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
const apiEndpoint = "https://www.ntppool.org/scores/"
const apiQuery = "/json?limit=1&monitor=24"
@ -73,19 +91,20 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) {
if err != nil {
level.Error(logger).Log("msg", "Error sending HTTP request", "url", url, "message", err)
httpError = true
return
}
res, err := client.Do(req)
if err != nil {
level.Error(logger).Log("msg", "Error in HTTP response", "status", res.Status)
httpError = true
level.Error(logger).Log("msg", "Error in HTTP response", "error", err)
return
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
level.Error(logger).Log("msg", "Error reading HTTP response body", "error", err)
httpError = true
return
}
var response models.ApiResponse
@ -93,17 +112,19 @@ func (c Collector) collect(ch chan<- prometheus.Metric, logger log.Logger) {
err = json.Unmarshal(body, &response)
if err != nil {
level.Error(logger).Log("msg", "Error unmarshaling JSON body", "error", err)
httpError = true
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
serverScoreMetric := prometheus.NewDesc("ntppool_score",
serverScoreMetric := prometheus.NewDesc("ntppool_server_score",
"Shows the server score currently assigned at ntppool.org",
nil, prometheus.Labels{"server": c.target.String()})
nil, nil)
// TODO: Test or delete
//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/netip"
"os"
"time"
"github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
@ -32,11 +33,13 @@ import (
"github.com/prometheus/common/version"
"github.com/prometheus/exporter-toolkit/web"
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
"golang.adora.codes/ntppool-exporter/cache"
"golang.adora.codes/ntppool-exporter/collector"
)
const (
namespace = "ntppool"
namespace = "ntppool"
appVersion = "0.2.0"
)
var (
@ -87,6 +90,7 @@ func handler(w http.ResponseWriter, r *http.Request, logger log.Logger, exporter
}
func main() {
version.Version = appVersion
promlogConfig := &promlog.Config{}
flag.AddFlags(kingpin.CommandLine, promlogConfig)
kingpin.Version(version.Print("ntppool_exporter"))
@ -94,7 +98,9 @@ func main() {
kingpin.Parse()
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())
exporterMetrics := collector.Metrics{
@ -146,7 +152,12 @@ func main() {
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 {
level.Error(logger).Log("msg", "Error starting HTTP server", "err", err)
os.Exit(1)

View file

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