Prometheus 官方和社区提供了非常多的exporter,涵盖数据库、中间件、OS、存储、硬件设备等,具体可查看exportersexporterhub.io,通过这些 exporter 基本可以覆盖80%的监控需求,依然有小部分需要通过自定义脚本或者定制、修改社区exporter实现。本文我们将学习如何通过go编写一个简单的expoter用于暴露OS的负载。

要实现的三个load指标如下:

exporter的核心是http服务,对外暴露exporter本身运行时指标和监控信息。我们可以直接通过net/http暴力实现,更好的方式是使用Prometheus 官方提供的client library 来简化一部分工作。

client library官方支持语言:

也有社区支持的其他语言库如C、C++、PHP等

获取数据源


在使用client library暴露数据之前,我们得先找到数据源,以linux为例要获取系统负载我们可以读取/proc目录下的loadavg文件。涉及到各类操作系统指标的获取可以参考官方的node-exporter,这里我们给他写成load包,等会直接调用GetLoad()就能拿到数据了。

package collect

import (
"fmt"
"io/ioutil"
"strconv"
"strings"
) // The path of the proc filesystem.
var procPath = "/proc/loadavg" // Read loadavg from /proc.
func GetLoad() (loads []float64, err error) {
data, err := ioutil.ReadFile(procPath)
if err != nil {
return nil, err
}
loads, err = parseLoad(string(data))
if err != nil {
return nil, err
}
return loads, nil
} // Parse /proc loadavg and return 1m, 5m and 15m.
func parseLoad(data string) (loads []float64, err error) {
loads = make([]float64, 3)
parts := strings.Fields(data)
if len(parts) < 3 {
return nil, fmt.Errorf("unexpected content in %s", procPath)
}
for i, load := range parts[0:3] {
loads[i], err = strconv.ParseFloat(load, 64)
if err != nil {
return nil, fmt.Errorf("could not parse load '%s': %w", load, err)
}
}
return loads, nil
}

通过client_golang暴露指标


开通我们提到exporter要暴露的指标包含两部分,一是本身的运行时信息,另一个监控的metrics。而运行时信息client_golang已经帮我们实现了,我们要做的是通过client_golang包将监控数据转换为metrics后再暴露出来。

一个最基础使用client_golang包示例如下:

package main

import (
"net/http" "github.com/prometheus/client_golang/prometheus/promhttp"
) func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":2112", nil)
}

promhttp.Handler()封装了本身的 go 运行时 metrics,并按照metircs后接value的格式在前端输出。

当我们访问2112端口的metrics路径时得到如下数据:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 7
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.15.14"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
...

如何暴露自定义metrics呢?

先看如下的示例:

package main

import (
"net/http"
"time"
"log" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
) func recordMetrics() {
go func() {
for {
opsProcessed.Inc()
time.Sleep(2 * time.Second)
}
}()
} var (
opsProcessed = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "myapp",
Name: "processed_ops_total",
Help: "The total number of processed events",
})
) func main() {
prometheus.MustRegister(opsProcessed)
recordMetrics() http.Handle("/metrics", promhttp.Handler())
log.Print("export /metrics on port :8085")
http.ListenAndServe(":8085", nil)
}

示例来自于官方仓库,做了稍加修改。可以看到使用NewCounter方法可以很快地帮我们创建一个Prometheus Counter数据类型实例。

Counter接口的定义包含了Counter本身的特性-只能增加即Inc和Add,同时还包含Meterics、Collector接口

Collector还包含2个方法,待会我们写自己的Collector时需要实现这两个方法。

type Collector interface {
Describe(chan<- *Desc)
Collect(chan<- Metric)
}

CounterOpts 来源于metrics.go 的Ops结构体定义了构成metrics的基本结构。

接着将opsProcessed这个Counter进行注册,所谓注册也就是让Handler跟踪这个Counter中的metircs和collector

运行后,访问/metircs可以看到自定义指标myapp_processed_ops_total通过定时的Inc()调用来更新value

