Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added prom metrics + changed sensor values to 2 decimal places #11

Merged
merged 3 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
# Go workspace file
go.work
cmd/terrarium-bot/test.yaml
cmd/terrarium-bot/terrarium-bot
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
[![Latest release](https://img.shields.io/github/v/release/alec-pinson/terrarium-bot?label=Latest%20release)](https://github.com/alec-pinson/terrarium-bot/releases/latest)
[![GitHub Release Date](https://img.shields.io/github/release-date/alec-pinson/terrarium-bot)](https://github.com/alec-pinson/terrarium-bot/releases/latest)

**CURRENTLY IN TESTING** - I have been running this for a week or so now and I am still fixing minor issues.

Manage a Terrarium. Temperature, humidity, heating, misting, lighting, all fully configurable and customisable via YAML.

I currently run this in a Kubernetes cluster (3 Raspberry Pis using [K3S](https://k3s.io/)). I run it along side my other apps:
Expand Down Expand Up @@ -244,5 +242,25 @@ Example response:-
}
```

## Prometheus Metrics
Prometheus metrics can be accessed via `:8081/metrics` and currently includes standard go metrics and the following custom metrics:-
```
# HELP terrarium_bot_http_client_pools_total The total number of http client pools
# TYPE terrarium_bot_http_client_pools_total counter
terrarium_bot_http_client_pools_total 3
# HELP terrarium_bot_http_requests_received_total The total number of http requests received by terrarium bot
# TYPE terrarium_bot_http_requests_received_total counter
terrarium_bot_http_requests_received_total 2
# HELP terrarium_bot_http_requests_sent_total The total number of http requests sent from terrarium bot
# TYPE terrarium_bot_http_requests_sent_total counter
terrarium_bot_http_requests_sent_total 33
# HELP terrarium_bot_sensor_humidity The current value of the humidity sensor
# TYPE terrarium_bot_sensor_humidity gauge
terrarium_bot_sensor_humidity 65
# HELP terrarium_bot_sensor_temperature The current value of the temperature sensor
# TYPE terrarium_bot_sensor_temperature gauge
terrarium_bot_sensor_temperature 27
```

## The End
Hopefully the configuration should be pretty self explanitory, if you get stuck or there are any features you think might be missing then feel free to create an issue :slightly_smiling_face:.
9 changes: 9 additions & 0 deletions cmd/terrarium-bot/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ import (
"net/http"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

type APIServer struct{}

var metricHttpRequestsReceived = promauto.NewCounter(prometheus.CounterOpts{
Name: "terrarium_bot_http_requests_received_total",
Help: "The total number of http requests received by terrarium bot",
})

func (apiServer APIServer) Start() {
log.Println("Starting API server...")
http.HandleFunc("/", apiServer.Endpoint)
Expand All @@ -19,6 +27,7 @@ func (apiServer APIServer) Start() {
}

func (apiServer APIServer) Endpoint(w http.ResponseWriter, r *http.Request) {
metricHttpRequestsReceived.Inc()
switch path := r.URL.Path[1:]; {
case path == "health/live" || path == "health/ready":
fmt.Fprintf(w, "ok")
Expand Down
10 changes: 5 additions & 5 deletions cmd/terrarium-bot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type Sensor struct {
Insecure bool `yaml:"insecure"`
JsonPath string `yaml:"jsonPath"`
Unit string `yaml:"unit"`
Value int
Value float64
}

type Notification struct {
Expand Down Expand Up @@ -90,13 +90,13 @@ type Alert struct {

type When struct {
Day struct {
Below int `yaml:"below"`
Above int `yaml:"above"`
Below float64 `yaml:"below"`
Above float64 `yaml:"above"`
Every time.Duration `yaml:"every"`
} `yaml:"day"`
Night struct {
Below int `yaml:"below"`
Above int `yaml:"above"`
Below float64 `yaml:"below"`
Above float64 `yaml:"above"`
Every time.Duration `yaml:"every"`
} `yaml:"night"`
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/terrarium-bot/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ alert:
assert.Equal(t, "trigger1", loadedConfig.Trigger[0].Id)
assert.Equal(t, "sensor1", loadedConfig.Trigger[0].Sensor)
assert.Equal(t, "http://localhost:8080", loadedConfig.Trigger[0].Endpoint)
assert.Equal(t, 5, loadedConfig.Trigger[0].When.Day.Below)
assert.Equal(t, 5, loadedConfig.Trigger[0].When.Night.Below)
assert.Equal(t, float64(5), loadedConfig.Trigger[0].When.Day.Below)
assert.Equal(t, float64(5), loadedConfig.Trigger[0].When.Night.Below)
assert.Equal(t, []string{"doSomething"}, loadedConfig.Trigger[0].Action)
assert.Equal(t, []string{"doSomethingElse"}, loadedConfig.Trigger[0].Else)

Expand Down Expand Up @@ -162,8 +162,8 @@ alert:
assert.Len(t, loadedConfig.Alert, 1)
assert.Equal(t, "alert1", loadedConfig.Alert[0].Id)
assert.Equal(t, "sensor1", loadedConfig.Alert[0].Sensor)
assert.Equal(t, 10, loadedConfig.Alert[0].When.Day.Below)
assert.Equal(t, 5, loadedConfig.Alert[0].When.Night.Below)
assert.Equal(t, float64(10), loadedConfig.Alert[0].When.Day.Below)
assert.Equal(t, float64(5), loadedConfig.Alert[0].When.Night.Below)
assert.Equal(t, 20*time.Minute, loadedConfig.Alert[0].After)
assert.Equal(t, []string{"notification1"}, loadedConfig.Alert[0].Notification)

Expand Down
17 changes: 16 additions & 1 deletion cmd/terrarium-bot/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,22 @@ import (
"net/url"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var httpClientPool []*HttpClientPool
var (
httpClientPool []*HttpClientPool
metricHttpClientPool = promauto.NewCounter(prometheus.CounterOpts{
Name: "terrarium_bot_http_client_pools_total",
Help: "The total number of http client pools",
})
metricHttpRequestsSent = promauto.NewCounter(prometheus.CounterOpts{
Name: "terrarium_bot_http_requests_sent_total",
Help: "The total number of http requests sent from terrarium bot",
})
)

type HttpClientPool struct {
Hostname string
Expand Down Expand Up @@ -45,6 +58,7 @@ func getClient(address string, insecure bool) *http.Client {
Hostname: hostname,
Client: client,
})
metricHttpClientPool.Inc()
return &client
}

Expand All @@ -71,6 +85,7 @@ func SendRequest(url string, insecure bool, retries int, decodeJson bool) (map[s
// retry x times if an error occurs, sleep 1 second each time
for i := 0; i < retries; i++ {
Debug("Request attempt %v/%v", i+1, retries)
metricHttpRequestsSent.Inc()
resp, err = client.Do(req)
if err == nil && resp.StatusCode == 200 {
break
Expand Down
60 changes: 60 additions & 0 deletions cmd/terrarium-bot/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,63 @@ func TestSendRequest(t *testing.T) {
log.SetOutput(os.Stderr)
})
}

func TestSendRequestConnectionPooling(t *testing.T) {
isTesting = true
config.Debug = true
httpClientPool = nil
var buf bytes.Buffer
log.SetOutput(&buf)

// Mock http request response
mockResponse := `{"key1": "value1", "key2": 2, "key3": ["element1", "element2"]}`
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, mockResponse)
})
server := httptest.NewServer(mux)
defer server.Close()

// first request should create a new http pool
SendRequest(server.URL, false, 1, true)
if got := buf.String(); !strings.Contains(got, "Creating http pool") {
t.Errorf("Expected new connection to be created: %q", got)
}

buf.Reset()
SendRequest(server.URL, false, 1, true)
if got := buf.String(); !strings.Contains(got, "conn was reused: true") {
t.Errorf("Expected connection to be reused: %q", got)
}

buf.Reset()
SendRequest(server.URL, false, 1, true)
if got := buf.String(); !strings.Contains(got, "conn was reused: true") {
t.Errorf("Expected connection to be reused: %q", got)
}

buf.Reset()
SendRequest(server.URL+"/123", false, 1, true)
if got := buf.String(); !strings.Contains(got, "conn was reused: true") {
t.Errorf("Expected connection to be reused: %q", got)
}

buf.Reset()
SendRequest(server.URL+"/abcdef", false, 1, true)
if got := buf.String(); !strings.Contains(got, "conn was reused: true") {
t.Errorf("Expected connection to be reused: %q", got)
}

buf.Reset()
SendRequest(server.URL, false, 1, true)
if got := buf.String(); !strings.Contains(got, "conn was reused: true") {
t.Errorf("Expected connection to be reused: %q", got)
}

// reset
isTesting = false
config.Debug = false
log.SetOutput(os.Stderr)
}
2 changes: 2 additions & 0 deletions cmd/terrarium-bot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
var (
config Config
apiServer APIServer
metrics Metrics
isTesting bool = false // flag used when testing
)

Expand All @@ -33,6 +34,7 @@ func main() {
InitTime()
InitAlerting()
apiServer.Start()
metrics.Start()
InitTriggers()
InitNotifications()

Expand Down
19 changes: 19 additions & 0 deletions cmd/terrarium-bot/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"log"
"net/http"
"time"

"github.com/prometheus/client_golang/prometheus/promhttp"
)

type Metrics struct{}

func (metrics Metrics) Start() {
log.Println("Starting Metrics server...")
http.Handle("/metrics", promhttp.Handler())
go http.ListenAndServe(":8081", nil)
time.Sleep(1 * time.Second) // give some time for metrics server to start before moving on
log.Println("Metrics Server started...")
}
27 changes: 21 additions & 6 deletions cmd/terrarium-bot/sensor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import (
"encoding/json"
"fmt"
"log"
"math"
"strconv"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

func GetSensor(id string) *Sensor {
Expand All @@ -26,15 +30,15 @@ func InitSensors() {
time.Sleep(5 * time.Second) // give abit of time for any sensors to collect data
}

func (s *Sensor) SetValue(value int) {
func (s *Sensor) SetValue(value float64) {
s.Value = value
}

func (s *Sensor) GetValue() int {
func (s *Sensor) GetValue() float64 {
return s.Value
}

func (s *Sensor) getSensorValue() int {
func (s *Sensor) getSensorValue() float64 {
r, respCode, err := SendRequest(s.Url, s.Insecure, 3, s.JsonPath != "")
if err != nil {
log.Println(err)
Expand All @@ -50,10 +54,15 @@ func (s *Sensor) getSensorValue() int {
return 0
}
value := getJsonValue(string(b), s.JsonPath)
intValue, err := strconv.Atoi(fmt.Sprintf("%.0f", value))
s.SetValue(intValue)
floatVal, err := strconv.ParseFloat(fmt.Sprint(value), 64)
if err != nil {
log.Println(err)
return 0
}
roundedValue := math.Round(floatVal*100) / 100
s.SetValue(roundedValue)
s.checkValue()
return intValue
return roundedValue
}

func (s *Sensor) checkValue() {
Expand All @@ -66,10 +75,16 @@ func (s *Sensor) checkValue() {
}

func (s *Sensor) monitor() {
var sensorMetrics = promauto.NewGauge(prometheus.GaugeOpts{
Name: "terrarium_bot_sensor_" + s.Id,
Help: "The current value of the " + s.Id + " sensor",
})

val := s.getSensorValue()
log.Printf("Monitoring sensor '%s' (%v%s)", s.Id, val, s.Unit)
for {
val = s.getSensorValue()
sensorMetrics.Set(float64(val))
Debug("%s: %v%s", strings.Title(s.Id), val, s.Unit)
time.Sleep(1 * time.Minute)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/terrarium-bot/sensor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ func TestSensorSetValue(t *testing.T) {
s := &Sensor{}
s.SetValue(42)
if s.Value != 42 {
t.Errorf("Expected sensor value to be 42, but got %d", s.Value)
t.Errorf("Expected sensor value to be 42, but got %v", s.Value)
}
}

func TestSensorGetValue(t *testing.T) {
s := &Sensor{Value: 42}
v := s.GetValue()
if v != 42 {
t.Errorf("Expected sensor value to be 42, but got %d", v)
t.Errorf("Expected sensor value to be 42, but got %v", v)
}
}
8 changes: 4 additions & 4 deletions cmd/terrarium-bot/trigger.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package main

import (
"fmt"
"log"
"strconv"
"time"
)

Expand All @@ -23,16 +23,16 @@ func InitTriggers() {
}
}

func GenerateReason(value int, unit string, maxValue int) string {
return strconv.Itoa(value) + unit + "/" + strconv.Itoa(maxValue) + unit
func GenerateReason(value float64, unit string, maxValue float64) string {
return fmt.Sprintf("%.2f", value) + unit + "/" + fmt.Sprintf("%.2f", maxValue) + unit
}

func (t *Trigger) monitor() {
var (
s *Sensor
runAction bool
reason string
value int
value float64
)

// get trigger sensor if one is set
Expand Down
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,23 @@ require github.com/thedevsaddam/gojsonq v2.3.0+incompatible

require (
github.com/gregdel/pushover v1.1.0
github.com/prometheus/client_golang v1.15.1
github.com/stretchr/testify v1.8.2
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/sys v0.6.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading