prometheus-yggdrasil-exporter/main.go

130 lines
3.0 KiB
Go

package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"net"
"net/http"
"net/url"
"strings"
)
type Request struct {
Name string `json:"request"`
KeepAlive bool `json:"keepalive"`
}
type Response struct {
Status string `json:"status"`
Response struct {
Error string `json:"error"`
Peers map[string]Peer `json:"peers"`
}
}
type Peer struct {
Key string `json:"key"`
Port int `json:"port"`
Coords []int `json:"coords"`
Remote string `json:"remote"`
BytesRecvd int `json:"bytes_recvd"`
BytesSent int `json:"bytes_sent"`
Uptime float64 `json:"uptime"`
}
func main() {
var endpoint string
var port int
flag.StringVar(&endpoint, "e", "", "The admin endpoint of the yggdrasil node")
flag.IntVar(&port, "p", 3000, "Port for the metrics endpoint to listen to")
flag.Parse()
if endpoint == "" {
panic("Specify endpoint, e.g. unix:///var/run/yggdrasil.sock")
}
conn := connect(endpoint)
encoder := json.NewEncoder(conn)
decoder := json.NewDecoder(conn)
recv := &Response{}
defer conn.Close()
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
encoder.Encode(&Request{
Name: "getPeers",
KeepAlive: true,
})
// compile the metrics here for the return document
var metrics []string
if err := decoder.Decode(&recv); err == nil {
for key, val := range recv.Response.Peers {
peers := []string{makeLabel("peer", key), makeLabel("remote", val.Remote)}
metrics = append(metrics, makeMetric("yggdrasil_peer_bytes_sent", val.BytesSent, peers...))
metrics = append(metrics, makeMetric("yggdrasil_peer_bytes_received", val.BytesRecvd, peers...))
metrics = append(metrics, makeMetric("yggdrasil_peer_uptime", val.Uptime, peers...))
}
results := strings.Join(metrics, "\n")
w.Write([]byte(results))
}
})
fmt.Printf("Starting yggdrasil exporter on port %d\n", port)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
// Make a metric
func makeMetric(name string, value interface{}, labels ...string) string {
if len(labels) > 0 {
return fmt.Sprintf("%s{%s} %v", name, strings.Join(labels, ","), value)
} else {
return fmt.Sprintf("%s %v", name, value)
}
}
// Make a metric label
func makeLabel(key string, value string) string {
return fmt.Sprintf("%s=\"%s\"", key, value)
}
// Connect to yggdrasil admin socket (shamelessly stole this from the source)
func connect(endpoint string) net.Conn {
var conn net.Conn
u, err := url.Parse(endpoint)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
fmt.Println("Connecting to UNIX socket", endpoint[7:])
conn, err = net.Dial("unix", endpoint[7:])
case "tcp":
fmt.Println("Connecting to TCP socket", u.Host)
conn, err = net.Dial("tcp", u.Host)
default:
fmt.Println("Unknown protocol or malformed address - check your endpoint")
err = errors.New("protocol not supported")
}
} else {
fmt.Println("Connecting to TCP socket", u.Host)
conn, err = net.Dial("tcp", endpoint)
}
if err != nil {
panic(err)
}
return conn
}