绘图链路常见问题

如何清除过期索引

监控数据停止上报后,该数据对应的索引也会停止更新、变为过期索引。过期索引,影响视听,部分用户希望删除之。

我们原来的方案,是: 通过task模块,有数据上报的索引、每天被更新一次,7天未被更新的索引、清除之。但是,很多用户不能正确配置graph实例的http接口,导致正常上报的监控数据的索引 无法被更新;7天后,合法索引被task模块误删除。

为了解决上述问题,我们停掉了task模块自动删除过期索引的功能、转而提供了过期索引删除的接口。用户按需触发索引删除操作,具体步骤为:

1.运行task模块,并正确配置graph集群及其http端口,即task配置文件中index.cluster的内容。此处配置不正确,不应该进行索引删除操作,否则将导致索引数据的误删除。

2.进行一次索引数据的全量更新。方法为 curl -s "$Hostname.Of.Task:$Http.Port/index/updateAll"。这里,”$Hostname.Of.Task:$Http.Port”是task的http接口地址。
PS:索引数据存放在graph实例上,这里,只是通过task,触发了各个graph实例的索引全量更新。更直接的办法,是,到每个graph实例上,运行curl -s "127.0.0.1:6071/index/updateAll",直接触发graph实例 进行索引全量更新(这里假设graph的http监听端口为6071)。

3.待索引全量更新完成后,发起过期索引删除 curl -s "$Hostname.Of.Task:$Http.Port/index/delete"。运行索引删除前,请务必确保索引全量更新已完成。典型的做法为,周六运行一次索引全量更新,周日运行一次索引删除;索引更新和删除之间,留出足够的时间。

在此,建议您: 若无必要,请勿删除索引;若确定要删除索引,请确保删除索引之前,对所有的graph实例进行一次索引全量更新。

Dashboard索引缺失、查询不到endpoint或counter

手动更改graph的数据库后,可能会出现上述情况。这里的手动更改,包括:更改graph的数据库配置(数据库地址,名称等)、删除重建graph数据库/表、手动更改graph数据表的内容等。出现上述情况后,可以通过 如下两种途径的任一种 来解决问题,

  1. 触发graph的索引全量更新、补救手工操作带来的异常。触发方式为,运行curl -s "http://$hostname:$port/index/updateAll",其中$hostname为graph所在的服务器地址,$port为graph的http监听端口。这种方式,不会删除已上报的监控数据,但是会对数据库造成短时间的读写压力。
  2. 删除graph已存储的数据,并重启graph。默认情况下,graph的数据存储目录为 /home/work/data/6070/。这种方式,会使已上报的数据被删除。

Dashboard图表曲线为空

图表曲线,一般会有3-5个上报周期的延迟。如果5个周期后仍然没有图表曲线,请往下看。
绘图链路,数据上报的流程为: agent -> transfer -> graph -> query -> dashboard。这个流程中任何一环节出问题,都会导致用户在dashboard中看不到曲线。一个建议的问题排查流程,如下

  1. 确保绘图链路的各组件,都已启动。
  2. 排查dashboard的问题。首先查看dashboard的日志,默认地址为./var/app.log,常见的日志报错原因有dashboard依赖库安装不完整、本地http代理劫持访问请求等。然后查看dashboard的配置,默认为./gunicorn.conf./rrd/config.py。确认gunicorn.conf中指定的访问地址,确认config.py中指定的query地址、dashboard数据库配置 和 graph数据库配置。
  3. 排查query的问题。首先看下query的日志./var/app.log是否有报错信息。然后确认query的graph列表配置./graph_backends.txt和 服务配置./cfg.json。可以通过脚本./scripts/query来做一些定量的debug,比如你上报了一个 endpoint=”ty-op-mon-cloud.us”, metric=”agent.alive” 的采集项,可以运行 bash ./scripts/query "ty-op-mon-cloud.us" "agent.alive" 来查看该采集项的数据,如果能够查到数据 则说明query及之前的graph、transfer、agent很可能是正常的。
  4. 排查graph的问题。首先看下graph的日志./var/app.log是否有报错。然后确认下graph配置文件./cfg.json是否配置了正确的数据库db。没有发现问题?启动对graph的debug,方法见Graph调试一节。
  5. 排查transfer的问题。首先看transfer的日志./var/app.log是否有报错。然后确认配置文件./cfg.json是否enable了对graph集群的发送功能、是否正确配置了graph集群列表。确认完毕后,仍没有发现问题,怎么办?启动对transfer的debug,方法见Transfer调试一节。
  6. 排查agent的问题。打开agent的debug日志,观察数据上报情况。

