如何基于Python和Flask编写Prometheus监控


Posted in Python onNovember 25, 2020

介绍

Prometheus 的基本原理是通过 HTTP 周期性抓取被监控组件的状态。

任意组件只要提供对应的 HTTP 接口并且符合 Prometheus 定义的数据格式,就可以接入 Prometheus 监控。

Prometheus Server 负责定时在目标上抓取 metrics(指标)数据并保存到本地存储。它采用了一种 Pull(拉)的方式获取数据,不仅降低客户端的复杂度,客户端只需要采集数据,无需了解服务端情况,也让服务端可以更加方便地水平扩展。

如果监控数据达到告警阈值,Prometheus Server 会通过 HTTP 将告警发送到告警模块 alertmanger,通过告警的抑制后触发邮件或者 Webhook。Prometheus 支持 PromQL 提供多维度数据模型和灵活的查询,通过监控指标关联多个 tag 的方式,将监控数据进行任意维度的组合以及聚合。

在python中实现服务器端,对外提供接口。在Prometheus中配置请求网址,Prometheus会定期向该网址发起申请获取你想要返回的数据。

另外Prometheus提供4种类型Metrics:Counter, Gauge, Summary和Histogram。

准备

pip install flask
pip install prometheus_client

Counter

Counter可以增长,并且在程序重启的时候会被重设为0,常被用于访问量,任务个数,总处理时间,错误个数等只增不减的指标。

定义它需要2个参数,第一个是metrics的名字,第二个是metrics的描述信息:

c = Counter('c1', 'A counter')

counter只能增加,所以只有一个方法:

def inc(self, amount=1):
    '''Increment counter by the given amount.'''
    if amount < 0:
      raise ValueError('Counters can only be incremented by non-negative amounts.')
    self._value.inc(amount)

测试示例:

import prometheus_client
from prometheus_client import Counter
from prometheus_client.core import CollectorRegistry

from flask import Response, Flask

app = Flask(__name__)
requests_total = Counter('c1','A counter')

@app.route("/api/metrics/count/")
def requests_count():
 requests_total.inc(1)
 # requests_total.inc(2)
 return Response(prometheus_client.generate_latest(requests_total),mimetype="text/plain")


if __name__ == "__main__":
 app.run(host="127.0.0.1",port=8081)

访问http://127.0.0.1:8081/api/metrics/count/:

# HELP c1_total A counter
# TYPE c1_total counter
c1_total 1.0
# HELP c1_created A counter
# TYPE c1_created gauge
c1_created 1.6053265493727107e+09

HELP是c1的注释说明,创建Counter定义的。

TYPE是c1的类型说明。

c1_total为我们定义的指标输出:你会发现多了后缀_total,这是因为OpenMetrics与Prometheus文本格式之间的兼容性,OpenMetrics需要_total后缀。

gauge

gauge可增可减,可以任意设置。

比如可以设置当前的CPU温度,内存使用量,磁盘、网络流量等等。

定义和counter基本一样:

from prometheus_client import Gauge
g = Gauge('my_inprogress_requests', 'Description of gauge')
g.inc()   # Increment by 1
g.dec(10)  # Decrement by given value
g.set(4.2)  # Set to a given value

方法:

def inc(self, amount=1):
   '''Increment gauge by the given amount.'''
   self._value.inc(amount)

def dec(self, amount=1):
   '''Decrement gauge by the given amount.'''
   self._value.inc(-amount)

 def set(self, value):
   '''Set gauge to the given value.'''
   self._value.set(float(value))

测试示例:

import random
import prometheus_client
from prometheus_client import Gauge
from prometheus_client.core import CollectorRegistry
from flask import Response, Flask


app = Flask(__name__)
random_value = Gauge("g1", 'A gauge')
@app.route("/api/metrics/gauge/")
def r_value():
  random_value.set(random.randint(0, 10))
  return Response(prometheus_client.generate_latest(random_value),
          mimetype="text/plain")

if __name__ == "__main__":
 app.run(host="127.0.0.1",port=8081)

访问http://127.0.0.1:8081/api/metrics/gauge/

# HELP g1 A gauge
# TYPE g1 gauge
g1 5.0

