Contents

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