使用go自定義prometheus的exporter
介紹
在prometheus中如果要監(jiān)控服務器和應用的各種指標,需要用各種各樣的exporter服務,例如node_exportes、mysql_exportes、pgsql_exportes等。這些都是官方或者第三方已經提供好的。但是如果自己想要監(jiān)控一些其它exportes沒有的指標,則就需要自己去構建一個屬于自己的exportes,好在官方提供相關的庫,目前支持以下語言:
官方支持語言:
metric的類型
在開始之前需要了解下metric的類型劃分
Counter(計數(shù)器):只增不減的計數(shù)器,用于記錄事件發(fā)生的次數(shù),例如請求數(shù)量、錯誤數(shù)量等。Gauge(儀表盤):可增可減的指標,用于記錄當前的狀態(tài),例如 CPU 使用率、內存使用量等。Histogram(直方圖):用于記錄數(shù)據(jù)的分布情況,例如請求響應時間的分布情況。Summary(摘要):與 Histogram 類似,但是它會在客戶端計算出一些摘要信息,例如平均值、標準差等。
類型詳解
Guage
Gauge的特點: 1. 可以任意上升或下降,沒有固定的范圍限制。 2. 可以被設置為任何值,不像Counter只能遞增。 3. 可以被用來表示瞬時值或累計值。 4. 可以被用來表示單個實體的狀態(tài),例如單個服務器的CPU使用率。 5. 可以被用來表示多個實體的總體狀態(tài),例如整個集群的CPU使用率。 Gauge的使用: 1. Gauge的值可以通過set()方法進行設置。 2. Gauge的值可以通過inc()和dec()方法進行增加或減少。 3. Gauge的值可以通過add()方法進行增加或減少指定的值。 4. Gauge的值可以通過set_to_current_time()方法設置為當前時間戳。 5. Gauge的值可以通過observe()方法進行設置,這個方法可以用來記錄樣本值和時間戳。
Counter
Counter的特點: 1. Counter只能增加,不能減少或重置。 2. Counter的值是一個非負整數(shù)。 3. Counter的值可以隨時間增加,但不會減少。 4. Counter的值在重啟Prometheus時會重置為0。 5. Counter的值可以被多個Goroutine同時增加,不需要加鎖。 6. Counter的值可以被推送到Pushgateway中,用于監(jiān)控非Prometheus監(jiān)控的數(shù)據(jù)。 Counter的使用方法: 1. 在程序中定義一個Counter對象,并初始化為0。 2. 當需要記錄計數(shù)時,調用Counter的Inc()方法增加計數(shù)器的值。 3. 將Counter對象暴露給Prometheus,使其能夠收集數(shù)據(jù)。 4. 在Prometheus中定義一個相應的指標,并將Counter對象與該指標關聯(lián)。
示例代碼:
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// 定義一個Counter對象
var requestCounter = promauto.NewCounter(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "The total number of HTTP requests",
})
// 記錄請求計數(shù)
func handleRequest() {
requestCounter.Inc()
// 處理請求
}在上面的代碼中,我們定義了一個名為http_requests_total的Counter對象,用于記錄HTTP請求的總數(shù)。每當處理一個請求時,我們調用requestCounter.Inc()方法增加計數(shù)器的值。最后,我們將Counter對象暴露給Prometheus,并在Prometheus中定義了一個名為http_requests_total的指標,將Counter對象與該指標關聯(lián)。這樣,Prometheus就能夠收集和展示http_requests_total指標的數(shù)據(jù)了
Histogram
Histogram是一種Prometheus指標類型,用于度量數(shù)據(jù)的分布情況。它將數(shù)據(jù)分成一系列桶(bucket),每個桶代表一段范圍內的數(shù)據(jù)。每個桶都有一個計數(shù)器(counter),用于記錄該范圍內的數(shù)據(jù)數(shù)量。在Prometheus中,Histogram指標類型的名稱以“_bucket”結尾。 Histogram指標類型通常用于度量請求延遲、響應大小等連續(xù)型數(shù)據(jù)。例如,我們可以使用Histogram指標類型來度量Web應用程序的請求延遲。我們可以將請求延遲分成幾個桶,例如0.1秒、0.5秒、1秒、5秒、10秒、30秒等。每個桶都記錄了在該范圍內的請求延遲的數(shù)量。 Histogram指標類型還有兩個重要的計數(shù)器:sum和count。sum用于記錄所有數(shù)據(jù)的總和,count用于記錄數(shù)據(jù)的數(shù)量。通過這兩個計數(shù)器,我們可以計算出平均值和其他統(tǒng)計信息。 在Prometheus中,我們可以使用histogram_quantile函數(shù)來計算某個百分位數(shù)的值。例如,我們可以使用histogram_quantile(0.9, my_histogram)來計算my_histogram指標類型中90%的請求延遲的值。 總之,Histogram指標類型是一種非常有用的指標類型,可以幫助我們了解數(shù)據(jù)的分布情況,從而更好地監(jiān)控和優(yōu)化應用程序的性能。
Summary
Summary是Prometheus中的一種指標類型,用于記錄一組樣本的總和、計數(shù)和分位數(shù)。它適用于記錄耗時、請求大小等具有較大變化范圍的指標。 Summary指標類型包含以下幾個指標: 1. sum:樣本值的總和。 2. count:樣本值的計數(shù)。 3. quantile:分位數(shù)。 其中,sum和count是必須的,而quantile是可選的。 在使用Summary指標類型時,需要注意以下幾點: 1. 每個Summary指標類型都會記錄所有樣本的總和和計數(shù),因此它們的值會隨時間變化而變化。 2. 每個Summary指標類型都可以記錄多個分位數(shù),例如50%、90%、95%、99%等。 3. 每個Summary指標類型都可以設置一個時間窗口,用于計算分位數(shù)。 4. 每個Summary指標類型都可以設置一個最大樣本數(shù),用于限制內存使用。 5. 每個Summary指標類型都可以設置一個標簽集,用于區(qū)分不同的實例。 總之,Summary指標類型是一種非常有用的指標類型,可以幫助我們更好地了解系統(tǒng)的性能和健康狀況
示例
以下示例實現(xiàn)了通過傳入的端口號監(jiān)聽對應的進程,并輸出進程的一些信息,如pid、cmdline、exe、ppid、內存使用等信息(通過讀/proc/pid/目錄下的文件來實現(xiàn)),后面如果有其他需要可自行修改。因為寫的比較倉促,這里也不詳細介紹代碼中的含義,有興趣的可以留言,或者直接拿走代碼試試。
目錄結構是
|-main.go |-go.mod |-go.sum |-collector |-- exec.go |-- port.go
main.go
package main
import (
"fmt"
"net/http"
"time"
"exporter/collector"
"github.com/alecthomas/kingpin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// 定義命令行參數(shù)
var (
ticker = kingpin.Flag("ticker", "Interval for obtaining indicators.").Short('t').Default("5").Int()
mode = kingpin.Flag("mode", "Using netstat or lsof for specified port pid information.").Short('m').Default("netstat").String()
port = kingpin.Flag("port", "This service is to listen the port.").Short('p').Default("9527").String()
ports = kingpin.Arg("ports", "The process of listening on ports.").Required().Strings()
)
func main() {
kingpin.Version("1.1")
kingpin.Parse()
// 注冊自身采集器
prometheus.MustRegister(collector.NewPortCollector(*ports, *mode))
// fmt.Printf("Would ping: %s with timeout %s \n", *mode, *ports)
go func() {
for {
collector.NewPortCollector(*ports, *mode).Updata()
time.Sleep(time.Duration(*ticker) * time.Second)
}
}()
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Ready to listen on port:", *port)
if err := http.ListenAndServe("0.0.0.0:"+*port, nil); err != nil {
fmt.Printf("Error occur when start server %v", err)
}
}
exec.go
package collector
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
)
var (
order int
awkMap = make(map[int]string)
result = make(map[string]string)
// 定義要在status文件里篩選的關鍵字
targetList = []string{"Name", "State", "PPid", "Uid", "Gid", "VmHWM", "VmRSS"}
targetResult = make(map[string]map[string]string)
)
func stringGrep(s string, d string) (bool, error) {
for k, v := range d {
if v != rune(s[k]) {
return false, fmt.Errorf("string does not match")
}
}
order = 1
resolv, err := stringAWK(s[len(d):])
if len(resolv) == 0 {
return false, err
}
order = 0
return true, nil
}
func stringAWK(s string) (map[int]string, error) {
i := 0
for k, v := range s {
if v != rune(9) && v != rune(32) && v != rune(10) {
i = 1
awkMap[order] += string(v)
} else {
if i > 0 {
order++
i = 0
}
stringAWK(s[k+1:])
return awkMap, nil
}
}
return awkMap, fmt.Errorf("awk error")
}
func GetProcessInfo(p []string, m string) map[string]map[string]string {
for _, port := range p {
// 通過端口號獲取進程pid信息
// 通過組合命令行的方式執(zhí)行l(wèi)inux命令,篩選出pid
cmd := "sudo " + m + " -tnlp" + "|grep :" + port + "|awk '{print $NF}'|awk -F'/' '{print $1}'"
getPid := exec.Command("bash", "-c", cmd)
out, err := getPid.Output()
if err != nil {
fmt.Println("exec command failed", err)
return nil
}
dir := strings.ReplaceAll(string(out), "\n", "")
if len(dir) == 0 {
fmt.Println("'dir' string is empty")
return nil
// panic("'dir' string is empty")
}
// fmt.Println("test_dir", dir)
result["pid"] = dir
// 獲取命令行絕地路徑
cmdRoot := "sudo ls -l /proc/" + dir + "/exe |awk '{print $NF}'"
getCmdRoot := exec.Command("bash", "-c", cmdRoot)
out, err = getCmdRoot.Output()
if err != nil {
fmt.Println("exec getCmdRoot command failed", err)
}
// fmt.Println("test_cmdroot", strings.ReplaceAll(string(out), "\n", ""))
result["cmdroot"] = strings.ReplaceAll(string(out), "\n", "")
// 獲取/proc/pid/cmdline文件內信息
cmdline, err := os.Open("/proc/" + dir + "/cmdline")
if err != nil {
fmt.Println("open cmdline file error :", err)
panic(err)
}
cmdlineReader, err := bufio.NewReader(cmdline).ReadString('\n')
if err != nil && err != io.EOF {
fmt.Println(err)
}
result["cmdline"] = strings.ReplaceAll(cmdlineReader, "\x00", " ")
// 獲取/proc/pid/status文件內信息
status, err := os.Open("/proc/" + dir + "/status")
if err != nil {
fmt.Println("open status file error :", err)
}
// 執(zhí)行函數(shù)返回前關閉打開的文件
defer cmdline.Close()
defer status.Close()
statusReader := bufio.NewReader(status)
if err != nil {
fmt.Println(err)
}
for {
line, err := statusReader.ReadString('\n') //注意是字符
if err == io.EOF {
if len(line) != 0 {
fmt.Println(line)
}
break
}
if err != nil {
fmt.Println("read file failed, err:", err)
// return
}
for _, v := range targetList {
istrue, _ := stringGrep(line, v)
if istrue {
result[v] = awkMap[2]
// fmt.Printf("%v結果是:%v\n", v, awkMap[2])
awkMap = make(map[int]string)
}
}
}
// fmt.Println("數(shù)據(jù)的和:", result)
// fmt.Println("test_result", result)
targetResult[port] = result
// 給result map重新賦值,要不然使用的是同一個map指針,targetResult結果是一樣的
result = make(map[string]string)
}
// fmt.Println("test_total", targetResult)
return targetResult
}
port.go
package collector
import (
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/shirou/gopsutil/host"
)
var (
isexist float64 = 1
namespace = "own_process"
endetail = "datails"
endmems = "mems"
)
// 定義收集指標結構體
// 分為進程信息和內存信息
type PortCollector struct {
ProcessDetail portMetrics
ProcessMems portMetrics
mutex sync.Mutex // 使用于多個協(xié)程訪問共享資源的場景
// value prometheus.Gauge
}
type portMetrics []struct {
desc *prometheus.Desc
value map[string]string
}
func (p *PortCollector) Describe(ch chan<- *prometheus.Desc) {
for _, metric := range p.ProcessDetail {
ch <- metric.desc
}
for _, metric := range p.ProcessMems {
ch <- metric.desc
}
// ch <- p.ProcessMems
}
func (p *PortCollector) Collect(ch chan<- prometheus.Metric) {
p.mutex.Lock()
defer p.mutex.Unlock()
// ch <- prometheus.MustNewConstMetric(p.ProcessMems, prometheus.GaugeValue, 0)
for _, metric := range p.ProcessDetail {
ch <- prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue, isexist, metric.value["cmdroot"], metric.value["cmdline"], metric.value["Name"], metric.value["State"], metric.value["PPid"], metric.value["Uid"], metric.value["Gid"])
}
for _, metric := range p.ProcessMems {
ch <- prometheus.MustNewConstMetric(metric.desc, prometheus.GaugeValue, isexist, metric.value["Name"], metric.value["pid"], metric.value["VmHWM"], metric.value["VmRSS"])
}
}
func (p *PortCollector) Updata() {
// Do nothing here as the value is generated in the Collect() function
}
func newMetrics(p []string, s map[string]map[string]string, u string) *portMetrics {
host, _ := host.Info()
hostname := host.Hostname
var detailList, memsList portMetrics
for _, v := range p {
// fmt.Println(k, v)
detailList = append(detailList, struct {
desc *prometheus.Desc
value map[string]string
}{
desc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, v, endetail),
"Process-related information of port "+v,
[]string{"cmdroot", "cmdline", "process_name", "status", "ppid", "ownuser", "owngroup"}, // 設置動態(tài)labels,collect函數(shù)里傳來的就是這個變量的值
prometheus.Labels{"host_name": hostname}), // 設置靜態(tài)labels
value: s[v],
})
memsList = append(memsList, struct {
desc *prometheus.Desc
value map[string]string
}{
desc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, v, endmems),
"Process memory usage information of port "+v,
[]string{"process_name", "pid", "vmhwm", "vmrss"}, // 設置動態(tài)labels,collect函數(shù)里傳來的就是這個變量的值
prometheus.Labels{"host_name": hostname}), // 設置靜態(tài)labels
value: s[v],
})
}
if u == "detail" {
return &detailList
} else {
return &memsList
}
}
// NewPortCollector 創(chuàng)建port收集器,返回指標信息
func NewPortCollector(p []string, m string) *PortCollector {
final := GetProcessInfo(p, m)
// fmt.Printf("test_fanal:%#v", len(final))
if len(final) == 0 {
isexist = 0
} else {
isexist = 1
}
return &PortCollector{
ProcessDetail: *newMetrics(p, final, "detail"),
ProcessMems: *newMetrics(p, final, "mems"),
}
}
到此這篇關于用go自定義prometheus的exporter的文章就介紹到這了,更多相關go自定義prometheus的exporter內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
基于HLS創(chuàng)建Golang視頻流服務器的優(yōu)缺點
HLS 是 HTTP Live Streaming 的縮寫,是蘋果開發(fā)的一種基于 HTTP 的自適應比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務器,需要的朋友可以參考下2021-08-08
go?tool?pprof?參數(shù)?'-base'?和?'-diff_base&
這篇文章主要介紹了go?tool?pprof?參數(shù)?'-base'?和?'-diff_base'之間的區(qū)別,兩個參數(shù)都是用于計算當前?profile文件減去基準profile文件所獲得的差值,用這個差值生成一個新的profile文件,本文給大家介紹的非常詳細,需要的朋友可以參考下2023-05-05
Golang 使用http Client下載文件的實現(xiàn)方法
今天小編就為大家分享一篇Golang 使用http Client下載文件的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07
淺析Golang開發(fā)中goroutine的正確使用姿勢
很多初級的Gopher在學習了goroutine之后,在項目中其實使用率不高,所以這篇文章小編主要來帶大家深入了解一下goroutine的常見使用方法,希望對大家有所幫助2024-03-03
golang獲取當前時間、時間戳和時間字符串及它們之間的相互轉換方法
這篇文章主要介紹了golang獲取當前時間、時間戳和時間字符串及它們之間的相互轉換,本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-04-04

