From 503ca0fd726be1fbaabc697dba1fbe05ec4b8ba6 Mon Sep 17 00:00:00 2001 From: sre Date: Thu, 14 Jul 2022 15:27:23 +0800 Subject: [PATCH] sre exporter --- .env.template | 3 + .gitignore | 9 ++ Dockerfile | 0 Makefile | 7 + collector/cpu.go | 76 +++++++++ collector/exporter.go | 114 +++++++++++++ collector/scraper.go | 21 +++ collector/switch.go | 8 + go.mod | 24 +++ main.go | 77 +++++++++ main.go.txt | 281 +++++++++++++++++++++++++++++++++ mirth_channel_exporter.service | 14 ++ readme.md | 92 +++++++++++ 13 files changed, 726 insertions(+) create mode 100644 .env.template create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 collector/cpu.go create mode 100644 collector/exporter.go create mode 100644 collector/scraper.go create mode 100644 collector/switch.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 main.go.txt create mode 100644 mirth_channel_exporter.service create mode 100644 readme.md diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..bd17732 --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +MIRTH_ENDPOINT=https://mirth-connect.yourcompane.com +MIRTH_USERNAME=admin +MIRTH_PASSWORD=admin \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2356efb --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.idea +/.idea/ +/.idea/* +.idea +.idea/ +.idea/* +go.sum +.env +mirth_channel_exporter \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ea7157 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +linux: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build +mac: + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build +arm: + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go mod tidy + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o exporter main.go \ No newline at end of file diff --git a/collector/cpu.go b/collector/cpu.go new file mode 100644 index 0000000..0d4970d --- /dev/null +++ b/collector/cpu.go @@ -0,0 +1,76 @@ +package collector + +//这个就相对简单。我们只需使用之前定义的函数方法。满足name以及Scrape 接口即可 然后通过chan <- prometheus.Metric只写管道来写入我们需要的指标, +//这里需要注意的是我们之前提到的4中prometheus指标类型有一些在这里需要float64的值传入 +//但是我们收集到的并不一定都是float64类型的那么我们在做类型转换的过程中需要考虑大转小溢出的问题 + +import ( + "fmt" + "github.com/prometheus/client_golang/prometheus" + "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/load" + "log" + "net" +) + +type CpuLoad struct{} + +// cpu info + +func (CpuLoad) Name() string { + return namespace + "_cpu_load" +} + +func (CpuLoad) Scrape(ch chan<- prometheus.Metric) error { + //hostinfo := GetHost() + hostinfo, err := host.Info() + + cpuload, err := load.Avg() + if err != nil { + log.Printf("cpu load is not ,%v\n", err) + return err + } + + ch <- prometheus.MustNewConstMetric( + //这里的label是固定标签 我们可以通过 + NewDesc("cpu_load", "one", "cpu load", []string{"type"}, prometheus.Labels{"host": hostinfo.Hostname, "ip": GetIP()}), + prometheus.GaugeValue, + cpuload.Load1, + //动态标签的值 可以有多个动态标签 + "metrics", + ) + ch <- prometheus.MustNewConstMetric( + NewDesc("cpu_load", "five", "cpu load", nil, nil), + prometheus.GaugeValue, + cpuload.Load5, + ) + ch <- prometheus.MustNewConstMetric( + NewDesc("cpu_load", "fifteen", "cpu load", nil, nil), + prometheus.GaugeValue, + cpuload.Load15, + ) + return nil +} + +// 获取网络信息 +func GetIP() string { + + addrs, err := net.InterfaceAddrs() + if err != nil { + fmt.Println(err) + return "" + } + + for _, address := range addrs { + // 检查ip地址判断是否回环地址 + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + + if ipnet.IP.To4() != nil { + //fmt.Println(ipnet.Mask) + //fmt.Println(ipnet.IP) + return ipnet.IP.String() + } + } + } + return "" +} diff --git a/collector/exporter.go b/collector/exporter.go new file mode 100644 index 0000000..711912d --- /dev/null +++ b/collector/exporter.go @@ -0,0 +1,114 @@ +package collector + +//Exporter.go中主要是通过New 以及NewMetrics 初始化exporter结构体以及Metrics 结构体 exporter结构体需要满足Describe 和Collect 方法 +import ( + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "sync" +) + +var ( + name string = "test_exporter" + namespace string = "test" + exporter string = "exporter" +) + +func Name() string { + return name +} + +type Exporter struct { + metrics Metrics + scrapers []Scraper +} + +type Metrics struct { + //收集指标的总次数 + TotalScrapes prometheus.Counter + //收集指标中发生错误的次数 + ScrapeErrors *prometheus.CounterVec + //最后一次是否发生了错误 + Error prometheus.Gauge +} + +// fqname会把namespace subsystem name拼接起来 +// 传入动态以及静态标签 设置标签 +func NewDesc(subsystem, name, help string, movinglabel []string, label prometheus.Labels) *prometheus.Desc { + return prometheus.NewDesc( + prometheus.BuildFQName(namespace, subsystem, name), + help, movinglabel, label, + ) +} + +// 判断*exporter是否实现了collector这个接口的所有方法 +var _ prometheus.Collector = (*Exporter)(nil) + +func New(metrics Metrics, scrapers []Scraper) *Exporter { + return &Exporter{ + metrics: metrics, + scrapers: scrapers, + } +} + +func NewMetrics() Metrics { + subsystem := exporter + return Metrics{ + TotalScrapes: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "scrapes_total", + Help: "Total number of times was scraped for metrics.", + }), + ScrapeErrors: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "scrape_errors_total", + Help: "Total number of times an error occurred scraping .", + }, []string{"collector"}), + Error: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "last_scrape_error", + Help: "Whether the last scrape of metrics resulted in an error (1 for error, 0 for success).", + }), + } +} + +// 添加描述符 +func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { + ch <- e.metrics.TotalScrapes.Desc() + ch <- e.metrics.Error.Desc() + e.metrics.ScrapeErrors.Describe(ch) +} + +// 收集指标 +func (e *Exporter) Collect(ch chan<- prometheus.Metric) { + e.scrape(ch) + ch <- e.metrics.TotalScrapes + ch <- e.metrics.Error + e.metrics.ScrapeErrors.Collect(ch) +} + +// 通过例程并发的收集指标 需要加waitgroup +func (e *Exporter) scrape(ch chan<- prometheus.Metric) { + var ( + wg sync.WaitGroup + err error + ) + + defer wg.Wait() + for _, scraper := range e.scrapers { + wg.Add(1) + //使用匿名函数 并且并发的收集指标 + go func(scraper Scraper) { + defer wg.Done() + label := scraper.Name() + err = scraper.Scrape(ch) + if err != nil { + log.WithField("scraper", scraper.Name()).Error(err) + e.metrics.ScrapeErrors.WithLabelValues(label).Inc() + e.metrics.Error.Set(1) + } + }(scraper) + } +} diff --git a/collector/scraper.go b/collector/scraper.go new file mode 100644 index 0000000..dbfe017 --- /dev/null +++ b/collector/scraper.go @@ -0,0 +1,21 @@ +package collector + +import "github.com/prometheus/client_golang/prometheus" + +// Scraper 所有指标收集需要满足2个方法 name返回指标名字来用于用户开关指标的 可以理解为所有指标都必须满足这个约定 +// +//等同于我们一个接口切片,将结构体赋值给接口 但是结构体需要满足接口的方法,然后range 并调用接口的Scrape 来采集指标 +// +//go的接口继承类似于鸭子 接口定义了鸭子长什么样子也就是需要实现的方法,那么我们实现了这些方法的结构体就是一只鸭子 然后map中的k是一个接口 我们赋予的是一个结构体 结构体实现了接口的方法, 那么我们就可以吧结构体赋值给接口 + +type Scraper interface { + // Name of the Scraper. Should be unique. + Name() string + + // Help describes the role of the Scraper. + // Example: "Collect from SHOW ENGINE INNODB STATUS" + //Help() string + + // Scrape collects data from client and sends it over channel as prometheus metric. + Scrape(ch chan<- prometheus.Metric) error +} diff --git a/collector/switch.go b/collector/switch.go new file mode 100644 index 0000000..63a65c2 --- /dev/null +++ b/collector/switch.go @@ -0,0 +1,8 @@ +package collector + +// Scrapers 这里就做了开关控制我们在代码中实现就是通过修改这个bool值来做 每当我们添加一个指标 需要在map中添加一个指标的name 以及bool 添加指标后续会介绍 +var ( + Scrapers = map[Scraper]bool{ + CpuLoad{}: true, + } +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..358c621 --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module sreExporter + +go 1.19 + +require ( + github.com/prometheus/client_golang v1.12.2 + github.com/sirupsen/logrus v1.6.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + google.golang.org/protobuf v1.26.0 // indirect +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..4c70f4e --- /dev/null +++ b/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" + "net/http" + "sreExporter/collector" + "strings" +) + +var ( + metricsPath string = "https://github.com/strive-after/demo-exporter" + version string = "v1.0" + listenAddress string + help bool + disable string //命令行传入的需要关闭的指标 + disables []string //处理命令行传入的根据,分割为一个切片做处理 +) + +func init() { + flag.StringVar(&listenAddress, "addr", ":9101", "addr") + flag.BoolVar(&help, "h", false, "help") + flag.StringVar(&disable, "disable", "", "关闭的指标收集器") +} + +// main函数作为总入口 提供web url /metrices以及访问/的时候提供一些基本介绍 +func main() { + flag.Parse() + if help { + flag.Usage() + return + } + disables = strings.Split(disable, ",") + //手动开关 + //通过用户输入的我们做关闭 + for scraper, _ := range collector.Scrapers { + for _, v := range disables { + if v == scraper.Name() { + collector.Scrapers[scraper] = false + break + } + } + } + + //访问/的时候返回一些基础提示 + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(` + ` + collector.Name() + ` + +