Dashboard图表曲线有断点

dashboard出现断点,可能的原因为:

  1. 用户监控数据上报异常。可能是用户自动的数据采集 被中断,或者上报周期不规律等。
  2. falcon系统异常,导致监控数据丢失。问题最可能发生在transfer到graph这个链路上,可以通过transfer&graph的debug接口来确认原因。

Graph绘图数据高可用

默认情况下,绘图数据以分片的形式、存储在单个graph实例上。一旦graph实例所在的机器磁盘坏掉, 绘图数据就会永久丢失。

为了解决这个问题,Open-Falcon提供了绘图数据高可用的解决方案: 数据双打/多打,即 将同一份绘图数据 同时打到2+个graph实例上、使这2+个graph实例的数据完全相同、互为镜像备份,当发生故障时 就可以用 镜像备份实例 来替换 故障的实例。具体的,绘图数据高可用的实现过程,如下:

  1. 配置transfer的graph节点列表,使每个节点都有2+个互为镜像的graph实例;transfer会向 互为镜像的几个graph实例 发送相同的数据。eg.你可以配置graph节点 形如"g-00" : "host1:6070,host2:6070",多个graph实例之间以逗号隔开,这样 host1:6070host2:6070就会互为镜像。注意,transfer版本应不低于0.0.14,否则 不能支持此功能。
  2. 启动/重启transfer,开始数据双打或者多打。transfer的压力,会随着镜像数量的增加而增大,所以 需要合理控制graph镜像的数量。
  3. 配置query的graph列表。当前,query只支持 一个节点对应一个graph实例。以第1步的transfer配置为例,你可以配置query的graph节点g-00的graph实例为 "g-00":"host1:6070" 或者 "g-00":"host2:6070"、而不能是 "g-00":"host1:6070,host2:6070"
  4. 当某个graph实例发生故障(eg.磁盘故障)时,修改query的graph列表、踢掉坏死的graph实例、并用其镜像实例顶替之。一方面,更上层的业务只会在query配置修改期间不可用,这个过程可以控制在分钟级别。另一方面,即使坏掉了一个graph实例,其上绘图数据 也可以从镜像实例上找到,从而实现了数据的高可用。

当然,这个高可用方案 需要增加更多的graph实例、会使transfer资源消耗倍增、需要手动进行故障切换,还有很多的改进空间。

如何确定某个counter对应的rrd文件

每个counter,上报到graph之后,都是以一个独立的rrd文件,存储在磁盘上。那么如何确定counter和rrd的对应关系呢?
query组件提供了一个调试用的http接口,可以帮助我们查询到该对应关系

info.py

  1. #!/usr/bin/env python
  2. import requests
  3. import json
  4. import sys
  5. d = [
  6. {
  7. "endpoint": sys.argv[1],
  8. "counter": sys.argv[2],
  9. },
  10. ]
  11. url = "http://127.0.0.1:9966/graph/info"
  12. r = requests.post(url, data=json.dumps(d))
  13. print r.text

使用方法

  1. python info.py your.hostname your.metric/tag1=tag1-val,tag2=tag2-val

Graph调试

graph以http的方式提供了多个调试接口。主要有 内部状态统计接口、历史数据查询接口等。脚本./test/debug将一些接口封装成了shell的形式,可自行查阅代码、不在此做介绍。

历史数据查询接口HTTP:GET, curl -s "http://hostname:port/history/$endpoint/$metric/$tags",返回graph接收到的、最新的3个数据。

  1. # history没有tags的数据,$endpoint=test.host, $metric=agent.alive
  2. curl -s "http://127.0.0.1:6071/history/test.host/agent.alive" | python -m json.tool
  3. # history有tags的数据,$tags='module=graph,pdl=falcon'
  4. curl -s "http://127.0.0.1:6071/history/test.host/qps/module=graph,pdl=falcon" | python -m json.tool

