1.前言

  本来想做一个比较完善的监控平台,只需要做少许改动就可以直接拿来用,但是在做的过程中发现要实现这个目标所需的工作量太大,而当前的工作中对其需求又不是特别明显。所以就退而求其次,做了一个类似教程系统的东西。在这个系统中,你应该可以找到做一个监控系统所需要的大部分技术点,而它的真正意义就在于其打通了整个数据流转的环节。

  先上一个效果图:

       

       因为只是做了一个简单的验证,所以只有内存曲线有变动,CPU使用情况没变化。X轴的坐标有时间重复,是由于数据里面有相同的IP地址造成的。

       磁盘使用情况、硬盘/网络IO的实现过程和内存、CPU类似,所以就没有具体实现。需要特别说明的是: 数据采用的拉的方式,所以目标机只能执行shell命令,磁盘使用情况的处理,尤其是IO情况的处理,比较麻烦。但是其好处是不需要在目标机器上安部署任何东西,只需要一个有权限执行shell命令的账号即可。如果不介意部署麻烦问题,可以在每个目标机上安装psutil包,可以很方便的获取系统信息,但是我看了看大部分系统自带的python都是2.7版本的,而psutil包需要3.0版本以上的。

2.遇到的问题

  把开发过程中遇到的问题放到前面说,是为了避免大家遇到相同的问题,从而走了弯路。

  2.1 flask-sqlalchemy

  这个是我最想吐槽的地方:如果一个ORM框架的使用成本和排查错误的成本远远超过了直接使用sql语句的成本,那么你还有啥存在的意义?

  (1)from flask_sqlalchemy import SQLAlchemy  #python3 的写法

  (2)安装2.1版本,最新2.3有问题,2.2不确定。使用pip按照的方法为:pip install flask-sqlalchemy==2.1

  (3)2.1版本应该也有问题,因为app、models、database三个功能类文件分开的话,只有第一次读取数据是从数据库读取,剩下的读取过程好像都是从内存中读取的。但是如果把所有的内容都放到app文件中就不存在这个问题(这就是我把所有内容都放到一个文件的原因)。这也是目前我能找到的唯一的一个解决方式,如果有其他方式可以解决这个问题,请大家在下面留言告诉我,谢谢。

  针对这个问题,我也咨询了几位群里朋友的意见,他们的建议就是最好使用sql直接操作数据库,不仅方便灵活可控,还可以减少框架本身的缺陷引起的莫名其妙的问题。这点我是深有同感,第一次是使用pip安装的flask-sqlalchemy 2.3,一堆莫名其妙的问题,查来查去原来是版本问题引起的。因为是使用pip安装的,所以当时没有考虑版本的问题。

  2.2 关于测试机器IP的问题

  如果测试过程中,目标机器就一个,IP地址也就一个,这种情况下频繁读取该机器的系统信息,由于机器的保护机制耗费的时间会阶段性变长,建议多连接几台机器测试或者挂几个VIP试试。

  2.3 关于提高的SQL脚本

       我把整个项目文件上传到了github上,项目中包含的sql脚本如果是在windows下的mysql中执行,需要把create语句整理成一行,否则会创建失败。linux下的mysql中没有这个问题。

3.系统组成及主要工具包

         

4.技术点详解

完整的项目及文件内容见:https://github.com/lichao1217/woodpecker

4.1 获取数据部分

  (1)通过SSH的方式连接目标机器(完整代码:\woodpecker\wpgd\serverconn.py)

  1. import paramiko #引用封装SSH连接方式的工具包
  2. #返回一个连接
  3. #host:ip地址 username:用户名 pwd 密码
  4. def get_connection(host,username,pwd):
  5. try:
  6. client = paramiko.SSHClient()
  7. client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  8. client.connect(host,22,username,pwd)
  9. return client
  10. except Exception as e:
  11. return None

View Code

      (2)使用configparser工具包读取连接mysql数据库的配置,并返回一个mysql连接(完整代码:\woodpecker\wpgd\DBconn.py)

  1. def __get_dbconn(self):
  2. try:
  3. _config = configparser.ConfigParser()
  4. _config.read(self._configPath)
  5. host = _config.get("dbconfig","host") #主机IP地址
  6. dbusername = _config.get("dbconfig","dbusername") #数据库用户名
  7. dbuserpwd = _config.get("dbconfig","dbuserpwd") #数据库用户密码
  8. dbname = _config.get("dbconfig","dbname") #数据库名称
  9. db = pymysql.connect(host,dbusername,dbuserpwd,dbname)
  10. return db
  11. except Exception as e:
  12. return None

