【运维】自定义exporter
文章目录
- 环境准备
- 代码编写
- 搭建开发环境和包依赖
- 创建main文件并进行初始化
- 添加prometheus metrics endpoint并监听服务端口
- 通过模拟url获取监控项的值
- 通过编写函数获取监控项的值
- 声明prometheus指标信息
- 声明prometheus接口框架
- 在main函数中声明exporter并注册
- 完整代码如下
环境准备
演示用例使用go语言进行开发,请准备golang开发环境。搭建方式可以参照golang环境搭建
代码编写
搭建开发环境和包依赖
- 创建工作目录my_exporter
go mod init my_exporter
go get github.com/prometheus/client_golang
go get github.com/joho/godotenv
创建main文件并进行初始化
package main
import (
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
package main
import (
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
}
添加prometheus metrics endpoint并监听服务端口
func main() {
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9141", nil))
}
通过模拟url获取监控项的值
http.HandleFunc("/api/channels/idsAndNames", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<map>
<entry>
<string>101af57f-f26c-40d3-86a3-309e74b93512</string>
<string>Send-Email-Notification</string>
</entry>
</map>`))
})
http.HandleFunc("/api/channels/statistics", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<list>
<channelStatistics>
<serverId>c5e6a736-0e88-46a7-bf32-5b4908c4d859</serverId>
<channelId>101af57f-f26c-40d3-86a3-309e74b93512</channelId>
<received>0</received>
<sent>0</sent>
<error>0</error>
<filtered>0</filtered>
<queued>0</queued>
</channelStatistics>
</list>`))
})
通过编写函数获取监控项的值
func get_sys_file_node_count() string {
cmd := exec.Command("bash", "-c", "cat /proc/sys/fs/file-nr | awk -F' ' '{ print $1 }'")
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
return string(out)
}
声明prometheus指标信息
在prometheus中,每个metric都由下面几部分组成:
- metric name:指标名称
- metric label value:指标标签的值
- metric help text:指标帮助文本
- metric type:指标类型
- measurement:测量值
messagesReceived = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_received_total"),
"How many messages have been received (per channel).",
[]string{"channel"}, nil,
)
声明prometheus接口框架
自定义exporter需要4个部分:
- A structure with member variables一个结构体
- A factory method that returns the structure返回结构体的工厂方法
- Describe function Describe函数
- Collect function Collect函数
type Exporter struct {
mirthEndpoint, mirthUsername, mirthPassword string
}
func NewExporter(mirthEndpoint string, mirthUsername string, mirthPassword string) *Exporter {
return &Exporter{
mirthEndpoint: mirthEndpoint,
mirthUsername: mirthUsername,
mirthPassword: mirthPassword,
}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
}
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
}
在main函数中声明exporter并注册
exporter := NewExporter(mirthEndpoint, mirthUsername, mirthPassword)
prometheus.MustRegister(exporter)
完整代码如下
package main
import (
"crypto/tls"
"encoding/xml"
"flag"
"io/ioutil"
"log"
"net/http"
"net"
"os"
"os/exec"
"strconv"
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type ChannelIdNameMap struct {
XMLName xml.Name `xml:"map"`
Entries []ChannelEntry `xml:"entry"`
}
type ChannelEntry struct {
XMLName xml.Name `xml:"entry"`
Values []string `xml:"string"`
}
type ChannelStatsList struct {
XMLName xml.Name `xml:"list"`
Channels []ChannelStats `xml:"channelStatistics"`
}
type ChannelStats struct {
XMLName xml.Name `xml:"channelStatistics"`
ServerId string `xml:"serverId"`
ChannelId string `xml:"channelId"`
Received string `xml:"received"`
Sent string `xml:"sent"`
Error string `xml:"error"`
Filtered string `xml:"filtered"`
Queued string `xml:"queued"`
}
const namespace = "mirth"
const channelIdNameApi = "/api/channels/idsAndNames"
const channelStatsApi = "/api/channels/statistics"
type tcpKeepAliveListener struct {
*net.TCPListener
}
func ListenAndServe(addr string, handler http.Handler) error {
srv := &http.Server{Addr: addr, Handler: handler}
addr = srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp4", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
var (
tr = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
listenAddress = flag.String("web.listen-address", "10.12.23.22:9141", "Address to listen on for telemetry")
metricsPath = flag.String("web.telemetry-path", "/metrics1", "Path under which to expose metrics")
up = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "up"),
"Was the last Mirth query successful.",
nil, nil,
)
messagesReceived = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_received_total"),
"How many messages have been received (per channel).",
[]string{"channel"}, nil,
)
messagesFiltered = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_filtered_total"),
"How many messages have been filtered (per channel).",
[]string{"channel"}, nil,
)
messagesQueued = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_queued"),
"How many messages have been queued (per channel).",
[]string{"channel"}, nil,
)
messagesSent = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_send_total"),
"How many messages have been sent (per channel).",
[]string{"channel"}, nil,
)
messagesErrored = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "messages_errored_total"),
"How many messages have been errored (per channel).",
[]string{"channel"}, nil,
)
count string = "10"
)
type Exporter struct {
mirthEndpoint, mirthUsername, mirthPassword string
}
func NewExporter(mirthEndpoint string, mirthUsername string, mirthPassword string) *Exporter {
return &Exporter{
mirthEndpoint: mirthEndpoint,
mirthUsername: mirthUsername,
mirthPassword: mirthPassword,
}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- up
ch <- messagesReceived
ch <- messagesFiltered
ch <- messagesQueued
ch <- messagesSent
ch <- messagesErrored
}
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
channelIdNameMap, err := e.LoadChannelIdNameMap()
if err !=nil {
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 0,
)
log.Println(err)
return
}
ch <- prometheus.MustNewConstMetric(
up, prometheus.GaugeValue, 1,
)
e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap, ch)
}
func (e *Exporter) LoadChannelIdNameMap() (map[string]string, error) {
channelIdNameMap := make(map[string]string)
req, err := http.NewRequest("GET", e.mirthEndpoint+channelIdNameApi, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(e.mirthUsername, e.mirthPassword)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
var channelIdNameMapXML ChannelIdNameMap
err = xml.Unmarshal(body, &channelIdNameMapXML)
if err != nil {
log.Println(err)
return nil, err
}
for i := 0; i < len(channelIdNameMapXML.Entries); i++ {
channelIdNameMap[channelIdNameMapXML.Entries[i].Values[0]] = channelIdNameMapXML.Entries[i].Values[1]
}
return channelIdNameMap, nil
}
func get_sys_file_node_count() string {
cmd := exec.Command("bash", "-c", "cat /proc/sys/fs/file-nr | awk -F' ' '{ print $1 }'")
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
return string(out)
}
func (e *Exporter) HitMirthRestApisAndUpdateMetrics(channelIdNameMap map[string]string, ch chan<- prometheus.Metric) {
req, err := http.NewRequest("GET", e.mirthEndpoint+channelStatsApi, nil)
if err != nil {
log.Fatal(err)
}
req.SetBasicAuth(e.mirthUsername, e.mirthPassword)
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Fatal(err)
}
var channelStatsList ChannelStatsList
err = xml.Unmarshal(body, &channelStatsList)
if err != nil {
log.Fatal(err)
}
for i := 0; i < len(channelStatsList.Channels); i++ {
channelName := channelIdNameMap[channelStatsList.Channels[i].ChannelId]
channelReceived, _ := strconv.ParseFloat(channelStatsList.Channels[i].Received, 64)
ch <- prometheus.MustNewConstMetric(
messagesReceived, prometheus.GaugeValue, channelReceived, channelName,
)
channelSent, _ := strconv.ParseFloat(channelStatsList.Channels[i].Sent, 64)
ch <- prometheus.MustNewConstMetric(
messagesSent, prometheus.GaugeValue, channelSent, channelName,
)
channelError, _ := strconv.ParseFloat(channelStatsList.Channels[i].Error, 64)
ch <- prometheus.MustNewConstMetric(
messagesErrored, prometheus.GaugeValue, channelError, channelName,
)
channelFiltered, _ := strconv.ParseFloat(channelStatsList.Channels[i].Filtered, 64)
ch <- prometheus.MustNewConstMetric(
messagesFiltered, prometheus.GaugeValue, channelFiltered, channelName,
)
var count1 = get_sys_file_node_count()
channelQueued, err := strconv.ParseFloat(count1[:len(count1)-1], 64)
if err != nil {
log.Println(err)
}
ch <- prometheus.MustNewConstMetric(
messagesQueued, prometheus.GaugeValue, channelQueued, channelName,
)
}
// log.Println("Endpoint scraped")
}
func main() {
err := godotenv.Load()
if err != nil {
log.Println("Error loading .env file, assume env variables are set.")
}
flag.Parse()
mirthEndpoint := os.Getenv("MIRTH_ENDPOINT")
mirthUsername := os.Getenv("MIRTH_USERNAME")
mirthPassword := os.Getenv("MIRTH_PASSWORD")
exporter := NewExporter(mirthEndpoint, mirthUsername, mirthPassword)
prometheus.MustRegister(exporter)
http.Handle(*metricsPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node Exporter</title>
<style>body {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Liberation Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
margin: 0;
}
header {
background-color: #e6522c;
color: #fff;
font-size: 1rem;
padding: 1rem;
}
main {
padding: 1rem;
}
label {
display: inline-block;
width: 0.5em;
}
</style>
</head>
<body>
<header>
<h1>Node Exporter</h1>
</header>
<main>
<h2>Prometheus Node Exporter</h2>
<div>Version: (version=1.8.2, branch=HEAD, revision=f1e0e8360aa60b6cb5e5cc1560bed348fc2c1895)</div>
<div>
<ul>
<li><a href="/metrics1">Metrics</a></li>
</ul>
</div>
</main>
</body>
</html>`))
})
http.HandleFunc("/api/channels/idsAndNames", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<map>
<entry>
<string>101af57f-f26c-40d3-86a3-309e74b93512</string>
<string>Send-Email-Notification</string>
</entry>
</map>`))
})
http.HandleFunc("/api/channels/statistics", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<list>
<channelStatistics>
<serverId>c5e6a736-0e88-46a7-bf32-5b4908c4d859</serverId>
<channelId>101af57f-f26c-40d3-86a3-309e74b93512</channelId>
<received>0</received>
<sent>0</sent>
<error>0</error>
<filtered>0</filtered>
<queued>0</queued>
</channelStatistics>
</list>`))
})
log.Fatal(http.ListenAndServe(*listenAddress, nil))
}