当前位置: 首页 > article >正文

【运维】自定义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))
}


http://www.kler.cn/news/311553.html

相关文章:

  • Redis——笔记01
  • 【PyQt5】object属性
  • Java中的异步编程模式:CompletableFuture与Reactive Programming的实战
  • 性格类型识别系统源码分享
  • DTD 实体
  • 【HTTP】HTTP报文格式和抓包
  • C++初阶:STL详解(五)——vector的模拟实现
  • 【JOIN 详解】SQL连接全面解析:从基础到实战
  • PostgreSQL主从切换测试
  • 使用BGP及静态路由方式实现链路冗余和ByPass
  • C:字符串函数(完)-学习笔记
  • 北斗盒子TD20——水上作业的安全防线,落水报警守护生命
  • React 中的延迟加载
  • 音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现
  • AUTOSAR_EXP_ARAComAPI的5章笔记(6)
  • 高级java每日一道面试题-2024年9月18日-设计模式篇-JDK动态代理,CGLIB代理,AspectJ区别?
  • 组件封装有哪些注意事项—面试常问优美回答
  • 2024网站建设比较好的公司都有哪些
  • re题(35)BUUCTF-[FlareOn4]IgniteMe
  • Docker Redis 7.2.3 部署
  • Spark实操学习
  • 集合框架底层使用了什么数据结构
  • 关于 Goroutines 和并发控制的 Golang 难题
  • 【网络安全的神秘世界】目录遍历漏洞
  • AJAX Jquery $.get $.post $.getJSON
  • STP生成树
  • css 中 em 单位怎么用
  • 医疗数据分析师
  • Uniapp的alertDialog返回值+async/await处理确定/取消问题
  • 矿场工程车检测数据集 4900张 工程车 带标注voc yolo