init project

This commit is contained in:
710leo 2020-03-11 18:25:20 +08:00
commit 5f00489392
1990 changed files with 567455 additions and 0 deletions

51
.gitignore vendored Normal file
View File

@ -0,0 +1,51 @@
*.exe
*.exe~
*.dll
*.dylib
*.test
*.out
*.prof
*.log
*.o
*.a
*.so
*.sw[po]
*.tar.gz
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
_obj
_test
/log*
/bin
/out
/build
/dist
/etc/*.local.yml
/data*
.idea
.index
.vscode
.DS_Store
.cache-loader
/n9e-*
/src/modules/index/index
/src/modules/collector/collector
/src/modules/transfer/transfer
/src/modules/tsdb/tsdb
/src/modules/monapi/monapi
/web/node_modules
/web/.cache-loader

433
LICENSE Normal file
View File

@ -0,0 +1,433 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

30
README.md Normal file
View File

@ -0,0 +1,30 @@
<img src="https://s3-gz01.didistatic.com/n9e-pub/image/n9e-logo-bg-white.png" width="150" alt="Nightingale"/>
<br>
[中文简介](README_ZH.md)
Nightingale is a fork of Open-Falcon, and all the core modules have been greatly optimized. It integrates the best practices of DiDi. You can think of it as the next generation of Open-Falcon, and use directly in production environment.
## Documentation
Nightingale user manual: [https://n9e.didiyun.com/](https://n9e.didiyun.com/)
## Compile
```bash
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale && ./control build
```
## Team
[ulricqin](https://github.com/ulricqin) [710leo](https://github.com/710leo) [jsers](https://github.com/jsers) [hujter](https://github.com/hujter) [n4mine](https://github.com/n4mine) [heli567](https://github.com/heli567)
## License
<img alt="Apache-2.0 license" src="https://s3-gz01.didistatic.com/n9e-pub/image/apache.jpeg" width="128">
Nightingale is available under the Apache-2.0 license. See the [LICENSE](LICENSE) file for more info.

30
README_ZH.md Normal file
View File

@ -0,0 +1,30 @@
<img src="https://s3-gz01.didistatic.com/n9e-pub/image/n9e-logo-bg-white.png" width="150" alt="Nightingale"/>
<br>
[English Introduction](README.md)
Nightingale是一套衍生自Open-Falcon的互联网监控解决方案融入了部分滴滴生产环境的最佳实践灵活易用稳定可靠是一个生产环境直接可用的版本 :-)
## 文档
使用手册请参考:[夜莺使用手册](https://n9e.didiyun.com/)
## 编译
```bash
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale && ./control build
```
## 团队
[ulricqin](https://github.com/ulricqin) [710leo](https://github.com/710leo) [jsers](https://github.com/jsers) [hujter](https://github.com/hujter) [n4mine](https://github.com/n4mine) [heli567](https://github.com/heli567)
## 协议
<img alt="Apache-2.0 license" src="https://s3-gz01.didistatic.com/n9e-pub/image/apache.jpeg" width="128">
Nightingale 基于 Apache-2.0 协议进行分发和使用,更多信息参见 [协议文件](LICENSE)。

206
control Executable file
View File

@ -0,0 +1,206 @@
#!/bin/bash
CWD=$(cd $(dirname $0)/; pwd)
cd $CWD
usage()
{
echo $"Usage: $0 {start|stop|restart|status|build|pack} <module>"
exit 0
}
start_all()
{
# http: 5800
test -x n9e-monapi && start monapi
# http: 5810 ; rpc: 5811
test -x n9e-transfer && start transfer
# http: 5820 ; rpc: 5821
test -x n9e-tsdb && start tsdb
# http: 5830 ; rpc: 5831
test -x n9e-index && start index
# http: 5840 ; rpc: 5841
test -x n9e-judge && start judge
# http: 2058
test -x n9e-collector && start collector
}
start()
{
mod=$1
if [ "x${mod}" = "x" ]; then
usage
return
fi
if [ "x${mod}" = "xall" ]; then
start_all
return
fi
binfile=n9e-${mod}
if [ ! -f $binfile ]; then
echo "file[$binfile] not found"
exit 1
fi
if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -gt 0 ]; then
echo "${mod} already started"
return
fi
mkdir -p logs/$mod
nohup $CWD/$binfile &> logs/${mod}/stdout.log &
for((i=1;i<=15;i++)); do
if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -gt 0 ]; then
echo "${mod} started"
return
fi
sleep 0.2
done
echo "cannot start ${mod}"
exit 1
}
stop_all()
{
test -x n9e-monapi && stop monapi
test -x n9e-transfer && stop transfer
test -x n9e-tsdb && stop tsdb
test -x n9e-index && stop index
test -x n9e-judge && stop judge
test -x n9e-collector && stop collector
}
stop()
{
mod=$1
if [ "x${mod}" = "x" ]; then
usage
return
fi
if [ "x${mod}" = "xall" ]; then
stop_all
return
fi
binfile=n9e-${mod}
if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -eq 0 ]; then
echo "${mod} already stopped"
return
fi
ps aux|grep -v grep|grep -v control|grep "$binfile"|awk '{print $2}'|xargs kill
for((i=1;i<=15;i++)); do
if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -eq 0 ]; then
echo "${mod} stopped"
return
fi
sleep 0.2
done
echo "cannot stop $mod"
exit 1
}
restart()
{
mod=$1
if [ "x${mod}" = "x" ]; then
usage
return
fi
if [ "x${mod}" = "xall" ]; then
stop_all
start_all
return
fi
stop $mod
start $mod
status
}
status()
{
ps aux|grep -v grep|grep "n9e"
}
build_one()
{
mod=$1
go build -o n9e-${mod} --tags "md5" src/modules/${mod}/${mod}.go
}
build()
{
mod=$1
if [ "x${mod}" = "x" ]; then
build_one monapi
build_one transfer
build_one index
build_one judge
build_one collector
build_one tsdb
return
fi
build_one $mod
}
reload()
{
mod=$1
if [ "x${mod}" = "x" ]; then
echo "arg: <mod> is necessary"
return
fi
build_one $mod
restart $mod
}
pack()
{
v=$(date +%Y-%m-%d-%H-%M-%S)
tar zcvf n9e-$v.tar.gz control sql plugin pub etc/log etc/port etc/service etc/nginx.conf etc/mysql.yml etc/address.yml \
n9e-collector etc/collector.yml \
n9e-tsdb etc/tsdb.yml \
n9e-index etc/index.yml \
n9e-judge etc/judge.yml \
n9e-transfer etc/transfer.yml \
n9e-monapi etc/monapi.yml
}
case "$1" in
start)
start $2
;;
stop)
stop $2
;;
restart)
restart $2
;;
status)
status
;;
build)
build $2
;;
reload)
reload $2
;;
pack)
pack
;;
*)
usage
esac

689
doc/api.md Normal file
View File

@ -0,0 +1,689 @@
# 前端接口
`POST /api/portal/auth/login`
校验用户登录信息的接口is_ldap=0表示不使用LDAP账号验证is_ldap=1表示使用LDAP账号验证
```
{
"username": "",
"password": "",
"is_ldap": 0
}
```
---
`GET /api/portal/auth/logout`
退出当前账号,如果请求成功,前端需要跳转到登录页面
---
`GET /api/portal/self/profile`
获取个人信息,可以用此接口校验用户是否登录了
---
`PUT /api/portal/self/profile`
更新个人信息
```
{
"dispname": "",
"phone": "",
"email": "",
"im": ""
}
```
---
`PUT /api/portal/self/password`
更新个人密码,新密码输入两次做校验,在前端完成
```
{
"oldpass": "",
"newpass": ""
}
```
---
`GET /api/portal/user`
获取用户列表支持搜索搜索条件参数是query每页显示条数是limit页码是p如果当前用户是root则展示相关操作按钮如果不是则所有按钮不展示只是查看
---
`POST /api/portal/user`
root账号新增一个用户is_root字段表示新增的这个用户是否是个root
```
{
"username": "",
"password": "",
"dispname": "",
"phone": "",
"email": "",
"im": "",
"is_root": 0
}
```
---
`GET /api/portal/user/:id/profile`
获取某个人的信息
---
`PUT /api/portal/user/:id/profile`
root账号修改某人的信息
```
{
"dispname": "",
"phone": "",
"email": "",
"im": "",
"is_root": 0
}
```
---
`PUT /api/portal/user/:id/password`
root账号重置某人的密码输入两次新密码保证一致的校验由前端来做
```
{
"password": ""
}
```
---
`DELETE /api/portal/user/:id`
root账号来删除某个用户
---
`GET /api/portal/team`
获取团队列表支持搜索搜索条件参数是query每页显示条数是limit页码是p
---
`POST /api/portal/team`
创建团队mgmt=0表示成员管理制mgmt=1表示管理员管理制admins是团队管理员的id列表members是团队普通成员的id列表
```
{
"ident": "",
"name": "",
"mgmt: 0,
"admins": [],
"members": []
}
```
---
`PUT /api/portal/team/:id`
修改团队信息
```
{
"ident": "",
"name": "",
"mgmt: 0,
"admins": [],
"members": []
}
```
---
`DELETE /api/portal/team/:id`
删除团队
---
`GET /api/portal/endpoint`
获取endpoint列表用于【服务树】-【对象列表】页面该页展示endpoint列表搜索条件参数是query每页显示条数是limit页码是p如果要做批量筛选则同时要指定用哪个字段(参数名字是field)来筛选只支持ident和alias批量筛选的内容是batch即batch和field一般是同时出现的
---
`POST /api/portal/endpoint`
导入endpoint要求传入列表每一条是ident::alias拼接在一起
```
{
"endpoints": []
}
```
---
`PUT /api/portal/endpoint/:id`
修改一个endpoint的alias信息
```
{
"alias": ""
}
```
---
`DELETE /api/portal/endpoint`
删除多个endpointids参数放到request body里
```
{
"ids": [10000, 200000]
}
```
---
`GET /api/portal/endpoints/bindings`
查询endpoint的绑定关系QueryStringidents逗号分隔多个
---
`GET /api/portal/endpoints/bynodeids`
根据节点id查询挂载了哪些endpointQueryStringids逗号分隔的多个节点id
---
`GET /api/portal/tree`
查询整颗服务树
---
`GET /api/portal/tree/search`
根据节点路径(path)查询服务树子树
---
`POST /api/portal/node`
创建服务树节点pid表示父节点idleaf=0表示非叶子节点leaf=1表示叶子节点note是备注信息
```
{
"pid": 0,
"name": "",
"leaf": 0,
"note": ""
}
```
---
`PUT /api/portal/node/:id/name`
服务树节点改名
```
{
"name": ""
}
```
---
`DELETE /api/portal/node/:id`
删除服务树节点
---
`GET /api/portal/node/:id/endpoint`
获取节点下面的endpoint列表查询字符串使用query每页展示多少条使用limit页码使用p如要批量筛选一行一个使用batch同时必须指定field即使用哪个字段进行批量筛选有ident和alias可选
---
`POST /api/portal/node/:id/endpoint-bind`
绑定一批endpoint到当前节点del_old=1表示同时删除老的挂载关系
```
{
"idents": [],
"del_old": 0
}
```
---
`POST /api/portal/node/:id/endpoint-unbind`
解绑endpoint和节点的挂载关系
```
{
"idents": []
}
```
---
`GET /api/portal/nodes/search`
搜索节点limit表示最多返回多少条query是搜索条件
---
`GET /api/portal/nodes/leafids`
获取节点对应的叶子节点的id参数是ids逗号分隔的多个节点id
---
`GET /api/portal/nodes/pids`
获取节点对应的父、祖节点的id参数是ids逗号分隔的多个节点id
---
`GET /api/portal/nodes/byids`
查询节点的信息参数是ids逗号分隔的多个节点id返回这多个节点的信息
---
`GET /api/portal/node/:id/maskconf`
获取报警屏蔽列表,因为已经是某个节点下的了,量比较少,后端不分页
---
`POST /api/portal/maskconf`
创建一个报警屏蔽策略
```
{
"nid": 0,
"endpoints": [],
"metric": "",
"tags": "",
"cause": "",
"btime": 1563838361,
"etime": 1563838461
}
```
---
`PUT /api/portal/maskconf/:id`
修改一个报警屏蔽策略
```
{
"endpoints": [],
"metric": "",
"tags": "",
"cause": "",
"btime": 1563838361,
"etime": 1563838461
}
```
---
`DELETE /api/portal/maskconf/:id`
删除一个报警屏蔽策略
---
`GET /api/portal/node/:id/screen`
获取screen列表因为已经是某个节点下的了量比较少后端不分页
---
`POST /api/portal/node/:id/screen`
创建screen
```
{
"name": ""
}
```
---
`PUT /api/portal/screen/:id`
修改screen其中node_id顺带也可以修改这样screen相当于直接挪动了挂载节点
```
{
"name": "",
"node_id": 0
}
```
---
`DELETE /api/portal/screen/:id`
删除某个screen
---
`GET /api/portal/screen/:id/subclass`
获取screen下面的子类返回的subclass按照weight字段排序
---
`POST /api/portal/screen/:id/subclass`
创建subclass
```
{
"name": "",
"weight": 0
}
```
---
`PUT /api/portal/subclass`
批量修改subclass
```
[
{
"id": 1,
"name": "a",
"weight": 1
},
{
"id": 2,
"name": "b",
"weight": 0
}
]
```
---
`DELETE /api/portal/subclass/:id`
删除某个subclass
---
`PUT /api/portal/subclasses/loc`
修改subclass的location即所属的screen
```
[
{
"id": 1,
"screen_id": 1
},
{
"id": 2,
"screen_id": 1
}
]
```
---
`GET /api/portal/subclass/:id/chart`
获取chart列表根据chart的weight排序不分页
---
`POST /api/portal/subclass/:id/chart`
创建chart
```
{
"configs": "",
"weight": 0
}
```
---
`PUT /api/portal/chart/:id`
修改某个chart的信息
```
{
"subclass_id": 1,
"configs": ""
}
```
---
`DELETE /api/portal/chart/:id`
删除某个chart
---
`PUT /api/portal/charts/weights`
修改chart的排序权重
```
{
"id": 1,
"weight": 9
}
```
---
`GET /api/portal/tmpchart`
获取临时图参数是QueryStringids逗号分隔的多个id
---
`POST /api/portal/tmpchart`
创建一个临时图返回生成的临时图的id列表
```
[
{
"configs": ""
},
{
"configs": ""
}
]
```
---
`GET /api/portal/event/cur`
获取某个节点下的未恢复报警列表QueryString
- 节点路径nodepath
- 开始时间stime
- 结束时间etime
- 每页条数limit
- 查询条件query
- 优先级priorities逗号分隔的多个
- 发送类型sendtypes逗号分隔的多个
---
`GET /api/portal/event/cur/:id`
获取当前某一个未恢复的报警
---
`DELETE /api/portal/event/cur/:id`
删除当前某一个未恢复的报警
---
`POST /api/portal/event/curs/claim`
认领某一些未恢复的告警避免告警升级到老板那里id和nodepath只能传入一个不能同时传入也不能一个都不传业务上的语义是要么认领某一个告警事件要么认领某个节点下的所有告警事件
```
{
"id": 1,
"nodepath": ""
}
```
---
`GET /api/portal/event/his`
获取某个节点下的所有报警列表QueryString
- 节点路径nodepath
- 开始时间stime
- 结束时间etime
- 每页条数limit
- 查询条件query
- 优先级priorities逗号分隔的多个
- 发送类型sendtypes逗号分隔的多个
- 事件类型type
---
`GET /api/portal/event/his/:id`
获取某个历史告警事件
---
`GET /api/portal/collect/list`
获取某个节点下面配置的采集策略必传QueryString: nid表示节点id后端不分页返回全部
---
`GET /api/portal/collect`
获取单个采集配置的详情QueryStringtype和id都必传type是port、proc、log之一。
---
`POST /api/portal/collect`
创建一个采集策略
```
{
"type": "",
"data": {
# 端口、进程、日志配置不同参阅model/collect.go
}
}
```
---
`PUT /api/portal/collect`
修改一个采集策略
```
{
"type": "",
"data": {
# 端口、进程、日志配置不同参阅model/collect.go
}
}
```
---
`DELETE /api/portal/collect`
删除采集策略
```
{
"type": "",
"ids": []
}
```
---
`POST /api/portal/collect/check`
校验用户输入的数据是否匹配正则,跟商业版本数据结构一致
---
`POST /api/portal/stra`
新增告警策略,跟商业版本数据结构一致
---
`PUT /api/portal/stra`
修改告警策略,跟商业版本数据结构一致
---
`DELETE /api/portal/stra`
删除告警策略,跟商业版本数据结构一致
---
`GET /api/portal/stra`
获取告警策略列表,跟商业版本数据结构一致
---
`GET /api/portal/stra/:id`
获取单个告警策略,跟商业版本数据结构一致

34
etc/address.yml Normal file
View File

@ -0,0 +1,34 @@
---
monapi:
http: 0.0.0.0:5800
addresses:
- 127.0.0.1
transfer:
http: 0.0.0.0:5810
rpc: 0.0.0.0:5811
addresses:
- 127.0.0.1
tsdb:
http: 0.0.0.0:5820
rpc: 0.0.0.0:5821
addresses:
- 127.0.0.1
index:
http: 0.0.0.0:5830
rpc: 0.0.0.0:5831
addresses:
- 127.0.0.1
judge:
http: 0.0.0.0:5840
rpc: 0.0.0.0:5841
addresses:
- 127.0.0.1
collector:
http: 0.0.0.0:2058

31
etc/collector.yml Normal file
View File

@ -0,0 +1,31 @@
# use shell if specify is blank
logger:
dir: logs/collector
level: WARNING
keepHours: 2
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1
sys:
# timeout in ms
# interval in second
timeout: 1000
interval: 20
ifacePrefix:
- eth
- em
mountPoint: []
mountIgnorePrefix:
- /var/lib
ignoreMetrics:
- cpu.core.idle
- cpu.core.util
- cpu.core.sys
- cpu.core.user
- cpu.core.nice
- cpu.core.guest
- cpu.core.irq
- cpu.core.softirq
- cpu.core.iowait
- cpu.core.steal

7
etc/index.yml Normal file
View File

@ -0,0 +1,7 @@
logger:
dir: logs/index
level: WARNING
keepHours: 2
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1

22
etc/judge.yml Normal file
View File

@ -0,0 +1,22 @@
query:
connTimeout: 1000
callTimeout: 2000
indexCallTimeout: 2000
redis:
addrs:
- 127.0.0.1:6379
pass: ""
# timeout:
# conn: 500
# read: 3000
# write: 3000
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1
logger:
dir: logs/judge
level: WARNING
keepHours: 2

12
etc/log/log.sys.oom.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "log.sys.oom",
"file_path": "/var/log/messages",
"time_format": "mmm dd HH:MM:SS",
"pattern": "Out of memory",
"interval": 10,
"tags": {},
"func": "cnt",
"degree": 6,
"unit": "次数",
"comment": "有进程oom了"
}

52
etc/monapi.yml Normal file
View File

@ -0,0 +1,52 @@
---
salt: "PLACE_SALT"
logger:
dir: "logs/monapi"
level: "WARNING"
keepHours: 24
http:
secret: "PLACE_SECRET"
# for ldap authorization
ldap:
host: "ldap.example.org"
port: 389
baseDn: "dc=example,dc=org"
bindUser: "cn=manager,dc=example,dc=org"
bindPass: "*******"
# openldap: (&(uid=%s))
# AD: (&(sAMAccountName=%s))
authFilter: "(&(uid=%s))"
tls: false
startTLS: false
# notify support: voice, sms, mail, im
# if we have all of notice channel
# notify:
# p1: ["voice", "sms", "mail", "im"]
# p2: ["sms", "mail", "im"]
# p3: ["mail", "im"]
# if we only have mail channel
notify:
p1: ["mail"]
p2: ["mail"]
p3: ["mail"]
# addresses accessible using browsers
link:
stra: http://n9e.example.com/#/monitor/strategy/%v
event: http://n9e.example.com/#/monitor/history/his/%v
claim: http://n9e.example.com/#/monitor/history/cur/%v
# for alarm event and message queue
redis:
addr: "127.0.0.1:6379"
pass: ""
# in ms
# timeout:
# conn: 500
# read: 3000
# write: 3000

16
etc/mysql.yml Normal file
View File

@ -0,0 +1,16 @@
---
uic:
addr: "root:1234@tcp(127.0.0.1:3306)/n9e_uic?charset=utf8&parseTime=True&loc=Asia%2FShanghai"
max: 16
idle: 4
debug: false
mon:
addr: "root:1234@tcp(127.0.0.1:3306)/n9e_mon?charset=utf8&parseTime=True&loc=Asia%2FShanghai"
max: 16
idle: 4
debug: false
hbs:
addr: "root:1234@tcp(127.0.0.1:3306)/n9e_hbs?charset=utf8&parseTime=True&loc=Asia%2FShanghai"
max: 16
idle: 4
debug: false

89
etc/nginx.conf Normal file
View File

@ -0,0 +1,89 @@
user nginx;
worker_processes auto;
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
use epoll;
worker_connections 204800;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
proxy_connect_timeout 500ms;
proxy_send_timeout 1000ms;
proxy_read_timeout 3000ms;
proxy_buffers 64 8k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 64k;
proxy_redirect off;
proxy_next_upstream error invalid_header timeout http_502 http_504;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-Port $remote_port;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
upstream n9e.monapi {
server 127.0.0.1:5800;
keepalive 10;
}
upstream n9e.index {
server 127.0.0.1:5830;
keepalive 10;
}
upstream n9e.transfer {
server 127.0.0.1:5810;
keepalive 10;
}
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
root /home/n9e/pub;
}
location /api/portal {
proxy_pass http://n9e.monapi;
}
location /api/index {
proxy_pass http://n9e.index;
}
location /api/transfer {
proxy_pass http://n9e.transfer;
}
}
}

1
etc/port/20_22 Normal file
View File

@ -0,0 +1 @@
sshd

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale collector
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-collector
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale index
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-index
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale judge
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-judge
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale monapi
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-monapi
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale transfer
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-transfer
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,20 @@
[Unit]
Description=Nightingale tsdb
After=network-online.target
Wants=network-online.target
[Service]
# modify when deploy in prod env
User=root
Group=root
Type=simple
ExecStart=/home/n9e/n9e-tsdb
WorkingDirectory=/home/n9e
Restart=always
RestartSec=1
StartLimitInterval=0
[Install]
WantedBy=multi-user.target

11
etc/transfer.yml Normal file
View File

@ -0,0 +1,11 @@
backend:
# in ms
# connTimeout: 1000
# callTimeout: 3000
cluster:
tsdb01: 127.0.0.1:5821
logger:
dir: logs/transfer
level: WARNING
keepHours: 2

8
etc/tsdb.yml Normal file
View File

@ -0,0 +1,8 @@
rrd:
storage: data/5821
cache:
keepMinutes: 120
logger:
dir: logs/tsdb
level: WARNING
keepHours: 2

30
go.mod Normal file
View File

@ -0,0 +1,30 @@
module github.com/didi/nightingale
go 1.12
require (
github.com/cespare/xxhash v1.1.0
github.com/codegangsta/negroni v1.0.0
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe
github.com/garyburd/redigo v1.6.0
github.com/gin-contrib/pprof v1.2.1
github.com/gin-contrib/sessions v0.0.3
github.com/gin-gonic/gin v1.5.0
github.com/go-sql-driver/mysql v1.4.1
github.com/gomodule/redigo v2.0.0+incompatible
github.com/gorilla/mux v1.6.2
github.com/hpcloud/tail v1.0.0
github.com/json-iterator/go v1.1.9
github.com/mattn/go-isatty v0.0.12
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.4.0
github.com/toolkits/pkg v1.1.1
github.com/ugorji/go/codec v1.1.7
github.com/unrolled/render v1.0.2
go.uber.org/automaxprocs v1.3.0 // indirect
gopkg.in/ldap.v3 v3.1.0
xorm.io/core v0.7.3
xorm.io/xorm v0.8.1
)

344
go.sum Normal file
View File

@ -0,0 +1,344 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe h1:VOrqop9SqFzqwZpROEOZpIufuLEUoJ3reNhdOdC9Zzw=
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe/go.mod h1:ft6P746mYUFQBCsH3OkFBG8FtjLx1XclLMo+9Jh1Yts=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/pprof v1.2.1 h1:p6mY/FsE3tDx2+Wp3ksrMe1QL5egQ7p+lsZ7WbQLT1U=
github.com/gin-contrib/pprof v1.2.1/go.mod h1:u2l4P4YNkLXYz+xBbrl7Pxu1Btng6VCD7j3O3pUPP2w=
github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI=
github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad h1:GXUy5t8CYdaaEj1lRnE22CbHVY1M5h6Rv4kk0PJQc54=
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad/go.mod h1:pXROoG0iWVnqq4u2Ii97S0Vt9iCTVypshsl9HXsV6cs=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/toolkits/pkg v1.1.1 h1:m57zdoBKQmTzhY83F3g56seDfLm+l/toBs8cKv8QFiE=
github.com/toolkits/pkg v1.1.1/go.mod h1:ge83E8FQqUnFk+2wtVtZ8kvbmoSjE1l8FP3f+qmR0fY=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/unrolled/render v1.0.2 h1:dGS3EmChQP3yOi1YeFNO/Dx+MbWZhdvhQJTXochM5bs=
github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE=
gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0=
xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ=
xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=

44
plugin/60_plugin_status.py Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: ulric.qin@gmail.com
import time
import commands
import json
import sys
import os
items = []
def collect_myself_status():
item = {}
item["metric"] = "plugin.myself.status"
item["value"] = 1
item["tags"] = ""
items.append(item)
def main():
code, endpoint = commands.getstatusoutput(
"timeout 1 /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1")
if code != 0:
sys.stderr.write('cannot get local ip')
return
timestamp = int("%d" % time.time())
plugin_name = os.path.basename(sys.argv[0])
step = int(plugin_name.split("_", 1)[0])
collect_myself_status()
for item in items:
item["endpoint"] = endpoint
item["timestamp"] = timestamp
item["step"] = step
print json.dumps(items)
if __name__ == "__main__":
main()

16
plugin/60_uptime.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# author: ulric.qin@gmail.com
duration=$(cat /proc/uptime | awk '{print $1}')
localip=$(/usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1)
step=$(basename $0|awk -F'_' '{print $1}')
echo '[
{
"endpoint": "'${localip}'",
"tags": "",
"timestamp": '$(date +%s)',
"metric": "sys.uptime.duration",
"value": '${duration}',
"step": '${step}'
}
]'

BIN
pub/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"index-3eb7c9ca4d88e3ad5c58.js","sources":["webpack:///index-3eb7c9ca4d88e3ad5c58.js"],"mappings":"AAAA;;;;;;;AAmsYA","sourceRoot":""}

1
pub/index.html Normal file
View File

@ -0,0 +1 @@
<!doctype html><html><head><meta charset="UTF-8"><title>Nightingale</title><link rel="shortcut icon" href="/favicon.ico"><link href="/index-3eb7c9ca4d88e3ad5c58.css" rel="stylesheet"></head><body><div id="react-content"></div><script src="/lib-033bee8514de110e36ef.dll.js"></script><script src="/index-3eb7c9ca4d88e3ad5c58.js"></script></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

17
sql/n9e_hbs.sql Normal file
View File

@ -0,0 +1,17 @@
set names utf8;
drop database if exists n9e_hbs;
create database n9e_hbs;
use n9e_hbs;
create table `instance` (
`id` int unsigned not null auto_increment,
`module` varchar(32) not null,
`identity` varchar(255) not null,
`rpc_port` varchar(16) not null,
`http_port` varchar(16) not null,
`remark` text,
`ts` int unsigned not null,
primary key (`id`),
key(`module`,`identity`,`rpc_port`,`http_port`)
) engine=innodb default charset=utf8;

268
sql/n9e_mon.sql Normal file
View File

@ -0,0 +1,268 @@
set names utf8;
drop database if exists n9e_mon;
create database n9e_mon;
use n9e_mon;
CREATE TABLE `node` (
`id` int unsigned not null AUTO_INCREMENT,
`pid` int unsigned not null,
`name` varchar(64) not null,
`path` varchar(255) not null,
`leaf` int(1) not null,
`note` varchar(128) not null default '',
PRIMARY KEY (`id`),
KEY (`path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `endpoint` (
`id` int unsigned not null AUTO_INCREMENT,
`ident` varchar(255) not null,
`alias` varchar(255) not null default '',
PRIMARY KEY (`id`),
UNIQUE KEY (`ident`),
KEY (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `node_endpoint` (
`node_id` int unsigned not null,
`endpoint_id` int unsigned not null,
KEY(`node_id`),
KEY(`endpoint_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
create table `maskconf` (
`id` int unsigned not null auto_increment,
`nid` int unsigned not null,
`metric` varchar(255) not null,
`tags` varchar(255) not null default '',
`cause` varchar(255) not null default '',
`user` varchar(32) not null default 'operate user',
`btime` bigint not null default 0 comment 'begin time',
`etime` bigint not null default 0 comment 'end time',
primary key (`id`),
key(`nid`)
) engine=innodb default charset=utf8;
create table `maskconf_endpoints` (
`id` int unsigned not null auto_increment,
`mask_id` int unsigned not null,
`endpoint` varchar(255) not null,
primary key (`id`),
key(`mask_id`)
) engine=innodb default charset=utf8;
create table `screen` (
`id` int unsigned not null auto_increment,
`node_id` int unsigned not null comment 'service tree node id',
`name` varchar(255) not null,
`last_updator` varchar(64) not null default '',
`last_updated` timestamp not null default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
primary key (`id`),
key(`node_id`)
) engine=innodb default charset=utf8;
create table `screen_subclass` (
`id` int unsigned not null auto_increment,
`screen_id` int unsigned not null,
`name` varchar(255) not null,
`weight` int not null default 0,
primary key (`id`),
key(`screen_id`)
) engine=innodb default charset=utf8;
create table `chart` (
`id` int unsigned not null auto_increment,
`subclass_id` int unsigned not null,
`configs` varchar(8192),
`weight` int not null default 0,
primary key (`id`),
key(`subclass_id`)
) engine=innodb default charset=utf8;
create table `tmp_chart` (
`id` int unsigned not null auto_increment,
`configs` varchar(8192),
`creator` varchar(64) not null,
`last_updated` timestamp not null default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
primary key (`id`)
) engine=innodb default charset=utf8;
create table `event_cur` (
`id` bigint(20) unsigned not null AUTO_INCREMENT comment 'id',
`sid` bigint(20) unsigned not null default 0 comment 'sid',
`sname` varchar(256) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(256) not null default '' comment 'node path',
`nid` int unsigned not null default '0' comment 'node id',
`endpoint` varchar(255) not null default '' comment 'endpoint',
`endpoint_alias` varchar(255) not null default '' comment 'endpoint alias',
`priority` tinyint(4) not null default 2 comment '优先级',
`event_type` varchar(45) not null default '' comment 'alert|recovery',
`category` tinyint(4) not null default 2 comment '1阈值 2智能',
`status` int(10) not null default 0 comment 'event status',
`detail` text comment 'counter points pred_points 详情',
`hashid` varchar(128) not null default '' comment 'sid+counter hash',
`etime` bigint(20) not null default 0 comment 'event ts',
`value` varchar(256) not null default '' comment '当前值',
`users` varchar(512) not null default '[]' comment 'notify users',
`groups` varchar(512) not null default '[]' comment 'notify groups',
`info` varchar(512) not null default '' comment 'strategy info',
`ignore_alert` int(2) not null default 0 comment 'ignore event',
`claimants` varchar(512) not null default '[]' comment 'claimants',
`need_upgrade` int(2) not null default 0 comment 'need upgrade',
`alert_upgrade` text comment 'alert upgrade',
`created` DATETIME not null default '1971-1-1 00:00:00' comment 'created',
KEY `idx_id` (`id`),
KEY `idx_sid` (`sid`),
KEY `idx_hashid` (`hashid`),
KEY `idx_node_path` (`node_path`),
KEY `idx_etime` (`etime`)
) engine=innodb default charset=utf8 comment 'event';
create table `event` (
`id` bigint(20) unsigned not null AUTO_INCREMENT comment 'id',
`sid` bigint(20) unsigned not null default 0 comment 'sid',
`sname` varchar(256) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(256) not null default '' comment 'node path',
`nid` int unsigned not null default '0' comment 'node id',
`endpoint` varchar(255) not null default '' comment 'endpoint',
`endpoint_alias` varchar(255) not null default '' comment 'endpoint alias',
`priority` tinyint(4) not null default 2 comment '优先级',
`event_type` varchar(45) not null default '' comment 'alert|recovery',
`category` tinyint(4) not null default 2 comment '1阈值 2智能',
`status` int(10) not null default 0 comment 'event status',
`detail` text comment 'counter points pred_points 详情',
`hashid` varchar(128) not null default '' comment 'sid+counter hash',
`etime` bigint(20) not null default 0 comment 'event ts',
`value` varchar(256) not null default '' comment '当前值',
`users` varchar(512) not null default '[]' comment 'notify users',
`groups` varchar(512) not null default '[]' comment 'notify groups',
`info` varchar(512) not null default '' comment 'strategy info',
`need_upgrade` int(2) not null default 0 comment 'need upgrade',
`alert_upgrade` text not null comment 'alert upgrade',
`created` DATETIME not null default '1971-1-1 00:00:00' comment 'created',
PRIMARY KEY (`id`),
KEY `idx_id` (`id`),
KEY `idx_sid` (`sid`),
KEY `idx_hashid` (`hashid`),
KEY `idx_node_path` (`node_path`),
KEY `idx_etime` (`etime`),
KEY `idx_event_type` (`event_type`),
KEY `idx_status` (`status`)
) engine=innodb default charset=utf8 comment 'event';
CREATE TABLE `stra` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL COMMENT 'strategy name',
`category` int(1) NOT NULL COMMENT '1 机器 2业务',
`nid` int(10) NOT NULL COMMENT '服务树节点id',
`excl_nid` varchar(255) NOT NULL COMMENT '被排除的服务树叶子节点id',
`alert_dur` int(4) NOT NULL COMMENT '单位秒持续异常n秒则产生异常event',
`recovery_dur` int(4) NOT NULL DEFAULT 0 COMMENT '单位秒持续正常n秒则产生恢复event0表示立即产生恢复event',
`exprs` varchar(1024) NOT NULL DEFAULT '' COMMENT '规则表达式',
`tags` varchar(1024) DEFAULT '' COMMENT 'tags过滤',
`enable_stime` varchar(6) NOT NULL DEFAULT '00:00' COMMENT '策略生效开始时间',
`enable_etime` varchar(6) NOT NULL DEFAULT '23:59' COMMENT '策略生效终止时间',
`enable_days_of_week` varchar(1024) NOT NULL DEFAULT '[0,1,2,3,4,5,6]' COMMENT '策略生效日期',
`converge` varchar(45) NOT NULL DEFAULT '' COMMENT 'n秒最多报m次警',
`recovery_notify` int(1) NOT NULL DEFAULT 1 COMMENT '1 发送恢复通知 0不发送恢复通知',
`priority` int(1) NOT NULL DEFAULT 3 COMMENT '告警等级',
`notify_group` varchar(256) NOT NULL DEFAULT '' COMMENT '告警通知组',
`notify_user` varchar(256) NOT NULL DEFAULT '' COMMENT '告警通知人',
`callback` varchar(1024) NOT NULL DEFAULT '' COMMENT 'callback url',
`creator` varchar(64) NOT NULL COMMENT '创建者',
`created` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'created',
`last_updator` varchar(64) NOT NULL DEFAULT '',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`need_upgrade` int(2) not null default 0 comment 'need upgrade',
`alert_upgrade` text comment 'alert upgrade',
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `stra_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`sid` bigint(20) NOT NULL DEFAULT '0' COMMENT 'collect id',
`action` varchar(255) NOT NULL DEFAULT '' COMMENT '动作 update, delete',
`body` text COMMENT '修改之前采集的内容',
`creator` varchar(255) NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'creator',
`created` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'created',
PRIMARY KEY (`id`),
KEY `idx_sid` (`sid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `port_collect` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`collect_type` varchar(64) NOT NULL DEFAULT 'PORT' COMMENT 'type',
`nid` int(10) NOT NULL COMMENT '服务树节点id',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
`tags` varchar(255) NOT NULL DEFAULT '' COMMENT 'tags',
`port` int(11) NOT NULL DEFAULT '0' COMMENT 'port',
`step` int(11) NOT NULL DEFAULT '0' COMMENT '采集周期',
`timeout` int(11) NOT NULL DEFAULT '0' COMMENT 'connect time',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(255) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last_updated',
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'port collect';
CREATE TABLE `proc_collect` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'nid',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
`tags` varchar(255) NOT NULL DEFAULT '' COMMENT 'tags',
`collect_type` varchar(64) NOT NULL DEFAULT 'PROC' COMMENT 'type',
`collect_method` varchar(64) NOT NULL DEFAULT 'name' COMMENT '采集方式',
`target` varchar(255) NOT NULL DEFAULT '' COMMENT '采集对象',
`step` int(11) NOT NULL DEFAULT '0' COMMENT '采集周期',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(255) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'proc collect';
CREATE TABLE `log_collect` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'nid',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
`tags` varchar(255) NOT NULL DEFAULT '' COMMENT 'tags',
`collect_type` varchar(64) NOT NULL DEFAULT 'PROC' COMMENT 'type',
`step` int(11) NOT NULL DEFAULT '0' COMMENT '采集周期',
`file_path` varchar(255) NOT NULL DEFAULT '' COMMENT 'file path',
`time_format` varchar(128) NOT NULL DEFAULT '' COMMENT 'time format',
`pattern` varchar(1024) NOT NULL DEFAULT '' COMMENT 'pattern',
`func` varchar(64) NOT NULL DEFAULT '' COMMENT 'func',
`degree` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'degree',
`func_type` varchar(64) NOT NULL DEFAULT '' COMMENT 'func_type',
`aggregate` varchar(64) NOT NULL DEFAULT '' COMMENT 'aggr',
`unit` varchar(64) NOT NULL DEFAULT '' COMMENT 'unit',
`zero_fill` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'zero fill',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(255) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'log collect';
CREATE TABLE `collect_hist` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`cid` bigint(20) NOT NULL DEFAULT '0' COMMENT 'collect id',
`collect_type` varchar(255) NOT NULL DEFAULT '' COMMENT '采集的种类 log,port,proc,plugin',
`action` varchar(255) NOT NULL DEFAULT '' COMMENT '动作 update, delete',
`body` text COMMENT '修改之前采集的内容',
`creator` varchar(255) NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'creator',
`created` datetime NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'created',
PRIMARY KEY (`id`),
KEY `idx_cid` (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'hist';

44
sql/n9e_uic.sql Normal file
View File

@ -0,0 +1,44 @@
set names utf8;
drop database if exists n9e_uic;
create database n9e_uic;
use n9e_uic;
CREATE TABLE `user` (
`id` int unsigned not null AUTO_INCREMENT,
`username` varchar(32) not null comment 'login name, cannot rename',
`password` varchar(128) not null default '',
`dispname` varchar(32) not null default '' comment 'display name, chinese name',
`phone` varchar(16) not null default '',
`email` varchar(64) not null default '',
`im` varchar(64) not null default '',
`is_root` int(1) not null,
PRIMARY KEY (`id`),
UNIQUE KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `invite` (
`id` int unsigned not null AUTO_INCREMENT,
`token` varchar(128) not null,
`expire` bigint not null,
`creator` varchar(32) not null,
PRIMARY KEY (`id`),
UNIQUE KEY (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `team` (
`id` int unsigned not null AUTO_INCREMENT,
`ident` varchar(255) not null,
`name` varchar(255) not null default '',
`mgmt` int(1) not null comment '0: member manage; 1: admin manage',
PRIMARY KEY (`id`),
UNIQUE KEY (`ident`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `team_user` (
`team_id` int unsigned not null,
`user_id` int unsigned not null,
`is_admin` int(1) not null,
KEY (`team_id`),
KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

24
src/dataobj/event.go Normal file
View File

@ -0,0 +1,24 @@
package dataobj
// Event 传递到alarm的结构体, 尽可能少的字段, 发出通知需要的信息由alarm自己补全
type Event struct {
ID string `json:"-"`
Sid int64 `json:"sid"`
EventType string `json:"event_type"` // alert/recover
Hashid uint64 `json:"hashid"` // 全局唯一 根据counter计算
Etime int64 `json:"etime"`
Endpoint string `json:"endpoint"`
History []History `json:"-"`
Detail string `json:"detail"`
Info string `json:"info"`
Value string `json:"value"`
Partition string `json:"-"`
}
type History struct {
Key string `json:"-"` // 用于计算event的hashid
Metric string `json:"metric"` // 指标名
Tags map[string]string `json:"tags,omitempty"` // endpoint/counter
Granularity int `json:"-"` // alarm补齐数据时需要
Points []*RRDData `json:"points"` // 现场值
}

17
src/dataobj/index.go Normal file
View File

@ -0,0 +1,17 @@
package dataobj
type IndexModel struct {
Endpoint string `json:"endpoint"`
Metric string `json:"metric"`
DsType string `json:"dsType"`
Step int `json:"step"`
Tags map[string]string `json:"tags"`
Timestamp int64 `json:"ts"`
}
type IndexResp struct {
Msg string
Total int
Invalid int
Latency int64
}

35
src/dataobj/judge.go Normal file
View File

@ -0,0 +1,35 @@
package dataobj
import (
"strconv"
"github.com/didi/nightingale/src/toolkits/str"
gstr "github.com/toolkits/pkg/str"
)
type JudgeItem struct {
Endpoint string `json:"endpoint"`
Metric string `json:"metric"`
Tags string `json:"tags"`
TagsMap map[string]string `json:"tagsMap"`
Value float64 `json:"value"`
Timestamp int64 `json:"timestamp"`
DsType string `json:"dstype"`
Step int `json:"step"`
Sid int64 `json:"sid"`
}
func (j *JudgeItem) PrimaryKey() string {
return str.PK(j.Endpoint, j.Metric, j.Tags)
}
func (j *JudgeItem) MD5() string {
return gstr.MD5(str.PK(strconv.FormatInt(j.Sid, 16), j.Endpoint, j.Metric, str.SortedTags(j.TagsMap)))
}
//告警现场的值
type HistoryData struct {
Timestamp int64 `json:"timestamp"`
Value float64 `json:"value"`
}

363
src/dataobj/metric.go Normal file
View File

@ -0,0 +1,363 @@
package dataobj
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"time"
)
const (
GAUGE = "GAUGE"
COUNTER = "COUNTER"
DERIVE = "DERIVE"
)
type MetricValue struct {
Metric string `json:"metric"`
Endpoint string `json:"endpoint"`
Timestamp int64 `json:"timestamp"`
Step int64 `json:"step"`
ValueUntyped interface{} `json:"value"`
Value float64 `json:"-"`
CounterType string `json:"counterType"`
Tags string `json:"tags"`
TagsMap map[string]string `json:"tagsMap"` //保留2种格式方便后端组件使用
}
const SPLIT = "/"
var bufferPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
func (m *MetricValue) String() string {
return fmt.Sprintf("<MetaData Endpoint:%s, Metric:%s, Timestamp:%d, Step:%d, Value:%v, Tags:%v(%v)>",
m.Endpoint, m.Metric, m.Timestamp, m.Step, m.ValueUntyped, m.Tags, m.TagsMap)
}
func (m *MetricValue) PK() string {
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
if m.TagsMap == nil || len(m.TagsMap) == 0 {
ret.WriteString(m.Endpoint)
ret.WriteString(SPLIT)
ret.WriteString(m.Metric)
return ret.String()
}
ret.WriteString(m.Endpoint)
ret.WriteString(SPLIT)
ret.WriteString(m.Metric)
ret.WriteString(SPLIT)
ret.WriteString(SortedTags(m.TagsMap))
return ret.String()
}
func (m *MetricValue) CheckValidity() (err error) {
if m == nil {
err = fmt.Errorf("item is nil")
return
}
//将保留字替换
var illegal bool
m.Metric, illegal = ReplaceReservedWords(m.Metric)
if illegal {
err = fmt.Errorf("Metric contains reserved word")
return
}
m.Endpoint, illegal = ReplaceReservedWords(m.Endpoint)
if illegal {
err = fmt.Errorf("Endpoint contains reserved word:%s", m.Endpoint)
return
}
if m.Metric == "" || m.Endpoint == "" {
err = fmt.Errorf("Metric|Endpoint is nil")
return
}
if m.CounterType == "" {
m.CounterType = GAUGE
}
if m.CounterType != COUNTER && m.CounterType != GAUGE && m.CounterType != DERIVE {
err = fmt.Errorf("CounterType error")
return
}
if m.ValueUntyped == "" {
err = fmt.Errorf("Value is nil")
return
}
if m.Step <= 0 {
err = fmt.Errorf("step < 0")
return
}
if len(m.TagsMap) == 0 {
m.TagsMap, err = SplitTagsString(m.Tags)
if err != nil {
return
}
}
m.Tags = SortedTags(m.TagsMap)
if len(m.Metric)+len(m.Tags) > 510 {
err = fmt.Errorf("metrc+tag > 510 is not illegal:")
return
}
//规范时间戳
now := time.Now().Unix()
if m.Timestamp <= 0 || m.Timestamp > now*2 {
m.Timestamp = now
}
valid := true
var vv float64
switch cv := m.ValueUntyped.(type) {
case string:
vv, err = strconv.ParseFloat(cv, 64)
if err != nil {
valid = false
}
case float64:
vv = cv
case uint64:
vv = float64(cv)
case int64:
vv = float64(cv)
case int:
vv = float64(cv)
default:
valid = false
}
if !valid {
err = fmt.Errorf("value is not illegal:%v", m)
return
}
m.Value = vv
return
}
func ReplaceReservedWords(str string) (string, bool) {
if -1 == strings.IndexFunc(str,
func(r rune) bool {
return r == '\t' ||
r == '\r' ||
r == '\n' ||
r == ',' ||
r == ' ' ||
r == ':' ||
r == '='
}) {
return str, false
}
return strings.Map(func(r rune) rune {
if r == '\t' ||
r == '\r' ||
r == '\n' ||
r == ',' ||
r == ' ' ||
r == ':' ||
r == '=' {
return '_'
}
return r
}, str), true
return str, false
}
func SortedTags(tags map[string]string) string {
if tags == nil {
return ""
}
size := len(tags)
if size == 0 {
return ""
}
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
if size == 1 {
for k, v := range tags {
ret.WriteString(k)
ret.WriteString("=")
ret.WriteString(v)
}
return ret.String()
}
keys := make([]string, size)
i := 0
for k := range tags {
keys[i] = k
i++
}
sort.Strings(keys)
for j, key := range keys {
ret.WriteString(key)
ret.WriteString("=")
ret.WriteString(tags[key])
if j != size-1 {
ret.WriteString(",")
}
}
return ret.String()
}
func SplitTagsString(s string) (tags map[string]string, err error) {
err = nil
tags = make(map[string]string)
s = strings.Replace(s, " ", "", -1)
if s == "" {
return
}
tagSlice := strings.Split(s, ",")
for _, tag := range tagSlice {
tagPair := strings.SplitN(tag, "=", 2)
if len(tagPair) == 2 {
tags[tagPair[0]] = tagPair[1]
} else {
err = fmt.Errorf("bad tag %s", tag)
return
}
}
return
}
func DictedTagstring(s string) map[string]string {
if s == "" {
return map[string]string{}
}
s = strings.Replace(s, " ", "", -1)
tag_dict := make(map[string]string)
tags := strings.Split(s, ",")
for _, tag := range tags {
tag_pair := strings.SplitN(tag, "=", 2)
if len(tag_pair) == 2 {
tag_dict[tag_pair[0]] = tag_pair[1]
}
}
return tag_dict
}
func PKWithCounter(endpoint, counter string) string {
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
ret.WriteString(endpoint)
ret.WriteString("/")
ret.WriteString(counter)
return ret.String()
}
func PKWithTags(metric, tags string) string {
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
if tags == "" {
ret.WriteString(metric)
return ret.String()
}
ret.WriteString(metric)
ret.WriteString("/")
ret.WriteString(tags)
return ret.String()
}
func PKWhitEndpointAndTags(endpoint, metric, tags string) string {
ret := bufferPool.Get().(*bytes.Buffer)
ret.Reset()
defer bufferPool.Put(ret)
if tags == "" {
ret.WriteString(endpoint)
ret.WriteString("/")
ret.WriteString(metric)
return ret.String()
}
ret.WriteString(endpoint)
ret.WriteString("/")
ret.WriteString(metric)
ret.WriteString("/")
ret.WriteString(tags)
return ret.String()
}
// e.g. tcp.port.listen or proc.num
type BuiltinMetric struct {
Metric string
Tags string
}
func (this *BuiltinMetric) String() string {
return fmt.Sprintf(
"%s/%s",
this.Metric,
this.Tags,
)
}
type BuiltinMetricRequest struct {
Ty int
IP string
Checksum string
}
type BuiltinMetricResponse struct {
Metrics []*BuiltinMetric
Checksum string
Timestamp int64
ErrCode int
}
func (this *BuiltinMetricResponse) String() string {
return fmt.Sprintf(
"<Metrics:%v, Checksum:%s, Timestamp:%v>",
this.Metrics,
this.Checksum,
this.Timestamp,
)
}
type BuiltinMetricSlice []*BuiltinMetric
func (this BuiltinMetricSlice) Len() int {
return len(this)
}
func (this BuiltinMetricSlice) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
func (this BuiltinMetricSlice) Less(i, j int) bool {
return this[i].String() < this[j].String()
}

39
src/dataobj/query_item.go Normal file
View File

@ -0,0 +1,39 @@
package dataobj
type QueryData struct {
Start int64 `json:"start"`
End int64 `json:"end"`
ConsolFunc string `json:"consolFunc"`
Endpoints []string `json:"endpoints"`
Counters []string `json:"counters"`
Step int `json:"step"`
DsType string `json:"dstype"`
}
type QueryDataForUI struct {
Start int64 `json:"start"`
End int64 `json:"end"`
Metric string `json:"metric"`
Endpoints []string `json:"endpoints"`
Tags []string `json:"tags"`
Step int `json:"step"`
DsType string `json:"dstype"`
GroupKey []string `json:"groupKey"` //聚合维度
AggrFunc string `json:"aggrFunc"` //聚合计算
ConsolFunc string `json:"consolFunc"`
Comparisons []int64 `json:"comparisons"` //环比多少时间
}
type QueryDataResp struct {
Data []*TsdbQueryResponse
Msg string
}
// judge 数据层 必须
func (req *QueryData) Key() string {
return req.Endpoints[0] + "/" + req.Counters[0]
}
func (resp *TsdbQueryResponse) Key() string {
return resp.Endpoint + "/" + resp.Counter
}

28
src/dataobj/rpc.go Normal file
View File

@ -0,0 +1,28 @@
package dataobj
import "fmt"
// code == 0 => success
// code == 1 => bad request
type SimpleRpcResponse struct {
Code int `json:"code"`
}
type NullRpcRequest struct {
}
type TransferResp struct {
Msg string
Total int
Invalid int
Latency int64
}
func (t *TransferResp) String() string {
s := fmt.Sprintf("TransferResp total=%d, err_invalid=%d, latency=%dms",
t.Total, t.Invalid, t.Latency)
if t.Msg != "" {
s = fmt.Sprintf("%s, msg=%s", s, t.Msg)
}
return s
}

21
src/dataobj/rrdlite.go Normal file
View File

@ -0,0 +1,21 @@
package dataobj
type File struct {
Key interface{}
Filename string
Body []byte
}
type RRDFileResp struct {
Files []File
Msg string
}
type RRDFileQuery struct {
Files []RRDFile
}
type RRDFile struct {
Key interface{}
Filename string
}

56
src/dataobj/stra.go Normal file
View File

@ -0,0 +1,56 @@
package dataobj
type StraData struct {
Dat []Stra `json:"dat"`
Err string `json:"err"`
}
type Stra struct {
ID int64 `json:"id"`
Name string `json:"name"`
Category int `json:"category"`
Nid int64 `json:"nid"`
AlertDur int `json:"alert_dur"`
RecoveryDur int `json:"recovery_dur"`
EnableStime string `json:"enable_stime"`
EnableEtime string `json:"enable_etime"`
Priority int `json:"priority"`
Callback string `json:"callback"`
Creator string `json:"creator"`
Created string `json:"created"`
LastUpdator string `json:"last_updator"`
LastUpdated string `json:"last_updated"`
ExclNid []int64 `json:"excl_nid"`
Exprs []Exp `json:"exprs"`
Tags []Tag `json:"tags"`
EnableDaysOfWeek []int `json:"enable_days_of_week"`
Converge []int `json:"converge"`
RecoveryNotify int `json:"recovery_notify"`
NotifyGroup []int64 `json:"notify_group"`
NotifyUser []int64 `json:"notify_user"`
LeafNids interface{} `json:"leaf_nids"`
NeedUpgrade int `json:"need_upgrade"`
AlertUpgrade AlertUpgrade `json:"alert_upgrade"`
}
type Exp struct {
Eopt string `json:"eopt"`
Func string `json:"func"`
Metric string `json:"metric"`
Params []int `json:"params"`
Threshold float64 `json:"threshold"`
}
type Tag struct {
Tkey string `json:"tkey"`
Topt string `json:"topt"`
Tval []string `json:"tval"` //修改为数组
}
type AlertUpgrade struct {
Users []int64 `json:"users"`
Groups []int64 `json:"groups"`
Duration int `json:"duration"`
Level int `json:"level"`
}

116
src/dataobj/tsdb.go Normal file
View File

@ -0,0 +1,116 @@
package dataobj
import (
"fmt"
"math"
"time"
"github.com/didi/nightingale/src/toolkits/str"
)
type JsonFloat float64
func (v JsonFloat) MarshalJSON() ([]byte, error) {
f := float64(v)
if math.IsNaN(f) || math.IsInf(f, 0) {
return []byte("null"), nil
} else {
return []byte(fmt.Sprintf("%f", f)), nil
}
}
type RRDData struct {
Timestamp int64 `json:"timestamp"`
Value JsonFloat `json:"value"`
}
type RRDValues []*RRDData
func (r RRDValues) Len() int { return len(r) }
func (r RRDValues) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r RRDValues) Less(i, j int) bool { return r[i].Timestamp < r[j].Timestamp }
func NewRRDData(ts int64, val float64) *RRDData {
return &RRDData{Timestamp: ts, Value: JsonFloat(val)}
}
func (this *RRDData) String() string {
return fmt.Sprintf(
"<RRDData:Value:%v TS:%d %v>",
this.Value,
this.Timestamp,
time.Unix(this.Timestamp, 0).Format("2006-01-02 15:04:05"),
)
}
type TsdbQueryResponse struct {
Start int64 `json:"start"`
End int64 `json:"end"`
Endpoint string `json:"endpoint"`
Counter string `json:"counter"`
DsType string `json:"dstype"`
Step int `json:"step"`
Values []*RRDData `json:"values"`
}
type TsdbItem struct {
Endpoint string `json:"endpoint"`
Metric string `json:"metric"`
Tags string `json:"tags"`
TagsMap map[string]string `json:"tagsMap"`
Value float64 `json:"value"`
Timestamp int64 `json:"timestamp"`
DsType string `json:"dstype"`
Step int `json:"step"`
Heartbeat int `json:"heartbeat"`
Min string `json:"min"`
Max string `json:"max"`
From int `json:"from"`
}
const GRAPH = 1
func (this *TsdbItem) String() string {
return fmt.Sprintf(
"<Endpoint:%s, Metric:%s, Tags:%v, TagsMap:%v, Value:%v, TS:%d %v DsType:%s, Step:%d, Heartbeat:%d, Min:%s, Max:%s>",
this.Endpoint,
this.Metric,
this.Tags,
this.TagsMap,
this.Value,
this.Timestamp,
str.UnixTsFormat(this.Timestamp),
this.DsType,
this.Step,
this.Heartbeat,
this.Min,
this.Max,
)
}
func (g *TsdbItem) PrimaryKey() string {
return str.PK(g.Endpoint, g.Metric, g.Tags)
}
func (g *TsdbItem) MD5() string {
return str.MD5(g.Endpoint, g.Metric, str.SortedTags(g.TagsMap))
}
func (this *TsdbItem) UUID() string {
return str.UUID(this.Endpoint, this.Metric, this.Tags, this.DsType, this.Step)
}
// ConsolFunc 是RRD中的概念比如MIN|MAX|AVERAGE
type TsdbQueryParam struct {
Start int64 `json:"start"`
End int64 `json:"end"`
ConsolFunc string `json:"consolFunc"`
Endpoint string `json:"endpoint"`
Counter string `json:"counter"`
Step int `json:"step"`
DsType string `json:"dsType"`
}
func (g *TsdbQueryParam) PK() string {
return PKWithCounter(g.Endpoint, g.Counter)
}

43
src/model/chart.go Normal file
View File

@ -0,0 +1,43 @@
package model
type Chart struct {
Id int64 `json:"id"`
SubclassId int64 `json:"subclass_id"`
Configs string `json:"configs"`
Weight int `json:"weight"`
}
func (c *Chart) Add() error {
_, err := DB["mon"].InsertOne(c)
return err
}
func ChartGets(subclassId int64) ([]Chart, error) {
var objs []Chart
err := DB["mon"].Where("subclass_id=?", subclassId).OrderBy("weight").Find(&objs)
return objs, err
}
func ChartGet(col string, val interface{}) (*Chart, error) {
var obj Chart
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (c *Chart) Update(cols ...string) error {
_, err := DB["mon"].Where("id=?", c.Id).Cols(cols...).Update(c)
return err
}
func (c *Chart) Del() error {
_, err := DB["mon"].Where("id=?", c.Id).Delete(new(Chart))
return err
}

434
src/model/collect.go Normal file
View File

@ -0,0 +1,434 @@
package model
import (
"fmt"
"regexp"
"strconv"
"sync"
"time"
"xorm.io/xorm"
)
type Collect struct {
sync.RWMutex
Ports map[int]*PortCollect `json:"ports"`
Procs map[string]*ProcCollect `json:"procs"`
Logs map[string]*LogCollect `json:"logs"`
}
func NewCollect() *Collect {
return &Collect{
Ports: make(map[int]*PortCollect),
Procs: make(map[string]*ProcCollect),
Logs: make(map[string]*LogCollect),
}
}
func (c *Collect) Update(cc *Collect) {
c.Lock()
defer c.Unlock()
//更新端口采集配置
c.Ports = make(map[int]*PortCollect)
for k, v := range cc.Ports {
c.Ports[k] = v
}
//更新进程采集配置
c.Procs = make(map[string]*ProcCollect)
for k, v := range cc.Procs {
c.Procs[k] = v
}
//更新log采集配置
c.Logs = make(map[string]*LogCollect)
for k, v := range cc.Logs {
c.Logs[k] = v
}
}
func (c *Collect) GetPorts() map[int]*PortCollect {
c.RLock()
defer c.RUnlock()
tmp := make(map[int]*PortCollect)
for k, v := range c.Ports {
tmp[k] = v
}
return tmp
}
func (c *Collect) GetProcs() map[string]*ProcCollect {
c.RLock()
defer c.RUnlock()
tmp := make(map[string]*ProcCollect)
for k, v := range c.Procs {
tmp[k] = v
}
return tmp
}
func (c *Collect) GetLogConfig() map[string]*LogCollect {
c.RLock()
defer c.RUnlock()
tmp := make(map[string]*LogCollect)
for k, v := range c.Logs {
tmp[k] = v
}
return tmp
}
type PortCollect struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
CollectType string `json:"collect_type"`
Name string `json:"name"`
Tags string `json:"tags"`
Step int `json:"step"`
Comment string `json:"comment"`
Creator string `json:"creator"`
Created time.Time `xorm:"updated" json:"created"`
LastUpdator string `xorm:"last_updator" json:"last_updator"`
LastUpdated time.Time `xorm:"updated" json:"last_updated"`
Port int `json:"port"`
Timeout int `json:"timeout"`
}
type ProcCollect struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
CollectType string `json:"collect_type"`
Name string `json:"name"`
Tags string `json:"tags"`
Step int `json:"step"`
Comment string `json:"comment"`
Creator string `json:"creator"`
Created time.Time `xorm:"updated" json:"created"`
LastUpdator string `xorm:"last_updator" json:"last_updator"`
LastUpdated time.Time `xorm:"updated" json:"last_updated"`
Target string `json:"target"`
CollectMethod string `json:"collect_method"`
}
type LogCollect struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
CollectType string `json:"collect_type"`
Name string `json:"name"`
TagsStr string `xorm:"tags" json:"-"`
Step int `json:"step"`
Comment string `json:"comment"`
Creator string `json:"creator"`
Created time.Time `xorm:"updated" json:"created"`
LastUpdator string `xorm:"last_updator" json:"last_updator"`
LastUpdated time.Time `xorm:"updated" json:"last_updated"`
Tags map[string]string `xorm:"-" json:"tags"`
FilePath string `json:"file_path"`
TimeFormat string `json:"time_format"`
Pattern string `json:"pattern"`
Func string `json:"func"`
FuncType string `json:"func_type"`
Unit string `json:"unit"`
Degree int `json:"degree"`
Zerofill int `xorm:"zero_fill" json:"zerofill"`
Aggregate string `json:"aggregate"`
LocalUpdated int64 `xorm:"-" json:"-"`
TimeReg *regexp.Regexp `xorm:"-" json:"-"`
PatternReg *regexp.Regexp `xorm:"-" json:"-"`
ExcludeReg *regexp.Regexp `xorm:"-" json:"-"`
TagRegs map[string]*regexp.Regexp `xorm:"-" json:"-"`
ParseSucc bool `xorm:"-" json:"-"`
}
type CollectHist struct {
Id int64 `json:"id"`
Cid int64 `json:"cid"`
CollectType string `json:"collect_type"`
Action string `json:"action"`
Body string `json:"body"`
Creator string `json:"creator"`
Created time.Time `xorm:"created" json:"created"`
}
func (l *LogCollect) Encode() error {
tags, err := json.Marshal(l.Tags)
if err != nil {
return fmt.Errorf("encode excl_nid err:%v", err)
}
l.TagsStr = string(tags)
return nil
}
func (l *LogCollect) Decode() error {
var err error
err = json.Unmarshal([]byte(l.TagsStr), &l.Tags)
if err != nil {
return err
}
return nil
}
func GetPortCollects() ([]*PortCollect, error) {
collects := []*PortCollect{}
err := DB["mon"].Find(&collects)
return collects, err
}
func (p *PortCollect) Update() error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
if _, err = session.Id(p.Id).AllCols().Update(p); err != nil {
session.Rollback()
return err
}
portByte, err := json.Marshal(p)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(p.Id, "port", "update", p.Creator, string(portByte), session); err != nil {
session.Rollback()
return err
}
if err = session.Commit(); err != nil {
return err
}
return err
}
func GetProcCollects() ([]*ProcCollect, error) {
collects := []*ProcCollect{}
err := DB["mon"].Find(&collects)
return collects, err
}
func (p *ProcCollect) Update() error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
if _, err = session.Id(p.Id).AllCols().Update(p); err != nil {
session.Rollback()
return err
}
b, err := json.Marshal(p)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(p.Id, "port", "update", p.Creator, string(b), session); err != nil {
session.Rollback()
return err
}
if err = session.Commit(); err != nil {
return err
}
return err
}
func GetLogCollects() ([]*LogCollect, error) {
collects := []*LogCollect{}
err := DB["mon"].Find(&collects)
return collects, err
}
func (p *LogCollect) Update() error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
if _, err = session.Id(p.Id).AllCols().Update(p); err != nil {
session.Rollback()
return err
}
b, err := json.Marshal(p)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(p.Id, "log", "update", p.Creator, string(b), session); err != nil {
session.Rollback()
return err
}
if err = session.Commit(); err != nil {
return err
}
return err
}
func CreateCollect(collectType, creator string, collect interface{}) error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
if _, err := session.Insert(collect); err != nil {
session.Rollback()
return err
}
b, err := json.Marshal(collect)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(0, collectType, "create", creator, string(b), session); err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func GetCollectByNid(collectType string, nids []int64) ([]interface{}, error) {
var res []interface{}
switch collectType {
case "port":
collects := []PortCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
res = append(res, c)
}
return res, err
case "proc":
collects := []ProcCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
res = append(res, c)
}
return res, err
case "log":
collects := []LogCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
c.Decode()
res = append(res, c)
}
return res, err
default:
return nil, fmt.Errorf("采集类型不合法")
}
}
func GetCollectById(collectType string, cid int64) (interface{}, error) {
switch collectType {
case "port":
collect := new(PortCollect)
_, err := DB["mon"].Where("id = ?", cid).Get(collect)
return collect, err
case "proc":
collect := new(ProcCollect)
_, err := DB["mon"].Where("id = ?", cid).Get(collect)
return collect, err
case "log":
collect := new(LogCollect)
_, err := DB["mon"].Where("id = ?", cid).Get(collect)
collect.Decode()
return collect, err
default:
return nil, fmt.Errorf("采集类型不合法")
}
return nil, nil
}
func GetCollectByName(collectType string, name string) (interface{}, error) {
var collect interface{}
_, err := DB["mon"].Table(collectType+"_collect").Where("name = ?", name).Get(&collect)
return collect, err
}
func DeleteCollectById(collectType, creator string, cid int64) error {
session := DB["mon"].NewSession()
defer session.Close()
sql := "delete from " + collectType + "_collect where id = ?"
_, err := DB["mon"].Exec(sql, cid)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(cid, collectType, "delete", creator, strconv.FormatInt(cid, 10), session); err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func saveHist(id int64, tp string, action, username, body string, session *xorm.Session) error {
h := CollectHist{
Cid: id,
CollectType: tp,
Action: action,
Creator: username,
Body: body,
}
_, err := session.Insert(&h)
if err != nil {
session.Rollback()
return err
}
return err
}
func GetCollectsModel(t string) (interface{}, error) {
switch t {
case "port":
collects := []*PortCollect{}
return collects, nil
case "proc":
collects := []*ProcCollect{}
return collects, nil
default:
return nil, fmt.Errorf("采集类型不合法")
}
}

254
src/model/endpoint.go Normal file
View File

@ -0,0 +1,254 @@
package model
import (
"strings"
"xorm.io/xorm"
"github.com/toolkits/pkg/str"
)
type Endpoint struct {
Id int64 `json:"id"`
Ident string `json:"ident"`
Alias string `json:"alias"`
}
func EndpointGet(col string, val interface{}) (*Endpoint, error) {
var obj Endpoint
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (e *Endpoint) Update(cols ...string) error {
_, err := DB["mon"].Where("id=?", e.Id).Cols(cols...).Update(e)
return err
}
func EndpointTotal(query, batch, field string) (int64, error) {
session := buildEndpointWhere(query, batch, field)
return session.Count(new(Endpoint))
}
func EndpointGets(query, batch, field string, limit, offset int) ([]Endpoint, error) {
session := buildEndpointWhere(query, batch, field).OrderBy(field).Limit(limit, offset)
var objs []Endpoint
err := session.Find(&objs)
return objs, err
}
func buildEndpointWhere(query, batch, field string) *xorm.Session {
session := DB["mon"].Table(new(Endpoint))
if batch == "" && query != "" {
q := "%" + query + "%"
session = session.Where("ident like ? or alias like ?", q, q)
}
if batch != "" {
endpoints := str.ParseCommaTrim(batch)
if len(endpoints) > 0 {
session = session.In(field, endpoints)
}
}
return session
}
func EndpointImport(endpoints []string) error {
count := len(endpoints)
if count == 0 {
return nil
}
session := DB["mon"].NewSession()
defer session.Close()
for i := 0; i < count; i++ {
arr := strings.Split(endpoints[i], "::")
ident := strings.TrimSpace(arr[0])
alias := ""
if len(arr) == 2 {
alias = strings.TrimSpace(arr[1])
}
if ident == "" {
continue
}
err := endpointImport(session, ident, alias)
if err != nil {
return err
}
}
return nil
}
func endpointImport(session *xorm.Session, ident, alias string) error {
var endpoint Endpoint
has, err := session.Where("ident=?", ident).Get(&endpoint)
if err != nil {
return err
}
if has {
endpoint.Alias = alias
_, err = session.Where("ident=?", ident).Cols("alias").Update(endpoint)
} else {
_, err = session.Insert(Endpoint{Ident: ident, Alias: alias})
}
return err
}
func EndpointDel(ids []int64) error {
if len(ids) == 0 {
return nil
}
bindings, err := NodeEndpointGetByEndpointIds(ids)
if err != nil {
return err
}
for i := 0; i < len(bindings); i++ {
err = NodeEndpointUnbind(bindings[i].NodeId, bindings[i].EndpointId)
if err != nil {
return err
}
}
if _, err := DB["mon"].In("id", ids).Delete(new(Endpoint)); err != nil {
return err
}
return nil
}
func buildEndpointUnderNodeWhere(leafids []int64, query, batch, field string) *xorm.Session {
session := DB["mon"].Where("id in (select endpoint_id from node_endpoint where node_id in (" + str.IdsString(leafids) + "))")
if batch == "" && query != "" {
q := "%" + query + "%"
session = session.Where("ident like ? or alias like ?", q, q)
}
if batch != "" {
endpoints := str.ParseCommaTrim(batch)
if len(endpoints) > 0 {
session = session.In(field, endpoints)
}
}
return session
}
func EndpointUnderNodeTotal(leafids []int64, query, batch, field string) (int64, error) {
session := buildEndpointUnderNodeWhere(leafids, query, batch, field)
return session.Count(new(Endpoint))
}
func EndpointUnderNodeGets(leafids []int64, query, batch, field string, limit, offset int) ([]Endpoint, error) {
session := buildEndpointUnderNodeWhere(leafids, query, batch, field).Limit(limit, offset).OrderBy(field)
var objs []Endpoint
err := session.Find(&objs)
return objs, err
}
func EndpointIdsByIdents(idents []string) ([]int64, error) {
idents = str.TrimStringSlice(idents)
if len(idents) == 0 {
return []int64{}, nil
}
var objs []Endpoint
err := DB["mon"].In("ident", idents).Find(&objs)
if err != nil {
return []int64{}, err
}
cnt := len(objs)
ret := make([]int64, 0, cnt)
for i := 0; i < cnt; i++ {
ret = append(ret, objs[i].Id)
}
return ret, nil
}
type EndpointBinding struct {
Ident string `json:"ident"`
Alias string `json:"alias"`
Nodes []Node `json:"nodes"`
}
func EndpointBindings(endpointIds []int64) ([]EndpointBinding, error) {
var nes []NodeEndpoint
err := DB["mon"].In("endpoint_id", endpointIds).Find(&nes)
if err != nil {
return []EndpointBinding{}, err
}
cnt := len(nes)
if cnt == 0 {
return []EndpointBinding{}, nil
}
h2n := make(map[int64][]int64)
arr := make([]int64, 0, cnt)
for i := 0; i < cnt; i++ {
arr = append(arr, nes[i].EndpointId)
h2n[nes[i].EndpointId] = append(h2n[nes[i].EndpointId], nes[i].NodeId)
}
var endpoints []Endpoint
err = DB["mon"].In("id", arr).Find(&endpoints)
if err != nil {
return []EndpointBinding{}, err
}
cnt = len(endpoints)
ret := make([]EndpointBinding, 0, cnt)
for i := 0; i < cnt; i++ {
nodeids := h2n[endpoints[i].Id]
if nodeids == nil || len(nodeids) == 0 {
continue
}
var nodes []Node
err = DB["mon"].In("id", nodeids).Find(&nodes)
if err != nil {
return []EndpointBinding{}, err
}
b := EndpointBinding{
Ident: endpoints[i].Ident,
Alias: endpoints[i].Alias,
Nodes: nodes,
}
ret = append(ret, b)
}
return ret, nil
}
func EndpointUnderLeafs(leafIds []int64) ([]Endpoint, error) {
var endpoints []Endpoint
if len(leafIds) == 0 {
return []Endpoint{}, nil
}
err := DB["mon"].Where("id in (select endpoint_id from node_endpoint where node_id in (" + str.IdsString(leafIds) + "))").Find(&endpoints)
return endpoints, err
}

239
src/model/event.go Normal file
View File

@ -0,0 +1,239 @@
package model
import (
"strings"
"time"
"github.com/didi/nightingale/src/modules/monapi/config"
)
type Event struct {
Id int64 `json:"id"`
Sid int64 `json:"sid"`
Sname string `json:"sname"`
NodePath string `json:"node_path"`
Endpoint string `json:"endpoint"`
EndpointAlias string `json:"endpoint_alias"`
Priority int `json:"priority"`
EventType string `json:"event_type"` // alert|recovery
Category int `json:"category"`
Status uint16 `json:"status"`
HashId uint64 `json:"hashid" xorm:"hashid"`
Etime int64 `json:"etime"`
Value string `json:"value"`
Info string `json:"info"`
Created time.Time `json:"created" xorm:"created"`
Detail string `json:"detail"`
Users string `json:"users"`
Groups string `json:"groups"`
Nid int64 `json:"nid"`
NeedUpgrade int `json:"need_upgrade"`
AlertUpgrade string `json:"alert_upgrade"`
}
type EventDetail struct {
Metric string `json:"metric"`
Tags map[string]string `json:"tags"`
Points []*EventDetailPoint `json:"points"`
PredPoints []*EventDetailPoint `json:"pred_points,omitempty"` // 预测值, 预测值不为空时, 现场值对应的是实际值
}
type EventDetailPoint struct {
Timestamp int64 `json:"timestamp"`
Value float64 `json:"value"`
}
type EventAlertUpgrade struct {
Users string `json:"users"`
Groups string `json:"groups"`
Duration int `json:"duration"`
Level int `json:"level"`
}
func ParseEtime(etime int64) string {
t := time.Unix(etime, 0)
return t.Format("2006-01-02 15:04:05")
}
type EventSlice []*Event
func (e EventSlice) Len() int {
return len(e)
}
func (e EventSlice) Less(i, j int) bool {
return e[i].Etime < e[j].Etime
}
func (e EventSlice) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func SaveEvent(event *Event) error {
_, err := DB["mon"].Insert(event)
return err
}
func SaveEventStatus(id int64, status string) error {
sql := "update event set status = status | ? where id = ?"
_, err := DB["mon"].Exec(sql, GetStatus(status), id)
return err
}
func UpdateEventPriority(id int64, priority int) error {
sql := "update event set priority=? where id=?"
_, err := DB["mon"].Exec(sql, priority, id)
return err
}
func (e *Event) GetEventDetail() ([]EventDetail, error) {
var detail []EventDetail
err := json.Unmarshal([]byte(e.Detail), &detail)
return detail, err
}
func EventTotal(stime, etime int64, nodePath, query, eventType string, priorities, sendTypes []string) (int64, error) {
session := DB["mon"].Where("etime > ? and etime < ? and node_path = ?", stime, etime, nodePath)
if len(priorities) > 0 && priorities[0] != "" {
session = session.In("priority", priorities)
}
if len(sendTypes) > 0 && sendTypes[0] != "" {
session = session.In("status", GetFlagsByStatus(sendTypes))
}
if eventType != "" {
session = session.Where("event_type=?", eventType)
}
if query != "" {
fields := strings.Fields(query)
for i := 0; i < len(fields); i++ {
if fields[i] == "" {
continue
}
q := "%" + fields[i] + "%"
session = session.Where("sname like ? or endpoint like ? or node_path like ?", q, q, q)
}
}
total, err := session.Count(new(Event))
return total, err
}
func EventGets(stime, etime int64, nodePath, query, eventType string, priorities, sendTypes []string, limit, offset int) ([]Event, error) {
var objs []Event
session := DB["mon"].Where("etime > ? and etime < ? and node_path = ?", stime, etime, nodePath)
if len(priorities) > 0 && priorities[0] != "" {
session = session.In("priority", priorities)
}
if len(sendTypes) > 0 && sendTypes[0] != "" {
session = session.In("status", GetFlagsByStatus(sendTypes))
}
if eventType != "" {
session = session.Where("event_type=?", eventType)
}
if query != "" {
fields := strings.Fields(query)
for i := 0; i < len(fields); i++ {
if fields[i] == "" {
continue
}
q := "%" + fields[i] + "%"
session = session.Where("sname like ? or endpoint like ? or node_path like ?", q, q, q)
}
}
err := session.Desc("etime").Limit(limit, offset).Find(&objs)
return objs, err
}
func EventGet(col string, value interface{}) (*Event, error) {
var obj Event
has, err := DB["mon"].Where(col+"=?", value).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func DelEventOlder(ts int64, batch int) (int64, error) {
sql := "delete from event where etime < ? limit ?"
ret, err := DB["mon"].Exec(sql, ts, batch)
if err != nil {
return 0, err
}
return ret.RowsAffected()
}
func EventAlertUpgradeUnMarshal(str string) (EventAlertUpgrade, error) {
var obj EventAlertUpgrade
if strings.TrimSpace(str) == "" {
return EventAlertUpgrade{
Users: "[]",
Groups: "[]",
Duration: 0,
Level: 0,
}, nil
}
err := json.Unmarshal([]byte(str), &obj)
return obj, err
}
func EventCnt(hashid uint64, stime, etime string, isUpgrade bool) (int64, error) {
session := DB["mon"].Where("hashid = ? and event_type = ? and created between ? and ?", hashid, config.ALERT, stime, etime)
if isUpgrade {
return session.In("status", GetFlagsByStatus([]string{STATUS_UPGRADE, STATUS_SEND})).Count(new(Event))
}
return session.In("status", GetFlagsByStatus([]string{STATUS_SEND})).Count(new(Event))
}
func EventAlertUpgradeMarshal(alertUpgrade AlertUpgrade) (string, error) {
eventAlertUpgrade := EventAlertUpgrade{
Duration: alertUpgrade.Duration,
Level: alertUpgrade.Level,
}
if alertUpgrade.Users == nil {
eventAlertUpgrade.Users = "[]"
} else {
upgradeUsers, err := json.Marshal(alertUpgrade.Users)
if err != nil {
return "", err
}
eventAlertUpgrade.Users = string(upgradeUsers)
}
if alertUpgrade.Groups == nil {
eventAlertUpgrade.Groups = "[]"
} else {
upgradeGroups, err := json.Marshal(alertUpgrade.Groups)
if err != nil {
return "", err
}
eventAlertUpgrade.Groups = string(upgradeGroups)
}
alertUpgradebytes, err := json.Marshal(eventAlertUpgrade)
return string(alertUpgradebytes), err
}

247
src/model/event_cur.go Normal file
View File

@ -0,0 +1,247 @@
package model
import (
"fmt"
"strings"
"time"
"github.com/toolkits/pkg/slice"
jsoniter "github.com/json-iterator/go"
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type EventCur struct {
Id int64 `json:"id"`
Sid int64 `json:"sid"`
Sname string `json:"sname"`
NodePath string `json:"node_path"`
Endpoint string `json:"endpoint"`
EndpointAlias string `json:"endpoint_alias"`
Priority int `json:"priority"`
EventType string `json:"event_type"` // alert|recovery
Category int `json:"category"`
Status uint16 `json:"status"`
HashId uint64 `json:"hashid" xorm:"hashid"`
Etime int64 `json:"etime"`
Value string `json:"value"`
Info string `json:"info"`
Created time.Time `json:"created" xorm:"created"`
Detail string `json:"detail"`
Users string `json:"users"`
Groups string `json:"groups"`
Nid int64 `json:"nid"`
IgnoreAlert int `json:"ignore_alert"`
Claimants string `json:"claimants"`
NeedUpgrade int `json:"need_upgrade"`
AlertUpgrade string `json:"alert_upgrade"`
}
func UpdateEventCurPriority(hashid uint64, priority int) error {
sql := "update event_cur set priority=? where hashid=?"
_, err := DB["mon"].Exec(sql, priority, hashid)
return err
}
func SaveEventCur(eventCur *EventCur) error {
session := DB["mon"].NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
return err
}
has, err := session.Where("hashid=?", eventCur.HashId).Get(new(EventCur))
if err != nil {
session.Rollback()
return err
}
if has {
if _, err := session.Where("hashid=?", eventCur.HashId).Cols("sid", "sname", "node_path", "endpoint", "priority", "category", "status", "etime", "detail", "value", "info", "users", "groups", "nid", "alert_upgrade", "need_upgrade", "endpoint_alias").Update(eventCur); err != nil {
session.Rollback()
return err
}
} else {
if _, err := session.Insert(eventCur); err != nil {
session.Rollback()
return err
}
}
if err := session.Commit(); err != nil {
session.Rollback()
return err
}
return nil
}
func UpdateClaimantsById(userId, id int64) error {
var obj EventCur
has, err := DB["mon"].Where("id=?", id).Cols("claimants").Get(&obj)
if err != nil {
return err
}
if !has {
return fmt.Errorf("event not exists")
}
var users []int64
if err = json.Unmarshal([]byte(obj.Claimants), &users); err != nil {
return err
}
users = append(users, userId)
data, err := json.Marshal(slice.UniqueInt64(users))
if err != nil {
return err
}
_, err = DB["mon"].Exec("update event_cur set claimants=? where id=?", string(data), id)
return err
}
func UpdateClaimantsByNodePath(userId int64, nodePath string) error {
var objs []EventCur
session := DB["mon"].NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
return err
}
if err := session.Where("node_path = ?", nodePath).Find(&objs); err != nil {
session.Rollback()
return err
}
for i := 0; i < len(objs); i++ {
var users []int64
if err := json.Unmarshal([]byte(objs[i].Claimants), &users); err != nil {
session.Rollback()
return err
}
users = append(users, userId)
data, err := json.Marshal(slice.UniqueInt64(users))
if err != nil {
session.Rollback()
return err
}
_, err = session.Exec("update event_cur set claimants=? where id=?", string(data), objs[i].Id)
if err != nil {
session.Rollback()
return err
}
}
if err := session.Commit(); err != nil {
session.Rollback()
return err
}
return nil
}
func EventCurDel(hashid uint64) error {
_, err := DB["mon"].Where("hashid=?", hashid).Delete(new(EventCur))
return err
}
func SaveEventCurStatus(hashid uint64, status string) error {
sql := "update event_cur set status = status | ? where hashid = ?"
_, err := DB["mon"].Exec(sql, GetStatus(status), hashid)
return err
}
func EventCurTotal(stime, etime int64, nodePath, query string, priorities, sendTypes []string) (int64, error) {
session := DB["mon"].Where("etime > ? and etime < ? and node_path = ? and ignore_alert=0", stime, etime, nodePath)
if len(priorities) > 0 && priorities[0] != "" {
session = session.In("priority", priorities)
}
if len(sendTypes) > 0 && sendTypes[0] != "" {
session = session.In("status", GetFlagsByStatus(sendTypes))
}
if query != "" {
fields := strings.Fields(query)
for i := 0; i < len(fields); i++ {
if fields[i] == "" {
continue
}
q := "%" + fields[i] + "%"
session = session.Where("sname like ? or endpoint like ? or node_path like ?", q, q, q)
}
}
total, err := session.Count(new(EventCur))
return total, err
}
func EventCurGets(stime, etime int64, nodePath, query string, priorities, sendTypes []string, limit, offset int) ([]EventCur, error) {
var obj []EventCur
session := DB["mon"].Where("etime > ? and etime < ? and node_path = ? and ignore_alert=0", stime, etime, nodePath)
if len(priorities) > 0 && priorities[0] != "" {
session = session.In("priority", priorities)
}
if len(sendTypes) > 0 && sendTypes[0] != "" {
session = session.In("status", GetFlagsByStatus(sendTypes))
}
if query != "" {
fields := strings.Fields(query)
for i := 0; i < len(fields); i++ {
if fields[i] == "" {
continue
}
q := "%" + fields[i] + "%"
session = session.Where("sname like ? or endpoint like ? or node_path like ? ", q, q, q)
}
}
err := session.Desc("etime").Limit(limit, offset).Find(&obj)
return obj, err
}
func EventCurGet(col string, value interface{}) (*EventCur, error) {
var obj EventCur
has, err := DB["mon"].Where(col+"=?", value).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (e *EventCur) EventIgnore() error {
_, err := DB["mon"].Exec("update event_cur set ignore_alert=1 where id=?", e.Id)
return err
}
func DelEventCurOlder(ts int64, batch int) (int64, error) {
sql := "delete from event_cur where etime < ? limit ?"
ret, err := DB["mon"].Exec(sql, ts, batch)
if err != nil {
return 0, err
}
return ret.RowsAffected()
}

View File

@ -0,0 +1,197 @@
package model
// 0 0 0 0 0 0 处理中
// 0 0 0 0 x 1 已发送
// 0 0 0 0 1 x 已回调
// 0 0 0 1 0 0 已屏蔽
// 0 0 1 0 0 0 被收敛
// 0 1 0 0 x 0 无接收人
// 1 0 0 0 x 0 升级发送
const (
FLAG_SEND = iota
FLAG_CALLBACK
FLAG_MASK
FLAG_CONVERGE
FLAG_NONEUSER
FLAG_UPGRADE
)
const (
STATUS_DOING = "doing" // 处理中
STATUS_SEND = "send" // 已发送
STATUS_NONEUSER = "none-user" // 无接收人
STATUS_CALLBACK = "callback" // 已回调
STATUS_MASK = "mask" // 已屏蔽
STATUS_CONVERGE = "converge" // 频率限制
STATUS_UPGRADE = "upgrade" // 升级报警
)
func StatusConvert(s []string) []string {
status := []string{}
for i := 0; i < len(s); i++ {
switch s[i] {
case STATUS_DOING:
status = append(status, "处理中")
case STATUS_SEND:
status = append(status, "已发送")
case STATUS_NONEUSER:
status = append(status, "无接收人")
case STATUS_CALLBACK:
status = append(status, "已回调")
case STATUS_MASK:
status = append(status, "已屏蔽")
case STATUS_CONVERGE:
status = append(status, "已收敛")
case STATUS_UPGRADE:
status = append(status, "已升级")
}
}
return status
}
func GetStatus(status string) int {
switch status {
case STATUS_SEND:
return 1 << FLAG_SEND
case STATUS_NONEUSER:
return 1 << FLAG_NONEUSER
case STATUS_CALLBACK:
return 1 << FLAG_CALLBACK
case STATUS_MASK:
return 1 << FLAG_MASK
case STATUS_CONVERGE:
return 1 << FLAG_CONVERGE
case STATUS_UPGRADE:
return 1 << FLAG_UPGRADE
}
return 0
}
func GetFlagsByStatus(ss []string) []uint16 {
if len(ss) == 0 {
return []uint16{}
}
flags := make(map[string][]uint16)
for _, s := range ss {
switch s {
case STATUS_DOING:
flags[s] = getDoing()
case STATUS_SEND:
flags[s] = getSend()
case STATUS_NONEUSER:
flags[s] = getNoneUser()
case STATUS_CALLBACK:
flags[s] = getCallback()
case STATUS_MASK:
flags[s] = getMask()
case STATUS_CONVERGE:
flags[s] = getConverge()
case STATUS_UPGRADE:
flags[s] = getUpgrade()
}
}
uss := make([][]uint16, 0)
for _, s := range flags {
uss = append(uss, s)
}
return interSection(uss)
}
func GetStatusByFlag(flag uint16) []string {
ret := make([]string, 0)
if flag == 0 {
ret = append(ret, STATUS_DOING)
return ret
}
if (flag>>FLAG_UPGRADE)&0x01 == 1 {
ret = append(ret, STATUS_UPGRADE)
}
if (flag>>FLAG_CONVERGE)&0x01 == 1 {
ret = append(ret, STATUS_CONVERGE)
return ret
}
if (flag>>FLAG_MASK)&0x01 == 1 {
ret = append(ret, STATUS_MASK)
return ret
}
if (flag>>FLAG_SEND)&0x01 == 1 {
ret = append(ret, STATUS_SEND)
}
if (flag>>FLAG_CALLBACK)&0x01 == 1 {
ret = append(ret, STATUS_CALLBACK)
}
if (flag>>FLAG_NONEUSER)&0x01 == 1 {
ret = append(ret, STATUS_NONEUSER)
}
return ret
}
// 0 0 0 0 0 0 正在处理
func getDoing() []uint16 {
return []uint16{0}
}
// x 0 0 0 x 1 已发送
func getSend() []uint16 {
return []uint16{1, 3, 33, 35}
}
// x 0 0 0 1 x 已回调
func getCallback() []uint16 {
return []uint16{2, 3, 34, 35}
}
// 0 0 0 1 0 0 已屏蔽
func getMask() []uint16 {
return []uint16{4}
}
// x 0 1 0 0 0 被收敛
func getConverge() []uint16 {
return []uint16{8, 40}
}
// x 1 0 0 x 0 无接收人
func getNoneUser() []uint16 {
return []uint16{16, 18, 48, 50}
}
// 1 x x 0 x x 已升级
func getUpgrade() []uint16 {
return []uint16{32, 33, 34, 35, 40, 41, 42, 43, 48, 49, 50, 51, 56, 57, 58, 59}
}
func interSection(ss [][]uint16) []uint16 {
if len(ss) == 0 {
return []uint16{}
}
umap := make(map[uint16]int)
for _, s := range ss {
for _, su := range s {
if _, found := umap[su]; found {
umap[su] += 1
} else {
umap[su] = 1
}
}
}
ret := []uint16{}
for su, cnt := range umap {
if cnt == len(ss) {
ret = append(ret, su)
}
}
return ret
}

15
src/model/funcs.go Normal file
View File

@ -0,0 +1,15 @@
package model
import "strings"
func Paths(longPath string) []string {
names := strings.Split(longPath, ".")
count := len(names)
paths := make([]string, 0, count)
for i := 1; i <= count; i++ {
paths = append(paths, strings.Join(names[:i], "."))
}
return paths
}

65
src/model/hbs.go Normal file
View File

@ -0,0 +1,65 @@
package model
import "time"
type Instance struct {
Id int64 `json:"id"`
Module string `json:"module"`
Identity string `json:"identity"` //ip 或者 机器名
RPCPort string `json:"rpc_port" xorm:"rpc_port"`
HTTPPort string `json:"http_port" xorm:"http_port"`
TS int64 `json:"ts" xorm:"ts"`
Remark string `json:"remark"`
Active bool `xorm:"-" json:"active"`
}
func (i *Instance) Add() error {
_, err := DB["hbs"].InsertOne(i)
return err
}
func (i *Instance) Update() error {
_, err := DB["hbs"].Where("id=?", i.Id).MustCols("ts", "http_port", "rpc_port").Update(i)
return err
}
func GetInstanceBy(mod, identity, rpcPort, httpPort string) (*Instance, error) {
var obj Instance
has, err := DB["hbs"].Where("module=? and identity=? and rpc_port=? and http_port=?", mod, identity, rpcPort, httpPort).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func GetAllInstances(mod string, alive int) ([]*Instance, error) {
objs := make([]*Instance, 0)
var err error
now := time.Now().Unix()
ts := now - 60
if alive == 1 {
err = DB["hbs"].Where("module = ? and ts > ?", mod, ts).OrderBy("id").Find(&objs)
} else {
err = DB["hbs"].Where("module = ?", mod).OrderBy("id").Find(&objs)
}
if err != nil {
return objs, err
}
for _, j := range objs {
if j.TS > now-60 { //上报心跳时间在1分钟之内
j.Active = true
}
}
return objs, err
}
func DelById(id int64) error {
_, err := DB["hbs"].Where("id=?", id).Delete(new(Instance))
return err
}

37
src/model/invite.go Normal file
View File

@ -0,0 +1,37 @@
package model
import (
"time"
)
type Invite struct {
Id int64
Token string
Expire int64
Creator string
}
func InviteGet(col string, val interface{}) (*Invite, error) {
var obj Invite
has, err := DB["uic"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func InviteAdd(token, creator string) error {
now := time.Now().Unix()
obj := Invite{
Token: token,
Creator: creator,
Expire: now + 3600*24*30,
}
_, err := DB["uic"].Insert(&obj)
return err
}

268
src/model/maskconf.go Normal file
View File

@ -0,0 +1,268 @@
package model
import (
"fmt"
"sort"
"strings"
"github.com/toolkits/pkg/str"
)
type Maskconf struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
NodePath string `json:"node_path" xorm:"-"`
Metric string `json:"metric"`
Tags string `json:"tags"`
Cause string `json:"cause"`
User string `json:"user"`
Btime int64 `json:"btime"`
Etime int64 `json:"etime"`
Endpoints []string `json:"endpoints" xorm:"-"`
}
func (mc *Maskconf) Add(endpoints []string) error {
_, err := DB["mon"].Insert(mc)
if err != nil {
return err
}
affected := 0
for i := 0; i < len(endpoints); i++ {
endpoint := strings.TrimSpace(endpoints[i])
if endpoint == "" {
continue
}
_, err = DB["mon"].Insert(&MaskconfEndpoints{
MaskId: mc.Id,
Endpoint: endpoint,
})
if err != nil {
return err
}
affected++
}
if affected == 0 {
return fmt.Errorf("arg[endpoints] empty")
}
return nil
}
func (mc *Maskconf) FillEndpoints() error {
var objs []MaskconfEndpoints
err := DB["mon"].Where("mask_id=?", mc.Id).OrderBy("id").Find(&objs)
if err != nil {
return err
}
cnt := len(objs)
arr := make([]string, cnt)
for i := 0; i < cnt; i++ {
arr[i] = objs[i].Endpoint
}
mc.Endpoints = arr
return nil
}
func MaskconfGets(nodeId int64) ([]Maskconf, error) {
node, err := NodeGet("id", nodeId)
if err != nil {
return nil, err
}
if node.Leaf == 1 {
var objs []Maskconf
err = DB["mon"].Where("nid=?", nodeId).OrderBy("id desc").Find(&objs)
if err != nil {
return nil, err
}
for i := 0; i < len(objs); i++ {
objs[i].NodePath = node.Path
}
return objs, nil
}
var relatedNodeIds []int64
err = DB["mon"].Table("maskconf").Select("nid").Find(&relatedNodeIds)
if err != nil {
return nil, err
}
if len(relatedNodeIds) == 0 {
return []Maskconf{}, nil
}
var nodes []Node
err = DB["mon"].Where("id in ("+str.IdsString(relatedNodeIds)+")").Where("id="+fmt.Sprint(node.Id)+" or path like ?", node.Path+".%").Find(&nodes)
if err != nil {
return nil, err
}
count := len(nodes)
if count == 0 {
return []Maskconf{}, nil
}
ids := make([]int64, 0, count)
nmap := make(map[int64]Node, count)
for i := 0; i < count; i++ {
nmap[nodes[i].Id] = nodes[i]
ids = append(ids, nodes[i].Id)
}
var objs []Maskconf
err = DB["mon"].In("nid", ids).Find(&objs)
if err != nil {
return nil, err
}
count = len(objs)
for i := 0; i < count; i++ {
n, has := nmap[objs[i].Nid]
if has {
objs[i].NodePath = n.Path
}
}
if count == 0 {
return []Maskconf{}, nil
}
sort.Slice(objs, func(i int, j int) bool {
if objs[i].NodePath < objs[j].NodePath {
return true
}
if objs[i].Id > objs[j].Id {
return true
}
return false
})
return objs, nil
}
func MaskconfDel(id int64) error {
_, err := DB["mon"].Where("mask_id=?", id).Delete(new(MaskconfEndpoints))
if err != nil {
return err
}
_, err = DB["mon"].Where("id=?", id).Delete(new(Maskconf))
return err
}
func MaskconfGetAll() ([]Maskconf, error) {
var objs []Maskconf
err := DB["mon"].Find(&objs)
return objs, err
}
func CleanExpireMask(now int64) error {
var objs []Maskconf
err := DB["mon"].Where("etime<?", now).Cols("id").Find(&objs)
if err != nil {
return err
}
if len(objs) == 0 {
return nil
}
session := DB["mon"].NewSession()
defer session.Close()
if err = session.Begin(); err != nil {
return err
}
for i := 0; i < len(objs); i++ {
if _, err := session.Exec("delete from maskconf where id=?", objs[i].Id); err != nil {
session.Rollback()
return err
}
if _, err := session.Exec("delete from maskconf_endpoints where mask_id=?", objs[i].Id); err != nil {
session.Rollback()
return err
}
}
err = session.Commit()
return err
}
func MaskconfGet(col string, value interface{}) (*Maskconf, error) {
var obj Maskconf
has, err := DB["mon"].Where(col+"=?", value).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (mc *Maskconf) Update(endpoints []string, cols ...string) error {
session := DB["mon"].NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
return err
}
if _, err := session.Where("id=?", mc.Id).Cols(cols...).Update(mc); err != nil {
session.Rollback()
return err
}
if _, err := session.Exec("delete from maskconf_endpoints where mask_id=?", mc.Id); err != nil {
session.Rollback()
return err
}
affected := 0
for i := 0; i < len(endpoints); i++ {
endpoint := strings.TrimSpace(endpoints[i])
if endpoint == "" {
continue
}
_, err := session.Insert(&MaskconfEndpoints{
MaskId: mc.Id,
Endpoint: endpoint,
})
if err != nil {
session.Rollback()
return err
}
affected += 1
}
if affected == 0 {
session.Rollback()
return fmt.Errorf("arg[endpoints] empty")
}
if err := session.Commit(); err != nil {
session.Rollback()
return err
}
return nil
}

View File

@ -0,0 +1,7 @@
package model
type MaskconfEndpoints struct {
Id int64 `json:"id"`
MaskId int64 `json:"mask_id"`
Endpoint string `json:"endpoint"`
}

58
src/model/mysql.go Normal file
View File

@ -0,0 +1,58 @@
package model
import (
"log"
"path"
"time"
"xorm.io/core"
"xorm.io/xorm"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/runner"
)
type MySQLConf struct {
Addr string `yaml:"addr"`
Max int `yaml:"max"`
Idle int `yaml:"idle"`
Debug bool `yaml:"debug"`
}
var DB = map[string]*xorm.Engine{}
func InitMySQL(names ...string) {
confdir := path.Join(runner.Cwd, "etc")
mysqlYml := path.Join(confdir, "mysql.local.yml")
if !file.IsExist(mysqlYml) {
mysqlYml = path.Join(confdir, "mysql.yml")
}
confs := make(map[string]MySQLConf)
err := file.ReadYaml(mysqlYml, &confs)
if err != nil {
log.Fatalf("cannot read yml[%s]: %v", mysqlYml, err)
}
count := len(names)
for i := 0; i < count; i++ {
conf, has := confs[names[i]]
if !has {
log.Fatalf("no such mysql conf: %s", names[i])
}
db, err := xorm.NewEngine("mysql", conf.Addr)
if err != nil {
log.Fatalf("cannot connect mysql[%s]: %v", conf.Addr, err)
}
db.SetMaxIdleConns(conf.Idle)
db.SetMaxOpenConns(conf.Max)
db.SetConnMaxLifetime(time.Hour)
db.ShowSQL(conf.Debug)
db.Logger().SetLevel(core.LOG_INFO)
DB[names[i]] = db
}
}

354
src/model/node.go Normal file
View File

@ -0,0 +1,354 @@
package model
import (
"fmt"
"log"
"strings"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type Node struct {
Id int64 `json:"id"`
Pid int64 `json:"pid"`
Name string `json:"name"`
Path string `json:"path"`
Leaf int `json:"leaf"`
Note string `json:"note"`
}
// InitNode 初始化第一个node节点
func InitNode() {
num, err := DB["mon"].Where("pid=0").Count(new(Node))
if err != nil {
log.Fatalln("cannot query first node", err)
}
if num > 0 {
return
}
node := Node{
Pid: 0,
Name: "cop",
Path: "cop",
Leaf: 0,
Note: "公司节点",
}
_, err = DB["mon"].Insert(&node)
if err != nil {
log.Fatalln("cannot insert node[cop]")
}
logger.Info("node cop init done")
}
func NodeGets(where string, args ...interface{}) (nodes []Node, err error) {
if where != "" {
err = DB["mon"].Where(where, args...).Find(&nodes)
} else {
err = DB["mon"].Find(&nodes)
}
return nodes, err
}
func NodeGetsByPaths(paths []string) ([]Node, error) {
if paths == nil || len(paths) == 0 {
return []Node{}, nil
}
var nodes []Node
err := DB["mon"].In("path", paths).Find(&nodes)
return nodes, err
}
func NodeByIds(ids []int64) ([]Node, error) {
if len(ids) == 0 {
return []Node{}, nil
}
return NodeGets(fmt.Sprintf("id in (%s)", str.IdsString(ids)))
}
func NodeQueryPath(query string, limit int) (nodes []Node, err error) {
err = DB["mon"].Where("path like ?", "%"+query+"%").OrderBy("path").Limit(limit).Find(&nodes)
return nodes, err
}
func TreeSearchByPath(query string) (nodes []Node, err error) {
session := DB["mon"].NewSession()
defer session.Clone()
if strings.Contains(query, " ") {
arr := strings.Fields(query)
cnt := len(arr)
for i := 0; i < cnt; i++ {
session.Where("path like ?", "%"+arr[i]+"%")
}
err = session.Find(&nodes)
} else {
err = session.Where("path like ?", "%"+query+"%").Find(&nodes)
}
if err != nil {
return
}
cnt := len(nodes)
if cnt == 0 {
return
}
pathset := make(map[string]struct{})
for i := 0; i < cnt; i++ {
pathset[nodes[i].Path] = struct{}{}
paths := Paths(nodes[i].Path)
for j := 0; j < len(paths); j++ {
pathset[paths[j]] = struct{}{}
}
}
var objs []Node
err = session.In("path", str.MtoL(pathset)).Find(&objs)
return objs, err
}
func NodeGet(col string, val interface{}) (*Node, error) {
var obj Node
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func NodesGetByIds(ids []int64) ([]Node, error) {
var objs []Node
err := DB["mon"].In("id", ids).Find(&objs)
return objs, err
}
func NodeValid(name, path string) error {
if len(name) > 32 {
return fmt.Errorf("name too long")
}
if len(path) > 255 {
return fmt.Errorf("path too long")
}
if !str.IsMatch(name, `^[a-z0-9\-]+$`) {
return fmt.Errorf("name permissible characters: [a-z0-9] and -")
}
arr := strings.Split(path, ".")
if name != arr[len(arr)-1] {
return fmt.Errorf("name and path not match")
}
return nil
}
func (n *Node) CreateChild(name string, leaf int, note string) (int64, error) {
if n.Leaf == 1 {
return 0, fmt.Errorf("parent node is leaf, cannot create child")
}
path := n.Path + "." + name
node, err := NodeGet("path", path)
if err != nil {
return 0, err
}
if node != nil {
return 0, fmt.Errorf("node[%s] already exists", path)
}
child := Node{
Pid: n.Id,
Name: name,
Path: path,
Leaf: leaf,
Note: note,
}
_, err = DB["mon"].Insert(&child)
return child.Id, err
}
func (n *Node) Bind(endpointIds []int64, delOld int) error {
if delOld == 1 {
bindings, err := NodeEndpointGetByEndpointIds(endpointIds)
if err != nil {
return err
}
for i := 0; i < len(bindings); i++ {
err = NodeEndpointUnbind(bindings[i].NodeId, bindings[i].EndpointId)
if err != nil {
return err
}
}
}
cnt := len(endpointIds)
for i := 0; i < cnt; i++ {
if err := NodeEndpointBind(n.Id, endpointIds[i]); err != nil {
return err
}
}
return nil
}
func (n *Node) Unbind(hostIds []int64) error {
if hostIds == nil || len(hostIds) == 0 {
return nil
}
for i := 0; i < len(hostIds); i++ {
if err := NodeEndpointUnbind(n.Id, hostIds[i]); err != nil {
return err
}
}
return nil
}
func (n *Node) LeafIds() ([]int64, error) {
if n.Leaf == 1 {
return []int64{n.Id}, nil
}
var nodes []Node
err := DB["mon"].Where("path like ? and leaf=1", n.Path+".%").Find(&nodes)
if err != nil {
return []int64{}, err
}
cnt := len(nodes)
arr := make([]int64, 0, cnt)
for i := 0; i < cnt; i++ {
arr = append(arr, nodes[i].Id)
}
return arr, nil
}
func (n *Node) Pids() ([]int64, error) {
if n.Pid == 0 {
return []int64{n.Pid}, nil
}
var objs []Node
arr := []int64{}
paths := []string{}
nodes := strings.Split(n.Path, ".")
cnt := len(nodes)
for i := 1; i < cnt; i++ {
path := strings.Join(nodes[:cnt-i], ".")
paths = append(paths, path)
}
err := DB["mon"].In("path", paths).Find(&objs)
if err != nil {
return []int64{}, err
}
cnt = len(objs)
for i := 0; i < cnt; i++ {
arr = append(arr, objs[i].Id)
}
return arr, nil
}
func (n *Node) Rename(name string) error {
oldprefix := n.Path + "."
arr := strings.Split(n.Path, ".")
arr[len(arr)-1] = name
newpath := strings.Join(arr, ".")
newprefix := newpath + "."
brother, err := NodeGet("path", newpath)
if err != nil {
return err
}
if brother != nil {
return fmt.Errorf("%s already exists", newpath)
}
var nodes []Node
err = DB["mon"].Where("path like ?", oldprefix+"%").Find(&nodes)
if err != nil {
return err
}
session := DB["mon"].NewSession()
defer session.Close()
if err = session.Begin(); err != nil {
return err
}
if _, err = session.Exec("UPDATE node SET name=?, path=? WHERE id=?", name, newpath, n.Id); err != nil {
session.Rollback()
return err
}
cnt := len(nodes)
for i := 0; i < cnt; i++ {
if _, err = session.Exec("UPDATE node SET path=? WHERE id=?", strings.Replace(nodes[i].Path, oldprefix, newprefix, 1), nodes[i].Id); err != nil {
session.Rollback()
return err
}
}
return session.Commit()
}
func (n *Node) Del() error {
if n.Pid == 0 {
return fmt.Errorf("cannot delete root node")
}
// 叶子节点下不能有endpoint
if n.Leaf == 1 {
cnt, err := DB["mon"].Where("node_id=?", n.Id).Count(new(NodeEndpoint))
if err != nil {
return err
}
if cnt > 0 {
return fmt.Errorf("there are endpoint binding this node")
}
}
// 非叶子节点下不能有子节点
if n.Leaf == 0 {
cnt, err := DB["mon"].Where("pid=?", n.Id).Count(new(Node))
if err != nil {
return err
}
if cnt > 0 {
return fmt.Errorf("node[%s] has children node", n.Path)
}
}
_, err := DB["mon"].Where("id=?", n.Id).Delete(new(Node))
return err
}

113
src/model/node_endpoint.go Normal file
View File

@ -0,0 +1,113 @@
package model
import (
"fmt"
)
type NodeEndpoint struct {
NodeId int64 `xorm:"'node_id'"`
EndpointId int64 `xorm:"'endpoint_id'"`
}
func (NodeEndpoint) TableName() string {
return "node_endpoint"
}
func NodeIdsGetByEndpointId(endpointId int64) ([]int64, error) {
if endpointId == 0 {
return []int64{}, nil
}
var ids []int64
err := DB["mon"].Table("node_endpoint").Where("endpoint_id = ?", endpointId).Select("node_id").Find(&ids)
return ids, err
}
func EndpointIdsByNodeIds(nodeIds []int64) ([]int64, error) {
if nodeIds == nil || len(nodeIds) == 0 {
return []int64{}, nil
}
var ids []int64
err := DB["mon"].Table("node_endpoint").In("node_id", nodeIds).Select("endpoint_id").Find(&ids)
return ids, err
}
func NodeEndpointGetByEndpointIds(endpointsIds []int64) ([]NodeEndpoint, error) {
if endpointsIds == nil || len(endpointsIds) == 0 {
return []NodeEndpoint{}, nil
}
var objs []NodeEndpoint
err := DB["mon"].In("endpoint_id", endpointsIds).Find(&objs)
return objs, err
}
// EndpointBindingsForMail 用来发告警邮件的时候带上各个endpoint的挂载信息
func EndpointBindingsForMail(endpoints []string) []string {
ids, err := EndpointIdsByIdents(endpoints)
if err != nil {
return []string{fmt.Sprintf("get endpoint ids by idents fail: %v", err)}
}
if len(ids) == 0 {
return []string{}
}
bindings, err := EndpointBindings(ids)
if err != nil {
return []string{fmt.Sprintf("get endpoint bindings fail: %v", err)}
}
var ret []string
size := len(bindings)
for i := 0; i < size; i++ {
for j := 0; j < len(bindings[i].Nodes); j++ {
ret = append(ret, bindings[i].Ident+" - "+bindings[i].Alias+" - "+bindings[i].Nodes[j].Path)
}
}
return ret
}
func NodeEndpointGetByNodeIds(nodeIds []int64) ([]NodeEndpoint, error) {
if nodeIds == nil || len(nodeIds) == 0 {
return []NodeEndpoint{}, nil
}
var objs []NodeEndpoint
err := DB["mon"].In("node_id", nodeIds).Find(&objs)
return objs, err
}
func NodeEndpointUnbind(nid, eid int64) error {
_, err := DB["mon"].Where("node_id=? and endpoint_id=?", nid, eid).Delete(new(NodeEndpoint))
return err
}
func NodeEndpointBind(nid, eid int64) error {
total, err := DB["mon"].Where("node_id=? and endpoint_id=?", nid, eid).Count(new(NodeEndpoint))
if err != nil {
return err
}
if total > 0 {
return nil
}
endpoint, err := EndpointGet("id", eid)
if err != nil {
return err
}
if endpoint == nil {
return fmt.Errorf("endpoint[id:%d] not found", eid)
}
_, err = DB["mon"].Insert(&NodeEndpoint{
NodeId: nid,
EndpointId: eid,
})
return err
}

61
src/model/screen.go Normal file
View File

@ -0,0 +1,61 @@
package model
import (
"time"
)
type Screen struct {
Id int64 `json:"id"`
NodeId int64 `json:"node_id"`
Name string `json:"name"`
LastUpdator string `json:"last_updator"`
LastUpdated time.Time `xorm:"<-" json:"last_updated"`
}
func (s *Screen) Add() error {
_, err := DB["mon"].Insert(s)
return err
}
func ScreenGets(nodeId int64) ([]Screen, error) {
var objs []Screen
err := DB["mon"].Where("node_id=?", nodeId).OrderBy("name").Find(&objs)
return objs, err
}
func ScreenGet(col string, val interface{}) (*Screen, error) {
var obj Screen
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (s *Screen) Update(cols ...string) error {
_, err := DB["mon"].Where("id=?", s.Id).Cols(cols...).Update(s)
return err
}
func (s *Screen) Del() error {
subclasses, err := ScreenSubclassGets(s.Id)
if err != nil {
return err
}
cnt := len(subclasses)
for i := 0; i < cnt; i++ {
err = subclasses[i].Del()
if err != nil {
return err
}
}
_, err = DB["mon"].Where("id=?", s.Id).Delete(new(Screen))
return err
}

View File

@ -0,0 +1,48 @@
package model
type ScreenSubclass struct {
Id int64 `json:"id"`
ScreenId int64 `json:"screen_id"`
Name string `json:"name"`
Weight int `json:"weight"`
}
func (s *ScreenSubclass) Add() error {
_, err := DB["mon"].Insert(s)
return err
}
func ScreenSubclassGets(screenId int64) ([]ScreenSubclass, error) {
var objs []ScreenSubclass
err := DB["mon"].Where("screen_id=?", screenId).OrderBy("weight").Find(&objs)
return objs, err
}
func ScreenSubclassGet(col string, val interface{}) (*ScreenSubclass, error) {
var obj ScreenSubclass
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func (s *ScreenSubclass) Update(cols ...string) error {
_, err := DB["mon"].Where("id=?", s.Id).Cols(cols...).Update(s)
return err
}
func (s *ScreenSubclass) Del() error {
_, err := DB["mon"].Where("subclass_id=?", s.Id).Delete(new(Chart))
if err != nil {
return err
}
_, err = DB["mon"].Where("id=?", s.Id).Delete(new(ScreenSubclass))
return err
}

537
src/model/stra.go Normal file
View File

@ -0,0 +1,537 @@
package model
import (
"fmt"
"strconv"
"strings"
"time"
"xorm.io/xorm"
)
type Stra struct {
Id int64 `json:"id"`
Name string `json:"name"`
Category int `json:"category"` //机器,非机器
Nid int64 `json:"nid"` //服务树节点id
ExclNidStr string `xorm:"excl_nid" json:"-"` //排除的叶子节点
AlertDur int `json:"alert_dur"` //单位秒持续异常10分钟则产生异常event
RecoveryDur int `json:"recovery_dur"` //单位秒持续正常2分钟则产生恢复event0表示立即产生恢复event
RecoveryNotify int `json:"recovery_notify"` //0 发送恢复通知 1不发送恢复通知
ExprsStr string `xorm:"exprs" json:"-"` //多个条件的监控实例需要相同并且同时满足才产生event
TagsStr string `xorm:"tags" json:"-"` //tag过滤条件
EnableStime string `json:"enable_stime"` //策略生效开始时间
EnableEtime string `json:"enable_etime"` //策略生效终止时间 支持23:00-02:00
EnableDaysOfWeekStr string `xorm:"enable_days_of_week" json:"-"` //策略生效日期
ConvergeStr string `xorm:"converge" json:"-"` //告警通知收敛第1个值表示收敛周期单位秒第2个值表示周期内允许发送告警次数
Priority int `json:"priority"`
Callback string `json:"callback"`
NotifyGroupStr string `xorm:"notify_group" json:"-"`
NotifyUserStr string `xorm:"notify_user" json:"-"`
Creator string `json:"creator"`
Created time.Time `xorm:"created" json:"created"`
LastUpdator string `xorm:"last_updator" json:"last_updator"`
LastUpdated time.Time `xorm:"<-" json:"last_updated"`
NeedUpgrade int `xorm:"need_upgrade" json:"need_upgrade"`
AlertUpgradeStr string `xorm:"alert_upgrade" json:"-"`
ExclNid []int64 `xorm:"-" json:"excl_nid"`
Exprs []Exp `xorm:"-" json:"exprs"`
Tags []Tag `xorm:"-" json:"tags"`
EnableDaysOfWeek []int `xorm:"-" json:"enable_days_of_week"`
Converge []int `xorm:"-" json:"converge"`
NotifyGroup []int `xorm:"-" json:"notify_group"`
NotifyUser []int `xorm:"-" json:"notify_user"`
LeafNids []int64 `xorm:"-" json:"leaf_nids"` //叶子节点id
Endpoints []string `xorm:"-" json:"endpoints"`
AlertUpgrade AlertUpgrade `xorm:"-" json:"alert_upgrade"`
JudgeInstance string `xorm:"-" json:"judge_instance"`
}
type StraLog struct {
Id int64 `json:"id"`
Sid int64 `json:"sid"`
Action string `json:"action"` // update|delete
Body string `json:"body"`
Creator string `json:"creator"`
Created time.Time `json:"created" xorm:"created"`
}
type Exp struct {
Eopt string `json:"eopt"`
Func string `json:"func"` //all,max,min
Metric string `json:"metric"` //metric
Params []int `json:"params"` //连续n秒
Threshold float64 `json:"threshold"` //阈值
}
type Tag struct {
Tkey string `json:"tkey"`
Topt string `json:"topt"`
Tval []string `json:"tval"` //修改为数组
}
type AlertUpgrade struct {
Users []int64 `json:"users"`
Groups []int64 `json:"groups"`
Duration int `json:"duration"`
Level int `json:"level"`
}
var MathOperators = map[string]bool{
">": true,
"<": true,
">=": true,
"<=": true,
"!=": true,
"=": true,
}
func (s *Stra) Save() error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
session.Rollback()
return err
}
_, err = session.Insert(s)
if err != nil {
session.Rollback()
return err
}
straByte, err := json.Marshal(s)
if err != nil {
session.Rollback()
return err
}
err = SaveStraCommit(s.Id, "add", s.Creator, string(straByte), session)
if err != nil {
session.Rollback()
return err
}
session.Commit()
return nil
}
func (s *Stra) Update() error {
var obj Stra
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
session.Rollback()
return err
}
exists, err := session.Id(s.Id).Get(&obj)
if err != nil {
session.Rollback()
return err
}
if !exists {
session.Rollback()
return fmt.Errorf("%d not exists", s.Id)
}
_, err = session.Id(s.Id).AllCols().Update(s)
if err != nil {
session.Rollback()
return err
}
straByte, err := json.Marshal(s)
if err != nil {
session.Rollback()
return err
}
err = SaveStraCommit(s.Id, "update", s.Creator, string(straByte), session)
if err != nil {
session.Rollback()
return err
}
session.Commit()
return nil
}
func StraGet(col string, val interface{}) (*Stra, error) {
var obj Stra
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func StraDel(id int64) error {
session := DB["mon"].NewSession()
defer session.Close()
var obj Stra
if err := session.Begin(); err != nil {
return err
}
exists, err := session.Id(id).Get(&obj)
if err != nil {
session.Rollback()
return err
}
if !exists {
session.Rollback()
return fmt.Errorf("%d not exists", obj.Id)
}
if _, err := session.Id(id).Delete(new(Stra)); err != nil {
session.Rollback()
return err
}
straByte, err := json.Marshal(obj)
if err != nil {
session.Rollback()
return err
}
err = SaveStraCommit(obj.Id, "delete", obj.Creator, string(straByte), session)
if err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func StrasList(name string, priority int, nid int64) ([]*Stra, error) {
session := DB["mon"].NewSession()
defer session.Close()
objs := make([]*Stra, 0)
whereClause := "1 = 1"
params := []interface{}{}
if name != "" {
whereClause += " AND name LIKE ?"
params = append(params, "%"+name+"%")
}
if priority <= 3 {
whereClause += " AND priority = ?"
params = append(params, priority)
}
var err error
if nid != 0 {
err = session.Where(whereClause, params...).Where("nid=?", nid).Find(&objs)
} else {
err = session.Where(whereClause, params...).Find(&objs)
}
if err != nil {
return objs, err
}
stras := make([]*Stra, 0)
for _, obj := range objs {
err = obj.Decode()
if err != nil {
return stras, err
}
stras = append(stras, obj)
}
return stras, err
}
func StrasAll() ([]*Stra, error) {
objs := make([]*Stra, 0)
err := DB["mon"].Find(&objs)
if err != nil {
return objs, err
}
stras := make([]*Stra, 0)
for _, obj := range objs {
err = obj.Decode()
if err != nil {
return stras, err
}
stras = append(stras, obj)
}
return stras, err
}
func EffectiveStrasList() ([]*Stra, error) {
session := DB["mon"].NewSession()
defer session.Close()
objs := make([]*Stra, 0)
t := time.Now()
now := t.Format("15:04")
weekday := strconv.Itoa(int(t.Weekday()))
err := session.Where("((enable_stime <= ? and enable_etime >= ? or (enable_stime > enable_etime and !(enable_stime > ? and enable_etime < ?))) and enable_days_of_week like ?)", now, now, now, now, "%"+weekday+"%").Find(&objs)
if err != nil {
return objs, err
}
stras := make([]*Stra, 0)
for _, obj := range objs {
err = obj.Decode()
if err != nil {
return stras, err
}
stras = append(stras, obj)
}
return stras, err
}
func SaveStraCommit(id int64, action, username, body string, session *xorm.Session) error {
strategyLog := StraLog{
Sid: id,
Action: action,
Body: body,
Creator: username,
}
if _, err := session.Insert(&strategyLog); err != nil {
session.Rollback()
return err
}
return nil
}
func (s *Stra) HasPermssion() error {
return nil
}
func (s *Stra) Encode() error {
alertUpgrade, err := AlertUpgradeMarshal(s.AlertUpgrade)
if err != nil {
return fmt.Errorf("encode alert_upgrade err:%v", err)
}
if s.NeedUpgrade == 1 {
if len(s.AlertUpgrade.Users) == 0 && len(s.AlertUpgrade.Groups) == 0 {
return fmt.Errorf("alert upgrade: users and groups is blank")
}
}
s.AlertUpgradeStr = alertUpgrade
exclNid, err := json.Marshal(s.ExclNid)
if err != nil {
return fmt.Errorf("encode excl_nid err:%v", err)
}
s.ExclNidStr = string(exclNid)
exprs, err := json.Marshal(s.Exprs)
if err != nil {
return fmt.Errorf("encode exprs err:%v", err)
}
s.ExprsStr = string(exprs)
//校验exprs
var exprsTmp []Exp
err = json.Unmarshal(exprs, &exprsTmp)
for _, exp := range exprsTmp {
if _, found := MathOperators[exp.Eopt]; !found {
return fmt.Errorf("unknown exp.eopt:%s", exp)
}
}
tags, err := json.Marshal(s.Tags)
if err != nil {
return fmt.Errorf("encode Tags err:%v", err)
}
s.TagsStr = string(tags)
//校验tags
var tagsTmp []Tag
err = json.Unmarshal(tags, &tagsTmp)
for _, tag := range tagsTmp {
if tag.Topt != "=" && tag.Topt != "!=" {
return fmt.Errorf("unknown tag.topt")
}
}
//校验时间
err = checkDurationString(s.EnableStime)
if err != nil {
return fmt.Errorf("unknown enable_stime: %s", s.EnableStime)
}
err = checkDurationString(s.EnableEtime)
if err != nil {
return fmt.Errorf("unknown enable_etime: %s", s.EnableEtime)
}
for _, day := range s.EnableDaysOfWeek {
if day > 7 || day < 0 {
return fmt.Errorf("illegal period_days_of_week %v", s.EnableDaysOfWeek)
}
}
enableDaysOfWeek, err := json.Marshal(s.EnableDaysOfWeek)
if err != nil {
return fmt.Errorf("encode EnableDaysOfWeek err:%v", err)
}
s.EnableDaysOfWeekStr = string(enableDaysOfWeek)
//校验收敛配置
if len(s.Converge) != 2 {
return fmt.Errorf("illegal converge %v", s.Converge)
}
Converge, err := json.Marshal(s.Converge)
if err != nil {
return err
}
s.ConvergeStr = string(Converge)
notifyGroup, err := json.Marshal(s.NotifyGroup)
if err != nil {
return err
}
s.NotifyGroupStr = string(notifyGroup)
notifyUser, err := json.Marshal(s.NotifyUser)
if err != nil {
return err
}
s.NotifyUserStr = string(notifyUser)
return nil
}
func (s *Stra) Decode() error {
var err error
s.AlertUpgrade, err = AlertUpgradeUnMarshal(s.AlertUpgradeStr)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.ExclNidStr), &s.ExclNid)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.ExprsStr), &s.Exprs)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.TagsStr), &s.Tags)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.EnableDaysOfWeekStr), &s.EnableDaysOfWeek)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.ConvergeStr), &s.Converge)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.NotifyUserStr), &s.NotifyUser)
if err != nil {
return err
}
err = json.Unmarshal([]byte(s.NotifyGroupStr), &s.NotifyGroup)
if err != nil {
return err
}
return nil
}
// 00:00-23:59
func checkDurationString(str string) error {
slice := strings.Split(str, ":")
if len(slice) != 2 {
return fmt.Errorf("illegal duration", str)
}
hour, err := strconv.Atoi(slice[0])
if err != nil {
return fmt.Errorf("illegal duration", str)
}
if hour < 0 || hour > 23 {
return fmt.Errorf("illegal duration", str)
}
minute, err := strconv.Atoi(slice[1])
if err != nil {
return fmt.Errorf("illegal duration", str)
}
if minute < 0 || minute > 59 {
return fmt.Errorf("illegal duration", str)
}
return nil
}
func AlertUpgradeMarshal(alterUpgrade AlertUpgrade) (string, error) {
dat := AlertUpgrade{
Duration: alterUpgrade.Duration,
Level: alterUpgrade.Level,
}
if alterUpgrade.Duration == 0 {
dat.Duration = 60
}
if alterUpgrade.Level == 0 {
dat.Level = 1
}
if alterUpgrade.Groups == nil {
dat.Groups = []int64{}
} else {
dat.Groups = alterUpgrade.Groups
}
if alterUpgrade.Users == nil {
dat.Users = []int64{}
} else {
dat.Users = alterUpgrade.Users
}
data, err := json.Marshal(dat)
return string(data), err
}
func AlertUpgradeUnMarshal(str string) (AlertUpgrade, error) {
var obj AlertUpgrade
if strings.TrimSpace(str) == "" {
return AlertUpgrade{
Users: []int64{},
Groups: []int64{},
Duration: 0,
Level: 0,
}, nil
}
err := json.Unmarshal([]byte(str), &obj)
return obj, err
}

334
src/model/team.go Normal file
View File

@ -0,0 +1,334 @@
package model
import (
"fmt"
"xorm.io/xorm"
jsoniter "github.com/json-iterator/go"
"github.com/toolkits/pkg/str"
)
type Team struct {
Id int64 `json:"id"`
Ident string `json:"ident"`
Name string `json:"name"`
Mgmt int `json:"mgmt"`
AdminObjs []User `json:"admin_objs" xorm:"-"`
MemberObjs []User `json:"member_objs" xorm:"-"`
}
func (t *Team) Del() error {
session := DB["uic"].NewSession()
defer session.Clone()
if err := session.Begin(); err != nil {
return err
}
if _, err := session.Exec("DELETE FROM team_user WHERE team_id=?", t.Id); err != nil {
session.Rollback()
return err
}
if _, err := session.Exec("DELETE FROM team WHERE id=?", t.Id); err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func (t *Team) CheckFields() error {
if len(t.Ident) > 255 {
return fmt.Errorf("ident too long")
}
if len(t.Name) > 255 {
return fmt.Errorf("name too long")
}
if t.Mgmt != 0 && t.Mgmt != 1 {
return fmt.Errorf("mgmt invalid")
}
if str.Dangerous(t.Ident) {
return fmt.Errorf("ident dangerous")
}
if str.Dangerous(t.Name) {
return fmt.Errorf("name dangerous")
}
if !str.IsMatch(t.Ident, `^[a-z0-9\-]+$`) {
return fmt.Errorf("ident permissible characters: [a-z0-9] and -")
}
return nil
}
func (t *Team) FillObjs() error {
var tus []TeamUser
err := DB["uic"].Where("team_id=?", t.Id).Find(&tus)
if err != nil {
return err
}
cnt := len(tus)
for i := 0; i < cnt; i++ {
user, err := UserGet("id", tus[i].UserId)
if err != nil {
return err
}
if user == nil {
continue
}
if tus[i].IsAdmin == 1 {
t.AdminObjs = append(t.AdminObjs, *user)
} else {
t.MemberObjs = append(t.MemberObjs, *user)
}
}
return nil
}
func safeUserIds(ids []int64) ([]int64, error) {
cnt := len(ids)
ret := make([]int64, 0, cnt)
for i := 0; i < cnt; i++ {
user, err := UserGet("id", ids[i])
if err != nil {
return nil, err
}
if user != nil {
ret = append(ret, ids[i])
}
}
return ret, nil
}
func (t *Team) Modify(ident, name string, mgmt int, admins, members []int64) error {
adminIds, err := safeUserIds(admins)
if err != nil {
return err
}
memberIds, err := safeUserIds(members)
if err != nil {
return err
}
if len(adminIds) == 0 && len(memberIds) == 0 {
return fmt.Errorf("no invalid memeber ids")
}
if mgmt == 1 && len(adminIds) == 0 {
return fmt.Errorf("arg[admins] is necessary")
}
// 如果ident有变化就要检查是否有重名
if ident != t.Ident {
cnt, err := DB["uic"].Where("ident = ? and id <> ?", ident, t.Id).Count(new(Team))
if err != nil {
return err
}
if cnt > 0 {
return fmt.Errorf("ident[%s] already exists", ident)
}
}
t.Ident = ident
t.Name = name
t.Mgmt = mgmt
if err = t.CheckFields(); err != nil {
return err
}
session := DB["uic"].NewSession()
defer session.Close()
if err = session.Begin(); err != nil {
return err
}
if _, err = session.Where("id=?", t.Id).Cols("ident", "name", "mgmt").Update(t); err != nil {
session.Rollback()
return err
}
if _, err = session.Exec("DELETE FROM team_user WHERE team_id=?", t.Id); err != nil {
session.Rollback()
return err
}
for i := 0; i < len(adminIds); i++ {
if err = teamUserBind(session, t.Id, adminIds[i], 1); err != nil {
session.Rollback()
return err
}
}
for i := 0; i < len(memberIds); i++ {
if err = teamUserBind(session, t.Id, memberIds[i], 0); err != nil {
session.Rollback()
return err
}
}
return session.Commit()
}
func TeamAdd(ident, name string, mgmt int, admins, members []int64) error {
adminIds, err := safeUserIds(admins)
if err != nil {
return err
}
memberIds, err := safeUserIds(members)
if err != nil {
return err
}
if len(adminIds) == 0 && len(memberIds) == 0 {
return fmt.Errorf("no invalid memeber ids")
}
if mgmt == 1 && len(adminIds) == 0 {
return fmt.Errorf("arg[admins] is necessary")
}
t := Team{
Ident: ident,
Name: name,
Mgmt: mgmt,
}
if err = t.CheckFields(); err != nil {
return err
}
session := DB["uic"].NewSession()
defer session.Close()
cnt, err := session.Where("ident=?", ident).Count(new(Team))
if err != nil {
return err
}
if cnt > 0 {
return fmt.Errorf("%s already exists", ident)
}
if err = session.Begin(); err != nil {
return err
}
if _, err = session.Insert(&t); err != nil {
session.Rollback()
return err
}
for i := 0; i < len(adminIds); i++ {
if err := teamUserBind(session, t.Id, adminIds[i], 1); err != nil {
session.Rollback()
return err
}
}
for i := 0; i < len(memberIds); i++ {
if err := teamUserBind(session, t.Id, memberIds[i], 0); err != nil {
session.Rollback()
return err
}
}
return session.Commit()
}
func teamUserBind(session *xorm.Session, teamid, userid int64, isadmin int) error {
var tu TeamUser
has, err := session.Where("team_id=? and user_id=?", teamid, userid).Get(&tu)
if err != nil {
return err
}
if has && isadmin != tu.IsAdmin {
_, err = session.Exec("UPDATE team_user SET is_admin=? WHERE team_id=? and user_id=?", isadmin, teamid, userid)
if err != nil {
return err
}
}
if !has {
_, err = session.Insert(&TeamUser{
TeamId: teamid,
UserId: userid,
IsAdmin: isadmin,
})
return err
}
return nil
}
func TeamGet(col string, val interface{}) (*Team, error) {
var obj Team
has, err := DB["uic"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func TeamTotal(query string) (int64, error) {
if query != "" {
q := "%" + query + "%"
return DB["uic"].Where("ident like ? or name like ?", q, q).Count(new(Team))
}
return DB["uic"].Count(new(Team))
}
func TeamGets(query string, limit, offset int) ([]Team, error) {
session := DB["uic"].Limit(limit, offset).OrderBy("ident")
if query != "" {
q := "%" + query + "%"
session = session.Where("ident like ? or name like ?", q, q)
}
var objs []Team
err := session.Find(&objs)
return objs, err
}
func TeamNameGetsByIds(ids string) ([]string, error) {
var objs []Team
var json = jsoniter.ConfigCompatibleWithStandardLibrary
var groupIds []int64
if err := json.Unmarshal([]byte(ids), &groupIds); err != nil {
return nil, err
}
err := DB["uic"].In("id", groupIds).Cols("name").Find(&objs)
if err != nil {
return nil, err
}
names := []string{}
for i := 0; i < len(objs); i++ {
names = append(names, objs[i].Name)
}
return names, nil
}

22
src/model/team_user.go Normal file
View File

@ -0,0 +1,22 @@
package model
type TeamUser struct {
TeamId int64 `json:"team_id" xorm:"'team_id'"`
UserId int64 `json:"user_id" xorm:"'user_id'"`
IsAdmin int `json:"is_admin" xorm:"'is_admin'"`
}
func UserIdGetByTeamIds(teamIds []int64) ([]int64, error) {
var objs []TeamUser
err := DB["uic"].In("team_id", teamIds).Find(&objs)
if err != nil {
return nil, err
}
userIds := []int64{}
for i := 0; i < len(objs); i++ {
userIds = append(userIds, objs[i].UserId)
}
return userIds, nil
}

26
src/model/tmp_chart.go Normal file
View File

@ -0,0 +1,26 @@
package model
type TmpChart struct {
Id int64 `json:"id"`
Configs string `json:"configs"`
Creator string `json:"creator"`
}
func (t *TmpChart) Add() error {
_, err := DB["mon"].InsertOne(t)
return err
}
func TmpChartGet(col string, val interface{}) (*TmpChart, error) {
var obj TmpChart
has, err := DB["mon"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}

296
src/model/user.go Normal file
View File

@ -0,0 +1,296 @@
package model
import (
"crypto/tls"
"fmt"
"log"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/toolkits/pkg/errors"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
"gopkg.in/ldap.v3"
"github.com/didi/nightingale/src/modules/monapi/config"
)
type User struct {
Id int64 `json:"id"`
Username string `json:"username"`
Password string `json:"-"`
Dispname string `json:"dispname"`
Phone string `json:"phone"`
Email string `json:"email"`
Im string `json:"im"`
IsRoot int `json:"is_root"`
}
func (u *User) CheckFields() {
u.Username = strings.TrimSpace(u.Username)
if u.Username == "" {
errors.Bomb("username is blank")
}
if str.Dangerous(u.Username) {
errors.Bomb("username is dangerous")
}
if str.Dangerous(u.Dispname) {
errors.Bomb("dispname is dangerous")
}
if u.Phone != "" && !str.IsPhone(u.Phone) {
errors.Bomb("%s format error", u.Phone)
}
if u.Email != "" && !str.IsMail(u.Email) {
errors.Bomb("%s format error", u.Email)
}
if len(u.Username) > 32 {
errors.Bomb("username too long")
}
if len(u.Dispname) > 32 {
errors.Bomb("dispname too long")
}
}
func (u *User) Update(cols ...string) error {
u.CheckFields()
_, err := DB["uic"].Where("id=?", u.Id).Cols(cols...).Update(u)
return err
}
func (u *User) Save() error {
u.CheckFields()
if u.Id > 0 {
return fmt.Errorf("user.id[%d] not equal 0", u.Id)
}
cnt, err := DB["uic"].Where("username=?", u.Username).Count(new(User))
if err != nil {
return err
}
if cnt > 0 {
return fmt.Errorf("username already exists")
}
_, err = DB["uic"].Insert(u)
return err
}
func (u *User) Del() error {
session := DB["uic"].NewSession()
defer session.Close()
if err := session.Begin(); err != nil {
return err
}
if _, err := session.Exec("DELETE FROM team_user WHERE user_id=?", u.Id); err != nil {
session.Rollback()
return err
}
if _, err := session.Exec("DELETE FROM user WHERE id=?", u.Id); err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func (u *User) CanModifyTeam(t *Team) (bool, error) {
if u.IsRoot == 1 {
return true, nil
}
session := DB["uic"].Where("team_id=? and user_id=?", t.Id, u.Id)
if t.Mgmt == 1 {
session = session.Where("is_admin=1")
}
cnt, err := session.Count(new(TeamUser))
return cnt > 0, err
}
func InitRoot() {
var u User
has, err := DB["uic"].Where("username=?", "root").Get(&u)
if err != nil {
log.Fatalln("cannot query user[root]", err)
}
if has {
return
}
// gen := str.RandLetters(32)
u = User{
Username: "root",
Password: config.CryptoPass("root"),
Dispname: "超管",
IsRoot: 1,
}
_, err = DB["uic"].Insert(&u)
if err != nil {
log.Fatalln("cannot insert user[root]")
}
logger.Info("user root init done")
}
func LdapLogin(user, pass string) error {
var conn *ldap.Conn
var err error
lc := config.Get().LDAP
addr := fmt.Sprintf("%s:%d", lc.Host, lc.Port)
if lc.TLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: true})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return fmt.Errorf("cannot dial ldap: %v", err)
}
defer conn.Close()
if !lc.TLS && lc.StartTLS {
err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
return fmt.Errorf("ldap.conn startTLS fail: %v", err)
}
}
err = conn.Bind(lc.BindUser, lc.BindPass)
if err != nil {
return fmt.Errorf("bind ldap fail: %v, use %s", err, lc.BindUser)
}
searchRequest := ldap.NewSearchRequest(
lc.BaseDn, // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(lc.AuthFilter, user), // The filter to apply
[]string{}, // A list attributes to retrieve
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return fmt.Errorf("ldap search fail: %v", err)
}
if len(sr.Entries) == 0 {
return fmt.Errorf("cannot find such user: %v", user)
}
if len(sr.Entries) > 1 {
return fmt.Errorf("multi users is search, query user: %v", user)
}
err = conn.Bind(sr.Entries[0].DN, pass)
if err != nil {
return fmt.Errorf("password error")
}
cnt, err := DB["uic"].Where("username=?", user).Count(new(User))
if err != nil {
return err
}
if cnt > 0 {
return nil
}
u := &User{
Username: user,
Password: "******",
Dispname: "",
Email: "",
}
_, err = DB["uic"].Insert(u)
return err
}
func PassLogin(user, pass string) error {
var u User
has, err := DB["uic"].Where("username=?", user).Cols("password").Get(&u)
if err != nil {
return err
}
if !has {
return fmt.Errorf("user[%s] not found", user)
}
if config.CryptoPass(pass) != u.Password {
return fmt.Errorf("password error")
}
return nil
}
func UserGet(col string, val interface{}) (*User, error) {
var obj User
has, err := DB["uic"].Where(col+"=?", val).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &obj, nil
}
func UserTotal(query string) (int64, error) {
if query != "" {
q := "%" + query + "%"
return DB["uic"].Where("username like ? or dispname like ? or phone like ? or email like ?", q, q, q, q).Count(new(User))
}
return DB["uic"].Count(new(User))
}
func UserGets(query string, limit, offset int) ([]User, error) {
session := DB["uic"].Limit(limit, offset).OrderBy("username")
if query != "" {
q := "%" + query + "%"
session = session.Where("username like ? or dispname like ? or phone like ? or email like ?", q, q, q, q)
}
var users []User
err := session.Find(&users)
return users, err
}
func UserNameGetByIds(ids string) ([]string, error) {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
var userIds []int64
if err := json.Unmarshal([]byte(ids), &userIds); err != nil {
return nil, err
}
var names []string
err := DB["uic"].Table("user").In("id", userIds).Select("username").Find(&names)
return names, err
}
func UserGetByIds(ids []int64) ([]User, error) {
var objs []User
err := DB["uic"].In("id", ids).Find(&objs)
return objs, err
}

View File

@ -0,0 +1,142 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/didi/nightingale/src/modules/collector/config"
"github.com/didi/nightingale/src/modules/collector/http/routes"
"github.com/didi/nightingale/src/modules/collector/log/worker"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/didi/nightingale/src/modules/collector/sys"
"github.com/didi/nightingale/src/modules/collector/sys/funcs"
"github.com/didi/nightingale/src/modules/collector/sys/plugins"
"github.com/didi/nightingale/src/modules/collector/sys/ports"
"github.com/didi/nightingale/src/modules/collector/sys/procs"
"github.com/didi/nightingale/src/toolkits/http"
"github.com/didi/nightingale/src/toolkits/identity"
tlogger "github.com/didi/nightingale/src/toolkits/logger"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
const version = 1
var (
vers *bool
help *bool
conf *string
)
func init() {
vers = flag.Bool("v", false, "display the version.")
help = flag.Bool("h", false, "print this help.")
conf = flag.String("f", "", "specify configuration file.")
flag.Parse()
if *vers {
fmt.Println("version:", version)
os.Exit(0)
}
if *help {
flag.Usage()
os.Exit(0)
}
}
func main() {
aconf()
pconf()
start()
cfg := config.Get()
tlogger.Init(cfg.Logger)
identity.Init(cfg.Identity)
if identity.Identity == "127.0.0.1" {
log.Fatalln("endpoint: 127.0.0.1, cannot work")
}
sys.Init(cfg.Sys)
stra.Init(cfg.Stra)
funcs.BuildMappers()
funcs.Collect()
stra.GetCollects()
//插件采集
plugins.Detect()
//进程采集
procs.Detect()
//端口采集
ports.Detect()
//日志采集
worker.Init(config.Config.Worker)
go worker.UpdateConfigsLoop()
go worker.PusherStart()
go worker.Zeroize()
r := gin.New()
routes.Config(r)
http.Start(r, "collector", cfg.Logger.Level)
ending()
}
// auto detect configuration file
func aconf() {
if *conf != "" && file.IsExist(*conf) {
return
}
*conf = "etc/collector.local.yml"
if file.IsExist(*conf) {
return
}
*conf = "etc/collector.yml"
if file.IsExist(*conf) {
return
}
fmt.Println("no configuration file for collector")
os.Exit(1)
}
// parse configuration file
func pconf() {
if err := config.Parse(*conf); err != nil {
fmt.Println("cannot parse configuration file:", err)
os.Exit(1)
}
}
func ending() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
select {
case <-c:
fmt.Printf("stop signal caught, stopping... pid=%d\n", os.Getpid())
}
logger.Close()
http.Shutdown()
fmt.Println("sender stopped successfully")
}
func start() {
runner.Init()
fmt.Println("collector start, use configuration file:", *conf)
fmt.Println("runner.Cwd:", runner.Cwd)
fmt.Println("runner.Endpoint:", runner.Hostname)
}

View File

@ -0,0 +1,83 @@
package config
import (
"bytes"
"fmt"
"sync"
"github.com/didi/nightingale/src/modules/collector/log/worker"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/didi/nightingale/src/modules/collector/sys"
"github.com/didi/nightingale/src/toolkits/identity"
"github.com/didi/nightingale/src/toolkits/logger"
"github.com/spf13/viper"
"github.com/toolkits/pkg/file"
)
type ConfYaml struct {
Identity identity.IdentitySection `yaml:"identity"`
Logger logger.LoggerSection `yaml:"logger"`
Stra stra.StraSection `yaml:"stra"`
Worker worker.WorkerSection `yaml:"worker"`
Sys sys.SysSection `yaml:"sys"`
}
var (
Config *ConfYaml
lock = new(sync.RWMutex)
Endpoint string
Cwd string
)
// Get configuration file
func Get() *ConfYaml {
lock.RLock()
defer lock.RUnlock()
return Config
}
func Parse(conf string) error {
bs, err := file.ReadBytes(conf)
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
lock.Lock()
defer lock.Unlock()
viper.SetConfigType("yaml")
err = viper.ReadConfig(bytes.NewBuffer(bs))
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
viper.SetDefault("worker", map[string]interface{}{
"workerNum": 10,
"queueSize": 1024000,
"pushInterval": 5,
"waitPush": 0,
})
viper.SetDefault("stra", map[string]interface{}{
"enable": true,
"timeout": 1000,
"interval": 10, //采集策略更新时间
"portPath": "/home/n9e/etc/port",
"procPath": "/home/n9e/etc/proc",
"logPath": "/home/n9e/etc/log",
"api": "/api/portal/collects/",
})
viper.SetDefault("sys", map[string]interface{}{
"timeout": 1000, //请求超时时间
"interval": 10, //基础指标上报周期
"plugin": "/home/n9e/plugin",
})
err = viper.Unmarshal(&Config)
if err != nil {
return fmt.Errorf("Unmarshal %v", err)
}
return nil
}

View File

@ -0,0 +1,3 @@
package config
const Version = 1

View File

@ -0,0 +1,92 @@
package routes
import (
"fmt"
"os"
"github.com/didi/nightingale/src/dataobj"
"github.com/didi/nightingale/src/modules/collector/log/strategy"
"github.com/didi/nightingale/src/modules/collector/log/worker"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/didi/nightingale/src/modules/collector/sys/funcs"
"github.com/didi/nightingale/src/toolkits/http/render"
"github.com/didi/nightingale/src/toolkits/identity"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errors"
"github.com/toolkits/pkg/logger"
)
func ping(c *gin.Context) {
c.String(200, "pong")
}
func addr(c *gin.Context) {
c.String(200, c.Request.RemoteAddr)
}
func pid(c *gin.Context) {
c.String(200, fmt.Sprintf("%d", os.Getpid()))
}
func pushData(c *gin.Context) {
if c.Request.ContentLength == 0 {
render.Message(c, "blank body")
return
}
recvMetricValues := []*dataobj.MetricValue{}
metricValues := []*dataobj.MetricValue{}
errors.Dangerous(c.ShouldBind(&recvMetricValues))
var msg string
for _, v := range recvMetricValues {
logger.Debug("->recv: ", v)
if v.Endpoint == "" {
v.Endpoint = identity.Identity
}
err := v.CheckValidity()
if err != nil {
msg += fmt.Sprintf("recv metric %v err:%v\n", v, err)
logger.Warningf(msg)
continue
}
metricValues = append(metricValues, v)
}
funcs.Push(metricValues)
if msg != "" {
render.Message(c, msg)
return
}
render.Data(c, "ok", nil)
return
}
func getStrategy(c *gin.Context) {
var resp []interface{}
port := stra.GetPortCollects()
for _, stra := range port {
resp = append(resp, stra)
}
proc := stra.GetProcCollects()
for _, stra := range proc {
resp = append(resp, stra)
}
logStras := strategy.GetListAll()
for _, stra := range logStras {
resp = append(resp, stra)
}
render.Data(c, resp, nil)
}
func getLogCached(c *gin.Context) {
render.Data(c, worker.GetCachedAll(), nil)
}

View File

@ -0,0 +1,22 @@
package routes
import (
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
)
// Config routes
func Config(r *gin.Engine) {
sys := r.Group("/api/collector")
{
sys.GET("/ping", ping)
sys.GET("/pid", pid)
sys.GET("/addr", addr)
sys.GET("/stra", getStrategy)
sys.GET("/cached", getLogCached)
sys.POST("/push", pushData)
}
pprof.Register(r, "/api/collector/debug/pprof")
}

View File

@ -0,0 +1,151 @@
package reader
import (
"os"
"time"
"github.com/toolkits/pkg/logger"
"github.com/hpcloud/tail"
)
type Reader struct {
FilePath string //配置的路径 正则路径
t *tail.Tail
Stream chan string
CurrentPath string //当前的路径
Close chan struct{}
FD uint64 // 文件的inode
}
func NewReader(filepath string, stream chan string) (*Reader, error) {
r := &Reader{
FilePath: filepath,
Stream: stream,
Close: make(chan struct{}),
}
path := GetCurrentPath(filepath)
err := r.openFile(os.SEEK_END, path) //默认打开seek_end
return r, err
}
func (r *Reader) openFile(whence int, filepath string) error {
seekinfo := &tail.SeekInfo{
Offset: 0,
Whence: whence,
}
config := tail.Config{
Location: seekinfo,
ReOpen: true,
Poll: true,
Follow: true,
}
t, err := tail.TailFile(filepath, config)
if err != nil {
return err
}
r.t = t
r.CurrentPath = filepath
r.FD = GetFileInodeNum(r.CurrentPath)
return nil
}
func (r *Reader) StartRead() {
var readCnt, readSwp int64
var dropCnt, dropSwp int64
analysClose := make(chan int, 0)
go func() {
for {
// 十秒钟统计一次
select {
case <-analysClose:
return
case <-time.After(time.Second * 10):
}
a := readCnt
b := dropCnt
logger.Debugf("read [%d] line in last 10s\n", a-readSwp)
logger.Debugf("drop [%d] line in last 10s\n", b-dropSwp)
readSwp = a
dropSwp = b
}
}()
for line := range r.t.Lines {
readCnt = readCnt + 1
select {
case r.Stream <- line.Text:
default:
dropCnt = dropCnt + 1
//TODO 数据丢失处理从现时间戳开始截断上报5周期
// 是否真的要做?
// 首先5 周期也是拍脑袋的,只能拍脑袋丢数据,并不能保证准确性
// 其次,是当前时间推五周期,并不知道日志是什么时候,这个地方有待斟酌
// 结论,暂且不做,后人注意
}
}
analysClose <- 0
}
func (r *Reader) StopRead() error {
return r.t.Stop()
}
func (r *Reader) Stop() {
r.StopRead()
close(r.Close)
}
func (r *Reader) Start() {
go r.StartRead()
for {
select {
case <-time.After(time.Second):
r.check()
case <-r.Close:
close(r.Stream)
return
}
}
}
func (r *Reader) check() {
nextpath := GetNowPath(r.FilePath)
// 文件名发生变化, 一般发生在配置了动态日志场景
if r.CurrentPath != nextpath {
if _, err := os.Stat(nextpath); err != nil {
logger.Warningf("stat nextpath err: %v\n", err.Error())
return
}
r.t.StopAtEOF()
if err := r.openFile(os.SEEK_SET, nextpath); err == nil { //从文件开始打开
go r.StartRead()
} else {
logger.Warningf("openFile err @check, err: %v\n", err.Error())
}
// 执行到这里, 动态日志已经reopen, 无需再进行下面同名文件的inode变化check
return
}
// 同名文件inode变化check
// 重新打开文件, 从头开始读取
// TODO hpcloud/tail 应该感知到这种场景
newFD := GetFileInodeNum(r.CurrentPath)
if r.FD == newFD {
return
}
r.FD = newFD
logger.Warningf("inode changed, reopen file %v\n", r.CurrentPath)
r.t.StopAtEOF()
if err := r.openFile(os.SEEK_SET, nextpath); err == nil { //从文件开始打开
go r.StartRead()
} else {
logger.Warningf("openFile err @check, err: %v\n", err.Error())
}
}

View File

@ -0,0 +1,56 @@
package reader
import (
"fmt"
"log"
"os"
"testing"
"time"
)
//当新文件出现时,是否自动读取
func TestCheck(t *testing.T) {
util(true)
}
//测试文件打开关闭
func TestStartAndStop(t *testing.T) {
util(false)
}
func util(isnext bool) {
stream := make(chan string, 100)
rj, err := NewReader("/test/aby.${%Y-%m-%d-%H}", stream)
if err != nil {
return
}
go rj.Start()
go func() {
time.Sleep(2 * time.Second) //2秒后创建文件
now := time.Now()
if isnext {
now = now.Add(time.Hour)
}
suffix := now.Format("2006-01-02-15")
filepath := fmt.Sprintf("/test/aby.%s", suffix)
{
f, err := os.Create(filepath)
if err != nil {
log.Fatal(err)
}
time.Sleep(time.Millisecond * 250) //因为tail 的巡检周期是250ms
defer f.Close()
fmt.Fprint(f, "this is a test\n")
}
time.Sleep(250 * time.Millisecond) //延迟关闭
rj.Stop()
}()
for line := range stream {
fmt.Println(line)
}
}

View File

@ -0,0 +1,82 @@
package reader
import (
"fmt"
"os"
"regexp"
"strings"
"syscall"
"time"
)
func GetNowPath(path string) string {
return getLogPath(path, true)
}
func GetCurrentPath(path string) string {
return getLogPath(path, false)
}
func getLogPath(path string, isnext bool) string {
pat := `(\$\{(%[YmdH][^\/]*)+\})`
reg := regexp.MustCompile(pat)
return reg.ReplaceAllStringFunc(path, func(s string) string {
stringv := strings.TrimFunc(s, func(r rune) bool {
if r == '$' || r == '{' || r == '}' {
return true
}
return false
})
name := strings.Split(strings.TrimLeft(stringv, "%"), "%")
now := time.Now()
if isnext {
switch name[len(name)-1] {
case "Y", "m", "d":
if now.Hour() == 23 {
now = time.Now() //.Add(time.Hour)
}
case "H":
now = time.Now() //.Add(time.Hour)
}
}
for k, v := range name {
if strings.Contains(v, "Y") {
if strings.HasPrefix(v, "Y") {
year := fmt.Sprintf("%d", now.Year())
name[k] = strings.Replace(v, "Y", year, 1)
}
} else if strings.Contains(v, "m") {
if strings.HasPrefix(v, "m") {
month := fmt.Sprintf("%02d", now.Month())
name[k] = strings.Replace(v, "m", month, 1)
}
} else if strings.Contains(v, "d") {
if strings.HasPrefix(v, "d") {
day := fmt.Sprintf("%02d", now.Day())
name[k] = strings.Replace(v, "d", day, 1)
}
} else if strings.Contains(v, "H") {
if strings.HasPrefix(v, "H") {
hour := fmt.Sprintf("%02d", now.Hour())
name[k] = strings.Replace(v, "H", hour, 1)
}
}
}
return strings.Join(name, "")
})
}
func GetFileInodeNum(path string) uint64 {
s, err := os.Stat(path)
if err != nil {
return 0
}
stat, ok := s.Sys().(*syscall.Stat_t)
if !ok {
return 0
}
return stat.Ino
}

View File

@ -0,0 +1,30 @@
package reader
import (
"fmt"
"testing"
"time"
)
func TestGetcurrentpath(t *testing.T) {
path := "/home/${%Y%m}/log/${%Y%m%d}/application.log.${%Y-%m-%d-%H}"
now := time.Now()
dir1 := now.Format("200601")
dir2 := now.Format("20060102")
suffix := now.Format("2006-01-02-15")
shouldbe := fmt.Sprintf("/home/%s/log/%s/application.log.%s", dir1, dir2, suffix)
if GetCurrentPath(path) != shouldbe {
t.Error("getcurrentpath failed")
}
}
func TestGetnextpath(t *testing.T) {
path := "/home/${%Y%m}/log/${%Y%m%d}/application.log.${%Y-%m-%d-%H}"
now := time.Now().Add(time.Hour)
dir1 := now.Format("200601")
dir2 := now.Format("20060102")
suffix := now.Format("2006-01-02-15")
shouldbe := fmt.Sprintf("/home/%s/log/%s/application.log.%s", dir1, dir2, suffix)
if GetNextPath(path) != shouldbe {
t.Error("getcurrentpath failed")
}
}

View File

@ -0,0 +1,94 @@
package strategy
import (
"fmt"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/toolkits/pkg/logger"
)
// 后续开发者切记 : 没有锁不能修改globalStrategy更新的时候直接替换否则会panic
var (
globalStrategy map[int64]*stra.Strategy
)
func init() {
globalStrategy = make(map[int64]*stra.Strategy, 0)
}
func Update() error {
strategys := stra.GetLogCollects()
err := UpdateGlobalStrategy(strategys)
if err != nil {
logger.Errorf("Update Strategy cache error ! [msg:%v]", err)
return err
}
logger.Infof("Update Strategy end")
return nil
}
func UpdateGlobalStrategy(sts []*stra.Strategy) error {
tmpStrategyMap := make(map[int64]*stra.Strategy, 0)
for _, st := range sts {
if st.Degree == 0 {
st.Degree = 6
}
tmpStrategyMap[st.ID] = st
}
globalStrategy = tmpStrategyMap
return nil
}
func GetListAll() []*stra.Strategy {
stmap := GetDeepCopyAll()
var ret []*stra.Strategy
for _, st := range stmap {
ret = append(ret, st)
}
return ret
}
func GetDeepCopyAll() map[int64]*stra.Strategy {
ret := make(map[int64]*stra.Strategy, len(globalStrategy))
for k, v := range globalStrategy {
ret[k] = DeepCopyStrategy(v)
}
return ret
}
func GetAll() map[int64]*stra.Strategy {
return globalStrategy
}
func GetByID(id int64) (*stra.Strategy, error) {
st, ok := globalStrategy[id]
if !ok {
return nil, fmt.Errorf("ID : %d is not exists in global Cache")
}
return st, nil
}
func DeepCopyStrategy(p *stra.Strategy) *stra.Strategy {
s := stra.Strategy{}
s.ID = p.ID
s.Name = p.Name
s.FilePath = p.FilePath
s.TimeFormat = p.TimeFormat
s.Pattern = p.Pattern
s.MeasurementType = p.MeasurementType
s.Interval = p.Interval
s.Tags = stra.DeepCopyStringMap(p.Tags)
s.Func = p.Func
s.Degree = p.Degree
s.Unit = p.Unit
s.Comment = p.Comment
s.Creator = p.Creator
s.SrvUpdated = p.SrvUpdated
s.LocalUpdated = p.LocalUpdated
return &s
}

View File

@ -0,0 +1,35 @@
package strategy
import (
"common/scheme"
"fmt"
"testing"
)
func TestGetMyStrategy(t *testing.T) {
fmt.Println("Now Test GetLocalStrategy:")
data, err := getMyStrategy()
if err == nil {
fmt.Println("Result:")
for _, x := range data {
fmt.Printf(" %v\n", x)
}
} else {
fmt.Println("Something Error:")
fmt.Println(err)
}
}
func TestPatternParse(t *testing.T) {
fmt.Println("Now Test PatternParse:")
var a scheme.Strategy
a.Pattern = "test"
parsePattern([]*scheme.Strategy{&a})
fmt.Printf("a.pat:[%s], a.ex[%s]\n", a.Pattern, a.Exclude)
a.Pattern = "```EXCLUDE```test"
parsePattern([]*scheme.Strategy{&a})
fmt.Printf("a.pat:[%s], a.ex[%s]\n", a.Pattern, a.Exclude)
a.Pattern = "test```EXCLUDE```"
parsePattern([]*scheme.Strategy{&a})
fmt.Printf("a.pat:[%s], a.ex[%s]\n", a.Pattern, a.Exclude)
}

View File

@ -0,0 +1,154 @@
package worker
import (
"encoding/json"
"fmt"
"strconv"
"sync"
"time"
"github.com/toolkits/pkg/logger"
"github.com/didi/nightingale/src/dataobj"
)
// cached时间周期
const CACHED_DURATION = 60
type counterCache struct {
sync.RWMutex
Points map[string]float64 `json:"points"`
}
type pushPointsCache struct {
sync.RWMutex
Counters map[string]*counterCache `json:"counters"`
}
var globalPushPoints = pushPointsCache{Counters: make(map[string]*counterCache, 0)}
func init() {
go CleanLoop()
}
func (this *counterCache) AddPoint(tms int64, value float64) {
this.Lock()
tmsStr := fmt.Sprintf("%d", tms)
this.Points[tmsStr] = value
this.Unlock()
}
func PostToCache(paramPoints []*dataobj.MetricValue) {
for _, point := range paramPoints {
globalPushPoints.AddPoint(point)
}
}
func CleanLoop() {
for {
// 遍历删掉过期的cache
globalPushPoints.CleanOld()
time.Sleep(5 * time.Second)
}
}
func GetCachedAll() string {
globalPushPoints.Lock()
str, err := json.Marshal(globalPushPoints)
globalPushPoints.Unlock()
if err != nil {
logger.Errorf("err when get cached all : [%s]", err.Error())
}
return string(str)
}
func (this *counterCache) GetKeys() []string {
this.RLock()
retList := make([]string, 0)
for k, _ := range this.Points {
retList = append(retList, k)
}
this.RUnlock()
return retList
}
func (this *counterCache) RemoveTms(tms string) {
this.Lock()
delete(this.Points, tms)
this.Unlock()
}
func (this *pushPointsCache) AddCounter(counter string) {
this.Lock()
tmp := new(counterCache)
tmp.Points = make(map[string]float64, 0)
this.Counters[counter] = tmp
this.Unlock()
}
func (this *pushPointsCache) GetCounters() []string {
ret := make([]string, 0)
this.RLock()
for k, _ := range this.Counters {
ret = append(ret, k)
}
this.RUnlock()
return ret
}
func (this *pushPointsCache) RemoveCounter(counter string) {
this.Lock()
delete(this.Counters, counter)
this.Unlock()
}
func (this *pushPointsCache) GetCounterObj(key string) (*counterCache, bool) {
this.RLock()
Points, ok := this.Counters[key]
this.RUnlock()
return Points, ok
}
func (this *pushPointsCache) AddPoint(point *dataobj.MetricValue) {
counter := calcCounter(point)
if _, ok := this.GetCounterObj(counter); !ok {
this.AddCounter(counter)
}
counterPoints, exists := this.GetCounterObj(counter)
if exists {
counterPoints.AddPoint(point.Timestamp, point.Value)
}
}
func (this *pushPointsCache) CleanOld() {
counters := this.GetCounters()
for _, counter := range counters {
counterObj, exists := this.GetCounterObj(counter)
if !exists {
continue
}
tmsList := counterObj.GetKeys()
//如果列表为空清理掉这个counter
if len(tmsList) == 0 {
this.RemoveCounter(counter)
} else {
for _, tmsStr := range tmsList {
tms, err := strconv.Atoi(tmsStr)
if err != nil {
logger.Errorf("clean cached point, atoi error : [%v]", err)
counterObj.RemoveTms(tmsStr)
} else if (time.Now().Unix() - int64(tms)) > CACHED_DURATION {
counterObj.RemoveTms(tmsStr)
}
}
}
}
}
func calcCounter(point *dataobj.MetricValue) string {
tagstring := dataobj.SortedTags(point.TagsMap)
counter := fmt.Sprintf("%s/%s", point.Metric, tagstring)
return counter
}

View File

@ -0,0 +1,14 @@
package worker
type WorkerSection struct {
WorkerNum int `yaml:"workerNum"`
QueueSize int `yaml:"queueSize"`
PushInterval int `yaml:"pushInterval"`
WaitPush int `yaml:"waitPush"`
}
var WorkerConfig WorkerSection
func Init(cfg WorkerSection) {
WorkerConfig = cfg
}

View File

@ -0,0 +1,159 @@
package worker
import (
"sync"
"time"
"github.com/didi/nightingale/src/modules/collector/log/reader"
"github.com/didi/nightingale/src/modules/collector/log/strategy"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/toolkits/pkg/logger"
)
type ConfigInfo struct {
Id int64
FilePath string
}
type Job struct {
r *reader.Reader
w *WorkerGroup
}
var ManagerJob map[string]*Job //管理job,文件路径为key
var ManagerJobLock *sync.RWMutex
var ManagerConfig map[int64]*ConfigInfo
func init() {
ManagerJob = make(map[string]*Job)
ManagerJobLock = new(sync.RWMutex)
ManagerConfig = make(map[int64]*ConfigInfo)
}
func UpdateConfigsLoop() {
for {
strategy.Update()
strategyMap := strategy.GetAll() //最新策略
ManagerJobLock.Lock()
//metric.MetricJobNum(len(ManagerJob))
for id, st := range strategyMap {
cfg := &ConfigInfo{
Id: id,
FilePath: st.FilePath,
}
cache := make(chan string, WorkerConfig.QueueSize)
if err := createJob(cfg, cache, st); err != nil {
logger.Errorf("create job fail [id:%d][filePath:%s][err:%v]", cfg.Id, cfg.FilePath, err)
}
}
for id, _ := range ManagerConfig {
if _, ok := strategyMap[id]; !ok { //如果策略中不存在,说明用户已删除
cfg := &ConfigInfo{
Id: id,
FilePath: ManagerConfig[id].FilePath,
}
deleteJob(cfg)
}
}
ManagerJobLock.Unlock()
//更新counter
GlobalCount.UpdateByStrategy(strategyMap)
time.Sleep(time.Second * 10)
}
}
func GetLatestTmsAndDelay(filepath string) (int64, int64, bool) {
ManagerJobLock.RLock()
job, ok := ManagerJob[filepath]
ManagerJobLock.RUnlock()
if !ok {
return 0, 0, false
}
latest, delay := job.w.GetLatestTmsAndDelay()
return latest, delay, true
}
//添加任务到管理map( managerjob managerconfig) 启动reader和worker
func createJob(config *ConfigInfo, cache chan string, st *stra.Strategy) error {
if _, ok := ManagerJob[config.FilePath]; ok {
if _, ok := ManagerConfig[config.Id]; !ok {
ManagerConfig[config.Id] = config
}
//依赖策略的周期更新, 触发文件乱序时间戳的重置
ManagerJob[config.FilePath].w.ResetMaxDelay()
return nil
}
ManagerConfig[config.Id] = config
//启动reader
r, err := reader.NewReader(config.FilePath, cache)
if err != nil {
return err
}
//metric.MetricReadAddReaderNum(config.FilePath)
//启动worker
w := NewWorkerGroup(config.FilePath, cache, st)
ManagerJob[config.FilePath] = &Job{
r: r,
w: w,
}
w.Start()
//启动reader
go r.Start()
logger.Infof("Create job success [filePath:%s][sid:%d]", config.FilePath, config.Id)
return nil
}
//先stop worker reader再从管理map中删除
func deleteJob(config *ConfigInfo) {
//删除jobs
tag := 0
for _, cg := range ManagerConfig {
if config.FilePath == cg.FilePath {
tag++
}
}
if tag <= 1 {
//metric.MetricReadDelReaderNum(config.FilePath)
if job, ok := ManagerJob[config.FilePath]; ok {
job.w.Stop() //先stop worker
job.r.Stop()
delete(ManagerJob, config.FilePath)
}
}
logger.Infof("Stop reader & worker success [filePath:%s][sid:%d]", config.FilePath, config.Id)
//删除config
if _, ok := ManagerConfig[config.Id]; ok {
delete(ManagerConfig, config.Id)
}
}
func Zeroize() {
t1 := time.NewTicker(time.Duration(10) * time.Second)
for {
<-t1.C
stras := strategy.GetListAll()
for _, stra := range stras {
if stra.Func == "cnt" && len(stra.Tags) < 1 {
point := &AnalysPoint{
StrategyID: stra.ID,
Value: -1,
Tms: time.Now().Unix(),
Tags: map[string]string{},
}
if err := PushToCount(point); err != nil {
logger.Errorf("push to counter error: %v", err)
}
}
}
}
}

View File

@ -0,0 +1,27 @@
package worker
import (
"fmt"
"testing"
"time"
)
func TestCreatejobAndDeletejob(t *testing.T) {
config := &ConfigInfo{
Id: 1,
FilePath: "a.log.${%Y-%m-%d-%H}",
}
cache := make(chan string, 100)
go func() {
time.Sleep(2 * time.Second)
deleteJob(config)
}()
if err := createJob(config, cache); err == nil {
for line := range cache {
fmt.Println(line)
}
} else {
fmt.Println("create job failed : %v", err)
}
}

View File

@ -0,0 +1,365 @@
package worker
import (
"fmt"
"math"
"reflect"
"sync"
"sync/atomic"
"unsafe"
"github.com/didi/nightingale/src/dataobj"
"github.com/didi/nightingale/src/modules/collector/log/strategy"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/toolkits/pkg/logger"
)
//从worker往计算部分推的Point
type AnalysPoint struct {
StrategyID int64
Value float64
Tms int64
Tags map[string]string
}
//统计的实体
type PointCounter struct {
sync.RWMutex
Count int64
Sum float64
Max float64
Min float64
}
// 单策略下单step的统计对象
// 以Sorted的tagkv的字符串来做索引
type PointsCounter struct {
sync.RWMutex
TagstringMap map[string]*PointCounter
}
// 单策略下的对象, 以step为索引, 索引每一个Step的统计
// 单step统计, 推送完则删
type StrategyCounter struct {
sync.RWMutex
Strategy *stra.Strategy //Strategy结构体扔这里以备不时之需
TmsPoints map[int64]*PointsCounter //按照时间戳分类的分别的counter
}
// 全局counter对象, 以key为索引索引每个策略的统计
// key : Strategy ID
type GlobalCounter struct {
sync.RWMutex
StrategyCounts map[int64]*StrategyCounter
}
var GlobalCount *GlobalCounter
func init() {
GlobalCount = new(GlobalCounter)
GlobalCount.StrategyCounts = make(map[int64]*StrategyCounter)
}
// 提供给Worker用来Push计算后的信息
// 需保证线程安全
func PushToCount(Point *AnalysPoint) error {
stCount, err := GlobalCount.GetStrategyCountByID(Point.StrategyID)
// 更新strategyCounts
if err != nil {
strategy, err := strategy.GetByID(Point.StrategyID)
if err != nil {
logger.Errorf("GetByID ERROR when count:[%v]", err)
return err
}
GlobalCount.AddStrategyCount(strategy)
stCount, err = GlobalCount.GetStrategyCountByID(Point.StrategyID)
// 还拿不到,就出错返回吧
if err != nil {
logger.Errorf("Get strategyCount Failed after addition: %v", err)
return err
}
}
// 拿到stCount更新StepCounts
stepTms := AlignStepTms(stCount.Strategy.Interval, Point.Tms)
tmsCount, err := stCount.GetByTms(stepTms)
if err != nil {
err := stCount.AddTms(stepTms)
if err != nil {
logger.Errorf("Add tms to strategy error: %v", err)
return err
}
tmsCount, err = stCount.GetByTms(stepTms)
// 还拿不到,就出错返回吧
if err != nil {
logger.Errorf("Get tmsCount Failed By Twice Add: %v", err)
return err
}
}
//拿到tmsCount, 更新TagstringMap
tagstring := dataobj.SortedTags(Point.Tags)
return tmsCount.Update(tagstring, Point.Value)
}
//时间戳向前对齐
func AlignStepTms(step, tms int64) int64 {
if step <= 0 {
return tms
}
newTms := tms - (tms % step)
return newTms
}
func (this *PointsCounter) GetBytagstring(tagstring string) (*PointCounter, error) {
this.RLock()
point, ok := this.TagstringMap[tagstring]
this.RUnlock()
if !ok {
return nil, fmt.Errorf("tagstring [%s] not exists!", tagstring)
}
return point, nil
}
func (this *PointCounter) UpdateCnt() {
atomic.AddInt64(&this.Count, 1)
}
func (this *PointCounter) UpdateSum(value float64) {
addFloat64(&this.Sum, value)
}
func (this *PointCounter) UpdateMaxMin(value float64) {
// 这里要用到结构体的小锁
// sum和cnt可以不用锁但是最大最小没办法做到原子操作
// 只能引入锁
this.RLock()
if math.IsNaN(this.Max) || value > this.Max {
this.RUnlock()
this.Lock()
if math.IsNaN(this.Max) || value > this.Max {
this.Max = value
}
this.Unlock()
} else {
this.RUnlock()
}
this.RLock()
if math.IsNaN(this.Min) || value < this.Min {
this.RUnlock()
this.Lock()
if math.IsNaN(this.Min) || value < this.Min {
this.Min = value
}
this.Unlock()
} else {
this.RUnlock()
}
}
func (this *PointsCounter) Update(tagstring string, value float64) error {
pointCount, err := this.GetBytagstring(tagstring)
if err != nil {
this.Lock()
tmp := new(PointCounter)
tmp.Count = 0
tmp.Sum = 0
if value == -1 {
tmp.Sum = math.NaN() //补零逻辑不处理Sum
}
tmp.Max = math.NaN()
tmp.Min = math.NaN()
this.TagstringMap[tagstring] = tmp
this.Unlock()
pointCount, err = this.GetBytagstring(tagstring)
// 如果还是拿不到,就出错返回吧
if err != nil {
return fmt.Errorf("when update, cannot get pointCount after add [tagstring:%s]", tagstring)
}
}
pointCount.Lock()
if value != -1 { //value=-1,是补零逻辑,不做特殊处理
pointCount.Sum = pointCount.Sum + value
if math.IsNaN(pointCount.Max) || value > pointCount.Max {
pointCount.Max = value
}
if math.IsNaN(pointCount.Min) || value < pointCount.Min {
pointCount.Min = value
}
pointCount.Count = pointCount.Count + 1
}
pointCount.Unlock()
return nil
}
func addFloat64(val *float64, delta float64) (new float64) {
for {
old := *val
new = old + delta
if atomic.CompareAndSwapUint64(
(*uint64)(unsafe.Pointer(val)),
math.Float64bits(old),
math.Float64bits(new),
) {
break
}
}
return
}
func (this *StrategyCounter) GetTmsList() []int64 {
tmsList := []int64{}
this.RLock()
for tms, _ := range this.TmsPoints {
tmsList = append(tmsList, tms)
}
this.RUnlock()
return tmsList
}
func (this *StrategyCounter) DeleteTms(tms int64) {
this.Lock()
delete(this.TmsPoints, tms)
this.Unlock()
}
func (this *StrategyCounter) GetByTms(tms int64) (*PointsCounter, error) {
this.RLock()
psCount, ok := this.TmsPoints[tms]
if !ok {
this.RUnlock()
return nil, fmt.Errorf("no this tms:%v", tms)
}
this.RUnlock()
return psCount, nil
}
func (this *StrategyCounter) AddTms(tms int64) error {
this.Lock()
_, ok := this.TmsPoints[tms]
if !ok {
tmp := new(PointsCounter)
tmp.TagstringMap = make(map[string]*PointCounter, 0)
this.TmsPoints[tms] = tmp
}
this.Unlock()
return nil
}
// 只做更新和删除,添加 由数据驱动
func (this *GlobalCounter) UpdateByStrategy(globalStras map[int64]*stra.Strategy) {
var delCount, upCount int
// 先以count的ID为准更新count
// 若ID没有了, 那就删掉
for _, id := range this.GetIDs() {
this.RLock()
sCount, ok := this.StrategyCounts[id]
this.RUnlock()
if !ok || sCount.Strategy == nil {
//证明此策略无效,或已被删除
//删一下
delCount = delCount + 1
this.deleteByID(id)
continue
}
newStrategy := globalStras[id]
// 一个是sCount.Strategy, 一个是newStrategy
// 开始比较
if !countEqual(newStrategy, sCount.Strategy) {
//需要清空缓存
upCount = upCount + 1
logger.Infof("strategy [%d] changed, clean data", id)
this.cleanStrategyData(id)
sCount.Strategy = newStrategy
} else {
this.upStrategy(newStrategy)
}
}
logger.Infof("Update global count done, [del:%d][update:%d]", delCount, upCount)
}
func (this *GlobalCounter) AddStrategyCount(st *stra.Strategy) {
this.Lock()
if _, ok := this.StrategyCounts[st.ID]; !ok {
tmp := new(StrategyCounter)
tmp.Strategy = st
tmp.TmsPoints = make(map[int64]*PointsCounter, 0)
this.StrategyCounts[st.ID] = tmp
}
this.Unlock()
}
func (this *GlobalCounter) upStrategy(st *stra.Strategy) {
this.Lock()
if _, ok := this.StrategyCounts[st.ID]; ok {
this.StrategyCounts[st.ID].Strategy = st
}
this.Unlock()
}
func (this *GlobalCounter) GetStrategyCountByID(id int64) (*StrategyCounter, error) {
this.RLock()
stCount, ok := this.StrategyCounts[id]
if !ok {
this.RUnlock()
return nil, fmt.Errorf("No this ID")
}
this.RUnlock()
return stCount, nil
}
func (this *GlobalCounter) GetIDs() []int64 {
this.RLock()
rList := make([]int64, 0)
for k, _ := range this.StrategyCounts {
rList = append(rList, k)
}
this.RUnlock()
return rList
}
func (this *GlobalCounter) deleteByID(id int64) {
this.Lock()
delete(this.StrategyCounts, id)
this.Unlock()
}
func (this *GlobalCounter) cleanStrategyData(id int64) {
this.RLock()
sCount, ok := this.StrategyCounts[id]
this.RUnlock()
if !ok || sCount == nil {
return
}
sCount.TmsPoints = make(map[int64]*PointsCounter, 0)
return
}
// countEqual意味着不会对统计的结构产生影响
func countEqual(A *stra.Strategy, B *stra.Strategy) bool {
if A == nil || B == nil {
return false
}
if A.Pattern == B.Pattern && A.Interval == B.Interval && A.Func == B.Func && reflect.DeepEqual(A.Tags, B.Tags) {
return true
}
return false
}

View File

@ -0,0 +1,234 @@
package worker
import (
"fmt"
"math"
"sort"
"time"
"github.com/didi/nightingale/src/dataobj"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/didi/nightingale/src/modules/collector/sys/funcs"
"github.com/didi/nightingale/src/toolkits/identity"
"github.com/toolkits/pkg/logger"
)
var pushQueue chan *dataobj.MetricValue
type SortByTms []*dataobj.MetricValue
func (p SortByTms) Len() int { return len(p) }
func (p SortByTms) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p SortByTms) Less(i, j int) bool { return p[i].Timestamp < p[j].Timestamp }
func init() {
//拍一个队列大小,10s一清理论上肯定够用
pushQueue = make(chan *dataobj.MetricValue, 1024*100)
}
func PusherStart() {
PosterLoop() //归类批量发送给collector
PusherLoop() //计算,推送给发送队列
}
//循环推送10s一次
func PosterLoop() {
go func() {
for {
select {
case p := <-pushQueue:
points := make([]*dataobj.MetricValue, 0)
points = append(points, p)
DONE:
for {
select {
case tmp := <-pushQueue:
points = append(points, tmp)
continue
default:
break DONE
}
}
//先推到cache中
PostToCache(points)
//开一个协程异步发送至collector
go postToCollector(points)
}
time.Sleep(10 * time.Second)
}
}()
}
func PusherLoop() {
for {
gIds := GlobalCount.GetIDs()
for _, id := range gIds {
stCount, err := GlobalCount.GetStrategyCountByID(id)
if err != nil {
logger.Errorf("get strategy count by id %v error: %v\n", id, err)
continue
}
if stCount.Strategy == nil {
logger.Errorf("strategy id %v is nil\n", id)
continue
}
step := stCount.Strategy.Interval
filePath := stCount.Strategy.FilePath
tmsList := stCount.GetTmsList()
for _, tms := range tmsList {
if tmsNeedPush(tms, filePath, step, WorkerConfig.WaitPush) {
pointsCount, err := stCount.GetByTms(tms)
if err == nil {
ToPushQueue(stCount.Strategy, tms, pointsCount.TagstringMap)
} else {
logger.Errorf("get by tms [%d] error : %v", tms, err)
}
stCount.DeleteTms(tms)
}
}
}
time.Sleep(time.Second * time.Duration(WorkerConfig.PushInterval))
}
}
func tmsNeedPush(tms int64, filePath string, step int64, waitPush int) bool {
latest, delay, found := GetLatestTmsAndDelay(filePath)
logger.Debugf("filepath:%s tms:%d latest tms:%d delay:%d", filePath, tms, latest, delay)
if !found {
return true
}
// 为解决日志时间戳乱序的最大等待时间, hard code
// delay == 0时, 不用额外等待, 进而提高时效性
if delay > 0 {
var maxDelay int64
if step <= 10 {
maxDelay = step * 3
} else if step > 10 && step <= 30 {
maxDelay = step * 2
} else {
maxDelay = step
}
if delay > maxDelay {
delay = maxDelay
}
}
waitTime := step
if waitPush != 0 {
waitTime = int64(waitPush)
}
//如果日志文件更新时间晚于一个采集周期,则进行补零
if latest < time.Now().Unix()-waitTime {
return true
}
if tms < AlignStepTms(step, latest-delay) {
return true
}
return false
}
// 这个参数是为了最大限度的对接
// pointMap的key是打平了的tagkv
func ToPushQueue(strategy *stra.Strategy, tms int64, pointMap map[string]*PointCounter) error {
for tagstring, PointCounter := range pointMap {
var value float64 = 0
switch strategy.Func {
case "cnt":
value = float64(PointCounter.Count)
case "avg":
if PointCounter.Count == 0 {
//这种就不用往监控推了
continue
} else {
avg := PointCounter.Sum / float64(PointCounter.Count)
value = getPrecision(avg, strategy.Degree)
}
case "sum":
value = PointCounter.Sum
case "max":
value = PointCounter.Max
case "min":
value = PointCounter.Min
default:
logger.Errorf("Strategy Func Error: %s ", strategy.Func)
return fmt.Errorf("Strategy Func Error: %s ", strategy.Func)
}
var tags map[string]string
if tagstring == "null" {
tags = make(map[string]string, 0)
} else {
tags = dataobj.DictedTagstring(tagstring)
}
if math.IsNaN(value) {
continue
}
tmpPoint := &dataobj.MetricValue{
Metric: strategy.Name,
Endpoint: identity.Identity,
ValueUntyped: value,
Timestamp: tms,
Step: strategy.Interval,
TagsMap: tags,
CounterType: "GAUGE",
}
//metric.MetricPushDelay(tms)
pushQueue <- tmpPoint
}
return nil
}
func postToCollector(paramPoints []*dataobj.MetricValue) {
// 按照时间戳分组发送
tsPsMap := make(map[int64][]*dataobj.MetricValue)
for _, p := range paramPoints {
if _, exist := tsPsMap[p.Timestamp]; !exist {
tsPsMap[p.Timestamp] = make([]*dataobj.MetricValue, 0)
}
tsPsMap[p.Timestamp] = append(tsPsMap[p.Timestamp], p)
}
var tsps tsPs
for ts, ps := range tsPsMap {
tsps = append(tsps, _tsPs{ts: ts, ps: ps})
}
sort.Sort(tsps)
for _, ps := range tsps {
funcs.Push(ps.ps)
// 1000ms是经验值
// 对于10G/小时的数据量+异步落盘的场景, 产生的结果友好一些
time.Sleep(time.Millisecond * 1000)
}
}
func getPrecision(num float64, degree int64) float64 {
tmpFloat := num * float64(math.Pow10(int(degree)))
tmpInt := int(tmpFloat + 0.5)
return float64(tmpInt) / float64(math.Pow10(int(degree)))
}
type _tsPs struct {
ts int64
ps []*dataobj.MetricValue
}
type tsPs []_tsPs
func (tp tsPs) Len() int { return len(tp) }
func (tp tsPs) Swap(i, j int) { tp[i], tp[j] = tp[j], tp[i] }
func (tp tsPs) Less(i, j int) bool { return tp[i].ts < tp[j].ts }

View File

@ -0,0 +1,329 @@
package worker
import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
"sync/atomic"
"time"
"github.com/didi/nightingale/src/modules/collector/log/strategy"
"github.com/didi/nightingale/src/modules/collector/stra"
"github.com/toolkits/pkg/logger"
)
type callbackHandler func(int64, int64)
//单个worker对象
type Worker struct {
FilePath string
Counter int64
LatestTms int64 //正在处理的单条日志时间
Delay int64 //时间戳乱序差值, 每个worker独立更新
Close chan struct{}
Stream chan string
Mark string //标记该worker信息方便打log及上报自监控指标, 追查问题
Analyzing bool //标记当前Worker状态是否在分析中,还是空闲状态
Callback callbackHandler
}
//worker组
type WorkerGroup struct {
WorkerNum int
LatestTms int64 //日志文件最新处理的时间戳
MaxDelay int64 //日志文件存在的时间戳乱序最大差值
ResetTms int64 //maxDelay上次重置的时间
Workers []*Worker
TimeFormatStrategy string
}
func (this WorkerGroup) GetLatestTmsAndDelay() (tms int64, delay int64) {
return this.LatestTms, this.MaxDelay
}
func (this *WorkerGroup) SetLatestTmsAndDelay(tms int64, delay int64) {
latest := atomic.LoadInt64(&this.LatestTms)
if latest < tms {
swapped := atomic.CompareAndSwapInt64(&this.LatestTms, latest, tms)
if swapped {
logger.Debugf("[work group:%s][set latestTms:%d]", this.Workers[0].Mark, tms)
}
}
if delay == 0 {
return
}
newest := atomic.LoadInt64(&this.MaxDelay)
if newest < delay {
atomic.CompareAndSwapInt64(&this.MaxDelay, newest, delay)
}
}
/*
* filepath和stream依赖外部其他的都自己创建
*/
func NewWorkerGroup(filePath string, stream chan string, st *stra.Strategy) *WorkerGroup {
wokerNum := WorkerConfig.WorkerNum
wg := &WorkerGroup{
WorkerNum: wokerNum,
Workers: make([]*Worker, 0),
}
logger.Infof("new worker group, [file:%s][worker_num:%d]", filePath, wokerNum)
for i := 0; i < wg.WorkerNum; i++ {
mark := fmt.Sprintf("[worker][file:%s][num:%d][id:%d]", filePath, wokerNum, i)
w := Worker{}
w.Close = make(chan struct{})
w.FilePath = filePath
w.Stream = stream
w.Mark = mark
w.Analyzing = false
w.Counter = 0
w.LatestTms = 0
w.Delay = 0
w.Callback = wg.SetLatestTmsAndDelay
wg.Workers = append(wg.Workers, &w)
}
return wg
}
func (wg *WorkerGroup) Start() {
for _, worker := range wg.Workers {
worker.Start()
}
}
func (wg *WorkerGroup) Stop() {
for _, worker := range wg.Workers {
worker.Stop()
}
}
func (wg *WorkerGroup) ResetMaxDelay() {
// 默认1天重置一次
ts := time.Now().Unix()
if ts-wg.ResetTms > 86400 {
wg.ResetTms = ts
atomic.StoreInt64(&wg.MaxDelay, 0)
}
}
func (w *Worker) Start() {
go func() {
w.Work()
}()
}
func (w *Worker) Stop() {
close(w.Close)
}
func (w *Worker) Work() {
defer func() {
if reason := recover(); reason != nil {
logger.Infof("%s -- worker quit: panic reason: %v", w.Mark, reason)
} else {
logger.Infof("%s -- worker quit: normally", w.Mark)
}
}()
logger.Infof("worker starting...[%s]", w.Mark)
var anaCnt, anaSwp int64
analysClose := make(chan int, 0)
go func() {
for {
//休眠10s
select {
case <-analysClose:
return
case <-time.After(time.Second * 10):
}
a := anaCnt
//metric.MetricWorkerAnalysisNum(int(a - anaSwp))
logger.Debugf("analysis %d line in last 10s", a-anaSwp)
anaSwp = a
}
}()
for {
select {
case line := <-w.Stream:
w.Analyzing = true
anaCnt = anaCnt + 1
w.analysis(line)
w.Analyzing = false
case <-w.Close:
analysClose <- 0
return
}
}
}
//内部的分析方法
//轮全局的规则列表
//单次遍历
func (w *Worker) analysis(line string) {
defer func() {
if err := recover(); err != nil {
logger.Infof("%s[analysis panic] : %v", w.Mark, err)
}
}()
sts := strategy.GetAll()
for _, strategy := range sts {
if strategy.FilePath == w.FilePath && strategy.ParseSucc {
analyspoint, err := w.producer(line, strategy)
if err != nil {
log := fmt.Sprintf("%s[producer error][sid:%d] : %v", w.Mark, strategy.ID, err)
//sample_log.Error(log)
logger.Error(log)
continue
} else {
if analyspoint != nil {
toCounter(analyspoint, w.Mark)
}
}
}
}
}
func (w *Worker) producer(line string, strategy *stra.Strategy) (*AnalysPoint, error) {
defer func() {
if err := recover(); err != nil {
logger.Errorf("%s[producer panic] : %v", w.Mark, err)
}
}()
var reg *regexp.Regexp
_, timeFormat := stra.GetPatAndTimeFormat(strategy.TimeFormat)
reg = strategy.TimeReg
t := reg.FindString(line)
if len(t) <= 0 {
return nil, errors.New(fmt.Sprintf("cannot get timestamp:[sname:%s][sid:%d][timeFormat:%v]", strategy.Name, strategy.ID, timeFormat))
}
// 如果没有年,需添加当前年
// 需干掉内部的多于空格, 如Dec 7,有的有一个空格,有的有两个,这里统一替换成一个
if timeFormat == "Jan 2 15:04:05" || timeFormat == "0102 15:04:05" {
timeFormat = fmt.Sprintf("2006 %s", timeFormat)
t = fmt.Sprintf("%d %s", time.Now().Year(), t)
reg := regexp.MustCompile(`\s+`)
rep := " "
t = reg.ReplaceAllString(t, rep)
}
// [风险]统一使用东八区
// loc, err := time.LoadLocation("Asia/Shanghai")
loc := time.FixedZone("CST", 8*3600)
tms, err := time.ParseInLocation(timeFormat, t, loc)
if err != nil {
return nil, err
}
tmsUnix := tms.Unix()
if tmsUnix > time.Now().Unix() {
logger.Debugf("%s[illegal timestamp][id:%d][tmsUnix:%d][current:%d]",
w.Mark, strategy.ID, tmsUnix, time.Now().Unix())
return nil, errors.New("illegal timestamp, greater than current")
}
// 更新worker的时间戳和乱序差值
// 如有必要, 更新上层group的时间戳和乱序差值
updateLatest := false
delay := int64(0)
if w.LatestTms < tmsUnix {
updateLatest = true
w.LatestTms = tmsUnix
} else if w.LatestTms > tmsUnix {
logger.Debugf("%s[timestamp disorder][id:%d][latest:%d][producing:%d]",
w.Mark, strategy.ID, w.LatestTms, tmsUnix)
delay = w.LatestTms - tmsUnix
}
if updateLatest || delay > 0 {
w.Callback(tmsUnix, delay)
}
//处理用户正则
var patternReg, excludeReg *regexp.Regexp
var value float64
patternReg = strategy.PatternReg
if patternReg != nil {
v := patternReg.FindStringSubmatch(line)
var vString string
if v != nil && len(v) != 0 {
if len(v) > 1 {
vString = v[1]
} else {
vString = ""
}
value, err = strconv.ParseFloat(vString, 64)
if err != nil {
value = math.NaN()
}
} else {
//外边匹配err之后要确保返回值不是nil再推送至counter
//正则有表达式,没匹配到,直接返回
return nil, nil
}
} else {
value = math.NaN()
}
//处理exclude
excludeReg = strategy.ExcludeReg
if excludeReg != nil {
v := excludeReg.FindStringSubmatch(line)
if v != nil && len(v) != 0 {
//匹配到exclude了需要返回
return nil, nil
}
}
//处理tag 正则
tag := map[string]string{}
for tagk, tagv := range strategy.Tags {
var regTag *regexp.Regexp
regTag, ok := strategy.TagRegs[tagk]
if !ok {
logger.Errorf("%s[get tag reg error][sid:%d][tagk:%s][tagv:%s]", w.Mark, strategy.ID, tagk, tagv)
return nil, nil
}
t := regTag.FindStringSubmatch(line)
if t != nil && len(t) > 1 {
tag[tagk] = t[1]
} else {
return nil, nil
}
}
ret := &AnalysPoint{
StrategyID: strategy.ID,
Value: value,
Tms: tms.Unix(),
Tags: tag,
}
return ret, nil
}
//将解析数据给counter
func toCounter(analyspoint *AnalysPoint, mark string) {
if err := PushToCount(analyspoint); err != nil {
logger.Errorf("%s push to counter error: %v", mark, err)
}
}

