使用go自定義prometheus的exporter
介紹
在prometheus
中如果要監(jiān)控服務(wù)器和應(yīng)用的各種指標(biāo),需要用各種各樣的exporter
服務(wù),例如node_exportes
、mysql_exportes
、pgsql_exportes
等。這些都是官方或者第三方已經(jīng)提供好的。但是如果自己想要監(jiān)控一些其它exportes
沒(méi)有的指標(biāo),則就需要自己去構(gòu)建一個(gè)屬于自己的exportes
,好在官方提供相關(guān)的庫(kù),目前支持以下語(yǔ)言:
官方支持語(yǔ)言:
metric的類型
在開始之前需要了解下metric
的類型劃分
Counter(計(jì)數(shù)器)
:只增不減的計(jì)數(shù)器,用于記錄事件發(fā)生的次數(shù),例如請(qǐng)求數(shù)量、錯(cuò)誤數(shù)量等。Gauge(儀表盤)
:可增可減的指標(biāo),用于記錄當(dāng)前的狀態(tài),例如 CPU 使用率、內(nèi)存使用量等。Histogram(直方圖)
:用于記錄數(shù)據(jù)的分布情況,例如請(qǐng)求響應(yīng)時(shí)間的分布情況。Summary(摘要)
:與 Histogram 類似,但是它會(huì)在客戶端計(jì)算出一些摘要信息,例如平均值、標(biāo)準(zhǔn)差等。
類型詳解
Guage
Gauge的特點(diǎn): 1. 可以任意上升或下降,沒(méi)有固定的范圍限制。 2. 可以被設(shè)置為任何值,不像Counter只能遞增。 3. 可以被用來(lái)表示瞬時(shí)值或累計(jì)值。 4. 可以被用來(lái)表示單個(gè)實(shí)體的狀態(tài),例如單個(gè)服務(wù)器的CPU使用率。 5. 可以被用來(lái)表示多個(gè)實(shí)體的總體狀態(tài),例如整個(gè)集群的CPU使用率。 Gauge的使用: 1. Gauge的值可以通過(guò)set()方法進(jìn)行設(shè)置。 2. Gauge的值可以通過(guò)inc()和dec()方法進(jìn)行增加或減少。 3. Gauge的值可以通過(guò)add()方法進(jìn)行增加或減少指定的值。 4. Gauge的值可以通過(guò)set_to_current_time()方法設(shè)置為當(dāng)前時(shí)間戳。 5. Gauge的值可以通過(guò)observe()方法進(jìn)行設(shè)置,這個(gè)方法可以用來(lái)記錄樣本值和時(shí)間戳。
Counter
Counter的特點(diǎn): 1. Counter只能增加,不能減少或重置。 2. Counter的值是一個(gè)非負(fù)整數(shù)。 3. Counter的值可以隨時(shí)間增加,但不會(huì)減少。 4. Counter的值在重啟Prometheus時(shí)會(huì)重置為0。 5. Counter的值可以被多個(gè)Goroutine同時(shí)增加,不需要加鎖。 6. Counter的值可以被推送到Pushgateway中,用于監(jiān)控非Prometheus監(jiān)控的數(shù)據(jù)。 Counter的使用方法: 1. 在程序中定義一個(gè)Counter對(duì)象,并初始化為0。 2. 當(dāng)需要記錄計(jì)數(shù)時(shí),調(diào)用Counter的Inc()方法增加計(jì)數(shù)器的值。 3. 將Counter對(duì)象暴露給Prometheus,使其能夠收集數(shù)據(jù)。 4. 在Prometheus中定義一個(gè)相應(yīng)的指標(biāo),并將Counter對(duì)象與該指標(biāo)關(guān)聯(lián)。
示例代碼:
import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) // 定義一個(gè)Counter對(duì)象 var requestCounter = promauto.NewCounter(prometheus.CounterOpts{ Name: "http_requests_total", Help: "The total number of HTTP requests", }) // 記錄請(qǐng)求計(jì)數(shù) func handleRequest() { requestCounter.Inc() // 處理請(qǐng)求 }
在上面的代碼中,我們定義了一個(gè)名為http_requests_total
的Counter
對(duì)象,用于記錄HTTP
請(qǐng)求的總數(shù)。每當(dāng)處理一個(gè)請(qǐng)求時(shí),我們調(diào)用requestCounter.Inc()
方法增加計(jì)數(shù)器的值。最后,我們將Counter
對(duì)象暴露給Prometheus
,并在Prometheus
中定義了一個(gè)名為http_requests_total
的指標(biāo),將Counter
對(duì)象與該指標(biāo)關(guān)聯(lián)。這樣,Prometheus
就能夠收集和展示http_requests_total
指標(biāo)的數(shù)據(jù)了
Histogram
Histogram是一種Prometheus指標(biāo)類型,用于度量數(shù)據(jù)的分布情況。它將數(shù)據(jù)分成一系列桶(bucket),每個(gè)桶代表一段范圍內(nèi)的數(shù)據(jù)。每個(gè)桶都有一個(gè)計(jì)數(shù)器(counter),用于記錄該范圍內(nèi)的數(shù)據(jù)數(shù)量。在Prometheus中,Histogram指標(biāo)類型的名稱以“_bucket”結(jié)尾。 Histogram指標(biāo)類型通常用于度量請(qǐng)求延遲、響應(yīng)大小等連續(xù)型數(shù)據(jù)。例如,我們可以使用Histogram指標(biāo)類型來(lái)度量Web應(yīng)用程序的請(qǐng)求延遲。我們可以將請(qǐng)求延遲分成幾個(gè)桶,例如0.1秒、0.5秒、1秒、5秒、10秒、30秒等。每個(gè)桶都記錄了在該范圍內(nèi)的請(qǐng)求延遲的數(shù)量。 Histogram指標(biāo)類型還有兩個(gè)重要的計(jì)數(shù)器:sum和count。sum用于記錄所有數(shù)據(jù)的總和,count用于記錄數(shù)據(jù)的數(shù)量。通過(guò)這兩個(gè)計(jì)數(shù)器,我們可以計(jì)算出平均值和其他統(tǒng)計(jì)信息。 在Prometheus中,我們可以使用histogram_quantile函數(shù)來(lái)計(jì)算某個(gè)百分位數(shù)的值。例如,我們可以使用histogram_quantile(0.9, my_histogram)來(lái)計(jì)算my_histogram指標(biāo)類型中90%的請(qǐng)求延遲的值。 總之,Histogram指標(biāo)類型是一種非常有用的指標(biāo)類型,可以幫助我們了解數(shù)據(jù)的分布情況,從而更好地監(jiān)控和優(yōu)化應(yīng)用程序的性能。
Summary
Summary是Prometheus中的一種指標(biāo)類型,用于記錄一組樣本的總和、計(jì)數(shù)和分位數(shù)。它適用于記錄耗時(shí)、請(qǐng)求大小等具有較大變化范圍的指標(biāo)。 Summary指標(biāo)類型包含以下幾個(gè)指標(biāo): 1. sum:樣本值的總和。 2. count:樣本值的計(jì)數(shù)。 3. quantile:分位數(shù)。 其中,sum和count是必須的,而quantile是可選的。 在使用Summary指標(biāo)類型時(shí),需要注意以下幾點(diǎn): 1. 每個(gè)Summary指標(biāo)類型都會(huì)記錄所有樣本的總和和計(jì)數(shù),因此它們的值會(huì)隨時(shí)間變化而變化。 2. 每個(gè)Summary指標(biāo)類型都可以記錄多個(gè)分位數(shù),例如50%、90%、95%、99%等。 3. 每個(gè)Summary指標(biāo)類型都可以設(shè)置一個(gè)時(shí)間窗口,用于計(jì)算分位數(shù)。 4. 每個(gè)Summary指標(biāo)類型都可以設(shè)置一個(gè)最大樣本數(shù),用于限制內(nèi)存使用。 5. 每個(gè)Summary指標(biāo)類型都可以設(shè)置一個(gè)標(biāo)簽集,用于區(qū)分不同的實(shí)例。 總之,Summary指標(biāo)類型是一種非常有用的指標(biāo)類型,可以幫助我們更好地了解系統(tǒng)的性能和健康狀況
示例
以下示例實(shí)現(xiàn)了通過(guò)傳入的端口號(hào)監(jiān)聽對(duì)應(yīng)的進(jìn)程,并輸出進(jìn)程的一些信息,如pid、cmdline、exe、ppid、內(nèi)存使用等信息(通過(guò)讀/proc/pid/目錄下的文件來(lái)實(shí)現(xiàn)),后面如果有其他需要可自行修改。因?yàn)閷懙谋容^倉(cāng)促,這里也不詳細(xì)介紹代碼中的含義,有興趣的可以留言,或者直接拿走代碼試試。
目錄結(jié)構(gòu)是
|-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() // 注冊(cè)自身采集器 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文件里篩選的關(guān)鍵字 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 { // 通過(guò)端口號(hào)獲取進(jìn)程pid信息 // 通過(guò)組合命令行的方式執(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文件內(nèi)信息 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文件內(nèi)信息 status, err := os.Open("/proc/" + dir + "/status") if err != nil { fmt.Println("open status file error :", err) } // 執(zhí)行函數(shù)返回前關(guān)閉打開的文件 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結(jié)果是:%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重新賦值,要不然使用的是同一個(gè)map指針,targetResult結(jié)果是一樣的 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" ) // 定義收集指標(biāo)結(jié)構(gòu)體 // 分為進(jìn)程信息和內(nèi)存信息 type PortCollector struct { ProcessDetail portMetrics ProcessMems portMetrics mutex sync.Mutex // 使用于多個(gè)協(xié)程訪問(wèn)共享資源的場(chǎng)景 // 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"}, // 設(shè)置動(dòng)態(tài)labels,collect函數(shù)里傳來(lái)的就是這個(gè)變量的值 prometheus.Labels{"host_name": hostname}), // 設(shè)置靜態(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"}, // 設(shè)置動(dòng)態(tài)labels,collect函數(shù)里傳來(lái)的就是這個(gè)變量的值 prometheus.Labels{"host_name": hostname}), // 設(shè)置靜態(tài)labels value: s[v], }) } if u == "detail" { return &detailList } else { return &memsList } } // NewPortCollector 創(chuàng)建port收集器,返回指標(biāo)信息 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"), } }
到此這篇關(guān)于用go自定義prometheus的exporter的文章就介紹到這了,更多相關(guān)go自定義prometheus的exporter內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang使用正則表達(dá)式解析網(wǎng)頁(yè)
這篇文章主要介紹了golang使用正則表達(dá)式解析網(wǎng)頁(yè),需要的朋友可以參考下2015-03-03golang elasticsearch Client的使用詳解
這篇文章主要介紹了golang elasticsearch Client的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05go將request?body綁定到不同的結(jié)構(gòu)體中教程
這篇文章主要為大家介紹了go將request?body綁定到不同的結(jié)構(gòu)體中教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10go語(yǔ)言版的ip2long函數(shù)實(shí)例
這篇文章主要介紹了go語(yǔ)言版的ip2long函數(shù),實(shí)例分析了Go語(yǔ)言實(shí)現(xiàn)的ip2long函數(shù)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02Golang中如何使用lua進(jìn)行擴(kuò)展詳解
這篇文章主要給大家介紹了關(guān)于Golang中如何使用lua進(jìn)行擴(kuò)展的相關(guān)資料,這是最近在工作中遇到的一個(gè)問(wèn)題,覺(jué)著有必要分享出來(lái)給大家學(xué)習(xí),文中給出了詳細(xì)的示例,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-10-10基于golang uint8、int8與byte的區(qū)別說(shuō)明
這篇文章主要介紹了基于golang uint8、int8與byte的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03