很多時候,我們在使用Prometheus時,官方提供的采集組件不能滿足監控需求,我們就需要自行編寫Exporter。
本文的示例采用go語言和Gauge (測量指標)類型實現。自定義Exporter去取MongoDB里動態增長的數據。
Metric接口
Prometheus client庫提供了四種度量標准類型。
雖然只有基本度量標准類型實現Metric接口,但是度量標准及其向量版本都實現了Collector接口。Collector管理許多度量標准的收集,但為方便起見,度量標准也可以"自行收集"。Prometheus數據模型的一個非常重要的部分是沿稱為label的維度對樣本進行划分,從而產生度量向量。基本類型是GaugeVec,CounterVec,SummaryVec和HistogramVec。注意,Gauge,Counter,Summary和Histogram本身是接口,而GaugeVec,CounterVec,SummaryVec和HistogramVec則不是接口。
-
Counter (累積)
Counter一般表示一個單調遞增的計數器。
-
Gauge (測量)
Gauge一般用於表示可能動態變化的單個值,可能增大也可能減小。
-
Histogram (直方圖)
-
Summary (概略圖)
注冊指標並啟動HTTP服務
需要先引入這個Prometheus client庫。通常metric endpoint使用http來暴露metric,通過http暴露metric的工具為promhttp子包,引入promhttp和net/http。
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang//prometheus/promhttp"
Registry/Register/Gatherers及http服務
func main() {
daystr := time.Now().Format("20060102")
logFile, err := os.Create("./exporter/log/" + daystr + ".txt")
defer logFile.Close()
if err != nil {
fmt.Printf("%v\n", err)
return
}
logger := log.New(logFile, "Prefix_", log.Ldate|log.Ltime|log.Lshortfile)
//Registry和Register部分
reg := prometheus.NewPedanticRegistry()
reg.MustRegister(metrics.initCollector()) //metrics.initCollector()是collector初始化模塊
//以下是官方文檔定義
// Register registers a new Collector to be included in metrics
// collection. It returns an error if the descriptors provided by the
// Collector are invalid or if they — in combination with descriptors of
// already registered Collectors — do not fulfill the consistency and
// uniqueness criteria described in the documentation of metric.Desc.
//
// If the provided Collector is equal to a Collector already registered
// (which includes the case of re-registering the same Collector), the
// returned error is an instance of AlreadyRegisteredError, which
// contains the previously registered Collector.
//
// A Collector whose Describe method does not yield any Desc is treated
// as unchecked. Registration will always succeed. No check for
// re-registering (see previous paragraph) is performed. Thus, the
// caller is responsible for not double-registering the same unchecked
// Collector, and for providing a Collector that will not cause
// inconsistent metrics on collection. (This would lead to scrape
// errors.)
// MustRegister works like Register but registers any number of
// Collectors and panics upon the first registration that causes an
// error.
//Gatherers部分
gatherers := prometheus.Gatherers{
reg,
}
//以下是官方文檔定義
// Gatherers is a slice of Gatherer instances that
// implements the Gatherer interface itself.
// Its Gather method calls Gather on all Gatherers
// in the slice in order and returns the merged results.
// Errors returned from the Gather calls are
// all returned in a flattened MultiError.
// Duplicate and inconsistent Metrics are
// skipped (first occurrence in slice order wins)
// and reported in the returned error.
//注冊http服務
h := promhttp.HandlerFor(gatherers,
promhttp.HandlerOpts{
ErrorHandling: promhttp.ContinueOnError,
})
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
})
log.Println("Start server at :8710")
logger.Printf("Start server at :8710")
if err := http.ListenAndServe(":8710", nil); err != nil {
log.Printf("Error occur when start server %v", err)
logger.Printf("Error occur when start server %v", err)
os.Exit(1)
}
}
Registry是為了根據Prometheus數據模型來確保所收集指標的一致性。如果注冊的Collector與注冊的Metrics不兼容或不一致,則返回錯誤。理想情況下,在注冊時而不是在收集時檢測到不一致。對注冊的Collector的檢測通常會在程序啟動時被檢測到,而對注冊的Metrics的檢測只會在抓取時發生,這就是Collector和Metrics必須向Registry描述自己的主要原因。
Registry實現了Gatherer接口。 然后Gather方法的調用者可以某種方式公開收集的指標。通常度量是通過/metrics端點上的HTTP提供的。在上面的示例中就是這種情況。通過HTTP公開指標的工具位於promhttp子軟件包中。
NewPedanticRegistry可以避免由DefaultRegisterer施加的全局狀態,可以同時使用多個注冊表,以不同的方式公開不同的指標, 也可以將單獨的注冊表用於測試目的。
實現Collector接口
type Collector interface {
// 用於傳遞所有可能的指標的定義描述符
// 可以在程序運行期間添加新的描述,收集新的指標信息
// 重復的描述符將被忽略。兩個不同的Collector不要設置相同的描述符
Describe(chan<- *Desc)
// Prometheus的注冊器調用Collect執行實際的抓取參數的工作,
// 並將收集的數據傳遞到Channel中返回
// 收集的指標信息來自於Describe中傳遞,可以並發的執行抓取工作,但是必須要保證線程的安全。
Collect(chan<- Metric)
}
在另外的模塊中編寫Collector以及其初始化的代碼。
先定義一個結構體,指標使用的是 prometheus的Desc類型。
type ProjectMetrics struct {
MetricsDescs []*prometheus.Desc
}
定義MetricsName和 MetricsHelp。
var MetricsNameXXX = "XXX"
var MetricsHelpXXX = "(XXX)"
對自定義類型ProjectMetrics定義Describe方法和Collect方法來實現實現Collector接口。
func (c *ProjectMetrics) Describe(ch chan<- *prometheus.Desc) {
len1 := len(c.MetricsDescs)
for i := 0; i < len1; i++ {
ch <- c.MetricsDescs[i]
}
}
func (c *ProjectMetrics) Collect(ch chan<- prometheus.Metric) {
start := time.Now()
nowUTC := start.UTC()
resp := mongodb.QueryAllData(nowUTC.Unix())
for _, v := range resp.Data1Tier {
item1 := v.Item1
fmt.Println("......................", isp)
ts := time.Unix(v.Clientutc, 0)
for _, v2 := range v.Data2Tier {
item2 := v2.Item2
item3 := v2.Item3
item4 := v2.Item4
tmp := prometheus.NewDesc(
MetricsNameXXX,
MetricsHelpXXX,
[]string{"Name"},
prometheus.Labels{"item1": item1, "item3": item3, "item4": item4},
)
ch <- prometheus.NewMetricWithTimestamp(
ts,
prometheus.MustNewConstMetric(
tmp,
prometheus.GaugeValue,
item2,
MetricsNameXXX,
),
)
}
}
eT := time.Since(start)
fmt.Printf("Project Metrics, Elapsed Time: %s, Date(UTC): %s\n", eT, start.UTC().Format("2006/01/02T15:04:05"))
}
初始化ProjectMetrics,
func AddMetricsItem2() *ProjectMetrics {
var tmpMetricsDescs []*prometheus.Desc
resp := mongodb.QueryAllData(time.Now().UTC().Unix())
for _, v := range resp.Data1Tier {
item1 := v.Item1
for _, v2 := range v.Data2Tier {
item3 := v2.Item3
item4 := v2.Item4
tmp := prometheus.NewDesc(
MetricsNameLatency,
MetricsHelpLatency,
[]string{"Name"},
prometheus.Labels{"item1": item1, "item3": item3, "item4": item4},
)
tmpMetricsDescs = append(tmpMetricsDescs, tmp)
}
} //aws
api := &ProjectMetrics{MetricsDescs: tmpMetricsDescs}
return api
}
MongoDB中的數據,
type SensorData struct {
Aaaa string `json:"aaaa"`
Item2 float64 `json:"item2"`
Item4 string `json:"item4"`
Item3 string `json:"item3"`
Bbbb float64 `json:"bbbb"`
Cccc string `json:"cccc"`
Dddd []CmdResult `json:"dddd"`
Eeee string `json:"eeee"`
}
type Sensor struct {
Item1 string `json:"item1"`
Clientutc int64 `json:"clientutc"`
Data2Tier []SensorData
}
type AllData struct {
Data1Tier []Sensor
}
mongodb.QueryAllData()是另外從mongodb中拉取數據的模塊,返回AllData類型數據。mongodb模塊每三分鍾去數據庫里拉一次數據。