序言

Prometheus是一个开源的监控系统,拥有许多Advanced Feature,他会定期用HTTP协议来pull所监控系统状态进行数据收集,在加上timestamp等数据组织成time series data,用metric name和label来标识不同的time series,用户可以将数据用可视化工具显示出来,并设置报警阈值进行报警。

本文将介绍Primetheus client的使用,基于golang语言,golang client 是当pro收集所监控的系统的数据时,用于响应pro的请求,按照一定的格式给pro返回数据,说白了就是一个http server, 源码参见github,相关的文档参见GoDoc,读者可以直接阅读文档进行开发,本文只是帮助理解。

基础

要想学习pro golang client,需要有一个进行测试的环境,笔者建议使用prometheus的docker环境,部署迅速,对于系统没有影响,安装方式参见Using Docker,需要在本地准备好Pro的配置文件prometheus.yml,然后以volme的方式映射进docker,配置文件中的内容如下:

  1. global:
  2. scrape_interval: 15s # By default, scrape targets every 15 seconds.
  3. # Attach these labels to any time series or alerts when communicating with
  4. # external systems (federation, remote storage, Alertmanager).
  5. external_labels:
  6. monitor: 'codelab-monitor'
  7. # A scrape configuration containing exactly one endpoint to scrape:
  8. # Here it's Prometheus itself.
  9. scrape_configs:
  10. # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  11. - job_name: "go-test"
  12. scrape_interval: 60s
  13. scrape_timeout: 60s
  14. metrics_path: "/metrics"
  15. static_configs:
  16. - targets: ["localhost:8888"]

可以看到配置文件中指定了一个job_name,所要监控的任务即视为一个job, scrape_interval和scrape_timeout是pro进行数据采集的时间间隔和频率,matrics_path指定了访问数据的http路径,target是目标的ip:port,这里使用的是同一台主机上的8888端口。此处只是基本的配置,更多信息参见官网

配置好之后就可以启动pro服务了:

docker run --network=host -p 9090:9090 -v /home/gaorong/project/prometheus_test/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

此处网络通信采用的是host模式,所以docker中的pro可以直接通过localhost来指定同一台主机上所监控的程序。prob暴露9090端口进行界面显示或其他操作,需要对docker中9090端口进行映射。启动之后可以访问web页面http://localhost:9090/graph,在status下拉菜单中可以看到配置文件和目标的状态,此时目标状态为DOWN,因为我们所需要监控的服务还没有启动起来,那就赶紧步入正文,用pro golang client来实现程序吧。

四种数据类型

pro将所有数据保存为timeseries data,用metric name和label区分,label是在metric name上的更细维度的划分,其中的每一个实例是由一个float64和timestamp组成,只不过timestamp是隐式加上去的,有时候不会显示出来,如下面所示(数据来源于pro暴露的监控数据,访问http://localhost:9090/metrics 可得),其中go_gc_duration_seconds是metrics name,quantile="0.5"是key-value pair的label,而后面的值是float64 value。

pro为了方便client library的使用提供了四种数据类型: Counter, Gauge, Histogram, Summary, 简单理解就是Counter对数据只增不减,Gauage可增可减,Histogram,Summary提供跟多的统计信息。下面的实例中注释部分# TYPE go_gc_duration_seconds summary 标识出这是一个summary对象。

  1. # HELP go_gc_duration_seconds A summary of the GC invocation durations.
  2. # TYPE go_gc_duration_seconds summary
  3. go_gc_duration_seconds{quantile="0.5"} 0.000107458
  4. go_gc_duration_seconds{quantile="0.75"} 0.000200112
  5. go_gc_duration_seconds{quantile="1"} 0.000299278
  6. go_gc_duration_seconds_sum 0.002341738
  7. go_gc_duration_seconds_count 18
  8. # HELP go_goroutines Number of goroutines that currently exist.
  9. # TYPE go_goroutines gauge
  10. go_goroutines 107

A Basic Example 演示了使用这些数据类型的方法(注意将其中8080端口改为本文的8888)

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "github.com/prometheus/client_golang/prometheus"
  6. "github.com/prometheus/client_golang/prometheus/promhttp"
  7. )
  8. var (
  9. cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
  10. Name: "cpu_temperature_celsius",
  11. Help: "Current temperature of the CPU.",
  12. })
  13. hdFailures = prometheus.NewCounterVec(
  14. prometheus.CounterOpts{
  15. Name: "hd_errors_total",
  16. Help: "Number of hard-disk errors.",
  17. },
  18. []string{"device"},
  19. )
  20. )
  21. func init() {
  22. // Metrics have to be registered to be exposed:
  23. prometheus.MustRegister(cpuTemp)
  24. prometheus.MustRegister(hdFailures)
  25. }
  26. func main() {
  27. cpuTemp.Set(65.3)
  28. hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
  29. // The Handler function provides a default handler to expose metrics
  30. // via an HTTP server. "/metrics" is the usual endpoint for that.
  31. http.Handle("/metrics", promhttp.Handler())
  32. log.Fatal(http.ListenAndServe(":8888", nil))
  33. }

