用Go语言从零开始开发一个Prometheus Exporter
用Go语言从零开始开发一个Prometheus Exporter
1 prometheus介绍
Prometheus 是一个开源的系统监控和报警工具,专为高效采集和存储时间序列数据而设计,目前广泛用于监控微服务架构、云基础设施和Kubernetes集群等环境。Prometheus的架构图如下。
exporter可以理解为prometheus的客户端,作用是把从节点 ( 操作系统或应用服务或存储等) 中采集到的监控数据作为Metrics指标,再将Metrics指标以HTTP的方式暴露给Prometheus。常用的exporter有node exporter、mysql exporter等。
2 metrics介绍
在Prometheus中,"metrics"(度量)是指系统或应用程序的性能数据或状态数据,这些数据以时间序列的形式存储,并用于监控和分析。Prometheus 的 metrics 数据模型是基于时间序列(time series)的,每个时间序列由以下几个部分组成:
-
指标名称 (Metric Name): 描述指标的名称。
-
标签 (Labels): 可选,一组键值对,用于标识同一指标的不同实例。
-
时间戳 (Timestamp): 可选的 Unix 时间戳,表示指标记录的时间点。
-
值 (Value): 指标的数值。
3 metrics的类型
Prometheus的client libraries中目前提供了四种核心的Metrics类型Counter、Gauge、Histogram和Summary
3.1 Counter类型
对于只会增加的数据,可以使用counter类型。counter是一个只增不减的计数器,除非重置,反映的是线性数据。它的值通常从零开始累加,例如可以用来表示promhttp_metric_handler_requests_total (/metrics的总请求数) 的值。
对于会增加也会减少的数据,可以使用Gauge类型。Gauge用来表示可增可减的数据,反映的是动态变化的数据。例如memory_free_bytes (节点剩余可用内存数)。
3.3 Histogram类型
Histogram测量离散事件的分布。常见例子是 HTTP 请求的延迟、函数运行时或 I/O 请求大小。Histogram统计了指标的时序流历史样本点,输出在对应区间内样本点的数量。
3.4 Summary类型
Summary也是测量离散事件的分布,不过引入了分位数的概念,能够输出指标的时序流历史中,指定分位数后的统计值。4 使用go开发一个exporter
尽管官方和第三方已经提供了很多实用的exporter,但有时候我们仍然需要自己去开发exporter,因为自己的Prometheus Exporter可以根据应用和业务的特定需求来自定义和精细化监控指标,确保监控更准确。并且还能整合多种数据源,并灵活调整监控内容,适应复杂和变化的环境。开发步骤如下- 根据开发语言下载好prometheus client库
- 创建metrics指标
- 给metrics指标赋值
- 注册metrics指标
- 通过http暴露metics指标
先来一个例子
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
)
// 定义自定义指标
var (
httpRequestsTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests.",
})
currentMemoryUsage = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "current_memory_usage_bytes",
Help: "Current memory usage in bytes.",
})
)
func init() {
// 注册自定义指标
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(currentMemoryUsage)
}
func main() {
// 增加计数器
httpRequestsTotal.Inc()
// 设置仪表的值
currentMemoryUsage.Set(512.0)
// 暴露metrics
http.Handle("/metrics", promhttp.Handler())
log.Println("Beginning to serve on port :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
运行代码,访问http://localhost:8080/metrics,可以查看到如下结果
# HELP current_memory_usage_bytes Current memory usage in bytes.
# TYPE current_memory_usage_bytes gauge
current_memory_usage_bytes 512
# HELP http_requests_total Total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total 1
4.1 准备工作
先新建一个文件夹singless_exporter,再将go模块的安装源修改为国内源。进入singless_exporter文件夹,安装prometheus client相关库。go不能采用太老的版本,否则有些库或模块可能无法使用。
检查模块是否已成功安装
4.2 自定义指标
以下通过四段代码演示如何自定义Counter、Gauge、Histogram和Summary四种Metrics指标- counter
package main
import (
"fmt"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
// 创建一个 Counter 指标
counter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "requests_total",
Help: "Total number of requests",
})
// 注册指标
prometheus.MustRegister(counter)
// 给指标赋值。模拟请求,每秒递增计数器
go func() {
for {
counter.Inc()
time.Sleep(time.Second)
}
}()
// 通过http暴露指标
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Counter exporter is running on :8080/metrics")
http.ListenAndServe(":8080", nil)
}
运行代码检查指标
- Gauge
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
package main
import (
"fmt"
"net/http"
"time"
"math/rand"
"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
package main
import (
"fmt"
"net/http"
"time"
"math/rand"
"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)
}
运行代码查看效果
4.3 通过http暴露指标
promhttp 包提供了有关 HTTP 服务器和客户端工具。可以通过创建http.Handler实例来达到以http协议暴露Prometheus指标的目的。package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
运行代码,访问localhost:8080/metrics可以看到默认的指标
5 实战案例
下面再通过一段代码演示如何获取Linux系统内存的total、used、free三个值package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"net/http"
"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 {}
}
运行代码后,输入端口号,可以看到内存相关信息
关注公众号 singless,获取更多有价值的文章
你可能还喜欢
点击下方文字即可阅读
微信扫一扫
关注该公众号