Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
salimd committed Nov 8, 2023
2 parents ede0683 + 2f220c3 commit 65dbef2
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 0 deletions.
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Supervisor Exporter

The Supervisor Exporter is a simple Go application that collects process status information from the Supervisor process control system and exposes it as Prometheus metrics. This allows you to monitor the state of processes managed by Supervisor.

## Table of Contents

- [Features](#features)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Prometheus Metrics](#prometheus-metrics)
- [License](#license)

## Features

- Collects process status information from Supervisor.
- Exposes process state, exit status, and more as Prometheus metrics.
- Configurable via command line parameters.
- Provides a simple HTTP server for Prometheus to scrape metrics.
- Handles unreachable Supervisord XML-RPC endpoints gracefully.

## Getting Started

### Prerequisites

Before running the Supervisor Exporter, make sure you have the following prerequisites:

- Go (Golang) installed on your system.

### Installation

1. Clone the repository:

```shell
git clone https://github.com/salimd/supervisord_exporter.git
```

2. Build the application:
```shell
go build
```

### Usage

To start the Supervisord Exporter, run the following command:

```shell
./supervisord_exporter
```

By default, the exporter will listen on port 9876 and use the Supervisor XML-RPC interface at `http://localhost:9001/RPC2`. You can change the defaults using command line parameters (see [Configuration](#configuration) section).

### Configuration

The Supervisord Exporter can be configured using command line parameters. Here are the available parameters:

* `-supervisord-url`: The URL of the Supervisord XML-RPC interface. Default is `http://localhost:9001/RPC2`
* `-web.listen-address`: The address and port where the exporter will listen for HTTP requests. Default is `:9876`
* `-web.telemetry-path`: Path under which to expose metrics. Default is `/metrics`
* `-version`: Print the version information and exit.

Example of custom configuration:

```shell
./supervisord_exporter -supervisord-url="http://example.com:9001/RPC2" -web.listen-address=":8080" -web.telemetry-path="/metrics"
```

### Prometheus Metrics

The Supervisord Exporter exposes the following Prometheus metric:

* `supervisor_process_info`: Gauge vector with labels for `name`, `group`, `state`, `start`, and `exit_status`. The `state` label will be `RUNNING` if the process is running, and `exit_status` will be `0` for a running process.
* `supervisord_up`: Gauge metric indicating the status of the connection to Supervisord (1 if up, 0 if down). If the Supervisord XML-RPC endpoint is unreachable, this metric will be set to 0, and there will be no supervisor_process_info metrics in the output.


Sample metric:
```
supervisor_process_info{exit_status="0",group="apache2",name="apache2",state="RUNNING"} 1
supervisord_up{} 1
```

### License

This project is licensed under the MIT License

17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module supervisord_exporter

go 1.19

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/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
golang.org/x/sys v0.11.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
40 changes: 40 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
132 changes: 132 additions & 0 deletions supervisord_exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"flag"
"fmt"
"log"
"net/http"
"os"

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

var (
supervisordURL string
listenAddress string
metricsPath string
version bool
appVersion float32 = 0.1

processesMetric = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "supervisor_process_info",
Help: "Supervisor process information",
},
[]string{"name", "group", "state", "start", "exit_status"},
)
supervisordUp = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "supervisord_up",
Help: "Supervisord XML-RPC connection status (1 if up, 0 if down)",
},
)
)

func init() {
flag.StringVar(&supervisordURL, "supervisord-url", "http://localhost:9001/RPC2", "Supervisord XML-RPC URL")
flag.StringVar(&listenAddress, "web.listen-address", ":9876", "Address to listen for HTTP requests")
flag.StringVar(&metricsPath, "web.telemetry-path", "/metrics", "Path under which to expose metrics")
flag.BoolVar(&version, "version", false, "Displays application version")

flag.Parse()
}

func fetchSupervisorProcessInfo() {
client, err := xmlrpc.NewClient(supervisordURL, nil)
if err != nil {
log.Printf("Error creating Supervisor XML-RPC client: %v", err)
supervisordUp.Set(0)
processesMetric.Reset()
return
}
defer client.Close()

result := []map[string]interface{}{}
if err := client.Call("supervisor.getAllProcessInfo", nil, &result); err != nil {
log.Printf("Error calling Supervisor XML-RPC method: %v", err)
supervisordUp.Set(0)
processesMetric.Reset()
return
}

supervisordUp.Set(1)

// Create a map to store the latest process information for each unique combination of name and group
latestInfo := make(map[string]map[string]interface{})

for _, data := range result {
name, _ := data["name"].(string)
group, _ := data["group"].(string)

// Generate a unique key for the combination of name and group
key := name + group

// Check if the latest information for this combination already exists
if existing, ok := latestInfo[key]; ok {
// Compare timestamps to determine which information is more recent
existingStartTime, _ := existing["start"].(int64)
newStartTime, _ := data["start"].(int64)

// If the new information is more recent, update the latestInfo map
if newStartTime > existingStartTime {
latestInfo[key] = data
}
} else {
// If no previous information exists for this combination, add it to the map
latestInfo[key] = data
}
}

// Clear the previous metric values
processesMetric.Reset()

for _, data := range latestInfo {
name, _ := data["name"].(string)
group, _ := data["group"].(string)
state, _ := data["statename"].(string)
start, _ := data["start"].(int64)
exitStatus, _ := data["exitstatus"].(int)

value := 0
if state == "RUNNING" {
value = 1
}

processesMetric.WithLabelValues(name, group, state, fmt.Sprintf("%d", start), fmt.Sprintf("%d", exitStatus)).Set(float64(value))
}
}

func metricsHandler(w http.ResponseWriter, r *http.Request) {
fetchSupervisorProcessInfo()
promhttp.Handler().ServeHTTP(w, r)
}

func main() {
if version {
fmt.Printf("Supervisor Exporter v%v\n", appVersion)
os.Exit(0)
}

prometheus.MustRegister(processesMetric)
prometheus.MustRegister(supervisordUp)

http.HandleFunc(metricsPath, metricsHandler)

fmt.Printf("Listening on %s\n", listenAddress)
if err := http.ListenAndServe(listenAddress, nil); err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
}
}

0 comments on commit 65dbef2

Please sign in to comment.