其中创建了一个gauge和CounterVec对象,并分别指定了metric name和help信息,其中CounterVec是用来管理相同metric下不同label的一组Counter,同理存在GaugeVec,可以看到上面代码中声明了一个lable的key为“device”,使用的时候也需要指定一个lable: hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()

变量定义后进行注册,最后再开启一个http服务的8888端口就完成了整个程序,pro采集数据是通过定期请求该服务http端口来实现的。

启动程序之后可以在web浏览器里输入http://localhost:8888/metrics 就可以得到client暴露的数据,其中有片段显示为:

  1. # HELP cpu_temperature_celsius Current temperature of the CPU.
  2. # TYPE cpu_temperature_celsius gauge
  3. cpu_temperature_celsius 65.3
  4. # HELP hd_errors_total Number of hard-disk errors.
  5. # TYPE hd_errors_total counter
  6. hd_errors_total{device="/dev/sda"} 1

上图就是示例程序所暴露出来的数据,并且可以看到counterVec是有label的,而单纯的gauage对象却不用lable标识,这就是基本数据类型和对应Vec版本的差别。此时再查看http://localhost:9090/graph 就会发现服务状态已经变为UP了。

上面的例子只是一个简单的demo,因为在prometheus.yml配置文件中我们指定采集服务器信息的时间间隔为60s,每隔60s pro会通过http请求一次自己暴露的数据,而在代码中我们只设置了一次gauge变量cupTemp的值,如果在60s的采样间隔里将该值设置多次,前面的值就会被覆盖,只有pro采集数据那一刻的值能被看到,并且如果不再改变这个值,pro就始终能看到这个恒定的变量,除非用户显式通过Delete函数删除这个变量。

使用Counter,Gauage等这些结构比较简单,但是如果不再使用这些变量需要我们手动删,我们可以调用resetfunction来清除之前的metrics。

自定义Collector