# HELP myapp_processed_ops_total The total number of processed events
# TYPE myapp_processed_ops_total counter
myapp_processed_ops_total 15

下面我们通过自定义collector实现一个简易的exporter

目录结构如下:

# tree .
.
├── collect
│   ├── collector.go
│   └── loadavg.go
├── go.mod
├── go.sum
└── main.go

loadavg.go即上面的获取数据源。

collector.go如下:

package collect

import (
"log" "github.com/prometheus/client_golang/prometheus"
) var namespace = "node" type loadavgCollector struct {
metrics []typedDesc
} type typedDesc struct {
desc *prometheus.Desc
valueType prometheus.ValueType
} func NewloadavgCollector() *loadavgCollector {
return &loadavgCollector{
metrics: []typedDesc{
{prometheus.NewDesc(namespace+"_load1", "1m load average.", nil, nil), prometheus.GaugeValue},
{prometheus.NewDesc(namespace+"_load5", "5m load average.", , nil), prometheus.GaugeValue},
{prometheus.NewDesc(namespace+"_load15", "15m load average.", nil, nil), prometheus.GaugeValue},
},
}
} //Each and every collector must implement the Describe function.
//It essentially writes all descriptors to the prometheus desc channel.
func (collector *loadavgCollector) Describe(ch chan<- *prometheus.Desc) { //Update this section with the each metric you create for a given collector
ch <- collector.metrics[1].desc
} //Collect implements required collect function for all promehteus collectors
func (collector *loadavgCollector) Collect(ch chan<- prometheus.Metric) { //Implement logic here to determine proper metric value to return to prometheus
//for each descriptor or call other functions that do so.
loads, err := GetLoad()
if err != nil {
log.Print("get loadavg error: ", err)
} //Write latest value for each metric in the prometheus metric channel.
//Note that you can pass CounterValue, GaugeValue, or UntypedValue types here. for i, load := range loads {
ch <- prometheus.MustNewConstMetric(collector.metrics[i].desc, prometheus.GaugeValue, load)
} }

collector中每一个要暴露的metrics都需要包含一个metrics描述即desc,都需要符合prometheus.Desc结构,我们可以直接使用NewDesc来创建。这里我们创建了三个metircs_name分别为node_load1、node_load5、node_15以及相应的描述,也可以加上对应的label。

接着实现collector的两个方法Describe、Collect分别写入对应的发送channel,其中prometheus.Metric的通道传入的值还包括三个load的value

最后在主函数中注册collector

prometheus.MustRegister(collect.NewloadavgCollector())

在Prometheus每个请求周期到达时都会使用GetLoad()获取数据,转换为metircs,发送给Metrics通道,http Handler处理和返回。


实现一个指标丰富、可靠性高的exporter感觉还是有一些困难的,需要对Go的一些特性以及Prometheus client包有较深入的了解。本文是对exporter编写的简单尝试,如实现逻辑、方式或理解不准确可参考开源exporter和官方文档。

文章涉及代码可查看:exporter

通过博客阅读:iqsing.github.io

