Compare commits

..

1 Commits

Author SHA1 Message Date
Ian Fijolek
6c7c0a470f WIP: Begin adding prometheus metrics exporting 2019-11-15 11:25:21 -08:00
9 changed files with 34 additions and 224 deletions
+4 -50
View File
@@ -1,59 +1,13 @@
---
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
@@ -1,19 +0,0 @@
---
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
+10 -10
View File
@@ -7,17 +7,17 @@ AND DISTRIBUTION
1. Definitions. 1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution "License" shall mean the terms and conditions for use, reproduction, and distribution
as defined by Sections 1 through 9 of this document. as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright "Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License. owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities "Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity. that control, are controlled by, or are under common control with that entity.
@@ -26,31 +26,31 @@ or indirect, to cause the direction or management of such entity, whether
by contract or otherwise, or (ii) ownership of fifty percent (50%) or more by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
of the outstanding shares, or (iii) beneficial ownership of such entity. of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions "You" (or "Your") shall mean an individual or Legal Entity exercising permissions
granted by this License. granted by this License.
"Source" form shall mean the preferred form for making modifications, including "Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration but not limited to software source code, documentation source, and configuration
files. files.
"Object" form shall mean any form resulting from mechanical transformation "Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled object or translation of a Source form, including but not limited to compiled object
code, generated documentation, and conversions to other media types. code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, "Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that made available under the License, as indicated by a copyright notice that
is included in or attached to the work (an example is provided in the Appendix is included in or attached to the work (an example is provided in the Appendix
below). below).
"Derivative Works" shall mean any work, whether in Source or Object form, "Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial revisions, that is based on (or derived from) the Work and for which the editorial revisions,
@@ -59,7 +59,7 @@ original work of authorship. For the purposes of this License, Derivative
Works shall not include works that remain separable from, or merely link (or Works shall not include works that remain separable from, or merely link (or
bind by name) to the interfaces of, the Work and Derivative Works thereof. bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version "Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative of the Work and any modifications or additions to that Work or Derivative
@@ -74,7 +74,7 @@ for the purpose of discussing and improving the Work, but excluding communicatio
that is conspicuously marked or otherwise designated in writing by the copyright that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution." owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently incorporated of whom a Contribution has been received by Licensor and subsequently incorporated
+1 -15
View File
@@ -1,7 +1,6 @@
.PHONY: all
DOCKER_TAG ?= minitor-go-${USER} DOCKER_TAG ?= minitor-go-${USER}
.PHONY: default .PHONY: test
default: test default: test
.PHONY: build .PHONY: build
@@ -15,10 +14,6 @@ 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
@@ -29,15 +24,6 @@ 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:
+7 -9
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 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. 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.
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, `AlertNotice` defined in `alert.go` 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 containing Alert info 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,11 +84,10 @@ 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
- [x] Implement Prometheus client to export metrics - [ ] Implement Prometheus client to export metrics
- [x] Test coverage - [ ] Test coverage
- [ ] Integration testing (manual or otherwise)
Improvement (potentially breaking): Improvement:
- [ ] 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
@@ -96,4 +95,3 @@ Improvement (potentially breaking):
- [ ] 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
+2 -5
View File
@@ -2,11 +2,10 @@ 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
@@ -85,9 +84,7 @@ func LoadConfig(filePath string) (config Config, err error) {
return return
} }
if LogDebug { log.Printf("config:\n%v\n", config)
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")
+9 -15
View File
@@ -3,7 +3,9 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log" "log"
"net/http"
"time" "time"
) )
@@ -13,10 +15,6 @@ 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"
@@ -25,13 +23,7 @@ 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() {
success, alertNotice := monitor.Check() _, 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 {
@@ -63,9 +55,6 @@ 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)
@@ -79,6 +68,11 @@ 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)")
@@ -101,7 +95,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
@@ -1,101 +0,0 @@
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,3 +6,4 @@ 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