LABELS的用法

使用labels来区分metric的特征,一个指标可以有其中一个label,也可以有多个label。

from prometheus_client import Counter
c = Counter('requests_total', 'HTTP requests total', ['method', 'clientip'])
c.labels('get', '127.0.0.1').inc()
c.labels('post', '192.168.0.1').inc(3)
c.labels(method="get", clientip="192.168.0.1").inc()
import random
import prometheus_client
from prometheus_client import Gauge
from flask import Response, Flask


app = Flask(__name__)
c = Gauge("c1", 'A counter',['method','clientip'])
@app.route("/api/metrics/counter/")
def r_value():
  c.labels(method='get',clientip='192.168.0.%d' % random.randint(1,10)).inc()
  return Response(prometheus_client.generate_latest(c),
          mimetype="text/plain")

if __name__ == "__main__":
 app.run(host="127.0.0.1",port=8081)

连续访问9次http://127.0.0.1:8081/api/metrics/counter/:

# HELP c1 A counter
# TYPE c1 gauge
c1{clientip="192.168.0.7",method="get"} 2.0
c1{clientip="192.168.0.1",method="get"} 1.0
c1{clientip="192.168.0.8",method="get"} 1.0
c1{clientip="192.168.0.5",method="get"} 2.0
c1{clientip="192.168.0.4",method="get"} 1.0
c1{clientip="192.168.0.10",method="get"} 1.0
c1{clientip="192.168.0.2",method="get"} 1.0

histogram

这种主要用来统计百分位的,什么是百分位?英文叫做quantiles。

比如你有100条访问请求的耗时时间,把它们从小到大排序,第90个时间是200ms,那么我们可以说90%的请求都小于200ms,这也叫做”90分位是200ms”,能够反映出服务的基本质量。当然,也许第91个时间是2000ms,这就没法说了。

实际情况是,我们每天访问量至少几个亿,不可能把所有访问数据都存起来,然后排序找到90分位的时间是多少。因此,类似这种问题都采用了一些估算的算法来处理,不需要把所有数据都存下来,这里面数学原理比较高端,我们就直接看看prometheus的用法好了。

首先定义histogram:

h = Histogram('hh', 'A histogram', buckets=(-5, 0, 5))

第一个是metrics的名字,第二个是描述,第三个是分桶设置,重点说一下buckets。

这里(-5,0,5)实际划分成了几种桶:(无穷小,-5],(-5,0],(0,5],(5,无穷大)。

如果我们喂给它一个-8:

h.observe(8)

那么metrics会这样输出:

# HELP hh A histogram
# TYPE hh histogram
hh_bucket{le="-5.0"} 0.0
hh_bucket{le="0.0"} 0.0
hh_bucket{le="5.0"} 0.0
hh_bucket{le="+Inf"} 1.0
hh_count 1.0
hh_sum 8.0

hh_sum记录了observe的总和,count记录了observe的次数,bucket就是各种桶了,le表示<=某值。

可见,值8<=无穷大,所以只有最后一个桶计数了1次(注意,桶只是计数,bucket作用相当于统计样本在不同区间的出现次数)。

bucket的划分需要我们根据数据的分布拍脑袋指定,合理的划分可以让promql估算百分位的时候更准确,我们使用histogram的时候只需要知道先分好桶,再不断的打点即可,最终百分位的计算可以基于histogram的原始数据完成。

测试示例:

import random
import prometheus_client
from prometheus_client import Histogram
from flask import Response, Flask
app = Flask(__name__)
h = Histogram("h1", 'A Histogram', buckets=(-5, 0, 5))
@app.route("/api/metrics/histogram/")
def r_value():
  h.observe(random.randint(-5, 5))
  return Response(prometheus_client.generate_latest(h),
          mimetype="text/plain")

if __name__ == "__main__":
 app.run(host="127.0.0.1",port=8081)

连续访问http://127.0.0.1:8081/api/metrics/histogram/:

# HELP h1 A Histogram
# TYPE h1 histogram
h1_bucket{le="-5.0"} 0.0
h1_bucket{le="0.0"} 5.0
h1_bucket{le="5.0"} 10.0
h1_bucket{le="+Inf"} 10.0
h1_count 10.0
# HELP h1_created A Histogram
# TYPE h1_created gauge
h1_created 1.6053319432993534e+09