实现一个Prometheus exporter的更多相关文章

  1. 编写一个简单的基于jmespath 的prometheus exporter

    目的很简单,因为系统好多监控指标是通过json 暴露的,并不是标准的prometheus metrics 格式,处理方法 实际上很简单,我们可以基于jsonpath 解析json数据,转换为prome ...

  2. Go语言开发Prometheus Exporter示例

    一.Prometheus中的基本概念 Prometheus将所有数据存储为时间序列,这里先来了解一下prometheus中的一些基本概念 指标名和标签每个时间序列都由指标名和一组键值对(也称为标签)唯 ...

  3. prometheus exporter简介

    一.服务分类 在线服务:请求的客户端和发起者需要立即响应(高并发.低延迟:并发数.接口响应时间.错误数.延迟时间),面对突发流量能进行资源的自动伸缩 离线服务:请求发送到服务端但不要求立即获取结果(监 ...

  4. prometheus学习系列十一: Prometheus exporter详解

    exporter详解 前面的系列中,我们在主机上面安装了node_exporter程序,该程序对外暴露一个用于获取当前监控样本数据的http的访问地址, 这个的一个程序成为exporter,Expor ...

  5. prometheus+exporter小测试:

    1.golang中使用expoter import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) fu ...

  6. Prometheus exporter的Node exporter是可以独立安装,用来测试的

    现在慢慢在把prometheus operator的一些概念组织完整. https://github.com/coreos/prometheus-operator/tree/master/contri ...

  7. 使用grok exporter 做为log 与prometheus 的桥

    grok 是一个工具,可以用来解析非结构化的日志文件,可以使其结构化,同时方便查询,grok 被logstash 大量依赖 同时社区也提供了一个prometheus 的exporter 可以方便的进行 ...

  8. Prometheus之Exporter开发

    Prometheus开发Exporter简介 Exporter 本身是一个http 服务,其指标结果只要符合 Prometheus 规范就可以被 Prometheus 使用. Prometheus中m ...

  9. 使用 Prometheus + Grafana 对 Kubernetes 进行性能监控的实践

    1 什么是 Kubernetes? Kubernetes 是 Google 开源的容器集群管理系统,其管理操作包括部署,调度和节点集群间扩展等. 如下图所示为目前 Kubernetes 的架构图,由 ...

随机推荐

  1. 条件表达式和switch语句

    一,条件表达式相当于(相当于if-else) 1.条件表达式 ? : 由条件运算符组成条件表达式的一般情况为: 表达式1?表达式2:表达式3: 求值规则:如果表达式的值为真,则以表达式2的值作为条件的 ...

  2. 如何使用 pyqt 实现 Groove 音乐播放器

    前言 Win10 自带了一款很好用的音乐播放器 -- Groove 音乐,但是只能支持本地音乐的播放.2020年3月底到4月底,自己一直在学习 PyQt5,然后从 2020年5月开始,着手使用 PyQ ...

  3. [AcWing 776] 字符串移位包含问题

    点击查看代码 #include<iostream> #include<algorithm> using namespace std; string a, b; int main ...

  4. [题解] [AGC022E] Median Replace

    题目大意 有个奇数长度的 \(01\) 串 \(s\) 其中有若干位置是 \(?\). 每次可将 \(3\) 个连续的字符替换成这三个数的中位数. 求有多少方案将 \(?\) 替换成 \(0/1\) ...

  5. Linux内存、Swap、Cache、Buffer详细解析

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 1. 通过free命令看Linux内存 total:总内存大小. used:已经使用的内存大小 ...

  6. DOM 事件与事件委托

    DOM 事件与事件委托 本文写于 2020 年 5 月 28 日 先思考一个问题:我们如何给一百个 button 添加点击事件? 遍历? 那岂不是要添加一百个监听器? 这就需要事件委托了. 其实这根本 ...

  7. if判断+while循环

    1.常量​ ​纯大写字母命名常量名,如:AGE_OF_OLDBOY=18 常量的值是可以改变的,如:AGE_OF_OLDBOY=19 2.基本运算符 (1).算术运算​ +.-.*./​ ​      ...

  8. 使用Go实现健壮的内存型缓存

    使用Go实现健壮的内存型缓存 本文介绍了缓存的常见使用场景.选型以及注意点,比较有价值. 译自:Implementing robust in-memory cache with Go 内存型缓存是一种 ...

  9. 干掉RedisHelper,请这样用分布式缓存

    前言 我们在项目中使用Redis时通常是写一个单例模式的RedisHelper静态类,暴露一些常用的Get.Set等操作,在需要使用地方直接RedisHelper.StringGet(xx,xx)就可 ...

  10. linux篇-linux 下建立多个tomcat

    第一步:复制,解压 将准备好的tomcat压缩包复制到你准备安装的目录,我的tomcat压缩包名字是tomcat.tar.gz,我的安 装目录是 /usr/java/tomcat 第二步:解压tomc ...