Prometheus之Exporter開發

men發表於2020-09-19

Prometheus開發Exporter簡介

Exporter 本身是一個http 服務,其指標結果只要符合 Prometheus 規範就可以被 Prometheus 使用。

Prometheus中metric的型別

Prometheus的Client Library提供度量的四種基本型別包括

// Counter 計數器
// Gauge  儀表盤
// Histogram 直方圖
// Summary 概要

// Prometheus中metric的格式
// 格式:<metric name>{<label name>=<label value>, ...}
// 例如:api_http_requests_total{method="POST", handler="/messages"}

// metric name:  唯一標識,命名遵循[a-zA-Z_:][a-zA-Z0-9_:]*.
Counter

Counter型別好比計數器,用於統計類似於: cpu時間,api訪問總次數,異常發生次數等等場景,這些指標的特點就是增加不減少.

因此當我們需要統計cpu的使用率時,我們需要使用rate{}函式計算該counter在過去一段時間每個時間序列上的每秒的平均增長率.

一個Counter標識一個累計度量,只增不減,重啟後恢復為0,適用於訪問次數統計,異常次數統計等場景.

Gauge

Gauge型別,英文直譯的話叫"計量器",但是和Counter的翻譯太類似了,因此我個人更喜歡使用"儀表盤"這個稱呼,儀表盤的特點就是可以增加或者減少的,因此Gauge適合用於如: 當前記憶體使用率,當前cpu使用率,當前溫度,當前速度等等一系列的監控指標

Gauge表示可變化的度量值,適用於CPU,記憶體使用率等

Histogram

Histogram柱狀圖這個比較直接,更多的是用於統計一些資料分佈的情況,用於計算在一定範圍的分佈情況,同時還提供了度量指標值的總和.

Histogram對指標的範圍性(區間)統計,比如記憶體在0%-30%, 30%-70%之間的取樣次數

Histogram包含三個指標

<basename>:  度量值名稱
<basename>_count:  樣本反正總次數
<basename>_sum:  樣本發生次數中值的總和
<basename>_bucket{le="+Inf"}: 每個區間的樣本數
Summary

Summary摘要和Histogram柱狀圖比較類似,主要用於計算在一定時間視窗範圍內度量指標物件的總數以及所有對量指標的總和.

和histogram類似,提供次數和總和,同時提供每個滑動視窗中的分位數.

Histogram和Summary的對比
序號 histogram Summary
配置 區間配置 分位數和滑動視窗
客戶端效能 只需增加counters代價小 需要流式計算代價高
服務端效能 計算分位數消耗大,可能會耗時 無需計算,代價小
時序數量 _sum、_count、bucket _sum、_count、quantile
分位數誤差 bucket的大小有關 φ的配置有關
φ和滑動視窗 Prometheus 表示式設定 客戶端設定
聚合 根據表示式聚合 一般不可聚合

Prometheus中的Jobs和INSTANCES

Instances
// 僅採集的API endpoint
Jobs
// 相同目的的Instances
// 例如: 四個節點上的api-server
job: api-server
instance 1: 1.2.3.4:5670
instance 2: 1.2.3.4:5671
instance 3: 5.6.7.8:5670
instance 4: 5.6.7.8:5671

Prometheus拉去目標資料時, 會自動給目標的時序上增加一些標籤,用於唯一標識,如果時序中本省已經包含,那麼取決於honor_labels

// Job: 會增加Job名稱
// instance: 增加host: port

開發一個簡單Exporter

Prometheus為開發提供了客戶端工具,用於為自己的中介軟體開發Exporter,對接Prometheus, 目前支援go,java,python,ruby

監聽HTTP請求返回一行字串

lexporter_request_count{user="admin"} 1000

package main

import (
	"fmt"
	"net/http"
)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "lexporter_request_count{user=\"admin\"} 1000" )
}

func main () {
	http.HandleFunc("/metrics", HelloHandler)
	http.ListenAndServe(":8000", nil)
}

配置Prometheus,將exporter到Prometheus中

計數器

Counter 型別代表一種樣本資料單調遞增的指標,即只增不減,除非監控系統發生了重置。例如,你可以使用 counter 型別的指標來表示服務的請求數、已完成的任務數、錯誤發生的次數等。

我們對程式進行改造,累計計算count的數量

example1

package main

import (
	"fmt"
	"net/http"
)

type Counter struct {
	count int64
}

func (c *Counter) Add(count int64) int64  {
	c.count += count
	return c.count
}


var counter = new(Counter)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "lexporter_request_count{user=\"admin\"} %d",counter.Add(10) )
}

func main () {
	http.HandleFunc("/metrics", HelloHandler)
	http.ListenAndServe(":8000", nil)
}

example2

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
)

