Skip to content

Instantly share code, notes, and snippets.

@jyxjjj
Created June 26, 2025 09:30
Show Gist options
  • Save jyxjjj/6ccf795cab21e34e035d0f13daee8d0d to your computer and use it in GitHub Desktop.
Save jyxjjj/6ccf795cab21e34e035d0f13daee8d0d to your computer and use it in GitHub Desktop.
Supervisor Monitor
package main
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"net"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// supervisor XML-RPC request body
const xmlrpcRequest = `<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.getAllProcessInfo</methodName>
</methodCall>`
type ProcessInfo struct {
Name string `xml:"name"`
Group string `xml:"group"`
Description string `xml:"description"`
Start int64 `xml:"start"`
Stop int64 `xml:"stop"`
Now int64 `xml:"now"`
State int `xml:"state"`
Statename string `xml:"statename"`
Spawnerr string `xml:"spawnerr"`
Exitstatus int `xml:"exitstatus"`
Logfile string `xml:"logfile"`
StdoutLogfile string `xml:"stdout_logfile"`
StderrLogfile string `xml:"stderr_logfile"`
Pid int `xml:"pid"`
}
func getSupervisorMetrics() (string, error) {
// 通过 unix socket 连接 supervisor
dial := func(proto, addr string) (net.Conn, error) {
return net.Dial("unix", "/var/run/supervisor.sock")
}
client := &http.Client{
Transport: &http.Transport{
Dial: dial,
},
}
// supervisor 的 XML-RPC API 路径
url := "http://unix/RPC2"
resp, err := client.Post(url, "text/xml", strings.NewReader(xmlrpcRequest))
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// 解析 XML-RPC 响应
var result struct {
XMLName xml.Name `xml:"methodResponse"`
Params struct {
Param struct {
Value struct {
Array struct {
Data struct {
Values []struct {
Struct struct {
Members []struct {
Name string `xml:"name"`
Value struct {
String string `xml:"string"`
Int int `xml:"int"`
I4 int `xml:"i4"`
} `xml:",any"`
} `xml:"member"`
} `xml:"struct"`
} `xml:"value"`
} `xml:"data"`
} `xml:"array"`
} `xml:"value"`
} `xml:"param"`
} `xml:"params"`
}
if err := xml.Unmarshal(body, &result); err != nil {
return "", err
}
// 组装 Prometheus 格式
var buf bytes.Buffer
buf.WriteString("# HELP supervisor_process_status Supervisor process status (1=RUNNING, 0=other)\n")
buf.WriteString("# TYPE supervisor_process_status gauge\n")
for _, v := range result.Params.Param.Value.Array.Data.Values {
var name, statename string
for _, m := range v.Struct.Members {
if m.Name == "name" {
name = m.Value.String
}
if m.Name == "statename" {
statename = m.Value.String
}
}
value := 0
if statename == "RUNNING" {
value = 1
}
buf.WriteString(fmt.Sprintf("supervisor_process_status{name=\"%s\"} %d\n", name, value))
}
return buf.String(), nil
}
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.GET("/metrics", func(c *gin.Context) {
metrics, err := getSupervisorMetrics()
if err != nil {
c.String(http.StatusOK, "# supervisor not available\n")
return
}
c.Data(http.StatusOK, "text/plain; charset=utf-8", []byte(metrics))
})
r.Run(":9093")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment