【运维】自定义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.mfbz.cn/a/882675.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024最受欢迎的3款|数据库管理和开发|工具

1.SQLynx&#xff08;原SQL Studio&#xff09; 概述&#xff1a; SQLynx是一个原生基于Web的SQL编辑器&#xff0c;由北京麦聪软件有限公司开发。它最初被称为SQL Studio&#xff0c;后改名为SQLynx&#xff0c;支持企业的桌面和Web数据库管理。SQLynx支持所有流行的数据库&a…

lettuce引起的Redis command timeout异常

项目使用Lettuce&#xff0c;在自己的环境下跑是没有问题的。在给客户做售前压测时&#xff0c;因为客户端环境比较恶劣&#xff0c;service服务和中间件服务不在同一机房。服务启动后不一会就会出现Redis command timeout异常。 经过差不多两周的追查&#xff0c;最后没办法把…

Fyne ( go跨平台GUI )中文文档-Fyne总览(二)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2​​​​​​​ 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne…

本地生活商城开发搭建 同城O2O线上线下推广

同城本地化商城目前如火如荼&#xff0c;不少朋友咨询本地生活同城平台怎么开发&#xff0c;今天商淘云与大家分享同城O2O线上商城的设计和开发。 本地生活商城一般会涉及到区域以及频道类&#xff0c;一般下单需要支持用户定位、商家定位&#xff0c;这样利于用户可以快速找到…

Leetcode 反转链表

使用递归 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ class S…

音频3A——初步了解音频3A

文章目录 前言一、3A使用的场景和原理1.AEC2.AGC3.ANS/ANR4.硬件3A和软件3A的区别1&#xff09;层级不同2&#xff09;处理顺序不同3&#xff09;优缺点 5.处理过程 二、3A带来的问题三、开源3A算法总结 前言 在日常的音视频通话过程中&#xff0c;说话的双端往往会面对比较复…

Davinci 大数据可视化分析

Davinci 大数据可视化分析 一、Davinci 架构设计1.1 Davinci定义1.2 Davinci 应用场景 二、Davinci 安装部署2.1 部署规划2.2 前置环境准备2.3 Davinci部署2.3.1 物料准备2.3.2 安装配置 2.4 环境变量配置2.5 初始化数据库2.5.1 创建数据库及用户 2.5.2 建表2.6 初始化配置 三、…

Java反射机制入门:解锁运行时类信息的秘密

反射技术&#xff1a; 其实就是对类进行解剖的技术 类中有什么&#xff1f;构造方法 成员方法成员变量 结论&#xff1a;反射技术就是把一个类进行了解剖&#xff0c;然后获取到 构造方法、成员变量、成员方法 反射技术的应用案例&#xff1a; idea框架技术&#xff1a;Spr…

网络安全-ssrf

目录 一、环境 二、漏洞讲解 三、靶场讲解 四、可利用协议 4.1 dict协议 4.2 file协议 4.3 gopher协议 五、看一道ctf题吧&#xff08;长亭的比赛&#xff09; 5.1环境 5.2开始测试 ​编辑 一、环境 pikachu&#xff0c;这里我直接docker拉取的&#xff0c;我只写原…

基于vue框架的传统文化传播网站设计与实现f7r43(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,文化类型,传统文化 开题报告内容 基于Vue框架的传统文化传播网站设计与实现开题报告 一、研究背景 在全球化加速的今天&#xff0c;各国文化相互交融&#xff0c;但也面临着传统文化被边缘化的风险。中国拥有五千年文明史&#…

【通俗易懂介绍OAuth2.0协议以及4种授权模式】

文章目录 一.OAuth2.0协议介绍二.设计来源于生活三.关于令牌与密码的区别四.应用场景五.接下来分别简单介绍下四种授权模式吧1.客户端模式1.1 介绍1.2 适用场景1.3 时序图 2.密码模式2.1 介绍2.2 适用场景2.3时序图 3.授权码模式3.1 介绍3.2 适用场景3.3 时序图 4.简化模式4.1 …

数据的表示和存储 第3讲 C语言中的整数

深耕AI ​互联网行业 算法研发工程师 概括 本讲主要介绍了C语言中的整数表示。 无符号整数能够表示的最大值比带符号整数要大。带符号整数使用补码来表示&#xff0c;补码的运算系统是一种模运算系统&#xff0c;能够实现加减运算的统一。在C语言中&#xff0c;如果一个表达式…

利用F.interpolate()函数进行插值操作

函数简介 功能&#xff1a; 利用插值方法&#xff0c;对输入的张量数组进行上\下采样操作&#xff0c;换句话说就是科学合理地改变数组的尺寸大小&#xff0c;尽量保持数据完整。 torch.nn.functional.interpolate(input, sizeNone, scale_factorNone, modenearest, align_c…

【赵渝强老师】K8s的DaemonSets控制器

DaemonSet控制器相当于在节点上启动了一个守护进程。通过使用DaemonSet可以确保一个Pod的副本运行在 Node节点上。如果有新的Node节点加入集群&#xff0c;DaemonSet也会自动给新加入的节点增加一个Pod的副本&#xff1b;反之&#xff0c;当有Node节点从集群中移除时&#xff0…

EdgeRoute_镜像烧录

1. EdgeRouter 概述 EdgeRouter Lite 是由 Ubiquiti Networks 公司生产的一款高性能网络路由器&#xff0c;适用于家庭和小型办公环境。它的尺寸为200 x 90 x 30 mm&#xff0c;重量为345克&#xff0c;配备了双核500 MHz的MIPS64处理器&#xff0c;并带有硬件加速功能&#x…

MySQL_数据类型简介

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

Vue使用Vue Router路由:开发单页应用

1、路由基础 在单页 Web 应用中&#xff0c;整个项目只有一个 HTML 文件&#xff0c;不同视图&#xff08;组件的模块&#xff09;的内容都是在同一个页面中渲染的。当用户切换页面时&#xff0c;页面之前的跳转都是在浏览器端完成的&#xff0c;这时就需要使用前端路由。 路…

APP自动化中 ADB Monkey用法

一、monkey是干什么的&#xff1f; 我们可以使用monkey做手机端性能的压力测试&#xff0c;稳定性测试 二、monkey在使用的时候&#xff0c;他的运行特性 monkey默认配置下执行&#xff0c;会在手机中随机的点击或者轻触我们的手机中应用&#xff0c;不过这个时候&#xff0…

Cortex-M7核心寄存器

参考内容&#xff1a;Cortex-M7编程手册 文章目录 软件执行的处理器模式和权限级别处理器模式软件执行的权限级别 栈Stacks核心寄存器Core registers通用寄存器General-purpose registers链接寄存器Link register程序计数器 Program counter程序状态寄存器Program status regis…

PMP--二模--解题--91-100

文章目录 14.敏捷91、 [单选] 在敏捷团队完成三次迭代之后&#xff0c;项目经理确定团队在这三次迭代中的平均速度是30个故事点。还有292个故事点来完成项目的剩余部分。团队需要多少次额外的迭代才能完成项目&#xff1f; 9.资源管理92、 [单选] 项目经理前往另一个国家执行最…