文章目录
- 关于监控服务器指标、CPU、内存、警报的一些解决方案
- `Prometheus` + `Grafana` 配置 `IRIS` / `Caché` 监控服务器
- `Prometheus`
- 简介
- 特点
- 架构图
- `Grafana`
- 简介
- 特点
- 配置流程
- 自定义`Prometheus`接口定义
- 配置 `Exporter` 监控服务器系统资源
- 简介
- 配置流程
- 使用 `Alertmanager`报警
- 简介
- 配置流程
- 基于`M`实现监控服务器,并用邮件报警
- 解析`Prometheus`数据接口信息
- 使用嵌入式`Python`方式获取系统`CPU`、内存等
- `python`模块之`psutil`详解
- `CPU`相关
- `Memory`内存相关
- `Disk`相关
- `Network`相关
- `Process`相关
- 通过嵌入式`Python`调用`psutil`库
- 使用邮件发送报警信息
- 使用`M`发送邮件发送消息
- 通过配置文件`csv`文件,来获取维护指标,进行邮件报警。
- 总结
- 思考
- 附
关于监控服务器指标、CPU、内存、警报的一些解决方案
本文章主要介绍以下几个章节内容:
Prometheus
+Grafana
配置IRIS
/Caché
监控服务器- 自定义
Prometheus
接口定义 - 配置Caché
监控服务 - 配置
Exporter
监控服务器系统资源 - 使用
Alertmanager
报警 - 基于
M
实现监控服务器,并用邮件报警- 解析
Prometheus
数据接口信息 - 使用嵌入式
Python
方式获取系统CPU
、内存等 - 使用邮件发送报警信息
- 解析
Prometheus
+ Grafana
配置 IRIS
/ Caché
监控服务器
首先我们介绍一下如何用IRIS结合Prometheus
+ Grafana
的使用,在介绍如何配置之前我们先了简单了解一下工具。
Prometheus
简介
Prometheus
是一个最初在SoundCloud
上构建的开源系统监视和警报工具包 。 自2012
年成立以来,许多公司和组织都采用了Prometheus
,该项目拥有一个非常活跃的开发人员和用户社区。它现在是一个独立的开源项目,可以独立于任何公司进行维护。为了强调这一点,并澄清项目的治理结构,Prometheus
于2016
年加入 云计算本地计算基金会,作为继Kubernetes
之后的第二个托管项目。
特点
- 具有由度量名称和键/值对标识的时间序列数据的多维数据模型。
- 一个灵活的查询语言 来利用这一维度。
- 不依赖分布式存储,单个服务器节点是自治的。
- 时间序列收集通过
HTTP
上的拉模型发生。 - 通过中间网关支持推送时间序列。
- 通过服务发现或静态配置发现目标。
- 多种图形和仪表板支持模式。
架构图
Grafana
简介
Grafana
是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知。
特点
- 展示方式:快速灵活的客户端图表,面板插件有许多不同方式的可视化指标和日志,官方库中具有丰富的仪表盘插件,比如热图、折线图、图表等多种展示方式。
- 支持多种数据源:
Graphite
,InfluxDB
,OpenTSDB
,Prometheus
,Elasticsearch
,CloudWatch
和KairosDB
等。 - 通知提醒:以可视方式定义最重要指标的警报规则,
Grafana
将不断计算并发送通知,在数据达到阈值时通过Slack
、PagerDuty
等获得通知。 - 混合展示:在同一图表中混合使用不同的数据源,可以基于每个查询指定数据源,甚至自定义数据源。
- 注释:使用来自不同数据源的丰富事件注释图表,将鼠标悬停在事件上会显示完整的事件元数据和标记。
- 过滤器:
Ad-hoc
过滤器允许动态创建新的键/值过滤器,这些过滤器会自动应用于使用该数据源的所有查询。
配置流程
- 下载
Prometheus
,选择操作系统,下载对应安装包,以windows
为例。
- 下载地址:https://prometheus.io/download/
- 下载完成后,直接解压在所需目录即可。解压后进入目录直接运行
premetheus.exe
,prometheus
默认端口为9090
。
- 在浏览器输入地址、即可查看监控页面。
- 地址 - http://localhost:9090/
- 配置监控服务器信息,编辑
prometheus-2.27.1.windows-amd64/prometheus.yml
文件。在scrape_configs:
输入以下代码:- 这里以我的私有服务器地址为例:https://8.142.29.250:2443/api/monitor/metrics。
输入以下代码:
- job_name: "250-IRIS"
metrics_path: /api/monitor/metrics
scheme: https
tls_config:
insecure_skip_verify: true
static_configs:
- targets: ['8.142.29.250:2443']
其中:
job_name
- 服务器名称。metrics_path
- 监控服务器地址路径。scheme
- 网络协议。tls_config - insecure_skip_verify
- 过滤完成验证。static_configs
-targets
-IP
端口号。
prometheus.yml
文件修改完成后,重新启动prometheus.exe
程序,进入到Status
->Targets
下,即可看到添加的监控服务器信息;
- 监控具体指标,点击
Graph
->勾选UseLocalime
->输入监控指标iris_cpu_usage
->点击Excute
->选择监控时长范围30min
。
iris_cpu_usage
- 代表CPU
使用率。
说明:这里监控指标为服务器接口里的具体指标,输入接口地址具体查看。
- 由于
Prometheus
展示的信息相对简单,图表类型也比较少,下面介绍如何使用Grafana
展示数据。输入Grafana
地址,下载对应版本。
- 下载地址:https://grafana.com/grafana/download?
- 下载后直接运行安装,安装完成后进入
grafana/bin
目录下,双击运行garafana-server.exe
。garafana
默认端口为3030
- 访问地址:http://localhost:3000/
- 默认用户名密码
admin/admin
- 在
grafana
里添加数据源Data Source
。
- 选择
Prometheus
。
- 填写数据源信息,并保存。
Name
-Prometheus
Url
-http://localhost:9090
- 点击
Save&test
显示测试成功。
- 创建模版,点击
Dashboards
->点击Browse
->输入模版名称->New
->NewDashboard
。
- 点击
Add a new panel
。
- 选择数据源
Data source
,指标iris_cpu_usage
->Run query
。
Title
- 修改仪表盘名称。
- 输入仪表盘名称,点击保存即可。
- 最终效果,依次类推建立多个监控指标。
iris_csp_sessions
- 会话使用数iris_cpu_usage
-cpu
使用率iris_system_alerts
- 系统警报数iris_process_count
- 进程数量iris_glo_ref_per_sec
-Global
每秒引用数量
自定义Prometheus
接口定义
那么我们是否可以自定义Prometheus
接口呢?
答案肯定是可以的,整好 Caché
没有自带的监控服务,所以我们给Caché
自定义个一个监控服务接口。
- 首先我们需要创建一个Rest接口类
M.Metrics
。
Class M.Metrics Extends %CSP.REST
{
}
- 在
Portal
中配置Rest
接口,系统->安全管理->Web
应用程序->编辑Web
引用程序。
- 名称 -
/api/metrics
- 命名空间 - 选择
Rest
接口类所在命名空间 - 分派类 -
M.Metrics
- 编写
Rest
接口逻辑,下面展示主要业务逻辑代码。
- 基础仪表盘接口
ClassMethod GetDashboardMetrics(ByRef array As %DynamicArray) As %Status
{
s dashboard = ##class(SYS.Stats.Dashboard).Sample()
s properties = ##class(%Dictionary.ClassDefinition).%OpenId(dashboard.%ClassName(1)).Properties
for i = 1 : 1 : properties.Count() {
s property = properties.GetAt(i)
s propertyName = property.Name
s propertyValue = $property(dashboard, propertyName)
if ((propertyValue '= "") && ('$match(propertyValue, ".*[-A-Za-z ]+.*"))) {
s metricsName = ..CamelCase2Underscore(propertyName)
s metricsValue = propertyValue
s obj = {}
s obj.key = metricsName
s obj.val = metricsValue
d array.%Push(obj)
}
}
q $$$OK
}
- 系统使用情况
d ##class(SYS.Metrics).GetMainMetrics("", 0, .pValues, .pStatus, .pMsg)
- 系统监视器统计
d ##class(SYS.Metrics).GetGlobalStatistics("", 0, .pValues, .pStatus, .pMsg)
ECP
数据统计
d ##class(SYS.Metrics).GetECPStatistics("", 0, .pValues, .pStatus, .pMsg)
- 磁盘和缓冲区数据统计
d ##class(SYS.Metrics).GetStatistics("",0,.pValues,.pStatus,.pMsg)
- 许可数据统计
ClassMethod GetLicenseStatistics(ByRef array As %DynamicArray)
{
s licenseUsed = ##class(%SYSTEM.License).LUConsumed()
s obj = {}
s obj.key = ..#PREFIX _ "_license_used"
s obj.val = licenseUsed
d array.%Push(obj)
s licenseAvailable=##class(%SYSTEM.License).LUAvailable()
s obj = {}
s obj.key = ..#PREFIX _ "_license_avail"
s obj.val = licenseAvailable
d array.%Push(obj)
s licenseTotal=##class(%SYSTEM.License).GetUserLimit()
s obj = {}
s obj.key = ..#PREFIX _ "_license_total"
s obj.val = licenseTotal
d array.%Push(obj)
s obj = {}
s obj.key =..#PREFIX _ "_license_load"
s obj.val = $fn((licenseUsed / (licenseAvailable + licenseUsed)), "N", "4")
d array.%Push(obj)
}
- 开放性事务数据统计
ClassMethod GetOpenTransactionStatistics(ByRef array As %DynamicArray)
{
if ($zv [ "Cache") {
s index = "^CacheTemp.SysMetrics"
} else {
s index = "^IRIS.Temp.SysMetrics"
}
if (@index@("Transactions")) = "OK" {
s val = 1
} else {
s val = 0
}
s obj = {}
s obj.key = ..#PREFIX _ "_open_transaction"
s obj.val = val
d array.%Push(obj)
}
- 数据库情况
ClassMethod GetDatabaseStatistics(ByRef array As %DynamicArray)
{
s statement = ##class(%SQL.Statement).%New()
s sc = statement.%PrepareClassQuery("%SYS.DatabaseQuery", "FreeSpace")
s rs = statement.%Execute()
while (rs.%Next()) {
s databaseName = rs.%Get("DatabaseName")
s freeRate = rs.%GetData(7)
s availableNum = rs.%Get("AvailableNum")
s diskFreeSpaceNum = rs.%Get("DiskFreeSpaceNum")
s obj = {}
s obj.key =..#PREFIX _ "_database_free"
s obj.val = (100 - freeRate) / 100
s tag = {}
s tag.id = databaseName
s obj.tag = tag
d array.%Push(obj)
}
}
- 把所有数据组装到一起。
ClassMethod GetMetrics()
{
s array = []
#; 基础仪表盘
d ..GetDashboardMetrics(.array)
#; 系统使用情况
d ##class(SYS.Metrics).GetMainMetrics("", 0, .pValues, .pStatus, .pMsg)
d ..ParseJson(.pValues, .array)
#; 系统监视器统计
d ##class(SYS.Metrics).GetGlobalStatistics("", 0, .pValues, .pStatus, .pMsg)
d ..ParseJson(.pValues, .array)
#; ECP 数据统计
d ##class(SYS.Metrics).GetECPStatistics("", 0, .pValues, .pStatus, .pMsg)
d ..ParseJson(.pValues, .array)
#; 磁盘和缓冲区数据统计
d ##class(SYS.Metrics).GetStatistics("",0,.pValues,.pStatus,.pMsg)
d ..ParseJson(.pValues, .array)
#; 许可数据统计
d ..GetLicenseStatistics(.array)
#; 开放性事务数据统计
d ..GetOpenTransactionStatistics(.array)
#; 数据库情况
d ..GetDatabaseStatistics(.array)
q array
}
- 将数据解析成
Prometheus
数据格式。
prometheus
将所有数据保存为timeseries data
,用metric name
和label
区分,label
是在metric name
上的更细维度的划分,其中的每一个实例是由一个float64
和timestamp
组成,只不过timestamp
是隐式加上去的,#HELP
代表指标的注释信息,#TYPE
用于定义样本的类型注释信息。如下示例:
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
metrics name
- 为go_gc_duration_seconds
label
- 为quantile="0"
float64
- 为0
ClassMethod Metrics2Prometheus() As %Status
{
s %response.ContentType = "text/plain;version=0.0.4;charset=utf-8"
s array = ..GetMetrics()
s iter = array.%GetIterator()
while iter.%GetNext(.key, .json) {
s str = json.key
if (json.%IsDefined("tag")) {
s tag = json.tag
s tagIter = tag.%GetIterator()
s str = str _ "{"
while tagIter.%GetNext(.tagKey, .tagVal) {
s str = str _ tagKey _ "=" _ """" _ tagVal _ """" _ ","
}
s str = $e(str, 1, * - 1) _ "}"
}
w str _" "_ json.val _ $c(10)
}
q $$$OK
}
- 配置路由
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/prometheus" Method="GET" Call="Metrics2Prometheus" />
<Route Url="/json" Method="GET" Call="Metrics2Json" />
</Routes>
}
- 在界面输入自定义配置的
Prometheus
接口地址。
prometheus
格式地址接口:http://localhost:57772/api/metrics/prometheus
Json
格式地址接口:http://localhost:57772/api/metrics/json
- 在
prometheus.yml
配置文件中添加自定义的接口。代码如下:
- job_name: "localhost-Cache"
metrics_path: /api/metrics/prometheus
static_configs:
- targets: ['localhost:57772']
- 重新启动
prometheus
,查看配置界面。这样我们就可以给Caché
配置一个自定义的接口拉。
配置 Exporter
监控服务器系统资源
- 通过服务器配置
Exporter
监控服务器的系统信息,例如内存使用情况,CPU
使用情况。
简介
为
Prometheus
提供监控数据源的应用都可以被成为Exporter
,比如Node Exporter
则用来提供节点相关的资源使用状况,而Prometheus
从这些不同的Exporter
中获取监控数据,然后可以在诸如Grafana
这样的可视化工具中进行结果的显示。广义上讲所有可以向
Prometheus
提供监控样本数据的程序都可以被称为一个Exporter
。而Exporter
的一个实例称为target
,如下所示,Prometheus
通过轮询的方式定期从这些target
中获取样本数据。
配置流程
- 下载
windows_exporter-0.21.0-amd64.msi
文件后,直接双击运行;
- 下载地址:https://github.com/prometheus-community/windows_exporter/releases
windows_exporter
默认端口号为9182,运行后浏览器访问地址, 出现以下信息,说明安装成功。
- 地址:http://localhost:9182/metrics
- 将
Exporter
接口添加到Prometheus
中。
- job_name: "localhost-Exporter"
metrics_path: /metrics
static_configs:
- targets: ['localhost:9182']
使用 Alertmanager
报警
简介
Alertmanager
是一个独立的告警模块,接收Prometheus
等客户端发来的警报,之后通过分组、删除重复等处理,并将它们通过路由发送给正确的接收器。
Prometheus
的报警分为两部分:
Prometheus
服务器中的警报规则向警报管理器(Alertmanager
)发送警报。- 警报管理器负责管理这些警报,包括告警信息进行去重,降噪,分组等,并通过丰富的告警通知渠道,如电子邮件、微信、钉钉、
Slack
等常用沟通工具发出通知。
配置流程
- 下载
Prometheus
,选择操作系统,下载对应安装包,下载完毕后并挤压,以windows
为例。
- 下载地址:https://github.com/prometheus/alertmanager/releases
- 双击
alertmanager.exe
,启动alertmanager
服务,浏览器访问端口地址可以看到默认提供的UI
页面。
- 端口 -
9093
- 地址 -
http://localhost:9093/#/alerts
AlertManager
默认配置文件为alertmanager.yml
,打开配置文件,配置使用Email
方式通知报警信息,这里以QQ
邮箱为例,配置信息如下:
global
- 全局配置,包括报警解决后的超时时间、SMTP
相关配置、各种渠道通知的API
地址等等。route
- 用来设置报警的分发策略,它是一个树状结构,按照深度优先从左向右的顺序进行匹配。receivers
- 配置告警消息接受者信息,例如常用的email
、wechat
、slack
、webhook
等消息通知方式。inhibit_rules
- 抑制规则配置,当存在与另一组匹配的警报时,抑制规则将禁用与一组匹配的警报(目标)。
global:
resolve_timeout: 5m
smtp_from: 'xxxx@qq.com'
smtp_smarthost: 'smtp.qq.com:465'
smtp_auth_username: 'xxxx@qq.com'
smtp_auth_password: 'xxxxxxxxxxxxxx'
smtp_require_tls: false
smtp_hello: 'qq.com'
route:
group_by: ['alertname']
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: 'xxxxx@mediway.cn'
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
- 接下来,需要在
Prometheus
配置AlertManager
服务地址以及告警规则,新建报警规则文件alert_rules.yml
,,放在prometheus.yml
同级路径下,配置如下:
name
- 规则名称,可自己定义。alert
- 报警名称,当触发报警时,会作为邮件标题显示。expr
- 报警规则,为PromQL
表达式验证特定节点,如上述配置中up{job="250-IRIS"}
为验证job="250-IRIS"
是否还活着,如果等于0
则启动报警。for
- 表示报警状态为Pending
后等待15s
变成Firing
状态,一旦变成Firing
状态则将报警发送到AlertManager
。summary
- 报警信息描述。
groups:
- name: "服务器报警"
rules:
- alert: yx服务器报警
expr: up{job="250-IRIS"} == 0
for: 15s
labels:
status: warning
annotations:
summary: "服务器{{ $labels.instance }} 挂了"
description: "姚鑫服务器挂了.请立即查看!"
- alert: 本地Cache报警
expr: up{job="localhost-Cache"} == 0
for: 15s
labels:
status: warning
annotations:
summary: "服务器{{ $labels.instance }} 挂了"
description: "本地Cache服务器挂了.请立即查看!"
- alert: 本地IRIS报警
expr: up{job="localhost-IRIS"} == 0
for: 15s
labels:
status: warning
annotations:
summary: "服务器{{ $labels.instance }} 挂了"
description: "本地IRIS服务器挂了.请立即查看!"
- name: test-rules
rules:
- alert: "内存报警"
expr: 100 - ((node_memory_MemAvailable_bytes * 100) / node_memory_MemTotal_bytes) > 3
for: 15s
labels:
status: warning
annotations:
summary: "服务器:{{$labels.instance}}内存使用率超过3%了"
description: "内存使用率: {{ $value }}"
value: "{{ $value }}"
- 配置好报警规则文件后,需要修改
prometheus.yml
配置文件,添加rules
规则文件:
alerting:
alertmanagers:
- static_configs:
- targets:
- 'localhost:9093'
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "alert_rules.yml"
# - "second_rules.yml"
- 配置完毕后打,浏览器访问
prometheus
主页,进入Alerts
和Rules
界面,可查看报警规则信息:
- 地址:http://localhost:9090/alerts?search=
- 测试报警功能,将本地
Caché
关闭,停库。
- 观察页面本地
Cache
警报状态。
Prometheus Alert
告警状态有三种:Inactive
、Pending
、Firing
。
Inactive
- 非活动状态,表示正在监控,但是还未有任何警报触发。Pending
- 表示这个警报将被触发。由于警报可以被分组、压抑/抑制或静默/静音,所以等待验证,一旦所有的验证都通过,则将转到Firing
状态。Firing
- 将警报发送到AlertManager
,将按照配置将警报的发送给所有接收者。一旦警报解除,则将状态转到Inactive
,如此循环。
首先发现警报变为Pending
状态。
过了15
秒后变为Firing
状态
- 触发报警,根据定义的报警规则,如果有触发告警的条件,自动向配置的接收邮箱中发送邮件,如下:
基于M
实现监控服务器,并用邮件报警
解析Prometheus
数据接口信息
- 获取接口信息
ClassMethod GetMetrics(url) As %Status
{
#; 初始化request对象
#dim request as %Net.HttpRequest= ##class(%Net.HttpRequest).%New()
#; 根据地址解析是否是https调用SSL配置
d ##class(%Net.URLParser).Parse(url, .components)
s scheme = components("scheme")
if (scheme = "https") {
s request.Https = 1
if ($zv [ "IRIS") {
s request.SSLConfiguration = "ISC.FeatureTracker.SSL.Config"
} else {
s request.SSLConfiguration = "WebTerminalSSL"
}
s request.SSLCheckServerIdentity = 0
}
#; 请求地址并返回流数据
s sc = request.Get(url)
$$$ThrowOnError(sc)
s response = request.HttpResponse
s stream = response.Data
q stream
}
- 将接口信息转为
Json
格式
ClassMethod Metrics2Json(stream) As %Status
{
s array = []
while (stream.AtEnd = 0) {
#; 读取每行信息
s metrics = stream.ReadLine()
#; 过滤注释描述信息
continue:(metrics [ "# HELP")
continue:(metrics [ "# TYPE")
#; 指标字符串
s str = $p(metrics, " " ,1)
#; 指标值
s value = $p(metrics, " " ,2)
#; 指标字名称
s metricsName = $p(str, "{", 1)
#; 指标字label信息
s label = $e($p(str, "{", 2), 1, * - 1)
s obj = {}
s obj.key = metricsName
s obj.val = value
#; 如果label不为空将label信息转为JSON
if (label '= "") {
s tag = {}
s len = $l(label, ",")
for i = 1 : 1 : len{
s item = $p(label, """," ,i)
s key = $p(item, "=", 1)
s val = $e($p(item, "=", 2), 2, *-1)
d tag.%Set(key, val)
}
s obj.tag = tag
}
d array.%Push(obj)
}
q array
}
- 调用方法。
/// w ##class(M.ParseMetrics).Main("https://localhost:2443/api/monitor/metrics").%ToJSON()
/// w ##class(M.ParseMetrics).Main("http://localhost:9182/metrics").%ToJSON()
/// w ##class(M.ParseMetrics).Main("http://localhost:57772/api/metrics/prometheus").%ToJSON()
ClassMethod Main(url) As %Status
{
#; 获取指标接口信息
s stream = ..GetMetrics(url)
#; 将接口信息转为JSON格式
s json = ..Metrics2Json(stream)
q json
}
- 调用自定义
Caché
接口
IMP>w ##class(M.ParseMetrics).Main("http://localhost:57772/api/metrics/prometheus").%ToJSON()
[{"key":"yx_application_errors","val":"0"},{"key":"yx_csp_sessions","val":"1"},{"key":"yx_cache_efficiency","val":"235.85"},{"key":"yx_disk_reads","val":"3316"},{"key":"yx_disk_writes","val":"1881"},{"key":"yx_ecp_app_srv_rate","val":"0"},{"key":"yx_ecp_data_srv_rate","val":"0"},{"key":"yx_glo_refs","val":"1225714"},{"key":"yx_glo_refs_per_sec","val":"439.00"},{"key":"yx_glo_sets","val":"108449"},{"key":"yx_journal_entries","val":"17178"},
...
{"key":"yx_database_free","val":".8988","tag":{"id":"DHC-EPRMETADATA"}},{"key":"yx_database_free","val":".45","tag":{"id":"DHC-EPRQUALITYDATA"}},{"key":"yx_database_free","val":".71","tag":{"id":"DHC-EPRRBACINST"}},{"key":"yx_database_free","val":".74","tag":{"id":"DHC-EPRRBACMETA"}},{"key":"yx_database_free","val":".78","tag":{"id":"DHC-HEIS"}},{"key":"yx_database_free","val":".48","tag":{"id":"DHC-HL7"}},{"key":"yx_database_free","val":".9936","tag":{"id":"DHC-HR"}},{"key":"yx_database_free","val":".0345","tag":{"id":"DHC-LISDATA"}},{"key":"yx_database_free","val":".9328","tag":{"id":"DHC-LISSRC"}},{"key":"yx_database_free","val":".9884","tag":{"id":"DHC-LOGS"}},{"key":"yx_database_free","val":".9098","tag":{"id":"DHC-MEDSRC"}},{"key":"yx_database_free","val":".8996","tag":{"id":"DHC-MRQDATA"}},{"key":"yx_database_free","val":".9871","tag":{"id":"DHC-MRQSRC"}},{"key":"yx_database_free","val":".1","tag":{"id":"DHC-MSG"}},{"key":"yx_database_free","val":".0481","tag":{"id":"DHC-ORDDATA"}},{"key":"yx_database_free","val":".0417","tag":{"id":"DHC-ORDINDEX"}},{"key":"yx_database_free","val":".8782","tag":{"id":"PACS"}},{"key":"yx_database_free","val":".6","tag":{"id":"DHC-PISDATA"}},{"key":"yx_database_free","val":".831","tag":{"id":"DHC-PISSRC"}},{"key":"yx_database_free","val":".9548","tag":{"id":"DHC-RIS"}},{"key":"yx_database_free","val":".9615","tag":{"id":"DHC-SYS"}},{"key":"yx_database_free","val":".9218","tag":{"id":"DHC-TEMP"}},{"key":"yx_database_free","val":".8096","tag":{"id":"DHC-DWR"}},{"key":"yx_database_free","val":".039","tag":{"id":"DHC-WORKLOAD"}},{"key":"yx_database_free","val":".1797","tag":{"id":"IMP"}}]
- 调用自定义
IRIS
接口
IMP>w ##class(M.ParseMetrics).Main("https://localhost:2443/api/monitor/metrics").%ToJSON()
[{"key":"iris_cpu_pct","val":"0","tag":{"id":"CSPSRV"}},{"key":"iris_cpu_pct","val":"0","tag":{"id":"ECPWorker"}},{"key":"iris_cpu_pct","val":"0","tag":{"id":"GARCOL"}},{"key":"iris_cpu_pct","val":"0","tag":{"id":"JRNDMN"}},
...
{"key":"iris_wqm_max_work_queue_depth","val":"0","tag":{"id":"SYS"}},{"key":"iris_wqm_waiting_worker_jobs","val":"1","tag":{"id":"SYS"}}]
- 调用自定义
Exporter
接口
IMP>w ##class(M.ParseMetrics).Main("http://localhost:9182/metrics").%ToJSON()
[{"key":"go_gc_duration_seconds","val":"0","tag":{"quantile":"0"}},{"key":"go_gc_duration_seconds","val":"0","tag":{"quantile":"0.25"}},{"key":"go_gc_duration_seconds","val":"0","tag":{"quantile":"0.5"}},{"key":"go_gc_duration_seconds","val":"0","tag":{"quantile":"0.75"}},{"key":"go_gc_duration_seconds","val":"0.0023476","tag":{"quantile":"1"}},{"key":"go_gc_duration_seconds_sum","val":"0.0038789"},{"key":"go_gc_duration_seconds_count","val":"87"},{"key":"go_goroutines","val":"14"},{"key":"go_info","val":"1","tag":
...
{"key":"go_threads","val":"19"},{"key":"process_cpu_seconds_total","val":"2.015625"},{"key":"process_max_fds","val":"1.6777216e+07"},{"key":"process_open_fds","val":"414"},{"key":"process_resident_memory_bytes","val":"3.5504128e+07"},{"key":"process_start_time_seconds","val":"1.67670656e+09"},{"key":"process_virtual_memory_bytes","val":"4.0259584e+07"},]
使用嵌入式Python
方式获取系统CPU
、内存等
python
模块之psutil
详解
在讲嵌入式Python
之前我们先了解一下psutil
库
psutil
是一个开源切跨平台的库,其提供了便利的函数用来获取才做系统的信息,比如CPU
,内存,磁盘,网络等。此外,psutil
还可以用来进行进程管理,包括判断进程是否存在、获取进程列表、获取进程详细信息等。而且psutil
还提供了许多命令行工具提供的功能,包括:ps
,top
,lsof
,netstat
,ifconfig
,who
,df
,kill
,free
,nice
,ionice
,iostat
,iotop
,uptime
,pidof
,tty
,taskset
,pmap
。
psutil
是一个跨平台的库,在官方网站上查到其支持如下操作系统。
Linux
Windows
OSX
FreeBSD
OpenBSD
NetBSD
Sun Solaris
AIX
CPU
相关
函数 | 描述 |
---|---|
psutil.cpu_count() | cpu_count(,[logical]) :默认返回逻辑CPU 的个数,当设置logical 的参数为False 时,返回物理CPU 的个数。 |
psutil.cpu_percent() | cpu_percent(,[percpu],[interval]) :返回CPU 的利用率,percpu 为True 时显示所有物理核心的利用率,interval 不为0 时,则阻塞时显示interval 执行的时间内的平均利用率。 |
psutil.cpu_times() | cpu_times(,[percpu]) :以命名元组(namedtuple )的形式返回cpu 的时间花费,percpu=True 表示获取每个CPU 的时间花费。 |
psutil.cpu_times_percent() | cpu_times_percent(,[percpu]) :功能和cpu_times 大致相同,看字面意思就能知道,该函数返回的是耗时比例。 |
psutil.cpu_stats() | cpu_stats() 以命名元组的形式返回CPU的统计信息,包括上下文切换,中断,软中断和系统调用次数。 |
psutil.cpu_freq() | cpu_freq([percpu]) :返回cpu 频率。 |
Memory
内存相关
函数 | 描述 |
---|---|
virtual_memory() | 以命名元组的形式返回内存使用情况,包括总内存,可用内存,内存利用率,buffer 和cache 等。单位为字节。 |
swap_memory() | 以命名元组的形式返回swap/memory 使用情况,包含swap 中页的换入和换出。 |
Disk
相关
函数 | 描述 |
---|---|
psutil.disk_io_counters() | disk_io_counters([perdisk]) :以命名元组的形式返回磁盘io 统计信息,包括读、写的次数,读、写的字节数等。 当perdisk 的值为True ,则分别列出单个磁盘的统计信息(字典:key 为磁盘名称,value 为统计的namedtuple )。 |
psutil.disk_partitions() | disk_partitions([all=False]) :以命名元组的形式返回所有已挂载的磁盘,包含磁盘名称,挂载点,文件系统类型等信息。 当all 等于True 时,返回包含/proc 等特殊文件系统的挂载信息。 |
psutil.disk_usage() | disk_usage(path) :以命名元组的形式返回path 所在磁盘的使用情况,包括磁盘的容量、已经使用的磁盘容量、磁盘的空间利用率等。 |
Network
相关
函数 | 详情 |
---|---|
psutil.net_io_counter([pernic]) | 以命名元组的形式返回当前系统中每块网卡的网络io 统计信息,包括收发字节数,收发包的数量、出错的情况和删包情况。当pernic 为True 时,则列出所有网卡的统计信息。 |
psutil.net_connections([kind]) | 以列表的形式返回每个网络连接的详细信息(namedtuple )。命名元组包含fd , family , type , laddr , raddr , status , pid 等信息。kind 表示过滤的连接类型,支持的值如下:(默认为inet )。 |
psutil.net_if_addrs() | 以字典的形式返回网卡的配置信息,包括IP 地址和mac 地址、子网掩码和广播地址。 |
psutil.net_if_stats() | 返回网卡的详细信息,包括是否启动、通信类型、传输速度与mtu 。 |
psutil.users() | 以命名元组的方式返回当前登陆用户的信息,包括用户名,登陆时间,终端,与主机信息。 |
psutil.boot_time() | 以时间戳的形式返回系统的启动时间。 |
Process
相关
函数 | 详情 |
---|---|
psutil.pids() | 以列表的形式返回当前正在运行的进程。 |
psutil.pid_exists(1) | 判断给点定的pid 是否存在。 |
psutil.process_iter() | 迭代当前正在运行的进程,返回的是每个进程的Process对象。 |
psutil.Process() | 对进程进行封装,可以使用该类的方法获取进行的详细信息,或者给进程发送信号。 |
通过嵌入式Python
调用psutil
库
这里仅简单介绍个主要方法、其他获取系统资源方法由读者自行实现。
- 在安装路径
C:\InterSystems\IRISHealth\bin
,输入cmd
,进入命令行控制台输入命令。安装psutil
库。
irispip install --target C:\InterSystems\IRISHealth\mgr\python psutil
- 获取服务器
CPU
使用率
ClassMethod GetSystemCpu() As %String [ Language = python ]
{
from psutil.__init__ import cpu_percent,virtual_memory
return cpu_percent(interval=2)
}
USER> w ##class(M.SystemMetrics).GetSystemCpu()
14.59999999999999964
根据上图可以观察到获取的内容使用 1
- 是 IRIS
监控接口、2
- 是M
方法实现、 3
- 是任务管理器,三者监控的CPU
使用率基本使用是一致的。(因为CPU使用率是瞬态的,所以可以认为是准确的)
- 获取服务系内存使用状态
ClassMethod GetSystemMemory() As %String [ Language = python ]
{
from psutil.__init__ import cpu_percent,virtual_memory
return virtual_memory()[2]
}
USER>w ##class(M.SystemMetrics).GetSystemMemory()
60
根据上图可以观察到获取的内容使用 1
- 是 360
监控、2
- 是任务管理器、 3
- 是M
方法实现,三者监控的内存使用是一致的。
使用邮件发送报警信息
这里考虑使用邮件来发送警报信息是因为:
- 邮件相对其他方式来说比较方便,只管发送,不用管是否接收。
IRIS
自带发送邮件接口,操作性大。- 相比其他方式,例如微信,公众号等,还需要做接口交互。使用邮件相对简单。
那么下一个考虑的问题是通过什么方式来触发监控发邮件呢,笔者考虑以下两点可以触发:
- 通过挂任务的方式定时轮询。
- 通过
SessionEvent
事件来触发。
这里仅提供代码示例,具体使用哪种方式由读者自行决定。
使用M
发送邮件发送消息
- 这里以
QQ
邮箱例,配置信息与Alertmanager
邮箱配置相同
ClassMethod SetMailConfig() As %Net.SMTP
{
#; 配置smtp服务
s server = ##class(%Net.SMTP).%New()
s server.smtpserver = "smtp.qq.com"
s server.port = 465
if ($zv [ "IRIS") {
s server.SSLConfiguration = "ISC.FeatureTracker.SSL.Config"
} else {
s server.SSLConfiguration = "WebTerminalSSL"
}
#; 配置邮箱
s auth = ##class(%Net.Authenticator).%New()
s auth.UserName = "454115408@qq.com"
s auth.Password = "xxxxxxxxxxxxxxxx"
s server.authenticator = auth
s server.AuthFrom = auth.UserName
q server
}
- 发送邮件具体方法
ClassMethod SendMailMessage(title, content) As %List
{
#; 获取配置
s server = ..SetMailConfig()
#; 初始化邮件对象
s msg = ##class(%Net.MailMessage).%New()
#; 添加from - 发送人,to - 接收人,Cc - 抄送人
s from = server.authenticator.UserName
s msg.From = from
d msg.To.Insert("yaoxin@mediway.cn")
d msg.Cc.Insert("965274651@qq.com")
#; 填写邮件具体内容
s msg.Subject = title
s msg.IsBinary = 0
s msg.IsHTML = 0
d msg.TextData.Write(content)
#; 执行发送
s sc = server.Send(msg)
$$$ThrowOnError(sc)
q server.FailedSend
}
- 测试邮件方法,发送成功目标邮箱会接收到邮件。
IMP>w ##class(M.Mail).SendMailMessage("yx发的邮件消息头","这是yx发的消息内容content") 10@%Collection.ListOfDT
通过配置文件csv
文件,来获取维护指标,进行邮件报警。
- 使用
csv
文件通过手动的方式来维护需要监控的指标key
- 指标名称,这里要与IRIS
接口信息的具体指标保持一致。val
- 报警的阈值。oper
- 操作符。指具体报警时操作符。是大于val
时报警,还是小于val
时报警,或其他。desc
- 指标描述信息。
- 读取
csv
文件将数据转为Json
。
ClassMethod Csv2Json(filename)
{
q:('##class(%File).Exists(filename)) $$$NO
s stream = ##class(%Stream.FileCharacter).%New()
s stream.Filename = filename
s array = []
s keyStr = ""
while 'stream.AtEnd {
s str = stream.ReadLine()
if ($i(count) = 1) {
s keyStr = str
}
s obj = {}
s len = $l(str, ",")
for i = 1 : 1 : len {
s val = $p(str, ",", i)
s key = $p(keyStr, ",", i)
d obj.%Set(key, val)
}
d array.%Push(obj)
}
q array
}
IMP>w ##class(M.Mail).LoadFile("E:\temp\metrics.csv").%ToJSON()
[{"key":"iris_cpu_usage ","val":"10","oper":">"},{"key":"iris_license_available","val":"290","oper":">"},{"key":"iris_trans_open_count","val":"0","oper":">"},{"key":"memory","val":"40","oper":">"}]
- 对比维护指标与接口指标,超过阈值发送邮件进行警报。
注:这里双循环,时间复杂O2
,数据量大,效率可能会比较低,需要注意下。
ClassMethod Main(url As %String, filename As %String) As %Status
{
#; 获取接口指标Json
s irismetrics = ##class(M.ParseMetrics).Main(url)
#; 获取Csv维护指标Json
s config = ##class(M.Mail).Csv2Json(filename)
#; 双向对比,维护指标超过阈值发送邮件警报
for i = 1 : 1 : irismetrics.%Size() - 1{
s metrics = irismetrics.%Get(i)
for j = 1 : 1 : config.%Size() - 1{
s obj = config.%Get(j)
if (obj.key = metrics.key) {
if @(metrics.val _ obj.oper _ obj.val) {
s content = obj.desc _ "警报,超过阈值:" _ obj.val
d ..SendMailMessage("来自系统的警报",content)
ret $$$NO
}
}
}
}
#; 监控内存使用率,接口中没有监视内存选项所以调用嵌入式接口
s memory = ..GetSystemMemory()
if (memory > 30) {
d ..SendMailMessage("来自系统的警报","内存使用率已经大于" _ 30 _ "")
}
ret $$$YES
}
USER>w ##class(M.Mail).Main("https://localhost:2443/api/monitor/metrics", "E:\temp\metrics.csv")
0
注:这里基本思路已经实现,可以通过挂任务或其他方式来定时轮询。
总结
以上是个人关于监控服务器指标、CPU、内存、警报的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。
思考
IRIS
还提供了alert
警报接口,该接口提供alert.log
警报日志内容,基于以上方案,思考如何通过M
程序进行监控
- 接口地址:https://localhost:2443/api/monitor/alerts
- 该接口有警报时会提示报警信息,调用一次后清空。
- 如果有警报,警报信息如下:
{"time":"02/19/23-15:49:22:650","severity":2,"message":"Previous system shutdown was abnormal, system forced down or crashed."}
附
/api/monitor/metrics
指标含义列表:
指标 | 描述 |
---|---|
iris_cpu_pct {id="ProcessType"} | IRIS 进程类型的 CPU 使用百分比。 ProcessType 可以是以下任何一项:ECPWorker、ECPCliR、ECPCliW、ECPSrvR、ECPSrvW、LICENSESRV、WDAUX、WRTDMN、JRNDMN、GARCOL、CSPDMN、CSPSRV、ODBCSRC、MirrorMaster、MirrorPri、MirrorBack、MirrorPre、MirrorSvrR、MirrorJrnR、MirrorSK、MirrorComm |
iris_cpu_usage | 操作系统上所有程序的 CPU 使用率百分比 |
iris_csp_activity {id="IPaddress:port"} | Web 网关服务器自启动以来处理的 Web 请求数 |
iris_csp_actual_connections {id="IPAddress:port"} | Web 网关服务器与该服务器的当前连接数 |
iris_csp_gateway_latency {id="IPaddress:port"} | 获取 iris_csp_ 指标时从 Web 网关服务器获得响应的时间,以毫秒为单位 |
iris_csp_in_use_connections {id="IPaddress:port"} | 正在处理 Web 请求的 Web 网关服务器与此服务器的当前连接数 |
iris_csp_private_connections {id="IPaddress:port"} | 为状态感知应用程序保留的 Web 网关服务器与此服务器的当前连接数(保留模式 1 ) |
iris_csp_sessions | 此服务器上当前活动的 Web 会话 ID 数 |
iris_cache_efficiency | 全局引用与物理读写的比率,以百分比表示 |
iris_db_expansion_size_mb {id="database"} | 扩展数据库的数量,以兆字节为单位 |
iris_db_free_space {id="database"} | 数据库中可用的可用空间,以兆字节为单位(此指标每天仅更新一次,可能不会反映最近的更改。) |
iris_db_latency {id="database"} | 完成从数据库随机读取的时间量,以毫秒为单位 |
iris_db_max_size_mb {id="database"} | `数据库可以增长到的最大大小,以兆字节为单位 |
iris_db_size_mb {id="database",dir="path"} | 数据库大小,以兆字节为单位 |
iris_directory_space {id="database",dir="path"} | 数据库目录存储卷上的可用空间,以兆字节为单位 |
iris_disk_percent_full {id="database",dir="path"} | 数据库目录存储卷上的空间百分比 |
iris_ecp_conn | 此 ECP 应用程序服务器上的活动客户端连接总数 |
iris_ecp_conn_max | 来自此 ECP 应用程序服务器的最大活动客户端连接数 |
iris_ecp_connections | 此 ECP 应用程序服务器与其配置的 ECP 数据服务器同步时同步的服务器数 |
iris_ecp_latency | ECP 应用服务器和 ECP 数据服务器之间的延迟,以毫秒为单位 |
iris_ecps_conn | 每秒与此 ECP 数据服务器的活动客户端连接总数 |
iris_ecps_conn_max | 与此 ECP 数据服务器的最大活动客户端连接数 |
iris_glo_a_seize_per_sec | 每秒全局资源上的 Aseizes 数 |
iris_glo_n_seize_per_sec | 每秒全局资源上的 Nseizes 数 |
iris_glo_ref_per_sec | 每秒对位于本地数据库上的全局变量的引用数 |
iris_glo_ref_rem_per_sec | 每秒对位于远程数据库上的全局变量的引用数 |
iris_glo_seize_per_sec | 每秒占用全局资源的次数 |
iris_glo_update_per_sec | 每秒对本地数据库上的全局变量进行更新(SET 和 KILL 命令)的次数 |
iris_glo_update_rem_per_sec | 每秒对位于远程数据库上的全局变量的更新(SET 和 KILL 命令)数 |
iris_jrn_block_per_sec | 每秒写入磁盘的日志块 |
iris_jrn_free_space {id="JournalType",dir="path"} | 每个日志目录的存储卷上可用的可用空间,以兆字节为单位。 JournalType 可以是 WIJ 、primary 或 secondary |
iris_jrn_size {id="JournalType"} | 每个日志文件的当前大小,以兆字节为单位。 JournalType 可以是 WIJ 、primary 或 secondary |
iris_license_available | 当前未使用的许可证数量 |
iris_license_consumed | 当前使用的许可证数量 |
iris_license_percent_used | 当前使用的许可证的百分比 |
iris_log_reads_per_sec | 每秒逻辑读取 |
iris_obj_a_seize_per_sec | 每秒对象资源上的 Aseizes 数 |
iris_obj_del_per_sec | 每秒删除的对象数 |
iris_obj_hit_per_sec | 进程内存中每秒的对象引用数 |
iris_obj_load_per_sec | 每秒从磁盘加载的对象数,不在共享内存中 |
iris_obj_miss_per_sec | 每秒在内存中找不到的对象引用数 |
iris_obj_new_per_sec | 每秒初始化的对象数 |
iris_obj_seize_per_sec | 每秒占用对象资源的次数 |
iris_page_space_percent_used | 已用最大分配页面文件空间的百分比 |
iris_phys_mem_percent_used | 当前使用的物理内存 (RAM ) 的百分比 |
iris_phys_reads_per_sec | 每秒从磁盘读取的物理数据库块 |
iris_phys_writes_per_sec | 每秒写入磁盘的物理数据库块 |
iris_process_count | 活跃的 IRIS 进程总数 |
iris_rtn_a_seize_per_sec | 每秒例程资源上的 Aseizes 数 |
iris_rtn_call_local_per_sec | 每秒对位于远程数据库上的全局变量的本地例程调用数 |
iris_rtn_call_miss_per_sec | 每秒在内存中找不到的例程调用数 |
iris_rtn_call_remote_per_sec | 每秒远程例程调用次数 |
iris_rtn_load_per_sec | 每秒从本地加载或保存到磁盘的例程数 |
iris_rtn_load_rem_per_sec | 每秒从磁盘远程加载或保存到磁盘的例程数 |
iris_rtn_seize_per_sec | 每秒占用例程资源的次数 |
iris_sam_get_db_sensors_seconds | 收集 iris_db* 传感器所花费的时间,以秒为单位 |
iris_sam_get_jrn_sensors_seconds | 收集 iris_jrn* 传感器所花费的时间,以秒为单位 |
iris_sam_get_sql_sensors_seconds | 收集 iris_sql* 传感器所花费的时间,以秒为单位 |
iris_sam_get_wqm_sensors_seconds | 收集 iris_wqm* 传感器所花费的时间,以秒为单位 |
iris_smh_available {id="purpose"} | 按目的可用的共享内存,以千字节为单位 |
iris_smh_percent_full {id="purpose"} | 按目的使用的已分配共享内存的百分比 |
iris_smh_total | 为当前实例分配的共享内存,以千字节为单位 |
iris_smh_total_percent_full | 当前实例使用的已分配共享内存的百分比 |
iris_smh_used {id="purpose"} | 按目的使用的共享内存,以千字节为单位 |
iris_sql_active_queries {id="namespace"} | 当前执行的 SQL 语句数 |
iris_sql_active_queries_95_percentile {id="namespace"} | 对于当前的活动 SQL 语句集,自语句开始执行以来经过的第 95 个百分位数的时间 |
iris_sql_active_queries_99_percentile {id="namespace"} | 对于当前活动的 SQL 语句集,自语句开始执行以来经过的第 99 个百分位数的时间 |
iris_sql_queries_avg_runtime {id="namespace"} | 平均 SQL 语句运行时间,以秒为单位 |
iris_sql_queries_avg_runtime_std_dev {id="namespace"} | 平均 SQL 语句运行时间的标准偏差 |
iris_sql_queries_per_second {id="namespace"} | 每秒平均 SQL 语句数 |
iris_system_alerts | 自系统启动以来发布到消息日志的警报数 |
iris_system_alerts_log | 当前位于警报日志中的警报数 |
iris_system_alerts_new | /api/monitor/alerts 端点上是否有新警报,作为布尔值 |
iris_system_state | 表示系统监视器健康状态的数字 |
iris_trans_open_count | 当前实例上打开的事务数 |
iris_trans_open_secs | 当前实例上打开事务的平均持续时间,以秒为单位 |
iris_trans_open_secs_max | 当前实例上当前打开的最长事务的 |
iris_wd_buffer_redirty | 写入守护进程在最近一个周期中写入并且也在前一个周期中写入的数据库缓冲区数 |
iris_wd_buffer_write | 写入守护进程在其最近周期写入的数据库缓冲区数 |
iris_wd_cycle_time | 完成最近的写入守护进程周期所花费的时间量,以毫秒为单位 |
iris_wd_proc_in_global | 在最近的写入守护进程周期开始时主动持有全局缓冲区的进程数 |
iris_wd_size_write | 写入守护程序在其最近周期写入的数据库缓冲区的大小,以千字节为单位 |
iris_wd_sleep | 写入守护进程在其最近的周期开始之前处于非活动状态的时间量,以毫秒为单位 |
iris_wd_temp_queue | 写入守护进程在其最近周期开始时使用的内存缓冲区数 |
iris_wd_temp_write | 写入守护进程在其最近周期中写入的内存缓冲区数 |
iris_wdwij_time | 写入守护进程在其最近的周期内写入 WIJ 文件所花费的时间,以毫秒为单位 |
iris_wd_write_time | 写守护进程在其最近的周期内将缓冲区写入数据库所花费的时间,以毫秒为单位 |
iris_wij_writes_per_sec | WIJ 每秒物理块写入 |
iris_wqm_active_worker_jobs {id="category"} | 未被阻塞的运行逻辑的平均工作者作业数 |
iris_wqm_commands_per_sec {id="category"} | 平均每秒在此工作队列管理类别中执行的命令数 |
iris_wqm_globals_per_sec {id="category"} | 平均每秒在此工作队列管理类别中运行的全局引用数 |
iris_wqm_max_active_worker_jobs {id="category"} | 自上次记录日志条目以来的最大活动工作人员数 |
iris_wqm_max_work_queue_depth {id="category"} | 自上次记录以来此工作队列管理类别队列中的最大条目数 |
iris_wqm_waiting_worker_jobs {id="category"} | 等待一个组连接并为其工作的空闲工人作业的平均数量 |