如何基于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实现一个简单的项目监控
Mar 31 Python
Python实现的数据结构与算法之双端队列详解
Apr 22 Python
python实现一次创建多级目录的方法
May 15 Python
python 读写、创建 文件的方法(必看)
Sep 12 Python
用python制作游戏外挂
Jan 04 Python
python实现requests发送/上传多个文件的示例
Jun 04 Python
python实现剪切功能
Jan 23 Python
python程序运行进程、使用时间、剩余时间显示功能的实现代码
Jul 11 Python
python 3.6.7实现端口扫描器
Sep 04 Python
python实现程序重启和系统重启方式
Apr 16 Python
解决keras使用cov1D函数的输入问题
Jun 29 Python
人工智能深度学习OpenAI baselines的使用方法
May 20 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实现发送邮件的方法(基于简单邮件发送类)
2015/12/17 PHP
YII2.0之Activeform表单组件用法实例
2016/01/09 PHP
php封装的验证码工具类完整实例
2016/10/19 PHP
Laravel源码解析之路由的使用和示例详解
2018/09/27 PHP
jquery 入门教程 [翻译] 推荐
2009/08/17 Javascript
node.js 一个简单的页面输出实现代码
2012/03/07 Javascript
JavaScript高级程序设计(第3版)学习笔记12 js正则表达式
2012/10/11 Javascript
JS加jquery简单实现标签元素的显示或隐藏
2013/09/23 Javascript
node.js中的dns.getServers方法使用说明
2014/12/08 Javascript
JS实现可自定义大小,可双击关闭的弹出层效果
2015/10/16 Javascript
Bootstrap表单组件教程详解
2016/04/26 Javascript
jQuery查找和过滤_动力节点节点Java学院整理
2017/07/04 jQuery
vue + webpack如何绕过QQ音乐接口对host的验证详解
2018/07/01 Javascript
js键盘事件实现人物的行走
2020/01/17 Javascript
基于JavaScript实现十五拼图代码实例
2020/04/26 Javascript
[01:27]DOTA2电竞之夜 今夜共饮庆功酒
2014/08/02 DOTA
[02:06]2018完美世界全国高校联赛秋季赛开始报名(附彩蛋)
2018/09/03 DOTA
Python实现远程调用MetaSploit的方法
2014/08/22 Python
Python的Flask开发框架简单上手笔记
2015/11/16 Python
Django实现自定义404,500页面教程
2017/03/26 Python
python在ubuntu中的几种安装方法(小结)
2017/12/08 Python
Python+OpenCV实现图像融合的原理及代码
2018/12/03 Python
利用pytorch实现对CIFAR-10数据集的分类
2020/01/14 Python
opencv python在视屏上截图功能的实现
2020/03/05 Python
HTML5在手机端实现视频全屏展示方法
2020/11/23 HTML / CSS
如何配置、使用和清除Smarty缓存
2015/12/23 面试题
经验丰富大学生村干部自我鉴定
2014/01/22 职场文书
学生党支部先进事迹
2014/02/04 职场文书
大学生新学期计划书
2014/04/28 职场文书
公司节能减排方案
2014/05/16 职场文书
群众路线问题查摆对照检查材料
2014/10/04 职场文书
工作态度不端正检讨书
2014/10/04 职场文书
婚前财产协议书范本
2014/10/19 职场文书
2015年计生工作总结范文
2015/04/24 职场文书
pytorch实现ResNet结构的实例代码
2021/05/17 Python
升级 Win11 还是坚守 Win10?微软 Win11 新系统缺失功能大盘点
2022/04/05 数码科技