更高阶的做法是使用Collector,go client Colletor只会在每次响应pro请求的时候才收集数据,并且需要每次显式传递变量的值,否则就不会再维持该变量,在pro也将看不到这个变量,Collector是一个接口,所有收集metrics数据的对象都需要实现这个接口,Counter和Gauage等不例外,它内部提供了两个函数,Collector用于收集用户数据,将收集好的数据传递给传入参数Channel就可,Descirbe函数用于描述这个Collector。当收集系统数据代价较大时,就可以自定义Collector收集的方式,优化流程,并且在某些情况下如果已经有了一个成熟的metrics,就不需要使用Counter,Gauage等这些数据结构,直接在Collector内部实现一个代理的功能即可,一些高阶的用法都可以通过自定义Collector实现。

  1. package main
  2. import (
  3. "github.com/prometheus/client_golang/prometheus"
  4. "github.com/prometheus/client_golang/prometheus/promhttp"
  5. "net/http"
  6. )
  7. type ClusterManager struct {
  8. Zone string
  9. OOMCountDesc *prometheus.Desc
  10. RAMUsageDesc *prometheus.Desc
  11. // ... many more fields
  12. }
  13. // Simulate prepare the data
  14. func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() (
  15. oomCountByHost map[string]int, ramUsageByHost map[string]float64,
  16. ) {
  17. // Just example fake data.
  18. oomCountByHost = map[string]int{
  19. "foo.example.org": 42,
  20. "bar.example.org": 2001,
  21. }
  22. ramUsageByHost = map[string]float64{
  23. "foo.example.org": 6.023e23,
  24. "bar.example.org": 3.14,
  25. }
  26. return
  27. }
  28. // Describe simply sends the two Descs in the struct to the channel.
  29. func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) {
  30. ch <- c.OOMCountDesc
  31. ch <- c.RAMUsageDesc
  32. }
  33. func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) {
  34. oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState()
  35. for host, oomCount := range oomCountByHost {
  36. ch <- prometheus.MustNewConstMetric(
  37. c.OOMCountDesc,
  38. prometheus.CounterValue,
  39. float64(oomCount),
  40. host,
  41. )
  42. }
  43. for host, ramUsage := range ramUsageByHost {
  44. ch <- prometheus.MustNewConstMetric(
  45. c.RAMUsageDesc,
  46. prometheus.GaugeValue,
  47. ramUsage,
  48. host,
  49. )
  50. }
  51. }
  52. // NewClusterManager creates the two Descs OOMCountDesc and RAMUsageDesc. Note
  53. // that the zone is set as a ConstLabel. (It's different in each instance of the
  54. // ClusterManager, but constant over the lifetime of an instance.) Then there is
  55. // a variable label "host", since we want to partition the collected metrics by
  56. // host. Since all Descs created in this way are consistent across instances,
  57. // with a guaranteed distinction by the "zone" label, we can register different
  58. // ClusterManager instances with the same registry.
  59. func NewClusterManager(zone string) *ClusterManager {
  60. return &ClusterManager{
  61. Zone: zone,
  62. OOMCountDesc: prometheus.NewDesc(
  63. "clustermanager_oom_crashes_total",
  64. "Number of OOM crashes.",
  65. []string{"host"},
  66. prometheus.Labels{"zone": zone},
  67. ),
  68. RAMUsageDesc: prometheus.NewDesc(
  69. "clustermanager_ram_usage_bytes",
  70. "RAM usage as reported to the cluster manager.",
  71. []string{"host"},
  72. prometheus.Labels{"zone": zone},
  73. ),
  74. }
  75. }
  76. func main() {
  77. workerDB := NewClusterManager("db")
  78. workerCA := NewClusterManager("ca")
  79. // Since we are dealing with custom Collector implementations, it might
  80. // be a good idea to try it out with a pedantic registry.
  81. reg := prometheus.NewPedanticRegistry()
  82. reg.MustRegister(workerDB)
  83. reg.MustRegister(workerCA)
  84. http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
  85. http.ListenAndServe(":8888", nil)
  86. }

此时就可以去http://localhost:8888/metrics 看到传递过去的数据了。示例中定义了两个matrics, host和zone分别是其label。 其实pro client内部提供了几个Collecto供我们使用,我们可以参考他的实现,在源码包中可以找到go_collector.go, process_collecor.go, expvar_collector这三个文件的Collecor实现。

--update at 2019.2.26---

强烈建议将pro官网Best practice 章节阅读一下,毕竟学会使用工具之后,我们需要明白作为一个系统,我们应该暴露哪些metriscs,该使用哪些变量最好....