View File

@ -0,0 +1,26 @@
package worker
import (
"fmt"
"testing"
"time"
)
func TestWorkerStart(t *testing.T) {
c := make(chan string, 10)
go func() {
for i := 0; i < 1000; i++ {
for j := 0; j < 10; j++ {
c <- fmt.Sprintf("test--%d--%d", i, j)
}
fmt.Println()
time.Sleep(time.Second * 1)
}
}()
wg := NewWorkerGroup("test", c)
wg.Start()
time.Sleep(10 * time.Second)
wg.Stop()
time.Sleep(1 * time.Second)
}

View File

@ -0,0 +1,65 @@
linux 服务器基础资源采集agent
## 功能
系统指标采集
## 接口
#### 上报数据
```
POST /api/push
request body:
// endpoint 可以填ip或者hostname, 如果ip是在运维平台是唯一表示, 那就填ip, hostname类同
// step 为监控指标的采集周期
// tags 监控指标的额外描述, a=b的形式, 可以填多个, 多个用逗号隔开, 比如 group=devops,module=api
[
{
"metric":"qps",
"endpoint":"hostname",
"timestamp":1559733442,
"step":10,
"value":1,
"tags":""
}
]
response body:
{
"dat": "ok"
"err": "",
}
```
-------
#### 获取已生效的采集策略
```
GET /api/stra
response body:
{
"dat": [
{
"collect_type": "port",
"comment": "test",
"created": "2019-06-05T18:52:58+08:00",
"creator": "root",
"id": 1,
"last_updated": "2019-06-17T15:46:06+08:00",
"last_updator": "root",
"name": "test",
"nid": 2,
"port": 8047,
"step": 10,
"tags": "port=8047,service=tsdb",
"timeout": 3
}
],
"err": ""
}
```

