Merge pull request 'Prometheus Metrics' (#2) from prometheus-metrics into master

Reviewed-on: #2
This commit is contained in:
Arti Zirk 2023-08-10 14:55:47 +00:00
commit 24ca04b2aa
5 changed files with 134 additions and 41 deletions

1
.env
View File

@ -1,3 +1,4 @@
KDOORPI_DOOR=workshop
KDOORPI_API_ALLOWED=http://127.0.0.1:3333/allowed KDOORPI_API_ALLOWED=http://127.0.0.1:3333/allowed
KDOORPI_API_LONGPOLL=http://127.0.0.1:3333/longpoll KDOORPI_API_LONGPOLL=http://127.0.0.1:3333/longpoll
KDOORPI_API_SWIPE=http://127.0.0.1:3333/cardswipe KDOORPI_API_SWIPE=http://127.0.0.1:3333/cardswipe

View File

@ -1,4 +1,13 @@
dev: .PHONY: all build build_arm64 dev
GOOS=linux GOARCH=arm64 go build .
all: build
build:
go build -ldflags="-X main.Version=`git describe --dirty`" .
build_arm64:
GOOS=linux GOARCH=arm64 go build -ldflags="-X main.Version=`git describe --dirty`" .
dev: build_arm64
scp godoor workshopdoor:/tmp/ scp godoor workshopdoor:/tmp/
ssh workshopdoor 'mv -f /tmp/godoor ~/ && sudo systemctl restart godoor' ssh workshopdoor 'mv -f /tmp/godoor ~/ && sudo systemctl restart godoor'

20
go.mod
View File

@ -3,8 +3,20 @@ module godoor
go 1.18 go 1.18
require ( require (
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1
github.com/warthog618/gpiod v0.8.0 // indirect github.com/prometheus/client_golang v1.16.0
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect github.com/warthog618/gpiod v0.8.0
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
golang.org/x/sys v0.8.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
) )

38
go.sum
View File

@ -1,10 +1,40 @@
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/pilebones/go-udev v0.0.0-20180820235104-043677e09b13 h1:Y+ynP+0QIjUejN2tsuIlWOJG1CThJy6amRuWlBL94Vg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4= github.com/warthog618/gpiod v0.8.0 h1:qxH9XVvWHpTxzWFSndBcujFyNH5zVRzHM63tcmm85o4=
github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY= github.com/warthog618/gpiod v0.8.0/go.mod h1:a7Csa+IJtDBZ39++zC/6Srjo01qWejt/5velrDWuNkY=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

103
godoor.go
View File

@ -7,11 +7,15 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime/debug"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -55,6 +59,7 @@ type Config struct {
wiegandB int wiegandB int
solenoid int solenoid int
} }
prometheusMetricsBind string
} }
type KeepDoorOpen struct { type KeepDoorOpen struct {
@ -67,22 +72,62 @@ type OpenedTimestamp struct {
Closed *time.Time Closed *time.Time
} }
var Commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value
}
}
}
return ""
}()
var Version string
var config Config var config Config
var globalLock sync.Mutex var globalLock sync.Mutex
var validUids ValidUids var validUids ValidUids
var wiegand Wiegand var wiegand Wiegand
var keepDoorOpen KeepDoorOpen var keepDoorOpen KeepDoorOpen
var lastSyncedTimestamp *time.Time var (
var openDoorTimestamps []OpenedTimestamp godoorBuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "godoor_build_info",
Help: "Build Information",
}, []string{"version", "revision"})
lastSyncTimestamp = promauto.NewGauge(prometheus.GaugeOpts{
Name: "godoor_last_allow_list_sync_timestamp_seconds",
Help: "Last time list of card hashes was pulled from the server",
})
apiFailuresCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "godoor_api_request_failures_total",
Help: "HTTP API request failures count",
}, []string{"api", "endpoint"})
nrCardsInAllowList = promauto.NewGauge(prometheus.GaugeOpts{
Name: "godoor_allowed_card_hashes_total",
Help: "Number of card hashes in memory that can open the door",
})
doorOpenedCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "godoor_door_opens_total",
Help: "Number of times door was opened",
}, []string{"source"})
cardSwipesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "godoor_swipes_total",
Help: "Number of times a card has been swiped",
}, []string{"status"})
)
func main() { func main() {
log.Printf("GoDoor ver: %s (%s)", Version, Commit)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel() defer cancel()
loadConfig() loadConfig()
godoorBuildInfo.WithLabelValues(Version, Commit).Set(1)
go func() { go func() {
setup() setup()
}() }()
@ -113,6 +158,10 @@ func loadConfig() {
config.doorOpenTime = 3 config.doorOpenTime = 3
} }
_, config.mock = os.LookupEnv("KDOORPI_MOCK_HW") _, config.mock = os.LookupEnv("KDOORPI_MOCK_HW")
config.prometheusMetricsBind = os.Getenv("KDOORPI_PROMETHEUS_METRICS_BIND")
if config.prometheusMetricsBind == "" {
config.prometheusMetricsBind = ":3334"
}
config.pins.wiegandA, err = strconv.Atoi(os.Getenv("KDOORPI_PIN_WIEGAND_A")) config.pins.wiegandA, err = strconv.Atoi(os.Getenv("KDOORPI_PIN_WIEGAND_A"))
if err != nil { if err != nil {
@ -142,16 +191,24 @@ func setup() {
} }
log.Println("HW Setup done") log.Println("HW Setup done")
go runHttpServer()
http.DefaultClient.Timeout = 120 * time.Second http.DefaultClient.Timeout = 120 * time.Second
go func() { go func() {
for { for {
err := waitEvents() err := waitEvents()
if err != nil { if err != nil {
apiFailuresCount.WithLabelValues("longpoll", config.api.longpoll).Inc()
log.Printf("LongPoll for events failed: %v", err) log.Printf("LongPoll for events failed: %v", err)
log.Println("Will try to LongPoll again in 120 seconds") log.Println("Will try to LongPoll again in 120 seconds")
time.Sleep(120 * time.Second) time.Sleep(120 * time.Second)
go reloadTokens() go func() {
err := reloadTokens()
if err != nil {
apiFailuresCount.WithLabelValues("allowed", config.api.allowed).Inc()
}
}()
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
@ -165,13 +222,12 @@ func setup() {
if err == nil { if err == nil {
break break
} }
apiFailuresCount.WithLabelValues("allowed", config.api.allowed).Inc()
log.Printf("Initial token population failed. err: %v", err) log.Printf("Initial token population failed. err: %v", err)
log.Println("Retrying in 10 seconds...") log.Println("Retrying in 10 seconds...")
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
} }
go runHttpServer()
log.Println("Initial token population success") log.Println("Initial token population success")
go cardRunner(wiegand) go cardRunner(wiegand)
@ -180,26 +236,9 @@ func setup() {
} }
func runHttpServer() { func runHttpServer() {
http.HandleFunc("/lastsync", func(w http.ResponseWriter, r *http.Request) { http.Handle("/metrics", promhttp.Handler())
e := json.NewEncoder(w) log.Printf("Running prometheus metrics on http://%s/metrics", config.prometheusMetricsBind)
e.Encode(map[string]any{ log.Fatal(http.ListenAndServe(config.prometheusMetricsBind, nil))
"last_synced": lastSyncedTimestamp,
})
})
http.HandleFunc("/opened", func(w http.ResponseWriter, r *http.Request) {
e := json.NewEncoder(w)
e.Encode(map[string]any{
"open_timestamps": openDoorTimestamps,
})
})
http.HandleFunc("/isopen", func(w http.ResponseWriter, r *http.Request) {
e := json.NewEncoder(w)
open, _ := wiegand.IsDoorOpen()
e.Encode(map[string]any{
"open": open,
})
})
http.ListenAndServe(":3334", nil)
} }
func OpenAndCloseDoor(w Wiegand) error { func OpenAndCloseDoor(w Wiegand) error {
@ -232,7 +271,6 @@ func OpenDoor(w Wiegand) error {
return nil return nil
} }
w.OpenDoor() w.OpenDoor()
openDoorTimestamps = append(openDoorTimestamps, OpenedTimestamp{Opened: time.Now(), Closed: nil})
return nil return nil
} }
@ -242,8 +280,6 @@ func CloseDoor(w Wiegand) error {
return nil return nil
} }
w.CloseDoor() w.CloseDoor()
t := time.Now()
openDoorTimestamps[len(openDoorTimestamps)-1].Closed = &t
return nil return nil
} }
@ -265,6 +301,7 @@ func cardRunner(w Wiegand) {
go func() { go func() {
err := sendSwipeEvent(hashedHex, ok) err := sendSwipeEvent(hashedHex, ok)
if err != nil { if err != nil {
apiFailuresCount.WithLabelValues("swipe", config.api.swipe).Inc()
log.Println("Failed to send swipe event: %v", err) log.Println("Failed to send swipe event: %v", err)
} }
}() }()
@ -272,10 +309,13 @@ func cardRunner(w Wiegand) {
if ok { if ok {
log.Println("Opening door") log.Println("Opening door")
err := OpenAndCloseDoor(w) err := OpenAndCloseDoor(w)
cardSwipesCount.WithLabelValues("accepted").Inc()
doorOpenedCount.WithLabelValues("card").Inc()
if err != nil { if err != nil {
log.Println("There was an error opening and closing the Door") log.Println("There was an error opening and closing the Door")
} }
} else { } else {
cardSwipesCount.WithLabelValues("denied").Inc()
log.Println("Unknown card") log.Println("Unknown card")
} }
@ -333,6 +373,7 @@ func waitEvents() error {
log.Printf("got server data: %q\n", data) log.Printf("got server data: %q\n", data)
if strings.TrimSpace(data) == config.door { if strings.TrimSpace(data) == config.door {
err := OpenAndCloseDoor(wiegand) err := OpenAndCloseDoor(wiegand)
doorOpenedCount.WithLabelValues("api").Inc()
if err != nil { if err != nil {
log.Println("There was an error opening and closing the Door") log.Println("There was an error opening and closing the Door")
} }
@ -381,13 +422,13 @@ func reloadTokens() error {
totalCardCount = i totalCardCount = i
} }
log.Printf("Got %d cards from server", totalCardCount) log.Printf("Got %d cards from server", totalCardCount)
nrCardsInAllowList.Set(float64(totalCardCount))
if cl.KeepOpenUntil != nil { if cl.KeepOpenUntil != nil {
updateKeepOpenDoor(*cl.KeepOpenUntil) updateKeepOpenDoor(*cl.KeepOpenUntil)
} }
t := time.Now() lastSyncTimestamp.SetToCurrentTime()
lastSyncedTimestamp = &t
return nil return nil
} }