【运维】自定义exporter

devtools/2024/9/24 21:48:15/

文章目录

  • 环境准备
  • 代码编写
    • 搭建开发环境和包依赖
    • 创建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 mainimport ("github.com/joho/godotenv""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp"
)
package mainimport ("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 mainimport ("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.Addrif 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 <- upch <- messagesReceivedch <- messagesFilteredch <- messagesQueuedch <- messagesSentch <- 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 ChannelIdNameMaperr = 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 ChannelStatsListerr = 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.ppmy.cn/devtools/116682.html

相关文章

一.python入门

gyp的读研日记&#xff0c;哈哈哈哈&#xff0c;&#x1f642;&#xff0c;从复习python开始&#xff0c; 目录 1.python入门 1.1 Python说明书 1.2 Python具备的功能 1.3 学习前提 1.4 何为Python 1.5 编程语言 2.Python环境搭建 2.1 开发环境概述 2.2 Python的安装与…

初始爬虫6

数据提取 数据提取总结 响应分类 结构化 json数据&#xff08;高频出现&#xff09; json模块 jsonpath模块 xml数据&#xff08;低频出现&#xff09; re模块 …

Spring中的容器接口

容器接口 首先了解一下BeanFactory和ApplicationContext这两个接口的关系。 其实在一个 SpringBoot 项目中&#xff0c;这个 SpringBoot 项目的启动类的返回值就是一个 ApplicationContext 接口的实现类。 然后在 IDEA 中选中这个类&#xff0c;按住ctrlaltU可以查看类图&…

德蒂企鹅PAEDIPROTECT:德国医研力作,专为敏感肌婴幼儿量身打造

新生儿的诞生总是伴随着喜悦&#xff0c;也充满着手忙脚乱&#xff0c;尤其是敏感肌宝宝的皮肤护理。宝宝的皮肤如同初绽的花瓣&#xff0c;皮肤角质层薄而脆弱&#xff0c;容易受到外界刺激物的影响&#xff0c;水分流失快&#xff0c;经常会出现干燥、瘙痒、红斑甚至湿疹等症…

胤娲科技:DeepMind的FermiNet——带你穿越“薛定谔的早餐桌”

当AI遇上量子迷雾&#xff0c;FermiNet成了你的“量子导航仪” 想象一下&#xff0c;你早晨醒来&#xff0c;发现家里的厨房变成了薛定谔的实验室&#xff0c;你的咖啡杯和吐司同时处于“存在与不存在”的叠加态。 你伸手去拿&#xff0c;却不确定会不会摸到冰冷的空气或是热腾…

关于中断和异常的一些理解

异常向量表的理解&#xff0c;每个异常都有对应的异常号码即中断号&#xff0c;根据发生的异常号去异常向量表(数组)里面执行对应的异常服务函数。这段话的表述哪里有问题&#xff1f; 总体上是正确的&#xff0c;但可以进一步澄清和细化几个方面&#xff0c;以增强对异常向量…

电脑如何设置代理IP:详细步骤指南

在网络世界中&#xff0c;代理IP是一种非常实用的工具。它不仅能保护你的隐私&#xff0c;还能访问更多的网络资源。今天&#xff0c;我们就来详细讲解一下如何在电脑上设置代理IP&#xff0c;让你轻松掌握这项技能。 什么是代理IP&#xff1f; 代理IP&#xff0c;简单来说&am…

element-plus表格操作

elememt-plus安装见上文 表格的特性 element-plus中的表格和原版表格最大的不同是写法不同&#xff0c;原版表格以行的方式写&#xff0c;element-plus以列的方式写。 element-plus的表格可以更方便的展示数据&#xff0c;只需要考虑数据的格式即可。 表格标签 表格标签有两种…