View File

@ -0,0 +1,81 @@
package stra
import (
"fmt"
"math/rand"
"time"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/net/httplib"
"github.com/didi/nightingale/src/model"
"github.com/didi/nightingale/src/toolkits/address"
"github.com/didi/nightingale/src/toolkits/identity"
)
func GetCollects() {
if !StraConfig.Enable {
return
}
detect()
go loopDetect()
}
func loopDetect() {
t1 := time.NewTicker(time.Duration(StraConfig.Interval) * time.Second)
for {
<-t1.C
detect()
}
}
func detect() {
c, err := GetCollectsRetry()
if err != nil {
logger.Errorf("get collect err:%v", err)
return
}
Collect.Update(&c)
}
type CollectResp struct {
Dat model.Collect `json:"dat"`
Err string `json:"err"`
}
func GetCollectsRetry() (model.Collect, error) {
count := len(address.GetHTTPAddresses("monapi"))
var resp CollectResp
var err error
for i := 0; i < count; i++ {
resp, err = getCollects()
if err == nil {
if resp.Err != "" {
err = fmt.Errorf(resp.Err)
continue
}
return resp.Dat, err
}
}
return resp.Dat, err
}
func getCollects() (CollectResp, error) {
addrs := address.GetHTTPAddresses("monapi")
i := rand.Intn(len(addrs))
addr := addrs[i]
var res CollectResp
var err error
url := fmt.Sprintf("http://%s%s%s", addr, StraConfig.Api, identity.Identity)
err = httplib.Get(url).SetTimeout(time.Duration(StraConfig.Timeout) * time.Millisecond).ToJSON(&res)
if err != nil {
err = fmt.Errorf("get collects from remote failed, error:%v", err)
}
return res, err
}

