如何基于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中封装GObject模块进行图形化程序编程的教程
Apr 14 Python
python使用socket连接远程服务器的方法
Apr 29 Python
Python字典实现简单的三级菜单(实例讲解)
Jul 31 Python
Python实现字典按照value进行排序的方法分析
Dec 23 Python
python实现石头剪刀布小游戏
Jan 20 Python
Python Selenium 之数据驱动测试的实现
Aug 01 Python
python迭代器常见用法实例分析
Nov 22 Python
python3实现往mysql中插入datetime类型的数据
Mar 02 Python
Pycharm中import torch报错的快速解决方法
Mar 05 Python
Python 如何创建一个线程池
Jul 28 Python
python 中的命名空间,你真的了解吗?
Aug 19 Python
python3中TQDM库安装及使用详解
Nov 18 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
我的论坛源代码(一)
2006/10/09 PHP
MySQL GBK→UTF-8编码转换
2007/05/24 PHP
php 代码优化之经典示例
2011/03/24 PHP
php中动态调用函数的方法
2015/03/16 PHP
php计算整个目录大小的方法
2015/06/19 PHP
PHP的Json中文处理解决方案
2016/09/29 PHP
Thinkphp5框架ajax接口实现方法分析
2019/08/28 PHP
用js解决数字不能换行问题
2010/08/10 Javascript
jquery插件制作 图片走廊 gallery
2012/08/17 Javascript
form表单只提交数据而不进行页面跳转的解决方案
2013/09/18 Javascript
结合JQ1.9通过js正则判断各种浏览器版本的方法
2013/12/30 Javascript
简介JavaScript中substring()方法的使用
2015/06/06 Javascript
AngularJS 日期格式化详解
2015/12/23 Javascript
jQuery设置Cookie及删除Cookie实例分析
2016/04/15 Javascript
jQuery+Ajax+PHP弹出层异步登录效果(附源码下载)
2016/05/27 Javascript
概述VUE2.0不可忽视的很多变化
2016/09/25 Javascript
IOS中safari下的select下拉菜单文字过长不换行的解决方法
2016/09/26 Javascript
Vue.js仿Metronic高级表格(二)数据渲染
2017/04/19 Javascript
浅谈vue 二级路由嵌套和二级路由高亮问题
2020/08/06 Javascript
Python Socket编程入门教程
2014/07/11 Python
简单总结Python中序列与字典的相同和不同之处
2016/01/19 Python
python读取几个G的csv文件方法
2019/01/07 Python
python实现超市商品销售管理系统
2019/10/25 Python
Python list和str互转的实现示例
2020/11/16 Python
HTML5新增的表单元素和属性实例解析
2014/07/07 HTML / CSS
美国电子产品购物网站:BuyDig.com
2020/06/17 全球购物
新闻专业毕业生求职信
2014/08/08 职场文书
学生会竞选演讲稿怎么写
2014/08/26 职场文书
违反交通法规检讨书
2014/09/10 职场文书
党支部季度考核意见
2015/06/02 职场文书
医院保洁员管理制度
2015/08/05 职场文书
四群教育工作总结
2015/08/10 职场文书
2019年共青团工作条例最新版
2019/11/12 职场文书
MySQL中你可能忽略的COLLATION实例详解
2021/05/12 MySQL
SQL SERVER存储过程用法详解
2022/02/24 SQL Server
分享CSS盒子模型隐藏的几种方式
2022/02/28 HTML / CSS