` + collector.Name() + `

+

Metrics

+

Build

+
` + version + `
+ + `)) + }) + //根据开关来判断指标的是否需要收集 这里只有代码里面的判断 用户手动开关还未做 + enabledScrapers := []collector.Scraper{} + for scraper, enabled := range collector.Scrapers { + if enabled { + log.Info("Scraper enabled ", scraper.Name()) + enabledScrapers = append(enabledScrapers, scraper) + } + } + + //注册自身采集器 + exporter := collector.New(collector.NewMetrics(), enabledScrapers) + prometheus.MustRegister(exporter) + + http.Handle("/metrics", promhttp.Handler()) + //监听端口 + if err := http.ListenAndServe(listenAddress, nil); err != nil { + log.Printf("Error occur when start server %v", err) + } +} diff --git a/main.go.txt b/main.go.txt new file mode 100644 index 0000000..162fe72 --- /dev/null +++ b/main.go.txt @@ -0,0 +1,281 @@ +package main + +import ( + "crypto/tls" + "encoding/xml" + "flag" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + + "github.com/joho/godotenv" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +/* + + + + 101af57f-f26c-40d3-86a3-309e74b93512 + Send-Email-Notification + + + +*/ +type ChannelIdNameMap struct { + XMLName xml.Name `xml:"map"` + Entries []ChannelEntry `xml:"entry"` +} +type ChannelEntry struct { + XMLName xml.Name `xml:"entry"` + Values []string `xml:"string"` +} + +/* + + + + c5e6a736-0e88-46a7-bf32-5b4908c4d859 + 101af57f-f26c-40d3-86a3-309e74b93512 + 0 + 0 + 0 + 0 + 0 + + + +*/ +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" + +var ( + tr = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client = &http.Client{Transport: tr} + + listenAddress = flag.String("web.listen-address", ":9101", + "Address to listen on for telemetry") + metricsPath = flag.String("web.telemetry-path", "/metrics", + "Path under which to expose metrics") + + // 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 are currently queued (per channel).", + []string{"channel"}, nil, + ) + messagesSent = prometheus.NewDesc( + prometheus.BuildFQName(namespace, "", "messages_sent_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 errored (per channel).", + []string{"channel"}, nil, + ) +) + +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) { + // Create the map of channel id to names + channelIdNameMap := make(map[string]string) + + req, err := http.NewRequest("GET", e.mirthEndpoint+channelIdNameApi, nil) + if err != nil { + return nil, err + } + + // This one line implements the authentication required for the task. + req.SetBasicAuth(e.mirthUsername, e.mirthPassword) + // Make request and show output. + 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 + } + // fmt.Println(string(body)) + + // we initialize our array + var channelIdNameMapXML ChannelIdNameMap + // we unmarshal our byteArray which contains our + // xmlFiles content into 'users' which we defined above + err = xml.Unmarshal(body, &channelIdNameMapXML) + if err != nil { + 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 (e *Exporter) HitMirthRestApisAndUpdateMetrics(channelIdNameMap map[string]string, ch chan<- prometheus.Metric) { + // Load channel stats + req, err := http.NewRequest("GET", e.mirthEndpoint+channelStatsApi, nil) + if err != nil { + log.Fatal(err) + } + + // This one line implements the authentication required for the task. + req.SetBasicAuth(e.mirthUsername, e.mirthPassword) + // Make request and show output. + 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) + } + // fmt.Println(string(body)) + + // we initialize our array + var channelStatsList ChannelStatsList + // we unmarshal our byteArray which contains our + // xmlFiles content into 'users' which we defined above + 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, + ) + + channelQueued, _ := strconv.ParseFloat(channelStatsList.Channels[i].Queued, 64) + 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(` + Mirth Channel Exporter + +