func main() {

	// counter
	requestTotal := prometheus.NewCounter(prometheus.CounterOpts{
		Name: "request_total",
		Help: "request total",
	})

	codeStatus := prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "status_code_total",
		Help: "status_code total",
	},[]string{"code"})

	requestTotal.Add(10)
	codeStatus.WithLabelValues("200").Add(10)
	codeStatus.WithLabelValues("500").Add(20)
	codeStatus.WithLabelValues("404").Add(30)

	prometheus.MustRegister(requestTotal)
	prometheus.MustRegister(codeStatus)

	// 暴露
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8888", nil)
}

Gauge(固定label和非固定label)
package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
)

func main()  {
	// counter
	// guage
	// historgram
	// summary

	// metrics name(label=label_value) metrics_valu
	// 有lable
	// label/label_value  固定

	// 無label(固定lable)
	// lable/label_value  變化的
	cpu := prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "cpu",
		Help: "cpu total",

		// 沒有lable和固定lable一樣的
		ConstLabels: prometheus.Labels{"a":"xxx"},
	})


	// 非固定label
	disk := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "disk",
		Help: "disk total",

	},[]string{"mount"})

	cpu.Set(2222)
	disk.WithLabelValues("/mnt/sda1:").Set(100)
	disk.WithLabelValues("/mnt/sda2:").Set(200)
	disk.WithLabelValues("/mnt/sda3:").Set(200)
	disk.WithLabelValues("/mnt/sda4:").Set(200)

	// 註冊指標資訊
	prometheus.MustRegister(cpu)
	prometheus.MustRegister(disk)

	// 暴露
	http.Handle("/metrics",promhttp.Handler())
	http.ListenAndServe(":8888",nil)

}
historgram

example1

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
)

func main() {

	// historgram
	request_time := prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Name: "request_time",
		Help: "request time",
	},[]string{"url"})

	

	prometheus.MustRegister(request_time)
	request_time.WithLabelValues("/aaa").Observe(6)
	

	// 暴露
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8888", nil)
}

summary
package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"net/http"
)

func main() {

	// summary
	requestsummary := prometheus.NewSummaryVec(prometheus.SummaryOpts{
		Name: "request_time_summary",
		Help: "request time summary",
		Objectives: map[float64]float64{0.5: 0.05,0.9:0.09},
	},[]string{"url"})



	prometheus.MustRegister(requestsummary)


	requestsummary.WithLabelValues("/aaa").Observe(6)
	requestsummary.WithLabelValues("/aaa").Observe(2)

	// 暴露
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(":8888", nil)
}

值的修改(事件觸發或者時間觸發)
package main

import (
	"fmt"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"math/rand"
	"net/http"
	"strconv"
	"time"
)

func main()  {
	// 無label(固定lable)
	// lable/label_value  變化的
	cpu := prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "cpu",
		Help: "cpu total",

		// 沒有lable和固定lable一樣的
		ConstLabels: prometheus.Labels{"a":"xxx"},
	})


	// 非固定label
	disk := prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "disk",
		Help: "disk total",

	},[]string{"mount"})

	cpu.Set(2222)
	disk.WithLabelValues("/mnt/sda1:").Set(100)
	disk.WithLabelValues("/mnt/sda2:").Set(200)
	disk.WithLabelValues("/mnt/sda3:").Set(200)
	disk.WithLabelValues("/mnt/sda4:").Set(200)


	codeStatus := prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "status_code_total",
		Help: "status_code total",
	},[]string{"code"})


	codeStatus.WithLabelValues("200").Add(10)
	codeStatus.WithLabelValues("500").Add(20)
	codeStatus.WithLabelValues("404").Add(30)
	// counter
	requestTotal := prometheus.NewCounter(prometheus.CounterOpts{
		Name: "request_total",
		Help: "request total",
	})

	requestTotal.Add(10)

	// 值的修改
	// 修改的時間 => 觸發
	// 時間觸發
	// 磁碟使用, cpu使用,記憶體使用
	go func() {
		for range time.Tick(time.Second) {
			disk.WithLabelValues("/mnt/sda1").Set(float64(rand.Int()))
		}
	}()

	// 事件觸發,業務請求
	http.HandleFunc("/",func(w http.ResponseWriter,r *http.Request){
		requestTotal.Inc()
		codeStatus.WithLabelValues(strconv.Itoa(rand.Intn(5) * 100)).Add(1)
		fmt.Fprintf(w,"hi")
	})
	


	// 註冊指標資訊
	prometheus.MustRegister(cpu)
	prometheus.MustRegister(disk)
	prometheus.MustRegister(requestTotal)

	// 暴露
	http.Handle("/metrics",promhttp.Handler())
	http.ListenAndServe(":8888",nil)
}

相關文章