summary

python客户端没有完整实现summary算法,这里不介绍。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
跟老齐学Python之??碌某?? target=
Sep 12 Python
星球大战与Python之间的那些事
Jan 07 Python
对比Python中__getattr__和 __getattribute__获取属性的用法
Jun 21 Python
python中numpy包使用教程之数组和相关操作详解
Jul 30 Python
Python3.6 Schedule模块定时任务(实例讲解)
Nov 09 Python
Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果示例
Jan 29 Python
Python logging模块用法示例
Aug 28 Python
Python实现的调用C语言函数功能简单实例
Mar 13 Python
浅谈Django中view对数据库的调用方法
Jul 18 Python
python 使用shutil复制图片的例子
Dec 13 Python
python实现梯度法 python最速下降法
Mar 24 Python
python缩进长度是否统一
Aug 02 Python
python3爬虫中多线程进行解锁操作实例
Nov 25 #Python
mac系统下安装pycharm、永久激活、中文汉化详细教程
Nov 24 #Python
python 基于wx实现音乐播放
Nov 24 #Python
Python WebSocket长连接心跳与短连接的示例
Nov 24 #Python
Python 利用Entrez库筛选下载PubMed文献摘要的示例
Nov 24 #Python
python实现企业微信定时发送文本消息的示例代码
Nov 24 #Python
python爬虫快速响应服务器的做法
Nov 24 #Python
You might like
php set_magic_quotes_runtime() 函数过时解决方法
2010/07/08 PHP
超级实用的7个PHP代码片段分享
2012/01/05 PHP
ThinkPHP5.0 图片上传生成缩略图实例代码说明
2018/06/20 PHP
Laravel 集成微信用户登录和绑定的实现
2019/12/27 PHP
巧用局部变量提升javascript性能
2014/02/24 Javascript
JavaScript实现彩虹文字效果的方法
2015/04/16 Javascript
js Canvas实现圆形时钟教程
2016/09/19 Javascript
Jquery给当前页或者跳转后页面的导航栏添加选中后样式的实例
2016/12/08 Javascript
Node.js用readline模块实现输入输出
2016/12/16 Javascript
浅谈js停止事件冒泡 阻止浏览器的默认行为(阻止超连接 #)
2017/02/08 Javascript
自带气泡提示的vue校验插件(vue-verify-pop)
2017/04/07 Javascript
Swiper实现轮播图效果
2017/07/03 Javascript
webpack项目轻松混用css module的方法
2018/06/12 Javascript
vue.js与后台数据交互的实例讲解
2018/08/08 Javascript
详解vue-router导航守卫
2019/01/19 Javascript
vue+koa2实现session、token登陆状态验证的示例
2019/08/30 Javascript
[05:06]TI4西雅图DOTA2前线报道 海涛密探LGD训练
2014/07/09 DOTA
Linux中Python 环境软件包安装步骤
2016/03/31 Python
sublime text 3配置使用python操作方法
2017/06/11 Python
python多维数组切片方法
2018/04/13 Python
python实现windows下文件备份脚本
2018/05/27 Python
Python http接口自动化测试框架实现方法示例
2018/12/06 Python
python判断字符串或者集合是否为空的实例
2019/01/23 Python
python 设置输出图像的像素大小方法
2019/07/04 Python
Python编程快速上手——PDF文件操作案例分析
2020/02/28 Python
Parts Express:音频、视频和扬声器的第一来源
2017/04/25 全球购物
Diptyque英国官方网站:源自法国的知名香氛品牌
2019/08/28 全球购物
在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern "C"
2014/08/09 面试题
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
机电工程学生自荐信范文
2013/12/07 职场文书
绩效工资分配方案
2014/01/18 职场文书
省级青年文明号申报材料
2014/05/23 职场文书
工作经验交流材料
2014/12/30 职场文书
前台岗位职责
2015/02/13 职场文书
自我检讨书怎么写
2015/05/07 职场文书
PyMongo 查询数据的实现
2021/06/28 Python