Compare commits

..

8 Commits

Author SHA1 Message Date
Ian Fijolek
5141df3878 Do build and test in one image 2019-11-21 15:38:29 -08:00
Ian Fijolek
c770da4d12 Try to cache go modules 2019-11-21 15:36:30 -08:00
Ian Fijolek
9b9f803231 Add pre-commit to the repo
This adds pre-commit which can be used to enforce consistent style
and common errors (like committing large files)
2019-11-21 15:32:57 -08:00
Ian Fijolek
b808df7365 Update README to indicate where to get alert template info 2019-11-20 17:21:48 -08:00
Ian Fijolek
b1422bbec2 Add split out metrics to a new make target
In case trying to run without having an available port to
serve http on
2019-11-15 17:36:10 -08:00
Ian Fijolek
604c27118a Add Docker deploy pipeline 2019-11-15 17:30:29 -08:00
Ian Fijolek
b2d9882c91 Update readme with accurate To do status 2019-11-15 17:17:46 -08:00
Ian Fijolek
457e19af9b Add prometheus metrics exporter
This should add metrics parity to the Python version
2019-11-15 17:14:20 -08:00
9 changed files with 224 additions and 34 deletions
+50 -4
View File
@@ -1,13 +1,59 @@
---
kind: pipeline kind: pipeline
name: test name: test
steps: steps:
- name: build
image: golang:1.12
commands:
- make build
- name: test - name: test
image: golang:1.12 image: golang:1.12
commands: commands:
- make build
- make test - make test
- name: check
image: python:3
commands:
- pip install pre-commit==1.20.0
- make check
- name: notify
image: drillster/drone-email
settings:
host:
from_secret: SMTP_HOST
username:
from_secret: SMTP_USER
password:
from_secret: SMTP_PASS
from: drone@iamthefij.com
when:
status: [changed, failure]
---
kind: pipeline
name: publish
depends_on:
- test
trigger:
event:
- push
- tag
refs:
- refs/heads/master
- refs/tags/v*
steps:
# Might consider moving this step into the previous pipeline
- name: push image
image: plugins/docker
settings:
repo: iamthefij/minitor-go
dockerfile: Dockerfile.multi-stage
auto_tag: true
username:
from_secret: docker_username
password:
from_secret: docker_password
+19
View File
@@ -0,0 +1,19 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-added-large-files
- id: check-yaml
args:
- --allow-multiple-documents
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- repo: git://github.com/dnephin/pre-commit-golang
rev: v0.3.5
hooks:
- id: go-fmt
- id: go-imports
# - id: gometalinter
# - id: golangci-lint
+15 -1
View File
@@ -1,6 +1,7 @@
.PHONY: all
DOCKER_TAG ?= minitor-go-${USER} DOCKER_TAG ?= minitor-go-${USER}
.PHONY: test .PHONY: default
default: test default: test
.PHONY: build .PHONY: build
@@ -14,6 +15,10 @@ minitor-go:
run: minitor-go build run: minitor-go build
./minitor-go -debug ./minitor-go -debug
.PHONY: run-metrics
run-metrics: minitor-go build
./minitor-go -debug -metrics
.PHONY: test .PHONY: test
test: test:
go test -coverprofile=coverage.out go test -coverprofile=coverage.out
@@ -24,6 +29,15 @@ test:
@go tool cover -func=coverage.out | awk -v target=80.0% \ @go tool cover -func=coverage.out | awk -v target=80.0% \
'/^total:/ { print "Total coverage: " $$3 " Minimum coverage: " target; if ($$3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }' '/^total:/ { print "Total coverage: " $$3 " Minimum coverage: " target; if ($$3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }'
# Installs pre-commit hooks
.PHONY: install-hooks
install-hooks:
pre-commit install --install-hooks
# Checks files for encryption
.PHONY: check
check:
pre-commit run --all-files
.PHONY: clean .PHONY: clean
clean: clean:
+9 -7
View File
@@ -2,7 +2,7 @@
A reimplementation of [Minitor](https://git.iamthefij/iamthefij/minitor) in Go A reimplementation of [Minitor](https://git.iamthefij/iamthefij/minitor) in Go
Minitor is already a very minimal monitoring tool. Python 3 was a quick way to get something live, but Python itself comes with a very large footprint.Thus Go feels like a better fit for the project, longer term. Minitor is already a minimal monitoring tool. Python 3 was a quick way to get something live, but Python itself comes with a large footprint. Thus Go feels like a better fit for the project, longer term.
Initial target is meant to be roughly compatible requiring only minor changes to configuration. Future iterations may diverge to take advantage of Go specific features. Initial target is meant to be roughly compatible requiring only minor changes to configuration. Future iterations may diverge to take advantage of Go specific features.
@@ -30,7 +30,7 @@ monitors:
command_shell: echo 'test' command_shell: echo 'test'
``` ```
Second, templating for Alert messages has been updated. In the Python version, `str.format(...)` was used with certain keys passed in that could be used to format messages. In the Go version, we use a struct containing Alert info and the built in Go templating format. Eg. Second, templating for Alert messages has been updated. In the Python version, `str.format(...)` was used with certain keys passed in that could be used to format messages. In the Go version, we use a struct, `AlertNotice` defined in `alert.go` and the built in Go templating format. Eg.
minitor-py: minitor-py:
```yaml ```yaml
@@ -38,7 +38,7 @@ alerts:
log_command: log_command:
command: ['echo', '{monitor_name}'] command: ['echo', '{monitor_name}']
log_shell: log_shell:
command_shell: "echo {monitor_name}" command_shell: 'echo {monitor_name}'
``` ```
minitor-go: minitor-go:
@@ -47,7 +47,7 @@ alerts:
log_command: log_command:
command: ['echo', '{{.MonitorName}}'] command: ['echo', '{{.MonitorName}}']
log_shell: log_shell:
command_shell: "echo {{.MonitorName}}" command_shell: 'echo {{.MonitorName}}'
``` ```
Finally, newlines in a shell command don't terminate a particular command. Semicolons must be used and continuations should not. Finally, newlines in a shell command don't terminate a particular command. Semicolons must be used and continuations should not.
@@ -84,10 +84,11 @@ Pairity:
- [x] Run alert commands - [x] Run alert commands
- [x] Run alert commands in a shell - [x] Run alert commands in a shell
- [x] Allow templating of alert commands - [x] Allow templating of alert commands
- [ ] Implement Prometheus client to export metrics - [x] Implement Prometheus client to export metrics
- [ ] Test coverage - [x] Test coverage
- [ ] Integration testing (manual or otherwise)
Improvement: Improvement (potentially breaking):
- [ ] Implement leveled logging (maybe glog or logrus) - [ ] Implement leveled logging (maybe glog or logrus)
- [ ] Consider switching from YAML to TOML - [ ] Consider switching from YAML to TOML
@@ -95,3 +96,4 @@ Improvement:
- [ ] Consider dropping `alert_up` and `alert_down` in favor of using Go templates that offer more control of messaging - [ ] Consider dropping `alert_up` and `alert_down` in favor of using Go templates that offer more control of messaging
- [ ] Async checking - [ ] Async checking
- [ ] Use durations rather than seconds checked in event loop - [ ] Use durations rather than seconds checked in event loop
- [ ] Revisit metrics and see if they all make sense
+5 -2
View File
@@ -2,10 +2,11 @@ package main
import ( import (
"errors" "errors"
"gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"gopkg.in/yaml.v2"
) )
// Config type is contains all provided user configuration // Config type is contains all provided user configuration
@@ -84,7 +85,9 @@ func LoadConfig(filePath string) (config Config, err error) {
return return
} }
log.Printf("config:\n%v\n", config) if LogDebug {
log.Printf("DEBUG: Config values:\n%v\n", config)
}
if !config.IsValid() { if !config.IsValid() {
err = errors.New("Invalid configuration") err = errors.New("Invalid configuration")
+15 -9
View File
@@ -3,9 +3,7 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log" "log"
"net/http"
"time" "time"
) )
@@ -15,6 +13,10 @@ var (
// ExportMetrics will track whether or not we want to export metrics to prometheus // ExportMetrics will track whether or not we want to export metrics to prometheus
ExportMetrics = false ExportMetrics = false
// MetricsPort is the port to expose metrics on
MetricsPort = 8080
// Metrics contains all active metrics
Metrics = NewMetrics()
// version of minitor being run // version of minitor being run
version = "dev" version = "dev"
@@ -23,7 +25,13 @@ var (
func checkMonitors(config *Config) error { func checkMonitors(config *Config) error {
for _, monitor := range config.Monitors { for _, monitor := range config.Monitors {
if monitor.ShouldCheck() { if monitor.ShouldCheck() {
_, alertNotice := monitor.Check() success, alertNotice := monitor.Check()
hasAlert := alertNotice != nil
// Track status metrics
Metrics.SetMonitorStatus(monitor.Name, success)
Metrics.CountCheck(monitor.Name, success, hasAlert)
// Should probably consider refactoring everything below here // Should probably consider refactoring everything below here
if alertNotice != nil { if alertNotice != nil {
@@ -55,6 +63,9 @@ func checkMonitors(config *Config) error {
err, err,
) )
} }
// Count alert metrics
Metrics.CountAlert(monitor.Name, alert.Name)
} else { } else {
// This case should never actually happen since we validate against it // This case should never actually happen since we validate against it
log.Printf("ERROR: Unknown alert for monitor %s: %s", alertNotice.MonitorName, alertName) log.Printf("ERROR: Unknown alert for monitor %s: %s", alertNotice.MonitorName, alertName)
@@ -68,11 +79,6 @@ func checkMonitors(config *Config) error {
return nil return nil
} }
func serveMetrics() {
http.Handle("/metrics", promhttp.Handler())
_ = http.ListenAndServe(":8080", nil)
}
func main() { func main() {
// Get debug flag // Get debug flag
flag.BoolVar(&LogDebug, "debug", false, "Enables debug logs (default: false)") flag.BoolVar(&LogDebug, "debug", false, "Enables debug logs (default: false)")
@@ -95,7 +101,7 @@ func main() {
// Serve metrics exporter, if specified // Serve metrics exporter, if specified
if ExportMetrics { if ExportMetrics {
log.Println("INFO: Exporting metrics to Prometheus") log.Println("INFO: Exporting metrics to Prometheus")
go serveMetrics() go ServeMetrics()
} }
// Start main loop // Start main loop
+101
View File
@@ -0,0 +1,101 @@
package main
import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// TODO: Not sure if this is the best way to handle. A global instance for
// metrics isn't bad, but it might be nice to curry versions of the metrics
// for each monitor. Especially since every monitor has it's own. Perhaps
// another new function that essentially curries each metric for a given
// monitor name would do. This could be run when validating monitors and
// initializing alert templates.
// MinitorMetrics contains all counters and metrics that Minitor will need to access
type MinitorMetrics struct {
alertCount *prometheus.CounterVec
checkCount *prometheus.CounterVec
monitorStatus *prometheus.GaugeVec
}
// NewMetrics creates and initializes all metrics
func NewMetrics() *MinitorMetrics {
// Initialize all metrics
metrics := &MinitorMetrics{
alertCount: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "minitor_alert_total",
Help: "Number of Minitor alerts",
},
[]string{"alert", "monitor"},
),
checkCount: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "minitor_check_total",
Help: "Number of Minitor checks",
},
[]string{"monitor", "status", "is_alert"},
),
monitorStatus: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "minitor_monitor_up_count",
Help: "Status of currently responsive monitors",
},
[]string{"monitor"},
),
}
// Register newly created metrics
prometheus.MustRegister(metrics.alertCount)
prometheus.MustRegister(metrics.checkCount)
prometheus.MustRegister(metrics.monitorStatus)
return metrics
}
// SetMonitorStatus sets the current status of Monitor
func (metrics *MinitorMetrics) SetMonitorStatus(monitor string, isUp bool) {
val := 0.0
if isUp {
val = 1.0
}
metrics.monitorStatus.With(prometheus.Labels{"monitor": monitor}).Set(val)
}
// CountCheck counts the result of a particular Monitor check
func (metrics *MinitorMetrics) CountCheck(monitor string, isSuccess bool, isAlert bool) {
status := "failure"
if isSuccess {
status = "success"
}
alertVal := "false"
if isAlert {
alertVal = "true"
}
metrics.checkCount.With(
prometheus.Labels{"monitor": monitor, "status": status, "is_alert": alertVal},
).Inc()
}
// CountAlert counts an alert
func (metrics *MinitorMetrics) CountAlert(monitor string, alert string) {
metrics.alertCount.With(
prometheus.Labels{
"alert": alert,
"monitor": monitor,
},
).Inc()
}
// ServeMetrics starts an http server with a Prometheus metrics handler
func ServeMetrics() {
http.Handle("/metrics", promhttp.Handler())
host := fmt.Sprintf(":%d", MetricsPort)
_ = http.ListenAndServe(host, nil)
}
-1
View File
@@ -6,4 +6,3 @@ monitors:
alert_down: [ 'alert_down', 'log_shell', 'log_command' ] alert_down: [ 'alert_down', 'log_shell', 'log_command' ]
# alert_every: -1 # alert_every: -1
alert_every: 0 alert_every: 0