中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用go自定義prometheus的exporter

發布時間:2023-03-28 15:22:59 來源:億速云 閱讀:135 作者:iii 欄目:開發技術

這篇文章主要介紹“如何使用go自定義prometheus的exporter”,在日常操作中,相信很多人在如何使用go自定義prometheus的exporter問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何使用go自定義prometheus的exporter”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

介紹

prometheus中如果要監控服務器和應用的各種指標,需要用各種各樣的exporter服務,例如node_exportesmysql_exportespgsql_exportes等。這些都是官方或者第三方已經提供好的。但是如果自己想要監控一些其它exportes沒有的指標,則就需要自己去構建一個屬于自己的exportes,好在官方提供相關的庫,目前支持以下語言:

官方支持語言:

  • Go

  • Java or Scala

  • Python

  • Ruby

  • Rust

metric的類型

在開始之前需要了解下metric的類型劃分

  • Counter(計數器):只增不減的計數器,用于記錄事件發生的次數,例如請求數量、錯誤數量等。

  • Gauge(儀表盤):可增可減的指標,用于記錄當前的狀態,例如 CPU 使用率、內存使用量等。

  • Histogram(直方圖):用于記錄數據的分布情況,例如請求響應時間的分布情況。

  • Summary(摘要):與 Histogram 類似,但是它會在客戶端計算出一些摘要信息,例如平均值、標準差等。

類型詳解

Guage

Gauge的特點:

1. 可以任意上升或下降,沒有固定的范圍限制。
2. 可以被設置為任何值,不像Counter只能遞增。
3. 可以被用來表示瞬時值或累計值。
4. 可以被用來表示單個實體的狀態,例如單個服務器的CPU使用率。
5. 可以被用來表示多個實體的總體狀態,例如整個集群的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的值是一個非負整數。
3. Counter的值可以隨時間增加,但不會減少。
4. Counter的值在重啟Prometheus時會重置為0。
5. Counter的值可以被多個Goroutine同時增加,不需要加鎖。
6. Counter的值可以被推送到Pushgateway中,用于監控非Prometheus監控的數據。

Counter的使用方法:

1. 在程序中定義一個Counter對象,并初始化為0。
2. 當需要記錄計數時,調用Counter的Inc()方法增加計數器的值。
3. 將Counter對象暴露給Prometheus,使其能夠收集數據。
4. 在Prometheus中定義一個相應的指標,并將Counter對象與該指標關聯。

示例代碼:

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",
})

// 記錄請求計數
func handleRequest() {
    requestCounter.Inc()
    // 處理請求
}

在上面的代碼中,我們定義了一個名為http_requests_totalCounter對象,用于記錄HTTP請求的總數。每當處理一個請求時,我們調用requestCounter.Inc()方法增加計數器的值。最后,我們將Counter對象暴露給Prometheus,并在Prometheus中定義了一個名為http_requests_total的指標,將Counter對象與該指標關聯。這樣,Prometheus就能夠收集和展示http_requests_total指標的數據了

Histogram

Histogram是一種Prometheus指標類型,用于度量數據的分布情況。它將數據分成一系列桶(bucket),每個桶代表一段范圍內的數據。每個桶都有一個計數器(counter),用于記錄該范圍內的數據數量。在Prometheus中,Histogram指標類型的名稱以“_bucket”結尾。

Histogram指標類型通常用于度量請求延遲、響應大小等連續型數據。例如,我們可以使用Histogram指標類型來度量Web應用程序的請求延遲。我們可以將請求延遲分成幾個桶,例如0.1秒、0.5秒、1秒、5秒、10秒、30秒等。每個桶都記錄了在該范圍內的請求延遲的數量。

Histogram指標類型還有兩個重要的計數器:sum和count。sum用于記錄所有數據的總和,count用于記錄數據的數量。通過這兩個計數器,我們可以計算出平均值和其他統計信息。

在Prometheus中,我們可以使用histogram_quantile函數來計算某個百分位數的值。例如,我們可以使用histogram_quantile(0.9, my_histogram)來計算my_histogram指標類型中90%的請求延遲的值。

總之,Histogram指標類型是一種非常有用的指標類型,可以幫助我們了解數據的分布情況,從而更好地監控和優化應用程序的性能。

Summary

Summary是Prometheus中的一種指標類型,用于記錄一組樣本的總和、計數和分位數。它適用于記錄耗時、請求大小等具有較大變化范圍的指標。

Summary指標類型包含以下幾個指標:

1. sum:樣本值的總和。
2. count:樣本值的計數。
3. quantile:分位數。

其中,sum和count是必須的,而quantile是可選的。
在使用Summary指標類型時,需要注意以下幾點:

1. 每個Summary指標類型都會記錄所有樣本的總和和計數,因此它們的值會隨時間變化而變化。
2. 每個Summary指標類型都可以記錄多個分位數,例如50%、90%、95%、99%等。
3. 每個Summary指標類型都可以設置一個時間窗口,用于計算分位數。
4. 每個Summary指標類型都可以設置一個最大樣本數,用于限制內存使用。
5. 每個Summary指標類型都可以設置一個標簽集,用于區分不同的實例。
總之,Summary指標類型是一種非常有用的指標類型,可以幫助我們更好地了解系統的性能和健康狀況

示例

以下示例實現了通過傳入的端口號監聽對應的進程,并輸出進程的一些信息,如pid、cmdline、exe、ppid、內存使用等信息(通過讀/proc/pid/目錄下的文件來實現),后面如果有其他需要可自行修改。因為寫的比較倉促,這里也不詳細介紹代碼中的含義,有興趣的可以留言,或者直接拿走代碼試試。

目錄結構是

|-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"
)

// 定義命令行參數
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信息
		// 通過組合命令行的方式執行linux命令,篩選出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)
		}

		// 執行函數返回前關閉打開的文件
		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("數據的和:", 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 // 使用于多個協程訪問共享資源的場景
	// 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"}, // 設置動態labels,collect函數里傳來的就是這個變量的值
				prometheus.Labels{"host_name": hostname}),                                               // 設置靜態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"}, // 設置動態labels,collect函數里傳來的就是這個變量的值
				prometheus.Labels{"host_name": hostname}),         // 設置靜態labels
			value: s[v],
		})
	}
	if u == "detail" {
		return &detailList
	} else {
		return &memsList
	}
}

// NewPortCollector 創建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”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

渑池县| 六安市| 乌兰察布市| 积石山| 山西省| 安塞县| 钟祥市| 正定县| 阳山县| 呼玛县| 当阳市| 陈巴尔虎旗| 奇台县| 东光县| 苏州市| 莒南县| 怀化市| 商南县| 清水县| 定日县| 潼关县| 玉树县| 灵台县| 黄平县| 大渡口区| 白沙| 临夏县| 新化县| 嵊泗县| 东港市| 克拉玛依市| 乌鲁木齐市| 石嘴山市| 安泽县| 饶阳县| 沐川县| 孝昌县| 靖州| 广饶县| 武山县| 绍兴县|