内部状态统计接口HTTP:GET, curl -s "http://hostname:port/statistics/all",输出json格式的内部状态数据,格式如下。这些内部状态数据,被task组件采集后push到falcon系统,用于绘图展示、报警等。

  1. curl -s "http://127.0.0.1:6071/statistics/all" | python -m json.tool
  2. # output
  3. {
  4. "data": [
  5. { // counter of received items
  6. "Cnt": 7, // cnt
  7. "Name": "GraphRpcRecvCnt", // name of counter
  8. "Other": {}, // other infos
  9. "Qps": 0, // growth rate of this counter, per second
  10. "Time": "2015-06-18 12:20:06" // time when this counter takes place
  11. },
  12. { // counter of query requests graph received
  13. "Cnt": 0,
  14. "Name": "GraphQueryCnt",
  15. "Other": {},
  16. "Qps": 0,
  17. "Time": "2015-06-18 12:20:06"
  18. },
  19. { // counter of all sent items in query
  20. "Cnt": 0,
  21. "Name": "GraphQueryItemCnt",
  22. "Other": {},
  23. "Qps": 0,
  24. "Time": "2015-06-18 12:20:06"
  25. },
  26. { // counter of info requests graph received
  27. "Cnt": 0,
  28. "Name": "GraphInfoCnt",
  29. "Other": {},
  30. "Qps": 0,
  31. "Time": "2015-06-18 12:20:06"
  32. },
  33. { // counter of last requests graph received
  34. "Cnt": 3,
  35. "Name": "GraphLastCnt",
  36. "Other": {},
  37. "Qps": 0,
  38. "Time": "2015-06-18 12:20:06"
  39. },
  40. { // counter of index updates
  41. "Cnt": 0,
  42. "Name": "IndexUpdateAllCnt",
  43. "Other": {},
  44. "Time": "2015-06-18 10:58:52"
  45. }
  46. ],
  47. "msg": "success"
  48. }

Transfer调试

transfer以http的方式提供了多个调试接口。主要有 内部状态统计接口、转发数据追踪接口等。脚本./test/debug将一些http接口封装成了shell的形式,可自行查阅代码、不在此做介绍。

转发数据追踪接口 HTTP:GET, curl -s "http://hostname:port/trace/$endpoint/$metric/$tags",输出json格式的trace数据。transfer会过滤出以/$endpoint/$metric/$tags标记的数据,第一次调用时设置过滤条件、不会返回数据,之后每次调用返回已接收到的数据、最多3个点。这个接口主要用于debug。

  1. # trace没有tags的数据,$endpoint=test.host, $metric=agent.alive
  2. curl -s "http://127.0.0.1:8433/trace/test.host/agent.alive" | python -m json.tool
  3. # trace有tags的数据,$tags='module=graph,pdl=falcon'
  4. curl -s "http://127.0.0.1:8433/trace/test.host/qps/module=graph,pdl=falcon" | python -m json.tool

