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. 2021.11.30 eleveni的水省选题的记录

    2021.11.30 eleveni的水省选题的记录 因为eleveni比较菜,eleveni决定先刷图论,再刷数据结构,同时每天都要刷dp.当然,对于擅长的图论,eleveni决定从蓝题开始刷.当然 ...

  2. Linux中,MySQL的常用命令

    我的博客 登录 mysql -u用户名 -p -- 然后在下面输入密码,Linux的密码不会显示出,盲打就可以 mysql -u用户名 -p密码 -- 这种方式将直接登录 开关 开启数据库 servi ...

  3. 9.1 Linux存储结构和文件系统

    1. 存储结构 Linux系统中的一切文件都是从"根"目录(/)开始的,并按照文件系统层次标准(FHS)采用倒树状结构来存放文件,以及定义了常见目录的用途. 目录名称 应放置文件的 ...

  4. Es5 - 11 详解

    一.ES简介 ECMAScript,欧洲计算机制造商协会 ES是一种标准,而JS是ES的一种实现 每年的ES版本中都会引入新特性 二.NRM的安装与使用 NRM是切换源的工具 ES6 2.1安装 wi ...

  5. XCTF练习题---MISC---something_in_image

    XCTF练习题---MISC---something_in_image flag:Flag{yc4pl0fvjs2k1t7T} 解题步骤: 1.观察题目,下载附件,这是一道2019湖湘杯的题目 2.下 ...

  6. JavaWeb之如何把请求数据转成实体类

    JavaWeb之如何把请求数据转成实体类 自己写个工具类加入下面两个静态方法 自定一个注解类DateTimeFormatting 调用方式User user = util.ObjectFromMap( ...

  7. 2022 Java生态系统报告:Java 11超Java 8、Oracle在缩水、Amazon在崛起!

    近日,New Relic发布了最新的2022 Java生态系统报告,这份报告可以帮助我们深入的了解Java体系的最新使用情况,下面就一起来看看2022年,Java发展的怎么样了,还是Java 8 YY ...

  8. Java 17中对switch的模式匹配增强

    还记得Java 16中的instanceof增强 吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); d ...

  9. Blazor和Vue对比学习(基础1.6):祖孙传值,联级和注入

    前面章节,我们实现了父子组件之间的数据传递.大多数时候,我们以组件形式来构建页面的区块,会涉及到组件嵌套的问题,一层套一层.这种情况,很大概率需要将祖先的数据,传递给子孙后代去使用.我们当然可以使用父 ...

  10. 一探 Vue 数据响应式原理

    一探 Vue 数据响应式原理 本文写于 2020 年 8 月 5 日 相信在很多新人第一次使用 Vue 这种框架的时候,就会被其修改数据便自动更新视图的操作所震撼. Vue 的文档中也这么写道: Vu ...