Skip to content

Commit

Permalink
Added Prometheus metrics.
Browse files Browse the repository at this point in the history
  • Loading branch information
a-bali committed Feb 8, 2024
1 parent 199fceb commit 6f2ea53
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 28 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ BUILD := $(shell date '+%FT%T%z')
COMMIT := $(shell git describe --always --long)

build:
go build -ldflags="-X main.version=$(VERSION) -X main.build=$(BUILD) -X main.commit=$(COMMIT)"
@go build -ldflags="-X main.version=$(VERSION) -X main.build=$(BUILD) -X main.commit=$(COMMIT)"

static_build:
CGO_ENABLED=0 go build -ldflags="-X main.version=$(VERSION) -X main.build=$(BUILD) -X main.commit=$(COMMIT)"
@CGO_ENABLED=0 go build -ldflags="-X main.version=$(VERSION) -X main.build=$(BUILD) -X main.commit=$(COMMIT)"

.PHONY: docker
docker:
docker build . -t $(IMAGE)

.PHONY: publish
.PHONY: publish
publish:
@docker buildx create --use --name=crossplat --node=crossplat && \
docker buildx build \
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Additionally, Janitor has a web interface where you can see the current status a
Finally, Janitor includes a simple JSON api with the following endpoints:
* `/api/data` provides a snapshot of all monitoring related data.
* `/api/stats` provides the count of monitoring targets in functional/dysfunctional state.
* `/api/metrics` provides target statistics in Prometheus metrics format.