Mirth Channel Exporter

+

Metrics

+ + `)) + }) + log.Fatal(http.ListenAndServe(*listenAddress, nil)) +} diff --git a/mirth_channel_exporter.service b/mirth_channel_exporter.service new file mode 100644 index 0000000..02359c6 --- /dev/null +++ b/mirth_channel_exporter.service @@ -0,0 +1,14 @@ +[Unit] +Description=mirth channel exporter +After=network.target +StartLimitIntervalSec=0 +[Service] +Type=simple +Restart=always +RestartSec=1 +WorkingDirectory=/mirth/mirthconnect +EnvironmentFile=/etc/sysconfig/mirth_channel_exporter +ExecStart=/mirth/mirthconnect/mirth_channel_exporter + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6272fc4 --- /dev/null +++ b/readme.md @@ -0,0 +1,92 @@ +# promethus exportor +https://github.com/teamzerolabs/mirth_channel_exporter +https://cloud.tencent.com/developer/article/1744817 +https://blog.csdn.net/weixin_45413603/article/details/107024467 +https://medium.com/teamzerolabs/15-steps-to-write-an-application-prometheus-exporter-in-go-9746b4520e26 + +# Prometheus的基本指标类型 +1、 Counter:只增不减的累加指标 + +Counter就是一个计数器,表示一种累积型指标,该指标只能单调递增或在重新启动时重置为零,例如,您可以使用计数器来表示所服务的请求数,已完成的任务或错误。 + +2、 Gauge:可增可减的测量指标 + +Gauge是最简单的度量类型,只有一个简单的返回值,可增可减,也可以set为指定的值。所以Gauge通常用于反映当前状态,比如当前温度或当前内存使用情况;当然也可以用于“可增加可减少”的计数指标。 + +3、Histogram:自带buckets区间用于统计分布的直方图 + +Histogram主要用于在设定的分布范围内(Buckets)记录大小或者次数。 + +例如http请求响应时间:0-100ms、100-200ms、200-300ms、>300ms 的分布情况,Histogram会自动创建3个指标,分别为: + +事件发送的总次数:比如当前一共发生了2次http请求 +所有事件产生值的大小的总和:比如发生的2次http请求总的响应时间为150ms +事件产生的值分布在bucket中的次数:比如响应时间0-100ms的请求1次,100-200ms的请求1次,其他的0次 +4、Summary:数据分布统计图 + +Summary和Histogram类似,都可以统计事件发生的次数或者大小,以及其分布情况。 + +Summary和Histogram都提供了对于事件的计数_count以及值的汇总_sum,因此使用_count,和_sum时间序列可以计算出相同的内容。 + +同时Summary和Histogram都可以计算和统计样本的分布情况,比如中位数,n分位数等等。不同在于Histogram可以通过histogram_quantile函数在服务器端计算分位数。 而Sumamry的分位数则是直接在客户端进行定义。因此对于分位数的计算。 Summary在通过PromQL进行查询时有更好的性能表现,而Histogram则会消耗更多的资源。相对的对于客户端而言Histogram消耗的资源更少。 + +# 视频讲解如何开发exporter +链接: https://pan.baidu.com/s/1I_eoqypKl6Pw7R3Ai5m0sQ 密码: cs75 + +go build +./mirth_channel_exporter [flags] + + +http://127.0.0.1:9101/metrics +[]: # Language: markdown +[]: # Path: readme.md + +# Mirth Channel Exporter + +Export [Mirth Connect](https://en.wikipedia.org/wiki/Mirth_Connect) channel +statistics to [Prometheus](https://prometheus.io). + +Metrics are retrieved using the Mirth Connect REST API. This has only been tested +with Mirth Connect 3.7.1, and it should work with version after 3.7.1. + +To run it: + + go build + ./mirth_channel_exporter [flags] + +## Exported Metrics +| Metric | Description | Labels | +| ------ | ------- | ------ | +| mirth_up | Was the last Mirth CLI query successful | | +| mirth_messages_received_total | How many messages have been received | channel | +| mirth_messages_filtered_total | How many messages have been filtered | channel | +| mirth_messages_queued | How many messages are currently queued | channel | +| mirth_messages_sent_total | How many messages have been sent | channel | +| mirth_messages_errored_total | How many messages have errored | channel | + +## Flags + ./mirth_channel_exporter --help + +| Flag | Description | Default | +| ---- | ----------- | ------- | +| log.level | Logging level | `info` | +| web.listen-address | Address to listen on for telemetry | `:9141` | +| web.telemetry-path | Path under which to expose metrics | `/metrics` | +| config.file-path | Optional environment file path | `None` | + +## Env Variables + +Use a .env file in the local folder, /etc/sysconfig/mirth_channel_exporter, or +use the --config.file-path command line flag to provide a path to your +environment file +``` +MIRTH_ENDPOINT=https://mirth-connect.yourcompane.com +MIRTH_USERNAME=admin +MIRTH_PASSWORD=admin +``` + +## Notice + +This exporter is inspired by the [consul_exporter](https://github.com/prometheus/consul_exporter) +and has some common code. Any new code here is Copyright © 2020 TeamZero, Inc. See the included +LICENSE file for terms and conditions. \ No newline at end of file