Go exporter
Contents
Exporter 开发
Prometheus Exporter是一个用来收集和暴露指标数据的工具,通过与Prometheus监控系统一起使用。它的结构包括两个组件:Collector和Exporter:
- Collector:用于从目标应用程序或系统收集指标并将其转化为Prometheus可识别的格式。收集器可以使用Prometheus客户端库来生成指标,并公开HTTP/metrics以便Prometheus Server进行定期调用和拉取指标。
- Exporter:它会从Collector获取指标数据,并将其转成为Prometheus可读格式。
metrics的组成
- 指标名称 (Metric Name): 描述指标的名称。
- 标签 (Labels): 可选,一组键值对,用于标识同一指标的不同实例。
- 时间戳 (Timestamp): 可选的 Unix 时间戳,表示指标记录的时间点。
- 值 (Value): 指标的数值。
- Prometheus定义了四种主要的指标类型:
- Counter(计数器):用于表示单调递增的指标,例如请求数等。Counter在每次观测时会增加它所代表的值(通常是一个整数),但不会减少或者重置。
- Gauge(仪表盘):用于表示可变化的度量值,例如CPU利用率、内存用量等。Gauge可以增加、减少或重置,代表着当前的状态。
- Histogram(直方图):用于表示数据样本的分布情况,例如请求延迟等。Histogram将数据按照桶(bucket)进行划分,并对每个桶内的样本计算出一些统计信息,如样本数量、总和、平均值等。
- Summary(摘要):类似于Histogram,也用于表示数据样本的分布情况,但同时展示更多的统计信息,如样本数量、总和、平均值、上分位数、下分位数等。
开发示例
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var counterMetric prometheus.Counter
func helloHandler(w http.ResponseWriter, r *http.Request) {
counterMetric.Add(1)
w.Write([]byte("Hello from Prometheus Exporter!"))
}
func main() {
// 创建一个Counter指标
counterMetric = prometheus.NewCounter(prometheus.CounterOpts{
Name: "ep1_counter", // 指标名称
Help: "ep1 hello counter metric.", // 指标帮助信息
})
// 给指标赋值。模拟请求,每秒递增计数器
go func() {
for {
counter.Inc(5)
time.Sleep(time.Second)
}
}()
// 注册指标
prometheus.MustRegister(counterMetric)
// 创建一个HTTP处理器来暴露指标
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/hello", helloHandler)
// 启动Web服务器
http.ListenAndServe(":8080", nil)
}
curl 127.0.0.1:8080/hello
##
curl 127.0.0.1:8080/metrics
# HELP ep1_counter ep1 hello counter metric.
# TYPE ep1_counter counter
ep1_counter 12
# HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
# HELP ep1_counter ep1 hello counter metric.
# TYPE ep1_counter counter
ep1_counter 12
# HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
一组counter指标
// CounterVec是一组counter,这些计数器具有相同的描述,但它们的变量标签具有不同的值。
//step1:初始化一个容器
httpReqs := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
},
[]string{"code", "method"},
)
//step2:注册容器
prometheus.MustRegister(httpReqs)
httpReqs.WithLabelValues("404", "POST").Add(42)
// If you have to access the same set of labels very frequently, it
// might be good to retrieve the metric only once and keep a handle to
// it. But beware of deletion of that metric, see below!
//step3:向容器中写入值,主要调用容器的方法如Inc()或者Add()方法
m := httpReqs.WithLabelValues("200", "GET")
for i := 0; i < 1000000; i++ {
m.Inc()
}
// Delete a metric from the vector. If you have previously kept a handle
// to that metric (as above), future updates via that handle will go
// unseen (even if you re-create a metric with the same label set
// later).
httpReqs.DeleteLabelValues("200", "GET")
// Same thing with the more verbose Labels syntax.
httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"})
guage 示例
// CounterVec是一组counter,这些计数器具有相同的描述,但它们的变量标签具有不同的值。
//step1:初始化一个容器
httpReqs := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
},
[]string{"code", "method"},
)
//step2:注册容器
prometheus.MustRegister(httpReqs)
httpReqs.WithLabelValues("404", "POST").Add(42)
// If you have to access the same set of labels very frequently, it
// might be good to retrieve the metric only once and keep a handle to
// it. But beware of deletion of that metric, see below!
//step3:向容器中写入值,主要调用容器的方法如Inc()或者Add()方法
m := httpReqs.WithLabelValues("200", "GET")
for i := 0; i < 1000000; i++ {
m.Inc()
}
// Delete a metric from the vector. If you have previously kept a handle
// to that metric (as above), future updates via that handle will go
// unseen (even if you re-create a metric with the same label set
// later).
httpReqs.DeleteLabelValues("200", "GET")
// Same thing with the more verbose Labels syntax.
httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"})
package main
import (
"fmt"
"net/http"
"math/rand"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
// 创建一个 Gauge 指标
gauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "temperature_celsius",
Help: "Current temperature in Celsius",
})
// 注册指标
prometheus.MustRegister(gauge)
// 模拟温度变化,每秒随机变化温度
go func() {
for {
// 模拟随机温度值
temperature := 20.0 + 10.0*rand.Float64()
gauge.Set(temperature)
time.Sleep(time.Second)
}
}()
// 暴露指标
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Gauge exporter is running on :8080/metrics")
http.ListenAndServe(":8080", nil)
}
histogram 示例
temps := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "pond_temperature_celsius",
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide.
})
// Simulate some observations.
for i := 0; i < 1000; i++ {
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
}
// Just for demonstration, let's check the state of the histogram by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
temps.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
// 创建一个 Histogram 指标
histogram := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "request_duration_seconds",
Help: "Duration of requests in seconds",
Buckets: prometheus.DefBuckets, // 默认桶设置
})
// 注册指标
prometheus.MustRegister(histogram)
// 模拟请求,每秒随机生成请求持续时间并观察直方图
go func() {
for {
duration := time.Duration(rand.Intn(100)) * time.Millisecond
histogram.Observe(duration.Seconds())
time.Sleep(time.Second)
}
}()
// 暴露指标
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Histogram exporter is running on :8080/metrics")
http.ListenAndServe(":8080", nil)
}
Summary 示例
temps := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "pond_temperature_celsius",
Help: "The temperature of the frog pond.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // 分为数:偏差值
})
// Simulate some observations.
for i := 0; i < 1000; i++ {
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
}
// Just for demonstration, let's check the state of the summary by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
temps.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
// 创建一个 Summary 指标
summary := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "request_latency_seconds",
Help: "Latency of requests in seconds",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // 指定摘要目标
})
// 注册指标
prometheus.MustRegister(summary)
// 模拟请求,每秒随机生成请求持续时间并观察摘要
go func() {
for {
duration := time.Duration(rand.Intn(100)) * time.Millisecond
summary.Observe(duration.Seconds())
time.Sleep(time.Second)
}
}()
// 暴露指标
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Summary exporter is running on :8080/metrics")
http.ListenAndServe(":8080", nil)
}
Collector 模式开发
package main
import (
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Define a custom Collector to collect memory metrics
type memoryCollector struct {
totalMetric prometheus.Gauge
usedMetric prometheus.Gauge
freeMetric prometheus.Gauge
total float64 // 新增字段,用于存储总内存值
used float64 // 新增字段,用于存储已使用内存值
free float64 // 新增字段,用于存储空闲内存值
}
// Implement the Describe method for the Collector interface
func (c *memoryCollector) Describe(ch chan<- *prometheus.Desc) {
c.totalMetric.Describe(ch)
c.usedMetric.Describe(ch)
c.freeMetric.Describe(ch)
}
// Implement the Collect method for the Collector interface
func (c *memoryCollector) Collect(ch chan<- prometheus.Metric) {
// Open /proc/meminfo file
file, err := os.Open("/proc/meminfo")
if err != nil {
fmt.Println("Error opening /proc/meminfo:", err)
return
}
defer file.Close()
// Read memory info from file
buf := make([]byte, 1024)
n, err := file.Read(buf)
if err != nil {
fmt.Println("Error reading /proc/meminfo:", err)
return
}
// Parse memory info
lines := strings.Split(string(buf[:n]), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
switch fields[0] {
case "MemTotal:":
total, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
fmt.Println("Error parsing MemTotal:", err)
continue
}
c.total = total
c.totalMetric.Set(total)
case "MemFree:":
free, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
fmt.Println("Error parsing MemFree:", err)
continue
}
c.free = free
c.freeMetric.Set(free)
case "MemAvailable:":
available, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
fmt.Println("Error parsing MemAvailable:", err)
continue
}
// Calculate used memory as total - available
c.used = c.total - available
c.usedMetric.Set(c.used)
}
}
// Collect metrics
c.totalMetric.Collect(ch)
c.usedMetric.Collect(ch)
c.freeMetric.Collect(ch)
}
func main() {
// Create new memoryCollector
memory := &memoryCollector{
totalMetric: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "memory_total_bytes",
Help: "Total memory in bytes",
}),
usedMetric: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "memory_used_bytes",
Help: "Used memory in bytes",
}),
freeMetric: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "memory_free_bytes",
Help: "Free memory in bytes",
}),
}
// Register the memoryCollector with Prometheus
prometheus.MustRegister(memory)
// Create a new ServeMux
mux := http.NewServeMux()
// Register the Prometheus handler to the /metrics endpoint
mux.Handle("/metrics", promhttp.Handler())
// Start HTTP server to expose metrics
httpServer := &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: mux, // Use the custom ServeMux
}
go func() {
if err := httpServer.ListenAndServe(); err != nil {
fmt.Println("Error starting HTTP server:", err)
}
}()
fmt.Println("Memory exporter is running on :8080/metrics")
// Keep the program running
select {}
}
Gin 集成
func PromHandler(handler http.Handler) gin.HandlerFunc {
return func(c *gin.Context) {
handler.ServeHTTP(c.Writer, c.Request)
}
}
router.GET("/metrics", PromHandler(promhttp.Handler()))