View File

@ -0,0 +1,24 @@
package stra
import "github.com/didi/nightingale/src/model"
var StraConfig StraSection
var Collect model.Collect
type StraSection struct {
Enable bool `yaml:"enable"`
Interval int `yaml:"interval"`
Api string `yaml:"api"`
Timeout int `yaml:"timeout"`
PortPath string `yaml:"portPath"`
ProcPath string `yaml:"procPath"`
LogPath string `yaml:"logPath"`
}
///api/portal/collects/%s
func Init(stra StraSection) {
StraConfig = stra
GetCollects()
}

View File

@ -0,0 +1,273 @@
package stra
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"github.com/didi/nightingale/src/model"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
)
type Strategy struct {
ID int64 `json:"id"`
Name string `json:"name"` //监控策略名
FilePath string `json:"file_path"` //文件路径
TimeFormat string `json:"time_format"` //时间格式
Pattern string `json:"pattern"` //表达式
Exclude string `json:"-"`
MeasurementType string `json:"measurement_type"`
Interval int64 `json:"interval"` //采集周期
Tags map[string]string `json:"tags"`
Func string `json:"func"` //采集方式max/min/avg/cnt
Degree int64 `json:"degree"`
Unit string `json:"unit"`
Comment string `json:"comment"`
Creator string `json:"creator"`
SrvUpdated string `json:"updated"`
LocalUpdated int64 `json:"-"`
TimeReg *regexp.Regexp `json:"-"`
PatternReg *regexp.Regexp `json:"-"`
ExcludeReg *regexp.Regexp `json:"-"`
TagRegs map[string]*regexp.Regexp `json:"-"`
ParseSucc bool `json:"parse_succ"`
}
func GetLogCollects() []*Strategy {
var stras []*Strategy
if StraConfig.Enable {
strasMap := Collect.GetLogConfig()
for _, s := range strasMap {
stra := ToStrategy(s)
stras = append(stras, stra)
}
}
//从文件中读取
stras = append(stras, GetCollectFromFile(StraConfig.LogPath)...)
parsePattern(stras)
updateRegs(stras)
return stras
}
func GetCollectFromFile(logPath string) []*Strategy {
logger.Info("get collects from local file")
var stras []*Strategy
files, err := file.FilesUnder(logPath)
if err != nil {
logger.Error(err)
return stras
}
//扫描文件采集配置
for _, f := range files {
err := checkName(f)
if err != nil {
logger.Warningf("read file name err:%s %v", f, err)
continue
}
stra := Strategy{}
b, err := file.ToBytes(logPath + "/" + f)
if err != nil {
logger.Warningf("read file name err:%s %v", f, err)
continue
}
err = json.Unmarshal(b, &stra)
if err != nil {
logger.Warningf("read file name err:%s %v", f, err)
continue
}
//todo 配置校验
stra.ID = genStraID(stra.Name, string(b))
stras = append(stras, &stra)
}
return stras
}
func genStraID(name, body string) int64 {
var id int64
all := name + body
if len(all) < 1 {
return id
}
id = int64(all[0])
for i := 1; i < len(all); i++ {
id += int64(all[i])
id += int64(all[i] - all[i-1])
}
id += 1000000 //避免与web端配置的id冲突
return id
}
func ToStrategy(p *model.LogCollect) *Strategy {
s := Strategy{}
s.ID = p.Id
s.Name = p.Name
s.FilePath = p.FilePath
s.TimeFormat = p.TimeFormat
s.Pattern = p.Pattern
s.MeasurementType = p.CollectType
s.Interval = int64(p.Step)
s.Tags = DeepCopyStringMap(p.Tags)
s.Func = p.Func
s.Degree = int64(p.Degree)
s.Unit = p.Unit
s.Comment = p.Comment
s.Creator = p.Creator
s.SrvUpdated = p.LastUpdated.String()
s.LocalUpdated = p.LocalUpdated
return &s
}
func DeepCopyStringMap(p map[string]string) map[string]string {
r := make(map[string]string, len(p))
for k, v := range p {
r[k] = v
}
return r
}
const PATTERN_EXCLUDE_PARTITION = "```EXCLUDE```"
func parsePattern(strategys []*Strategy) {
for _, st := range strategys {
patList := strings.Split(st.Pattern, PATTERN_EXCLUDE_PARTITION)
if len(patList) == 1 {
st.Pattern = strings.TrimSpace(st.Pattern)
continue
} else if len(patList) >= 2 {
st.Pattern = strings.TrimSpace(patList[0])
st.Exclude = strings.TrimSpace(patList[1])
continue
} else {
logger.Errorf("bad pattern to parse : [%s]", st.Pattern)
}
}
}
func updateRegs(strategys []*Strategy) {
for _, st := range strategys {
st.TagRegs = make(map[string]*regexp.Regexp, 0)
st.ParseSucc = false
//更新时间正则
pat, _ := GetPatAndTimeFormat(st.TimeFormat)
reg, err := regexp.Compile(pat)
if err != nil {
logger.Errorf("compile time regexp failed:[sid:%d][format:%s][pat:%s][err:%v]", st.ID, st.TimeFormat, pat, err)
continue
}
st.TimeReg = reg //拿到时间正则
if len(st.Pattern) == 0 && len(st.Exclude) == 0 {
logger.Errorf("pattern and exclude are all empty, sid:[%d]", st.ID)
continue
}
//更新pattern
if len(st.Pattern) != 0 {
reg, err = regexp.Compile(st.Pattern)
if err != nil {
logger.Errorf("compile pattern regexp failed:[sid:%d][pat:%s][err:%v]", st.ID, st.Pattern, err)
continue
}
st.PatternReg = reg
}
//更新exclude
if len(st.Exclude) != 0 {
reg, err = regexp.Compile(st.Exclude)
if err != nil {
logger.Errorf("compile exclude regexp failed:[sid:%d][pat:%s][err:%v]", st.ID, st.Exclude, err)
continue
}
st.ExcludeReg = reg
}
//更新tags
for tagk, tagv := range st.Tags {
reg, err = regexp.Compile(tagv)
if err != nil {
logger.Errorf("compile tag failed:[sid:%d][pat:%s][err:%v]", st.ID, st.Exclude, err)
continue
}
st.TagRegs[tagk] = reg
}
st.ParseSucc = true
}
}
func checkName(f string) (err error) {
if !strings.Contains(f, "log.") {
err = fmt.Errorf("name illege not contain log. %s", f)
return
}
arr := strings.Split(f, ".")
if len(arr) < 3 {
err = fmt.Errorf("name illege %s len:%d len < 3 ", f, len(arr))
return
}
if arr[len(arr)-1] != "json" {
err = fmt.Errorf("name illege %s not json file", f)
return
}
return
}
func GetPatAndTimeFormat(tf string) (string, string) {
var pat, timeFormat string
switch tf {
case "dd/mmm/yyyy:HH:MM:SS":
pat = `([012][0-9]|3[01])/[JFMASOND][a-z]{2}/(2[0-9]{3}):([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02/Jan/2006:15:04:05"
case "dd/mmm/yyyy HH:MM:SS":
pat = `([012][0-9]|3[01])/[JFMASOND][a-z]{2}/(2[0-9]{3})\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02/Jan/2006 15:04:05"
case "yyyy-mm-ddTHH:MM:SS":
pat = `(2[0-9]{3})-(0[1-9]|1[012])-([012][0-9]|3[01])T([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006-01-02T15:04:05"
case "dd-mmm-yyyy HH:MM:SS":
pat = `([012][0-9]|3[01])-[JFMASOND][a-z]{2}-(2[0-9]{3})\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "02-Jan-2006 15:04:05"
case "yyyy-mm-dd HH:MM:SS":
pat = `(2[0-9]{3})-(0[1-9]|1[012])-([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006-01-02 15:04:05"
case "yyyy/mm/dd HH:MM:SS":
pat = `(2[0-9]{3})/(0[1-9]|1[012])/([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "2006/01/02 15:04:05"
case "yyyymmdd HH:MM:SS":
pat = `(2[0-9]{3})(0[1-9]|1[012])([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "20060102 15:04:05"
case "mmm dd HH:MM:SS":
pat = `[JFMASOND][a-z]{2}\s+([1-9]|[1-2][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "Jan 2 15:04:05"
case "mmdd HH:MM:SS":
pat = `(0[1-9]|1[012])([012][0-9]|3[01])\s([01][0-9]|2[0-4])(:[012345][0-9]){2}`
timeFormat = "0102 15:04:05"
default:
logger.Errorf("match time pac failed : [timeFormat:%s]", tf)
return "", ""
}
return pat, timeFormat
}

View File

@ -0,0 +1,85 @@
package stra
import (
"fmt"
"strconv"
"strings"
"github.com/didi/nightingale/src/model"
"github.com/didi/nightingale/src/toolkits/str"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
)
func NewPortCollect(port, step int, tags string) *model.PortCollect {
return &model.PortCollect{
CollectType: "port",
Port: port,
Step: step,
Tags: tags,
}
}
func GetPortCollects() map[int]*model.PortCollect {
portPath := StraConfig.PortPath
ports := make(map[int]*model.PortCollect)
if StraConfig.Enable {
ports = Collect.GetPorts()
for _, p := range ports {
tagsMap := str.DictedTagstring(p.Tags)
tagsMap["port"] = strconv.Itoa(p.Port)
p.Tags = str.SortedTags(tagsMap)
}
}
files, err := file.FilesUnder(portPath)
if err != nil {
logger.Error(err)
return ports
}
//扫描文件采集配置
for _, f := range files {
port, step, err := parseName(f)
if err != nil {
logger.Warning(err)
continue
}
service, err := file.ToTrimString(StraConfig.PortPath + "/" + f)
if err != nil {
logger.Warning(err)
continue
}
tags := fmt.Sprintf("port=%s,service=%s", strconv.Itoa(port), service)
p := NewPortCollect(port, step, tags)
ports[p.Port] = p
}
return ports
}
func parseName(name string) (port, step int, err error) {
arr := strings.Split(name, "_")
if len(arr) < 2 {
err = fmt.Errorf("name is illegal %s, split _ < 2", name)
return
}
step, err = strconv.Atoi(arr[0])
if err != nil {
err = fmt.Errorf("name is illegal %s %v", name, err)
return
}
port, err = strconv.Atoi(arr[1])
if err != nil {
err = fmt.Errorf("name is illegal %s %v", name, err)
return
}
return
}

View File

@ -0,0 +1,82 @@
package stra
import (
"fmt"
"strconv"
"strings"
"github.com/didi/nightingale/src/model"
"github.com/didi/nightingale/src/toolkits/str"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
)
func NewProcCollect(method, name, tags string, step int) *model.ProcCollect {
return &model.ProcCollect{
CollectType: "proc",
CollectMethod: method,
Target: name,
Step: step,
Tags: tags,
}
}
func GetProcCollects() map[string]*model.ProcCollect {
procs := make(map[string]*model.ProcCollect)
if StraConfig.Enable {
procs = Collect.GetProcs()
for _, p := range procs {
tagsMap := str.DictedTagstring(p.Tags)
tagsMap["target"] = p.Target
p.Tags = str.SortedTags(tagsMap)
}
}
files, err := file.FilesUnder(StraConfig.ProcPath)
if err != nil {
logger.Error(err)
return procs
}
//扫描文件采集配置
for _, f := range files {
method, name, step, err := parseProcName(f)
if err != nil {
logger.Warning(err)
continue
}
service, err := file.ToTrimString(StraConfig.ProcPath + "/" + f)
if err != nil {
logger.Warning(err)
continue
}
tags := fmt.Sprintf("target=%s,service=%s", name, service)
p := NewProcCollect(method, name, tags, step)
procs[p.Name] = p
}
return procs
}
func parseProcName(fname string) (method string, name string, step int, err error) {
arr := strings.Split(fname, "_")
if len(arr) < 3 {
err = fmt.Errorf("name is illegal %s, split _ < 3", fname)
return
}
step, err = strconv.Atoi(arr[0])
if err != nil {
err = fmt.Errorf("name is illegal %s %v", fname, err)
return
}
method = arr[1]
name = strings.Join(arr[2:len(arr)], "_")
return
}

View File

@ -0,0 +1,27 @@
package sys
type SysSection struct {
IfacePrefix []string `yaml:"ifacePrefix"`
MountPoint []string `yaml:"mountPoint"`
MountIgnorePrefix []string `yaml:"mountIgnorePrefix"`
IgnoreMetrics []string `yaml:"ignoreMetrics"`
IgnoreMetricsMap map[string]struct{} `yaml:"-"`
NtpServers []string `yaml:"ntpServers"`
Plugin string `yaml:"plugin"`
Interval int `yaml:"interval"`
Timeout int `yaml:"timeout"`
}
var Config SysSection
func Init(s SysSection) {
Config = s
l := len(Config.IgnoreMetrics)
m := make(map[string]struct{}, l)
for i := 0; i < l; i++ {
m[Config.IgnoreMetrics[i]] = struct{}{}
}
Config.IgnoreMetricsMap = m
}

View File

@ -0,0 +1,11 @@
package funcs
import (
"github.com/didi/nightingale/src/dataobj"
)
func CollectorMetrics() []*dataobj.MetricValue {
return []*dataobj.MetricValue{
GaugeValue("proc.agent.alive", 1),
}
}

View File

@ -0,0 +1,31 @@
package funcs
import (
"strings"
"github.com/didi/nightingale/src/dataobj"
)
func NewMetricValue(metric string, val interface{}, dataType string, tags ...string) *dataobj.MetricValue {
mv := dataobj.MetricValue{
Metric: metric,
ValueUntyped: val,
CounterType: dataType,
}
size := len(tags)
if size > 0 {
mv.Tags = strings.Join(tags, ",")
}
return &mv
}
func GaugeValue(metric string, val interface{}, tags ...string) *dataobj.MetricValue {
return NewMetricValue(metric, val, "GAUGE", tags...)
}
func CounterValue(metric string, val interface{}, tags ...string) *dataobj.MetricValue {
return NewMetricValue(metric, val, "COUNTER", tags...)
}

Some files were not shown because too many files have changed in this diff Show More