内部状态统计接口HTTP:GET, curl -s "http://hostname:port/statistics/all",输出json格式的内部状态数据,格式如下。这些内部状态数据,被task组件采集后push到falcon系统,用于绘图展示、报警等。

  1. curl -s "http://127.0.0.1:8433/statistics/all" | python -m json.tool
  2. # output
  3. {
  4. "data": [
  5. { // counter of items received
  6. "Cnt": 0, // counter, total number of items received since transfer started
  7. "Name": "RecvCnt", // name of this this counter
  8. "Other": {}, // other infos
  9. "Qps": 0, // growth rate of this counter, items per sec
  10. "Time": "2015-06-10 07:46:35" // time when this counter takes place
  11. },
  12. { // counter of items received from RPC
  13. "Cnt": 0,
  14. "Name": "RpcRecvCnt",
  15. "Other": {},
  16. "Qps": 0,
  17. "Time": "2015-06-10 07:46:35"
  18. },
  19. { // counter of items received from HTTP-API
  20. "Cnt": 0,
  21. "Name": "HttpRecvCnt",
  22. "Other": {},
  23. "Qps": 0,
  24. "Time": "2015-06-10 07:46:35"
  25. },
  26. { // counter of items received from SOCKET
  27. "Cnt": 0,
  28. "Name": "SocketRecvCnt",
  29. "Other": {},
  30. "Qps": 0,
  31. "Time": "2015-06-10 07:46:35"
  32. },
  33. { // counter of items sent to judge
  34. "Cnt": 0,
  35. "Name": "SendToJudgeCnt",
  36. "Other": {},
  37. "Qps": 0,
  38. "Time": "2015-06-10 07:46:35"
  39. },
  40. { // counter of items sent to graph
  41. "Cnt": 0,
  42. "Name": "SendToGraphCnt",
  43. "Other": {},
  44. "Qps": 0,
  45. "Time": "2015-06-10 07:46:35"
  46. },
  47. { // counter of items sent to graph-migrating
  48. "Cnt": 0,
  49. "Name": "SendToGraphMigratingCnt",
  50. "Other": {},
  51. "Qps": 0,
  52. "Time": "2015-06-10 07:46:35"
  53. },
  54. { // counter of dropped items sent to judge. transfer would drop items if it could not push them to receivers timely
  55. "Cnt": 0,
  56. "Name": "SendToJudgeDropCnt",
  57. "Other": {},
  58. "Qps": 0,
  59. "Time": "2015-06-10 07:46:35"
  60. },
  61. { // counter of dropped items sent to graph
  62. "Cnt": 0,
  63. "Name": "SendToGraphDropCnt",
  64. "Other": {},
  65. "Qps": 0,
  66. "Time": "2015-06-10 07:46:35"
  67. },
  68. { // counter of dropped items sent to graph-migrating
  69. "Cnt": 0,
  70. "Name": "SendToGraphMigratingDropCnt",
  71. "Other": {},
  72. "Qps": 0,
  73. "Time": "2015-06-10 07:46:35"
  74. },
  75. { // counter of items transfer failed to send to judge
  76. "Cnt": 0,
  77. "Name": "SendToJudgeFailCnt",
  78. "Other": {},
  79. "Qps": 0,
  80. "Time": "2015-06-10 07:46:35"
  81. },
  82. { // counter of items transfer failed to send to graph
  83. "Cnt": 0,
  84. "Name": "SendToGraphFailCnt",
  85. "Other": {},
  86. "Qps": 0,
  87. "Time": "2015-06-10 07:46:35"
  88. },
  89. { // counter of items transfer failed to send to graph-migrating
  90. "Cnt": 0,
  91. "Name": "SendToGraphMigratingFailCnt",
  92. "Other": {},
  93. "Qps": 0,
  94. "Time": "2015-06-10 07:46:35"
  95. },
  96. { // counter of cached items which would be sent to judge instances
  97. "Cnt": 0,
  98. "Name": "JudgeSendCacheCnt",
  99. "Other": {},
  100. "Time": "2015-06-10 07:46:33"
  101. },
  102. { // counter of cached items which would be sent to graph instances
  103. "Cnt": 0,
  104. "Name": "GraphSendCacheCnt",
  105. "Other": {},
  106. "Time": "2015-06-10 07:46:33"
  107. },
  108. { // counter of cached items which would be sent to grap-migrating instances
  109. "Cnt": 0,
  110. "Name": "GraphMigratingCacheCnt",
  111. "Other": {},
  112. "Time": "2015-06-10 07:46:33"
  113. }
  114. ],
  115. "msg": "success"
  116. }

设置绘图数据的存储周期

Graph组件默认保存5年的数据,存储数据的RRA配置为:

  1. // find this in 'graph/rrdtool/rrdtool.go'
  2. func create(filename string, item *model.GraphItem) error {
  3. now := time.Now()
  4. start := now.Add(time.Duration(-24) * time.Hour)
  5. step := uint(item.Step)
  6. c := rrdlite.NewCreator(filename, start, step)
  7. c.DS("metric", item.DsType, item.Heartbeat, item.Min, item.Max)
  8. // 设置各种归档策略
  9. // 1分钟一个点存 12小时
  10. c.RRA("AVERAGE", 0.5, 1, 720)
  11. // 5m一个点存2d
  12. c.RRA("AVERAGE", 0.5, 5, 576)
  13. c.RRA("MAX", 0.5, 5, 576)
  14. c.RRA("MIN", 0.5, 5, 576)
  15. // 20m一个点存7d
  16. c.RRA("AVERAGE", 0.5, 20, 504)
  17. c.RRA("MAX", 0.5, 20, 504)
  18. c.RRA("MIN", 0.5, 20, 504)
  19. // 3小时一个点存3个月
  20. c.RRA("AVERAGE", 0.5, 180, 766)
  21. c.RRA("MAX", 0.5, 180, 766)
  22. c.RRA("MIN", 0.5, 180, 766)
  23. // 1天一个点存5year
  24. c.RRA("AVERAGE", 0.5, 720, 730)
  25. c.RRA("MAX", 0.5, 720, 730)
  26. c.RRA("MIN", 0.5, 720, 730)
  27. return c.Create(true)
  28. }

你可以通过修改rrdtool的RRA c.RRA($CF, 0.5, $PN, $PCNT),来设置Graph的数据存储周期。关于RRA的概念,请查阅rrdtool的相关资料。