Satori 监控系统¶
Satori 是一个由 LeanCloud 发起的监控系统。
基本情况¶
起初在 LeanCloud 内部是直接使用了 Open-Falcon 。 后续的使用过程中因为自己的需求做了大量修改,然后开源。
设计思路¶
Satori 希望最大程度的减少监控系统的部署维护难度。如果在任何的部署、增删维护报警的时候觉得好麻烦,那么这是个 bug。
监控时的需求很多样,Satori 希望做到『让简单的事情简单,让复杂的事情可能』。常用的监控项都会有模板,可以用 Copy & Paste 解决。略复杂的监控需求可以阅读 riemann 的文档,来得知怎么编写复杂的监控规则。
因为 LeanCloud 的机器规模不大,另外再加上现在机器的性能也足够强劲了,所以放弃了 Open-Falcon 中横向扩展目标。如果你的机器数量或者指标数目确实很大,可以将 transfer、 InfluxDB、 riemann 分开部署。这样的结构支撑 5k 左右的节点应该是没问题的。
在考察了原有的 Open-Falcon 的架构后,发现实质上高可用只有 transfer 实现了,graph、judge 任何一个实例挂掉都会影响可用性。对于 graph,如果一个实例挂掉的话,还必须要将那个节点恢复,不能通过新加节点改配置的方式来恢复;judge 尽管可以起新节点,但是还是要依赖外部的工具来实现 failover,否则是需要修改 transfer 的配置的。因此 Satori 中坚持用单点的方式来部署,然后通过配合公网提供的 APM 服务保证可用性。真的希望有高可用的话,riemann 和 alarm 可以部署两份,通过 keepalived 的方式来实现,InfluxDB 可以官方的 Relay 来实现。
架构图¶
![digraph Architecture {
{
rank=same;
"用户" [shape=doublecircle];
"机器" [shape=doublecircle];
}
"规则仓库" [shape=rect];
InfluxDB [shape=cylinder];
"用户" -> "规则仓库" [label="修改"];
"规则仓库" -> {riemann alarm} [label="触发更新"];
"用户" -> alarm [label="查看报警"];
"用户" -> Grafana [label="查看指标"];
Grafana -> InfluxDB [label="请求数据"];
"机器" -> agent -> transfer -> {InfluxDB riemann} [label="监控指标"];
riemann -> alarm [label="生成报警"];
alarm -> {SMS Mail "微信"};
riemann -> master -> agent [label="下发插件参数"];
agent -> master [label="心跳"];
master -> transfer [label="报告 agent 存活"];
}](_images/graphviz-6eab8ea2775ee1adb852eab4d405755cdc13df6c.png)
与 Open-Falcon 的比较¶
Satori | Open-Falcon | |
---|---|---|
agent |
|
可以单机部署 |
transfer | 可以配合原版 graph 和 judge 运行 | |
alarm |
|
支持报警合并 |
sender | 已经合并进了 alarm 中 | |
links | 移除了 [4] | |
graph | 替换成 InfluxDB 了 | |
query | 移除了 | |
dashboard | 替换成 Grafana 了 | |
task | InfluxDB 自带 task 的功能 | |
aggreagator | 规则中有 aggregate 和 slot-window 实现相同功能 | |
nodata | 规则中有 watchdog 实现相同功能 | |
portal | 移除了,所有信息通过规则仓库管理 | |
gateway | 合并进了 transfer | |
master | 没有数据库依赖 | 在 Open-Falcon 叫 hbs |
portal | 移除了,所有信息通过规则仓库管理 | |
frontend | 完全重写,采用了纯前端的方案 | 对应 Open-Falcon 中的 fe |
InfluxDB | 开源组件,用来存储和查询收集上来的监控信息 | Open-Falcon 中不存在 |
riemann | 开源组件,负责报警判定和产生插件规则 | Open-Falcon 中不存在 |
Grafana | 开源组件,替换了 graph | 对应 Open-Falcon 中的 dashboard |
nginx | 为前端页面提供服务 | Open-Falcon 中不存在 |
[1] | 使用了 hitripod 的补丁 |
[2] | 即 Open-Falcon 中 gateway 的功能 |
[3] | 电话、短信、BearyChat、OneAlert、PagerDuty、邮件、微信企业号 |
[4] | 推荐直接使用低优先级的通道(如 BearyChat/其他IM,或者 BitBar), 或者在规则中做聚合(参见 复杂需求 ),不做报警合并。 |
注解
与 Open-Falcon 的比较是基于 v0.1 的,很多东西对 Falcon-Plus 不再适用了
一分钟快速感受¶
常见需求¶
(def infra-mongodb-rules
; 在 mongo1 mongo2 mongo3 ... 上做监控
(where (host #"^mongo\d+")
; 执行 mongodb 目录里的插件(里面有收集 mongodb 指标的脚本)
(plugin-dir "mongodb")
; 每 30s 收集 27018 端口是不是在监听
(plugin "net.port.listen" 30 {:port 27018})
; 过滤一下 mongodb 可用连接数的 metric(上面插件收集的)
(where (service "mongodb.connections.available")
; 按照 host(插件中是 endpoint)分离监控项,并分别判定
(by :host
; 报警在监控值 < 10000 时触发,在 > 50000 时恢复
(judge-gapped (< 10000) (> 50000)
; 600 秒内只报告一次
(alarm-every 10 :min
; 报告的标题、级别(影响报警方式)、报告给 operation 组和 api 组
(! {:note "mongod 可用连接数 < 10000 !"
:level 1
:groups [:operation :api]})))))
; 另一个监控项
(where (service "mongodb.globalLock.currentQueue.total")
(by :host
(judge-gapped (> 250) (< 50)
(alarm-every 10 :min
(! {:note "MongoDB 队列长度 > 250"
:level 1
:groups [:operation :api]})))))))
cd /path/to/rules/repo # 规则是放在 git 仓库中的
git commit -a -m 'Add mongodb rules'
git push # 然后就生效了
注解
完整版请看 satori-rules/rules/infra/mongodb.clj
这个模板可以满足常见的需求,真正编写规则的时候可以复制粘贴这一份规则并做微调
复杂需求¶
这是一个监控队列堆积的规则。 队列做过 sharding,分布在多个机器上。 但是有好几个数据中心,要分别报告每个数据中心队列情况。 堆积的定义是:在一定时间内,队列非空,而且队列元素数量没有下降。
(def infra-kestrel-rules
; 在所有的队列机器上做监控
(where (host #"kestrel\d+$")
; 执行队列相关的监控脚本(插件)
(plugin-dir "kestrel")
; 过滤『队列项目数量』的 metric
(where (service "kestrel_queue.items")
; 按照队列名和数据中心分离监控项,并分别判定
(by [:queue :region]
; 将传递下来的监控项暂存 60 秒,然后打包(变成监控项的数组)再向下传递
(fixed-time-window 60
; 将打包传递下来的监控项做聚集:将所有的 metric 值都加起来。
; 因为队列监控的插件是每 60 秒报告一次,并且之前已经按照队列名和数据中心分成子流了,
; 所以这里将所有 metric 都加起来以后,获得的是单个数据中心单个队列的项目数量。
; 聚集后会由监控项数组变成单个的监控项。
(aggregate +
; 将传递下来的聚集后的监控项放到滑动窗口里,打包向下传递。
; 这样传递下去的,就是一个过去 600 秒单个数据中心单个队列的项目数量的监控项数组。
(moving-event-window 10
; 如果已经集满了 10 个,而且这 10 个监控项中都不为 0 (队列非空)
(where (and (>= (count event) 10)
(every? pos? (map :metric event)))
; 再次做聚集:比较一下是不是全部 10 个数量都是先来的小于等于后来的(是不是堆积)
(aggregate <=
; 如果结果是 true,那么认为现在是有问题的
(judge (= true)
; 每半小时告警一次
(alarm-every 0.5 :hour
; 这里 outstanding-tags 是用来区分报警的,
; 即如果 region 的值不一样,那么就会被当做不同的报警
(! {:note #(str "队列 " (:queue %) " 正在堆积!")
:level 2
:outstanding-tags [:region]
:groups [:operation]}))))))))))))
注解
这是一个简化了的版本,完整版可以看 satori-rules/rules/infra/kestrel.clj
常见的问题¶
如果配置有大的变更,可以在 /etc/satori
找到相关的配置文件并做修改。
重启了 master,机器信息都丢掉了?¶
master 不会持久化保存机器信息,只会在内存中记录发来心跳的 agent 的信息。 这种情况等两分钟就可以了。
如果长时间都是空白,可以检查一下 agent 的日志。
agent 的日志会输出到 stdout、stderr
上,所以日志文件具体在哪里就跟你的部署方式有关了,比如,upstart
的日志可以在 /var/log/upstart
中找到,systemd 的日志可以用
journalctl 工具看到。
机器信息里看不到插件项,插件版本也不对?¶
这种情况是因为 master 没有收到来自监控规则的配置,通常在重启了 master 之后会出现这种情况。
触发一下规则更新就可以了,可以重启下 riemann 中的 reloader(重启 riemann 的 docker 容器也可以),或者做一个小的规则修改,push 上去,两分钟后就会恢复。
这种情况下,只要 agent 不重启,之前的配置就仍然有效。
我修改了规则之后,为什么相关报警一直留在报警页面里?¶
报警页面的记录是由 riemann 发给 alarm 的,恢复时也是。
修改了规则之后,riemann 就不会发送恢复的事件,alarm 会一直保留这个事件。
可以观察下触发时间,如果时间很长的话,就是这种情况了, 直接删除这个事件就可以了。
插件目录中的插件总是不执行?¶
插件需要 chmod +x
,以及正确的 shebang (#!/bin/bash
这个东西)。
初次安装¶
机器要求¶
请找一个干净的虚拟机,内存大一点,要有 SSD,不要部署其他的服务。 LeanCloud 用了一个 2 cores, 8GB 的内存的 VM,目测这个配置可以撑住大概 2000 个左右的节点(InfluxDB 查询不多的情况下)。
交互式安装¶
satori
子目录中有一个 install
脚本,以一个可以 ssh 并且 sudo
的用户执行它就可以了。
不要用 root,因为安装后会以当前用户的身份配置规则仓库。
执行下面的命令:
git clone https://github.com/leancloud/satori
cd satori/satori
./install
install 时会问你几个问题,填好以后会自动的生成配置,
build 出 docker image 并启动。这之后设置好DNS和防火墙就可以用了。
在仓库的第一层会有编译好的 satori-agent
和
agent-cfg.yaml
可以用来部署,命令行如下:
/usr/bin/satori-agent -c /etc/satori-agent/agent-cfg.yaml
安装的时候会检查一下需要的组件,如果没有安装的话会进行安装。
安装后再次运行 ./install
即可。
非交互式(无人工干预/批量)安装¶
请创建一个配置文件:
USE_MIRROR=1 # 或者留空,是否使用国内的镜像
DOMAIN="www.example.com" # 外网可以访问的域名
INTERNAL_DOMAIN="satori01" # 内网可以访问的域名
RULES_REPO="/home/app/satori-conf" # 规则仓库的地址
RULES_REPO_SSH="app@www.example.com:/home/app/satori-conf" # 外网可以访问的 git 仓库地址
RESOLVERS="223.5.5.5,119.29.29.29,ipv6=off" # 使用逗号(`,`)分隔的 DNS IP地址列表
保存为 /tmp/install.conf
。
然后执行:
git clone https://github.com/leancloud/satori
cd satori/satori
./install -f /tmp/install.conf
Agent 安装¶
如果你使用 Ansible 和 SystemD,那么在 deploy/ansible 中有一份样例 ansible playbook,可以参考一下。
注解
agent 不会自己 daemonize,如果用了传统的 /etc/init.d
脚本的方式部署,需要注意这个问题。
登录 Web 界面¶
Web 界面的密码会在安装后给出,注意提示。
如果没有看到,可以在 clone 出规则仓库之后在内部找到 bitbar-plugin.py
,
可以看到写入的 Web 界面的用户名和密码。
注解
建议在测试成功后自己搭建并开启 TLS 客户端验证,或者使用 Kerberos 认证。 这两种方式在 /etc/satori/nginx 中都有未开启的样例配置。
更新¶
因为规则仓库是开放修改的,与更新同步会比较麻烦,需要手工操作。 所以这里会记录更新相关的步骤,怎么更新内置规则、配置文件等等。
从 1.x 更新到 2.x¶
- 首先参照下面的 没有大变化的话该怎么更新? 更新
- 将规则仓库
alarm
下的backends
和hooks
复制到你的规则仓库的相应位置 - 将你自己的规则仓库中的
_metric
下的所有插件移动到上层目录,与其他插件目录处于同一级 - 将规则仓库
rules
下最新的agent_plugin.clj
、alarm.clj
、lib.clj
、once.clj
、riemann.config
复制到你的规则仓库内的相应位置 - 修改所有的监控规则文件,确保最上面的
(ns ...)
语句引用到了agent_plugin
alarm
和lib
的所有符号(参见新规则仓库中的规则) - 提交、签名(如果开启)并 push 你的规则仓库,并且到机器上的规则仓库执行
git reset --hard
以确保 Satori 能读到最新的规则 - 通过
sudo docker-compose restart
重新启动所有服务 - 通过
sudo docker-compose logs <XXX>
命令查看riemann
和alarm
的日志,确认服务可以正常工作。 如果riemann
不能正常工作,请按照报错提示确认是否遗漏了上述的升级过程 - 组件都能正常工作后,更新 agent 到最新版
没有大变化的话该怎么更新?¶
cd satori/satori
git pull
./install rebuild
sudo docker-compose up -d
agent 配置¶
样例配置¶
debug: false
hostname: ''
noBuiltin: false
ip: ''
cgroups:
cpu: 50.0
mem: 256
enforce: false
plugin:
enabled: true
signingKeys:
- owner: You
key: NotAVaildKeyGetOneKeyBySignToolInRulesRepo==
- owner: youmu
key: RYzIU5vEK3e4asrN0KrPpNdvjBRQq+3Mva5z27ba9sw=
- owner: yuyuko
key: 6h0llzeOAcOsn4A5vGzEkp/icjQhGddYzRzbOUh1PL0=
- owner: satori
key: f1mKpSYpnpKDqZbci0/TFWTfND1NZ5QSyXpPEa1h7XY=
- owner: koishi
key: Vpk9Ev2li0d2jRUCPwbG4PfylqmspYFPaFS21Zhrs/g=
- owner: reimu
key: m0HT43K6xOxm11Ybjz0BrdfbQYt7WHesqpZ/VcaTwTA=
authorizedKeys: authorized_keys.yaml
update: agent-update.yaml
git: "http://satori:PASSWD@DOMAIN/plugin"
checkoutPath: "/var/lib/satori/plugin"
subDir: plugin
logs: "/var/log/satori"
master: "master://INTERNAL_DOMAIN:6040/?interval=60&timeout=5000"
transfer:
- "transfer://INTERNAL_DOMAIN:8433/?timeout=5000"
http: ":1988"
collector:
ifacePrefix:
- enp
- eth
- em
addTags:
region: default
ignore:
- metric: "^df\\."
tag: "mount"
tagValue: ".*/(docker|ureadahead|netns)/.*$"
参数解释¶
Key | 意义 |
---|---|
debug | 是否开启调试日志 |
hostname | 指定一个 hostname [1] |
ip | 指定一个 ip 地址 [2] |
cgroups | 参见 为 agent 开启 cgroups 限制 |
plugin | 参见 插件配置 |
master | master 的地址。这个值在安装后生成的 agent-cfg.yaml 里会自动帮你填写 |
transfer | transfer 的地址。这个值在安装后生成的 agent-cfg.yaml 里会自动帮你填写 |
http | HTTP 监听端口,负责响应 ping、push 请求 |
collector | 目前只有一个用处,用来指示 agent 需要收集指标的网卡前缀 |
addTags | 在所有经过这个 agent 的上报指标上附带 tags [3] |
ignore | 参见 过滤不想要的事件 |
[1] | 这个 hostname 会上报给 master,并且如果插件没有填写上报指标的 hostname 的话,agent 会用这个值来填写。默认为本机 hostname。 |
[2] | 同样这个值也会上报给 master |
[3] | 这个功能常用作给指标加上跟机器相关的元数据,比如这个机器所在的区域、机架 |
为 agent 开启 cgroups 限制¶
作为一个监控 agent 如果因为自身的 bug 挤占了正常服务的资源是非常尴尬的, 所以 agent 可以使用 cgroups 机制来限制自身和插件的资源占用。
参见 样例配置 中的 cgroups
项
Key | 意义 |
---|---|
cpu | CPU 限制,百分比。可以超过100,代表1个核以上。超过限制系统就不会调度了。 |
mem | 内存限制,MiB。超过限制会挑组内内存占用最大的一个杀掉 |
enforce | 强制开启 cgroups。如果为 true,那么 agent 在设置 cgroups 失败的情况下会直接 panic |
插件配置¶
与 agent 拉取规则仓库相关的配置。
参见 样例配置 中的 plugin
项
Key | 意义 |
---|---|
enabled | 是否开启插件功能 |
signingKeys | agent 信任的签名公钥,参见 规则仓库签名 |
authorizedKeys | 规则仓库中的 agent 会信任的签名公钥文件名,参见 规则仓库签名 |
update | 规则仓库中自更新配置文件,参见 agent 的自动更新 |
git | 可以由 agent 访问的规则仓库的地址。 |
checkoutPath | 规则仓库 checkout 地址(本地磁盘上的存储地址) |
subDir | 规则仓库中存储插件的子目录 |
logs | 存放插件日志的地址 |
注解
其中 git
、 subDir
安装脚本会帮你填写,应该不用修改,
过滤不想要的事件¶
agent 收集的时间很可能有很多是不希望要的,本身无意义,导致误报,而且占用存储空间,比如 docker 环境下的 netns 和 aufs 挂载的信息。 ignore
可以将这些都过滤掉。
ignore
接受一个列表,列表中的每个元素可以指定如果下的规则:
Key | 意义 |
---|---|
metric | 上报指标的名字 |
tag | 上报指标中 tag 的名字 |
tagValue | tag 的值 |
同一个元素中指定的多个 key 之间是逻辑与关系,不同的元素之间是逻辑或关系。
这3个 key 都接受正则表达式。
例子:
ignore:
- metric: "^foo"
- metric: "^bar"
tag: "^baz$"
tagValue: "quux|meh"
这个例子中,metric
的开头是 foo
的指标会被过滤;开头是 bar
并且带着一个叫 baz
的 tag 并且 tag 的内容中有 quux
或者 meh
的会被过滤。
agent 的自动更新¶
在配置中的 update
指定了一个用于自动更新的配置(默认是 agent-update.yaml
)。
每次更新后 agent 会检查这个文件中指定的 sha256,如果不匹配,则会按照指定的 url 下载最新的 agent binary,
验证 sha256 通过之后,就会覆盖掉现有的 agent binary 并重新启动。
配置样例:
# This file is used for satori-agent auto update
# You MUST provide sha256 of new binary
sha256: 4e2f5eed455e56879f4140a26f5a17ac53011df83fba8afd13ab482d27a0c2fb
url: "http://example.com/satori-agent.690f210fead60542"
规则仓库签名¶
为了防止监控机被入侵导致可以随意向被监控机推送任意的可执行文件(插件), Satori 提供了可选的签名机制。
在安装初次安装完成后,签名机制是没有打开的。
生成签名用的 key¶
在安装完成后,可以先 clone 下来规则仓库,并且在规则仓库中找到 sign
工具。
通过如下命令生成密钥对:
./sign --generate
执行这个命令之后,私钥会被保存在 ~/.satori-signkey
中,公钥会直接告诉你。
如果你弄丢了公钥,可以通过如下命令获得公钥:
./sign --get-public
这个命令会根据你的私钥重新计算出公钥来。
警告
私钥丢失无法恢复。
对规则仓库进行签名¶
在修改过规则仓库并且 commit 之后,执行如下命令
./sign
就会以 ~/.satori-signkey
作为私钥对当前 commit 进行签名了,签名后就可以 push 到监控机的规则仓库中并开始生效了。
签名是通过 git amend
修改最后一个 commit 的 commit message 来嵌入的,可以通过 git log
命令观察到。
警告
如果不小心重新签名了已经 push 出去的 commit,可以通过 git reflog
找到之前的 commit 并 git reset --hard
过去。
配置 agent 接受的签名公钥¶
在 样例配置 能看到 signingKeys
的配置,将你的公钥填进去就好。
plugin:
signingKeys:
- owner: You
key: NotAVaildKeyGetOneKeyBySignToolInRulesRepo==
- owner: youmu
key: RYzIU5vEK3e4asrN0KrPpNdvjBRQq+3Mva5z27ba9sw=
其中 key
是你的 公钥 ,其他的都是元信息,可以任意添加删除。
agent 按照配置了 signingKey
的新配置启动后,就会在每次更新规则仓库后,先验证签名,再检出最新的 commit。
另外,authorizedKeys
选项指定了一个规则仓库中的文件名(默认为 authorized_keys.yaml
),
这个文件中配置的额外的 key 也都会被 agent 接受,
但是修改了这个配置的 commit 必须由 signingKeys
指定的 key 来签名。
这个机制用来方便的增删签名 key 而不需要修改所有机器上 agent 的配置。
阻止未签名的 push¶
开启了签名机制后会经常忘了先签名再 push,所以在 deploy/rules-repo-pre-receive-hook
提供了一个 git hook,将这个文件复制到 /path/to/rules-repo-on-server/.git/hooks/pre-receive
并且加上可执行权限,git 就会阻挡没有签名的 push。
警告
这个 hook 只会验证签名是否存在,不会验证有效性,请不要依赖这个机制作为安全措施!
报警配置¶
Satori 中的报警是由 alarm 组件负责的,alarm 组件的配置都在规则仓库中的 alarm
目录中。
alarm
目录中的所有 yaml 文件都会解析合并,所以可以任意的拆分文件。
LeanCloud 内部的报警级别是这么设定的:
LV | 效果 |
---|---|
0 | 语音通话 |
1 | 短信(包括运维手机) |
2 | 短信 |
3 | 微信企业号 |
4 | 邮件(但是0123级别的不会发) |
5 | BearyChat |
6 | 什么都不做(但是会显示在 BitBar 插件中) |
规则仓库的默认配置就是按照这个来的,可以自己按照需求修改。
报警人员和组配置¶
样例配置如下,感觉不用解释了……
其中 users
下每一个人员的配置需要填 name
表示名字,以及一个可选的 threshold
表示『过滤级别在这之下的报警』。 剩余的字段是给报警的 backend 的参数,需要参考各个 backend 的文档。
groups:
operation:
- proton
- phone_ops
- bearychat_ops
- pagerduty_ops
users:
phone_ops:
name: "运维手机"
phone: 18888888888
threshold: 1
proton:
name: "Proton"
email: proton@example.com
phone: 1888888888
wechat: proton
bearychat_ops:
name: "运维 BearyChat 机器人"
bearychat: "[PUT_YOUR_URL_HERE]"
pagerduty_ops:
name: "运维 PagerDuty"
pagerduty: "PUT_YOUR_KEY_HERE"
onealert: "PUT_YOUR_KEY_HERE"
报警策略配置¶
在 strategies
下的每一个策略需要提供至少 2 个参数:
Key | 意义 |
---|---|
backend | 策略的 backend [1] |
level | 当前策略处理的报警级别,是个 string,可以指定多个级别 |
[1] | backend 就是在 Satori 源码的 alarm/src/backend 中的用于发送报警的小段代码,
可以自己扩展。 |
剩下的参数是提供给 backend 用的。
strategies:
phonecall:
backend: nexmo_tts
level: '0'
api_key: 'foooo'
api_secret: 'barrrr'
voice: female
lg: zh-cn
repeat: 3
prefix: ''
sms:
backend: yunpian_sms
level: '012'
signature: 'LC报警'
api_key: '812912897398172387893687401298'
SMTP 发送邮件¶
- Backend 名字:
- smtp
- 报警策略中需要配置的参数:
Key 意义 server 邮件服务器地址 send_from 邮件的 Sender 地址 username SMTP 认证用户名 password SMTP 认证密码 - 用户中需要配置的参数:
Key 意义 email 当前用户的邮件地址 - 配置样例:
strategies: email: backend: smtp level: '4' server: smtp.mailgun.org send_from: satori-alarm@example.com username: fooooo password: barrrr users: example: name: "例子" email: example@example.com
电话报警¶
这里使用了 Nexmo 的服务,需要在上面注册账号,获得 API key
- Backend 名字:
- nexmo_tts
- 报警策略中需要配置的参数:
Key 意义 api_key API Key api_secret API Secret voice 语音声音,可以填 female
lg 语言, zh-cn
为中文repeat 重复次数 prefix 在报警标题前加的固定的话 - 用户中需要配置的参数:
Key 意义 phone 当前用户的手机 - 配置样例:
strategies: phonecall: backend: nexmo_tts level: '0' api_key: PUT_YOUR_KEY_HERE api_secret: PUT_YOUR_KEY_HERE voice: female lg: zh-cn repeat: 3 prefix: '' users: example: name: "例子" phone: 18888888888
微信企业号¶
警告
腾讯现在只提供企业微信了,所以不再提供文档
BearyChat¶
这个会 POST 到 BearyChat 的 Incoming 机器人上。
- Backend 名字:
- bearychat
- 报警策略中需要配置的参数:
- 无
- 用户中需要配置的参数:
Key 意义 bearychat 当前用户的 Incoming 机器人地址 - 配置样例:
strategies: bearychat: backend: bearychat level: '012345' users: example: name: "例子" bearychat: "https://hook.bearychat.com/=foobar/incoming/bazbazbazbazbazabaz"
PagerDuty¶
- Backend 名字:
- pagerduty
- 报警策略中需要配置的参数:
- 无
- 用户中需要配置的参数:
Key 意义 pagerduty 当前用户的 PagerDuty service_key - 配置样例:
strategies: pagerduty: backend: pagerduty level: '012345' users: example: name: "例子" pagerduty: "abcdefg123123123123123"
OneAlert¶
- Backend 名字:
- onealert
- 报警策略中需要配置的参数:
- 无
- 用户中需要配置的参数:
Key 意义 onealert 当前用户的 OneAlert app key - 配置样例:
strategies: onealert: backend: onealert level: '012345' users: example: name: "例子" onealert: "abcdefg123123123123123"
静默(不报警)¶
- Backend 名字:
- noop
- 报警策略中需要配置的参数:
- 无
- 用户中需要配置的参数:
- 无
- 配置样例:
strategies: indicator: backend: noop level: '0123456' users: example: name: "例子"
注解
通常用作最低优先级的报警。静默的报警会出现在 Web UI 和 BitBar Plugin 中。
BitBar Plugin 的插件配置可以参考 Web UI 首屏的说明。
编写规则简介¶
规则文件的组织¶
进行报警判定的规则是由 Clojure 语言编写的,放在 rules
文件夹中。
这些规则将由 Riemann 进行执行, Riemann 会接受从 transfer 发来的指标,交给编写的规则进行判定,然后再发送给 alarm 进行报警。
规则的 .clj
文件需按照约定放在单层的文件夹下,并且 namespace 需要与文件夹和文件名对应。
文件中所有以 -rules
结尾的公开变量都会被当做 riemann 流来做判定。
Copy & Paste 的正确姿势¶
因为通常规则文件长得都差不多,所以推荐找一个附带的规则 Copy & Paste 一下,
比如,我想添加一个 foo/bar.clj
作为 foo 服务下关于 bar 的监控,
那么就随便找一个文件夹中的规则(最外层的不行!),然后修改 ns 为 foo.bar
,
然后挑一个规则复制进来,规则的名字改成 foo-bar-rules
,然后按照需求修改规则就好了。
(ns infra.memcache ; 这里要修改
(:use riemann.streams
agent-plugin
alarm))
(def infra-memcache-rules ; 这里名字要修改
(where (host #"cache\d+$") ; 规则当然也要改
(plugin-dir "memcache")
(plugin "net.port.listen" 30 {:port 11211})
(where (and (service "net.port.listen")
(= (:port event) 11211))
(by :host
(judge-gapped (< 1) (> 0)
(alarm-every 2 :min
(! {:note "memcache 端口不监听了!"
:level 0
:groups [:operation :api]})))))))
修改成
(ns foo.bar
(:use riemann.streams
agent-plugin
alarm))
(def foo-bar-rules ; 随便叫什么但是一定要以 -rules 结尾
(where (host "host-which-run-foo-bar")
(plugin-dir "foo/bar") ; 要运行 foo/bar 目录里的插件
(plugin "net.port.listen" 30 {:port 12345}) ; 运行 net.port.listen 插件,30秒一次,以及参数
(where (and (service "net.port.listen")
(= (:port event) 12345)) ; 匹配 net.port.listen 插件收集的 metric
(by :host ; 按照 host 分成子流(这个例子里就只有一个 host 所以并不会分)
(judge-gapped (< 1) (> 0) ; 根据条件设置事件的 :state
(alarm-every 2 :min ; 如果是 :problem 每2分钟发一次报警。
(! {:note "foobar 服务端口不监听了!"
:level 0
:groups [:operation :api]})))))))
commit 然后 push 上去就会生效了。
事件流的组织¶
所有的事件汇集到 Riemann 之后,你看到的是一个由所有机器、服务收集上来的指标组成的一个单一的事件流,
或者说,每一个 (def xxx-yyy-rules ...)
都能够看到整个集群(或者n个集群,看你是怎么部署的了)所有的指标,
你需要做的是通过过滤、变换等等手段最后将你关心的东西过滤出来,判定问题并告诉 alarm。
发送报警¶
流向 发送报警(!) 流的事件都会发送到 alarm 进行报警。
但是不能直接把事件喂给 发送报警(!) ,因为
发送报警(!) 并不知道这个事件是正常还是有问题的状态, 所以需要指定事件的状态是
:ok
还是 :problem
。 通常可以用 judge 和
judge-gapped 这两个流来完成。
Riemann 提供的文档¶
在 Riemann 中可用的流不仅仅是这里介绍的,还可以参考 Riemann 官方文档 ,还有很多不常用函数/流在里面有介绍。
过滤事件¶
判定和发送报警¶
发送报警(!)¶
(! m)
创建一个发送报警的流,流经这个流的事件都会被发到 alarm 产生报警。 接受一个 map 做参数,map 中需要可以指定如下的参数:
Key | 类型 | 意义 |
---|---|---|
:note | string | 报警标题 [1] |
:level | int | 报警级别 [2] |
:event? | bool | 事件类型?[3] |
:expected | float | 期望的正常值 [4] |
:outstanding-tags | vector | 区分报警的 tags [5] |
:groups | vector | 将报警发送到报警组 [6] |
:meta | assoc | 元信息[#]_ |
[1] | 标题对于一个特定的报警是不能变的,不要把报警的数据编码在这里面! |
[2] | 约定 0 级别最高,最小是15,不过一般来说用不到那么多级别。 报警级别影响报警方式。 |
[3] | 期望的正常值。 这个值暂时没有用到,但是也最能填上。 |
[4] | 可选 ,默认是 false 。事件类型只会发送报警,不会记录和维护状态,无法在 alarm 中看到。 |
[5] | 可选 ,默认为事件中所有的 tag。在这里指定的 tag 值的组合如果不一样, 就会被 alarm 当做不同的报警分别追踪 |
[6] | 报警组的配置请参考 报警人员和组配置 |
[7] | 这里的信息会不加修改直接发送给 alarm,在 alarm 的 API 中可以看到这个信息 |
样例:
(! {:note "服务器炸了!"
:level 1
:event? false
:expected 0
:outstanding-tags [:region]
:groups [:operation :boss]}
:meta {:graph "http://path.to.graph/graph1"})
judge*¶
(judge* c & children)
设置事件的状态。 c
是接受事件作为参数的函数。
c
返回值为 true
则会将事件的 :state
设置成 :problem
,否则会设置成 :ok
(judge* #(> (:metric %) 1)
(! ...))
judge¶
(judge c & children)
参见 judge* ,这里 c 是形如 (> 1.0)
的 form。
(judge (> 1)
(! ...))
注解
judge (> 1.0) ...)
会被重写成 (judge* #(> (:metric %) 1.0) ...)
judge-gapped*¶
(judge-gapped* rising falling & children)
设置事件的状态。 rising
是 OK -> PROBLEM 的条件, falling
是 PROBLEM -> OK 的条件
参见 judge*
(judge-gapped* #(> (:metric %) 10) #(< (:metric %) 1)
(! ...))
judge-gapped¶
(judge-gapped rising falling & children)
参见 judge-gapped* ,这里 rising
和 falling
是形如 (> 1.0)
的 form。
(judge-gapped (> 10) (< 1)
(! ...))
alarm-every¶
(alarm-every dt unit & children)
用于对报警事件限流,通常接在 发送报警(!) 流前面。
当事件的 :state
是 :problem
时,每 dt
时间向下传递一次。时间的单位由 unit
决定。
当事件的 :state
由 :problem
变成 :ok
时,向下传递一次。
其他时间不放行。
unit
可以是 :sec
:secs
:second
:seconds
:min
:mins
:minute
:minutes
:hour
:hours
。
(judge (> 1)
(alarm-every 1 :min
(! ...)))
调度插件¶
plugin-dir¶
(plugin-dir & dirs)
所有流过这个流的事件中的主机都会执行 dirs
指定的目录中的插件。
(where (host #"^redis-.*$")
(plugin-dir "redis"))
这个例子中,当 redis-foo
主机的第一个事件到达这个流的时候,
就会被调度规则仓库中 plugin/redis
目录中的的插件。
注解
因为 agent 总会发送 agent.alive
事件,所以不用担心插件无法调度的情况。
plugin¶
(plugin metric step args)
为机器指定一个接受参数的插件。
其中 metric
是插件名称,在规则仓库中的 plugin/<metric>
中。
插件会每个 step
秒调度一次,并且将 args
的参数通过标准输入传递给插件。
(where (host "url-checker")
(plugin "url.check" 30 {:name "example",
:url "http://example.com",
:timeout 15}))
这个例子中会在 url-checker
这个主机上每隔30秒执行一下 url.check
插件,指定的参数也会传递给插件。
具体参数是怎么传递的请参考 编写插件 。
注解
在调度持续执行的插件时,这里的 step
须填 0
数据聚集¶
aggregate*¶
(aggregate* f & children)
接受事件的数组,变换成一个事件。 f
函数接受事件的值的数组作为参数,返回一个计算过后值,
然后将 aggregate
会将这个值替换掉最后一个事件中的值,并向下传递这个事件。
希望计算一组数据的和、平均、方差…… 等等的话就需要这个流。
aggregate
会将每个事件的值按照顺序写在变换后的事件的 description
里,
如果事件中有 :aggregate-desc-key
这个 key 那么每个事件的信息会用这个值来标识,否则使用 :host
来标识。
常接在各种窗口后面。
(fixed-time-window 60
(aggregate* #(apply + %)
(judge (> 100)
(! ...))))
aggregate¶
(aggregate f & children)
与 aggregate* 一样, 区别是 f
接受可变长参数,
而不是一个 vector( (f & vals)
而不是 (f vals)
)
例子参见 复杂需求 。
->difference¶
(->difference & children)
接受事件的数组,变换成另外一个事件数组。 新的事件数组中,每一个事件的监控值是之前相邻两个事件监控值的差。
如果你的事件是一个一直增长的计数器,那么用这个流可以将它变成每次实际增长的值。
(moving-time-window 60
(->difference
(aggregate maxpdiff
(judge-gapped (|>| 0.5) (|<| 0.1)
(! ...)))))
maxpdiff¶
(maxpdiff & m)
计算列表中最后一个点相对之前的点的最大变化率(MAX Percentage DIFFerence), 与 aggregate 搭配使用。
计算变化率时总是使用两个点中的最小值做分母, 所以由1变到2的变化率是 1.0, 由2变到1的变化率是 -1.0 (而不是 -0.5)
|>| 和 |<|¶
(|>| & args)
(|<| & args)
将 args
取绝对值后进行比较。
通常你不会关心变化率的符号(变化方向),只对绝对值感兴趣。
在 judge 和 judge-gapped 上可以用这个函数作比较。
参见 ->difference 上的例子。
Riemann 自带的窗口函数¶
(fixed-event-window n & children)
(fixed-time-window n & children)
(moving-event-window n & children)
(moving-time-window n & children)
窗口函数会收集传下来的事件并缓存住,然后按照指定的规则将缓存住的事件以数组(vector)向下传递。
类型 | 解释 |
---|---|
fixed event | 收集过去 n 个事件后向下传递,然后再收集下一组 n 个事件 |
moving event | 维护一个 n 个事件的滑动窗口,每到达一个新事件后将过去 n 个事件向下传递。 |
fixed time | 收集过去 n 秒内的事件,超时后向下传递,然后再收集下一组 n 秒内收集的事件。 |
moving time | 维护一个 n 秒的滑动窗口,每到达一个新事件将过去 n 秒内的事件向下传递 |
参考 ->difference 中的代码样例
另外可参考 Riemann 官方文档 。
group-window¶
(group-window group-fn & children)
将事件分组后向下传递,类似 Riemann 自带的窗口函数 ,但不使用时间或者事件个数进行切割,
而是通过 (group-fn event)
的值进行切割。 (group-fn event)
的值会被记录下来,
每一次出现重复值的时候,会将当前缓存住的事件数组向下传递。
比如你有一组同质的机器,跑了相同的服务,但是机器名不一样,可以通过
(group-window :host
...)
将事件分组后处理(e.g. 对单台的容量求和获得总体容量)
举例:一个事件流中的事件先后到达,其中 :host
的值如下
a b c d b a c a b
那么会被这个流分成
[a b c d] [b a c]
分成 2 次向下游传递,最后 的 [a b]
因为还没有重复事件到达所以还会在缓冲区内等待。
slot-window¶
(slot-window slot-fn fields & children)
收集指定的几个事件并打包向下传递。事件的特征由 (slot-fn event)
提取,
并与 fields
中的的定义匹配,如果 fields
中的所有条件匹配的事件都收集到了,
则打包向下传递并开始下一轮收集。
fields
是形如
{:key1 "value1", :key2 "value2"}
的 map, :key1
和 :key2
是自己定义的,用于引用匹配后的事件,
"value1"
和 "value2"
是希望匹配的 (slot-fn event)
的值,与之相等的事件会被放到相应的槽中。
slot-window
会向下传递形如
{:key1 event1, :key2 event2}
的 map,其中 event1
和 event2
是匹配到的事件。
与 group-window 相反,group-window 收集一组同质的事件,
slot-window
用于收集一组异质的事件。
当 slot-window
遇到重复的事件但是还没有满足向下传递的条件时,新的事件会替换掉缓存住的已有事件。
常用于收集同一个资源不同的指标用于复杂的判定。
比如有一个服务,同时收集了错误请求量和总量,希望按照错误数量在一定之上后按照错误率报警
(slot-window :service {:error "app.req.error"
:count "app.req.count"}
; 此时会有形如 {:error {:service "app.req.error", ...},
; :count {:service "app.req.count", ...}} 的事件传递下来
; 构造出想要的 event
(slot-coalesce {:service "app.req.error_rate"
:metric (if (> error 100) (/ error count) -1)}
(judge (> 0.5)
(runs :state 5
(alarm-every 2 :min
(! ...))))))
slot-coalesce¶
(slot-coalesce ev' & children)
对 slot-window 的结果进行计算,并构造出单一的事件。
ev'
是构造出的新事件的模板,这里的值会被复制到新事件中,
并且在模板中可以直接引用 slot-window 中定义的事件的值。
以 slot-window 中的代码为例,表达式中可以直接用如下的约定引用槽中的值:
变量名 | 意义 |
---|---|
error , count |
slot-window 中定义的槽捕捉到的事件的监控值 |
ev:error , ev:count |
slot-window 中定义的槽捕捉到的事件本身 |
event |
slot-window 传递下来的 map 本身 |
看门狗¶
看门狗可以实现在指定的事件一定时间之后没有上报之后主动报警。
feed-dog¶
(feed-dog ttl)
(feed-dog ttl outstanding-tags)
喂狗。当有事件流向这个流,看门狗就会静默。如果 ttl
秒之后没有再次喂狗,就会触发 watchdog 报警,之前缓存住的事件会在 watchdog 流后面出现,这时就可以报警了。
事件的区分与 alarm 的区分是一致的, outstanding-tags
的说明可以参照 发送报警(!) 。
watchdog¶
(watchdog & children)
过滤出看门狗事件,用于之后的判定和报警。
在事件超时之后,超时的事件会用特殊的方式包装起来,需要用这个流来提取出来。
这个流需要直接接在最外层,即需要看到集群内所有的事件。会将解包的过期事件向下传递。
(sdo
(where (service "agent.alive")
(feed-dog 90))
(watchdog
(where (service "agent.alive")
(! {:note "Agent.Alive 不上报了!"
:level 2
:expected 1
:outstanding-tags [:region]
:groups [:operation]})))
警告
watchdog
流下出现的事件都是已经过期或者恢复的报警,
state
已经帮你设置成 :problem
或者 :ok
了,并且一个周期内只会出现一次,
所以请不要接 (judge ...)
流或者 (runs 2 ...)
之类过滤的流,直接用 where
过滤出想要的结果喂给 发送报警(!) 就可以了。
其他需要用到的流¶
copy¶
(copy from to & children)
将事件的 from
字段复制到 to
字段。
通常接在 aggregate 后面,用于修正 :host
。
(aggregate +
(copy :region :host
(...))
sdo¶
(sdo & children)
将多个子流合并成一个流,传下来的事件会给每一个子流派发。
因为 def
后面只能接一个 form, 如果需要将多个流接在同一个 def
后面,就需要 sdo
来包裹一下。
(sdo
(rule1 ...)
(rule2 ...))
->waterfall¶
(->waterfall & children)
用这个将规则改写成瀑布流的方式,会比较好看。 仅适用于每个规则下仅有一个子流的情况。
比如:
(->waterfall
(where (service "nvgpu.gtemp"))
(by :host)
(copy :id :aggregate-desc-key)
(group-window :id)
(aggregate max)
(judge-gapped (> 90) (< 86))
(alarm-every 2 :min)
(! {:note "GPU 过热"
:level 1
:expected 85
:outstanding-tags [:host]
:groups [:operation :devs]}))
与如下代码等价:
(where (service "nvgpu.gtemp")
(by :host
(copy :id :aggregate-desc-key
(group-window :id
(aggregate max
(judge-gapped (> 90) (< 86)
(alarm-every 2 :min
(! {:note "GPU 过热"
:level 1
:expected 85
:outstanding-tags [:host]
:groups [:operation :devs]}))))))))
注解
并没有推荐这样做,要不要看个人喜好就好。
编写插件¶
插件分为两种,一种接受参数,另外一种不接受。
带参数的插件¶
所有接受参数的插件都在 plugin
目录里,命名需要跟报告的
metric 一致, 比如 plugin/net.port.listen
会上报
net.port.listen
这个 metric 。 接受参数的插件需要在在运行的时候通过
stdin
读取 json 格式的参数:
[
{"_metric": "net.port.listen", "_step": 30, "port": 6379},
{"_metric": "net.port.listen", "_step": 30, "port": 3306}
]
警告
注意这里是 object 列表,不是单一的 object
参数是在规则配置中指定的,比如
(def mysql-and-redis-rules
(where (host "mysql-and-redis-host")
(plugin "net.port.listen" 30 {:port 3306})
(plugin "net.port.listen" 30 {:port 6379})
(your-other-rules ...)))
这里 plugin 的参数分别是插件名字、采集周期(step)、插件的参数。
采集周期如果是 0
则代表这个插件是持续运行的,agent 不会设置超时时间将插件杀死。
插件名字和采集周期会在参数里以 _metric
和 _step
作为 key 传递进去。
之后,插件需要输出如下格式的 json :
[
{
"endpoint": "mysql-and-redis-host",
"metric": "net.port.listen",
"value": 1,
"timestamp": 1431349763,
"tags": {"port": "3306"}
},
{
"endpoint": "mysql-and-redis-host",
"metric": "net.port.listen",
"value": 0,
"timestamp": 1431349763,
"tags": {"port": "6379"}
}
]
插件参数(比如上个例子中的 port
)可以任意添加。
警告
注意这里是 object 的 list,不是单一的 object。
文档中为了清晰,json 是多行的,实际编写插件的时候 同一个 list 请保持单行输出 , 因为 Satori 接受多行的输出(每一行是一个 list),需要通过换行来作为边界。
Key | Value |
---|---|
endpoint | 可选 [1] ,机器名,在 riemann 对应事件的 :host 。 |
tags | 可选 [2] ,标签。在 riemann 中会直接附加到 event 上。 |
timestamp | 时间戳,事件发生的时间,以秒为单位的 UNIX 时间戳。 |
metric | 监控项的名字。在 riemann 中对应事件的 :service 表示。 |
value | 监控项的值。在 riemann 中对应事件的 :metric 。 |
[1] | 缺失则 agent 会按照自身配置填充这个值。 如果不需要控制这个值推荐省略让 agent 填充。 |
[2] | 缺失则不会附加额外的 tag 。 |
注解
插件的输出格式基本与 Open-Falcon 的插件格式相同,但是 tags 是个 object, 不是拼接的字符串。
插件中的 Key 跟 riemann 中对应不上是一个比较恼人的坑,需要特别注意。
无参数的插件¶
无参数插件的命名需要类似于 30_nginx.py
这样的, _
前面需要是数字,表示采集的周期。 采集周期如果是 0
则代表这个插件是持续运行的,agent 不会设置超时时间将插件杀死。
插件输出的格式跟跟带参数插件格式一致(上面的 json)。
之后可以在规则中配置使用这个插件(假设你把这个插件放到了 plugin/nginx
目录)
(def some-nginx-related-rules
(where (host "nginx-machine")
(plugin-dir "nginx")
(your-other-rules ...)))
持续输出的插件¶
插件可以输出多行 json,输出的 json 会马上被 agent 收集起来并上报。
此类插件可以将采集周期 step
设置成 0
,agent
会认为插件没有超时时间,并且只会在插件失败后才会重新调度。
Agent 存活¶
- agent
意义: Agent 存活 取值: 1 Tags: 无
CPU 占用¶
- cpu.idle
意义: CPU 空闲时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 - cpu.busy
意义: CPU 忙时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
与
cpu.idle
的和总是100- cpu.user
意义: 用来运行用户态代码的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
通常
cpu.user
占用意味着正在运行业务逻辑- cpu.nice
意义: 用来运行低优先级进程的用户态代码的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
可以通过
nice
命令启动低优先级进程,或者top
中的 renice 功能调整优先级低优先级进程会被正常进程抢占,所以考虑 CPU 占用时可以当做是空闲
- cpu.system
意义: 用来运行内核态代码的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
内核态代码通常在进行系统调用、中断、换页的时候执行
- cpu.iowait
意义: 用来等待 IO 的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
IO wait 不意味着 CPU 被浪费掉了,只是现在只能等待做 IO 的进程,此时如果有需要 CPU 时间的进程还是会被调度的,跟 idle 有点像
- cpu.irq
意义: 用来处理中断的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
这里是指不能被抢占的中断处理,实际上大部分的中断处理的部分会放在软中断中处理,软中断可以被抢占
- cpu.softirq
意义: 用来处理软中断 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
软中断通常是中断的『bottom half』,是实际的中断处理部分,比如网卡在接受数据包并传给网络栈的 CPU 会被计算在软中断里(也是最常见的软中断占满的原因)
- cpu.steal
意义: 被宿主机截取的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
半虚拟化环境中存在,宿主机会根据情况不再调度当前虚拟机,此时的 CPU 时间会被计算成 steal
并不是每个 VM 环境都会看到这个 metric,有一些环境会把 steal 算在 system 里
- cpu.guest
意义: 用来执行虚拟机代码的 CPU 时间百分比 取值: 0 - 100,与 CPU 核数无关 Tags: 无 注解
虚拟化环境的宿主机中存在,当 CPU 在执行 VM 内的代码时算在 guest 里
- cpu.switches
意义: 进程上下文切换数 取值: 0 - 无上界,整数 Tags: 无 注解
单调递增(COUNTER 类型)
当一个 CPU 核由运行着一个进程切换到运行另一个进程时,
cpu.switches
加1
内存占用¶
- mem.memtotal
意义: 内存总量 取值: 0 - 无上限,整数,单位:字节 Tags: 无 注解
memtotal
真的会在一些极端情况下变的,并不是无意义的值- mem.memused
意义: 内存用量 取值: 0 - 无上限,整数,单位:字节 Tags: 无 注解
这个值不包括 page cache 和 buffer 是通过
memtotal
-memusable
算出来的- mem.free
意义: 空闲内存 取值: 0 - 无上限,整数,单位:字节 Tags: 无 注解
这个值不包括 page cache 和 buffer, 是真正没有被利用的内存
- mem.buffers
意义: 用作 buffer 的内存 取值: 0 - 无上限,整数,单位:字节 Tags: 无 - mem.cached
意义: 用作 page cache 的内存 取值: 0 - 无上限,整数,单位:字节 Tags: 无 - mem.memusable
意义: 可用内存 取值: 0 - 无上限,整数,单位:字节 Tags: 无 注解
这个值包括 page cache、buffer 和 free。
- mem.swaptotal
意义: 交换内存总量 取值: 0 - 无上限,整数,单位:字节 Tags: 无 - mem.swapused
意义: 交换内存用量 取值: 0 - 无上限,整数,单位:字节 Tags: 无 - mem.swapfree
意义: 交换内存剩余 取值: 0 - 无上限,整数,单位:字节 Tags: 无 - mem.memfree.percent
意义: 内存剩余百分比 取值: 0 - 100.0 Tags: 无 - mem.memused.percent
意义: 内存用量百分比 取值: 0 - 100.0 Tags: 无 - mem.swapfree.percent
意义: 交换内存剩余百分比 取值: 0 - 100.0 Tags: 无 - mem.swapused.percent
意义: 交换内存用量百分比 取值: 0 - 100.0 Tags: 无
网络相关¶
警告
这里的表述模糊的指标具体什么情况会 +1 是网卡驱动程序决定的,需要看源码确定
- net.if.in.bytes
意义: 网卡成功收到的字节数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.in.packets
意义: 网卡成功收到的包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.in.errors
意义: 网卡收到的错误包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.in.dropped
意义: 网络栈丢掉的包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}注解
这里统计的包已经被网卡接收到了,但是在处理过程中被丢掉了。
比如内存不足无法分配 skb。
- net.if.in.fifo.errs
意义: 因为接收缓冲区满丢掉的包 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.in.frame.errs
意义: 网卡接收到的错误帧数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.in.compressed
意义: 网卡接收到的压缩包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}注解
只对支持压缩的网卡有意义(比如 PPP 的网卡)
- net.if.in.multicast
意义: 网卡收到的多播包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.bytes
意义: 网卡成功发送的字节数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.packets
意义: 网卡成功发送的包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.errors
意义: 网卡发送错误的包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.dropped
意义: 网卡丢弃的发送包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.fifo.errs
意义: 网卡发送错误的包数量(FIFO相关) 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.collisions
意义: 网卡发送包在链路上冲突的数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.carrier.errs
意义: 网卡发送包因为信号问题失败的数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.out.compressed
意义: 网卡发送的压缩过的包的数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}注解
只对支持压缩的网卡有意义(比如 PPP 的网卡)
- net.if.total.bytes
意义: 网卡发送/接收的字节数总和 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.total.packets
意义: 网卡发送/接收的数据包总和 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.total.errors
意义: 网卡发送/接收的错误包总和 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}- net.if.total.dropped
意义: 网卡发送/接收时被丢弃的包总和 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"iface": " 网卡名
"}
机器负载(Load)¶
注解
Load 的意义是『在取样的时候处于运行和可以运行的线程数量』
D状态(Uninterruptable sleep)也算在处于运行状态
- load.1min
意义: 1分钟平均负载 取值: 0 - 无上限,浮点数 Tags: 无 - load.5min
意义: 5分钟平均负载 取值: 0 - 无上限,浮点数 Tags: 无 - load.15min
意义: 15分钟平均负载 取值: 0 - 无上限,浮点数 Tags: 无
磁盘利用率¶
计算值(对标 iostat 工具,比较容易使用)¶
- disk.io.read_bytes
意义: 磁盘读取的字节数(较上一次取样) 取值: 0 - 无上限,整数,单位:Bytes Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.write_bytes
意义: 磁盘写入的字节数(较上一次取样) 取值: 0 - 无上限,整数,单位:Bytes Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.avgrq_sz
意义: 平均 IO 大小 取值: 0 - 无上限,单位:Sectors/IORequest Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.avgqu_sz
意义: 平均 IO 队列长度 取值: 0 - 无上限,单位:Sectors Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.await
意义: 平均 IO 耗时 取值: 0 - 无上限,单位:ms Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.svctm
- 已废弃的指标,不要再使用了
原始值(/proc/diskstat 的原始数据)¶
- disk.io.read_requests
意义: 磁盘完成的读请求数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
合并的多个请求在这里算1个
- disk.io.read_merged
意义: 被合并的读请求数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
在队列中的请求如果相邻会被合并成一个大的请求
- disk.io.read_sectors
意义: 磁盘完成读取的扇区数 取值: 0 - 无上限,整数,单调递增(COUNTER) Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.msec_read
意义: 磁盘花在读取上的总时间 取值: 0 - 无上限,整数,单位:毫秒(ms),单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.write_requests
意义: 磁盘完成的写请求数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
合并的多个请求在这里算1个
- disk.io.write_merged
意义: 被合并的写请求数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
在队列中的请求如果相邻会被合并成一个大的请求
- disk.io.write_sectors
意义: 磁盘完成写入的扇区数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.msec_write
意义: 磁盘花在写入上的总时间 取值: 0 - 无上限,整数,单位:毫秒(ms),单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}- disk.io.ios_in_progress
意义: 当前正在处理的请求数 取值: 0 - 无上限,整数,单位:毫秒(ms) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
或者叫『排队中的请求数』
- disk.io.msec_total
意义: 磁盘花在处理请求上的总时间 取值: 0 - 无上限,整数,单位:毫秒(ms),单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
或者叫『请求队列不为空的总时间』
- disk.io.msec_weighted_total
意义: 磁盘花在处理请求上的总加权时间 取值: 0 - 无上限,整数,单位:毫秒(ms),单调递增(COUNTER类型) Tags: {"device": " 设备路径, 比如 /dev/sda
"}注解
这里的值的意义是『所有请求的总等待时间』
每一次请求结束后,这个值会增加这个请求的处理时间乘以当前的队列长度
内核参数¶
- kernel.maxfiles
意义: 整个系统可以打开的 fd 数量 取值: 0 - 无上限,整数 Tags: 无 - kernel.files.allocated
意义: 整个系统已经打开的 fd 数量 取值: 0 - kernel.maxfiles
,整数Tags: 无 - kernel.files.left
意义: 整个系统剩余可分配的 fd 数量 取值: 0 - kernel.maxfiles
,整数Tags: 无 - kernel.maxproc
意义: 整个系统可以创建的进程数量 取值: 0 - 无上限,整数 Tags: 无 注解
实际上指的是 Task 数量,Linux 并不会严格的区分进程/线程, 而是统一当做 Task 来调度。
这个值也可以同时是最大的 pid 值。
TCP 统计¶
注解
这里的指标都是 ss -s
的输出
- ss.estab
意义: 已经建立的 TCP Socket 数量 取值: 0 - 无上限,整数 Tags: 无 - ss.closed
意义: 已经建立的 TCP Socket 数量 取值: 0 - 无上限,整数 Tags: 无 注解
或者是『处于
CLOSE_WAIT
、LAST_ACK
、TIME_WAIT
状态的 TCP Socket 数量』- ss.orphaned
意义: 没有用户态 fd 的 TCP Socket 数量 取值: 0 - 无上限,整数 Tags: 无 注解
或者是『处于
FIN_WAIT_1
、FIN_WAIT_2
、CLOSING
状态的 TCP Socket 数量』在用户态程序发送数据并关闭 fd,若此时这个 fd 关联的 socket 还没有将缓冲区内的数据全部发出去,则在内核中会存在一个 orphaned socket。
- ss.synrecv
意义: 处于 SYN_RECV
状态的 TCP Socket 数量取值: 0 - 无上限,整数 Tags: 无 - ss.timewait
意义: 处于 TIMEWAIT
状态的 TCP Socket 数量取值: 0 - 无上限,整数 Tags: 无
高级 TCP 指标¶
注解
这里的文档参(chao)考(xi)了很多 http://perthcharles.github.io/2015/11/10/wiki-netstat-proc 和 http://www.cnblogs.com/lovemyspring/articles/5087895.html 的内容
这里的大部分指标可以通过 netstat -st
命令获得
数据包统计¶
- TcpExt.EmbryonicRsts
意义: 在 SYN_RECV
状态收到带 RST/SYN 标记的包个数取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
SYN Cookies 功能¶
From Wikipedia:
SYN cookie 是一种用于阻止 SYN flood 攻击的技术。 这项技术的主要发明人 Daniel J. Bernstein 将 SYN cookies 定义为“TCP 服务器进行的对开始 TCP 数据包序列数字的特定选择”。 举例来说,SYN Cookies 的应用允许服务器当 SYN 队列被填满时避免丢弃连接。 相反,服务器会表现得像 SYN 队列扩大了一样。服务器会返回适当的 SYN+ACK 响应,但会丢弃 SYN 队列条目。 如果服务器接收到客户端随后的 ACK 响应,服务器能够使用编码在 TCP 序号内的信息重构 SYN 队列条目。
SYN Cookies 一般不会被触发,只有在 tcp_max_syn_backlog
队列被占满时才会被触发,
因此 SyncookiesSent
和 SyncookiesRecv
一般应该是0。
但是 SyncookiesFailed
值即使SYN Cookies 机制没有被触发,也很可能不为0。
这是因为一个处于 LISTEN
状态的 socket 收到一个不带 SYN 标记的数据包时,就会调
用 cookie_v4_check()
尝试验证 cookie 信息。
而如果验证失败,SyncookiesFailed
次数就加1。
- TcpExt.SyncookiesSent
意义: 使用 SYN Cookie 发送的 SYN/ACK 包个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.SyncookiesRecv
意义: 收到携带有效 SYN Cookie 信息的包的个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.SyncookiesFailed
意义: 收到携带无效 SYN Cookie 信息的包的个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
TIME_WAIT 回收¶
TIME_WAIT
状态是 TCP 协议状态机中的重要一环,服务器设备一般都有非常多处于 TIME_WAIT
状态的 socket。
如果是在主要提供 HTTP 服务的设备上,TW
值应该接近 TcpPassiveOpens
值。
一般情况下,sysctl_tcp_tw_reuse
和 sysctl_tcp_tw_recycle
都是不推荐开启的, 这里解释了为什么 。
所以 TWKilled
和 TWRecycled
都应该是0。
同时 TCPTimeWaitOverflow
也应该是0,否则就意味着内存使用方面出了大问题。
- TcpExt.TW
意义: 经过正常的的超时结束 TIME_WAIT
状态的 socket 数量取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TWRecycled
意义: 通过 tcp_tw_reuse
机制结束TIME_WAIT
状态的 socket 数量取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
注意这里命名的不一致……
这是 Linux 内核的坑,所以保留下来了
- TcpExt.TWKilled
意义: 通过 tcp_tw_recycle
机制结束TIME_WAIT
状态的 socket 数量取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 警告
你不应该关心这个值,
tcp_tw_recycle
机制即将被取消。注解
注意这里命名的不一致……
这是 Linux 内核的坑,所以保留下来了
只有在
net.ipv4.tcp_tw_recycle
开启时,这个值才会增加。- TcpExt.TCPTimeWaitOverflow
意义: 因为超过限制而无法分配的 TIME_WAIT
socket 数量取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
这个数量可以在
net.ipv4.tcp_max_tw_buckets
调整
超时重传相关¶
From Wikipedia:
Whenever a packet is sent, the sender sets a timer that is a conservative estimate of when that packet will be acked. If the sender does not receive an ack by then, it transmits that packet again. The timer is reset every time the sender receives an acknowledgement. This means that the retransmit timer fires only when the sender has received no acknowledgement for a long time. Typically the timer value is set to \({\displaystyle {\text{smoothed RTT}}+\max(G,4\times {\text{RTT variation}})}\) where \({\displaystyle G}\) is the clock granularity. Further, in case a retransmit timer has fired and still no acknowledgement is received, the next timer is set to twice the previous value (up to a certain threshold). Among other things, this helps defend against a man-in-the-middle denial of service attack that tries to fool the sender into making so many retransmissions that the receiver is overwhelmed.
RTO 超时对 TCP 性能的影响是巨大的,因此关心 RTO 超时的次数也非常必要。
- TcpExt.TCPTimeouts
意义: RTO timer第一次超时的次数,仅包含直接超时的情况 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
3.10 中的 TLP 机制能够减少一定量的
TCPTimeouts
数,将其转换为快速重传。 关于TLP的原理部分,可参考 这篇wiki 。- TcpExt.TCPSpuriousRTOs
意义: 通过F-RTO机制发现的虚假超时个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPLossProbes
意义: Probe Timeout(PTO) 导致发送 Tail Loss Probe (TLP) 包的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPLossProbeRecovery
意义: 丢失包刚好被TLP探测包修复的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPRenoRecovery:
意义: 进入 Recovery 阶段的次数,对端不支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPSackRecovery:
意义: 进入 Recovery 阶段的次数,对端支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPRenoRecoveryFail:
意义: 先进入 Recovery 阶段,然后又 RTO 的次数,对端不支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPSackRecoveryFail:
意义: 先进入 Recovery 阶段,然后又 RTO 的次数,对端支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPRenoFailures
意义: 先进入 TCP_CA_Disorder 阶段,然后又 RTO 超时的次数,对端不支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPSackFailures
意义: 先进入 TCP_CA_Disorder 阶段,然后又 RTO 超时的次数,对端支持 SACK 选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPLossFailures
意义: 先进入 TCP_CA_Loss 阶段,然后又 RTO 超时的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPSACKReneging
意义: 收到的不正常的 SACK 包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
重传数量¶
- TcpExt.TCPFastRetrans
意义: 成功快速重传的 skb 数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPForwardRetrans
意义: 成功 ForwardRetrans 的 skb 数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
Forward Retrans 重传的序号高于 retransmit_high 的数据 retransmit_high 目前的理解是被标记为 lost 的 skb 中,最大的 end_seq 值
- TcpExt.TCPSlowStartRetrans
意义: 成功在Loss状态发送的重传 skb 数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
这里仅记录非 RTO 超时进入 Loss 状态下的重传数量。
目前找到的一种非 RTO 进入 Loss 状态的情况是:
tcp_check_sack_reneging()
函数发现 接收端违反(renege)了之前的 SACK 信息时,会进入 Loss 状态。- TcpExt.TCPLostRetransmit
意义: 丢失的重传 skb 数量,没有 TSO(TCP Segment Offloading) 时,等于丢失的重传包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPRetransFail
意义: 尝试 FastRetrans、ForwardRetrans、SlowStartRetrans 重传失败的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
注解
这些计数器统计的重传包,都 不是 由于 RTO 超时导致进行的重传。
FastOpen¶
TCP FastOpen(TFO) 是 Google 提出来减少三次握手开销的技术, 核心原理就是在第一次建连时服务端计算一个 cookie 发给 client,之后客户端向 服务端再次发起建连请求时就可以携带 cookie ,如果 cookie 验证通过, 服务端可以不等三次握手的最后一个 ACK 包就将客户端放在 SYN 包里面的数据传递给应用层。
在 3.10 内核中,TFO 由 net.ipv4.tcp_fastopen
开关控制,默认值为0(关闭)。
而且 net.ipv4.tcp_fastopen
目前也是推荐关闭的,
因为网络中有些中间节点会丢弃那些带有不认识的 option 的 SYN 包。
所以正常情况下这些值也应该都是0,当然如果收到过某些不怀好意带 TFO cookie 信息的 SYN 包,
TCPFastOpenPassive
计数器就可能不为0。
- TcpExt.TCPFastOpenActive
意义: 发送的带 TFO cookie 的 SYN 包个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPFastOpenPassive
意义: 收到的带 TFO cookie 的 SYN 包个数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPFastOpenPassiveFail
意义: 使用TFO技术建连失败的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPFastOpenListenOverflow
意义: TFO 请求数超过 listener queue 设置的上限则加1 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPFastOpenCookieReqd
意义: 收到一个请求 TFO cookie 的 SYN 包时加1 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
MD5¶
TCP MD5 Signature 选项是为提高 BGP Session 的安全性而提出的,详见 RFC 2385 。
因此内核中是以编译选项,而不是 sysctl 接口来配置是否使用该功能的。
如果内核编译是的 CONFIG_TCP_MD5SIG
选项未配置,则不会支持 TCPMD5Sig,下面两个计数器也就只能是0
- TcpExt.TCPMD5NotFound
意义: 希望收到带MD5选项的包,但是包里面没有MD5选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPMD5Unexpected
意义: 不希望收到带MD5选项的包,但是包里面有MD5选项 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
DelayedACK¶
From Wikipedia:
TCP delayed acknowledgment is a technique used by some implementations of the Transmission Control Protocol in an effort to improve network performance. In essence, several ACK responses may be combined together into a single response, reducing protocol overhead. However, in some circumstances, the technique can reduce application performance.
Delayed ACK 是内核中默认支持的,但即使使用 Delayed ACK,每收到两个数据包也
必须发送一个ACK。所以 DelayedACKs
可以估算为发送出去的 ACK 数量的一半。
同时 DelayedACKLocked
反应的是应用与内核争抢 socket 的次数,
如果占 DelayedACKs
比例过大可能就需要看看应用程序是否有问题了。
- TcpExt.DelayedACKs
意义: 尝试发送 delayed ACK 的次数,包括未成功发送的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.DelayedACKLocked
意义: 由于用户态进程锁住了 socket,而无法发送 delayed ACK 的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.DelayedACKLost
意义: TODO暂时不理解准确含义 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPSchedulerFailed
意义: 如果在 delay ack 处理函数中发现 prequeue 还有数据,就加1。 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
这里 有一个看起来靠谱的解释:
Its the number of packets that are prequeued (partially completed tcp processing, waiting for a recvmsg to complete & send ack etc) when the delayed ack timer goes off. i.e I think, the thinking is this shouldnt happen, since the delayed ack timer really shouldnt go off - the receiver should have picked up the skb's and sent the acks. I'm probably off by a mile here..Dont know.这个值应该非常接近于零才正常
DSACK¶
该类型计数器统计的是收/发 DSACK 信息次数。
DSACKOldSent
+ DSACKOfoSent
可以当做是发送出的 DSACK 信息的次数,
而且概率上来讲 OldSent 应该占比更大。
同理 DSACKRecv 的数量也应该远多于 DSACKOfoRecv 的数量。
另外 DSACK 信息的发送是需要 net.ipv4.tcp_dsack
开启的,如果发现 sent 两个计数器为零,则要检查一下了。
一般还是建议开启 DSACK。
- TcpExt.TCPDSACKOldSent
意义: 如果收到的重复数据包序号比 rcv_nxt
小,则+1取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
rcv_nxt
代表接收端想收到的下一个序号- TcpExt.TCPDSACKOfoSent
意义: 如果收到的重复数据包序号比 rcv_nxt
大,则是一个乱序的重复数据包,则+1取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPDSACKRecv
意义: 收到的 old DSACK 信息次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
判断 old 的方法:DSACK 序号小于 ACK 号
- TcpExt.TCPDSACKOfoRecv
意义: 收到的 Ofo DSACK 信息次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
Ofo 是 Out-of-Order 的意思
- TcpExt.TCPDSACKIgnoredOld
意义: 当一个 DSACK block 被判定为无效,且设置过 undo_marker,则+1 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TcpExt.TCPDSACKIgnoredNoUndo
意义: 当一个 DSACK block 被判定为无效,且未设置 undo_marker,则+1 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
注解
关于 partial ACK 的完整内容可参考 RFC6582 ,这里摘要定义部分
In the case of multiple packets dropped from a single window of data, the first new information available to the sender comes when the sender receives an acknowledgment for the retransmitted packet (that is, the packet retransmitted when fast retransmit was first entered). If there is a single packet drop and no reordering, then the acknowledgment for this packet will acknowledge all of the packets transmitted before fast retransmit was entered. However, if there are multiple packet drops, then the acknowledgment for the retransmitted packet will acknowledge some but not all of the packets transmitted before the fast retransmit. We call this acknowledgment a partial acknowledgment.
Reorder¶
当发现了需要更新某条 TCP 流的 reordering 值(乱序值)时,以下计数器可能被使用到。
不过下面四个计数器为互斥关系,最少见的应该是 TCPRenoReorder
,毕竟 SACK 已经被
广泛部署使用了。
- TcpExt.TCPFACKReorder
意义: 使用 FACK 机制检测到的乱序次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
与TCPSACKReorder类似,如果同时启用了 SACK 和 FACK,就增加本计数器。
- TcpExt.TCPSACKReorder
意义: 使用 SACK 机制检测到的乱序次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
在
tcp_update_reordering()
中更新,当metric > tp->reordering
并且启用 SACK 但关闭 FACK 时,本计数器加1- TcpExt.TCPRenoReorder
意义: 使用 Reno 快速重传机制检测到的乱序次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
在
tcp_update_reordering()
中更新,当metric > tp->reordering
并且没有启用 SACK ,本计数器加1- TcpExt.TCPTSReorder
意义: 使用 TCP Timestamp 机制检测到的乱序次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack() -> tcp_fastretrans_alert() -> tcp_undo_partial() -> tcp_update_reordering()
Recovery 状态时,接收到到部分确认(snd_una < high_seq)时但已经 undo 完成(undo_retrans == 0)的次数。 数量上与 TCPPartialUndo 相等。
连接终止¶
- TCPAbortOnClose:
意义: 用户态程序在缓冲区内还有数据时关闭 socket 的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
此时会主动发送一个 RST 包给对端
- TCPAbortOnData:
意义: socket 收到未知数据导致被关闭的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
如果在
FIN_WAIT_1
和FIN_WAIT_2
状态下收到后续数据,或TCP_LINGER2
设置小于0,则计数器加1- TCPAbortOnTimeout:
意义: 因各种计时器(RTO/PTO/keepalive)的重传次数超过上限而关闭连接的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 - TCPAbortOnMemory:
意义: 因内存问题关闭连接的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
如果 orphaned socket 数量或者 tcp_memory_allocated 超过上限,则加1, 一般值为0。
- TCPAbortOnLinger:
意义: TCPAbortOnLinger 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
tcp_close()
中,因tp->linger2
被设置小于 0,导致FIN_WAIT_2
立即切换到CLOSE
状态的次数,一般值为0。- TCPAbortFailed:
意义: 尝试结束连接失败的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
如果在准备发送 RST 时,分配 skb 或者发送 skb 失败,则加1, 一般值为0
内存 Prune¶
- TcpExt.TCPMemoryPressures
意义: TCP 内存压力由“非压力状态”切换到“有压力状态”的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_enter_memory_pressure()
可能的触发点有:
- tcp_sendmsg()
- tcp_sendpage()
- tcp_fragment()
- tso_fragment()
- tcp_mtu_probe()
- tcp_data_queue()
- TcpExt.PruneCalled
意义: 因为 socket 缓冲区满而被 prune 的包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_data_queue() -> tcp_try_rmem_schedule()
慢速路径中,如果不能将数据直接复制到用户态内存,需要加入到 sk_receive_queue 前,会检查 receiver side memory 是否允许,如果 rcv_buf 不足就可能 prune ofo queue。此时计数器加1。
- TcpExt.RcvPruned
意义: RcvPruned 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_data_queue() -> tcp_try_rmem_schedule()
慢速路径中,如果不能将数据直接复制到用户态内存,需要加入到 sk_receive_queue 前,会检查 receiver side memory 是否允许,如果 rcv_buf 不足就可能 prune receive queue ,如果 prune 失败了,此计数器加1。
- TcpExt.OfoPruned
意义: 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_data_queue() -> tcp_try_rmem_schedule()
慢速路径中,如果不能将数据直接复制到用户态内存,需要加入到 sk_receive_queue 前,会检查 receiver side memory 是否允许,如果 rcv_buf 不足就可能 prune ofo queue ,此计数器加1。
PAWS [1] 相关¶
[1] | Protect Against Wrapping Sequence,TCP 序列号溢出保护 |
- TcpExt.PAWSPassive
意义: 三路握手最后一个 ACK 的 PAWS 检查失败次数。 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_v4_conn_request()
- TcpExt.PAWSActive
意义: 在发送 SYN 后,接收到 ACK,但 PAWS 检查失败的次数。 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_rcv_synsent_state_process()
- TcpExt.PAWSEstab
意义: 输入包 PAWS 失败次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数:
tcp_validate_incoming()
tcp_timewait_state_process()
tcp_check_req()
Listen相关¶
- TcpExt.ListenOverflows
意义: 三路握手最后一步完成之后,Accept 队列超过上限的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_v4_syn_recv_sock()
- TcpExt.ListenDrops
意义: 任何原因导致的丢弃传入连接(SYN 包)的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_v4_syn_recv_sock()
包括 Accept 队列超限,创建新连接,继承端口失败等
Undo 相关¶
- TcpExt.TCPFullUndo
意义: TCP 窗口在不使用 slow start 完全恢复的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_recovery()
Recovery 状态时,接收到到全部确认(snd_una >= high_seq)后且已经 undo 完成(undo_retrans == 0)的次数。
- TcpExt.TCPPartialUndo
意义: TCP 窗口通过 Hoe heuristic [2] 机制部分恢复的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_recovery()
Recovery 状态时,接收到到全部确认(snd_una >= high_seq)后且已经 undo 完成(undo_retrans == 0)的次数。
[2] | 我也不知道这是啥,从 netstat 里看到的 |
- TcpExt.TCPDSACKUndo
意义: 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_dsack()
Disorder状态下,undo 完成(undo_retrans == 0)的次数。
- TcpExt.TCPLossUndo
意义: 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack() -> tcp_fastretrans_alert() -> tcp_try_undo_loss()
Loss 状态时,接收到到全部确认(snd_una >= high_seq)后且已经 undo 完成(undo_retrans == 0)的次数。
快速路径与慢速路径¶
- TcpExt.TCPHPHits
意义: 包头预测命中的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_rcv_established()
如果有 skb 通过“快速路径”进入到
sk_receive_queue
上,计数器加1。 特别地,Pure ACK 以及直接复制到用户态内存上的都不算在这个计数器上。- TcpExt.TCPHPHitsToUser
意义: 包头预测命中并且 skb 直接复制到了用户态内存中的次数 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_rcv_established()
- TcpExt.TCPPureAcks
意义: 接收慢速路径中的 Pure ACK 数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack()
- TcpExt.TCPHPAcks
意义: 接收到进入快速路径的 ACK 包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无 注解
相关函数
tcp_ack()
未归类¶
- TcpExt.OutOfWindowIcmps
意义: 因为指定的数据已经在窗口外而被丢弃的 ICMP 包数量 取值: 0 - 无上限,整数,单调递增(COUNTER类型) Tags: 无
暂时无解释¶
暂时无解释:
TcpExt.LockDroppedIcmps
TcpExt.ArpFilter
TcpExt.TCPPrequeued
TcpExt.TCPDirectCopyFromBacklog
TcpExt.TCPDirectCopyFromPrequeue
TcpExt.TCPPrequeueDropped
TcpExt.TCPRcvCollapsed
TcpExt.TCPSACKDiscard
TcpExt.TCPSackShifted
TcpExt.TCPSackMerged
TcpExt.TCPSackShiftFallback
TcpExt.TCPBacklogDrop
TcpExt.TCPMinTTLDrop
TcpExt.TCPDeferAcceptDrop
TcpExt.IPReversePathFilter
TcpExt.TCPReqQFullDoCookies
TcpExt.TCPReqQFullDrop
TcpExt.TCPRcvCoalesce
TcpExt.TCPOFOQueue
TcpExt.TCPOFODrop
TcpExt.TCPOFOMerge
TcpExt.TCPChallengeACK
TcpExt.TCPSYNChallenge
TcpExt.TCPSpuriousRtxHostQueues
TcpExt.BusyPollRxPackets
端口监听¶
收集指定端口是否被监听
- 插件文件地址
- net.port.listen
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
port | 希望监听的端口 |
name | 可选 ,这个监控的名字,用来区分其他的端口监听监控 [1] |
[1] | port 也会放到 tags 里,所以 name 做成了可选的。
出于语义上的考虑,还是建议给一个名字,用名字来区分。 |
上报的监控值¶
- net.port.listen
意义: 指定的端口是否正在监听 取值: 0 和 1,分别表示没有监听和在监听 Tags: {"port": " 指定的端口
", "name": "指定的监控名字
"}
监控规则样例¶
(def infra-redis-rules
(sdo
(where (host #"^redis-.+$")
(plugin "net.port.listen" 30 {:port 6379 :name "redis-port"})
(where (and (service "net.port.listen")
(= (:name event) "redis-port"))
(by [:host :port]
(judge (< 1)
(runs 3 :state
(alarm-every 2 :min
(! {:note "Redis 端口不监听了"
:level 1
:expected 3
:groups [:operation :api]})))))))))
进程数量监控¶
收集满足过滤条件的进程数量
- 插件文件地址
- proc.num
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
cmdline | 可选 ,被监控进程的命令行正则表达式 [1] |
name | 可选 ,这个监控的名字,用来区分其他的监控,同时也会作为匹配进程名的依据 [2] |
[1] | 命令行是从 /proc/ pid /cmdline 读取并且将其中的 \x00 替换成空格后进行匹配。
匹配是任意位置的,需要限定从一开始匹配的话请在正则前面加上 ^ |
[2] | 进程名是匹配 /proc/ pid /status 中的 Name 项,有可能会有残缺,这里要注意。 |
注解
cmdline
和 name
必选其一。
若指定了 name
但没有指定 cmdline
,则会按照 name
匹配进程名;
若指定了 cmdline
但是没有指定 name
,则会匹配 cmdline
,同时 cmdline
也会加入 tag 用作区分。
若同时指定,则按照 cmdline
进行匹配,但是 name
的 tag 会使用指定的值。
上报的监控值¶
- proc.num
意义: 指定进程的数量 取值: 0 - 无上限,整数 Tags: {"cmdline": " 指定的 cmdline
", "name": "指定的监控名字
"}
监控规则样例¶
(def infra-dns-rules
(sdo
(where (host #"dns\d")
(plugin "proc.num" 30 {:cmdline "^/usr/sbin/unbound$" :name "proc-unbound"}))
(where (and (service "proc.num")
(= (:name event) "proc-unbound"))
(by :host
(judge (< 1)
(alarm-every 2 :min
(!{:note "Unbound 进程不在了"
:level 1
:expected 1.0
:groups [:operation]})))))))
单进程 CPU 监控¶
收集指定进程的 CPU 占用情况
- 插件文件地址
- proc.cpu
- 插件类型
- 接受参数,持续执行(Step 周期须指定为
0
)
插件参数¶
参数 | 功能 |
---|---|
cmdline | 被监控进程的命令行正则表达式 [1] |
interval | 收集和上报的周期 [2] |
name | 这个监控的名字,用来区分其他的单进程 CPU 监控 |
[1] | 命令行是从 /proc/ pid /cmdline 读取并且将其中的 \x00 替换成空格后进行匹配。
匹配是任意位置的,需要限定从一开始匹配的话请在正则前面加上 ^ |
[2] | CPU 的占用是通过读取进程的 CPU 时间并除以监控间隔得出的,这里的 interval 太大的话会把
实际的 CPU 占用尖刺抹平。 |
上报的监控值¶
- proc.cpu
意义: 指定进程的 CPU 占用 取值: 百分比,取值 0 - 100。超过 100 代表占用了 1 个以上的核。 Tags: {"port": " 指定的端口
", "name": "指定的监控名字
"}
监控规则样例¶
(def infra-redis-cpu-rules
(sdo
(where (host #"^redis-.+$")
(plugin "proc.cpu" 0 {:name "redis", :cmdline "^/usr/bin/redis-server", :interval 5})
(where (and (service "proc.cpu")
(= (:name event) "redis"))
(by [:host :name]
(judge (> 90)
(runs 12 :state
(alarm-every 2 :min
(! {:note "Redis 进程 CPU 占用过高"
:level 1
:expected 3
:groups [:operation :api]})))))))))
URL 监控¶
收集满足过滤条件的进程数量
- 插件文件地址
- url.check
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
url | 需要监控的 URL |
method | 可选 ,HTTP method,默认是 GET |
params | 可选 ,URL Query String |
headers | 可选 ,额外的 HTTP 头 |
data | 可选 ,POST 数据 |
match | 可选 ,对返回结果做匹配 |
timeout | 可选 ,访问超时时间,默认是 5 秒 |
verify | 可选 ,若 URL 是 https 链接,是否验证证书合法性 |
name | 可选 ,这个监控的名字,用来区分其他的监控 |
注解
params
、 headers
、 match
都是 Key-Value 对(Python 的 dict,或者 clojure 里的 assoc)。
match
中的 key 代表了 match 的名字,值代表了需要 match 的内容的正则表达式。
上报的监控值¶
- url.check.time
意义: 访问指定 URL 的请求时间 取值: 0 - 无上限,浮点数,单位:秒 Tags: {"name": " 指定的监控名字
"}- url.check.status
意义: 访问指定 URL 的 HTTP Status Code 取值: 整数 Tags: {"name": " 指定的监控名字
"}注解
按照 Cloudflare 的约定新增了两个 Code:
Code 意义 524 成功建立了 TCP 连接并发送了请求,但是服务器没有及时响应 521 无法建立 TCP 连接 - url.check.content-length
意义: 访问指定 URL 返回的内容的长度 取值: 0 - 无上限,整数,单位:Byte Tags: {"name": " 指定的监控名字
"}- url.check.match.
key
意义: match
中key
指定的正则表达式在返回的内容中出现的次数取值: 0 - 无上限,整数 Tags: {"name": " 指定的监控名字
"}
监控规则样例¶
(def check-baidu-rules
(where (host #"^host1")
(plugin "url.check" 30
{:name "check-baidu",
:method "GET"
:params {:foo "bar"}
:headers {:User-Agent "Monitoring script from Satori"}
:timeout 3
:verify true
:match {:html5 "<!DOCTYPE html>",
:not-exist "I'll be surprised if this strings exists!"}
:url "https://www.baidu.com"}))
(where (and (service "url.check.status")
(= (:name event) "check-baidu"))
(by :host
(adjust [:metric int]
(judge (not= 200)
(runs 3 :state
(alarm-every 5 :min
(! {:note "百度挂了!"
:level 3
:expected true
:groups [:operation]})))))))
(where (and (service "url.check.time")
(= (:name event) "check-baidu"))
(by :host
(judge (> 1)
(runs 3 :state
(alarm-every 5 :min
(! {:note "百度好卡!"
:level 3
:expected true
:groups [:operation]}))))))
(where (and (service "url.check.match.not-exist")
(= (:name event) "check-baidu"))
(by :host
(judge (> 0)
(runs 3 :state
(alarm-every 5 :min
(! {:note "百度被我们入侵了咩哈哈!"
:level 3
:expected true
:groups [:operation]})))))))
Java 进程 OldGen 占用¶
收集指定 Java 进程的 OldGen (老代堆)占用
- 插件文件地址
- proc.java.cpu
- 插件类型
- 接受参数,重复执行
警告
如果你的 Java 进程是在容器内运行的,那么你必须在容器内进行收集。
插件是通过 jstat -gccause <pid>
命令收集的,这个命令需要读取 /tmp/hsperfdata_*
文件中的信息。
插件参数¶
参数 | 功能 |
---|---|
cmdline | 被监控进程的命令行正则表达式 [1] |
name | 这个监控的名字,用来区分其他的监控 |
[1] | 命令行是从 /proc/ pid /cmdline 读取并且将其中的 \x00 替换成空格后进行匹配。
匹配是任意位置的,需要限定从一开始匹配的话请在正则前面加上 ^ |
上报的监控值¶
- proc.java.heap
意义: 指定的 Java 进程的 OldGen 占用百分比 取值: 0 - 100 Tags: {"pid": " 指定进程的 pid
", "name": "指定的监控名字
"}
监控规则样例¶
(def infra-es-rules
(sdo
(where (host #"^es\d$")
(plugin "proc.java.heap" 30
{:name "elasticsearch", :cmdline "org.elasticsearch.bootstrap.Elasticsearch"})
(where (and (service "proc.java.heap")
(= (:name event) "elasticsearch"))
(by [:host :region]
(judge-gapped (> 99.8) (< 95)
(runs 3 :state
(alarm-every 2 :min
(! {:note "ElasticSearch OldGen 满了!"
:level 1
:expected true
:outstanding-tags [:host :name]
:groups [:operation :api]})))))))))
MySQL 查询¶
这个插件提供了收集 MySQL 查询结果的功能。
- 插件文件地址
- mysql.query
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
host | MySQL 服务器地址 [1] |
port | MySQL 服务器端口 |
database | 希望执行查询的数据库名 |
user | MySQL 用户 |
password | MySQL 用户对应的密码 |
name | 这个监控的名字,用来区分其他的 MySQL 查询监控 |
sql | 执行的 SQL 语句 [2] |
json-file | 可选 ,从这个文件中读取上述参数 [3] |
[1] | 同时这个值也会作为监控的 host 上报,如果 host 是 localhost 则会使用本机 hostname 上报。 |
[2] | SQL 语句必须返回数值类型的 scalar (1行1列)。 |
[3] | 因为是插件在读取这个文件,所以这个文件需要在插件调度的机器上。 |
上报的监控值¶
- mysql.query.
name
意义: 执行指定的 SQL 的结果 取值: 浮点数,与指定的 SQL 有关 Tags: 无
监控规则样例¶
(def mysql-query-rules
(sdo
(where (host "mysql-host")
(plugin "mysql.query" 60
{:name "bad-user-count"
:host "localhost"
:port 3306
:user "user_for_monitor"
:password "Secret!IMeanIt!"
:database "awesome_app"
:sql "SELECT count(*) FROM user WHERE bad = 1"})
(where (service "mysql.query.bad-user-count")
(judge (> 50)
(runs 2 :state
(alarm-every 5 :min
(! {:note "坏用户太多了!"
:level 3
:groups [:operation]}))))))))
NVidia GPU 监控¶
这个插件提供了收集 NVidia GPU 指标的功能。
- 插件文件地址
- nvgpu
- 插件类型
- 接受参数,持续执行
插件参数¶
参数 | 功能 |
---|---|
duration | 采集周期,单位是秒 |
上报的监控值¶
- nvgpu.pwr
意义: 指定 GPU 当前功率 取值: 0-无上限,整数,单位是 W(瓦) Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.gtemp
意义: 指定 GPU 当前内核温度 取值: 整数,单位是摄氏度 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.fan
意义: 指定 GPU 当前风扇转速 取值: 0-100,百分比 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.mem.used
意义: 指定 GPU 已使用显存 取值: 0-无上限,单位是字节 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.mem.free
意义: 指定 GPU 空闲显存 取值: 0-无上限,单位是字节 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.mem.total
意义: 指定 GPU 总显存 取值: 0-无上限,单位是字节 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.util.gpu
意义: 指定 GPU 当前核心利用率 取值: 0-100,百分比 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},- nvgpu.util.mem
意义: 指定 GPU 当前显存带宽利用率 取值: 0-100,百分比 Tags: {"idx": " GPU 数字编号
", "id": "GPU ID,由型号和 GUID 的一部分组成
"},
监控规则样例¶
(def nvgpu-rules
(sdo
(where (host "gpuhost1" "gpuhost2")
(plugin "nvgpu" 0 {:duration 5}))
(->waterfall
(where (service "nvgpu.gtemp"))
(by :host)
(copy :id :aggregate-desc-key)
(group-window :id)
(aggregate max)
(judge-gapped (> 90) (< 86))
(alarm-every 2 :min)
(! {:note "GPU 过热"
:level 1
:expected 85
:outstanding-tags [:host]
:groups [:operation :devs]}))
#_(place holder)))
基础监控插件¶
satori-agent 内置指标的扩展,建议每个机器上都收集
- 插件文件地址
- infra/*
- 插件类型
- 不接受参数(直接执行),重复执行
Conntrack¶
Conntrack 是 Netfilter 用来跟踪连接的机制,如果使用了 NAT 或者有状态防火墙功能就会启用 conntrack 跟踪连接。
- net.netfilter.conntrack.used
意义: Conntrack 桶用量 提供: infra/30_conntrack.py
取值: 0 - 无上限,整数 Tags: 无 - net.netfilter.conntrack.max
意义: Conntrack 桶最大值 提供: infra/30_conntrack.py
取值: 0 - 无上限,整数 Tags: 无 - net.netfilter.conntrack.used_ratio
意义: Conntrack 桶用量百分比 提供: infra/30_conntrack.py
取值: 0 - 1.0,浮点数 Tags: 无
重整化的机器负载(Load)¶
- load.1min.normalized
意义: 1分钟平均负载 提供: infra/30_normalized_load.py
取值: 0 - 无上限,浮点数 Tags: 无 - load.5min.normalized
意义: 5分钟平均负载 提供: infra/30_normalized_load.py
取值: 0 - 无上限,浮点数 Tags: 无 - load.15min.normalized
意义: 15分钟平均负载 提供: infra/30_normalized_load.py
取值: 0 - 无上限,浮点数 Tags: 无
注解
这个值与 机器负载(Load) 监控的是相同的值, 但是这里的 Load 会除以 CPU 核数,在编写监控规则的时候就不用考虑核数了。
MegaRAID 磁盘损坏监控¶
- megaraid.offline
意义: 第一个 MegaRAID 控制器中 Failed/Offline 状态的磁盘个数 提供: infra/600_megaraid.py
取值: 0 - 无上限,整数 Tags: 无 注解
这个插件需要安装
megacli
工具后才能使用, 请确认/opt/MegaRAID/MegaCli/MegaCli64
文件是否存在。
内核 dmesg 监控¶
- kernel.dmesg.bug
意义: dmesg 中出现 BUG:
字样的次数提供: infra/60_kernel.py
取值: 0 - 无上限,整数 Tags: 无 - kernel.dmesg.io_error
意义: dmesg 中出现 I/O error
字样的次数提供: infra/60_kernel.py
取值: 0 - 无上限,整数 Tags: 无
注解
处理完故障之后可以使用 dmesg --clear
来清除内核的 dmesg buffer,否则会一直报告 0 以上的值。
异常状态进程数量¶
- proc.zombies
意义: 僵尸进程数量 提供: infra/60_zombies.py
取值: 0 - 无上限,整数 Tags: 无 注解
僵尸进程是已经结束的进程,不占用内存/CPU,只占用内核进程表的一个条目。 大量出现僵尸进程通常是程序编写的问题。
- proc.uninterruptables
意义: 处于不可中断睡眠状态的进程数量 提供: infra/60_zombies.py
取值: 0 - 无上限,整数 Tags: 无 注解
最常见的不可中断睡眠状态的进程状态是由磁盘 IO 导致的,是正常状态。 如果持续性的数量过多就需要调查了。
软中断统计¶
- softirq.timer
意义: 时钟中断 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 - softirq.net_tx: 89 84
意义: 发送网络数据 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 - softirq.net_rx: 776 1331
意义: 接收网络数据 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 - softirq.block: 60 89
意义: 块设备请求 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 - softirq.tasklet: 141 29
意义: Tasklet 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 注解
一些内核中高优先级但是不合适做成软中断的任务
- softirq.sched:
意义: 进程调度 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 - softirq.rcu:
意义: RCU 回收 提供: infra/30_softirq.py
取值: 0 - 无上限,累积,整数 Tags: 无 注解
RCU 是内核中用的一种并发数据结构,需要定期清理
监控规则样例¶
注解
请直接参考规则仓库中附带的 infra/common.clj
文件,篇幅过长不再贴了。
集群交叉检测¶
Ping 检测¶
在指定的机器上 Ping 整个集群的机器
- 插件文件地址
- cross.ping
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
region | 希望检测的集群名称 |
上报的监控值¶
- cross.ping.alive
意义: 机器是否可以 Ping 通 取值: 0 代表失败,1 代表成功 Tags: {"from": " 插件执行的机器的 hostname
"},另外endpoint
(对应 riemann 中的:host
) 值是被检测的机器的 hostname- cross.ping.latency
意义: 机器的 Ping 延迟 取值: 0-无上限,单位 ms Tags: {"from": " 插件执行的机器的 hostname
"},另外endpoint
(对应 riemann 中的:host
) 值是被检测的机器的 hostname
注解
插件需要在执行插件的机器上安装 fping
工具
警告
插件会通过 utils/region.py
中的 nodes_of
函数取得指定集群的机器列表,
你需要自己实现这个函数
监控规则样例¶
(where (host "hosts" "which" "perform" "ping")
(plugin "cross.ping" 15 {:region "office"})
(where (service "cross.ping.alive")
(by :host
(judge (< 1)
(runs 3 :state
(alarm-every 2 :min
(! {:note "Ping 不通了!"
:level 1
:expected 1
:outstanding-tags [:region]
:groups [:operation]}))))))
Agent 存活检测¶
在指定的机器上 Satori agent 是否存活
- 插件文件地址
- cross.agent
- 插件类型
- 接受参数,重复执行
插件参数¶
参数 | 功能 |
---|---|
region | 希望检测的集群名称 |
上报的监控值¶
- cross.agent.alive
意义: 指定机器上的 agent 是否存活 取值: 0 代表不存活,1 代表存活 Tags: {"from": " 插件执行的机器的 hostname
"},另外endpoint
(对应 riemann 中的:host
) 值是被检测的机器的 hostname
警告
插件会通过 utils/region.py
中的 nodes_of
函数取得指定集群的机器列表,
你需要自己实现这个函数
监控规则样例¶
(where (host "hosts" "which" "perform" "ping")
(plugin "cross.agent" 15 {:region "office"})
(where (service "cross.agent.alive")
(by :host
(judge (< 1)
(runs 3 :state
(alarm-every 2 :min
(! {:note "Satori Agent 不响应了!"
:level 1
:expected 1
:outstanding-tags [:region]
:groups [:operation]}))))))
交换机监控(swcollector)¶
这个插件提供了通过 SNMP 收集交换机性能指标的的功能
- 插件文件地址
- swcollector
- 插件类型
- 接受参数,持续执行(Step 周期须指定为
0
)
插件参数¶
参数 | 功能 | 默认值 |
---|---|---|
interval | 收集间隔 | 30 s |
logFile | 日志文件路径,运行日志会输出到这里 | "" |
ipRange | 交换机 IP 地址段 [1] | [] |
pingTimeout | Ping 超时时间 | 300 ms |
pingRetry | Ping 探测重试次数 | 4 次 |
snmpCommunity | SNMP 认证字符串 | "public" |
snmpTimeout | SNMP超时时间 | 1000 ms |
snmpRetry | SNMP重试次数 | 5 次 |
gosnmp | 是否使用 gosnmp 采集, 否则使用 snmpwalk | true |
concurrentCollectors | 采集的并发限制 | 1000 |
concurrentQueriesPerHost | 单台机器多指标的采集并发限制 | 4 |
fastPingMode | 快速 Ping 模式 | true |
reverseLookup | 通过 DNS 反向解析交换机的 hostname | false |
ignore | 忽略的采集指标(见下文) | [] |
ignoreIface | 忽略的接口 [2] | ["Nu","NU","Vlan","Vl"] |
customMetrics | 自定义监控项(见下文) | [] |
customHosts | 自定义 IP -> hostname 映射 [3] | {} |
[1] | 对该网段有效 IP,先发 Ping 包探测,对存活 IP 发送 SNMP 请求,
形如 ["192.168.1.1", "192.168.2.1-192.168.2.233", "172.16.123.0/24"] |
[2] | Nu 匹配 ifName 为 *Nu* 的接口 |
[3] | 形如 {"192.168.1.1": "router1"} |
ignore
是形如 ["broadcasts", "multicasts"]
的 list,具体定义如下:
ignore 项 | 忽略指标 |
---|---|
operstatus |
switch.if.OperStatus |
packets |
switch.if.InPkts switch.if.OutPkts |
broadcasts |
switch.if.InBroadcastPkt switch.if.OutBroadcastPkt |
multicasts |
switch.if.InMulticastPkt switch.if.OutMulticastPkt |
discards |
switch.if.InDiscards switch.if.OutDiscards |
errors |
switch.if.InErrors switch.if.OutErrors |
unknownprotos |
switch.if.InUnknownProtos |
qlen |
switch.if.OutQLen |
speed |
switch.if.Speed |
octets |
switch.if.In switch.if.Out |
customMetrics
是一个 list,list 元素具体定义如下:
customMetric 项 | 意义 |
---|---|
ipRange |
对该 ip 范围启用该自定义监控项 [4] |
metric |
自定义监控项的名称 |
oid |
监控项的 OID |
tags |
监控项附加的 tags |
[4] | 与第一层配置中的 ipRange 格式相同 |
自定义的 oid 只支持 snmp get 方式采集,因此务必填写完整,建议先通过 snmpwalk 验证一下。
上报的监控值¶
- switch.CollectTime
意义: 单个交换机收集指标所用的时间 单位: ms 取值: -1.0,或者 (0.0, +Inf) 说明: 取值 -1.0 说明收集出错或超时 - switch.Ping
意义: Ping RTT 单位: ms 取值: -1.0,或者 (0.0, +Inf) 说明: 取值 -1.0 说明 Ping 出错或超时 - switch.if.OperStatus
意义: 接口状态 取值: [1-7] Tags: {"ifName": " 接口名
", "ifIndex":接口序号
}
取值 | 意义 |
---|---|
1 | up |
2 | down |
3 | testing |
4 | unknown |
5 | dormant |
6 | notPresent |
7 | lowerLayerDown |
剩余的指标意义明显,不再解释了
监控规则样例¶
(def swcollector-rules
(sdo
(where (host "host-which-runs-swcollector")
(plugin "swcollector" 0
{:interval 30
:snmpCommunity "Your-snmp-community!"
:ignore [:multicasts :unknownprotos]
:ipRange ["192.168.10.1-192.168.10.100" "192.168.20.1-192.168.20.100"]
:reverseLookup true
:customMetrics [{:metric "switch.AnyconnectSession"
:oid "1.3.6.1.4.1.9.9.392.1.3.35.0"
:ipRange ["192.168.10.1-192.168.10.100"]
:tags {}}
{:metric "switch.ConnectionStat"
:oid "1.3.6.1.4.1.9.9.147.1.2.2.2.1.5.40.6"
:ipRange ["192.168.20.1-192.168.20.100"]
:tags {}}]}))
(where (service "switch.if.OperStatus")
(by [:host :ifName]
(adjust [:metric int]
(judge (!= 1)
(runs 2 :state
(alarm-every 5 :min
(! {:note "交换机接口挂掉了"
:level 3
:groups [:operation]})))))))))
其他监控插件¶
Satori 还附带了很多监控应用的插件,因为解释起来每一个插件都是深坑,且这些指标都在各个应用的文档中有解释,所以不再一一在这里解释了,这里挑选几个需要解释的放在这里,剩下的只列一个列表用来记录。
插件的具体行为麻烦去看一下插件的源码和配套附带的规则。
相关软件 | 插件路径 |
---|---|
Docker | docker/* |
MongoDB | mongodb/* |
MySQL | mysql/* |
nginx | nginx/* |
Redis | redis/* |
Codis | codis/* |
ElasticSearch | elasticsearch/* |
HAProxy | haproxy/* |
Kestrel | kestrel/* |
Marathon | marathon/* |
Memcached | memcache/* |
Mesos | mesos/* |
Unbound | unbound/* |
Zookeeper | zookeeper/* |
ChangeLog¶
2.0.0¶
相对于 1.x 更新了以下东西¶
- alarm 的后端放进了规则仓库,增加报警方式不再需要 fork alarm
- alarm 增加了 hook 机制,用来在发送报警的时候对报警做变换,或者阻止发送
- 报警规则现在可以附带元信息
- riemann 去掉了经常出问题的 reloader,将重载规则的逻辑直接集成在规则内
- nginx 改为 openresty 并集成了自动申请 Let's Encrypt 证书的功能,不用再为了 SSL 头疼了
- InfluxDB Riemann Grafana 都升级成了最新版
- NVIDIA GPU 收集插件
- 集群交叉探测插件(Ping)重写
- 遇到 agent 自己的 crash 现在也能通过报警机制发送出来了
- agent 现在可以选择使用 FQDN 作为自己的机器名(可配置)
- 添加了 Kerberos(SPNEGO) 验证的支持
不兼容的改变¶
- agent 不再将接受参数的插件放在单独的
_metric
目录中 - agent 内置的内存指标
mem.memfree
改名为mem.memusable
, 并分拆了mem.free
mem.cached
mem.buffers
修复 BUG¶
- 修复了一个插件调度的极端情况导致 agent crash 的 bug
- 修复了一个 agent 无法更新规则仓库的 bug
- 修复了部署描述符错误导致丢失数据的 bug
- 修复了几处安装脚本的 bug
Satori 社区¶
Satori 目前提供了一个 QQ 群可以交流:
Satori 交流群: 554765935
如果找到了 bug、有 feature request 或者 patch,可以直接去 Satori 仓库的 issues 上提一个 issue。