## Screenshot
![Screenshot](https://raw.githubusercontent.com/a-bali/janitor/master/docs/screenshot.png)
Expand Down Expand Up @@ -58,7 +59,7 @@ Once you created a configuration file, Janitor can be launched as follows:

$ janitor path/to/your/configfile.yml

Janitor will log to standard output. The log is viewable on the web interface as well, where you can delete monitored targets and reload the configuration file (e.g. in case you added new targets or changed any of the settings).
Janitor will log to standard output. The log is viewable on the web interface as well, where you can delete monitored targets and reload the configuration file (e.g. in case you added new targets or changed any of the settings).

Janitor will not daemonize itself. It is recommended to create a systemd service for janitor in case you want it running continuously.

Expand Down
3 changes: 3 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ debug: true
# Maximum size of log history to maintain (default: 1000)
logsize: 1000

# hostname (default: obtained from OS or "janitor"), used for Prometheus metrics
hostname: "janitor"

# Configuration of built in web server:
# - Host name (default: '' meaning all interfaces will be bound)
# - Port number (default: 8080)
Expand Down
97 changes: 73 additions & 24 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"encoding/json"
"fmt"
"hash/fnv"
"io/ioutil"
"io"
"math"
"net/http"
"net/url"
Expand All @@ -26,9 +26,10 @@ import (

// Config stores the variables for runtime configuration.
type Config struct {
Debug bool
LogSize int
Web struct {
Debug bool
LogSize int
HostName string
Web struct {
Host string
Port int
}
Expand Down Expand Up @@ -172,11 +173,11 @@ type ExecMonitorData struct {

// MonitorData stores the actual status data of the monitoring process.
type MonitorData struct {
MQTT map[string]*MQTTMonitorData
Ping map[string]*PingMonitorData
HTTP map[string]*HTTPMonitorData
Exec map[string]*ExecMonitorData
sync.RWMutex
MQTT map[string]*MQTTMonitorData
Ping map[string]*PingMonitorData
HTTP map[string]*HTTPMonitorData
Exec map[string]*ExecMonitorData
sync.RWMutex `json:"-"`
}

// Data struct for serving main web page.
Expand Down Expand Up @@ -238,6 +239,8 @@ var (
const (
// MAXLOGSIZE defines the maximum lenght of the log history maintained (can be overridden in config)
MAXLOGSIZE = 1000
// Default hostname
HOSTNAME = "janitor"
// Status flags for monitoring.
STATUS_OK = 0
STATUS_WARN = 1
Expand All @@ -264,11 +267,19 @@ func main() {
http.HandleFunc("/config", configWebItem)
http.HandleFunc("/api/stats", serveAPIStats)
http.HandleFunc("/api/data", serveAPIData)
http.HandleFunc("/api/metrics", serveAPIMetrics)
panic(http.ListenAndServe(fmt.Sprintf("%s:%d", getConfig().Web.Host, getConfig().Web.Port), nil))
}

// Set defaults for configuration values.
func setDefaults(c *Config) {
if c.HostName == "" {
hostname, err := os.Hostname()
if err != nil {
hostname = HOSTNAME
}
c.HostName = hostname
}
if c.LogSize == 0 {
c.LogSize = MAXLOGSIZE
}
Expand Down Expand Up @@ -325,7 +336,7 @@ func loadConfig() {
log("Starting " + fullversion)

// (re)populate config struct from file
yamlFile, err := ioutil.ReadFile(configFile)
yamlFile, err := os.ReadFile(configFile)
if err != nil {
log("Unable to load config: " + err.Error())
return
Expand Down Expand Up @@ -921,7 +932,7 @@ func performHTTPCheck(url string, pattern string, timeout int) (bool, string, st
errValue = fmt.Sprintf("status code %d", resp.StatusCode)
} else {
defer resp.Body.Close()
bodyBytes, bodyErr := ioutil.ReadAll(resp.Body)
bodyBytes, bodyErr := io.ReadAll(resp.Body)
if bodyErr != nil {
errValue = bodyErr.Error()
} else {
Expand Down Expand Up @@ -1022,48 +1033,86 @@ func serveIndex(w http.ResponseWriter, r *http.Request) {
logLock.RUnlock()
}

// Serves the api/stats page.
func serveAPIStats(w http.ResponseWriter, r *http.Request) {
debug("Web request " + r.RequestURI + " from " + r.RemoteAddr)
o := 0
e := 0
// Counts targets per type and status
func calcStats() (map[string]int, map[string]int) {
up := make(map[string]int)
down := make(map[string]int)

monitorData.RLock()

up["mqtt"] = 0
down["mqtt"] = 0
for k := range monitorData.MQTT {
if !monitorData.MQTT[k].Deleted {
if monitorData.MQTT[k].Status == STATUS_ERROR {
e++
down["mqtt"]++
} else {
o++
up["mqtt"]++
}
}
}
up["ping"] = 0
down["ping"] = 0
for k := range monitorData.Ping {
if monitorData.Ping[k].Status == STATUS_ERROR {
e++
down["ping"]++
} else {
o++
up["ping"]++
}
}
up["http"] = 0
down["http"] = 0
for k := range monitorData.HTTP {
if monitorData.HTTP[k].Status == STATUS_ERROR {
e++
down["http"]++
} else {
o++
up["http"]++
}
}
up["exec"] = 0
down["exec"] = 0
for k := range monitorData.Exec {
if monitorData.Exec[k].Status == STATUS_ERROR {
e++
down["exec"]++
} else {
o++
up["exec"]++
}
}

monitorData.RUnlock()
return up, down

}

// Serves the api/stats page.
func serveAPIStats(w http.ResponseWriter, r *http.Request) {
debug("Web request " + r.RequestURI + " from " + r.RemoteAddr)
o := 0
e := 0
up, down := calcStats()
for k, c := range up {
o += c
e += down[k]
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(StatsData{o, e})
}

// Serves the api/metrics page.
func serveAPIMetrics(w http.ResponseWriter, r *http.Request) {
debug("Web request " + r.RequestURI + " from " + r.RemoteAddr)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, "HELP janitor_targets Number of Janitor targets")
fmt.Fprintln(w, "TYPE janitor_targets gauge")
up, down := calcStats()
for t, c := range up {
fmt.Fprintf(w, "janitor_targets{state=\"%s\", type=\"%s\", host=\"%s\"} %d\n", "up", t, config.HostName, c)
}
for t, c := range down {
fmt.Fprintf(w, "janitor_targets{state=\"%s\", type=\"%s\", host=\"%s\"} %d\n", "down", t, config.HostName, c)
}
}

// Serves the api/data page.
func serveAPIData(w http.ResponseWriter, r *http.Request) {
debug("Web request " + r.RequestURI + " from " + r.RemoteAddr)
Expand Down

0 comments on commit 6f2ea53

Please sign in to comment.