prometheus client_golang使用的更多相关文章

  1. 从零开始搭建Prometheus自动监控报警系统

    从零搭建Prometheus监控报警系统 什么是Prometheus? Prometheus是由SoundCloud开发的开源监控报警系统和时序列数据库(TSDB).Prometheus使用Go语言开 ...

  2. Go语言开发Prometheus Exporter示例

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

  3. 使用golang编写prometheus metrics exporter

    metrcis输出 collector.go package main import ( "github.com/prometheus/client_golang/prometheus&qu ...

  4. golang prometheus包的使用

    prometheus包提供了用于实现监控代码的metric原型和用于注册metric的registry.子包(promhttp)允许通过HTTP来暴露注册的metric或将注册的metric推送到Pu ...

  5. hadoop_exporter+prometheus

    1.准备工作 安装go.glibe(需要连google服务器,咋连的,我就不写了,因为尝试了各种办法,都失败了,很伤心) 2.下载hadoop_exporter cd /usr/local/prom/ ...

  6. Prometheus笔记(二)监控go项目实时给grafana展示

    欢迎加入go语言学习交流群 636728449 Prometheus笔记(二)监控go项目实时给grafana展示 Prometheus笔记(一)metric type 文章目录 一.promethe ...

  7. Prometheus笔记(一)metric type

    欢迎加入go语言学习交流群 636728449 Prometheus笔记(二)监控go项目实时给grafana展示 Prometheus笔记(一)metric type 文章目录 Prometheus ...

  8. 如何利用Prometheus监控你的应用

    Prometheus作为一套完整的开源监控接近方案,因为其诸多强大的特性以及生态的开放性,俨然已经成为了监控领域的事实标准并在全球范围内得到了广泛的部署应用.那么应该如何利用Prometheus对我们 ...

  9. 从零搭建Prometheus监控报警系统

    什么是Prometheus? Prometheus是由SoundCloud开发的开源监控报警系统和时序列数据库(TSDB).Prometheus使用Go语言开发,是Google BorgMon监控系统 ...

随机推荐

  1. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  2. Android 6.0 双向通话自动录音

    package com.example.hgx.phoneinfo60.Recording; import android.content.BroadcastReceiver; import andr ...

  3. LINUX 笔记-条件测试

    格式:test condition 文件测试状态 -d 目录 -s 文件长度大于0,非空 -f 正规文件 -w 可写 -l 符号链接 -u 文件有suid位设置 -r 可读 -x 可执行 字符串测试 ...

  4. HTTP 简要

    HTTP协议就是客户端和服务器交互的一种通迅的格式. 当在浏览器中点击这个链接的时候,浏览器会向服务器发送一段文本,告诉服务器请求打开的是哪一个网页.服务器收到请求后,就返回一段文本给浏览器,浏览器会 ...

  5. 217. Contains Duplicate (leetcode)

    Given an array of integers, find if the array contains any duplicates. Your function should return t ...

  6. typeof、constructor和instance

    在JavaScript中,我们经常使用typeof来判断一个变量的类型,使用格式为:typeof(data)或typeof data.typeof返回的数据类型有六种:number.string.bo ...

  7. LeetCode 604. Design Compressed String Iterator (设计压缩字符迭代器)$

    Design and implement a data structure for a compressed string iterator. It should support the follow ...

  8. R学习笔记 第三篇:数据框

    数据框(data.frame)用于存储二维表(即关系表)的数据,每一列存储的数据类型必须相同,不同的数据列的数据类型可以相同,也可以不同,但是,每列的长度必须相同.数据框的每列可以有唯一的命名,在已创 ...

  9. ios 类的内部结构

    1. Class 和 Object 的定义 /// An opaque type that represents an Objective-C class. typedef struct objc_c ...

  10. 不借助vue-cli,自行构建一个vue项目

    前言: 对于刚刚接触vue的同学来说,直接用官方的构建工具vue-cli来生成一个项目结构会存在一些疑惑,比如:   .vue组件 为什么可以写成三段式(tempalte.script.style)? ...