View Code

      (3)使用pymysql读取数据(完整代码:\woodpecker\wpgd\DBconn.py)

  1. def get_serverList(self,sqltext):
  2. db = self.__get_dbconn()
  3. if db is None:
  4. return 0 #0表示连接数据库失败
  5. else :
  6. try:
  7. cursor = db.cursor()
  8. cursor.execute(sqltext) #执行sql语句
  9. results = cursor.fetchall() #读取全部结果
  10. return results
  11. except Exception as e:
  12. return -1 # -1 表示读取数据失败,有可能SQL语句不对
  13. finally :
  14. db.close()

View Code

4.2读取数据部分(代码都在app.py 和index.html中)

  (1)flask-sqlalchemy连接mysql

  1. from flask_sqlalchemy import SQLAlchemy #python3 的写法
  2. app = Flask(__name__)
  3. app.config[\'SQLALCHEMY_DATABASE_URI\'] = \'mysql+pymysql://woodpecker:woodpecker@localhost/woodpecker\'
  4. app.config[\'SQLALCHEMY_TRACK_MODIFICATIONS\'] = False #是否
  5. app.config[\'SQLALCHEMY_COMMIT_ON_TEARDOWN\'] = True
  6. db = SQLAlchemy(app)

View Code

    详细配置参数见:http://www.pythondoc.com/flask-sqlalchemy/config.html

  (2)models定义部分

  这里只有2点需要注意:

  第一:如果定义的类名和表名不一致,需要使用__tablename__参数说明(__是双下划线),例如: __tablename__ = \’t_servers\’

       第二:表必须要有主键

  (3)无参数读取数据库数据

         html部分:

  1. <ul id="serverlist" class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
  2. {% for group in groupList %}
  3. <li><a role="menuitem" href="#" >{{ group.groupname }}</a></li>
  4. <li class="divider"></li>
  5. {% else %}
  6. <li><a role="menuitem" href="#">读取服务器分组失败</a></li>
  7. {% endfor %}
  8. </ul>

View Code

       {{ group.groupname }} 这是flask表示变量的方式,并且上述代码中的class都是在bootstrap中定义的。

         python部分:

  1. def index():
  2. groupList = Group.query.all()
  3. return render_template(\'index.html\', groupList=groupList)

View Code

      Group.query.all()   这是flask-sqlalchemy查询数据的方式

    (4)页面传参数到后台读取数据

    html部分(ajax):      

  1. var data = {\'ip\':ip}; //ip是传到后台的参数
  2. $.ajax({
  3. type:\'post\',
  4. async:false,
  5. url:"/get_sys_info", //接收参数的后台路由函数
  6. data:data,
  7. success:function (result) {
  8. drawchart(result,ip) //此函数是接收到返回的结果后做前台处理的
  9. ;}
  10. });

View Code

    python部分:

  1. @app.route(\'/get_sys_info\', methods=[\'POST\',\'GET\'])
  2. def getSysInfo():
  3. ip = request.form.get("ip")
  4. .......

View Code

    (5)动态添加li元素

     有两种方式:

      第一种在页面装载过程中动态添加li元素及点击事件:

  1. //给服务器列表的下拉框每个li元素添加点击事件
  2. window.onload = function () {
  3. var obj_lis = document.getElementById("serverlist").getElementsByTagName("li");
  4. for(var i=0;i<obj_lis.length;i++)
  5. {
  6. obj_lis[i].onclick = function()
  7. {
  8. getiplist(this.innerText);
  9. }
  10. }
  11. }
  12. //根据选择的分组设置btn的现实,并读取当前分组下的ip列表
  13. function getiplist(groupname)
  14. {
  15. var btn=document.getElementById("dropdownMenu1");
  16. //直接给btn.innerText = groupname,表示下来的倒三角不显示
  17. btn.innerHTML=groupname+"<span class=\"caret\"></span>";
  18. getipList(groupname)
  19. }

View Code

     第二种当页面装载完成后点击页面元素局部刷新添加li元素及点击事件

  1. //添加ip地址到ul中
  2. function addIPToUL(data){
  3. delIPFromUL();
  4. var ip_str = String(data);
  5. var ips = ip_str.split(",");
  6. //alert(ips[0]);
  7. for(var i=0;i<ips.length;i++)
  8. {
  9. var li = document.createElement("li");
  10. li.setAttribute("class","list-group-item");
  11. li.innerText=ips[i];
  12. li.onclick=getSysInfo;
  13. document.getElementById("iplist").appendChild(li);
  14. }
  15. }

View Code

    (6)动态删除li元素

  1. //选择新分组之后需要清空当前的ip地址
  2. function delIPFromUL() {
  3. var obj_ul = document.getElementById("iplist");
  4. var obj_lis = document.getElementById("iplist").getElementsByTagName("li");
  5. var cnt = obj_lis.length;
  6. //alert(cnt);
  7. if(cnt>0) {
  8. for (var i=cnt-1;i>=0;i--){
  9. var m_li=obj_lis[i];
  10. obj_ul.removeChild(m_li);
  11. }
  12. }
  13. }

View Code

   (7)定时刷新函数

  1. setInterval(function () {
  2. //alert("开始执行");
  3. getSysInfo();
  4. }, 60000);

View Code

   这里需要说一下,定时函数中执行的函数最好紧跟着定时函数,否则容易识别不了。

    (8)时间格式转换

     js好像没有自带的时间转换格式函数,所以引入了moment.js来处理时间格式。

     (9)highcharts

     html部分:

  1. <div id="chart_cpu" style="height: 50%;width: 50%;background: white;float: left"></div>

View Code

    js部分:

  1. //CPU显示
  2. var options_cpu ={
  3. chart:{
  4. type:\'line\'
  5. },
  6. title:{
  7. text:\'CPU使用情况\'
  8. },
  9. subtitle:{
  10. text:ip
  11. },
  12. xAxis:{
  13. type:"datetime",
  14. dateTimeLabelFormat:{
  15. day:\'%H:%M\'
  16. },
  17. labels:{
  18. overflow:\'justify\'
  19. },
  20. categories:data["time"]
  21. },
  22. yAxis:{
  23. title:{
  24. text:\'百分比(%)\'
  25. }
  26. },
  27. legend:{
  28. layout: "vertical",
  29. align:\'left\',
  30. verticalAlign: "middle"
  31. },
  32. credits:{
  33. enabled:false
  34. },
  35. series:[{
  36. name:\'sy\',
  37. data:data["sy"]
  38. },
  39. {
  40. name:\'us\',
  41. data:data["us"]
  42. },
  43. {
  44. name:\'total\',
  45. data:data["totalcpu"]
  46. }]
  47. };
  48. $(\'#chart_cpu\').highcharts(options_cpu);

View Code

  python部分:

  1. @app.route(\'/get_sys_info\', methods=[\'POST\',\'GET\'])
  2. def getSysInfo():
  3. ip = request.form.get("ip")
  4. #倒序排列去除最近10条数据
  5. sysinfos = SysInfo.query.filter_by(serverip=ip.strip()).order_by(SysInfo.id.desc()).limit(10).all()
  6. #重新处理成正序
  7. sysinfolist = []
  8. for i in range(len(sysinfos)-1, 0, -1):
  9. sysinfolist.append(sysinfos[i])
  10. print(sysinfolist[0].id)
  11. cpudic = getCPUinfoList(sysinfolist)
  12. memdic = getMeminfoList(sysinfolist)
  13. sysinfodic = dict(cpudic, **memdic)
  14. sysinfodic = jsonify(sysinfodic)
  15. return sysinfodic

View Code

    (10) div排列

  1. <div id="mainchart" style="height: 80%;width:87%;background: #8c8c8c;position: absolute;margin-left: 13%">
  2. <div id="chart_cpu" style="height: 50%;width: 50%;background: white;float: left"></div>
  3. <div id="chart_mem" style="height: 50%;width: 50%;background:beige;float: right" ></div>
  4. <div id="chart_disk" style="height: 50%;width: 50%;background: beige;float: left;text-align: center;font-size: 30px">磁盘使用情况</div>
  5. <div id="chart_io" style="height: 50%;width: 50%;background:white;float: right;text-align: center;font-size: 30px" >硬盘/网络IO情况</div>
  6. </div>

View Code

5.总结

  按照上述提到的知识点就可以搭建出来一个自己的监控系统。需要完善的就是highcharts的显示问题了,这个可以参考下相关资料设置成自己满意的显示方式。  

  当然,由于本人水平有限,其中难免有不足的地方,欢迎大家提出来,多做交流,共同进步。

 

版权声明:本文为lc1217原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lc1217/p/8864993.html