从零开始写一个Exporter
前言
上一篇文章中已经给大家整体的介绍了开源监控系统Prometheus,其中Exporter作为整个系统的Agent端,通过HTTP接口暴露需要监控的数据。那么如何将用户指标通过Exporter的形式暴露出来呢?比如说在线,请求失败数,异常请求等指标可以通过Exporter的形式暴露出来,从而基于这些指标做告警监控。
演示环境
$ uname -a Darwin 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64 $ go version go version go1.12.4 darwin/amd64
四类指标介绍
Prometheus定义了4种不同的指标类型:Counter(计数器),Gauge(仪表盘),Histogram(直方图),Summary(摘要)。
其中Exporter返回的样本数据中会包含数据类型的说明,例如:
# TYPE node_network_carrier_changes_total counter node_network_carrier_changes_total{device="br-01520cb4f523"} 1
这四类指标的特征为:
Counter:只增不减(除非系统发生重启,或者用户进程有异常)的计数器。常见的监控指标如http_requests_total, node_cpu都是Counter类型的监控指标。一般推荐在定义为Counter的指标末尾加上_total作为后缀。
Gauge:可增可减的仪表盘。Gauge类型的指标侧重于反应系统当前的状态。因此此类指标的数据可增可减。常见的例如node_memory_MemAvailable_bytes(可用内存)。
HIstogram:分析数据分布的直方图。显示数据的区间分布。例如统计请求耗时在0-10ms的请求数量和10ms-20ms的请求数量分布。
Summary: 分析数据分布的摘要。显示数据的中位数,9分数等。
实战
接下来我将用Prometheus提供的Golang SDK 编写包含上述四类指标的Exporter,示例的编写修改自SDK的example。由于example中示例比较复杂,我会精简一下,尽量让大家用最小的学习成本能够领悟到Exporter开发的精髓。第一个例子会演示Counter和Gauge的用法,第二个例子演示Histogram和Summary的用法。
Counter和Gauge用法演示:
package mainimport ("flag""log""net/http""github.com/prometheus/client_golang/prometheus/promhttp" )var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")func main() {flag.Parse()http.Handle("/metrics", promhttp.Handler())log.Fatal(http.ListenAndServe(*addr, nil)) }
上述代码就是一个通过0.0.0.0:8080/metrics 暴露golang信息的原始Exporter,没有包含任何的用户自定义指标信息。接下来往里面添加Counter和Gauge类型指标:
1 func recordMetrics() { 2 go func() { 3 for { 4 opsProcessed.Inc() 5 myGague.Add(11) 6 time.Sleep(2 * time.Second) 7 } 8 }() 9 } 10 11 var ( 12 opsProcessed = promauto.NewCounter(prometheus.CounterOpts{ 13 Name: "myapp_processed_ops_total", 14 Help: "The total number of processed events", 15 }) 16 myGague = promauto.NewGauge(prometheus.GaugeOpts{ 17 Name: "my_example_gauge_data", 18 Help: "my example gauge data", 19 ConstLabels:map[string]string{"error":""}, 20 }) 21 )
在上面的main函数中添加recordMetrics方法调用。curl 127.0.0.1:8080/metrics 能看到自定义的Counter类型指标myapp_processed_ops_total 和 Gauge 类型指标my_example_gauge_data。
# HELP my_example_gauge_data my example gauge data # TYPE my_example_gauge_data gauge my_example_gauge_data{error=""} 44 # HELP myapp_processed_ops_total The total number of processed events # TYPE myapp_processed_ops_total counter myapp_processed_ops_total 4
其中#HELP 是代码中的Help字段信息,#TYPE 说明字段的类型,例如my_example_gauge_data是gauge类型指标。my_example_gauge_data是指标名称,大括号括起来的error是该指标的维度,44是该指标的值。需要特别注意的是第12行和16行用的是promauto包的NewXXX方法,例如:
func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {c := prometheus.NewCounter(opts)prometheus.MustRegister(c)return c }
可以看到该函数是会自动调用MustRegister方法,如果用的是prometheus包的NewCounter则需要再自行调用MustRegister注册收集的指标。其中Couter类型指标有以下的内置接口:
type Counter interface {MetricCollector// Inc increments the counter by 1. Use Add to increment it by arbitrary// non-negative values. Inc()// Add adds the given value to the counter. It panics if the value is <// 0. Add(float64) }
可以通过Inc()接口给指标直接进行+1操作,也可以通过Add(float64)给指标加上某个值。还有继承自Metric和Collector的一些描述接口,这里不做展开。
Gauge类型的内置接口有:
type Gauge interface {MetricCollector// Set sets the Gauge to an arbitrary value. Set(float64)// Inc increments the Gauge by 1. Use Add to increment it by arbitrary// values. Inc()// Dec decrements the Gauge by 1. Use Sub to decrement it by arbitrary// values. Dec()// Add adds the given value to the Gauge. (The value can be negative,// resulting in a decrease of the Gauge.) Add(float64)// Sub subtracts the given value from the Gauge. (The value can be// negative, resulting in an increase of the Gauge.) Sub(float64)// SetToCurrentTime sets the Gauge to the current Unix time in seconds. SetToCurrentTime() }
需要注意的是Gauge提供了Sub(float64)的减操作接口,因为Gauge是可增可减的指标。Counter因为是只增不减的指标,所以只有加的接口。
Histogram和Summary用法演示:
1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 "math" 8 "math/rand" 9 "net/http" 10 "time" 11 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/prometheus/client_golang/prometheus/promhttp" 14 ) 15 16 var ( 17 addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") 18 uniformDomain = flag.Float64("uniform.domain", 0.0002, "The domain for the uniform distribution.") 19 normDomain = flag.Float64("normal.domain", 0.0002, "The domain for the normal distribution.") 20 normMean = flag.Float64("normal.mean", 0.00001, "The mean for the normal distribution.") 21 oscillationPeriod = flag.Duration("oscillation-period", 10*time.Minute, "The duration of the rate oscillation period.") 22 ) 23 24 var ( 25 rpcDurations = prometheus.NewSummaryVec( 26 prometheus.SummaryOpts{ 27 Name: "rpc_durations_seconds", 28 Help: "RPC latency distributions.", 29 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, 30 }, 31 []string{"service","error_code"}, 32 ) 33 rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ 34 Name: "rpc_durations_histogram_seconds", 35 Help: "RPC latency distributions.", 36 Buckets: prometheus.LinearBuckets(0, 5, 20), 37 }) 38 ) 39 40 func init() { 41 // Register the summary and the histogram with Prometheus's default registry. 42 prometheus.MustRegister(rpcDurations) 43 prometheus.MustRegister(rpcDurationsHistogram) 44 // Add Go module build info. 45 prometheus.MustRegister(prometheus.NewBuildInfoCollector()) 46 } 47 48 func main() { 49 flag.Parse() 50 51 start := time.Now() 52 53 oscillationFactor := func() float64 { 54 return 2 + math.Sin(math.Sin(2*math.Pi*float64(time.Since(start))/float64(*oscillationPeriod))) 55 } 56 57 go func() { 58 i := 1 59 for { 60 time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond) 61 if (i*3) > 100 { 62 break 63 } 64 rpcDurations.WithLabelValues("normal","400").Observe(float64((i*3)%100)) 65 rpcDurationsHistogram.Observe(float64((i*3)%100)) 66 fmt.Println(float64((i*3)%100), " i=", i) 67 i++ 68 } 69 }() 70 71 go func() { 72 for { 73 v := rand.ExpFloat64() / 1e6 74 rpcDurations.WithLabelValues("exponential", "303").Observe(v) 75 time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond) 76 } 77 }() 78 79 // Expose the registered metrics via HTTP. 80 http.Handle("/metrics", promhttp.Handler()) 81 log.Fatal(http.ListenAndServe(*addr, nil)) 82 }
第25-32行定义了一个Summary类型指标,其中有service和errro_code两个维度。第33-37行定义了一个Histogram类型指标,从0开始,5为宽度,有20个直方。也就是0-5,6-10,11-15 .... 等20个范围统计。
其中直方图HIstogram指标的相关结果为:
1 # HELP rpc_durations_histogram_seconds RPC latency distributions. 2 # TYPE rpc_durations_histogram_seconds histogram 3 rpc_durations_histogram_seconds_bucket{le="0"} 0 4 rpc_durations_histogram_seconds_bucket{le="5"} 1 5 rpc_durations_histogram_seconds_bucket{le="10"} 3 6 rpc_durations_histogram_seconds_bucket{le="15"} 5 7 rpc_durations_histogram_seconds_bucket{le="20"} 6 8 rpc_durations_histogram_seconds_bucket{le="25"} 8 9 rpc_durations_histogram_seconds_bucket{le="30"} 10 10 rpc_durations_histogram_seconds_bucket{le="35"} 11 11 rpc_durations_histogram_seconds_bucket{le="40"} 13 12 rpc_durations_histogram_seconds_bucket{le="45"} 15 13 rpc_durations_histogram_seconds_bucket{le="50"} 16 14 rpc_durations_histogram_seconds_bucket{le="55"} 18 15 rpc_durations_histogram_seconds_bucket{le="60"} 20 16 rpc_durations_histogram_seconds_bucket{le="65"} 21 17 rpc_durations_histogram_seconds_bucket{le="70"} 23 18 rpc_durations_histogram_seconds_bucket{le="75"} 25 19 rpc_durations_histogram_seconds_bucket{le="80"} 26 20 rpc_durations_histogram_seconds_bucket{le="85"} 28 21 rpc_durations_histogram_seconds_bucket{le="90"} 30 22 rpc_durations_histogram_seconds_bucket{le="95"} 31 23 rpc_durations_histogram_seconds_bucket{le="+Inf"} 33 24 rpc_durations_histogram_seconds_sum 1683 25 rpc_durations_histogram_seconds_count 33
xxx_count反应当前指标的记录总数,xxx_sum表示当前指标的总数。不同的le表示不同的区间,后面的数字是从开始到这个区间的总数。例如le="30"后面的10表示有10个样本落在0-30区间,那么26-30这个区间一共有多少个样本呢,只需要用len="30" - len="25",即2个。也就是27和30这两个点。
Summary相关的结果如下:
1 # HELP rpc_durations_seconds RPC latency distributions. 2 # TYPE rpc_durations_seconds summary 3 rpc_durations_seconds{error_code="303",service="exponential",quantile="0.5"} 7.176288428497417e-07 4 rpc_durations_seconds{error_code="303",service="exponential",quantile="0.9"} 2.6582266087185467e-06 5 rpc_durations_seconds{error_code="303",service="exponential",quantile="0.99"} 4.013935374172691e-06 6 rpc_durations_seconds_sum{error_code="303",service="exponential"} 0.00015065426336339398 7 rpc_durations_seconds_count{error_code="303",service="exponential"} 146 8 rpc_durations_seconds{error_code="400",service="normal",quantile="0.5"} 51 9 rpc_durations_seconds{error_code="400",service="normal",quantile="0.9"} 90 10 rpc_durations_seconds{error_code="400",service="normal",quantile="0.99"} 99 11 rpc_durations_seconds_sum{error_code="400",service="normal"} 1683 12 rpc_durations_seconds_count{error_code="400",service="normal"} 33
其中sum和count指标的含义和上面Histogram一致。拿第8-10行指标来说明,第8行的quantile 0.5 表示这里指标的中位数是51,9分数是90。
自定义类型
如果上面Counter,Gauge,Histogram,Summary四种内置指标都不能满足我们要求时,我们还可以自定义类型。只要实现了Collect接口的方法,然后调用MustRegister即可:
func MustRegister(cs ...Collector) {DefaultRegisterer.MustRegister(cs...) }type Collector interface {Describe(chan<- *Desc)Collect(chan<- Metric) }
总结
文章通过Prometheus内置的Counter(计数器),Gauge(仪表盘),Histogram(直方图),Summary(摘要)演示了Exporter的开发,最后提供了自定义类型的实现方法。
参考
https://prometheus.io/docs/guides/go-application/
https://yunlzheng.gitbook.io/prometheus-book/parti-prometheus-ji-chu/promql/prometheus-metrics-types
https://songjiayang.gitbooks.io/prometheus/content/concepts/metric-types.html
转载于:https://www.cnblogs.com/makelu/p/11082485.html
从零开始写一个Exporter相关推荐
- 从零开始写一个武侠冒险游戏-3-地图生成
2019独角兽企业重金招聘Python工程师标准>>> 从零开始写一个武侠冒险游戏-3-地图生成 概述 前面两章我们设计了角色的状态, 绘制出了角色, 并且赋予角色动作, 现在是时候 ...
- 如何搭建python框架_从零开始:写一个简单的Python框架
原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...
- 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)
从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...
- mysql c测试程序_Linux平台下从零开始写一个C语言访问MySQL的测试程序
Linux 平台下从零开始写一个 C 语言访问 MySQL 的测试程序 2010-8-20 Hu Dennis Chengdu 前置条件: (1) Linux 已经安装好 mysql 数据库: (2) ...
- 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)
从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...
- dotnet 从零开始写一个人工智能 从一个神经元开始
现在小伙伴说的人工智能都是弱智能,可以基于神经网络来做.而神经网络是有多层网络,每一层网络都有多个神经元.那么最简单的神经网络就是只有一层,而这一层只有一个神经元,也就是整个神经网络只是有一个神经元. ...
- 用python从零开始写一个注册机(新手也能操作)-前言
今天开始带领大家从零开始写一个网站的账号注册机,达到批量注册的目的. 涉及到的相关知识包含: python的基本使用 playwright库的相关用法 验证码的识别 欢迎大家关注.
- 从零开始写一个抖音App——Apt代码生成技术、gradle插件开发与protocol协议
1.讨论--总结前两周评论中有意义的讨论并给予我的解答 2.mvps代码生成原理--将上周的 mvps 架构的代码生成原理进行解析 3.开发一款gradle插件--从 mvps 的代码引出 gradl ...
- 从零开始写一个武侠冒险游戏-2-帧动画
从零开始写一个武侠冒险游戏-2-帧动画 ---- 用基本绘图函数实现帧动画 作者:FreeBlues 修订记录 2016.06.10 初稿完成. 2016.08.03 增加对 XCode 项目文件的说 ...
最新文章
- (读书笔记).NET大局观-.NET语言(1)
- 招聘|华为诺亚方舟实验室AI算法实习生
- Qt多线程中的信号与槽
- abb限位开关已打开drv1_施工升降机上有10个限位器,你都知道了吗?
- 18_使用react脚手架构建应用
- IP地址与无符号整数值相互转换
- sap中泰国有预扣税设置吗_泰国的绘图标志| Python中的图像处理
- Apache开启Gzip压缩设置(转)
- java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View...错误原因和解决方法
- JAVA中易出错的小问题(二)
- 太难了 | 2019年互联网寒冬,如何防止自己被裁员失业?
- 2019年 腾讯校园招聘JAVA
- Swarm-bzz/Ipfs-fil的去中心化存储到底是什么?
- u盘安装centos8黑屏_u盘centos7 安装 黑屏苹果电脑怎么安装win7系统还原
- python编辑svg文件_使用Python创建SVG
- html计算平方米,尺和平方米换算(尺与平方米换算计算器)
- uniapp开发APP使用echart
- php 单笔转账到支付宝账户,支付宝公钥证书实现版本
- 细说促销(淘宝销售可看)
- DataCastle X WAIC | 2022世界人工智能大会AI开发的数据基础分论坛即将举行!